[2026-09-26] eagle: family/documents/vault-sync/2026-09-26-syncthing-clone-root-objects-acl-confusion.md family/how-to/vault-git-sync.md personal/plans/balda-mm-phase4-agents.md personal/tech/docker_colima_setup.md personal/tech/macos-docker-virtiofs-mount-limits.md personal/tech/truenas-nfs4-acl-and-arrmultiuser.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 15:52:55 +06:00
1 parent 8efe5c8970
commit f482778257
6 files changed
+330 -25

No files matched your search

@@ -0,0 +1,144 @@
---
title: obsidian-syncthing — root-owned objects + битый remote + путаница ACL/chown
type: log
namespace: family
tags: [vault, sync, syncthing, git, permissions, acl, nfsv4, root]
date: 2026-09-26
---
# `obsidian-syncthing`: 4031 root-объект, битый remote, ACL/chown не стоит
**Когда:** 2026-09-26, разбор по запросу Alex «почему документ не доехал до телефона».
**Статус:** диагностика завершена, **фиксы НЕ применены** — ждём решения Alex.
## Симптом
Нота `personal/plans/music-tags-review/1-needs-decision.md` закоммичена и запушена с Mac
(`66d4d62..f2501dc main -> main`), но в клон `/obsidian-syncthing` на TrueNAS **не попала**.
`git pull` в клоне падает, а `sync-vault.sh` глушит ошибку (`2>/dev/null`) — синк выглядит
успешным.
## Две независимые поломки
### 1. Битый remote в клоне (починено в этой сессии)
```
было: origin /vault.git ← потерян префикс пути
стало: origin /mnt/RED_2TB/storage/git/obsidian-vault.git
```
`git fetch` при этом падал с `fatal: '/vault.git' does not appear to be a git repository`.
Remote переписан через `git remote set-url`. **Это не связано с правами** — отдельный дефект.
### 2. Права: новые объекты пишет root (НЕ починено)
```
объекты от root: 4051 ← свежие, 2026-09-26 02:42
объекты от truenas_admin: 4 ← только старые pack с 2026-07-06
```
`git fetch` падает на записи:
```
error: insufficient permission for adding an object to repository database .git/objects
fatal: failed to write object
fatal: unpack-objects failed
```
**Причина:** контейнер `hermes-taiga` работает **от root** (`docker inspect` → `Config.User`
пусто, `docker exec ... id` → `uid=0(root)`). Именно он делает `git pull` в
`/obsidian-syncthing` — значит каждый новый loose-объект рождается `root:root`, а git под
`truenas_admin` (uid 950) писать в них не может.
## ACL на `obsidian-syncthing` НЕ СТОИТ — `trivial_acl: true`
```
nfs4xdr_getfacl /mnt/RED_2TB/storage/obsidian-syncthing
# owner: 950 # group: 950 # mode: 0o40770
# trivial_acl: true ← расширенного ACL нет
owner@:rwxpD-aARWcCos:-------:allow
group@:rwxpD-a-R-c--s:-------:allow
everyone@:------a-R-c--s:-------:allow
```
Только дефолтный набор TrueNAS (`owner@` / `group@` / `everyone@`), **ни одной именованной
записи**. POSIX-вид (`getfacl`) показывает `user::rwx group::rwx other::---` и **скрывает**
отсутствие расширенного ACL — поэтому диагностировать надо `nfs4xdr_getfacl`, а не `getfacl`.
## 🔴 Путаница: ACL ставился на `Edu`, не на `obsidian-syncthing`
Alex помнил, что фикс ACL уже делался («ты вроде ставил рекурсивный acl а не chown»).
Проверка по базе Hermes (`state.db`, таблица `messages`) нашла сессию
**`20260923_124118_9e70782e` (2026-09-23)** — там рекурсивный ACL **действительно ставился**,
но на **`/mnt/RED_2TB/Edu`**, по прямой просьбе Alex: «ты можешь просто группу под acl
сделать с inherit чтобы весь edu был доступен группе включающей truenas_admin?»
Что тогда было сделано (и сработало — на `Edu`):
```bash
# 1. бэкап ACL
midclt call filesystem.getacl /mnt/RED_2TB/Edu > .../acl-edu-before-YYYYmmdd-HHMMSS.json
# 2. setacl recursive: gid -> 3000 (nas_users) + INHERIT на всех ACE → job SUCCESS
# 3. chgrp -R nas_users → FAILED (Operation not permitted)
# 4. повтор через midclt setacl с uid/gid=3000 → job 15393 SUCCESS
```
Результат на `Edu`: `drwxrwxr-x root:nas_users`, `gid=3000`, все ACE `INHERITED: True`,
`touch` → WRITE_OK. **Группа `nas_users` (gid 3000)** — рабочая: в ней `truenas_admin`,
`mallexxx`, `nas`, `root`, `transmission`.
> ⚠️ **Урок: «ACL уже ставили» ≠ «ACL стоит на этой папке».** Правка была на `Edu`.
> На `obsidian-syncthing` расширенного ACL **не было никогда** (`trivial_acl: true` доказывает).
> Прежде чем повторять «известный фикс» — проверить, что он на **той самой** папке.
> 🔴 **`chgrp` от `truenas_admin` даёт `Operation not permitted`.** В NFSv4 `group@` — это
> POSIX-gid файла, а не ACE. Без смены gid группа не получает ничего. Менять gid надо через
> `midclt filesystem.setacl` (root-контекст), а не `chgrp`/`chown` из шелла.
## Почему однократный `chown` не лечил
Прошлый разовый `chown` объектов на `truenas_admin` чинил только **уже существующие** объекты.
Следующий `git pull` от root снова рождал root-owned объекты — и `chown` приходилось повторять.
**Это лечение симптома.** Перманентный фикс — либо `ACL + INHERIT` (новые объекты наследуют
права группы), либо запуск синка **не от root** (`user: "950:950"` в taiga service).
## Что предлагается (решение за Alex)
| Вариант | Как | Плюс / минус |
|---|---|---|
| **A. ACL + INHERIT** | `setfacl`/`midclt setacl` с `g:truenas_admin:rwx` + `d:` (default) + `chmod g+s` | root и uid 950 пишут одновременно; нужен контроль, что TrueNAS не сбросит ACL при операциях датасета |
| **B. Контейнер не от root** | `user: "950:950"` в taiga service `/mnt/RED_2TB/docker/hermes/docker-compose.yml` | устраняет корень; риск — образ может требовать root для `/opt/data` |
| **C. Повторный `chown`** | перед каждым pull | НЕ рекомендуется — лечит симптом |
## Сопутствующий дефект: скрипт глушит ошибки
`sync-vault.sh` / `sync-vault-lib.sh` вызывают `git fetch` и `git merge` с `2>/dev/null`.
При падении синк рапортует **«Pushed ✓» / exit 0**, хотя pull не прошёл. Это и скрывало
поломку. Предложение — убрать `2>/dev/null` с `fetch`/`merge`, чтобы падение было видно в логе
(та же логика, что уже описана для launchd: `exit code 0` ≠ «синк прошёл»).
## Диагностика (воспроизвести)
```bash
# владельцы объектов в клоне
ssh truenas_admin@mallexxx.duckdns.org \
"cd /mnt/RED_2TB/storage/obsidian-syncthing && find .git/objects -type f -printf '%u\n' | sort | uniq -c"
# реальный ACL (NFSv4, не POSIX!)
nfs4xdr_getfacl /mnt/RED_2TB/storage/obsidian-syncthing # trivial_acl: true?
# под кем реально работает контейнер
docker inspect hermes-taiga --format '{{.Config.User}}' # пусто => root
docker exec hermes-taiga id # uid=0(root)
# remote клона
cd /mnt/RED_2TB/storage/obsidian-syncthing && git remote -v
```
## Связанные документы
- `family/how-to/vault-git-sync.md` — архитектура и питфоллы синка
- `family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md` — тот же
root-инстанс Hermes, но на **bare repo**
- `family/documents/vault-sync/2026-09-26-mode-flip-644-755.md` — шум прав от Syncthing
- `personal/tech/truenas-administration` (skill) — NFSv4 ACL через `midclt`
+21
View File
@@ -25,6 +25,21 @@ tags:
> реальная доставка до телефона — в пределах тика. Перезапись `schedule_display` через
> `hermes cron edit 29851c413903 --schedule '*/5 * * * *'` — на решение Алексея (косметика, на исполнение не влияет).
> **🔴 STATE 2026-09-26 (later) — `obsidian-syncthing` НЕ получает обновления: root-owned objects + битый remote.**
> Нота `1-needs-decision.md` была запушена с Mac (`66d4d62..f2501dc`), но в клон
> `/obsidian-syncthing` **не доехала**. Два независимых дефекта:
> **(1)** `origin` в клоне был **`/vault.git`** (потерян префикс пути) → `git fetch` падал
> `does not appear to be a git repository`. Переписан на полный путь `git remote set-url`.
> **(2)** **4051 из 4055 объектов принадлежат `root`** — контейнер `hermes-taiga` работает от
> root (`Config.User` пусто, `id` → `uid=0`), он и делает pull → новые объекты root-owned,
> git под uid 950 не может писать (`insufficient permission for adding an object`).
> **ACL на `obsidian-syncthing` НЕ СТОИТ** — `nfs4xdr_getfacl` показывает `trivial_acl: true`
> (только `owner@`/`group@`/`everyone@`, ни одной именованной записи). Рекурсивный ACL с группой
> ставился ранее (2026-09-23), но на **`/mnt/RED_2TB/Edu`**, не на эту папку.
> 🔴 **Урок: «ACL уже ставили» ≠ «ACL стоит на этой папке» — проверять фактом.**
> Разовый `chown` лечит только уже существующие объекты; следующий pull от root возвращает дефект.
> Фиксы НЕ применены. Подробно: `family/documents/vault-sync/2026-09-26-syncthing-clone-root-objects-acl-confusion.md`.
>
> **🔴 STATE 2026-09-26 — РАСХОЖДЕНИЕ РАСПИСАНИЯ: Taiga-сервер синкается РАЗ В ЧАС, не 5 минут.**
> Job `sync-vault` внутри контейнера `hermes-taiga` (Hermes cron, id `29851c413903`) расписан
> **`0 * * * *`** — то есть раз в час, **хотя таблица ниже (и `family/how-to/obsidian-sync`)
@@ -200,6 +215,12 @@ cd ~/Developer/vault-sync-test && bash test-sync.sh
## Pitfalls
- **`nfs4xdr_getfacl`, а НЕ `getfacl`, если проверяешь наличие расширенного ACL** (2026-09-26). POSIX-вид (`getfacl`, `ls -l`) показывает `user::rwx group::rwx` и **скрывает** отсутствие именованных ACE. Только `nfs4xdr_getfacl` печатает `trivial_acl: true` — что значит «кастомного ACL нет». Все диагностики прав на TrueNAS начинать с него.
- **`chgrp` / `chown` от `truenas_admin` → `Operation not permitted`** (2026-09-26). В NFSv4 `group@` — это POSIX-gid файла, а не ACE: без смены gid группа не получает доступа. Менять gid/uid только через `midclt filesystem.setacl` (root-контекст). TrueNAS `sudo` требует пароль (в отличие от Kraken, где `sudo -n` работает).
- **Разовый `chown` — лечение симптома, не фикс** (2026-09-26). Если root-процесс продолжает писать в папку, root-owned объекты вернутся на следующем же запуске. Перманентно лечит только `ACL`+`INHERIT` (наследование новыми объектами) либо запуск процесса **не от root**.
- **«Этот фикс уже делали» — проверить, что он на ТОЙ САМОЙ папке** (2026-09-26). Рекурсивный ACL с группой `nas_users` ставился 2026-09-23, но на `/mnt/RED_2TB/Edu`. На `obsidian-syncthing` ACL не было никогда. Каждый «известный фикс» верифицировать фактом на целевом объекте, а не памятью. Искать прошлые сессии: `sqlite3 ~/.hermes/hermes-whale/state.db "SELECT session_id, substr(content,1,300) FROM messages WHERE content LIKE '%setfacl%' ORDER BY id DESC LIMIT 40;"`.
- **Клон может иметь битый `origin`** (2026-09-26). `obsidian-syncthing` имел `origin = /vault.git` (потерян префикс пути) → `fatal: does not appear to be a git repository`. Проверять `git remote -v` в клоне до диагностики прав — это независимый дефект, который выглядит как проблема доступа.
- **`sync-vault-lib.sh` глушит ошибки (`2>/dev/null` на fetch/merge)** → «Pushed ✓» / `exit 0` при фактически упавшем pull. Это уже второй раз, когда поломка синка была не видна в логе. Диагностировать по `git status`/`git log` в клоне, не по exit-code.
- `git add pathspec` fails with `fatal: pathspec did not match any files` when a directory doesn't exist on disk — use `git add -A` instead
- `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`
+83 -20
View File
@@ -1,6 +1,8 @@
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
> План. **Работа НЕ начата** (образ не собран, контейнер не запущен).
> План. **Шаг 1 выполнен** (2026-09-26): образ собран, контейнер `balda-executor` Up (healthy),
> `claude 2.1.283` + `codex 0.157.1` внутри, монтирования проверены в обе стороны.
> ⏳ Осталось в шаге 1: логин CLI (нужен апрув на копирование auth-файлов).
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фазы 4-5).
> Предыдущее: [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3 ✅ — бот отвечает Alex).
@@ -15,6 +17,7 @@
| 3 | Управление линками в `~/agents` | Новая секция «Agents» в Eagle Dashboard |
| 4 | Auth CLI (claude/codex) | на хосте, `~/cli-auth`, **вне** `~/agents` |
| 5 | Сеть Kraken → Mac | Cloudflare Tunnel (существующий cloudflared на Mac) |
| 6 | Провайдер исполнителя (открытый п.1) | `claude` — оба CLI стоят в образе, переключение через `EXECUTOR_PROVIDER` в `.env` |
---
@@ -68,6 +71,13 @@ diff + тесты + коммит → Балда → Mattermost
| Codex auth | `~/cli-auth/codex` | `/root/.codex` | 0700 |
| OAuth-токен Claude | `~/Docker/balda-executor/.env` | env | 0600 |
📌 **Основной путь авторизации — не `claude setup-token`, а копия auth-файлов** из
`~/.claude`/`~/.codex`. Токен в `.env` — запасной вариант для headless без монтирования.
Выбранный вариант: копируются `~/.claude/.credentials.json` + `~/.claude/.claude.json` и
`~/.codex/auth.json` + `~/.codex/config.toml`. Полные каталоги **не** копируются:
`~/.codex` содержит ~640 МБ sqlite-логов/состояний (`logs_2.sqlite`, `state_5.sqlite`,
`memories_1.sqlite`, `queue_1.sqlite`) — исполнителю не нужны, а вес и шум дают.
Пересборка образа и обновление CLI auth не сбрасывают. Токены вне `/agents` → агент их не видит.
---
@@ -111,26 +121,77 @@ GET /api/agents/{name}/usage → размер
## 6. Шаги
### Артефакты, уже созданные на диске (2026-09-26)
### ✅ Шаг 1 ВЫПОЛНЕН (2026-09-26)
Созданы **до** подтверждения плана — статус «черновик», при старте сверить и доделать:
Артефакты на диске (все сверены и рабочие; черновики от 15:36 доведены до состояния «собрано и запущено»):
| Путь | Что |
|---|---|
| `~/agents` | рабочая область (drwxr-xr-x, `admin:staff`) |
| `~/cli-auth/{claude,codex}` | auth CLI, 0700, **вне** `~/agents` |
| `~/Docker/balda-executor/Dockerfile` | Node 24 + claude + codex + git, HEALTHCHECK |
| `~/Docker/balda-executor/docker-compose.yml` | 4 тома, сеть `executor-net`, лимит 8G |
| `~/Docker/balda-executor/.env` | ⏳ не создан (токен) |
| Путь | Что | Состояние |
|---|---|---|
| `~/agents` | рабочая область (`admin:staff`) | ✅ монтируется в `/agents`, запись в обе стороны проверена |
| `~/cli-auth/{claude,codex}` | auth CLI, 0700, **вне** `~/agents` | ✅ монтируются в `/root/.claude`, `/root/.codex` |
| `~/Docker/balda-executor/Dockerfile` | `node:24-bookworm` + claude + codex + git + `tini` | ✅ исправлен (см. ниже) |
| `~/Docker/balda-executor/docker-compose.yml` | 3 тома, сеть `executor-net`, `build:` | ✅ исправлен (см. ниже) |
| `~/Docker/balda-executor/agents-mounts.yaml` | манифест внешних проектов (пустой список) | ✅ создан |
| `~/Docker/balda-executor/tools/render-mounts.py` | генератор `compose.override.yaml` | ✅ создан, `--check` работает |
| `~/Docker/balda-executor/compose.override.yaml` | **генерируется**, руками не править | ✅ сгенерирован |
| `~/Docker/balda-executor/.env` | 0600, `EXECUTOR_PROVIDER=claude` | ✅ создан (без секретов) |
`docker-compose.yml` ссылается на `compose.override.yaml`, который генерирует
`tools/render-mounts.py` из `agents-mounts.yaml` — **override вручную не править**.
Ни образ не собран, ни контейнер не запущен.
**Что уже работает (проверено):**
```
balda-executor Up (healthy) balda-executor:0.1.0
claude → 2.1.283 (Claude Code)
codex → codex-cli 0.157.1
```
`~/agents` ↔ `/agents` пишется в обе стороны; рестарт контейнера состояние сохраняет;
auth-каталоги хоста видны внутри.
**1. `~/agents` + `~/cli-auth` + контейнер (~2 ч)**
`mkdir ~/agents`, `~/cli-auth/{claude,codex}` 0700 → `~/Docker/balda-executor/Dockerfile`
(Node 24 + claude + codex + git) → `docker-compose.yml` с 4 томами → `claude setup-token` в `.env` 0600 →
проверка `claude --version`, `codex --version`, повторный `up -d` остаётся залогинен.
#### 🔴 Три исправленных дефекта черновика (не повторять)
| Дефект | Симптом | Фикс |
|---|---|---|
| `env_file: .env` без файла | `docker compose up` падает на старте | `.env` создаётся **первым** шагом, 0600 |
| Нет `compose.override.yaml` | `up` падает на монтировании несуществующего пути | `tools/render-mounts.py` запускается **до** `up` |
| `FROM node:24-bookworm` без явных CA | — | `ca-certificates` + `update-ca-certificates` в Dockerfile (условие работы HTTPS у CLI) |
#### ⚠️ На macOS этой машины — `docker-compose`, НЕ `docker compose`
Docker CLI 29.4.1 + buildx как плагин (`~/.docker/cli-plugins/`), а compose — **отдельный бинарь**
`/opt/homebrew/bin/docker-compose` (v5.1.3) с симлинком в `/opt/homebrew/lib/docker/cli-plugins/`.
`docker compose` → `docker: unknown command: docker compose`; `docker compose config --quiet` →
`unknown flag: --quiet`.
📌 **Все команды в этом проекте — через `docker-compose`**, включая `docker-compose config` (без `--quiet`).
#### Сборка образа (сработавшая команда)
```bash
cd ~/Docker/balda-executor
python3 tools/render-mounts.py --check # 0 записей, ошибок нет
python3 tools/render-mounts.py # → compose.override.yaml
docker-compose config --services # → executor (валидация)
docker buildx build --builder arm64builder --platform linux/arm64 \
--provenance=false --load -t balda-executor:0.1.0 .
docker-compose up -d
docker-compose restart # проверка переживания рестарта
```
⚠️ Билдер — `arm64builder` (colima, `docker-container` driver). Дефолтный билдер
(`default` → `/var/run/docker.sock`) в статусе `error` — не использовать.
⚠️ `deploy.resources.limits.memory: 8G` из черновика **убран**: в не-swarm compose
эта секция молча игнорируется, лимит создавал ложное ощущение защиты.
#### ⏳ Осталось в шаге 1: логин CLI (ждёт апрува)
`~/cli-auth/claude` пуст, в `~/cli-auth/codex` только `tmp/`. Копировать **только auth-файлы**,
не целые каталоги (`~/.codex` ≈ 640 МБ служебных sqlite — агенту не нужны):
| CLI | Хост | Куда |
|---|---|---|
| claude | `~/.claude/.credentials.json`, `~/.claude/.claude.json` | `~/cli-auth/claude/` |
| codex | `~/.codex/auth.json`, `~/.codex/config.toml` | `~/cli-auth/codex/` |
📌 В `~/cli-auth` кладётся **копия** (оригиналы на месте, `~/.claude` не переезжает):
у агента-исполнителя свои токены, изолированные от рабочих. Права `~/cli-auth` = 0700.
**2. Мост (~3-4 ч)**
`~/Developer/agents-bridge/`, FastAPI :8790, `POST /task` (idempotency), `GET /task/{id}`, `GET /healthz`;
@@ -167,9 +228,11 @@ MCP-инструмент в Балде → `POST /task`; проба `echo ok > /
## 8. Открыто
1. Провайдер исполнителя — claude или codex
2. Нужен ли `ask`-апрув в будущем
3. Hostname для моста (`agents-bridge.qentra.top`?)
1. ~~Провайдер исполнителя — claude или codex~~ → **claude** (оба в образе, переключение `EXECUTOR_PROVIDER`)
2. ~~Нужен ли `ask`-апрув в будущем~~ → при `allow_all` не нужен; при потребности начинать с варианта B (апрув в мосте, ~0.5 дня)
3. Hostname для моста (`agents-bridge.qentra.top`?) — ждёт шага 3
4. ⏳ **Апрув на копирование auth-файлов CLI** в `~/cli-auth` — блокирует завершение шага 1
5. Порт моста `8790` — проверено, свободен (`lsof -nP -i :8790`)
## Связанные заметки
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
+15 -1
View File
@@ -111,10 +111,21 @@ cat /tmp/colima.err
## 10. Docker usage
🔴 **Команда — `docker-compose`, а НЕ `docker compose`.** В homebrew-установке compose
приезжает отдельным бинарём, не плагином, поэтому `docker compose …` падает с
`docker: unknown command: docker compose` (выглядит как сломанный Docker — не сломан).
Проверено 2026-09-26: CLI 29.4.1, compose 5.1.3.
``` bash
docker-compose up -d # ✅
docker-compose config # валидация (флага --quiet у этой версии НЕТ)
docker compose up -d # ❌ unknown command
```
Start containers:
``` bash
docker compose up -d
docker-compose up -d
```
Check running containers:
@@ -123,6 +134,9 @@ Check running containers:
docker ps
```
Подробнее про границы проброса путей в контейнер (virtiofs) и билдер arm64builder:
[[tech/macos-docker-virtiofs-mount-limits]].
------------------------------------------------------------------------
## Behavior
@@ -1,15 +1,16 @@
# macOS Docker Desktop — что НЕ проезжает в контейнер (virtiofs)
> Создан 2026-09-26. Статус: **проверено живыми тестами** (alpine:3.20, linux/arm64,
> Docker Desktop на macOS 26.6.1, Apple Silicon).
> **Colima** на macOS 26.6.1, Apple Silicon; `colima start --cpu 8 --memory 24 --disk 100`).
> Тема: границы проброса путей из мира macOS в контейнер.
> Все выводы получены прогонами, не по памяти. Команды воспроизведения — в §5.
> Все выводы получены прогонами, не по памяти. Команды воспроизведения — в §6.
---
## 0. Главный вывод одной строкой
На macOS Docker Desktop отдаёт контейнеру файлы **не через Linux `mount`**, а через
На macOS рантайм контейнеров (Colima, Docker Desktop) отдаёт контейнеру файлы
**не через Linux `mount`**, а через
**virtiofs** — демон, который строит маппинг путей **в момент старта контейнера**
и знает только пути **мира macOS**, известные ему на старте.
@@ -160,6 +161,55 @@ ln /tmp/a /tmp/b # → Operation not permitt
---
## 8. ⚠️ Реализация на macOS: `docker-compose`, а не `docker compose`
Проверено 2026-09-26 на этой машине (Docker CLI 29.4.1, macOS 26.6.1, Apple Silicon).
Compose здесь — **отдельный бинарь**, не подкоманда `docker`:
| Что | Значение |
|---|---|
| CLI | `/opt/homebrew/bin/docker` (v29.4.1) |
| buildx | плагин, симлинк в `~/.docker/cli-plugins/docker-buildx` |
| compose | `/opt/homebrew/bin/docker-compose` (**v5.1.3**), симлинк в `/opt/homebrew/lib/docker/cli-plugins/` |
```bash
docker compose config # → docker: unknown command: docker compose
docker compose config --quiet # → unknown flag: --quiet
docker-compose config # ✅ работает
docker-compose config --services
```
📌 **Всегда `docker-compose` (с дефисом) в этом окружении.** Сообщение об ошибке
выглядит как «docker не знает такой команды», а не «compose не установлен» — легко
принять за сломанный Docker.
⚠️ Дефолтный билдер (`default` → `/var/run/docker.sock`) в статусе **`error`**.
Для arm64-сборок использовать именованный билдер:
```bash
docker buildx build --builder arm64builder --platform linux/arm64 \
--provenance=false --load -t <tag> .
```
### Порядок файлов при генерации override
`compose.override.yaml` подхватывается compose **автоматически**, но если он ссылается на
несуществующий путь на хосте, `up` падает на монтировании. Поэтому порядок строго такой:
```
1. .env ← env_file, иначе up падает сразу
2. agents-mounts.yaml ← манифест (можно пустой: mounts: [])
3. tools/render-mounts.py ← → compose.override.yaml
4. docker-compose up -d
```
### Ловушка: `deploy.resources.limits` в не-swarm compose
Секция `deploy.resources.limits.memory` **молча игнорируется** запуском через
`docker-compose up` (это формат swarm). Лимит создаёт ложное ощущение защиты —
что-то ограничивать нужно иначе (cgroups напрямую или `mem_limit`, зависят от версии).
---
## Связанные заметки
- [[personal/plans/balda-mm-phase4-agents]] — применение этих выводов (Фаза 4)
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план проекта
@@ -1,7 +1,7 @@
---
title: TrueNAS NFS4 ACL repair + cross-container media PUID
created: 2026-09-02
updated: 2026-09-23
updated: 2026-09-26
type: tech
namespace: personal
tags: [truenas, nfs4, acl, midclt, arr, radarr, sonarr, jellyfin, transmission, docker, git]
@@ -10,6 +10,7 @@ related:
- "[[arr-stack-taiga]]"
- "[[watchlist-disk-overflow-fix]]"
- "[[2026-09-21-bare-repo-push-permission-denied]]"
- "[[2026-09-26-syncthing-clone-root-objects-acl-confusion]]"
---
# TrueNAS NFS4 ACL repair + cross-container media PUID
@@ -33,6 +34,18 @@ Any "permission denied" / file not readable / git `loose object ... corrupt` / e
## Core facts
- TrueNAS SCALE uses **NFSv4 ACL**, not plain POSIX. `ls -la` shows a POSIX mask that can mislead.
- Read real ACL: `midclt call filesystem.getacl <path>`; write: `midclt call filesystem.setacl`.
- 🔴 **`getfacl` (POSIX view) HIDES the absence of a custom ACL — use `nfs4xdr_getfacl`** (2026-09-26).
`getfacl` prints `user::rwx / group::rwx / other::---` whether or not named ACEs exist.
`nfs4xdr_getfacl <path>` prints `trivial_acl: true` (only `owner@`/`group@`/`everyone@`) — the
definitive "no custom ACL here". Start every permission diagnosis with it, not `getfacl`.
- 🔴 **"We already fixed this with ACL" ≠ "the ACL is set on THIS path"** (2026-09-26). A recursive
group ACL had been applied to `/mnt/RED_2TB/Edu` and was remembered as "done" — but the failing
path was a **different** folder with `trivial_acl: true` (never touched). Verify by fact on the
target object before repeating or ruling out a fix. Find past sessions:
`sqlite3 ~/.hermes/hermes-whale/state.db "SELECT session_id, substr(content,1,300) FROM messages WHERE content LIKE '%setfacl%' ORDER BY id DESC LIMIT 40;"`
- 🔴 **A one-off `chown` is symptom-treatment** (2026-09-26). If a **root** process keeps writing
into the folder, root-owned files/objects reappear on the very next run. Permanent fixes only:
`ACL` + inherit, OR run the writer **not as root** (`user: "950:950"` in compose).
- **`truenas_admin` is FULL_ADMIN in midclt** → can chown/setacl without passwordless root.
- Web UI ACL editing only works on zfs **datasets**, not arbitrary subdirs under one dataset → use midclt/CLI.
- Post-restore a tree may be owned by wrong uid (e.g. 921 `transmission`) with files `mode 0000`.