cloudflare r2用のバックアップマネージャ(?)
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Zel9278 b2ba95eec2 Default to zstd level 3
Level 19 runs on a single core at a couple of MB/s: the 1.4 GB Pleroma dump
took 15 minutes and the 1 GB relay dump 8. Level 3 does the relay in about
30 seconds for roughly 50% larger files, a few tens of MB.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 18:36:50 +09:00
src Default to zstd level 3 2026-10-07 18:36:50 +09:00
systemd timer: run twice daily at 06:00 and 18:00 local 2026-07-05 16:06:09 +09:00
.gitignore chore: ignore local secrets and backup artifacts 2026-08-21 10:19:37 +09:00
Cargo.lock Initial commit: backup-manager-san 2026-07-03 17:56:46 +09:00
Cargo.toml Initial commit: backup-manager-san 2026-07-03 17:56:46 +09:00
config.example.toml Default to zstd level 3 2026-10-07 18:36:50 +09:00
README.md Add generic targets and a status file for monitoring 2026-10-07 17:36:29 +09:00

backup-manager-san

PostgreSQL・ファイル・docker volume を Cloudflare R2 にバックアップするワンショット CLI。 ホスト上、LXC コンテナ内、docker コンテナ内(LXC の中の docker も可)で pg_dump / tar などを実行し、zstd 圧縮 → age 暗号化(.zst.enc)してから R2 に (大きいファイルはマルチパートで)アップロードします。完了時に Discord へ通知し、 結果を状態ファイル(JSON)に残すので infra-status などから監視できます。

LXC 上の Pleroma と ActivityRelay にはプリセット(kind = "pleroma" / "relay")が あり、それ以外は kind = "generic" で中身を1つずつ書きます。

lxd / docker グループに所属したユーザー(root 不要)で動かす前提で、 systemd --user タイマーで定期実行します。

各アーティファクトは <元名>.zst.enc(例 pleroma-db-<ts>.dump.zst.enc)として保存され、 中身は age 形式で暗号化されます。復号は backup-manager-san decrypt(下記)で行います。

バックアップ対象

ターゲット 内容
generic [[targets.artifacts]] に書いたもの(下記)
pleroma pg_dump -Fc(DB pleroma)/ /etc/pleroma・/opt/pleroma/config/ /var/lib/pleroma/uploads(任意)
relay docker の activity-relay-postgres を pg_dump -Fc/ docker volume relay_keys・relay_redis_data(redis は事前に SAVE)/ /root/relay-server(compose・.env)

R2 上のキー: <prefix>/<target>/<YYYY>/<MM>/<target>-<artifact>-<timestamp>.<ext>

generic ターゲット

各アーティファクトは「どこで実行するか(exec)」と「何を取るか(type)」の組み合わせ。

exec 実行場所
省略 / {} このホスト(sh -c)
{ lxc = "c" } lxc exec c -- sh -c
{ docker = "d" } docker exec d sh -c
{ lxc = "c", docker = "d" } LXC c の中の docker d
type 中身 拡張子
postgres pg_dump -Fc -Z 0 -d <db>(user で -U、run_as で su - して peer 認証) .dump
paths root(既定 /)からの相対パスで paths を tar(excludes 可) .tar
docker_volume /var/lib/docker/volumes/<volume>/_data を tar .tar
command command の標準出力 ext で指定

before に書いたコマンド(例: redis-cli SAVE)はアーティファクトの前に実行され、 失敗しても警告だけで続行します。例は config.example.toml の Mitra を参照。

状態ファイル

各ターゲットの最後の実行結果(成否・エラー・開始/終了時刻・アップロードしたキーとサイズ)を [general].status_file(既定 ~/.local/state/backup-manager/status.json)に書きます。 --target で一部だけ実行したときは、そのターゲットの分だけ置き換えます。

ビルド

cargo build --release
install -m 0755 -D target/release/backup-manager-san ~/.local/bin/backup-manager-san

どのディストリでも動く静的バイナリ(musl)は docker で作れます:

docker run --rm -v "$PWD":/src -w /src rust:alpine \
  sh -c 'apk add -q musl-dev && cargo build --release --target-dir target/alpine'
# → target/alpine/release/backup-manager-san

設定

設定ファイルは(--config 未指定時)~/.config/backup-manager/config.toml → /etc/backup-manager/config.toml の順に探索します。ユーザー運用ではホーム側に置きます。

mkdir -p ~/.config/backup-manager
cp config.example.toml ~/.config/backup-manager/config.toml
chmod 600 ~/.config/backup-manager/config.toml   # R2 の秘密鍵が入るので必ず
$EDITOR ~/.config/backup-manager/config.toml

R2 の API トークンは Cloudflare ダッシュボードの R2 → "Manage API Tokens" で Object Read & Write 権限のものを発行し、access_key_id / secret_access_key / account_id(または endpoint)を設定します。

暗号鍵([encryption])

推奨は**非対称(age recipient)**方式。鍵ペアを作り、公開鍵だけをこのホストの config に置き、秘密鍵はホスト外(手元の PC など)に保管します:

# 秘密鍵を手元に生成(このホストには置かない想定)
backup-manager-san keygen -o ~/backup-identity.txt
# → 標準エラーに age1... の公開 recipient が出るので config.toml に貼る:
#   [encryption]
#   recipients = ["age1..."]

簡易にやるなら対称パスフレーズでも可(recipients を空にして passphrase = "...")。

Discord 通知([notify.discord])

Discord のチャンネル設定 → 連携サービス → Webhook で URL を発行し、 webhook_url に設定します。on = "failure" にすると失敗時のみ通知します。 通知には各オブジェクト名と圧縮後サイズ、合計サイズが載ります。

実行

# 設定の検証だけ(dump/upload はしない)
backup-manager-san --dry-run

# 全ターゲット実行
backup-manager-san

# 特定ターゲットのみ
backup-manager-san --target pleroma

# 保持期間による削除をスキップ
backup-manager-san --no-prune

ログレベルは RUST_LOG=debug などで調整。

定期実行(systemd --user timer)

mkdir -p ~/.config/systemd/user
cp systemd/backup-manager-san.service ~/.config/systemd/user/
cp systemd/backup-manager-san.timer   ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now backup-manager-san.timer

# ログアウト中/未ログインでもタイマーを動かす(初回のみ)
loginctl enable-linger "$USER"

# 手動トリガ / 状態確認
systemctl --user start backup-manager-san.service
systemctl --user list-timers backup-manager-san.timer
# ログ(--user -u が空になる環境があるのでこちらが確実)
journalctl _SYSTEMD_USER_UNIT=backup-manager-san.service -n 50

復元の概要

R2 からオブジェクト(*.zst.enc)を取得し、まず復号します。復号すると元の .dump(Postgres custom フォーマット)や .tar に戻ります。

# 非対称方式なら秘密鍵ファイルを指定
backup-manager-san decrypt --identity ~/backup-identity.txt \
  -i pleroma-db-<ts>.dump.zst.enc -o pleroma-db.dump
# パスフレーズ方式なら config の passphrase を使う(--identity 不要)
backup-manager-san decrypt -i relay-db-<ts>.dump.zst.enc -o relay-db.dump

Pleroma DB(custom フォーマット):

lxc file push pleroma-db.dump pleroma/tmp/restore.dump
lxc exec pleroma -- su - postgres -c \
  'pg_restore --clean --if-exists -d pleroma /tmp/restore.dump'

Pleroma 設定 / uploads(/ 起点の相対パスで格納された tar):

backup-manager-san decrypt --identity ~/backup-identity.txt \
  -i pleroma-config-<ts>.tar.zst.enc -o pleroma-config.tar
lxc exec pleroma -- tar xf - -C /   < pleroma-config.tar

Relay DB:

cat relay-db.dump | lxc exec relay -- \
  docker exec -i activity-relay-postgres pg_restore --clean --if-exists -U relay -d relay

Relay volume: 対象コンテナを停止してから volume 内へ展開:

backup-manager-san decrypt --identity ~/backup-identity.txt \
  -i relay-volume-relay_keys-<ts>.tar.zst.enc -o relay_keys.tar
lxc exec relay -- docker compose -f /root/relay-server/compose.yaml down
lxc exec relay -- tar xf - -C / < relay_keys.tar
lxc exec relay -- docker compose -f /root/relay-server/compose.yaml up -d

tar アーカイブは先頭 / を除いた相対パスで格納しているため、tar x -C / で 元の位置に戻ります。

注意

  • staging_dir には「単一アーティファクトの最大サイズ」ぶんの空き容量が必要 (Pleroma の uploads を含める場合は特に)。アップロード成功後に逐次削除されます。
  • 保持期間 (retention_days) の削除はバックアップ成功後に走るため、常に最新が残ります。
  • postgres は稼働中でも pg_dump は一貫したスナップショットを取れます。redis volume は アーカイブ前に SAVE を発行しています。