Files
obsidian-vault/family/how-to/vault-git-sync.md
T

38 KiB
Executable File
Raw Blame History

title, updated, type, tags
title updated type tags
Vault Git Sync — Architecture & Scripts 2026-10-15 tech
vault
git
sync
infra
obsidian

Vault Git Sync

✅ STATE 2026-09-26 (поправка Taiga, 09:50 UTC) — «раз в час» было ошибкой чтения: sync идёт каждые 5 минут. Блок ниже («РАСХОЖДЕНИЕ РАСПИСАНИЯ», «задержка до 60 минут») построен на поле schedule_display: "0 * * * *" в /opt/data/cron/jobs.json — это устаревшее поле, диспетчер его не использует. Факты:

  • jobs.json → schedule.expr = "*/5 * * * *", display = "*/5 * * * *" (устарело только schedule_display);
  • executions.db, job 29851c413903, 1001 тик за 09-22 22:25 → 09-26 09:40 UTC: интервалы 985 × 5 мин, 14 × 4 мин, 1 × 1 мин → 288,6 тика/сутки, а не 24. Тик «раз в час» дал бы 98 записей за это окно;
  • /tmp/vault-sync.log — timestamped Pushed ✓ каждые 5 минут.

hermes cron list печатает то же устаревшее поле (Schedule: 0 * * * *), поэтому каденцию проверять по executions.db или jobs.json:schedule.expr, а не по выводу CLI. Задержка «до 60 минут» бумажная; реальная доставка до телефона — в пределах тика. Перезапись schedule_display через hermes cron edit 29851c413903 --schedule '*/5 * * * *' — на решение Алексея (косметика, на исполнение не влияет).

✅ STATE 2026-09-26 (later, RESOLVED) — ACL с nas_users+INHERIT применён на obsidian-syncthing, синк работает. Оба дефекта закрыты: (1) Битого remote — origin = /vault.git переписан на полный путь (но снова слетел после sync-vault.sh: тот форсит file:///vault.git внутри контейнера — это правильно для /vault, но для хостового клона путь другой. Проверять git remote -v перед диагностикой прав). (2) Права — решено перманентно: midclt filesystem.setacl рекурсивно на /mnt/RED_2TB/storage/obsidian-syncthing с gid=3000 (nas_users) и флагами FILE_INHERIT+DIRECTORY_INHERIT на все ACE. Было trivial_acl: true + 4051 объект root → стало trivial_acl: false, group: 3000, 0 объектов root (все 4073 = truenas_admin). Группа nas_users (gid 3000) содержит root, truenas_admin, nas, mallexxx, transmission — поэтому и root-процесс Taiga, и Syncthing (uid 950) пишут в одну папку. Наследование проверено ФАКТОМ: touch .inherit_test → truenas_admin:nas_users, mkdir .inherit_dir_test → truenas_admin:nas_users. Артефакты после теста удалены. git fetch origin main → Already up to date; HEAD клона содержал коммит с Mac (f2501dc). Бэкап ACL перед правкой: /home/truenas_admin/variantA_bk/acl-obsidian-syncthing-before-20260926-154827.json. Скрипт: acllib_obsidian_syncthing_fix.py (см. раздел «ACL repair» → рецепт).

🔴 ГЛАВНЫЙ ПИТФОЛЛ ЭТОЙ ПРАВКИ: midclt filesystem.getacl отдаёт КЭШ. Сразу после setacl он печатал старые значения (trivial: True, gid: 950, NOINHERIT), и скрипт рапортовал ложный «VERIFY FAILED». Реальное состояние видно только через nfs4xdr_getfacl и stat. Никогда не верить midclt getacl сразу после setacl — сверять nfs4xdr_getfacl (там trivial_acl: false и fd----- = FILE_INHERIT+DIRECTORY_INHERIT) плюс stat -c '%A %U:%G'.

Историческая путаница (для будущих сессий): рекурсивный ACL с nas_users действительно ставился 2026-09-23, но на /mnt/RED_2TB/Edu (запрос Alex: «можешь просто группу под acl сделать с inherit чтобы весь edu был доступен группе включающей truenas_admin?»), не на obsidian-syncthing. Отсюда «я это уже чинил» — чинил, но другую папку. Искать прошлые сессии: 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;".

Разовый chown лечит только уже существующие объекты; следующий pull от root возвращает дефект. Перманентно лечит ACL+INHERIT (наследование новыми объектами) либо запуск процесса не от root. Корень по-прежнему не устранён: контейнер hermes-taiga работает от 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) заявляет «*/5». Обнаружено при проверке «доехал ли док до телефона»: ноты, залитые с Mac в 14:32, в клоне obsidian-syncthing отсутствовали.

