Scripts on VDS at /opt/stacks/gc/scripts/, /etc/cron.d/vds-gc Sun 03:00/03:30 MSK. Verdaccio prune smoke: 8.5G → 6.8G (1.7G freed, 4524 tgz, 1551 pkgs mod, 0 err). Registry GC smoke: alpine push → DELETE manifest → 61M freed. Empty-storage guard added. Notifications via ntfy vds-ops (low on small/quick, high on fail). New wiki concept verdaccio-prune-semantics — _distfiles keyed by tarball filename (NOT version string), locally-published (@snollajs/*) protected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.4 KiB
title, type, tags, sources, updated
| title | type | tags | sources | updated | |||||
|---|---|---|---|---|---|---|---|---|---|
| Verdaccio prune semantics — proxied vs locally-published | concept |
|
2026-05-21 |
Verdaccio prune — proxied vs locally-published
Прежде чем удалять .tgz из verdaccio storage, надо отличать proxied (cached from upstream — безопасно удалить, можно re-fetch) от locally-published (authoritative copy — удаление = permanent loss).
Storage layout
/storage/data/<package>/:
package.json— packument (metadata, дешёвый)<package>-<version>.tgz— binary tarballs (дорогие)
Для scoped: /storage/data/<scope>/<package>/.... Scope в filename .tgz НЕ включается — @snollajs/snolla/snolla-0.2.4.tgz.
Detection rule
В package.json есть три relevant fields:
_uplinks: { npmjs: { etag, fetched } } # upstream registries verdaccio queried
_distfiles: { "<pkg>-<v>.tgz": { url, sha, registry } } # ключи — ИМЯ tarball, не version
_attachments: { "<pkg>-<v>.tgz": { shasum } } # cached + locally-uploaded tarballs
Detection:
_distfiles[<tgzName>]defined → proxied, tarball mirrored from upstream URL. Удалять.tgzфайл безопасно — verdaccio re-fetch'нет при следующемnpm install pkg@v._distfiles[<tgzName>]undefined, but_attachments[<tgzName>]defined и.tgzесть на диске → locally-published (npm publishв verdaccio черезverdaccioregistry). НЕ удалять — это единственная копия.
Gotcha: keying
Ключи _distfiles — это filename (lodash-0.1.0.tgz), НЕ version string (0.1.0). Lookup _distfiles["0.1.0"] всегда возвращает undefined, ложно классифицируя ВСЕ versions как locally-published.
Easy mistake: первый draft prune скрипта проверял _distfiles[v] → dry-run показал 4597/7021 packages "защищены" (на самом деле ВСЕ proxied lodash/react/etc). Fix: _distfiles[<tgzBase>-<v>.tgz] где tgzBase = name.replace(/^@[^/]+\//, '') (strip scope для filename).
Safe prune algorithm
для каждого package:
keepSet = dist-tagged versions # {latest, next, beta, ...}
others = versions − keepSet, отсортированные по time[v] desc
keepSet += first (N - |keepSet|) из others # N = 10
для v в (versions − keepSet):
tgzName = "<scope-stripped-name>-<v>.tgz"
if _distfiles[tgzName] undefined:
continue # locally-published, skip
if .tgz file exists: delete file
if _attachments[tgzName] exists: delete entry
если что-то изменили в _attachments:
bump _rev (`<num+1>-<random_hex>`)
atomic rewrite package.json (tmp + rename)
Atomic write + _rev
package.json rewrite через fs.writeFileSync(tmp); fs.renameSync(tmp, real) — atomic on POSIX same-fs. Race window между «file gone» и «package.json updated» ничтожен.
_rev field в формате <num>-<hash> (CouchDB-style). Verdaccio проверяет его для optimistic concurrency. Bump = increment number + new random hex. Это invalidates npm client packument cache.
Дополнительная подстраховка от race: stop verdaccio перед prune, start после (~1 min downtime weekly). Альтернатива — atomic rewrite без stop — допустима, но cache invalidation менее clean.
Что НЕ трогать
versions{}иtime{}— оставляем metadata in-place даже после удаления tarballs. Для proxied packages npm re-fetch'нет tgz при request; metadata лёгкая (~KB)._uplinks{}— agent-level state о fetch timestamps; не относится к individual versions.readme,users,_id— irrelevant для prune.
Применено
vds-gc-cron — weekly cron Sun 03:30 MSK на VDS. Smoke 2026-05-21: 8.5G→6.8G freed, 4524 tgz deleted, 73 locally-published versions защищены (@snollajs/* + ~40 другие internal packages).
Ссылки
- Verdaccio local-storage docs: https://verdaccio.org/docs/configuration#storage
- Algorithm в репо:
/opt/stacks/gc/scripts/verdaccio-prune.jsна VDS.