12 KiB
title, updated, type, tags
| title | updated | type | tags | |||||
|---|---|---|---|---|---|---|---|---|
| Vault Git Sync — Architecture & Scripts | 2026-09-02 | tech |
|
Vault Git Sync
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.⛔ НОВАЯ проблема 2026-09-02 (не решена): оба Taiga-клиента (
/storage/obsidianи/storage/obsidian-syncthing) отстали на месяц — HEADceea848(2026-08-01) vs bare repo2fab879(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 fetchfail →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
Four clients sync to it: Eagle, Kraken, Taiga (vault), Taiga (syncthing/phone).
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):
- syncthing → phone changes land in bare repo first
- vault → agent (mcpvault) changes land in bare repo
- 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 |
⭐ УТОЧНЕНО 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).
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
git add pathspecfails withfatal: pathspec did not match any fileswhen a directory doesn't exist on disk — usegit add -Ainsteadgit add -Ain 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 ingit ls-filesoutput — use-c core.quotePath=false- ZFS on TrueNAS blocks
chmod—git initfails from host; must run from inside Docker container or create.gitstructure manually - NFS4
owner@ DENY READ_DATAACL 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 ACLowner@ 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-packreportsloose object ... is corruptand rejects push, even though data is intact (git fsck --fullunder root is clean). Fix =filesystem.setacl <repo> ... {stripacl:true}(chmod/chown do NOT remove DENY ACEs). Tell-tale: POSIX file mode40(r--------) on loose objects vs normal750.
History
-
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 -Abefore merge staged deletions of files outside sparse cone. Fixed with scopedgit add. -
2026-06-01: "Керамика" lost from gift-ideas. Root cause:
git add $SPARSE_PATHSfailed silently when.obsidian/absent — patch+move not committed. -
2026-06-02: Full architecture migration. Taiga
/vaultconverted fromGIT_DIR=bare GIT_WORK_TREEworkaround to proper sparse clone. All clients unified on samecommit→fetch→merge→pushalgorithm. Sync interval: every 5 min.