restinpieces-backup

module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2026 License: MIT

README

restinpieces-backup

Go Reference Test golangci-lint Coverage sloc deps GitHub Release Built Go

This repository holds the backup tools for a restinpieces deployment.

cmd/sqlite-rsync/origin/daemon and cmd/sqlite-rsync/replica/daemon keep a live database and a replica — a second copy of the database — in continuous sync. Over the sqlite3_rsync protocol the replica receives only the parts that changed, so it always matches the live database without copying the whole file.

A snapshot is a full copy of a database, frozen at one moment in time. The snapshot tools come in two steps: making a snapshot, then moving it to another machine.

cmd/local-copy/daemon makes the snapshots on the machine that runs the databases: it produces a snapshot of each database at a fixed interval and keeps a hard link — a second name for the same file — to the last snapshot.

cmd/rsync/oneshot, cmd/rsync/daemon, and cmd/sftp/oneshot move snapshots to a backup machine, so a broken server does not lose its backups. They pull the latest-*.db links (or the compressed .bck.gz archives) and verify every received database with PRAGMA integrity_check — SQLite's built-in check that a database file is not corrupted.

If you need to restore a database to any past moment, not just the latest snapshot, use the litestream package — see restinpieces-litestream.

Content

sqlite3-rsync origin (cmd/sqlite-rsync/origin/daemon)

The origin server runs on the machine that holds the live database. It listens on one TCP address, and for every connection it sends only the parts that changed, so the client receives just the changes instead of the whole file. The server never starts a sync on its own: the client decides when. It serves the databases listed in its TOML config file, one per label.

Build
go build -o sqlite-rsync-origin ./cmd/sqlite-rsync/origin/daemon
Configuration file

It reads a TOML config file with -config <path>:

[backup.files.db]
source_path = "/path/to/db"

sqlite3-rsync client (cmd/sqlite-rsync/replica/daemon)

The client is an always-on daemon that copies the origin's database to a replica. On each interval it connects to the origin server and brings the replica database up to the origin's content. Two ways to connect: over SSH (the default), or directly on the same machine with -l/--local.

Build
go build -o sqlite-rsync-client ./cmd/sqlite-rsync/replica/daemon
Environment variables
Variable Required Description
RIP_BCK_REPLICA_LABEL yes The name both sides use for the database (the origin serves db for now); the replica lives at <dir>/<label>.db
RIP_BCK_REPLICA_DIR yes Local directory the replica database is written into (created if missing)

Everything else is hardcoded for now: the origin address (127.0.0.1:9909), the sync interval (15 minutes), and the SSH credentials — see SSH mode (the default). Edit them in main.go, rebuild, and run.

SSH mode (the default)

The default transport reaches the origin over SSH. The origin server runs on the machine that also runs the system SSH server and listens on 127.0.0.1:9909. Each sync connects to that machine's sshd, authenticates with the private key, and asks the SSH server to open the connection to 127.0.0.1:9909 on its side — the sync then runs over that connection, exactly as in local mode but with the SSH hop in front. No extra port is opened on the origin. The host key is pinned: a connection to a server with any other key fails. The credentials are hardcoded in main.go for now: user backup, host 127.0.0.1, port 22, private key at /etc/restinpieces-backup/backup_ed25519, host key at /etc/restinpieces-backup/host_key; the host is a placeholder that points at the local machine so SSH mode can be exercised in a development setup.

Local mode for testing (-l / --local)

-l runs the sync without SSH: the client connects to the origin's listener directly. This is how to test both programs on one machine. Start the server in one terminal, then run the client in a second:

# Terminal 1 — origin server
./sqlite-rsync-origin -config /path/to/config.toml
# Terminal 2 — client in local mode
RIP_BCK_REPLICA_LABEL=db RIP_BCK_REPLICA_DIR=/tmp/replica ./sqlite-rsync-client -l
Signals

SIGINT, SIGQUIT, and SIGTERM stop the daemon gracefully: the in-flight sync is cancelled, the connection is closed to unblock a sync stuck reading or writing, and the process exits within 15 seconds.

