Closes most of #41. Operator's choice of off-host target is the only remaining open item; until that lands the script and timer are useful in local-only mode.
Summary
`scripts/backup.sh` gains a second off-host transport: `BACKUP_REMOTE_RSYNC`. Each run rsyncs the new archive into `/YYYY-MM-DD/` at the target with `--link-dest=..//`, so unchanged archives become hard links. Daily cost = (size of changed bytes) instead of (size of full archive). `BACKUP_REMOTE_SSH_KEY` lets the rsync ssh leg use a dedicated identity for restricted accounts (rsync.net etc.).
`BACKUP_REMOTE` (rclone, object-store transport) stays — the two transports compose, so an operator can run both in parallel.
`scripts/librenotes-backup.timer` moves to `OnCalendar=--* 03:00:00 Europe/Berlin` (was 03:17 UTC) per the issue.
`docs/operations.md` documents:
the rsync vs rclone trade-off,
a four-step restore procedure (parallel stack first, atomic swap second) with exact commands,
and the `.bak-*` rollback path if the swap fails.
Verification
`bash -n scripts/backup.sh` parses.
`scripts/backup.sh` is unchanged when no `BACKUP_REMOTE*` is set; existing local-disk-only deployments keep working.
Out of scope
Picking the actual off-host target (rsync.net vs B2 vs another VPS) is the operator's call and stays in #41.
Closes most of #41. Operator's choice of off-host target is the only remaining open item; until that lands the script and timer are useful in local-only mode.
## Summary
- \`scripts/backup.sh\` gains a second off-host transport: \`BACKUP_REMOTE_RSYNC\`. Each run rsyncs the new archive into \`<root>/YYYY-MM-DD/\` at the target with \`--link-dest=../<previous-day>/\`, so unchanged archives become hard links. Daily cost = (size of changed bytes) instead of (size of full archive). \`BACKUP_REMOTE_SSH_KEY\` lets the rsync ssh leg use a dedicated identity for restricted accounts (rsync.net etc.).
- \`BACKUP_REMOTE\` (rclone, object-store transport) stays — the two transports compose, so an operator can run both in parallel.
- \`scripts/librenotes-backup.timer\` moves to \`OnCalendar=*-*-* 03:00:00 Europe/Berlin\` (was 03:17 UTC) per the issue.
- \`docs/operations.md\` documents:
- the rsync vs rclone trade-off,
- a four-step restore procedure (parallel stack first, atomic swap second) with exact commands,
- and the \`.bak-*\` rollback path if the swap fails.
## Verification
- \`bash -n scripts/backup.sh\` parses.
- \`scripts/backup.sh\` is unchanged when no \`BACKUP_REMOTE*\` is set; existing local-disk-only deployments keep working.
## Out of scope
- Picking the actual off-host target (rsync.net vs B2 vs another VPS) is the operator's call and stays in #41.
backup.sh now supports BACKUP_REMOTE_RSYNC alongside BACKUP_REMOTE.
The rsync path writes <root>/YYYY-MM-DD/<archive> on the target with
--link-dest pointing at the previous day's directory, so unchanged
archives become hard links and daily snapshots cost almost zero
extra bytes. BACKUP_REMOTE_SSH_KEY routes the rsync ssh leg to a
dedicated identity (e.g. rsync.net restricted accounts).
Timer moved to 03:00 Europe/Berlin (was 03:17 UTC) per #41.
docs/operations.md: full restore procedure (parallel stack first,
then atomic swap) plus the rsync vs rclone trade-off. Closes most
of #41 — the only remaining task is the operator's choice of
off-host target.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes most of #41. Operator's choice of off-host target is the only remaining open item; until that lands the script and timer are useful in local-only mode.
Summary
Verification
Out of scope