Следствие: изменения с Eagle/Kraken доезжают до телефона с задержкой до 60 минут. Цепочка bare repo → git-клон obsidian-syncthing → Syncthing → телефон рвётся на втором шаге (Syncthing здоров ≠ док доехал).

Ручной триггер (проверено):

docker exec hermes-taiga sh -c 'cd /opt/data && HERMES_HOME=/opt/data \
  /opt/hermes/.venv/bin/hermes cron run 29851c413903'
# → Triggered job: sync-vault … Ran now: succeeded.

После триггера: клон обновлён, Syncthing needFiles: 0, телефон completion: 100.

Открытый вопрос к Alex: менять расписание на */5 * * * * или оставить раз в час?

⚠️ Диагностика синка — только изнутри контейнера Syncthing: порт 8384 слушает лишь в контейнере, curl с хоста TrueNAS даёт пустоту.

docker exec syncthing sh -c 'K=$(grep -oE "<apikey>[^<]+" /var/syncthing/config/config.xml | sed "s|<apikey>||"); \
  curl -s -H "X-API-Key: $K" "http://localhost:8384/rest/db/status?folder=k5vyy-gzdgj"'

Папка Syncthing: id k5vyy-gzdgj «Obsidian Vault», устройства SM-S931B (телефон) и syncthing-taiga. Готовый скрипт: ~/Developer/personal-os/tools/music-tags/check_syncthing2.sh.

✅ STATE 2026-09-22 — рецидив ACL закрыт перманентно. Дефект вернулся (35 root-каталогов), push с Mac падал. Починено: 236/236 каталогов INHERIT + WRITE через midclt filesystem.setacl (read-modify-write + ожидание job). Наследование подтверждено ФАКТОМ — mkdir objects/zzz → новый ACL унаследован (INHERITED:True), touch внутрь OK. End-to-end push: ba3780c..8b831a8 main -> main ✅. Поставлен cron-сторож (каждые 2 ч): ~/scripts/vault-acl-watchdog.py на TrueNAS — SILENT когда всё ок, чинит + докладывает когда сломано. Скрипты: /Users/admin/Developer/vault-tools/. 🔴 Корень НЕ устранён: контейнер hermes-taiga работает от root (HERMES_ALLOW_ROOT_GATEWAY=1, User: пусто) → mcpvault /vault пишет root-owned объекты. Перманентный фикс — user: "950:950" в taiga service /mnt/RED_2TB/docker/hermes/docker-compose.yml (риск: образ может требовать root для /opt/data). Сторож пока закрывает последствия. Подробно: family/documents/vault-sync/2026-09-21-bare-repo-push-permission-denied.md.

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.

STATE 2026-09-26 — pipefail + grep -q молча отменял коммиты в sync-vault-lib.sh. При большом числе изменений (492 файла ≈ 23 КБ вывода) git status --porcelain | grep -q . падал по SIGPIPE (grep выходил после первого совпадения, git дописывал в закрытый pipe), set -o pipefail объявлял pipeline упавшим (exit 141) → ветка else → коммит не создавался вообще, при этом тик печатал Pushed ✓ ×3 и exit 0. Так застряли 491 mode-flip и правка телефона (1-needs-decision.md, 4 чекбокса [x]). Фикс: условие через STATUS="$(git status --porcelain)" + [ -n "$STATUS" ], сообщение коммита — одним awk (первые 25 путей, дальше (... +N more)). ⚠️ awk | head -25 в подстановке даёт тот же SIGPIPE → тик падает с exit 141, так делать нельзя. Проверено: коммит 12a7566e (492 файла), три SHA равны, git status пуст. Разбор: family/documents/vault-sync/2026-09-26-commit-skip-pipefail.md.

⛔ НОВАЯ проблема 2026-09-02 (не решена): оба Taiga-клиента (/storage/obsidian и /storage/obsidian-syncthing) отстали на месяц — HEAD ceea848 (2026-08-01) vs bare repo 2fab879 (2026-09-02). Причина: контейнер hermes-taiga в crash-loop (16339 рестартов) — uv run hermes gateway run не может докачать депы (pytz/markdown-it-py) с PyPI, т.к. весь HTTP идёт через мёртвый socks5://vless-proxy:1080, а UV_CACHE_DIR=/tmp/uv-cache пустеет на каждом рестарте. Taiga 3-phase sync (крон внутри агента) не выполняется => телефон видит устаревший vault. Подробно: family/documents/vault-sync/2026-09-02-hermes-taiga-crashloop.md. Фикс НЕ применён (ждёт Alex).

Триггер cron на маке (Eagle) — это launchd, НЕ Hermes cron: ~/Library/LaunchAgents/com.sync-vault.plist → /Users/admin/scripts/sync-vault.sh, StartInterval=300 (5 мин). Скрипт sync-vault-lib.sh намеренно глушит dead remote (git fetch fail → exit 0), поэтому launchd показывает last exit code = 0 даже при фактически стоящем sync. Системный crontab на маке пуст.

Obsidian vault is a bare git repo on TrueNAS: /mnt/RED_2TB/storage/git/obsidian-vault.git Also mirrored in Gitea: https://git.mallexxx.duckdns.org/git_admin/obsidian-vault

Five clients sync to it: Eagle, Kraken, Taiga (vault), Taiga (syncthing/phone), iPad (Edhita Documents).

Architecture

bare repo (obsidian-vault.git) — source of truth
   ↑↓                ↑↓               ↑↓                    ↑↓
Eagle              Kraken         Taiga /vault         Taiga /obsidian-syncthing
full clone         sparse clone   sparse clone          full clone
~/obsidian         ~/obsidian     personal/family/      phone ↔ Syncthing
                   personal/family/  .obsidian/
                   .obsidian/

Taiga runs 3-phase sync (order matters):

  1. syncthing → phone changes land in bare repo first
  2. vault → agent (mcpvault) changes land in bare repo
  3. syncthing → agent changes propagate to phone

Sync Algorithm (same for all clients)

git add -A && git commit          # commit local changes
git fetch origin main             # get remote state
git merge origin/main --no-edit   # 3-way merge (conflict → commit markers)
git push origin main              # push back

No stash. No GIT_DIR/GIT_WORK_TREE workarounds.

Scripts

Host Script Type Trigger
Eagle ~/scripts/sync-vault.sh full clone launchd com.sync-vault (⚠️ НЕ Hermes cron) StartInterval=300
Kraken ~/scripts/sync-vault.sh sparse clone host crontab */5
Taiga /opt/data/sync-vault.sh 3-phase orch. Hermes cron */5 * * * * — job 29851c413903. ⚠️ hermes cron list и поле schedule_display в jobs.json показывают устаревшее 0 * * * * (см. поправку в STATE 2026-09-26 выше); фактические тики — каждые 5 мин
iPad /var/mobile/Library/VaultSync/sync-vault.sh full working tree, initial shallow clone launchd local.ipad.obsidian-vault-sync, mobile, StartInterval300

⭐ УТОЧНЕНО 2026-09-02 (Eagle/мак sync-механизм): автосинка на маке запускается НЕ Hermes cron, а launchd-агентом ~/Library/LaunchAgents/com.sync-vault.plist:

  • Программа: /Users/admin/scripts/sync-vault.sh, StartInterval = 300 сек (5 мин)
  • launchctl list → активен, runs = 3762, last exit code = 0
  • ⚠️ sync-vault-lib.sh умышленно глушит недоступность remote: если git fetch падает → логирует Fetch failed (...) и exit 0. Поэтому launchd показывает exit code 0, даже когда sync фактически сломан (см. случай ahead 55 при сломанном bare repo после restore). Это не отсутствие cron и не сбой — диагностировать нужно по git status (ahead count), а не по exit-code агента.
  • Системный crontab и /etc/crontab на маке отсутствуют.

All clients share a common library: sync-vault-lib.sh (same dir as sync-vault.sh). Source of truth for scripts: ~/Developer/vault-sync-test/scripts/ on Eagle.

Taiga Container Mounts

Container path Host path Purpose
/vault /mnt/RED_2TB/storage/obsidian sparse clone (agent rw)
/vault.git /mnt/RED_2TB/storage/git/obsidian-vault.git bare repo (git remote)
/obsidian-syncthing /mnt/RED_2TB/storage/obsidian-syncthing full clone (phone sync)
/opt/data /mnt/RED_2TB/docker/hermes/config Hermes config + scripts

/vault is a sparse clone with remote = file:///vault.git. /vault.git is hidden from the agent (mcpvault points to /vault).

/obsidian-syncthing is also a clone with remote = file:///vault.git, но его .git/ сам синкается Syncthing'ом с других устройств, и remote.origin.url периодически перезаписывается чужим адресом — хост-путём /mnt/RED_2TB/storage/git/obsidian-vault.git (2026-09-26 09:36) или Gitea-URL https://git.mallexxx.duckdns.org/git_admin/obsidian-vault.git (версия с телефона). Внутри контейнера не резолвится ни один: git fetch origin main → fatal: … does not appear to be a git repository, sync-vault-lib.sh печатает Fetch failed (origin offline?) — skipping merge/push и выходит с exit 0, поэтому фазы 1 и 3 молча не выполняются (правки телефона не доезжают до bare, правки агента — до телефона), а cron рапортует «успех». С 2026-09-26 09:42 sync-vault.sh в начале каждого тика принудительно ставит origin = file:///vault.git и добавляет url."file:///vault.git".insteadOf для обоих чужих адресов. Разбор: family/documents/vault-sync/2026-09-26-syncthing-clone-origin-url-flip.md.

Sparse Checkout Paths

Taiga and Kraken check out only:

personal/
family/
.obsidian/

work/ and wiki/ are never checked out on sparse clients — they never touch those paths.

Deploy a Script Fix

# Edit on Eagle:
~/Developer/vault-sync-test/scripts/sync-vault-lib.sh   # common algorithm
~/Developer/vault-sync-test/scripts/sync-vault.sh       # Taiga 3-phase orchestrator

# Deploy to Taiga:
scp ~/Developer/vault-sync-test/scripts/sync-vault-lib.sh \
    ~/Developer/vault-sync-test/scripts/sync-vault.sh \
    truenas_admin@mallexxx.duckdns.org:/mnt/RED_2TB/docker/hermes/config/

# Deploy to Kraken (via sudo):
cat ~/scripts/sync-vault-lib.sh | ssh kraken "sudo tee ~/scripts/sync-vault-lib.sh > /dev/null"
cat ~/scripts/sync-vault.sh     | ssh kraken "sudo tee ~/scripts/sync-vault.sh > /dev/null"

.gitignore

Plugin binaries excluded to avoid cross-device conflicts:

.obsidian/plugins/obsidian-git/main.js
.obsidian/plugins/obsidian-git/styles.css
.obsidian/plugins/obsidian-git/manifest.json

data.json (plugin config) is tracked — shared settings.

Regression Tests

~/Developer/vault-sync-test/test-sync.sh — 16 scenarios (A–P): covers Cyrillic filenames, moves, deletes, conflicts, work/ phantom-delete, 3-phase sync.

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 начинать с него.
  • 🔴 midclt filesystem.getacl СРАЗУ ПОСЛЕ setacl отдаёт КЭШ (2026-09-26). Печатает старые значения (trivial: True, старый gid, NOINHERIT), из-за чего правка выглядит неприменённой. Реальное состояние — только nfs4xdr_getfacl <path> (trivial_acl: false, флаги fd-----) + stat -c '%A %U:%G %n' + фактический touch внутрь папки. Не откатывать и не повторять setacl по показаниям getacl.
  • 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 (запрос Alex про Маскелиаде). На 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
  • 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 = 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.

