Следствие закрытия [books-task-runner-registry-auth-cred]. concepts/registry-oci-image-index-gc.md (new): books-* образы в registry = OCI image-index (buildx), top-level .config=null, дата .created в платформенном sub-manifest. Наивный GC по top-level дате → null у всех → null-dated группы защищаются → drop=0 всегда (keepLastN не применяется). Правила: Accept со всеми media-types, дата из sub, DELETE по index-digest не sub. Зафиксировано на books registryGc dryRun (deleted=0 при 14 tags) + манифест-dump books-web. concepts/bindmount-config-edit-preserve-mode.md: дополнен гочей про bind-mount shadow (config образа затенён целиком → полная секция, не дельта) + worked example task-runner (mode не слетел, постмортем сработал). +index.md (2 строки) +log.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.9 KiB
title, type, tags, updated
| title | type | tags | updated | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| Правка bind-mounted config'а — сохранять file mode (иначе EACCES crash-loop у non-root контейнера) | concept |
|
2026-06-18 |
Bind-mounted config: сохраняй mode при правке
Когда правишь config-файл, bind-mounted в контейнер, который бежит под non-root юзером (например books node, uid 1000), атомарная запись через mktemp+mv теряет исходный режим файла и роняет контейнер в crash-loop с EACCES.
Механизм
jq ... > "$(mktemp)" && mv tmp default.json # ← баг
mktemp создаёт файл 0600 root. mv (rename) заменяет inode, перенося права temp-файла на цель — режим назначения НЕ сохраняется (в отличие от cp/install). Исходный был 0644; после правки others-read бит пропал → контейнерный uid 1000 не может открыть файл → падение ещё в загрузчике config'а (node lib config, util.parseFile), до любого прикладного кода:
Error: Config file .../default.json cannot be read. Error code is: EACCES
Правильно
Восстановить mode/owner после mv — проще всего по до-правочному бэкапу (cp -a его сохраняет):
chmod --reference="$backup" "$f" && chown --reference="$backup" "$f"
Альтернативы: install -m 644, либо запись в тот же inode (сохраняет mode):
jq ... > "$f.new" && cat "$f.new" > "$f" && rm "$f.new"
Всегда ls -l после — убедиться, что others-read бит на месте для контейнерного uid.
Инцидент 2026-06-18 (worked example)
Добавлял docker.registryAuth (кред books-ci) в /opt/books/job-scheduler/config/default.json на books-vds через jq>mktemp && mv. Режим слетел 644 → 600 root. На ближайшем деплое books (all-tenant пересоздаёт контейнеры) books-job-scheduler ушёл в Restarting (1) каждые ~60с — прод-даун, slovo-cron'ы стояли. Корень — не код books и не деплой, а мой способ записи.
Фикс: chmod/chown --reference=<backup> → -rw-r--r-- root root. Контейнер сам поднялся на следующей попытке restart-policy (отдельный рестарт не нужен — policy уже крутила). registryAuth остался цел.
Связанная гоча: bind-mount затеняет config-каталог образа ЦЕЛИКОМ
Books-стеки монтируют host-каталог поверх config-пути образа:
/opt/books/task-runner/config → /usr/src/app/packages/task-runner/config (rw)
/opt/books/job-scheduler/config → /usr/src/app/packages/job-scheduler/config
Bind-mount каталога полностью заменяет содержимое целевого каталога — default.json, запечённый в образ, не виден контейнеру. Читается только host'овый файл.
Следствие при добавлении кред-секции: нельзя класть «только дельту» (один password), полагаясь что baseUrl/username придут из образа — их там нет, образный default.json затенён. Кладётся полная секция. На этом спотыкаются, когда таска сформулирована как «в git default.json baseUrl+username уже есть, нужен только password» — в git есть, в эффективном (host) конфиге нет.
Проверка эффективного конфига — изнутри контейнера, не по git:
docker exec <ctr> node -e "const c=require('config'); console.log(c.get('registry.baseUrl'))"
Host config-каталоги книг от 25.05 (миграция в Portainer) и с тех пор не пере-наливались из образа → дрейфуют от git. node-config читает ровно их. Worked example — registry-секция в task-runner (2026-06-18): см. registry-oci-image-index-gc (кред books-ci для registryGc).
Инцидент-2 2026-06-18 (task-runner, без поломки)
Тот же кред-класс (books-ci для registryGc), но в /opt/books/task-runner/config/default.json, секция registry (не docker.registryAuth — другой потребитель). Учтя оба урока выше: бэкап cp -a → jq > .new && cat .new > "$F" (запись в тот же inode, mode сохранён) → ls -l показал -rw-r--r-- root root. Структурный diff: добавлен только ключ registry. Рестарт контейнера (node-config читает на старте) → healthy, EACCES не было. Mode-гоча не повторилась — постмортем сработал.
Связанные uid-гочи
ocis-on-vds-deploy-recipe— UID 1001 vs 1000 на oCIS (тот же класс: контейнерный uid ≠ root, права host-файлов должны это учитывать).registry-kzntsv-auth-model— что за кред клали, когда словили инцидент.