From 80e8dec910cbf50d221250a4fa26c51a48c45dc2 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Mon, 27 Jul 2026 17:41:01 +0100 Subject: [PATCH] Align CLI docs with one cache interface --- INSTALLATION.md | 152 ++++++++++++++++++--------- README.md | 45 ++++---- docs/README.md | 23 ++++- docs/adapter-commands.md | 178 ++++++++++++++++++++++---------- docs/archive-mode.md | 28 +++++ docs/github-actions.md | 75 +++++++++----- docs/onboard.md | 19 +++- docs/proxy-mode.md | 78 -------------- docs/quick-start.md | 49 ++++++--- docs/tool-guides.md | 141 +++++++++---------------- install-web/install.sh | 28 +++++ install.sh | 28 +++++ scripts/test-installer-trust.sh | 7 ++ 13 files changed, 507 insertions(+), 344 deletions(-) delete mode 100644 docs/proxy-mode.md diff --git a/INSTALLATION.md b/INSTALLATION.md index efdb386a..74cc28b9 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -1,69 +1,117 @@ -# BoringCache CLI Installation +# BoringCache CLI Installation Setup -This repository publishes the installer and GitHub Release assets used by the -BoringCache CLI. The normal install command is: +This document describes how to set up the +`curl -sSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://install.boringcache.com/install.sh | sh` +installation method for the BoringCache CLI. The explicit no-cache headers ensure +Cloudflare (or any intermediary CDN) revalidates the script on each install. -```bash -curl -sSL https://install.boringcache.com/install.sh | sh -``` - -The installer always verifies `SHA256SUMS`. When `cosign` is available it also -verifies `SHA256SUMS.bundle` automatically. To make Sigstore verification -mandatory, install `cosign` first and run: +The installer always verifies every downloaded artifact against `SHA256SUMS`. +On macOS it installs both the universal CLI and the universal Xcode CAS adapter +beside it. When `cosign` is available it also verifies the signed checksum +bundle automatically. +For fail-closed Sigstore verification, install `cosign` first and run: ```bash curl -sSL https://install.boringcache.com/install.sh | BORINGCACHE_VERIFY_SIGNATURE=1 sh ``` -When testing the installer itself, use: +## Files Overview -```bash -curl -sSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://install.boringcache.com/install.sh | sh +- `install.sh` - The installation script that users will curl and execute +- `install-web/` - Static website files for the installation page +- `install-web/index.html` - Landing page with installation instructions and manual download links + +## Setup Instructions + +### 1. Domain Setup + +You'll need to configure `install.boringcache.com/install.sh` to serve the installation script. Here are a few options: + +#### Option A: GitHub Pages (Recommended) +1. Create a new repository `boringcache/install` +2. Upload `install.sh` and `install-web/` contents to the repository +3. Enable GitHub Pages to serve from the main branch +4. Configure custom domain `install.boringcache.com/install.sh` in repository settings +5. Add CNAME record in DNS: `install.boringcache.com/install.sh` → `boringcache.github.io` + +#### Option B: Cloudflare Pages +1. Connect the CLI repository to Cloudflare Pages +2. Set build command to copy installation files to output directory +3. Configure custom domain `install.boringcache.com/install.sh` + +#### Option C: Simple Static Hosting +Use any static file hosting service (Netlify, Vercel, etc.) to serve: +- Root path `/` serves `install.sh` with `Content-Type: text/plain` +- Path `/web` serves `install-web/index.html` + +### 2. Web Server Configuration + +The installation script should be served with proper headers: + +```nginx +# Nginx example +location = / { + add_header Content-Type text/plain; + try_files /install.sh =404; +} + +location /web/ { + try_files $uri $uri/ /install-web/index.html; +} ``` -The explicit no-cache headers ensure Cloudflare or another intermediary CDN -revalidates the script on each test install. +### 3. DNS Configuration -## Files +Add these DNS records: +``` +install.boringcache.com/install.sh CNAME your-hosting-provider.com +``` -- `install.sh` - installer script served by `install.boringcache.com` -- `install-web/` - static install page -- `README.md` and `docs/` - public CLI usage docs -- GitHub Releases - platform binaries and `SHA256SUMS` +### 4. SSL Certificate -## Testing +Ensure HTTPS is enabled since users will be downloading and executing scripts. -Test the script URL: +## Testing the Installation +### Test the script URL: ```bash curl -sSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" \ https://install.boringcache.com/install.sh ``` -Test the latest release asset URL: +Should return the installation script content. + +### Test the installation: +```bash +curl -sSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" \ + https://install.boringcache.com/install.sh | sh +``` + +Should detect platform, download appropriate binary, and install it. +### Test manual download links: ```bash +# Should redirect to latest release curl -I https://github.com/boringcache/cli/releases/latest/download/boringcache-linux-amd64 ``` ## Platform Support -Release assets currently target: +The installation script supports: -- Linux glibc: AMD64, ARM64 -- Linux musl: AMD64, ARM64 -- macOS universal -- Windows: AMD64, ARM64 +- **Linux**: AMD64, ARM64 +- **macOS**: Apple Silicon and Intel through universal binaries +- **Windows**: AMD64 (experimental, manual download recommended) ## Binary Names -The release workflow publishes: - +The GitHub release workflow creates these binaries: - `boringcache-linux-amd64` - `boringcache-linux-arm64` - `boringcache-linux-musl-amd64` - `boringcache-linux-musl-arm64` - `boringcache-macos-universal` +- `libboringcache_xcode_cas-macos-universal.dylib` - `boringcache-windows-amd64.exe` - `boringcache-windows-arm64.exe` - `SHA256SUMS` @@ -71,22 +119,34 @@ The release workflow publishes: ## Installation Locations -The script installs to the first writable location: - -1. `/usr/local/bin` with `sudo` when needed +The script installs the CLI to the first writable location. On macOS it also +installs `libboringcache_xcode_cas.dylib` in that same directory so +`boringcache xcode` works without a separate plugin setup: +1. `/usr/local/bin` (with sudo if needed) 2. `$HOME/.local/bin` 3. `$HOME/bin` -## Security Notes - -- Downloads use HTTPS. -- The installer always verifies the binary against `SHA256SUMS`. -- When `cosign` is available, the installer verifies `SHA256SUMS.bundle` - automatically; `BORINGCACHE_VERIFY_SIGNATURE=1` makes this fail closed. -- Sigstore verification accepts only the monorepo CLI release workflow on a - semantic-version tag or the checksum-repair workflow on `main`. -- The installer verifies the downloaded binary can run before finishing. -- Product source is maintained in the BoringCache monorepo; this public repo is - the distribution channel. -- Potential vulnerabilities should be reported through the private process in - [the security policy](.github/SECURITY.md), not a public issue. +## Security Considerations + +- Downloads over HTTPS only +- Provides warning about piping curl to shell +- Verifies the downloaded binary against `SHA256SUMS` +- Automatically verifies `SHA256SUMS.bundle` with Sigstore when `cosign` is + available; `BORINGCACHE_VERIFY_SIGNATURE=1` makes the signature mandatory +- Accepts checksum signatures only from the monorepo CLI release workflow on a + semantic-version tag or the checksum-repair workflow on `main` +- Shows installation path and PATH information +- Includes verification step after installation + +## Maintenance + +- Update `REPO` variable in `install.sh` if repository moves +- Update download links in `index.html` if repository moves +- Monitor GitHub API rate limits for release fetching + +## Future Enhancements + +- Support for installing specific versions +- Package manager installations (brew, apt, etc.) +- Windows MSI installer +- Auto-update mechanism in the CLI itself diff --git a/README.md b/README.md index 71babeb7..e278bee8 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,15 @@ cd your-project boringcache onboard ``` -The installer always verifies the release checksum. If `cosign` is installed, -it also verifies the signed checksum bundle automatically. To require the -signature and fail closed, run: - -```bash -curl -sSL https://install.boringcache.com/install.sh | BORINGCACHE_VERIFY_SIGNATURE=1 sh -``` - `boringcache onboard` authenticates the CLI, chooses a workspace, writes `.boringcache.toml` when it can, and lines up the same cache names across local runs, Docker builds, and GitHub Actions. +For restore-first laptop use, set `boringcache config set read_only true` once +and add `--write` only when a local command should publish. + If you want to start sign-in from the terminal by email, use `boringcache onboard --email you@example.com`. For a brand-new account, pass `--name` and `--username` too. Repo config can also keep the repeated command itself under `[adapters.]`. `command` accepts either an argv array or a shell-style string. -This is not general TOML templating: proxy-backed commands only substitute `{PORT}`, `{ENDPOINT}`, and `{CACHE_REF}`. After that, start with the shortest command that fits the tool: ```bash @@ -33,27 +27,32 @@ boringcache run -- bundle install # Docker adapter from repo config boringcache docker - -# Same adapter without repo config -boringcache docker --tag docker-cache -- docker buildx build . - -# Long-lived local proxy -boringcache cache-registry my-org/app registry-cache --port 5000 +# Native Xcode compilation cache (macOS) +boringcache xcode -- xcodebuild -workspace App.xcworkspace -scheme App build ``` Use archive mode commands (`run`, `save`, and `restore`) when you are caching explicit directories. Use adapter commands when the build tool already speaks a remote-cache protocol and BoringCache has a dedicated wrapper for it. -Use `cache-registry` when the repo already has a checked-in local endpoint setup or another process should keep the proxy alive. `cache-registry` is the proxy. `run --proxy` and adapter commands temporarily start that same proxy for one command. -When `.boringcache.toml` stores the Docker command, `boringcache docker` is the short form. Use the longer version when you want to pass the Docker command inline. +`.boringcache.toml` keeps repeated adapter commands and cache identity out of +shell scripts. -If you are wiring GitHub Actions, pin the verified `boringcache/one` v1.13.99 -distribution commit after onboard so CI can reuse the same repo config and -trust model: +Image publication is opt-in and uses the same workspace CAS as the BuildKit +cache. Configure a stable default with `publish-image = "web:latest"` under +`[adapters.docker]`, or pass a dynamic tag with `--publish-image`. To pull from +another machine, use a workspace restore token as the password. The hosted +registry is pull-only in this release; publish through the BoringCache wrapper, +not a direct `docker push`: -```yaml -- uses: boringcache/one@b55458ec8a4165e3fd70b1a1645f518a2095ed02 # v1.13.99 +```bash +boringcache docker --publish-image web:$GITHUB_SHA + +echo "$BORINGCACHE_RESTORE_TOKEN" | docker login registry.boringcache.com \ + --username boringcache --password-stdin +docker pull registry.boringcache.com/my-org/my-workspace/web:latest ``` +If you are wiring GitHub Actions, use [`boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27`](https://github.com/boringcache/one) after onboard so CI can reuse the same repo config and trust model. + ## Docs - [Quick start](docs/quick-start.md) @@ -61,7 +60,7 @@ trust model: - [Archive mode](docs/archive-mode.md) - [Adapter commands](docs/adapter-commands.md) - [Tool guides](docs/tool-guides.md) -- [Proxy mode](docs/proxy-mode.md) - [GitHub Actions](docs/github-actions.md) +- [Development](docs/development.md) - [Installation setup](INSTALLATION.md) - [Website docs](https://boringcache.com/docs) diff --git a/docs/README.md b/docs/README.md index e90b9ad6..0e35feaa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,6 @@ # CLI docs -The CLI README is the entrypoint. This repository is the public distribution -home for releases, installation, and customer docs. +The CLI README is the entrypoint. If you are new to BoringCache, start here: @@ -10,8 +9,26 @@ If you are new to BoringCache, start here: - [Archive mode](archive-mode.md) - [Adapter commands](adapter-commands.md) - [Tool guides](tool-guides.md) -- [Proxy mode](proxy-mode.md) - [GitHub Actions](github-actions.md) + +If you are working on the CLI repo itself: + +- [Development](development.md) +- [Adapter cache profiles](adapter-cache-profiles.md) +- [Adapter contract matrix](adapter-contract-matrix.md) +- [Adapter spec guardrails](adapter-spec-guardrails.md) +- [Historical performance learning log](performance-learning-log.md) +- [Engine boundary ADR](adr/0001-engine-boundary.md) +- [Proxy engine Plan B ADR](adr/0002-proxy-engine-plan-b.md) +- [Runner proxy optimization roadmap ADR](adr/0003-runner-proxy-optimization-roadmap.md) +- [OCI large blob stream-through ADR](adr/0004-oci-large-blob-stream-through.md) +- [Borrowed upload sessions and blob cache policy ADR](adr/0005-borrowed-upload-sessions-and-blob-cache-policy.md) +- [Cache session trace and OCI negative cache ADR](adr/0006-cache-session-trace-and-oci-negative-cache.md) +- [Docker registry-cache identity ADR](adr/0007-docker-human-tag-registry-identity.md) +- [OCI mistake ledger](oci-mistake-ledger.md) +- [sccache mistake ledger](sccache-mistake-ledger.md) +- [Bazel mistake ledger](bazel-mistake-ledger.md) +- [Gradle/Maven mistake ledger](gradle-maven-mistake-ledger.md) - [Installation setup](../INSTALLATION.md) The website docs live at [boringcache.com/docs](https://boringcache.com/docs). diff --git a/docs/adapter-commands.md b/docs/adapter-commands.md index 8d5f4285..6634b010 100644 --- a/docs/adapter-commands.md +++ b/docs/adapter-commands.md @@ -4,8 +4,8 @@ Adapter commands are the preferred path for tools that already speak a remote-ca Use them when you want BoringCache to: -- start a local proxy for one tool invocation -- inject the tool-specific env vars or cache flags when the adapter supports that +- manage one cache adapter for one tool invocation +- inject the tool-specific environment or cache flags - keep the repeated command short with `.boringcache.toml` Supported adapter commands today: @@ -19,6 +19,7 @@ Supported adapter commands today: - `boringcache sccache` - `boringcache go` - `boringcache buildkit` +- `boringcache xcode` (macOS) ## Common commands @@ -29,24 +30,25 @@ boringcache run -- bundle install # Docker adapter from repo config boringcache docker -# Same adapter without repo config -boringcache docker --tag docker-cache -- docker buildx build . +# Other adapters use the same shape +boringcache bazel +boringcache turbo +boringcache sccache -# Direct BuildKit adapter -boringcache buildkit --tag docker-cache -- buildctl build --frontend dockerfile.v0 - -# Long-lived local proxy -boringcache cache-registry my-org/app registry-cache --port 5000 +# Xcode compilation cache; the CLI supplies stable CAS and DerivedData paths +boringcache xcode -- xcodebuild -workspace App.xcworkspace -scheme App build ``` -Archive mode commands (`run`, `save`, and `restore`) are for explicit directory caches. Adapter commands are for supported remote-cache tools. Use `cache-registry` when the repo already has a checked-in local endpoint setup or another process should keep the proxy alive. -When `.boringcache.toml` stores the Docker command, `boringcache docker` is the short form. Use the longer version when you want to pass the Docker command inline. -Proxy-backed adapter commands start in warm mode by default. Use `--on-demand` when a proxy-backed command should skip startup warming and serve colder first reads. - -The product split is: +Archive mode commands (`run`, `save`, and `restore`) are for explicit directory caches. Adapter commands are for supported remote-cache tools. +`.boringcache.toml` keeps repeated adapter commands and cache identity out of +shell scripts. -- `cache-registry` is the proxy -- adapter commands temporarily start that proxy for one tool invocation, wait internally until it is ready, and wire the tool to it +For a restore-first laptop workflow, run +`boringcache config set read_only true` once. The setting is machine-local, +applies even when `CI=true` is present, and makes `run` plus adapter commands +skip writes. Add `--write` to one invocation to publish intentionally. See +[Archive mode](archive-mode.md#laptop-read-only-default) for the trust boundary +and GitHub Action behavior. ## Repo config @@ -68,68 +70,44 @@ boringcache docker Useful adapter fields: -- `tag` — cache tag for the proxy session +- `tag` — cache identity for the adapter - `command` — command to run when you call `boringcache ` with no args; accepts an argv array or a shell-style string -- `no-platform`, `no-git`, `read-only` — proxy scope and write-mode defaults you would otherwise keep repeating as flags +- `no-platform`, `no-git`, `read-only` — scope and write-mode defaults you would otherwise keep repeating as flags - `entries` / `profiles` — optional archive entries to restore before the tool runs - `metadata-hints` — low-cardinality session metadata -- `host`, `endpoint-host`, `port` — local endpoint settings +- `port` — an exceptional local coordination override; otherwise every adapter uses `22243` - `skip-restore`, `skip-save`, `save-on-failure` — archive behavior overrides - `cache-mode` — Docker/BuildKit cache export mode - `sccache-key-prefix` — sccache-only WebDAV key prefix/root ## Session hints -Proxy-backed CLI flows can label sessions directly. This is the non-GitHub path +Adapter CLI flows can label sessions directly. This is the non-GitHub path for grouping dashboard sessions and misses by stable labels instead of anonymous traffic. -Use CLI flags when you want one-off labels: - -```bash -boringcache run --proxy bazel-main \ - --metadata-hint project=web \ - --metadata-hint tool=bazel \ - --metadata-hint lane=ci \ - -- bazel build //... -``` - Use repo config when the labels are part of the normal adapter contract: ```toml -[proxy] -metadata-hints = ["project=web"] - [adapters.turbo] tag = "turbo-main" command = ["pnpm", "turbo", "run", "build"] metadata-hints = ["tool=turborepo", "lane=ci"] ``` -Use `BORINGCACHE_PROXY_METADATA_HINTS` when another script or service starts -`cache-registry` and you do not want to repeat `--metadata-hint` flags: - -```bash -export BORINGCACHE_PROXY_METADATA_HINTS=project=web,tool=gradle,lane=ci -boringcache cache-registry my-org/my-project gradle-main --port 5000 -``` - Keep these hints low-cardinality and replayable. Good values are `project=web`, `benchmark=grpc-bazel`, `tool=gradle`, `lane=ci`, and `workflow=build`. Avoid commit SHAs, run ids, timestamps, and other per-run values. Normal sessions do not need `cold` or `warm` labels; BoringCache derives new-vs-recurring misses from cache target and lifecycle telemetry. -The normal precedence is repo config first, then -`BORINGCACHE_PROXY_METADATA_HINTS`, then explicit CLI flags. Use the same kebab-case spellings in `.boringcache.toml` that you see in CLI -flags and docs; the config reader accepts both kebab-case and snake_case for -adapter proxy fields. +flags and docs. `command` is repo config, not a general templating system. -For proxy-backed commands, BoringCache only substitutes these placeholders inside command arguments: +For advanced adapter command templates, BoringCache only substitutes these placeholders inside command arguments: - `{PORT}` — the advertised local proxy port -- `{ENDPOINT}` — the advertised local proxy endpoint, for example `http://127.0.0.1:5000` +- `{ENDPOINT}` — the advertised local endpoint - `{CACHE_REF}` — the proxy cache ref when the wrapped tool expects a registry-style cache ref Example: @@ -152,12 +130,31 @@ These adapters inject the tool-specific settings for you: - `maven` - `sccache` - `go` +- `xcode` For Bazel, the adapter injects the remote-cache flags directly. +For Docker, `--tool-cache TOOL:TAG` starts the selected tool proxy outside the +build and lets managed BuildKit inject its session-only environment and config +into every `RUN` step. BuildKit cannot see through package scripts, aliases, +Makefiles, or compiler wrappers, so command-name matching would silently +disable an explicitly selected cache. Plain +`--tool-cache TOOL` is also accepted when +`[adapters.].tag` is set in `.boringcache.toml`. For BuildKit, the adapter injects `--import-cache` and `--export-cache` for `buildctl build`. -For Gradle, the adapter adds `--build-cache` plus a generated init script that points the remote cache at the local proxy. -For Maven, the adapter injects the `maven.build.cache.*` remote endpoint properties, but the Maven build cache extension still needs to be present in the repo. -For sccache, the adapter injects `RUSTC_WRAPPER`, `SCCACHE_WEBDAV_ENDPOINT`, `SCCACHE_WEBDAV_KEY_PREFIX`, and `CARGO_INCREMENTAL=0` when the caller has not set it, then prints a best-effort `sccache --show-stats` summary and records normalized native tool evidence after the wrapped command. Leave `sccache-key-prefix` unset unless you need a stable WebDAV sub-root within the proxy cache. +For Turbo and Nx, the adapter generates a random per-proxy bearer credential, +injects it only into the wrapped process, and rejects tokens from another proxy. +This isolates local proxy instances; it does not model team or tenant authorization. +Dry-run JSON uses the non-secret `boringcache` preview placeholder because no +proxy instance exists yet; it is never the runtime credential for a wrapped run. +For Gradle, the adapter adds `--build-cache` plus a generated init script in the effective Gradle user home. Command-line `-g`/`--gradle-user-home` wins over `GRADLE_USER_HOME`, just as it does in Gradle. +For Maven, the adapter injects the `maven.build.cache.*` endpoint/save properties and bootstraps extension 1.2.3 plus a marker-owned 1.2.0 cache config when those files are absent. Any existing build-cache extension declaration and user-owned cache config are preserved. Unrelated or unreadable extension XML, and an explicit requested-version mismatch, produce a setup error instead of a rewrite. +For sccache, the adapter injects `RUSTC_WRAPPER`, the WebDAV endpoint/prefix/read-write mode, and `SCCACHE_MULTILEVEL_CHAIN=webdav`, plus `CARGO_INCREMENTAL=0` when unset. It removes inherited alternate-backend selectors and WebDAV credentials, starts the child on a dedicated daemon port, collects best-effort stats on that same port, and stops the daemon afterward. This lifecycle contract requires supported sccache 0.16.0 or a newer version that has passed the adapter review. Leave `sccache-key-prefix` unset unless you need a stable WebDAV sub-root. +For Xcode, the adapter injects native compilation-cache settings, a stable CAS +and DerivedData path, and a credential-free Unix-socket bridge to the normal +BoringCache proxy. A direct `xcodebuild` command gets `-derivedDataPath` +automatically unless it already supplies one. Xcode version, build, path cohort, +compilation actions, CAS objects, bytes, and publication failures are recorded +as native evidence. If a repo already has a stable checked-in cache config, that still works. Explicit tool flags and checked-in config stay user-owned. @@ -178,18 +175,24 @@ Useful flags: - `--tag` - `--entry` - `--profile` -- `--host` -- `--endpoint-host` - `--port` - `--read-only` +- `--write` - `--fail-on-cache-error` - `--dry-run` - `--json` +Leave `--port` unset normally. Set it only when another process must coordinate +on an explicit local port or the product default conflicts on that machine. + Docker and BuildKit also support: - `--cache-mode` +Docker also supports: + +- `--tool-cache turbo:turbo-cache,nx:nx-cache,bazel:bazel-cache,gradle:gradle-cache,maven:maven-cache,sccache:rust-cache,go:go-cache` + Override precedence: | Input kind | Rule | @@ -201,6 +204,77 @@ Override precedence: For Docker and BuildKit, `--tag` is the cache tag all the way through: the proxy session, BuildKit registry ref, backend cache entry, and human-visible tag all use the resolved human tag. The CLI still applies the shared branch/default/PR restore ordering, but it expresses those candidates as human tags rather than Docker-specific ref aliases. The direct `buildkit` adapter uses the same OCI plan as Docker, but injects `buildctl build` flags as `--import-cache` and `--export-cache`. +`--tool-cache` is Docker-only. Supported names are `turbo`, `nx`, `bazel`, +`gradle`, `maven`, `sccache`, and `go`. Each requested tool uses the same tag +contract as running that adapter directly: pass +`--tool-cache turbo:turbo-cache`, or set +`[adapters.turbo].tag = "turbo-cache"` and pass `--tool-cache turbo`. +BoringCache starts the Docker/OCI proxy under the Docker tag and one normal +tool proxy for each requested tool tag. + +The managed builder applies every selected native contract throughout Docker +build execution: + +- Turbo and Nx receive their endpoint and per-proxy credential environment. +- sccache receives its WebDAV/compiler environment, so compiler subprocesses + do not have to mention `sccache` in the Dockerfile command. +- Bazel receives a read-only generated bazelrc. +- Gradle receives a read-only init script and properties file inside a writable, + session-only Gradle user-home tmpfs. +- Maven receives `MAVEN_OPTS` and a read-only cache config. The project must + still declare `maven-build-cache-extension` in `.mvn/extensions.xml` or its + POM; BoringCache does not overwrite project source during a build. +- Go receives `GOCACHEPROG` plus a standard-library-only helper source file. + The target image's Go toolchain compiles that helper, avoiding host/container + architecture mismatch. + +Injected values are BuildKit secrets and mounts, not image-layer contents. +They are scoped to build execution. An explicit Dockerfile secret-env entry or +mount at the same destination wins; ordinary image `ENV` values are replaced +because selecting `--tool-cache` makes BoringCache the cache configuration +owner for that build. Session configuration is attached only when an uncached +`RUN` executes and is not added to the layer cache key, so selecting a tool +cache does not invalidate otherwise reusable Docker layers. The file-backed +adapters require a managed BuildKit image with tool-env plan v2 support. + +The earlier explicit shell-secret contract remains available for Dockerfiles +that deliberately source it. It is an environment-only compatibility path for +Turbo, Nx, and sccache: + +```Dockerfile +RUN --mount=type=secret,id=boringcache-tool-cache-env \ + . /run/secrets/boringcache-tool-cache-env && \ + turbo run build +``` + +Managed native injection does not require that mount or shell snippet; a normal +`RUN turbo run build`, `RUN ./gradlew build`, or `RUN go build ./...` is enough. + +Repeat the flag when that reads better than a comma list: + +```bash +boringcache docker \ + --tool-cache sccache:rust-cache \ + --tool-cache turbo:turbo-cache \ + -- docker buildx build . +``` + +Or keep the tags in repo config and pass only tool names: + +```toml +[adapters.docker] +tag = "docker-cache" + +[adapters.turbo] +tag = "turbo-cache" + +[adapters.sccache] +tag = "rust-cache" +``` + +```bash +boringcache docker --tool-cache turbo,sccache -- docker buildx build . +``` So the common Docker command can stay short: diff --git a/docs/archive-mode.md b/docs/archive-mode.md index 2221ce54..c8e96995 100644 --- a/docs/archive-mode.md +++ b/docs/archive-mode.md @@ -33,3 +33,31 @@ The basic unit is `tag:path`: Tags are git-aware and platform-aware by default. Use `--no-git` to disable branch suffixing and `--no-platform` only when the cached directory is genuinely portable across operating systems and architectures. + +## Laptop read-only default + +If you want normal laptop commands to reuse shared caches without publishing, +set the machine-local default once: + +```bash +boringcache config set read_only true +``` + +`boringcache run` and every adapter command will still restore, but will skip +archive saves and proxy writes. Use `--write` on one invocation when you +intentionally want to publish the current Git-scoped tag: + +```bash +boringcache run --write -- bundle install +boringcache turbo --write +``` + +The preference lives in `~/.boringcache/config.json`; it is not written to the +repository by onboarding, and a `CI=true` environment does not bypass it. +`boringcache/one` always passes an explicit read-only or write policy based on +its trusted-event and save-token decision, so a self-hosted runner does not +inherit an ambiguous laptop default. + +This setting is an accident-prevention guardrail, not an authorization +boundary. A restore-only token is what prevents writes at the service. The +lower-level `boringcache save` command remains an explicit write operation. diff --git a/docs/github-actions.md b/docs/github-actions.md index e2c42609..a1cdc87c 100644 --- a/docs/github-actions.md +++ b/docs/github-actions.md @@ -5,46 +5,51 @@ The preferred path is: 1. install the CLI locally 2. run `boringcache onboard` 3. commit `.boringcache.toml` when it helps -4. use the verified, immutable [`boringcache/one`](https://github.com/boringcache/one) release commit in GitHub Actions +4. use [`boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27`](https://github.com/boringcache/one) in GitHub Actions That keeps CI and local runs on the same workspace, entries, and cache profiles. Example: ```yaml -- uses: boringcache/one@b55458ec8a4165e3fd70b1a1645f518a2095ed02 # v1.13.99 +- uses: boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27 # v1.13.106 with: - workspace: my-org/my-project + setup: none + mode: archive cache-profiles: bundle-install env: BORINGCACHE_RESTORE_TOKEN: ${{ secrets.BORINGCACHE_RESTORE_TOKEN }} - BORINGCACHE_SAVE_TOKEN: ${{ secrets.BORINGCACHE_SAVE_TOKEN }} + BORINGCACHE_SAVE_TOKEN: ${{ github.event_name != 'pull_request' && secrets.BORINGCACHE_SAVE_TOKEN || '' }} ``` -For proxy-backed modes, `boringcache/one` also accepts first-class `metadata-hints` so sessions and misses stay grouped by stable labels instead of per-run noise: +For proxy-backed modes, `boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27` also accepts first-class `metadata-hints` so sessions and misses stay grouped by stable labels instead of per-run noise: ```yaml -- uses: boringcache/one@b55458ec8a4165e3fd70b1a1645f518a2095ed02 # v1.13.99 +- uses: boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27 # v1.13.106 with: + setup: none mode: bazel - workspace: my-org/my-project metadata-hints: | project=web tool=bazel lane=ci env: BORINGCACHE_RESTORE_TOKEN: ${{ secrets.BORINGCACHE_RESTORE_TOKEN }} - BORINGCACHE_SAVE_TOKEN: ${{ secrets.BORINGCACHE_SAVE_TOKEN }} + BORINGCACHE_SAVE_TOKEN: ${{ github.event_name != 'pull_request' && secrets.BORINGCACHE_SAVE_TOKEN || '' }} ``` Keep those hints low-cardinality. Good values are `project=web`, `benchmark=grpc-bazel`, `tool=gradle`, `lane=ci`, or `workflow=build`. Avoid commit SHAs, run ids, timestamps, and cold/warm labels for normal sessions; BoringCache classifies new and recurring misses from cache target and lifecycle data. -If the repo already defines `[proxy]` or adapter `metadata-hints` in `.boringcache.toml`, `boringcache/one` inherits them through the CLI dry-run plan. Prefer repo config for durable defaults and use the action input only when the workflow needs an explicit override. +If the repo already defines `[proxy]` or adapter `metadata-hints` in `.boringcache.toml`, `boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27` inherits them through the CLI dry-run plan. Prefer repo config for durable defaults and use the action input only when the workflow needs an explicit override. The canonical repo-config starting points in [Tool guides](tool-guides.md) are meant to be shared between local CLI runs and GitHub Actions for exactly this reason. -If you are migrating an existing workflow and do not have repo config yet, raw `entries` and `actions/cache`-compatible `path` / `key` / `restore-keys` inputs still work. +Adapter modes use the same names as their CLI commands: `mode: docker`, +`mode: bazel`, `mode: turbo`, `mode: nx`, and `mode: sccache`. + +When migrating `actions/cache`, run `boringcache onboard` first and commit the +profile. Do not copy GitHub cache paths or keys into the Action. If you already manage the tool-specific setup yourself and only want proxy lifecycle plus adapter env injection, the CLI now also supports direct adapter commands: @@ -65,7 +70,7 @@ If you already manage the tool-specific setup yourself and only want proxy lifec - run: boringcache turbo env: BORINGCACHE_RESTORE_TOKEN: ${{ secrets.BORINGCACHE_RESTORE_TOKEN }} - BORINGCACHE_SAVE_TOKEN: ${{ secrets.BORINGCACHE_SAVE_TOKEN }} + BORINGCACHE_SAVE_TOKEN: ${{ github.event_name != 'pull_request' && secrets.BORINGCACHE_SAVE_TOKEN || '' }} ``` You can still override a configured adapter from the workflow when needed: @@ -78,30 +83,46 @@ You can still override a configured adapter from the workflow when needed: -- pnpm turbo run build env: BORINGCACHE_RESTORE_TOKEN: ${{ secrets.BORINGCACHE_RESTORE_TOKEN }} - BORINGCACHE_SAVE_TOKEN: ${{ secrets.BORINGCACHE_SAVE_TOKEN }} + BORINGCACHE_SAVE_TOKEN: ${{ github.event_name != 'pull_request' && secrets.BORINGCACHE_SAVE_TOKEN || '' }} ``` -Use `boringcache/one` when you want the action to keep owning tool setup such as Bazel rc files, Maven or Gradle cache config, buildx setup, or container networking. Keep the full commit pin and update it deliberately after verifying a newer public release. +Use `boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27` when you want the action to keep owning tool setup such as Bazel rc files, Maven or Gradle cache config, buildx setup, or container networking. When you run `boringcache docker` directly in GitHub Actions, the CLI derives the same branch/default/PR human tags from GitHub metadata that archive and proxy flows use. The action path passes provider-neutral metadata so Docker cache artifacts report the resolved human import/export tags and CI context. - -For Docker and BuildKit registry caches on pull requests, restore-only is the default. -A PR-scoped human tag may be absent and return 404, so restore-only PRs import the CLI-planned base/default tags without gaining write access. -If a workflow intentionally wants PR-scoped Docker writes, give the job a save-capable token and set `save-on-pull-request: true`; do that only when the PR write scope is isolated, not merely to make the PR tag exist. In PR context, the derived write target is the PR human tag. - -Keep the proxy story simple in CI too: - -- `cache-registry` is the long-lived proxy when a workflow or service needs to keep one around -- adapter commands and `run --proxy` temporarily start that same proxy for one command and wait internally -- if a detached helper needs to spawn `cache-registry` and continue only after startup readiness, it should consume the CLI-owned readiness handoff instead of reimplementing local HTTP polling -- use `/_boringcache/status` for diagnostics, explicit lifecycle assertions, and publish-settlement checks +For Dockerfile `RUN` steps that invoke native remote-cache tools, use the +Docker-specific action input with inline tags such as +`docker-tool-cache: turbo:turbo-cache,gradle:gradle-cache,go:go-cache`, or pass plain tool +names when `.boringcache.toml` defines `[adapters.].tag`. The action +delegates those builds to `boringcache docker --tool-cache ...`, so the CLI +owns the session-only BuildKit env/config injection and Docker-visible proxy +endpoints. The supported Docker tool set is Turbo, Nx, Bazel, Gradle, Maven, +sccache, and Go. + +For managed Docker and BuildKit caches on pull requests, `trust-policy: auto` +is restore-only. A separately trusted job can use `trust-policy: stage` to +create an immutable candidate. A later trusted solve supplies its exact ID or +digest through `cache-candidates`, imports it beside the current published +manifest, and exports only the fresh published manifest. +`boringcache/one` exposes the authenticated stage receipts as its +`cache-candidates` and `cache-candidate-digests` outputs for direct job-to-job +handoff. + +Adapter modes start, wait for, diagnose, and stop their local cache process. +Workflows select the adapter name and should not recreate that lifecycle. Keep the trust model simple: - every job gets `BORINGCACHE_RESTORE_TOKEN` +- candidate-producing jobs get `BORINGCACHE_STAGE_TOKEN` - only trusted jobs get `BORINGCACHE_SAVE_TOKEN` -- `pull_request` jobs stay restore-only by default inside `boringcache/one`; set `save-on-pull-request: true` only when the write scope is intentionally isolated -- restore-only PR Docker refs may 404 and should fall through to CLI-planned base/default imports -- avoid broad legacy `BORINGCACHE_API_TOKEN` use in CI +- `pull_request` jobs stay restore-only under `trust-policy: auto` +- exact candidates are explicit inputs, never ambient PR or Git fallbacks +- use only the purpose-specific restore/stage/save variables + +The Action does not use `CI=true` or a self-hosted runner's machine config as a +trust signal. After it evaluates event intent and token capability, every CLI +plan call receives exactly one explicit policy: `--read-only`, `--stage`, or +`--write`. The service still enforces token capability; the CLI flag is +orchestration, not authority. Read from pull requests. Write from trusted branches, tags, or manual jobs. diff --git a/docs/onboard.md b/docs/onboard.md index c4e5efd2..346bac1b 100644 --- a/docs/onboard.md +++ b/docs/onboard.md @@ -43,6 +43,19 @@ boringcache onboard --email you@example.com --name "Jane Doe" --username janedoe With `--workspace --apply`, onboard writes or verifies the repo `workspace` setting even when the repo has CI files that do not need optimization. +Onboard does not silently choose who may publish caches. It does not write a +read-only policy into `.boringcache.toml`, and it does not infer one from +whether the current process looks like CI. If this laptop should reuse caches +without publishing by default, opt in separately with: + +```bash +boringcache config set read_only true +``` + +That preference stays in the machine config. An intentional local publication +uses `--write`; GitHub Actions derives its explicit read/write choice from the +Action trust policy and token capability. + `--github-secrets` uses the GitHub CLI to set `BORINGCACHE_RESTORE_TOKEN` and `BORINGCACHE_SAVE_TOKEN` without printing token values. If both secrets already exist, it leaves them alone; pass `--rotate-ci-tokens` to replace them. @@ -61,12 +74,12 @@ boringcache onboard \ ``` That command keeps the same product path as interactive onboarding, but returns a -bounded JSON report with `workspace`, `repo_config`, `github_secrets`, -`optimize_results`, and `next_steps`. +bounded JSON report with `schema_version`, `workspace`, `repo_config`, +`github_secrets`, `optimize_results`, and `next_steps`. Use `--create-ci-tokens` only when another system must receive token values directly. Its JSON output includes the new restore/save token values, so do not -write that output to public logs or commit it to the repo. +write that output to CI logs or commit it to the repo. If onboard writes `.boringcache.toml`, later commands can use semantic entries and profiles instead of repeating raw `tag:path` pairs: diff --git a/docs/proxy-mode.md b/docs/proxy-mode.md deleted file mode 100644 index 40673911..00000000 --- a/docs/proxy-mode.md +++ /dev/null @@ -1,78 +0,0 @@ -# Proxy mode - -This page covers the proxy itself: the lower-level local endpoint path for supported native remote-cache tools. - -Most teams should read [Adapter commands](adapter-commands.md) first. -Use this page when the repo already has a checked-in local endpoint setup or another process should connect to the same local endpoint. - -## `cache-registry` - -`cache-registry` is the proxy. -It keeps the local endpoint running as a standalone process: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 -``` - -That command starts in warm mode by default. -The proxy warms its current internal active tag and shared blob cache state before it reports `ready`. -That warm path improves first reads for disk-backed proxy traffic today; startup does not preseed per-repository OCI manifest refs unless they are listed explicitly with `--oci-prefetch-ref`. -OCI/BuildKit refs use a metadata-only default: selected manifests and blob download URLs are resolved before the proxy reports `ready`, then BuildKit fetches blob bodies on demand through the proxy. -That is the product path for BuildKit performance, because it avoids blocking proxy readiness or competing with large layer graphs while preserving OCI read-through diagnostics. -The lower-level hydration policy flag is hidden and reserved for diagnostics or benchmark work; normal workflows should not set it. - -Use `--on-demand` only when you want the proxy to come up immediately and accept colder first reads: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 --on-demand -``` - -Use it when: - -- the repo already has a checked-in local endpoint setup -- another process should connect to the proxy -- you want to point several commands at the same local endpoint - -If you only need the proxy for one wrapped command, use `boringcache run --proxy ...` instead. -That path temporarily starts this same proxy, waits for `ready`, runs one command, then shuts the proxy down. - -Supported native protocols: - -- OCI registry APIs for BuildKit -- Bazel HTTP remote cache -- Gradle HTTP build cache -- Maven build cache extension -- Nx self-hosted remote cache -- Turborepo remote cache -- sccache -- Go `GOCACHEPROG` - -For Go, most teams should use `boringcache go`. Direct `go-cacheprog` wiring is the advanced/manual path when another process is already keeping `cache-registry` alive. - -The proxy binds to `127.0.0.1` by default. -Use `--host 0.0.0.0` when the proxy must listen on every interface. -Use `--endpoint-host host.docker.internal` when the wrapped client must reach the proxy through a different hostname than the bind address, for example with `docker buildx build` and a containerized builder. - -## Status endpoint - -The proxy exposes `/_boringcache/status` for readiness and drain checks. -It reports lifecycle phase (`warming`, `ready`, `error`, `draining`) and whether publish is settled for fresh readers. -CLI-managed proxy paths wait until the proxy is ready before they start the wrapped tool. -Most users do not need to poll it during startup; warm-by-default CLI paths and internal orchestration already handle that. -Use it when an external observer needs machine-readable lifecycle or publish-settlement state. -The exact contract lives in [Proxy readiness contract](contracts/readiness.md). - -## Expert tuning - -Most users should rely on the default proxy behavior. - -The intended expert overrides are: - -- `BORINGCACHE_BLOB_DOWNLOAD_CONCURRENCY` to cap read/download parallelism -- `BORINGCACHE_PROXY_MIN_FREE_MB` to raise or lower the local free-memory pressure threshold used by adaptive prefetch -- `BORINGCACHE_PROXY_DEBUG=1` to print detailed proxy diagnostics such as prefetch, tag refresh, blob upload, flush, and per-session lines - -Startup prefetch uses a separate auto budget so warmup can hydrate many-object tags quickly before the wrapped tool starts. GitHub Actions can target 100 startup reads for many-small-object tags, and the adaptive window ramps faster when observed RTT and goodput show the workload is RTT-bound. Rate limits pause replacement prefetch spawns for `Retry-After`, failures back off the window, and resource pressure holds increases. An explicit `BORINGCACHE_BLOB_DOWNLOAD_CONCURRENCY` still caps prefetch by default; `BORINGCACHE_BLOB_PREFETCH_CONCURRENCY` exists for benchmark/proof runs that need to tune startup hydration separately. - -The proxy keeps normal stderr quiet: one startup line, warnings/errors, and one shutdown summary. -Use global `--verbose`, `BORINGCACHE_PROXY_DEBUG=1`, or debug-level logging when debugging proxy internals. diff --git a/docs/quick-start.md b/docs/quick-start.md index 9d539743..c47c4820 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -11,26 +11,28 @@ boringcache onboard `boringcache onboard` is the default starting point. It authenticates the CLI, chooses a default workspace, and writes `.boringcache.toml` when it can so local runs, Docker builds, and CI can reuse the same cache names. -After onboard, start with the shortest command that fits the tool: +For restore-first laptop use, opt in with +`boringcache config set read_only true`; add `--write` only to an invocation +that should publish. Onboarding does not choose this policy for you. + +After onboard, use the plan it wrote. The command name is the cache adapter: ```bash -# Archive mode (run/save/restore) +# Archive entries inferred from the command or selected by profile boringcache run -- bundle install -# Docker adapter from repo config +# Native adapters use the same name everywhere boringcache docker - -# Same adapter without repo config -boringcache docker --tag docker-cache -- docker buildx build . - -# Long-lived local proxy -boringcache cache-registry my-org/app registry-cache --port 5000 +boringcache turbo +boringcache nx +boringcache bazel +boringcache sccache ``` Use archive mode commands (`run`, `save`, and `restore`) when you want to cache an explicit directory such as `vendor/bundle`, `node_modules`, or `dist`. Use adapter commands when the build tool already knows how to talk to a remote cache and BoringCache has a dedicated wrapper for it. -Use `cache-registry` when the repo already has a checked-in local endpoint setup or another process should keep the proxy alive. `cache-registry` is the proxy. `run --proxy` and adapter commands temporarily start that proxy for one command and wait internally until it is ready. -When `.boringcache.toml` stores the Docker command, `boringcache docker` is the short form. Use the longer version when you want to pass the Docker command inline. +`.boringcache.toml` keeps repeated adapter commands and cache identity out of +shell scripts. For repeated remote-cache commands, put the adapter setup in `.boringcache.toml` and keep the invocation short: @@ -42,9 +44,8 @@ tag = "docker-cache" command = ["docker", "buildx", "build", "."] ``` -`command` can be an argv array like the example above or a shell-style string such as `command = "docker buildx build ."`. -This is not general TOML templating. -When the adapter starts a local proxy, command arguments can use `{PORT}`, `{ENDPOINT}`, and `{CACHE_REF}`. +`command` can be an argv array like the example above or a shell-style string +such as `command = "docker buildx build ."`. ```bash boringcache docker @@ -52,7 +53,21 @@ boringcache docker The next docs to read are usually [Adapter commands](adapter-commands.md) and [Tool guides](tool-guides.md). -If the repo uses GitHub Actions, the next step is usually the immutable -[`boringcache/one`](https://github.com/boringcache/one) v1.13.99 distribution -commit `b55458ec8a4165e3fd70b1a1645f518a2095ed02`. +If the repo uses GitHub Actions, the next step is usually [`boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27`](https://github.com/boringcache/one). See [GitHub Actions](github-actions.md). + +## Security defaults + +- API credentials are sent only over HTTPS. Plain HTTP is accepted only when + `BORINGCACHE_API_URL` or the saved `api_url` names `localhost`, `127.0.0.0/8`, + or `::1` explicitly for local development. +- Authenticated API requests do not follow redirects. Configure the final API + URL directly. +- The official `https://api.boringcache.com` service requires a valid server + signature for cache hits automatically. Custom and self-hosted endpoints keep + compatibility warning behavior unless `--require-server-signature` or + `BORINGCACHE_REQUIRE_SERVER_SIGNATURE=1` is set. +- `~/.boringcache/config.json` is replaced atomically and symlinked config + paths are refused. On Unix, the directory is `0700` and the file is `0600`. + On Windows, keep the user profile on a user-only ACL; the CLI still performs + atomic replacement and symlink checks, but does not rewrite Windows ACLs. diff --git a/docs/tool-guides.md b/docs/tool-guides.md index 46c4033f..b2301a6a 100644 --- a/docs/tool-guides.md +++ b/docs/tool-guides.md @@ -7,14 +7,9 @@ The pattern is simple: 1. run `boringcache onboard` 2. put repeated setup in `.boringcache.toml` 3. use `boringcache ` -4. drop to `cache-registry` only when the repo already has a checked-in local endpoint setup or another process should keep the proxy alive - -`cache-registry` is the proxy. -`boringcache ` and `boringcache run --proxy` temporarily start that same proxy for one command, wait until it is ready, then hand traffic to the wrapped tool. -`cache-registry` itself is warm by default. Use `--on-demand` only for advanced shared-proxy setups that prefer immediate startup over warmed first reads. The snippets below are intended to be copy-pasteable `.boringcache.toml` -starting points. They work for local CLI runs and for `boringcache/one` +starting points. They work for local CLI runs and for `boringcache/one@9721d419d2c78c0780963d297eb3f81f24641a27` because the action asks the CLI for the same repo plan. Shared defaults for the examples: @@ -48,7 +43,7 @@ boringcache docker --tag docker-cache -- docker buildx build . Do not pass those flags yourself. Use `--cache-mode` only when you need to change the BuildKit export mode. -Direct BuildKit runs use the same OCI cache plan: +Direct BuildKit runs use the same managed cache plan: ```toml [adapters.buildkit] @@ -63,11 +58,11 @@ boringcache buildkit --tag docker-cache -- buildctl build --frontend dockerfile. ``` `boringcache buildkit` injects `--import-cache` and `--export-cache` for `buildctl build`. -Keep builder installation, daemon lifecycle, QEMU/binfmt, and Docker container networking in the caller's runtime setup. +The Docker and BuildKit adapters own the managed BoringCache builder lifecycle and its Docker networking, including selecting a cache endpoint reachable from the builder. Docker has one BoringCache tag concept: -- `--tag docker-cache` selects the resolved human cache tag, and the CLI uses that same tag for the BuildKit registry ref and backend cache entry. +- `--tag docker-cache` selects the resolved human cache tag, and the CLI uses that same tag for the managed BuildKit cache ref and backend entry. In GitHub Actions or another CI environment that provides BoringCache CI metadata, the adapter applies the shared branch/default/PR restore ordering as human tags. It injects the planned human-tag imports and exports the resolved write tag: @@ -78,22 +73,9 @@ It injects the planned human-tag imports and exports the resolved write tag: --cache-to .../cache:docker-cache-pr-3208-ubuntu-24-x86_64 ``` -BoringCache backs the BuildKit registry cache through the Docker and direct BuildKit adapters. -By default the Docker path warms the selected OCI manifest, blob URLs, and blob bodies before BuildKit starts. -That keeps the normal path simple: a warm BuildKit run should read cache bodies through the local proxy instead of discovering remote body reads after the manifest hit. - -If the builder runs in another container, set `endpoint-host = "host.docker.internal"` in `.boringcache.toml` or pass `--endpoint-host host.docker.internal`. - -For a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 - -docker buildx build \ - --cache-from type=registry,ref=127.0.0.1:5000/my-cache:main \ - --cache-to type=registry,ref=127.0.0.1:5000/my-cache:main,mode=max \ - . -``` +BoringCache provides the managed BuildKit backend through the Docker and direct BuildKit adapters. +By default the Docker path warms the selected cache metadata before BuildKit starts, then lets BuildKit fetch blob bodies on demand through the local proxy. +Use explicit OCI hydration settings only for evidence-backed read-path experiments; do not assume blob bodies are warmed before every BuildKit run. ## Nx @@ -107,7 +89,8 @@ command = ["nx", "run-many", "--target=build"] boringcache nx ``` -Nx gets the local endpoint and access token automatically. +Nx gets the local endpoint and a random per-proxy access token automatically; +the proxy rejects a token copied from another wrapped run. If `nx.json` is still connected to Nx Cloud with `nxCloudId`, `nxCloudAccessToken`, or an `nx-cloud` task runner, Nx may select its private cloud runner before the self-hosted cache endpoint. Remove that Nx Cloud binding from the workspace @@ -115,16 +98,6 @@ config before using the BoringCache Nx proxy, or use a prepared disposable checkout for benchmarks. Do not use `NX_NO_CLOUD` as the BoringCache setup path; Nx treats it as remote-cache disablement in current releases. -For a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 - -NX_SELF_HOSTED_REMOTE_CACHE_SERVER=http://127.0.0.1:5000 \ -NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN=boringcache \ -nx run-many --target=build -``` - ## Turborepo ```toml @@ -143,18 +116,8 @@ metadata-hints = ["tool=turborepo", "lane=ci"] boringcache turbo ``` -Turborepo gets `TURBO_API`, `TURBO_TOKEN`, and `TURBO_TEAM` automatically. - -For a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 - -TURBO_API=http://127.0.0.1:5000 \ -TURBO_TOKEN=boringcache \ -TURBO_TEAM=boringcache \ -turbo run build -``` +Turborepo gets `TURBO_API`, `TURBO_TOKEN`, and `TURBO_TEAM` automatically. The +wrapped token is random per proxy and is not a multi-tenant authorization model. ## Bazel @@ -174,19 +137,11 @@ metadata-hints = ["tool=bazel", "lane=ci"] boringcache bazel ``` -`boringcache bazel` starts the proxy and runs Bazel. -It injects `--remote_cache=http://127.0.0.1:5000` automatically and keeps upload enabled unless you run the adapter in read-only mode. +`boringcache bazel` starts the local cache process and runs Bazel. +It injects the remote-cache endpoint automatically and keeps upload enabled unless you run the adapter in read-only mode. If the repo already has Bazel cache flags in `.bazelrc`, those stay in effect and explicit user flags still win. -For a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 - -bazel build --remote_cache=http://127.0.0.1:5000 //... -``` - ## Gradle ```toml @@ -205,19 +160,12 @@ metadata-hints = ["tool=gradle", "lane=ci"] boringcache gradle ``` -`boringcache gradle` starts the proxy and runs Gradle. -It injects `--build-cache` and a generated init script that points Gradle remote cache traffic at `http://127.0.0.1:5000/cache/`. +`boringcache gradle` starts the local cache process and runs Gradle. +It injects `--build-cache` and a generated init script that points Gradle remote cache traffic at the managed local endpoint. The init script is written under the effective Gradle user home, including command-line `-g`/`--gradle-user-home` overrides. The adapter keeps push enabled unless you run it in read-only mode. If the repo already has build cache config in `settings.gradle(.kts)`, that still works. The adapter-owned init script just makes the local proxy turnkey for one command. -For a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 -./gradlew build --build-cache --no-daemon -``` - ## Maven ```toml @@ -230,17 +178,40 @@ command = ["mvn", "install", "-DskipTests", "--batch-mode", "-ntp"] boringcache maven ``` -`boringcache maven` starts the proxy and runs Maven. -It injects the `maven.build.cache.remote.url` and `maven.build.cache.remote.save.enabled` properties automatically. -If the repo does not already use the Maven build cache extension, add that first. The adapter owns the endpoint and save mode, but it does not bootstrap the extension itself. +`boringcache maven` starts the local cache process and runs Maven. +It injects the `maven.build.cache.remote.url` and `maven.build.cache.remote.save.enabled` properties automatically. When setup is absent, it also creates `.mvn/extensions.xml` for extension 1.2.3 and a marker-owned 1.2.0 build-cache config. Any existing build-cache extension declaration and user-owned cache config are preserved; when a version override is explicit, a mismatch is an error. The adapter refuses to splice into unrelated or unreadable user-owned XML. -For a long-lived endpoint: +## Xcode + +```toml +workspace = "my-org/my-project" + +[proxy] +metadata-hints = ["project=ios-app"] + +[adapters.xcode] +tag = "xcode-cache" +command = ["xcodebuild", "-workspace", "App.xcworkspace", "-scheme", "App", "build"] +metadata-hints = ["tool=xcode", "lane=ci"] +``` ```bash -boringcache cache-registry my-org/app registry-cache --port 5000 -mvn install -DskipTests --batch-mode -ntp -Dmaven.build.cache.remote.save.enabled=true +boringcache xcode +boringcache xcode -- xcodebuild -workspace App.xcworkspace -scheme App build ``` +The wrapper requires macOS and a supported Xcode installation. It finds +Apple's toolchain CAS plugin, loads the BoringCache adapter shipped beside the +CLI, creates stable per-tag CAS and DerivedData directories, starts the local +bridge, and injects the compilation-cache settings. Xcode and the plugin never +receive a workspace token. + +Xcode 26 reuse is scoped by absolute paths. Keep the checkout at the same real +path across runners; use one explicit canonical checkout path when sharing +between CI providers. Inspect `boringcache xcode --dry-run --json` for the +Xcode build, resolved paths, path strategy, and `path_cohort` before comparing +cache performance across lanes. + ## sccache ```toml @@ -261,19 +232,8 @@ sccache-key-prefix = "rust/ci" boringcache sccache ``` -The adapter sets `RUSTC_WRAPPER=sccache`, `SCCACHE_WEBDAV_ENDPOINT`, `SCCACHE_WEBDAV_KEY_PREFIX`, and `CARGO_INCREMENTAL=0` when the caller has not set it. -After the wrapped command exits, it reads `sccache --show-stats`, prints a concise hit/miss summary, and records normalized native tool evidence with the run/session diagnostics when sccache reports stats. - -For a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 - -RUSTC_WRAPPER=sccache \ -SCCACHE_WEBDAV_ENDPOINT=http://127.0.0.1:5000/ \ -SCCACHE_WEBDAV_KEY_PREFIX=rust/ci \ -cargo build --release -``` +The adapter sets `RUSTC_WRAPPER=sccache`, the WebDAV endpoint/prefix/read-write mode, and `SCCACHE_MULTILEVEL_CHAIN=webdav`, plus `CARGO_INCREMENTAL=0` when unset. It removes inherited alternate-backend selectors and WebDAV credentials so host or CI configuration cannot bypass the local proxy. +Each invocation uses a dedicated sccache daemon port. After the wrapped command exits, the adapter reads stats on that port, records normalized native-tool evidence when available, and stops the dedicated daemon. Use supported sccache 0.16.0 or a newer version only after its adapter compatibility review passes. ## Go @@ -288,12 +248,3 @@ boringcache go ``` Use `boringcache go` for normal Go cache integration. The adapter sets `GOCACHEPROG` automatically for Go 1.24+. - -Advanced: manual `GOCACHEPROG` wiring for a long-lived endpoint: - -```bash -boringcache cache-registry my-org/app registry-cache --port 5000 - -GOCACHEPROG="boringcache go-cacheprog --endpoint http://127.0.0.1:5000" \ -go build ./... -``` diff --git a/install-web/install.sh b/install-web/install.sh index 5fc61840..403e86ee 100644 --- a/install-web/install.sh +++ b/install-web/install.sh @@ -170,10 +170,12 @@ install_binary() { # Map OS and architecture to actual binary names local binary_name="" + local xcode_plugin_name="" case "${os}-${arch}" in "darwin-amd64"|"darwin-arm64") binary_name="boringcache-macos-universal" + xcode_plugin_name="libboringcache_xcode_cas-macos-universal.dylib" ;; "linux-amd64") if [ -f /etc/alpine-release ]; then @@ -210,6 +212,7 @@ install_binary() { # Create temporary directory local temp_dir=$(mktemp -d) local temp_file="${temp_dir}/${binary_name}" + local xcode_plugin_file="" if ! prepare_checksum_signature_verification; then rm -rf "${temp_dir}" @@ -217,6 +220,10 @@ install_binary() { fi download_file "${download_url}" "${temp_file}" + if [ -n "${xcode_plugin_name}" ]; then + xcode_plugin_file="${temp_dir}/${xcode_plugin_name}" + download_file "${release_url}/${xcode_plugin_name}" "${xcode_plugin_file}" + fi download_file "${release_url}/SHA256SUMS" "${temp_dir}/SHA256SUMS" if [ "${VERIFY_CHECKSUM_SIGNATURE}" = "1" ]; then if ! download_file "${release_url}/SHA256SUMS.bundle" "${temp_dir}/SHA256SUMS.bundle"; then @@ -233,6 +240,10 @@ install_binary() { print_error "Please check if the release exists at: ${download_url}" exit 1 fi + if [ -n "${xcode_plugin_name}" ] && { [ ! -f "${xcode_plugin_file}" ] || [ ! -s "${xcode_plugin_file}" ]; }; then + print_error "Failed to download ${xcode_plugin_name}" + exit 1 + fi if ! verify_checksum_signature "${temp_dir}"; then print_error "Checksum signature verification failed" @@ -243,9 +254,16 @@ install_binary() { print_error "Checksum verification failed for ${binary_name}" exit 1 fi + if [ -n "${xcode_plugin_name}" ] && ! verify_checksum "${temp_dir}" "${xcode_plugin_name}"; then + print_error "Checksum verification failed for ${xcode_plugin_name}" + exit 1 + fi # Make binary executable chmod +x "${temp_file}" + if [ -n "${xcode_plugin_name}" ]; then + chmod +x "${xcode_plugin_file}" + fi # Determine install directory local install_dir @@ -262,6 +280,7 @@ install_binary() { fi local final_binary="${install_dir}/${BINARY_NAME}" + local final_xcode_plugin="${install_dir}/libboringcache_xcode_cas.dylib" # Install the binary print_status "Installing to ${final_binary}..." @@ -269,8 +288,14 @@ install_binary() { if [ "$install_dir" = "/usr/local/bin" ] && [ ! -w "/usr/local/bin" ]; then # Need sudo for /usr/local/bin sudo mv "${temp_file}" "${final_binary}" + if [ -n "${xcode_plugin_name}" ]; then + sudo mv "${xcode_plugin_file}" "${final_xcode_plugin}" + fi else mv "${temp_file}" "${final_binary}" + if [ -n "${xcode_plugin_name}" ]; then + mv "${xcode_plugin_file}" "${final_xcode_plugin}" + fi fi # Cleanup @@ -278,6 +303,9 @@ install_binary() { print_success "BoringCache CLI installed successfully!" print_status "Binary location: ${final_binary}" + if [ -n "${xcode_plugin_name}" ]; then + print_status "Xcode adapter location: ${final_xcode_plugin}" + fi # Check if install directory is in PATH case ":$PATH:" in diff --git a/install.sh b/install.sh index 0e2db666..67114be5 100755 --- a/install.sh +++ b/install.sh @@ -171,10 +171,12 @@ install_binary() { # Map OS and architecture to actual binary names local binary_name="" + local xcode_plugin_name="" case "${os}-${arch}" in "darwin-amd64"|"darwin-arm64") binary_name="boringcache-macos-universal" + xcode_plugin_name="libboringcache_xcode_cas-macos-universal.dylib" ;; "linux-amd64") if [ -f /etc/alpine-release ]; then @@ -211,6 +213,7 @@ install_binary() { # Create temporary directory local temp_dir=$(mktemp -d) local temp_file="${temp_dir}/${binary_name}" + local xcode_plugin_file="" if ! prepare_checksum_signature_verification; then rm -rf "${temp_dir}" @@ -218,6 +221,10 @@ install_binary() { fi download_file "${download_url}" "${temp_file}" + if [ -n "${xcode_plugin_name}" ]; then + xcode_plugin_file="${temp_dir}/${xcode_plugin_name}" + download_file "${release_url}/${xcode_plugin_name}" "${xcode_plugin_file}" + fi download_file "${release_url}/SHA256SUMS" "${temp_dir}/SHA256SUMS" if [ "${VERIFY_CHECKSUM_SIGNATURE}" = "1" ]; then if ! download_file "${release_url}/SHA256SUMS.bundle" "${temp_dir}/SHA256SUMS.bundle"; then @@ -234,6 +241,10 @@ install_binary() { print_error "Please check if the release exists at: ${download_url}" exit 1 fi + if [ -n "${xcode_plugin_name}" ] && { [ ! -f "${xcode_plugin_file}" ] || [ ! -s "${xcode_plugin_file}" ]; }; then + print_error "Failed to download ${xcode_plugin_name}" + exit 1 + fi if ! verify_checksum_signature "${temp_dir}"; then print_error "Checksum signature verification failed" @@ -244,9 +255,16 @@ install_binary() { print_error "Checksum verification failed for ${binary_name}" exit 1 fi + if [ -n "${xcode_plugin_name}" ] && ! verify_checksum "${temp_dir}" "${xcode_plugin_name}"; then + print_error "Checksum verification failed for ${xcode_plugin_name}" + exit 1 + fi # Make binary executable chmod +x "${temp_file}" + if [ -n "${xcode_plugin_name}" ]; then + chmod +x "${xcode_plugin_file}" + fi # Determine install directory local install_dir @@ -263,6 +281,7 @@ install_binary() { fi local final_binary="${install_dir}/${BINARY_NAME}" + local final_xcode_plugin="${install_dir}/libboringcache_xcode_cas.dylib" # Install the binary print_status "Installing to ${final_binary}..." @@ -270,8 +289,14 @@ install_binary() { if [ "$install_dir" = "/usr/local/bin" ] && [ ! -w "/usr/local/bin" ]; then # Need sudo for /usr/local/bin sudo mv "${temp_file}" "${final_binary}" + if [ -n "${xcode_plugin_name}" ]; then + sudo mv "${xcode_plugin_file}" "${final_xcode_plugin}" + fi else mv "${temp_file}" "${final_binary}" + if [ -n "${xcode_plugin_name}" ]; then + mv "${xcode_plugin_file}" "${final_xcode_plugin}" + fi fi # Cleanup @@ -279,6 +304,9 @@ install_binary() { print_success "BoringCache CLI installed successfully!" print_status "Binary location: ${final_binary}" + if [ -n "${xcode_plugin_name}" ]; then + print_status "Xcode adapter location: ${final_xcode_plugin}" + fi # Check if install directory is in PATH case ":$PATH:" in diff --git a/scripts/test-installer-trust.sh b/scripts/test-installer-trust.sh index 26ee8fa2..58146a93 100755 --- a/scripts/test-installer-trust.sh +++ b/scripts/test-installer-trust.sh @@ -107,4 +107,11 @@ trap cleanup EXIT HUP INT TERM test_installer "${CLI_ROOT}/install.sh" "root-installer" test_installer "${CLI_ROOT}/install-web/install.sh" "web-installer" +for installer in "${CLI_ROOT}/install.sh" "${CLI_ROOT}/install-web/install.sh"; do + grep -F 'libboringcache_xcode_cas-macos-universal.dylib' "${installer}" >/dev/null || + fail "$(basename "${installer}") does not download the universal Xcode adapter" + grep -F 'libboringcache_xcode_cas.dylib' "${installer}" >/dev/null || + fail "$(basename "${installer}") does not install the Xcode adapter beside the CLI" +done + printf 'installer trust tests passed\n'