Security

In SSH mode the client loads the SSH keys into memory once at startup, then restricts itself before the first sync: the process may access only the replica directory (read/write) and /etc (read-only), so a bug or a malicious origin cannot read or write anything beyond that. The restriction uses the landlock sandbox described in cmd/rsync/daemon/README.md. Local mode is the trusted same-machine transport and runs without the restriction.

local-copy daemon (cmd/local-copy/daemon)

cmd/local-copy/daemon copies the databases on a machine into local backup directories: it produces a snapshot of each database at a fixed interval and updates a hard link to the last snapshot. The rsync and sftp commands use that link as their sync target.

Build
go build -o local-copy ./cmd/local-copy/daemon
Configuration

The daemon reads a TOML file (default /etc/restinpieces-backup/local-copy.toml, override with -config <path>). Each database is one [files.<key>] section; <key> is a name you choose, for example app_db:

[files.app_db]
source_path = "/data/app.db"
dest_path = "/data/backups"
frequency = "24h"
Field Description
source_path The database file to back up (required)
dest_path Directory the snapshots go into (required)
frequency How often to back up this database, as a Go duration such as 24h (required)
strategy online (default) copies the live database in small steps; vacuum first compacts a private copy, then copies it
compression true writes a gzip-compressed .bck.gz snapshot; false (default) writes a plain .db copy
online_api_pages_per_step In online mode, how many pages (SQLite's unit of storage) each step copies (default 100)
online_api_sleep_interval Pause between online steps, as a Go duration (default 10ms)

At startup every entry is validated — the paths must exist and frequency must be positive — and a broken config refuses to start. An entry whose source_path or dest_path is empty is skipped. Snapshot files are named <key>-<name>-<timestamp>.db (or .bck.gz when compressed); the latest- link is kept for plain snapshots only.

Backup cadence
  • The first backup runs immediately at startup; the next one starts one full interval after the previous one completes.
  • The interval is the smallest frequency among the configured entries.
  • Backups run one at a time: a tick that fires while a backup is still running is dropped.
  • A failing backup is logged and retried on the next tick — the daemon never exits on a failure.
Signals

SIGINT, SIGQUIT, and SIGTERM stop the daemon gracefully: the in-flight backup is cancelled and the process exits. A copy aborted mid-way by the shutdown is not an error — the next backup covers it.

rsync one-shot (cmd/rsync/oneshot)

The rsync client runs as the receiver: it starts the rsync binary in server (sender) mode — over SSH, or locally on the same machine with -l — and pulls every latest-*.db file (the hard links the local-copy daemon keeps) into a local destination directory. Files are written atomically (temp file + rename), and every received database must pass PRAGMA integrity_check.

Build
go build -o backup-client ./cmd/rsync/oneshot

The machine that runs the rsync server side (the remote host in SSH mode, the local machine in local mode) must have an rsync-compatible binary in PATH.

Environment variables
Variable Required Description
RIP_BCK_SOURCE_DIR yes Backup directory on the server containing the latest-*.db hard links. In SSH mode the directory is shell-quoted on the remote command; only the latest-*.db glob is expanded by the remote shell
RIP_BCK_DEST_DIR yes Local directory the files are pulled into (created if missing)
RIP_BCK_SSH_USER yes* SSH user
RIP_BCK_SSH_HOST yes* SSH host
RIP_BCK_SSH_PORT no SSH port (default 22)
RIP_BCK_SSH_PRIVATE_KEY_PATH yes* Path to the SSH private key used for authentication
RIP_BCK_SSH_HOST_KEY_PATH yes* Path to the server's public host key; the host key is pinned, so a dial against any other key fails

* Only required in SSH mode (the default). Local mode needs only RIP_BCK_SOURCE_DIR and RIP_BCK_DEST_DIR.

Local mode for testing (-l / --local)

-l runs the whole pipeline without SSH: the client starts the local rsync binary in server mode on the same machine and pulls from a local RIP_BCK_SOURCE_DIR. This is how to test the whole pipeline without a remote machine: run it on the server itself (where the backup directory with the latest-*.db hard links already lives) or on any machine that has a copy of the source directory and an rsync binary in PATH:

RIP_BCK_SOURCE_DIR=/var/backups RIP_BCK_DEST_DIR=./backups ./backup-client -l

In local mode the source glob is expanded by the client itself (there is no shell in between), so zero matches fail before the transfer starts with no backup files received: server glob matched nothing.

rsync daemon (cmd/rsync/daemon)

The rsync daemon performs the same transfer as the rsync one-shot — the receiver protocol over SSH (or locally with -l), pulling the latest-*.db hard links, atomic writes, PRAGMA integrity_check — but on a fixed interval instead of once. It is the always-on alternative to scheduling the one-shot command.

Build
go build -o backup-daemon ./cmd/rsync/daemon

The machine that runs the rsync server side (the remote host in SSH mode, the local machine in local mode) must have an rsync-compatible binary in PATH.

Environment variables
Variable Required Description
RIP_BCK_SOURCE_DIR yes Backup directory on the server containing the latest-*.db hard links
RIP_BCK_DEST_DIR yes Local directory the files are pulled into (created if missing)
RIP_BCK_INTERVAL yes How often to run a backup, as a Go duration string (e.g. 5m, 1h); must be positive
RIP_BCK_SSH_USER yes* SSH user
RIP_BCK_SSH_HOST yes* SSH host
RIP_BCK_SSH_PORT no SSH port (default 22)
RIP_BCK_SSH_PRIVATE_KEY_PATH yes* Path to the SSH private key used for authentication
RIP_BCK_SSH_HOST_KEY_PATH yes* Path to the server's public host key; the host key is pinned, so a dial against any other key fails

* Only required in SSH mode (the default). Local mode needs only RIP_BCK_SOURCE_DIR, RIP_BCK_DEST_DIR, and RIP_BCK_INTERVAL.

Local mode for testing (-l / --local)

-l runs the whole pipeline without SSH: the client starts the local rsync binary in server mode on the same machine and pulls from a local RIP_BCK_SOURCE_DIR. This is how to test the whole pipeline without a remote machine: run it on the server itself (where the backup directory with the latest-*.db hard links already lives) or on any machine that has a copy of the source directory and an rsync binary in PATH:

RIP_BCK_SOURCE_DIR=/var/backups RIP_BCK_DEST_DIR=./backups RIP_BCK_INTERVAL=5m ./backup-daemon -l

Ctrl-C stops the daemon gracefully. In local mode the source glob is expanded by the client itself (there is no shell in between), so zero matches fail before the transfer starts with no backup files received: server glob matched nothing.

Backup cadence
  • The first backup runs immediately at startup; the next one starts one full interval after the previous backup completes.
  • Backups run one at a time: a tick that fires while a backup is still running is dropped, so at most one backup per interval is guaranteed.
  • At least one backup per interval is not guaranteed: when a transfer takes longer than the interval (e.g. a 12-minute transfer with a 5-minute interval), backups run back-to-back and the intended cadence is lost. Set the interval so a single backup finishes comfortably within it.
  • A failing backup (transfer or verification) is logged and the next tick retries — the daemon never exits on a failure.
  • The destination directory is created at startup and re-created by the receiver on every run, so a mid-run removal self-heals on the next tick.
Signals

SIGINT, SIGQUIT, and SIGTERM stop the daemon gracefully: the in-flight transfer is cancelled and the process has up to 15 seconds to exit; a verification scan that is already running is allowed to finish within that time.

Security

The daemon's security is documented in cmd/rsync/daemon/README.md: the landlock sandbox and the threat it addresses, the in-memory SSH keys, and the optional systemd hardening.

sftp one-shot (cmd/sftp/oneshot)

The SFTP client connects to the server with a pinned host key, opens an SFTP session, lists the remote backup directory, picks the most recent snapshot by filename (names carry a timestamp, so sorting the names finds the latest), downloads it, decompresses the .bck.gz archive, and verifies the resulting database with PRAGMA integrity_check.

Build
go build -o sftp-client ./cmd/sftp/oneshot

The connection parameters and directories are hardcoded in the Config struct at the top of main() (SSHUser, SSHHost, SSHPort, SSHPrivateKeyPath, SSHHostKeyPath, RemoteBackupDir, LocalBackupDir) — edit them, rebuild, and run.

Running on a schedule

The one-shot commands (cmd/rsync/oneshot and cmd/sftp/oneshot) are one-shot runs: exit code 0 means the transfer and the integrity verification succeeded, 1 means any step failed (e.g. the glob matched nothing, a file failed verification, or the server process errored). Run them from a cron job or a systemd timer. The daemons (cmd/rsync/daemon, cmd/sqlite-rsync/replica/daemon, cmd/sqlite-rsync/origin/daemon) are always-on and need no scheduling.

Cron

rsync client example:

*/5 * * * * RIP_BCK_SOURCE_DIR=/var/backups RIP_BCK_DEST_DIR=/home/user/backups RIP_BCK_SSH_USER=backup RIP_BCK_SSH_HOST=server.example.com RIP_BCK_SSH_PORT=22 RIP_BCK_SSH_PRIVATE_KEY_PATH=/home/user/.ssh/id_ed25519 RIP_BCK_SSH_HOST_KEY_PATH=/etc/ssh_host_ed25519_key.pub /usr/local/bin/backup-client 2>>/var/log/backup-client.log
Systemd timer

Environment in a separate file:

# /etc/backup-client.env
RIP_BCK_SOURCE_DIR=/var/backups
RIP_BCK_DEST_DIR=/home/user/backups
RIP_BCK_SSH_USER=backup
RIP_BCK_SSH_HOST=server.example.com
RIP_BCK_SSH_PORT=22
RIP_BCK_SSH_PRIVATE_KEY_PATH=/home/user/.ssh/id_ed25519
RIP_BCK_SSH_HOST_KEY_PATH=/etc/ssh_host_ed25519_key.pub
# /etc/systemd/system/backup-client.service
[Unit]
Description=restinpieces backup client (rsync pull)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/backup-client.env
ExecStart=/usr/local/bin/backup-client
# /etc/systemd/system/backup-client.timer
[Unit]
Description=Run the restinpieces backup client every 5 minutes

[Timer]
OnCalendar=*:0/5

[Install]
WantedBy=timers.target
systemctl enable --now backup-client.timer

Directories

Path Synopsis
cmd
rsync/daemon command
rsync/oneshot command
sftp/oneshot command
sqlite-rsync/origin/restinpieces command
Command restinpieces is an example of embedding the origin daemon in a restinpieces application: the app serves its HTTP API and, on a second loopback listener, serves its configured databases over the sqlite3_rsync protocol.
Command restinpieces is an example of embedding the origin daemon in a restinpieces application: the app serves its HTTP API and, on a second loopback listener, serves its configured databases over the sqlite3_rsync protocol.
Package config loads the backup client configuration from the environment.
Package config loads the backup client configuration from the environment.
Package landlock confines the process to the backup paths, applied once at startup.
Package landlock confines the process to the backup paths, applied once at startup.
Package rsync provides the rsync transfer flow shared by the one-shot backup script (cmd/rsync) and the backup daemon.
Package rsync provides the rsync transfer flow shared by the one-shot backup script (cmd/rsync) and the backup daemon.
Package sqlitedb provides a read-only handle to a SQLite database file.
Package sqlitedb provides a read-only handle to a SQLite database file.
Package sqlitersync sends the label of the database to back up from the client to the server.
Package sqlitersync sends the label of the database to back up from the client to the server.
Package ssh provides the SSH connection helpers shared by the backup client commands: loading the in-memory credentials, the host-key-pinned dial, and remote command sessions.
Package ssh provides the SSH connection helpers shared by the backup client commands: loading the in-memory credentials, the host-key-pinned dial, and remote command sessions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL