restinpieces-backup

module
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 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.

It implements the three standard SQLite backup methods: the Online Backup API (cmd/onlineapi) and VACUUM INTO (cmd/vacuum) for local backups on the same machine, and the sqlite3_rsync protocol (cmd/sqlite-rsync) for remote backups to another machine.

It also provides pure Go rsync and sftp clients (cmd/rsync and cmd/sftp). They are meant to sync the local backups the online API and VACUUM methods make. Do not use them for live databases.

Method Use Description Implementation
sqlite3_rsync remote backup, delta-based syncs origin to replica, sending only the changed pages cmd/sqlite-rsync
Online Backup API local backup copies a live database page-by-page, with throttling cmd/onlineapi
VACUUM INTO local backup writes a clean, defragmented copy of the database cmd/vacuum
rsync pull client remote backup, delta-based pulls the latest-*.db snapshots over SSH cmd/rsync
sftp pull client remote backup pulls the newest snapshot over SFTP cmd/sftp

For point-in-time restores and syncing to S3 and other object stores, see restinpieces-litestream.

Content

sqlite3-rsync (cmd/sqlite-rsync)

The sqlite3_rsync protocol syncs a origin database and a replica by sending only the parts that changed, so it always matches the live database without copying the whole file.

This repository ships the protocol in two forms: a restinpieces framework implementation that embeds the origin role inside a restinpieces app, and a pair of mostly standalone origin and replica daemons that run without the framework.

restinpieces integration (cmd/sqlite-rsync/origin/restinpieces)

It embeds the origin role inside a restinpieces application as a daemon: the daemon creates a loopback listener and waits for the replica to reqeust updates of its configured databases. It reads the [backup.sqlite-rsync] section from the application's config.

The complete, runnable example is in main.go: it builds the application, creates the origin daemon from the app's config pointer, registers it with srv.AddDaemon, then runs the server. The daemon comes from the github.com/caasmo/restinpieces-backup/sqlitersync/origin package.

After that configure which databases to sync with the ripc tool:

ripc scaffold backup-sqlite-rsync app-rsync
ripc set backup.sqlite-rsync.entries.app-rsync.source_path /path/to/app.db

After that reload the application configuration.

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

A Go daemon implementing the origin role of the sqlite3-rsync protocol, for showcasing the protocol and for use outside restinpieces.

It runs on the machine that holds the live database and listens on one TCP address. For every connection it sends only the parts that changed, so the client receives just the changes instead of the whole file.

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

It reads a TOML config file with -config <path>. It uses the same shape ripc scaffolds:

[backup.sqlite-rsync]
listen_addr = "127.0.0.1:54321"

[backup.sqlite-rsync.entries.db]
source_path = "/path/to/db"
sync_timeout = "15m"
replica daemon (cmd/sqlite-rsync/replica/daemon)

A Go daemon implementing the replica role of the sqlite3-rsync protocol, for showcasing the protocol and for use outside restinpieces. It is an always-on client that pulls the origin's databases to local replica files on a fixed interval.

Every [entries.<name>] entry names a database the origin serves, the local file the replica writes, and how often to pull it. 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
Configuration

The daemon reads one TOML document given by -config; the document root is the replica configuration. origin_addr is the dial target of the origin listener. sync_timeout caps every single pull and is required. The optional [ssh] block selects the SSH transport; leaving it out requires -l/--local, and when present its port is required. Each [entries.<name>] entry pulls the named database into path every frequency; a zero frequency disables the entry. The parent directory of every configured path is created at startup if missing.

origin_addr = "127.0.0.1:54321"
sync_timeout = "15m"

[ssh]
user = "backup"
host = "127.0.0.1"
port = "22"
private_key_path = "/etc/restinpieces-backup/backup_ed25519"
host_key_path = "/etc/restinpieces-backup/host_key"

[entries.logs]
frequency = "30s"
path = "/var/backups/logs.db"

[entries.app]
frequency = "15m"
path = "/var/backups/app.db"
SSH mode (the default)

The default transport reaches the origin over SSH. The origin listens on 127.0.0.1:54321. Each sync connects to the origin machine's sshd, authenticates, and asks sshd to open 127.0.0.1:54321 on its side. The sync runs over that connection, as in local mode but with an SSH hop. No extra port is opened. The host key is pinned. The credentials come from the [ssh] block; the keys load into memory once at startup and are reused on every dial.

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
./sqlite-rsync-client -config /path/to/replica.toml -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. It runs without filesystem confinement by design: the sqlite-rsync protocol is label-addressed — no filesystem path crosses the wire — and each sync applies the received stream to exactly one pre-configured replica file, so a bug or a malicious origin cannot steer reads or writes anywhere else. The full rationale lives in AGENTS.md. Local mode is the trusted same-machine transport.

online API (cmd/onlineapi)

The Online Backup API is SQLite's built-in way to copy a live database: the copy runs while the database keeps being written, so a backup never blocks the application.

This repository ships the Online Backup API in two forms: a restinpieces framework implementation that registers the daemon inside a restinpieces app, and a standalone daemon that runs outside restinpieces.

restinpieces integration (cmd/onlineapi/restinpieces)

It embeds the onlineapi daemon inside a restinpieces application: the daemon produces snapshots of the databases configured in the backup section. The complete, runnable example is in main.go: it builds the application, creates the onlineapi daemon from the app's config pointer, registers it with srv.AddDaemon, then runs the server.

Configure which databases to back up with the ripc tool:

ripc scaffold backup-online app-online
ripc set backup.online.app-online.source_path /path/to/db

A SIGHUP reload of the application configuration is visible at the next daemon tick.

