Skip to content

Add sticky disk cache modes#43

Open
crohr wants to merge 9 commits into
mainfrom
feature/sticky
Open

Add sticky disk cache modes#43
crohr wants to merge 9 commits into
mainfrom
feature/sticky

Conversation

@crohr

@crohr crohr commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add sticky-disk cache support through the new cache and path action inputs
  • add dedicated BuildKit, Git mirror, and APT cache modes, including Windows support where applicable
  • monitor disk pressure and fall back safely when sticky-disk caching is unavailable
  • document the new cache modes and rebuild the bundled action binaries

Why

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 ./...

crohr added 8 commits July 3, 2026 16:12
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).
@crohr
crohr marked this pull request as ready for review July 10, 2026 09:29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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"}},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +63 to +65
buildkitd := filepath.Join(binDir, "buildkitd")
if _, statErr := os.Stat(buildkitd); os.IsNotExist(statErr) {
if err := downloadBuildkit(action, binDir); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant