From 15c273c153fc257d41b6a1aa1750bf59dfa503d2 Mon Sep 17 00:00:00 2001 From: vitya Date: Thu, 11 Jun 2026 17:10:01 +0300 Subject: [PATCH] =?UTF-8?q?wiki(ingest):=20concepts/verdaccio-restore-pack?= =?UTF-8?q?ument-desync=20=E2=80=94=20409=20postmortem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit disaster-restore вернул тарболлы но древний/пустой packument → publish свежей версии EEXISTS 409. Fix: снять только коллизирующий целевой .tgz (backup first), republish; НЕ rm -rf каталог. Применено к @snollajs/{data,mailer,numbering,content-api}. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../verdaccio-restore-packument-desync.md | 77 +++++++++++++++++++ .wiki/index.md | 1 + .wiki/log.md | 1 + 3 files changed, 79 insertions(+) create mode 100644 .wiki/concepts/verdaccio-restore-packument-desync.md diff --git a/.wiki/concepts/verdaccio-restore-packument-desync.md b/.wiki/concepts/verdaccio-restore-packument-desync.md new file mode 100644 index 0000000..8d74ed1 --- /dev/null +++ b/.wiki/concepts/verdaccio-restore-packument-desync.md @@ -0,0 +1,77 @@ +--- +title: Verdaccio disaster-restore desync — tarball present, packument stale → EEXISTS 409 +type: concept +tags: [verdaccio, npm, storage, restore, backup, 409, gotcha, postmortem] +sources: [] +updated: 2026-06-11 +--- + +# Verdaccio disaster-restore desync — 409 при publish свежей версии + +## Симптом + +После disaster-restore verdaccio из бэкапа: `npm/yarn publish @` падает с +**`409 "this package is already present"`**, хотя в реестре этой версии (по GET) нет — +`dist-tags.latest` показывает древнюю версию. На некоторых пакетах GET вообще `404`. + +## Root cause: packument ↔ tarball рассинхрон + +Restore вернул в `/storage/...//`: +- **все `.tgz` тарболлы** — до текущих версий включительно (`data-0.9.0.tgz` физически на диске); +- но **`package.json` (packument-индекс) — древний** (`versions{}` знает только 0.1.x/0.2.x, + `dist-tags.latest=0.2.0`), потому что метадата восстановилась из старого слоя бэкапа + либо была затёрта сорванным `unpublish`. + +Verdaccio publish-flow: +1. merge новой версии в packument → **OK** (версии нет в индексе); +2. запись `.tgz` на диск → файл **уже существует** (с restore) → `EEXISTS` → + surface как **`409 this package is already present`**. + +Лог-отпечаток: +``` +PUT /@snollajs%2Fdata +http <-- 409 ... error: this package is already present +``` + +### Подвид: пустой packument после сорванного unpublish + +`unpublish` пишет пустой packument-stub (161 байт, `versions:[]`, `_rev:""`), затем пытается +`rmdir` каталог → **`ENOTEMPTY`** (тарболлы на месте) → `500`. Итог: GET `404` (пустой индекс), +publish `409` (тарболл цел). Каталог «полупустой»: индекс мёртв, бинарники живы. + +## Fix: хирургия, НЕ `rm -rf` каталога + +`rm -rf /storage/.../` снимет 409, но **уничтожит всю историю версий** (тарболлы — +locally-published, `_distfiles:{}` ⇒ единственная копия, см. [[verdaccio-prune-semantics]]). + +Правильно — удалить **только коллизирующий целевой `.tgz`** под версию, которую republish'ат: + +```bash +# backup сначала +sudo tar czf /tmp/verdaccio-rescue-$(date -u +%Y%m%dT%H%M%SZ).tar.gz +# снять коллизию — ровно целевой тарболл, остальное не трогать +sudo rm /opt/stacks/verdaccio/storage/@scope//-.tgz +# → republish @ из source проходит, packument регенерится корректно +``` + +Рестарт контейнера **не нужен**: local-storage читает packument/тарболл с диска пер-реквест; +packument не трогаем, только убираем лишний `.tgz`, которого в индексе и так нет. + +**Trade-off:** промежуточные версии (тарболлы на диске, но не в индексе после republish) остаются +orphaned — безвредны, не ставятся по pin, GC их не трогает (locally-published). Если нужна полная +история в индексе — отдельный скрипт-reconstruct packument из всех on-disk тарболлов +(`tar xzO package/package.json` per tgz + shasum/integrity) — heavier, по умолчанию не делаем. + +## Применено 2026-06-11 + +`@snollajs/{data@0.9.0, mailer@0.7.4, numbering@0.7.4, content-api@0.8.1}` — удалены 4 +коллизирующих тарболла, бэкап `/tmp/verdaccio-snollajs-rescue-20260611T140801Z.tar.gz` на +[[../entities/vds-kzntsv]]. Republish делегирован владельцу пакетов (сессия snolla). +Контекст: восстановление verdaccio после wipe (см. также [[verdaccio-token-lifecycle]] — другой +класс отказа того же restore-эпизода). + +## Связанные + +- [[verdaccio-prune-semantics]] — locally-published vs proxied, почему `rm` тарболла = permanent loss +- [[verdaccio-token-lifecycle]] — restart trap / ephemeral secret (соседний симптом того же restore) +- [[../entities/vds-kzntsv]] — хост diff --git a/.wiki/index.md b/.wiki/index.md index bd43afe..0b29efb 100644 --- a/.wiki/index.md +++ b/.wiki/index.md @@ -50,6 +50,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever - [vbox-windows-stability-tuning](concepts/vbox-windows-stability-tuning.md) — VirtualBox + Windows-гость — нюансы стабильности cross-hypervisor миграции - [vds-kzntsv-dhcp-outage-2026-05-28](concepts/vds-kzntsv-dhcp-outage-2026-05-28.md) — DHCP outage 2026-05-28 на vds-kzntsv — диагностика + recovery runbook (link UP но без IPv4 → статика + тикет хостеру) - [verdaccio-prune-semantics](concepts/verdaccio-prune-semantics.md) — Verdaccio prune semantics — proxied vs locally-published +- [verdaccio-restore-packument-desync](concepts/verdaccio-restore-packument-desync.md) — disaster-restore: тарболл на диске + древний packument → EEXISTS 409; fix = снять коллизирующий .tgz, не rm -rf каталог - [verdaccio-token-lifecycle](concepts/verdaccio-token-lifecycle.md) — restart trap (ephemeral secret) + max_users:-1 pnpm 409 + JWT fix + yarn clients - [winacme-iis-owin-catchall-http01](concepts/winacme-iis-owin-catchall-http01.md) — win-acme HTTP-01 авто-renewal LE на IIS под OWIN-catch-all CMS — challenge в отдельном No-Managed-Code приложении + патч шаблона; заменяет ручной PFX-импорт - [wd40efax-smr-cascade](concepts/wd40efax-smr-cascade.md) — WD40EFAX SMR Cascade — root cause NAS failure 2026-05-18 diff --git a/.wiki/log.md b/.wiki/log.md index d643f88..109d5b6 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -2,6 +2,7 @@ Append-only log of wiki operations (ingests, promotions, lints, migrations). +## [2026-06-11] ingest | verdaccio-restore-packument-desync — postmortem: disaster-restore вернул тарболлы но древний/пустой packument → publish свежей версии EEXISTS 409. Fix: снять только коллизирующий целевой .tgz (бэкап first), republish; НЕ rm -rf каталог (убьёт locally-published историю). Применено к @snollajs/{data,mailer,numbering,content-api}. ## [2026-06-11] ingest | verdaccio-token-lifecycle — restart trap (ephemeral secret → 401), max_users:-1 + pnpm 409 постмортем, JWT config fix, yarn classic vs berry token refresh recipe. ## [2026-06-11] update | MSSQL backup gap closed — `/opt/stacks/backup/scripts/run.sh` расширен: 5 prod DBs через `docker exec mssql sqlcmd BACKUP DATABASE ... WITH COPY_ONLY, INIT` (Express, без COMPRESSION); .bak в bind-mount → mv в DUMP_DIR → rsync kreknin. Обновлён [[concepts/mssql-on-vds]] (TODO→реализовано).