Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 15:49:16 -05:00
pkg-proxy/internal/server/apk_snapshots.go
Simon Chrzanowski 1abb8c029a Serve dated snapshots of Alpine APK indexes
Alpine mirrors keep only the current build of each package, so once a
package is upgraded `apk add curl=8.5.0-r0` fails: the current index no
longer lists the old build, even when the proxy still has it cached.

With upstream.apk_snapshots set (e.g. "90d"), each version of an index or
detached signature the metadata cache stores is copied to
_snapshots/alpine/ and recorded in a new metadata_snapshots table with the
time it was first served. /apk/{name}@{YYYY-MM-DD}/ serves the version that
was current at the end of that day; packages below it are the plain path's
requests. The kept bytes are the signed upstream bytes, so apk verifies
them unchanged.

An hourly job deletes versions replaced longer ago than the configured
duration and queues their blobs on the pending-delete queue; the newest
version of each index stays. Packages listed by kept snapshots are not
protected from eviction or retention; the docs say so.
2026-10-27 16:18:26 +01:00

48 lines
1.2 KiB
Go

package server
import (
"context"
"time"
)
// apkSnapshotPruneInterval is how often snapshots kept past
// upstream.apk_snapshots are deleted.
const apkSnapshotPruneInterval = time.Hour
// startAPKSnapshotPruning deletes APK index snapshots that were replaced more
// than upstream.apk_snapshots ago, once at startup and then every hour. Their
// blobs go through the pending-delete queue like evicted cache entries.
func (s *Server) startAPKSnapshotPruning(ctx context.Context) {
keep := s.cfg.ParseAPKSnapshots()
if keep <= 0 {
return
}
if !s.cfg.CacheMetadata {
s.logger.Warn("upstream.apk_snapshots needs cache_metadata, snapshots disabled")
return
}
ticker := time.NewTicker(apkSnapshotPruneInterval)
defer ticker.Stop()
for {
s.pruneAPKSnapshots(ctx, keep, time.Now())
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}
func (s *Server) pruneAPKSnapshots(ctx context.Context, keep time.Duration, now time.Time) {
s.runExclusive(ctx, "apk-snapshot-prune", func() {
n, err := s.db.PruneMetadataSnapshots("alpine", now.Add(-keep))
if err != nil {
s.logger.Warn("apk snapshots: prune failed", "error", err)
return
}
if n > 0 {
s.logger.Info("apk snapshots: pruned", "deleted", n)
}
})
}