106 lines
6.6 KiB
Markdown
Executable File
106 lines
6.6 KiB
Markdown
Executable File
---
|
||
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), глобальный конфиг — нет, но он
|
||
пересоздаётся при каждом тике самим скриптом.
|