feat(vds-gc-cron): done — weekly registry GC + verdaccio prune armed
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>
This commit is contained in:
12
STATUS.md
12
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
|
||||
|
||||
---
|
||||
|
||||
@@ -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[<tgzName>]` (НЕ `_distfiles[<version>]`). 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` (`<num+1>-<random_hex>`) чтобы 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/...`. Странно но работает.
|
||||
|
||||
Reference in New Issue
Block a user