generated from atu/saiop-infrastructure
Add saiop-git-sync-sidecar skill: reusable pattern from the Obsidian vault sync
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: saiop-git-sync-sidecar
|
||||||
|
description: "Pattern for keeping a container's data volume continuously synced to a Gitea repo via a small sidecar container — used for the Obsidian vault, reusable elsewhere."
|
||||||
|
version: 1.0.0
|
||||||
|
author: Claude Code (SAIOP ops session)
|
||||||
|
license: MIT
|
||||||
|
platforms: [linux]
|
||||||
|
prerequisites:
|
||||||
|
env_vars: []
|
||||||
|
commands: [docker, git]
|
||||||
|
metadata:
|
||||||
|
hermes:
|
||||||
|
tags: [SAIOP, git, sync, Gitea, pattern, sidecar]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git-sync sidecar pattern
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
|
||||||
|
Any time an app's data volume should be continuously backed by a Gitea repo
|
||||||
|
(used for the Obsidian vault → `atu/saiop-knowledge`). Generalizes to any
|
||||||
|
app that just reads/writes files in a directory without needing to know
|
||||||
|
git exists.
|
||||||
|
|
||||||
|
## The pattern
|
||||||
|
|
||||||
|
A second, tiny container (`alpine/git`) mounts the **same volume** as the
|
||||||
|
main app and loops: `git pull` → check `git status --porcelain` → if dirty,
|
||||||
|
`add`/`commit`/`push` → `sleep`. The main app never needs to know about
|
||||||
|
git at all.
|
||||||
|
|
||||||
|
## Gotchas hit building this
|
||||||
|
|
||||||
|
1. **`rm -rf` on the mount point itself fails** ("Resource busy") — you can't
|
||||||
|
remove a bind-mounted directory, only its contents. Use
|
||||||
|
`find "$DIR" -mindepth 1 -delete`, or `git clone <url> .` directly into
|
||||||
|
the already-existing empty directory instead of removing-then-cloning.
|
||||||
|
2. **UID mismatch silently breaks the main app's write access.** If the
|
||||||
|
sidecar runs as root (the `alpine/git` default) and the main app runs as
|
||||||
|
a non-root `PUID`/`PGID` (common for LinuxServer.io images), every file
|
||||||
|
the sidecar creates is unwritable by the main app. Set `user: "UID:GID"`
|
||||||
|
on the sidecar to match the main app's user — don't just chown once
|
||||||
|
after the fact, the mismatch comes back on every commit.
|
||||||
|
3. **Add a `.gitignore` for the app's own volatile state files** (Obsidian:
|
||||||
|
`workspace.json`, `cache/`) before the first commit, or every sync cycle
|
||||||
|
produces a noise commit even with zero real content changes.
|
||||||
|
4. **Don't hardcode a container's internal IP for inter-container access** —
|
||||||
|
IPs churn (confirmed twice on this stack already). Use the container
|
||||||
|
**name** if both containers share a network (Docker's embedded DNS
|
||||||
|
resolves it), which is why the sync sidecar talks to `gitea:3000`
|
||||||
|
directly rather than an IP.
|
||||||
Reference in New Issue
Block a user