standalone daemon (cmd/onlineapi/daemon)

A Go daemon running the Online Backup API outside restinpieces, on any machine that holds live databases. It copies each database into a local backup directory while the database keeps being written, producing a snapshot at a fixed interval and updating a hard link to the last snapshot.

The rsync and sftp commands use that link as their sync target.

Build
go build -o onlineapi ./cmd/onlineapi/daemon
Configuration

The daemon reads a TOML file (default /etc/restinpieces-backup/onlineapi.toml, override with -config <path>). It uses the same [backup] shape the restinpieces application uses: each database is one [backup.online.<key>] section; <key> is a label you choose, for example app-online:

[backup.online.app-online]
source_path = "/data/app.db"
dest_path = "/data/backups"
frequency = "24h"
pages_per_step = 100
sleep_interval = "10ms"

VACUUM (cmd/vacuum)

The VACUUM INTO command writes a clean, defragmented copy of a database to a new file, so the snapshot stays compact and consistent while the database keeps being written.

This repository ships VACUUM INTO in two forms: a restinpieces framework implementation that registers the daemon inside a restinpieces app, and a standalone daemon that runs outside restinpieces.

restinpieces integration (cmd/vacuum/restinpieces)

It embeds the vacuum daemon inside a restinpieces application: the daemon produces VACUUM INTO snapshots of the databases configured in the backup section. The complete, runnable example is in main.go: it builds the application, creates the vacuum daemon from the app's config pointer, registers it with srv.AddDaemon, then runs the server.

Configure which databases to back up with the ripc tool:

ripc scaffold backup-vacuum app-vacuum
ripc set backup.vacuum.app-vacuum.source_path /path/to/db

A SIGHUP reload of the application configuration is visible at the next daemon tick.

standalone daemon (cmd/vacuum/daemon)

A Go daemon running VACUUM INTO outside restinpieces, on any machine that holds live databases. It produces a clean, defragmented 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 vacuum ./cmd/vacuum/daemon
Configuration

The daemon reads a TOML file (default /etc/restinpieces-backup/vacuum.toml, override with -config <path>). It uses the same [backup] shape the restinpieces application uses: each database is one [backup.vacuum.<key>] section:

[backup.vacuum.app-vacuum]
source_path = "/data/other.db"
dest_path = "/data/backups"
frequency = "24h"

rsync (cmd/rsync)

A pure Go rsync client. It pulls the local backups the online API and VACUUM methods make. Do not use it for live databases.

rsync one-shot (cmd/rsync/oneshot)

A one-shot run. A script to be used alongside a scheduler like cron or a systemd timer.

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.

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.

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

Same as the rsync one-shot local mode, but running the daemon:

RIP_BCK_SOURCE_DIR=/var/backups RIP_BCK_DEST_DIR=./backups RIP_BCK_INTERVAL=5m ./backup-daemon -l
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.

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

sftp (cmd/sftp)

A pure Go sftp client. It pulls the local backups the online API and VACUUM methods make. Do not use it for live databases.

sftp one-shot (cmd/sftp/oneshot)

A one-shot run. A script to be used alongside a scheduler like cron or a systemd timer.

It 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.

Directories

Path Synopsis
cmd
onlineapi/restinpieces command
Command restinpieces is an example of embedding the onlineapi daemon in a restinpieces application: the app serves its HTTP API and, in the background, produces online backup snapshots of the databases configured in the backup section.
Command restinpieces is an example of embedding the onlineapi daemon in a restinpieces application: the app serves its HTTP API and, in the background, produces online backup snapshots of the databases configured in the backup section.
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.
sqlite-rsync/replica/daemon command
Command daemon is the replica side of the sqlite3_rsync backup flow: it periodically asks the origin server for the database behind each configured entry and applies the received pages to that entry's local file.
Command daemon is the replica side of the sqlite3_rsync backup flow: it periodically asks the origin server for the database behind each configured entry and applies the received pages to that entry's local file.
vacuum/daemon command
vacuum/restinpieces command
Command restinpieces is an example of embedding the vacuum daemon in a restinpieces application: the app serves its HTTP API and, in the background, produces VACUUM INTO snapshots of the databases configured in the backup section.
Command restinpieces is an example of embedding the vacuum daemon in a restinpieces application: the app serves its HTTP API and, in the background, produces VACUUM INTO snapshots of the databases configured in the backup section.
Package config loads the backup client configuration from the environment.
Package config loads the backup client configuration from the environment.
sqlitersync/replica
Package replica holds the replica configuration document: the root shape the sqlite-rsync replica command reads from its -config TOML file.
Package replica holds the replica configuration document: the root shape the sqlite-rsync replica command reads from its -config TOML file.
internal
localcopy
Package localcopy provides the shared snapshot engine used by the vacuum and onlineapi daemons.
Package localcopy provides the shared snapshot engine used by the vacuum and onlineapi daemons.
sqlite
Package sqlite provides a read-only handle to a SQLite database file.
Package sqlite provides a read-only handle to a SQLite database file.
sqlitersync/replica
Package replica provides the transports the replica daemon uses to reach the origin server: dialing the origin's loopback listener directly when both run on the same machine (LocalClient), or opening a direct-tcpip channel through the remote machine's system sshd (SSHClient).
Package replica provides the transports the replica daemon uses to reach the origin server: dialing the origin's loopback listener directly when both run on the same machine (LocalClient), or opening a direct-tcpip channel through the remote machine's system sshd (SSHClient).
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 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.
replica
Package replica runs the pulling side of the sqlite3_rsync backup flow.
Package replica runs the pulling side of the sqlite3_rsync backup flow.
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