Help us shape the future of Go! Take the 2026 Go Developer Survey

actionscache

package
v0.2.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package actionscache serves GitHub Actions' cache service (v2) from the LAN, backed by a writable OCI registry, so `actions/cache` and everything built on it (Swatinem/rust-cache, setup-* caching) stop moving gigabyte-sized archives over the WAN on every job.

How a job reaches it. The runner exports ACTIONS_RESULTS_URL to every step from the job message, overwriting the container's environment, so ephemerd patches that name out of the job's own copy of Runner.Worker.dll (see pkg/runner.PatchResultsURL) and sets ACTIONS_RESULTS_URL to this server. The cache client then talks to us; GitHub's real address survives under a renamed variable and is what we relay everything that is not the cache to.

Who a request is from. The cache client builds its twirp URLs with an absolute path ("/twirp/..."), which discards any path we could have put a per-job secret in, and it sends GitHub's own runtime token, which we cannot verify. So a request is attributed by its SOURCE ADDRESS: every container of a job joins that job's network under the job's id (networking.JobForIP), and job containers cannot spoof another's address on the bridge. The upload and download URLs we hand out are used verbatim, so those additionally carry an unguessable token.

What a job may touch. Not decided here: a job is unbound until the scheduler binds it to a Scope derived from the forge's record of the job that ACTUALLY took the runner (GitHub hands same-label JIT runners to any queued job, so the job a runner was started for is not evidence). Until then every lookup misses and every save is refused.

Failure is always a cache miss. Unbound, registry down, entry missing: the twirp answer is ok=false (which the client treats as "no cache" or "could not reserve", warning and moving on), never a 5xx, which it would retry five times and then log as an error.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// ListenAddr is the address to bind, normally the bridge gateway
	// ("10.88.0.1:8089").
	ListenAddr string
	// AdvertiseBase is the base URL job containers reach this server at
	// ("http://10.88.0.1:8089"). Upload and download URLs are built on it.
	AdvertiseBase string

	// Registry is the writable registry's base URL ("http://cache:5001").
	Registry         string
	RegistryUser     string
	RegistryPassword string

	// MaxBytesPerRepo caps each forge repo's total cache; the least
	// recently used entries are deleted past it. 0 disables eviction.
	MaxBytesPerRepo int64
	// MaxEntryBytes refuses a single save larger than this. Default 10 GiB
	// (GitHub's own per-repo limit).
	MaxEntryBytes int64

	// UpstreamResultsURL is GitHub's results service, where every request
	// that is not the cache (artifact uploads, …) is relayed unchanged.
	// Default https://results-receiver.actions.githubusercontent.com.
	UpstreamResultsURL string

	// JobForIP attributes a request to a job by its source address
	// (networking.Manager.JobForIP). Required.
	JobForIP func(ip string) string

	// HealthInterval is how often the registry is probed; negative disables
	// the loop (tests). Default 30s.
	HealthInterval time.Duration

	// HTTPClient talks to the registry. Default: a client without a total
	// timeout (blobs are large) but with dial/response-header bounds.
	HTTPClient *http.Client

	Log *slog.Logger
}

Config configures the cache server.

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy is the cache server.

func New

func New(cfg Config) (*Proxy, error)

New validates cfg and builds the server. It does not listen until Start.

func (*Proxy) Addr

func (p *Proxy) Addr() string

Addr is the bound address (after Start).

func (*Proxy) Bind

func (p *Proxy) Bind(runner string, s Scope)

Bind grants a tracked runner its scope. It is a no-op for an untracked runner (the cache was not turned on for it) and refuses to re-bind: a runner runs exactly one job, so a second, different binding is a bug or an attack, and the safe answer is to drop the runner's access entirely.

func (*Proxy) Forget

func (p *Proxy) Forget(runner string)

Forget drops a runner and everything it was granted. Call it when the runner's environment is destroyed.

func (*Proxy) Healthy

func (p *Proxy) Healthy() bool

Healthy reports whether the registry answered the last probe. The scheduler only turns the cache on for a new job while this is true.

func (*Proxy) ResultsURL

func (p *Proxy) ResultsURL() string

ResultsURL is the ACTIONS_RESULTS_URL value for job containers.

func (*Proxy) ServeHTTP

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP routes cache calls here and relays everything else to GitHub.

func (*Proxy) Start

func (p *Proxy) Start() error

Start probes the registry once, binds the listener and starts serving.

func (*Proxy) Stop

func (p *Proxy) Stop() error

Stop shuts the server down.

func (*Proxy) Track

func (p *Proxy) Track(runner string)

Track registers a runner the cache is turned on for. Until Bind it is unbound: every lookup misses, every save is refused.

type Scope

type Scope struct {
	// Repo is the forge-native "owner/repo" the job belongs to. Entries of
	// different repos never meet: each repo gets its own registry namespace.
	Repo string
	// Write is the ref this job saves entries under, e.g. "refs/heads/main"
	// or "refs/pull/12/merge". Empty means read-only.
	Write string
	// Reads are the refs a restore searches, in order — the job's own ref
	// first, then the default branch. This mirrors GitHub: a PR can restore
	// what main saved, but nothing a PR saves is ever visible to main.
	Reads []string
}

Scope is what one job may do with the cache. It is decided by ephemerd from the forge's own record of the job (see the scheduler's in_progress binding), never from anything the job sends.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL