# coe-mirror Mirror repos from a Forgejo/Gitea instance to a local directory. Built for `git.churchofmalware.org` but usable against any Forgejo/Gitea host. ## What it does For every owner in your config, it fetches the repo list, downloads a zip of the current HEAD of the default branch, and writes it to `//_.zip`. Old snapshots stay as history — new commits produce new files. Runs unattended via a systemd user timer. ## Requirements - Python 3.11+ (uses stdlib `tomllib`) - `pip install -r requirements.txt` ## Quick start ``` git clone cd coe-mirror python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt python coe_mirror.py config init # writes ~/.config/coe-mirror/config.toml $EDITOR ~/.config/coe-mirror/config.toml # edit owners + target dir python coe_mirror.py run # first mirror ``` Add or remove owners any time by editing the `owners = [...]` list in the config file. ## Configuration **Precedence** (uniform for every key): env var > config file > hardcoded default. **Config file** location: `~/.config/coe-mirror/config.toml` (override with `COE_MIRROR_CONFIG=/path/to/file.toml`). Full schema: ```toml api_base = "https://git.churchofmalware.org" target_dir = "~/coe-mirror" owners = ["Nightmare_Eclipse", "shai_hulud"] # pending_dir = "~/.local/share/coe-mirror/pending" # optional [matrix] # optional; leave blank to disable notifications homeserver = "" room = "" ``` **Env-var overrides** (all optional; each beats the config file): | Var | Purpose | |---|---| | `COE_MIRROR_CONFIG` | Alternate path to config.toml | | `COE_MIRROR_API_BASE` | Forgejo/Gitea base URL | | `COE_MIRROR_OWNERS` | Comma-separated owners (`"a,b,c"`) | | `COE_MIRROR_TARGET_DIR` | Target directory | | `COE_MIRROR_PENDING_DIR` | Local staging dir | | `COE_MIRROR_MATRIX_HOMESERVER` | Matrix homeserver URL | | `COE_MIRROR_MATRIX_ROOM` | Matrix room ID | | `COE_MIRROR_MATRIX_USER` | Matrix bot user (fallback if `creds` binary unavailable) | | `COE_MIRROR_MATRIX_PASSWORD` | Matrix bot password (fallback) | ## CLI ``` python coe_mirror.py # run the mirror (default) python coe_mirror.py run # same python coe_mirror.py config init # write example config (won't overwrite) python coe_mirror.py config path # print config file path python coe_mirror.py --help ``` ## Install as a systemd user timer ``` bash install.sh ``` Timer defaults to 00:00 and 12:00 daily with boot catch-up (`Persistent=true`). Missed windows run on next boot. ## Matrix notifications (optional) - **Disabled by default.** Leave `[matrix]` blank to skip. - If both `homeserver` and `room` are configured, the tool posts a summary after every run that lands at least one new zip (or reports any owner whose fetch failed). - Credential lookup order: `creds get COE_MIRROR_BOT_USER matrix` / `COE_MIRROR_BOT_PASSWORD matrix` (Infisical CLI, if you use it), then `COE_MIRROR_MATRIX_USER` / `COE_MIRROR_MATRIX_PASSWORD` env vars. Tokens cached at `~/.local/share/coe-mirror/matrix-token.json` (mode 0600), auto-refreshed on expiry or 401. - Matrix failures are non-fatal — a warning is logged and the run proceeds. ## Filename convention - Final: `//_.zip` - Staging (while pending flush): `/___.zip` — owner prefix prevents cross-owner collisions when two owners have identically-named repos. ## Behavior when the target is unavailable Downloads land in `pending_dir`. Next run (or any subsequent invocation) flushes them to the target once it's healthy. Owner-scoped: one owner's fetch failure logs and continues; the next run retries. ## Logs - File: `~/.local/share/coe-mirror/coe-mirror.log` (rotated at 5 MB, 3 backups) - Journal: `journalctl --user -u coe-mirror.service -n 100` ## Storage Old snapshots accumulate — no automatic pruning. Prune the target directory manually if you need to reclaim space. ## Testing ``` python -m pytest tests/ -v ``` ## Uninstall ``` systemctl --user disable --now coe-mirror.timer rm ~/.config/systemd/user/coe-mirror.{service,timer} systemctl --user daemon-reload ```