# 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).

✅ Рабочий рецепт: группа + INHERIT на папку (проверено 2026-09-26)

Скрипт (acllib_obsidian_syncthing_fix.py, отработал на /mnt/RED_2TB/storage/obsidian-syncthing): read-modify-write ACL — читает существующий dacl, сохраняет id полей (без них TrueNAS падает missing required keys: {'id'}), переписывает flags в FILE_INHERIT+DIRECTORY_INHERIT, ставит gid нужной группы, recursive: true, stripacl: false.

payload = {
    "path": TARGET,
    "uid": 950,        # truenas_admin — оставить владельцем
    "gid": 3000,       # nas_users — группа, в которой и root, и uid 950
    "acltype": "NFS4",
    "dacl": new_dacl,  # read-modify-write, флаги INHERIT, id сохранены
    "options": {"recursive": True, "traverse": True, "stripacl": False},
}

Запуск: scp скрипта на NAS → ssh truenas_admin@mallexxx.duckdns.org "python3 /home/truenas_admin/<script>.py".

Критические детали:

  • stripacl: false — true не убирает DENY и ломает наследование (проверено 2026-09-21).
  • gid в payload при recursive: true заодно переписывает владельца всех вложенных объектов (4051 root-объект → truenas_admin одной операцией).
  • group@ в NFSv4 — это POSIX-gid, а не ACE. chgrp от truenas_admin даёт Operation not permitted; менять gid только через midclt.
  • 🔴 VERIFY через midclt getacl бесполезен — это кэш. После setacl он печатает старые значения. Проверять: nfs4xdr_getfacl <path> (trivial_acl: false, fd-----) + stat -c '%A %U:%G %n' + фактический touch нового файла внутрь папки.
  • Бэкап перед правкой: midclt call filesystem.getacl <path> > /home/truenas_admin/variantA_bk/acl-<name>-before-$(date +%Y%m%d-%H%M%S).json.
  • Приёмка: find <path> -type f -printf '%u\n' | sort | uniq -c → 0 объектов от root; git fetch origin main → Already up to date.

