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.
homehub — operator notes
Operational doc. Why each decision exists is in 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)
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
- Default deny.
rules/denylistis 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. - Set syntax. One home-relative path per line; trailing
/= subtree./Documents/→- /Documents/***;build/(no leading slash) matches at any depth. - Never add a git repo path to a set. Repos are class B (
homehub git inventory). ~/.configis 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.- Deletions never propagate as
--delete. They become tombstones on the hub, are reaped afterGRACE_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:
# /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 (
timedatectlon Debian,ntpdon 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--updateonly. --checksumis off, so a file edited to the same size in the same second as a previous sync may be missed. Re-runrsync -mcby 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,~/gitor/tmpwill dangle on a machine that has not cloned the target.
Troubleshooting
~/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