Files
druppy 75a7f74576 Add .rules: invariants and gotchas for future development
Most entries are a data-loss bug that was found in testing and must not come
back: filter ordering, harvest-before-pull, seen-gated deletion attribution,
edits-beat-deletes, POSIX sh on DSM, and the never-2>/dev/null-an-rsync rule.
Symlinked as AGENTS.md so a future agent session loads it automatically.
2026-10-24 16:41:30 +02:00

119 lines
5.3 KiB
Markdown

# homehub — operator notes
Operational doc. **Why** each decision exists is in [`DESIGN.md`](DESIGN.md); this file
is only the operational half.
```
~/homehub/
bin/homehub the tool (~750 lines of bash + one python merge)
config machine-specific settings (per-machine, not synced)
rules/denylist DEFAULT DENY, emitted first, wins over everything
rules/sets.d/*.list named path-lists ("sets")
rules/hosts/<hostname> which sets this machine joins
lib/merge_bookmarks.py class C union-merge (jq + python3 only)
systemd/homehub-*.{service,timer}
DESIGN.md rationale for everything below
.rules -> AGENTS.md invariants + gotchas for future changes (auto-loaded by agents)
```
## Install (per machine)
```sh
git clone https://gitea.lue.dk/druppy/homehub.git ~/homehub && cd ~/homehub
cp rules/hosts/_laptop-template rules/hosts/$(hostname -s) # pick your sets
./bin/homehub setup-key # once, see warning below
ssh-copy-id -i ~/.ssh/homehub_ed25519.pub nas.lue.dk # YOU run this
./bin/homehub setup
./bin/homehub sync # DRY: prints what would move, writes nothing
homehub sync --exec # first real run
sudo loginctl enable-linger $USER # headless boxes only
~/homehub/bin/homehub install # systemd user timers
```
`homehub install` copies the units into `~/.config/systemd/user` and enables
`homehub-sync.timer` (10 min) and `homehub-fresh.timer` (30 min).
## Commands
| Command | What |
|---|---|
| `sync [--exec]` | pull → apply deletions → harvest deletions → push. **Dry unless `--exec`.** |
| `status` | last sync per machine, sets, pending dry-run lists, repo manifest, bookmark exports |
| `fresh [--dry]` | the 24 h alert check; only printer of alerts |
| `git inventory\|list\|missing\|clone [--exec] [--lazy]` | class B |
| `bookmarks [--write]` | class C export + union merge |
| `forget <path>` | drop tombstones so a deleted path can return |
| `filter` | dump the generated rsync filter |
| `setup`, `setup-key`, `install` | one-time plumbing |
## The rules that matter when editing config
1. **Default deny.** `rules/denylist` is emitted first, then the host's set includes,
then `- *`. A path in a set that sits under a deny rule can never sync — the tool
warns (`shadowed: ...`) when you write one.
2. **Set syntax.** One home-relative path per line; trailing `/` = subtree.
`/Documents/` → `- /Documents/***`; `build/` (no leading slash) matches at any depth.
3. **Never add a git repo path to a set.** Repos are class B (`homehub git inventory`).
4. **`~/.config` is unreachable by design** (it is denied wholesale). State you want
shared goes under `~/.local/state/shared/<app>/`, with the real config path
symlinked to it. `~/.local/state/homehub/` is homehub's *private* scratch and is
denied too — syncing it would make machines overwrite each other's generated
filters forever.
5. **Deletions never propagate as `--delete`.** They become tombstones on the hub,
are reaped after `GRACE_DAYS`, and always lose to a competing edit.
## Alerting
`homehub fresh` alerts only when a machine is **on** (heartbeat < 3 h) but has not
synced for > 24 h, and never for network-class rsync/ssh exit codes — so a laptop
away for three weeks is silent indefinitely. Delivery auto-detects `msmtp`, then
`sendmail`, else appends to `~/.local/state/homehub/alerts.out`. To wire it to your
own mail server:
```sh
# /etc/msmtprc (or ~/.config/msmtp/config)
defaults
tls on
tls_starttls on
account homehub
host mail.lue.dk
port 587
auth on
user homehub@lue.dk
from homehub@lue.dk
password <app-password>
```
Then `msmtp -a homehub you@lue.dk` is picked up automatically. On Debian:
`apt install msmtp msmtp-mta`.
A machine whose **own** config is broken (ssh key revoked, hub disk full) records a
local marker in `~/.local/state/homehub/broken.*` and alerts about itself even with
the hub unreachable — otherwise a dead key would produce no heartbeat, no marker,
and therefore no alert, forever.
## Known limits (deliberate, not bugs)
* **mtime is the arbiter.** Two machines editing the same file while both offline
resolve by mtime, so clocks must be NTP-synced (`timedatectl` on Debian, `ntpd` on
NixOS). The overwritten copy is kept in `<hub>/.hub/prev/YYYY-MM/`.
* **rsync 3.1.2 on DSM 6/7** — no `--ignore-older`; the design uses `--update` only.
* `--checksum` is off, so a file edited to the same size in the same second as a
previous sync may be missed. Re-run `rsync -mc` by hand if you ever suspect it.
* Unpushed commits are advisory in the manifest and never transferred. A dead laptop
disk loses them.
* Empty bookmark folders do not survive the union merge (there is nothing to key on).
* Symlinks travel as symlinks (`-l`, never `-L`); ones pointing into `~/src`, `~/git`
or `/tmp` will dangle on a machine that has not cloned the target.
## Troubleshooting
```sh
~/homehub/bin/homehub status # ages, sets, manifest
journalctl --user -u homehub-sync.service -n 50
cat ~/.local/state/homehub/{pull,push}.err
cat ~/.local/state/homehub/broken.* # self-detected breakage
ssh nas.lue.dk 'cat /volume1/homes/bl/homehub/.hub/log.tsv | tail'
~/homehub/bin/homehub filter | grep -n shadowed
```