docs(wiki): registry.kzntsv.site auth-model concept + bind-mount config-mode postmortem
- concepts/registry-kzntsv-auth-model.md (new): standalone registry:2 + htpasswd Basic, binary access, NOT Gitea-packages; how to add htpasswd users (hot-reload), GC via v2 DELETE + host garbage-collect; users vitya + books-ci. - concepts/bindmount-config-edit-preserve-mode.md (new): mktemp+mv drops file mode 644->600 -> non-root container (uid 1000) EACCES crash-loop; chmod --reference. Worked example: books-job-scheduler prod-down incident 2026-06-18. - vds-kzntsv entity registry row links the auth-model concept. - index.md + log.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
44
.wiki/concepts/bindmount-config-edit-preserve-mode.md
Normal file
44
.wiki/concepts/bindmount-config-edit-preserve-mode.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Правка bind-mounted config'а — сохранять file mode (иначе EACCES crash-loop у non-root контейнера)
|
||||
type: concept
|
||||
tags: [docker, bind-mount, permissions, gotcha, postmortem, books-vds, eacces]
|
||||
updated: 2026-06-18
|
||||
---
|
||||
|
||||
# Bind-mounted config: сохраняй mode при правке
|
||||
|
||||
Когда правишь config-файл, **bind-mounted в контейнер, который бежит под non-root юзером** (например books `node`, uid 1000), атомарная запись через `mktemp`+`mv` **теряет исходный режим файла** и роняет контейнер в crash-loop с `EACCES`.
|
||||
|
||||
## Механизм
|
||||
|
||||
```bash
|
||||
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` его сохраняет):
|
||||
```bash
|
||||
chmod --reference="$backup" "$f" && chown --reference="$backup" "$f"
|
||||
```
|
||||
Альтернативы: `install -m 644`, либо запись **в тот же inode** (сохраняет mode):
|
||||
```bash
|
||||
jq ... > "$f.new" && cat "$f.new" > "$f" && rm "$f.new"
|
||||
```
|
||||
Всегда `ls -l` после — убедиться, что `others`-read бит на месте для контейнерного uid.
|
||||
|
||||
## Инцидент 2026-06-18 (worked example)
|
||||
|
||||
Добавлял `docker.registryAuth` (кред [`books-ci`](registry-kzntsv-auth-model.md)) в `/opt/books/job-scheduler/config/default.json` на [`books-vds`](../entities/books-vds.md) через `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` остался цел.
|
||||
|
||||
## Связанные uid-гочи
|
||||
|
||||
- [`ocis-on-vds-deploy-recipe`](ocis-on-vds-deploy-recipe.md) — UID 1001 vs 1000 на oCIS (тот же класс: контейнерный uid ≠ root, права host-файлов должны это учитывать).
|
||||
- [`registry-kzntsv-auth-model`](registry-kzntsv-auth-model.md) — что за кред клали, когда словили инцидент.
|
||||
56
.wiki/concepts/registry-kzntsv-auth-model.md
Normal file
56
.wiki/concepts/registry-kzntsv-auth-model.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: registry.kzntsv.site auth model — standalone registry:2 + htpasswd (НЕ Gitea-packages)
|
||||
type: concept
|
||||
tags: [registry, vds-kzntsv, htpasswd, docker, auth, gotcha]
|
||||
sources: [../sources/vds-kzntsv-bootstrap-2026-05-20.md]
|
||||
updated: 2026-06-18
|
||||
---
|
||||
|
||||
# registry.kzntsv.site — auth model
|
||||
|
||||
`registry.kzntsv.site` — это **отдельный `registry:2.8.3` + Joxit UI** на [`vds-kzntsv`](../entities/vds-kzntsv.md) (fresh-install 2026-05-20, см. [bootstrap](../sources/vds-kzntsv-bootstrap-2026-05-20.md) Phase 3.3). Auth — **htpasswd Basic**:
|
||||
|
||||
```
|
||||
GET https://registry.kzntsv.site/v2/ → 401
|
||||
Www-Authenticate: Basic realm="Registry" ← Basic, не Bearer/token
|
||||
```
|
||||
|
||||
## Это НЕ Gitea package registry
|
||||
|
||||
Частая ошибка (books-сессия 2026-06-18 на ней споткнулась): принять реестр за Gitea container-registry. Следствия неверны:
|
||||
|
||||
- **Нет** robot-токенов, scope `read:package`/`delete:package`, per-package visibility, public-read флага. Это не Gitea — таких понятий тут нет.
|
||||
- htpasswd-auth **бинарный**: любой аутентифицированный юзер = full **pull + push + delete** по всем репо. Per-repo ACL нет (без token-auth-сервера, которого тут нет). «Read-only кред» физически невозможен — максимум изоляции = отдельный htpasswd-юзер на потребителя (отзывается точечно).
|
||||
|
||||
## Юзеры (htpasswd)
|
||||
|
||||
Файл: `/opt/stacks/registry/auth/htpasswd` (на vds-kzntsv, `vitya:vitya` rw — sudo не нужен), mount `:ro` в контейнер.
|
||||
|
||||
| Юзер | Кред | Назначение |
|
||||
|---|---|---|
|
||||
| `vitya` | `pass vds-kzntsv/full-env` (Pryakhin9) | master / push с CI |
|
||||
| `books-ci` | `pass vds-kzntsv/registry-books-ci` | books job-scheduler docker-runner pull (createPickingListPdf и др.), заведён 2026-06-18 |
|
||||
|
||||
## Добавить юзера
|
||||
|
||||
Реестр **hot-reload'ит htpasswd при изменении файла — рестарт не нужен** (проверено 2026-06-18). bcrypt-строку проще всего сгенерить через httpd-образ, пароль — через stdin (не в argv):
|
||||
|
||||
```bash
|
||||
printf '%s' "$PW" | docker run --rm -i httpd:2-alpine htpasswd -niB <user> >> /opt/stacks/registry/auth/htpasswd
|
||||
# затем: docker rmi httpd:2-alpine (не оставлять образ на VDS — диск)
|
||||
```
|
||||
|
||||
Проверка: `curl -u <user>:<pw> -I -H 'Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json' https://registry.kzntsv.site/v2/<repo>/manifests/<tag>` → 200. **Важно:** без правильного `Accept` (включая manifest-list + oci) HEAD вернёт 404 даже при существующем теге — это media-type mismatch, не отсутствие образа.
|
||||
|
||||
## GC / cleanup
|
||||
|
||||
Не через Gitea API. Чистка standalone-реестра:
|
||||
- **mark-delete:** registry v2 `DELETE /v2/<repo>/manifests/<digest>` (`REGISTRY_STORAGE_DELETE_ENABLED=true` уже выставлен). Тот же htpasswd-кред (бинарный auth = delete разрешён).
|
||||
- **disk-reclaim:** только `registry garbage-collect` на host'е — см. [`registry-gc-mount-and-modify-flag`](registry-gc-mount-and-modify-flag.md). Это host-cron, не job из приложения. v2 DELETE сам место не возвращает.
|
||||
- Ручная альтернатива — Joxit UI (`DELETE_IMAGES=true`) на `registry-ui.vds.kzntsv.site`.
|
||||
|
||||
## Связь
|
||||
|
||||
- Хост: [`vds-kzntsv`](../entities/vds-kzntsv.md) (registry-строка в hostnames).
|
||||
- Класс ошибки «no basic auth» в Portainer-стеках: тот же бинарный htpasswd → [`portainer-stack-management-vds`](portainer-stack-management-vds.md).
|
||||
- Инцидент при заведении books-ci кред-файла: [`bindmount-config-edit-preserve-mode`](bindmount-config-edit-preserve-mode.md).
|
||||
Reference in New Issue
Block a user