From 61826cb1ef689062d827fcef7b1d4a0efde434af Mon Sep 17 00:00:00 2001 From: vitya Date: Thu, 21 May 2026 00:23:40 +0300 Subject: [PATCH] =?UTF-8?q?feat(vds-gc-cron):=20done=20=E2=80=94=20weekly?= =?UTF-8?q?=20registry=20GC=20+=20verdaccio=20prune=20armed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- STATUS.md | 12 ++++--- vds-gc-cron.md | 88 +++++++++++++++++++++++--------------------------- 2 files changed, 48 insertions(+), 52 deletions(-) diff --git a/STATUS.md b/STATUS.md index 27270ed..bd76eb1 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,5 +1,5 @@ # Task Board -_Updated: 2026-05-20_ +_Updated: 2026-05-21_ ## 🟢 [cms-admin-assets-root-folders-seed] — seed 15 missing root AssetsFolder rows в DB, admin assets открывается **Status:** done (2026-05-19 вечер). Browser-verified user'ом на pilorama98/emspb. Detail в [[cms-admin-assets-root-folder-seed]]. @@ -45,10 +45,12 @@ _Updated: 2026-05-20_ --- -## ⚪ [vds-gc-cron] — cron GC для verdaccio и registry на VDS -**Status:** ready (заведена 2026-05-20). Детали + sketch implementation в [vds-gc-cron.md](vds-gc-cron.md). -**Where I stopped:** task создана как follow-up к [[vds-kzntsv-bootstrap]] Phase 3 — после стабилизации миграции нужно автоматизировать periodic GC чтобы registry не разрастался снова до 99G, и verdaccio storage не пух от старых версий. -**Next action:** дождаться завершения vds-kzntsv-bootstrap Phase 3, потом написать cron + GC-скрипты + проверить. +## 🟢 [vds-gc-cron] — weekly GC armed для verdaccio + registry на VDS +**Status:** done (2026-05-21 00:21 MSK; ~1ч работы). Детали в [vds-gc-cron.md](vds-gc-cron.md). +**Result:** `/etc/cron.d/vds-gc` armed — Sun 03:00 MSK registry-gc.sh, Sun 03:30 MSK verdaccio-prune.sh. Scripts в `/opt/stacks/gc/scripts/` (`notify.sh`, `registry-gc.sh`, `verdaccio-prune.sh`+`verdaccio-prune.js`). Logs в `/var/log/vds-gc/`. Notify через ntfy `vds-ops`. +**Verdaccio prune real run:** 8.5G → 6.8G (freed 1.7G), 7021 packages scanned, 1551 modified, 4524 tgz deleted, 0 errors, 73 locally-published versions защищены (@snollajs/* + similar — detected via `_distfiles[tgzName]` absent). Atomic JSON rewrite (`tmp + rename`) + `_rev` bump. +**Registry GC smoke:** alpine v1+v2 pushed → DELETE manifest → GC freed 61M (whole tree, since both tags pointed одну digest). Empty-storage guard added (first run на свежем registry — `/var/lib/registry/docker/registry/v2/repositories` отсутствует → skip+min-prio notify, не FAIL). +**Atomic revert:** `ssh vitya@89.253.255.94 'sudo rm /etc/cron.d/vds-gc; sudo systemctl restart cron; sudo rm -rf /opt/stacks/gc /var/log/vds-gc'` **Branch:** master --- diff --git a/vds-gc-cron.md b/vds-gc-cron.md index 32a6944..5cef692 100644 --- a/vds-gc-cron.md +++ b/vds-gc-cron.md @@ -2,58 +2,52 @@ ## Goal -Настроить ежедневный (или другой регулярный) cron GC для двух container-сервисов на VDS: -1. **Registry** (`/opt/stacks/registry/`) — `docker exec registry registry garbage-collect /etc/docker/registry/config.yml`. Иначе старые блобы не удалённых тэгов накапливаются — 99G после нескольких лет накопления было исходно (см. [[vds-kzntsv-bootstrap]] Phase 3.3). -2. **Verdaccio** (`/opt/stacks/verdaccio/`) — нет встроенной GC, но есть несколько подходов: - - `npm-cache clean --force` внутри контейнера (очищает npm-side cache, не storage); - - Скрипт-prune старых versions: для каждого `package.json` в storage оставлять последние N версий, удалить остальные tarball'ы; - - Или использовать verdaccio plugin типа `verdaccio-cache-cleaner`. +Weekly GC для двух container-сервисов на VDS, чтобы storage не разрастался: +1. **Registry** (`/opt/stacks/registry/`) — `registry garbage-collect -m` против nested mount `./docker:/var/lib/registry` (host data в `/opt/stacks/registry/docker/docker/registry/v2/...`). +2. **Verdaccio** (`/opt/stacks/verdaccio/`) — keep N=10 most-recent versions + dist-tagged per-package, drop ТОЛЬКО proxied tarballs (`_distfiles[tgzName]` defined). Локально-published versions (`@snollajs/*` + similar) защищены. -## Open questions +## Key files -- [ ] Cadence: ежедневно ночью? Раз в неделю? Зависит от роста кеша. -- [ ] Retention для verdaccio: сколько версий каждого package хранить? 5 последних? Все из @snollajs/* — всегда? -- [ ] Notify on GC fail — через [[vds-backup-rsync-kreknin]] почтовый канал? - -## Implementation sketch - -`/etc/cron.d/vds-gc`: - -```cron -# Registry GC — каждое воскресенье в 03:00 MSK -0 3 * * 0 root /opt/stacks/registry/scripts/gc.sh > /var/log/registry-gc.log 2>&1 - -# Verdaccio prune — каждое воскресенье в 04:00 MSK -0 4 * * 0 root /opt/stacks/verdaccio/scripts/prune.sh > /var/log/verdaccio-prune.log 2>&1 -``` - -`/opt/stacks/registry/scripts/gc.sh`: - -```bash -#!/bin/bash -set -e -# read-only mode → GC → re-enable writes -docker exec registry sh -c 'export REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED=true && sleep 1' -docker exec registry registry garbage-collect -m /etc/docker/registry/config.yml -docker exec registry sh -c 'unset REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED' -docker restart registry # для надёжности -``` - -(Hint: подход с env var может не сработать на running container — лучше через compose `docker-compose.override.yml` с read-only flag + docker compose up -d.) - -`/opt/stacks/verdaccio/scripts/prune.sh`: - -```bash -#!/bin/bash -# Удалить tarball'ы старше N=10 versions для каждого package -# (нужно написать скрипт) -``` +- `/opt/stacks/gc/scripts/notify.sh` — ntfy helper, sources `/opt/stacks/ntfy/.env`, posts на `vds-ops` topic. +- `/opt/stacks/gc/scripts/registry-gc.sh` — stop registry → offline `garbage-collect -m` → start; empty-storage guard в начале. +- `/opt/stacks/gc/scripts/verdaccio-prune.sh` — wrapper, stop verdaccio → docker run `node:20-alpine` mounts storage + script → start. +- `/opt/stacks/gc/scripts/verdaccio-prune.js` — ядро prune: walk storage, sort versions by `time[v]` desc, keep top-N + dist-tagged, delete tgz + `_attachments` entries for proxied versions, atomic JSON rewrite + `_rev` bump. +- `/etc/cron.d/vds-gc` — Sunday 03:00 / 03:30 MSK. +- `/var/log/vds-gc/{registry,verdaccio}-YYYY-MM-DD.log` — append-only logs. ## Decisions log -- _Пусто — задача только заведена._ +- **2026-05-21:** keep N=10 per package для verdaccio (включая `@snollajs/*` — uniform, no special-case scope). Reasoning: N=10 покрывает любые reasonable rollback scenarios, локально-published уже защищены логикой `_distfiles[tgzName]` absent. +- **2026-05-21:** weekly Sunday 03:00 / 03:30 MSK (2 часа до 05:00 backup cron — нет конфликта). Reasoning: weekly достаточно для personal scale; verdaccio cache растёт ~MB/день не GB/день. +- **2026-05-21:** registry GC = stop→`-m`→start (~30s downtime) instead of readonly+restart-twice. Reasoning: проще, более atomic; на personal registry 30s downtime в воскресенье ночью acceptable. +- **2026-05-21:** verdaccio prune = stop+prune+start (~1min downtime). Reasoning: избегаем torn-read race window между tgz delete и package.json rewrite. +- **2026-05-21:** verdaccio detection of locally-published — через `_distfiles[]` (НЕ `_distfiles[]`). Initial code был bug — verdaccio keys `_distfiles` by tarball filename like `lodash-0.1.0.tgz`, не version string. Dry-run показал 4597 "locally-published" из 7021 — обнаружен через inspection lodash/react/`@snollajs` storage. Real run после fix: 73 версии legitimately protected. +- **2026-05-21:** notify policy — success-low (`broom` tag, prio low) если freed < 10 MiB AND duration < 5 min; success-default иначе; fail-high (`x,boom` tag, prio high) на любой rc≠0; skip-min на empty registry. +- **2026-05-21:** atomic package.json rewrite через `write tmp + rename` (POSIX atomic on same fs). Bump `_rev` (`-`) чтобы npm clients не закешировали stale packument. +- **2026-05-21:** verdaccio prune skip `versions{}` and `time{}` entries — оставляем metadata in-place даже для удалённых tarballs. Reasoning: npm может re-fetch tarball from upstream при следующем install request (proxy behaviour); metadata weighs ~KB не GB. + +## Open questions + +- [ ] (post-первого live cron run) — phone-verify notification приходит на `vds-ops`. +- [ ] Logrotate для `/var/log/vds-gc/` — пока nope (weekly cadence + лог ~10KB/run = 500KB/year, терпимо). + +## Completed steps + +- [x] recon ntfy creds + verdaccio storage layout (package.json structure: `versions{}`, `_distfiles{}`, `_attachments{}`, `_rev`) +- [x] write `notify.sh`, `registry-gc.sh`, `verdaccio-prune.sh`+`verdaccio-prune.js` +- [x] dry-run verdaccio prune (initial bug discovery — `_distfiles[v]` vs `_distfiles[tgzName]`) +- [x] fix detection → re-dry-run: 4524 tgz / 1.7 GiB candidate +- [x] real verdaccio prune: 8.5G→6.8G, 1551 pkgs modified, 0 errors +- [x] verify verdaccio responsive post-prune (lodash metadata 117 versions) +- [x] push alpine v1+v2 to registry → test GC scenario +- [x] delete manifest via DELETE API → re-run GC → freed 61M, registry restarted +- [x] patch registry-gc.sh — empty-storage guard +- [x] install `/etc/cron.d/vds-gc`, restart cron service ## Notes -- Триггер: после стабилизации VDS bootstrap (`vds-kzntsv-bootstrap` Phase 3 завершена). -- Кронить только write-ops после убеждения что live registry/verdaccio не получит traffic во время GC, иначе риск повредить. +- Verdaccio `_distfiles` keyed by tarball filename (`lodash-0.1.0.tgz`), не version. Gotcha — easy mistake. +- Multi-arch image push через современный buildkit оставляет manifest как OCI index. Для DELETE через API нужно `Accept: application/vnd.oci.image.manifest.v1+json` (или index variants). Иначе registry возвращает 404. +- `_attachments` field optional — на proxied packages обычно ~5-10 entries (только cached tarballs), на locally-published — все версии package. +- `du -sh` (default block-size) vs `du -sb` (bytes) могут rounding-discrepancy на 5-10%. Used `du -sb` в скриптах для precise byte math. +- registry compose mount `./docker:/var/lib/registry` → registry создаёт data в `/var/lib/registry/docker/registry/v2/...` → host path nested `/opt/stacks/registry/docker/docker/registry/v2/...`. Странно но работает.