diff --git a/family/documents/vault-sync/2026-09-26-syncthing-clone-origin-url-flip.md b/family/documents/vault-sync/2026-09-26-syncthing-clone-origin-url-flip.md new file mode 100644 index 00000000..6863a90e --- /dev/null +++ b/family/documents/vault-sync/2026-09-26-syncthing-clone-origin-url-flip.md @@ -0,0 +1,105 @@ +--- +tags: [vault, git, syncthing, incident] +date: 2026-09-26 +--- + +# 2026-09-26 — Флип `origin` у клона `/obsidian-syncthing` (Syncthing переписал `.git/config`) + +## Симптом + +Первый за всю историю лога (21646 строк) провал fetch в тике 09:40:31: + +``` +[2026-09-26 09:40:31] [syncthing] Fetch failed (origin offline?) — skipping merge/push +[2026-09-26 09:40:31] [taiga-vault] Pushed ✓ +[2026-09-26 09:40:31] [syncthing-out] Fetch failed (origin offline?) — skipping merge/push +``` + +Тик завершался `exit 0` (graceful skip в `sync-vault-lib.sh`), поэтому cron-задача +рапортовала «успех», хотя **фазы 1 и 3 не выполнялись вообще**: + +- фаза 1 (`syncthing`): правки с телефона коммитились локально, но не доезжали до bare; +- фаза 3 (`syncthing-out`): правки агента/Eagle не доезжали до клона → и дальше по + Syncthing не уходили на телефон. + +Состояние на момент обнаружения: bare и `/vault` = `5c939ed8`, `/obsidian-syncthing` += `66d4d626` (отстал на один коммит Eagle), `git status --porcelain` пуст в обоих, +маркеров конфликта 0. Данные не потеряны — git идемпотентен, отставание догоняется +одним прогоном. + +## Причина + +`remote.origin.url` клона `/obsidian-syncthing` оказался + +``` +/mnt/RED_2TB/storage/git/obsidian-vault.git +``` + +— **хост-путь, которого в контейнере hermes-taiga нет** (`/mnt` пуст, mount-ов на +`/mnt/RED_2TB` нет; bare смонтирован как `/vault.git`). Проба вручную: + +``` +$ git -C /obsidian-syncthing fetch origin main +fatal: '/mnt/RED_2TB/storage/git/obsidian-vault.git' does not appear to be a git repository +``` + +До 09:35:33 фаза 1 успешно фетчила и мерджила (`reflog` клона: `merge origin/main: +Fast-forward` в 09:06, 09:12, 09:25, 09:35), а **в 09:36:19 файл `.git/config` был +перезаписан** — `mtime == ctime == 2026-09-26 09:36:19`, владелец uid 950 (наши git-процессы +в контейнере пишут файлы клона как root: `index`, `ORIG_HEAD`, `refs/remotes/origin/main` +— `root`, а сам `config` — `950:950`, `-rw-rw----`). Значит записал **внешний процесс, +а не наш контейнер**. + +Это тот же механизм, что и `.sync-conflict`-файлы внутри `.git` (от 2026-05-29: +`config.sync-conflict-…`, `index.sync-conflict-…`, `FETCH_HEAD.sync-conflict-…`): +**каталог `.git` клона `/obsidian-syncthing` сам синкается Syncthing'ом с других +устройств**. Старая конфликт-копия конфига (2026-05-15) содержит совсем другой remote — +телефонный: + +``` +[remote "origin"] url = https://git.mallexxx.duckdns.org/git_admin/obsidian-vault.git +[user] name = alex phone S25 +``` + +То есть у клона периодически «прилетает» `origin` с чужого устройства, и внутри +контейнера этот адрес не резолвится. Сегодня прилетел хост-путь. + +## Что сделано (Taiga, 2026-09-26 09:42–09:43 UTC) + +1. Клону вернули контейнерно-корректный remote: `origin = file:///vault.git` + (тот же bare, что и у `/vault`). +2. В `/opt/data/sync-vault.sh` добавлен **идемпотентный guard** (рядом с блоками + `safe.directory` и git identity) — он выполняется в начале каждого тика: + - если `remote.origin.url` клона ≠ `file:///vault.git` → `git remote set-url origin file:///vault.git`; + - в глобальный конфиг (`/root/.gitconfig`) добавляется правило перезаписи + `url."file:///vault.git".insteadOf` для обоих известных чужих адресов + (хост-путь `/mnt/RED_2TB/storage/git/obsidian-vault.git` и Gitea-URL + `https://git.mallexxx.duckdns.org/git_admin/obsidian-vault.git`) — на случай, + если Syncthing перепишет конфиг **посреди** тика, уже после guard'а. + + Побочных эффектов нет: других репозиториев, использующих эти два адреса, в + контейнере нет (проверено `grep` по git-конфигам). + +## Проверка (после правки) + +- `bash -n /opt/data/sync-vault.sh` → syntax OK; +- три прогона, все `exit 0`, `Pushed ✓` ×3, новых `Fetch failed` нет (в логе остались + только 4 исторические строки тика 09:40); +- три SHA равны: `bare` = `/vault` = `/obsidian-syncthing` = `3edbddf5`; +- `git status --porcelain` пуст в обоих клонах, маркеров конфликта 0, `Push failed` 0; +- idempotency guard'а: два прогона подряд → правила `insteadOf` не дублируются (2 шт.); +- функциональный тест перезаписи: временный клон с `origin = /mnt/RED_2TB/storage/git/obsidian-vault.git` + → `git fetch origin main` проходит (**REWRITE_OK**), т.е. даже чужой адрес теперь + разрешается в локальный bare. + +## Осталось за владельцем (Taiga молча не делает) + +- **Корень класса** — `.git` клона `/obsidian-syncthing` синкается Syncthing'ом между + устройствами (отсюда и `.sync-conflict-*` внутри `.git`, и флип remote'а). Правильное + лечение — исключить `.git/` из синка этого folder'а на стороне Syncthing + (`.stignore` в корне folder'а или в конфиге демона). Это правка чужой инфраструктуры, + решение Алексея. Пока guard в `sync-vault.sh` держит fetch рабочим независимо от + содержимого `.git/config`. +- Конфиги `/root/.gitconfig` и `/opt/data/sync-vault.sh` живут в контейнере: + скрипт персистентен (`/opt/data` = ZFS), глобальный конфиг — нет, но он + пересоздаётся при каждом тике самим скриптом. diff --git a/family/how-to/vault-git-sync.md b/family/how-to/vault-git-sync.md index 0aec40dd..1c8db7da 100644 --- a/family/how-to/vault-git-sync.md +++ b/family/how-to/vault-git-sync.md @@ -12,6 +12,19 @@ tags: # 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 — РАСХОЖДЕНИЕ РАСПИСАНИЯ: Taiga-сервер синкается РАЗ В ЧАС, не 5 минут.** > Job `sync-vault` внутри контейнера `hermes-taiga` (Hermes cron, id `29851c413903`) расписан > **`0 * * * *`** — то есть раз в час, **хотя таблица ниже (и `family/how-to/obsidian-sync`) @@ -105,7 +118,7 @@ No stash. No GIT_DIR/GIT_WORK_TREE workarounds. |--------|-------------------------------|---------------|----------------------| | 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 `0 * * * *` (РАЗ В ЧАС!)** — job `29851c413903`, см. STATE 2026-09-26 выше. Не `*/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 мин | > **⭐ УТОЧНЕНО 2026-09-02 (Eagle/мак sync-механизм):** автосинка на маке запускается **НЕ Hermes cron, а launchd-агентом** `~/Library/LaunchAgents/com.sync-vault.plist`: > - Программа: `/Users/admin/scripts/sync-vault.sh`, `StartInterval = 300` сек (5 мин) @@ -128,6 +141,17 @@ Source of truth for scripts: `~/Developer/vault-sync-test/scripts/` on Eagle. `/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: @@ -202,6 +226,9 @@ Pitfalls: `setacl`/`setperm` are **async** (return a job id) — poll `core.get_ ## History +- **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). Подробно: