Add sticky disk cache modes#43
Conversation
Adds nscloud-cache-action-style caching backed by RunsOn sticky disks (snap=<size>[:<name>] label). The new 'cache' input takes a list of package manager modes (go, node, ruby, rust, python, apt, ...) whose cache directories are bind-mounted onto the sticky disk volume; 'path' accepts arbitrary paths. Outputs 'cache-hit' when all paths were restored from a previous snapshot.
Blacksmith-style: runs a pinned buildkitd whose state and binaries live on the sticky disk, registered as the current buildx builder via the remote driver. The docker daemon is never touched. The action's post step stops buildkitd gracefully so the state is consistent before the volume is unmounted and snapshotted.
- buildkitd now runs with an explicit GC config (cache capped at 75% of the volume, 20% min free) so the buildkit cache stays bounded. - Post step logs volume usage and emits a warning + job summary with a per-cache breakdown when free space drops below 20% (or inodes 10%). - If a restored volume is critically full at job start (<5% free space or inodes), all caches on it are reset so the job runs cold instead of dead-looping on ENOSPC, and the next snapshot starts clean.
- Junction-based cacheMount on Windows (mklink /J, moves existing targets aside) - Windows cache paths (~/AppData/Local) for go, node, yarn, pnpm, pip, uv, poetry - Skip apt/buildkit modes on Windows; disk stats via GetDiskFreeSpaceExW
- Regenerate main-* binaries with Windows sticky-disk support
Accelerates an unmodified actions/checkout (and any github.com fetch) by running a local git smart-HTTP proxy whose bare mirrors live on the sticky disk, with github.com fetch URLs rewritten to it via global git config. Serving is delegated to git http-backend (protocol-exact, incl. v2); pushes are pinned to upstream via pushInsteadOf, and anything the mirror cannot serve (LFS, receive-pack, force-push-orphaned SHAs, mirror failures) is transparently forwarded to github.com. Mirror code adapted from github.com/crohr/smart-git-proxy, minus the shared-proxy pack cache (unnecessary per-job, and buggy per issue #1).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4505b1cc1f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "go": {Name: "go", Paths: []string{"~/.cache/go-build", "~/go/pkg/mod"}, WindowsPaths: []string{"~/AppData/Local/go-build", "~/go/pkg/mod"}}, | ||
| "node": {Name: "node", Paths: []string{"~/.npm"}, WindowsPaths: []string{"~/AppData/Local/npm-cache"}}, | ||
| "yarn": {Name: "yarn", Paths: []string{"~/.cache/yarn"}, WindowsPaths: []string{"~/AppData/Local/Yarn/Cache"}}, | ||
| "pnpm": {Name: "pnpm", Paths: []string{"~/.pnpm-store"}, WindowsPaths: []string{"~/AppData/Local/pnpm/store"}}, |
There was a problem hiding this comment.
Mount pnpm’s actual Linux store directory
When cache: pnpm is used on Linux, this mounts ~/.pnpm-store, but pnpm’s current default store is under ~/.local/share/pnpm/store (or $XDG_DATA_HOME/pnpm/store). In the common case where users do not explicitly set store-dir, pnpm will keep downloading into the unmounted default location, so the advertised pnpm cache mode records misses and provides no persistence.
Useful? React with 👍 / 👎.
| buildkitd := filepath.Join(binDir, "buildkitd") | ||
| if _, statErr := os.Stat(buildkitd); os.IsNotExist(statErr) { | ||
| if err := downloadBuildkit(action, binDir); err != nil { |
There was a problem hiding this comment.
Version the cached BuildKit binary directory
For users with a warm sticky disk, this presence check skips downloading whenever any cached buildkitd exists, regardless of the pinned buildkitVersion. After the action later bumps BuildKit for a security or compatibility fix, existing lineages will silently keep running the old daemon from buildkit/bin, so the upgrade will not take effect unless users manually wipe the sticky disk.
Useful? React with 👍 / 👎.
| func (m *Mirror) scheduleOptimize(repoPath string, full bool) { | ||
| go func() { | ||
| m.maintGroup.Do(repoPath, func() (interface{}, error) { | ||
| m.optimizeRepo(context.Background(), repoPath, full) |
There was a problem hiding this comment.
Tie git mirror maintenance to proxy shutdown
When cache: git mirrors a large repository, the background git repack/commit-graph maintenance can still be running when the post step stops the proxy, because it is launched with context.Background() and is not waited for or cancelled. The proxy process can exit while the child git process keeps writing under the sticky disk, so job-end snapshot/unmount can race with repository maintenance and preserve an inconsistent or locked mirror.
Useful? React with 👍 / 👎.
| } | ||
| firstMissing = parent | ||
| } | ||
| if err := os.MkdirAll(target, 0755); err != nil { |
There was a problem hiding this comment.
Reject or handle file paths before creating directories
For Linux users who pass a file cache path such as .eslintcache or another tool cache file via the advertised arbitrary path input, this unconditionally creates a directory at that path when it is absent; if the file already exists, the later directory bind mount fails instead. Either case prevents the tool from using its expected file path, so file cache entries can break the job rather than simply being skipped or persisted.
Useful? React with 👍 / 👎.
| action.Infof("Starting buildkitd %s (root: %s)", buildkitVersion, stateRoot) | ||
| startCmd := fmt.Sprintf("nohup %s --config %s --root %s --addr unix://%s --group docker > %s 2>&1 &", | ||
| buildkitd, configFile, stateRoot, buildkitSocketPath, buildkitLogFile) | ||
| if err := runLogged(action, "sudo", "sh", "-c", startCmd); err != nil { |
There was a problem hiding this comment.
Protect cached BuildKit binaries before sudo execution
When untrusted workflow code can write to the sticky disk, it can replace the cached buildkitd under buildkit/bin; a later job on the same sticky-disk lineage then skips the download and this command executes that persisted runner-writable file via sudo. That turns the cache into a persistent root-code execution path, so the binary should be root-owned/verified or re-downloaded before it is launched as root.
Useful? React with 👍 / 👎.
Summary
cacheandpathaction inputsWhy
Sticky disks can preserve expensive caches between ephemeral runner jobs. This change exposes that storage to common build workloads while keeping the action usable when a sticky disk is unavailable or under pressure.
Impact
Users can opt into persistent BuildKit, Git, or path-based caches from workflow configuration. Existing workflows remain unchanged unless the new inputs are configured.
Validation
mise exec -- go test ./...