diff --git a/family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md b/family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md new file mode 100644 index 00000000..10ef4900 --- /dev/null +++ b/family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md @@ -0,0 +1,99 @@ +--- +title: "2026-09-21 — bare repo push Permission denied (root-owned objects/XX)" +aliases: [2026-09-21-vault-bare-repo-acl, push permission denied objects] +tags: [family, vault-sync, incident, truenas, acl, git, midclt] +created: 2026-09-21 +updated: 2026-09-21 +type: incident +namespace: family +--- + +# 2026-09-21 — bare repo push: `Permission denied` на `objects/XX` + +> **Итог:** push починен, **права поставлены с наследованием** (`FILE_INHERIT`+`DIRECTORY_INHERIT`), чтобы дефект не возвращался. Root-пароль на TrueNAS не потребовался — всё через `midclt` API. Полный рецепт: `personal/tech/truenas-nfs4-acl-and-arrmultiuser.md` §«Durable fix with INHERITANCE». + +## Симптом + +``` +error: unable to write file ./objects/80/09a34e…: Permission denied + ! [remote rejected] main -> main (unable to migrate objects to permanent storage) +``` + +Найден при попытке запушить 15 коммитов с Mac (Eagle) → `nas`. Мак опережал bare на 14–15 коммитов, push отклонялся стабильно. + +## Диагноз (два независимых дефекта) + +| # | Дефект | Деталей | +|---|---|---| +| 1 | **13 каталогов `drw-r-x---`** | owner `rw-` **без `x`** → внутри нельзя создать файл. Репо-root, `objects`, `objects/{info,pack}`, `refs{,/heads,/tags}`, `branches`, `hooks`, `info`, `logs{,/refs,/refs/heads}`. Файлы были в порядке (0 без `u+r`). | +| 2 | **28 каталогов `objects/XX/` принадлежали `root`** | ACL `group@:r-x---a-R-c--s` — **только чтение для группы**. git работает от uid 950 (`truenas_admin`), не root → **писать не может**. Именно этот дефект блокировал push. | + +**Кто создал root-owned объекты:** `mcpvault` — Obsidian MCP-сервер **второго инстанса Hermes на TrueNAS** (`/opt/hermes`), запущенный **от root**: + +``` +root 13727 /opt/hermes/.venv/bin/python3 … mcp_stdio_watchdog.py --ppid 9 -- mcpvault /vault +root 13738 node /opt/data/.local/bin/mcpvault /vault +``` + +> ⚠️ Это **НЕ vault Алекса на Mac** (`~/obsidian`). Это чужой/второй инстанс Hermes со своим `/vault`, который пушит в **тот же самый** bare-репозиторий. Коммиты 2026-09-20 21:26 и 22:25 сделаны от root — вероятно, им же. + +## Что НЕ сработало + +- `chown -R 950:950` и `chmod -R g+w` от `truenas_admin` → **`Operation not permitted`** (uid 950 не владелец root-owned файлов). +- `sudo` на TrueNAS **требует пароль** (`sudo -n true` падает). ⚠️ В отличие от **Кра́кена**, где passwordless sudo ЕСТЬ. +- `midclt call filesystem.setperm` → `[EINVAL] Non-trivial ACL present … Option "stripacl" required`. **НЕ использовать `stripacl`** — срывает ACL до POSIX. + +## Что сработало — `midclt filesystem.setacl` + +`truenas_admin` — FULL_ADMIN в midclt, поэтому ACL читается и пишется **без root-пароля**. + +**Ключевое отличие от прошлого ремонта (2026-09-02):** тогда правили отдельные ACE (`group@` +perms). Это чинит *текущие* каталоги, но **баг возвращается**: следующий push root-инстанса создаёт новый `objects/XX/`, который наследует ACL от родителя, и если там нет `WRITE_DATA` + inherit-флагов — новый каталог снова root-owned и read-only. + +**Durable-фикс = полный dacl-REPLACE** с наследованием: + +```python +# perms для owner@ И group@ И everyone@: +# READ_DATA WRITE_DATA APPEND_DATA EXECUTE DELETE_CHILD +# READ_ATTRIBUTES WRITE_ATTRIBUTES READ_NAMED_ATTRS WRITE_NAMED_ATTRS READ_ACL +# (+ WRITE_ACL WRITE_OWNER DELETE для owner@) +# flags на каждом ACE: +# {"FILE_INHERIT": True, "DIRECTORY_INHERIT": True, +# "NO_PROPAGATE_INHERIT": False, "INHERIT_ONLY": False, "INHERITED": False} +# вызов: midclt call filesystem.setacl '<один JSON: path + dacl + options>' +# options = {"recursive": False, "traverse": True, "stripacl": False} +``` + +**Результат:** **291 каталог, 0 ошибок**, уложилось в один SSH-вызов (~2 мин), `nohup` не потребовался. + +### Питфоллы midclt (стоили времени) + +- `filesystem.getacl` и `filesystem.listdir` принимают **голую строку пути**, НЕ `{"path": …}`. Объект → `[EINVAL] path: Not a string`. +- `filesystem.setacl` — **наоборот**: один JSON-объект с `path` ВНУТРИ. Легко перепутать на одном и том же сервисе. +- `setacl`/`setperm` **асинхронные** → возвращают job id. Немедленная проверка даёт `WAITING` и выглядит как провал. Поллить `core.get_jobs` до `SUCCESS`/`FAILED`. При длинном цикле по SSH процесс переживает таймаут сессии — первый зависоний запуск **фактически уже применил ACL**. + +## Верификация (и её предел) + +```bash +cd ~/obsidian && git push nas main +# → 177da1e..3b923d3 main -> main +git rev-parse HEAD; git rev-parse nas/main # оба 3b923d3 +``` + +Два успешных push подряд, хеши Mac и bare совпадают. + +> ⚠️ **Честная оговорка.** Прямой тест записи +> `touch …/objects/20/.wrtest` от `truenas_admin` **не выполнялся** (запрос апрува истёк). +> `git push` доказал, что *текущее* дерево синхронно, но **не** что свежий root-владельческий объект реально записывается. +> Определяющий тест остаётся открытым — см. «Remaining» ниже. + +## Remaining (открытые вопросы) + +1. **Наследование не проверено на практике** — root-инстанс `mcpvault` при следующем пуше создаст объекты; с inherit права должны наследоваться, но это подтвердит только реальный push того инстанса. +2. **`mcpvault` продолжает работать от root** — пока он root, дефект может вернуться. Профилактика: перезапустить его под uid 950. +3. **`cleanup` не видит `/media/movies`** — на Кра́кене удаляются только Radarr-файлы; просмотренное лежит в `/media/movies`, диск будет расти. Отдельная задача (см. `family/plans/watchlist-disk-overflow-fix.md`). + +## Связанные + +- `personal/tech/truenas-nfs4-acl-and-arrmultiuser.md` — полный рецепт + прошлый кейс 2026-09-02 +- `family/how-to/vault-git-sync.md` — топология vault (4 клиента, один bare-хаб) +- `family/how-to/vault-sync-pipeline.md` — три звена синка, ручной прогон diff --git a/family/how-to/vault-git-sync.md b/family/how-to/vault-git-sync.md index 3faefe13..2ec3790c 100644 --- a/family/how-to/vault-git-sync.md +++ b/family/how-to/vault-git-sync.md @@ -1,6 +1,6 @@ --- title: Vault Git Sync — Architecture & Scripts -updated: '2026-09-02' +updated: '2026-09-21' type: tech tags: - vault @@ -12,6 +12,8 @@ tags: # Vault Git Sync +> **STATE 2026-09-21 — bare repo push починен (наследуемый ACL).** `git push nas` падал `Permission denied` на `objects/XX`: 28 каталогов были root-owned (создавал `mcpvault` **второго Hermes на TrueNAS, от root**), 13 каталогов без owner-`x`. Фикс: **полный dacl-REPLACE с `FILE_INHERIT`+`DIRECTORY_INHERIT`** через `midclt filesystem.setacl` — 291 каталог, 0 ошибок, без root-пароля. Push прошёл дважды, Mac=`nas`=`3b923d3`. ⚠️ Прямой write-тест `touch` в `objects/` **не выполнялся** → наследование подтвердит только следующий push root-инстанса. Подробно: `family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md`. +> > **STATE 2026-09-02 (позднее обновление):** Eagle/bare repo снова **синхронны** (ahead=0/behind=0, `2fab879`). Ранее (см. ниже) git-sync стоял из-за битых прав `storage/git` — **починено** полной dacl-заменой (`family/documents/vault-sync/2026-09-02-restore-privilege-scope.md` §RESOLVED). > > **STATE 2026-09-04:** починена потеря git identity в контейнере Taiga (слетела при рекреации после crashloop 09-02) — коммиты молча не проходили ~1.5 суток, изменения телефона не доезжали до bare. Фикс: `sync-vault.sh` теперь идемпотентно ставит `user.name "Taiga"` / `user.email "taiga@hermes"` при каждом запуске. Накопленное закоммичено (`2b45264`). Подробно: `family/documents/vault-sync/2026-09-04-git-identity-loss-silent-commit-failure.md`. @@ -135,10 +137,30 @@ cd ~/Developer/vault-sync-test && bash test-sync.sh - `git add -A` in sparse worktree still stages deletions of out-of-cone files tracked in index — need to unstage them (fixed in old script; irrelevant with proper clone) - `core.quotePath=true` (default) escapes Cyrillic paths in `git ls-files` output — use `-c core.quotePath=false` - ZFS on TrueNAS blocks `chmod` — `git init` fails from host; must run from inside Docker container or create `.git` structure manually -- **NFS4 `owner@ DENY READ_DATA` ACL breaks push (fake "corrupt" objects)** — see History 2026-09-02. Key: after a TrueNAS **pool restore**, a subset of loose git objects in the bare repo can carry an inverted NFS4 ACL `owner@ type=DENY READ_DATA=True`. In NFSv4 a DENY overlays ALLOW, so the owning uid (e.g. 950) can't mmap-read those objects → on receive, `git-receive-pack`/`index-pack` reports `loose object ... is corrupt` and rejects push, even though data is intact (`git fsck --full` under root is clean). Fix = `filesystem.setacl ... {stripacl:true}` (chmod/chown do NOT remove DENY ACEs). Tell-tale: POSIX file mode `40` (`r--------`) on loose objects vs normal `750`. +- **NFS4 `owner@ DENY READ_DATA` ACL breaks push (fake "corrupt" objects)** — see History 2026-09-02. Key: after a TrueNAS **pool restore**, a subset of loose git objects in the bare repo can carry an inverted NFS4 ACL `owner@ type=DENY READ_DATA=True`. In NFSv4 a DENY overlays ALLOW, so the owning uid (e.g. 950) can't mmap-read those objects → on receive, `git-receive-pack`/`index-pack` reports `loose object ... is corrupt` and rejects push, even though data is intact (`git fsck --full` under root is clean). Fix = **full dacl REPLACE** via `filesystem.setacl` (chmod/chown do NOT remove DENY ACEs). Tell-tale: POSIX file mode `40` (`r--------`) on loose objects vs normal `750`. + > ⚠️ **Don't reach for `stripacl`** (2026-09-21): it tears the ACL down to POSIX and does **not** remove a DENY in all cases. Always pass a complete `dacl` array — `setacl` treats it as a full replace. +- **`Permission denied` writing `objects/XX/` (NOT the corrupt-object case)** — see History 2026-09-21. Two defects: (a) dirs with owner `rw-` and **no `x`** (`drw-r-x---`), (b) `objects/XX/` dirs **owned by `root`** with `group@:r-x` — git runs as uid 950 → cannot write. Root cause is `mcpvault` (a **second Hermes on TrueNAS, `/opt/hermes`**, vault `/vault`) running **as root** and creating root-owned objects in the shared bare repo. Requires `midclt` (see below) — `chown`/`chmod` as `truenas_admin` give `Operation not permitted` because uid 950 isn't the owner, and TrueNAS `sudo` **needs a password** (unlike Kraken, where `sudo -n` works). Fix must include **`FILE_INHERIT`+`DIRECTORY_INHERIT`** or the defect returns on the next push by the root instance. Recipe: `personal/tech/truenas-nfs4-acl-and-arrmultiuser.md` §«Durable fix with INHERITANCE». + +## ACL repair via `midclt` (no root password needed) + +`truenas_admin` is FULL_ADMIN in midclt → can read/write ACLs directly. + +```bash +# READ — path as a BARE STRING +midclt call filesystem.getacl /mnt/RED_2TB/storage/git/obsidian-vault.git +midclt call filesystem.listdir /mnt/RED_2TB/storage/git/obsidian-vault.git/objects + +# WRITE — ONE json object with "path" INSIDE it (opposite convention!) +midclt call filesystem.setacl '{"path":"/x","acltype":"NFS4","dacl":[…], + "options":{"recursive":false,"traverse":true,"stripacl":false}}' +``` + +Pitfalls: `setacl`/`setperm` are **async** (return a job id) — poll `core.get_jobs` until `SUCCESS`/`FAILED`, or the immediate `WAITING` reads as a failure. `filesystem.setperm` refuses when a non-trivial ACL is present (`[EINVAL] … Option "stripacl" required`). ## History +- **2026-09-21**: bare repo push `Permission denied` on `objects/XX`. 28 dirs root-owned (created by `mcpvault` of a **second Hermes on TrueNAS** running as root) + 13 dirs with owner `rw-` and no `x`. `chown`/`chmod` as `truenas_admin` → `Operation not permitted` (TrueNAS sudo needs a password, unlike Kraken). Fixed with a **full dacl REPLACE + `FILE_INHERIT`/`DIRECTORY_INHERIT`** via `midclt filesystem.setacl` — **291 dirs, 0 errors**, no root password. Two clean pushes after; Mac = `nas` = `3b923d3`. ⚠️ Direct write-test in `objects/` was NOT run. Full log: `family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md`. + - **2026-06-20**: Kraken ACL fix. mcpvault (Hermes контейнер, uid 10000) не мог писать в существующие файлы vault из-за прав (EACCES). Подробно: **Проблема 1 — права доступа:** файлы в vault создаются от uid 1000 (kraken через git на хосте), а mcpvault в контейнере работает от uid 10000. `patch_note` падал: `EACCES: permission denied`. `delete_note` + `write_note` срабатывали, но mcpvault при write перезаписывает файл целиком, переформатируя YAML фронтматер (убирает кавычки, меняет стиль) — шум в git-diff. diff --git a/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md b/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md index f01c71e0..1a1861cb 100644 --- a/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md +++ b/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md @@ -9,6 +9,7 @@ related: - "[[truenas-zfs-panic-recovery]]" - "[[arr-stack-taiga]]" - "[[watchlist-disk-overflow-fix]]" + - "[[2026-09-21-bare-repo-push-permission-denied]]" --- # TrueNAS NFS4 ACL repair + cross-container media PUID @@ -84,7 +85,11 @@ To ssh://…/obsidian-vault.git 1. **13 dirs had `drw-r-x---`** (owner `rw-` **without `x`**): repo root, `objects`, `objects/info`, `objects/pack`, `refs{,/heads,/tags}`, `branches`, `hooks`, `info`, `logs{,/refs,/refs/heads}`. Without owner-`x` nothing can be created inside. Fixed with `chmod u+x` on dirs only (files were all fine: `0 files without u+r`). 2. **28 dirs `objects/XX/` were owned by `root`** with ACL `group@:r-x---a-R-c--s` (read-only for the group). Git runs as uid 950 (`truenas_admin`), not root → **cannot write**. This is the defect that actually blocked the push. -**Why it happened:** `mcpvault` (the Obsidian MCP server) runs **as root** — `/opt/hermes/.venv/bin/python3 … mcp_stdio_watchdog.py … mcpvault /vault` (`ps aux` shows uid `root`). Every object it wrote landed root-owned, and `objects/XX/` dirs created by it were root-owned too. **The problem WILL recur** unless mcpvault is restarted as uid 950. Left as an open item. +**Why it happened:** `mcpvault` runs **as root** on TrueNAS — `/opt/hermes/.venv/bin/python3 … mcp_stdio_watchdog.py … mcpvault /vault` (`ps aux` shows uid `root`). Every object it wrote landed root-owned, and `objects/XX/` dirs created by it were root-owned too. + +> ⚠️ **Do not confuse this with the Mac's vault.** This `mcpvault` belongs to a **second Hermes instance living on TrueNAS itself** (`/opt/hermes`), whose vault is `/vault` — **not** Alex's `~/obsidian` on the Mac. It is not "the agent's own MCP server". Both instances merely push to the **same shared bare repo**, which is why the collision happens. PID refs seen 2026-09-21: 13727 (`mcp_stdio_watchdog.py`), 13738 (`node … mcpvault /vault`). + +With the inheritance fix below, root-owned objects are no longer fatal (rights are inherited from the now-writable parent), but **the root instance is still a latent hazard** — restarting it as uid 950 removes the cause. Left as an open item. **What does NOT work:** - `chown -R 950:950` and `chmod -R g+w` via SSH as `truenas_admin` → **`Operation not permitted`** (uid 950 is not the owner of root-owned objects). `sudo` on TrueNAS **requires a password** (`sudo -n true` fails; unlike Kraken where `sudo -n` is passwordless).