History

  • 2026-09-26 (ACL на obsidian-syncthing — закрыто): синк не доезжал до телефона. Два независимых дефекта: (1) origin клона = /vault.git (потерян префикс) → fetch падал; (2) 4051 из 4055 объектов принадлежали root — контейнер hermes-taiga работает от root (Config.User пусто, id → uid=0), он делает pull, git под uid 950 не мог писать (insufficient permission for adding an object). Пользователь потребовал поставить ACL с INHERIT на папку — сделано: midclt filesystem.setacl recursive, gid=3000 (nas_users) + FILE_INHERIT+DIRECTORY_INHERIT на все ACE. Было trivial_acl: true → стало false, group: 3000, 0 root-объектов (4073 = truenas_admin). Наследование подтверждено touch/mkdir фактом. git fetch → Already up to date. Коммит с Mac f2501dc найден в истории клона. 🔴 midclt getacl после setacl отдаёт кэш (ложный «не применилось») — сверять nfs4xdr_getfacl + stat. Бэкап: /home/truenas_admin/variantA_bk/acl-obsidian-syncthing-before-20260926-154827.json.

  • 2026-09-26: клон /obsidian-syncthing потерял рабочий remote — Syncthing переписал .git/config (mtime 09:36:19, owner uid 950 ≠ наши root-записи) на хост-путь /mnt/RED_2TB/storage/git/obsidian-vault.git. Первый за всю историю лога Fetch failed (тик 09:40:31) → фазы 1 и 3 молча скипались при exit 0; /obsidian-syncthing отстал на 1 коммит Eagle (66d4d626 vs bare 5c939ed8). Фикс: sync-vault.sh форсит origin = file:///vault.git каждый тик + insteadOf для хост-пути и Gitea-URL; after-fix три SHA равны 3edbddf5, статусы пусты, новых Fetch failed нет. Корень (.git клона синкается Syncthing'ом — отсюда же .sync-conflict-* внутри .git) — на решение владельца. Подробно: family/documents/vault-sync/2026-09-26-syncthing-clone-origin-url-flip.md.

  • 2026-09-26 (поправка каденции): снято ложное «Taiga синкается раз в час». hermes cron list и jobs.json:schedule_display показывают 0 * * * *, но schedule.expr = "*/5 * * * *" и executions.db дают 985 интервалов по 5 мин из 1001 тика (288,6/сутки). Диагностировать каденцию — только по executions.db/schedule.expr.

  • 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.

    Проблема 2 — экранирование backtick в patch_note: mcpvault patch_note не может найти строку, содержащую тройные обратные кавычки (```). Пришлось якориться на соседнюю строку без backtick — например, на \n\n## Заголовок вместо \``\n\n## Заголовок`.

    Решение — ACL ext4 (setfacl):

    sudo setfacl -R -m u:10000:rwx /srv/.../obsidian                                     # mcpvault — write
    sudo setfacl -R -d -m u:10000:rwx /srv/.../obsidian                                   # default для новых файлов
    sudo setfacl -R -m g:kraken:rwx /srv/.../obsidian                                     # kraken (uid 1000) — write
    sudo setfacl -R -d -m g:kraken:rwx /srv/.../obsidian                                  # default для новых файлов
    

    Итоговая совместимость: uid 10000 (mcpvault) и uid 1000 (kraken/git) могут читать и писать одни и те же файлы. patch_note работает без EACCES.

    Ограничение (mcpvault patch_note + backtick): patch_note не может найти строку, содержащую тройные обратные кавычки (```). Пример: нужно было вставить новый раздел между closing code fence \``и заголовком## Исправления (v0.0.92.... Варианты oldString`, которые НЕ сработали:

    • "}\n\n\``\n\n## Исправления (v0.0.92..."` — не найдено
    • "\``\n\n\n## Исправления (v0.0.92..."` — не найдено
    • Любая комбинация пустых строк между \``` и заголовком — не найдено

    Что сработало: якориться на соседнюю строку без обратных кавычек. Вместо \``\n\n## Исправления...использовать\n\n## Исправления (v0.0.92...— замена пустой строки + заголовка, куда перед заголовком вставляется новый контент, а сам заголовок остаётся.patch_note(oldString="\n\n## Исправления (v0.0.92...")` — найдено и отработало.

  • 2026-05-23: Taiga deleted 32+ wiki files. Root cause: git add -A before merge staged deletions of files outside sparse cone. Fixed with scoped git add.

  • 2026-06-01: "Керамика" lost from gift-ideas. Root cause: git add $SPARSE_PATHS failed silently when .obsidian/ absent — patch+move not committed.

  • 2026-06-02: Full architecture migration. Taiga /vault converted from GIT_DIR=bare GIT_WORK_TREE workaround to proper sparse clone. All clients unified on same commit→fetch→merge→push algorithm. Sync interval: every 5 min.

iPad iOS9.3.5 — подключён 2026-10-15

Прямой SSH Git клиент того же TrueNAS bare main, Git2.8.1 от Saurik, отдельный restricted Ed25519 key (только этот repo), persisted TrueNAS user settings. Vault в Documents Edhita/ObsidianVault; task работает от mobile каждые300с и при load. Проверены actual iPad→NAS push и NAS→iPad automatic pull на двух последовательных ticks. Native fixture tests повторены 2026-10-15: offline сохранение/retry, commit/push конфликтных маркеров с обеими версиями, получение другим clone, продолжение синка и возобновление прерванного merge. Script/last-success/last-result/error logs находятся вне Git vault; core.fileMode=false, auto gc отключён. При конфликте, как на Mac, client коммитит маркеры с пометкой merge conflict — review needed и пушит; ручное разрешение не блокирует последующие ticks. Скрипты прочих клиентов не менялись. Полные пути, source/evidence и обслуживание: personal/how-to/ipad-vault-sync.