157 lines
5.5 KiB
Bash
157 lines
5.5 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# scripts/restic-init.sh
|
||
|
|
#
|
||
|
|
# One-time bootstrap for the Phase 2 restic backup chain. Run this on
|
||
|
|
# nullstone as root (sudo) AFTER `apt install restic mcrcon`.
|
||
|
|
#
|
||
|
|
# What it does:
|
||
|
|
# 1. Generates /etc/mc-backup.pw (40-byte random restic password) if absent.
|
||
|
|
# 2. Writes /etc/mc-backup.env (consumed by restic-backup-playerdata.sh).
|
||
|
|
# 3. Initialises the local restic repo at /home/user/restic/mc-frequent.
|
||
|
|
# 4. Takes a baseline snapshot so the timer's first run is fast.
|
||
|
|
# 5. Optionally adds an SFTP-mirror block once onyx is provisioned.
|
||
|
|
#
|
||
|
|
# Idempotent — re-running is safe; existing files are preserved.
|
||
|
|
#
|
||
|
|
# Cross-ref: docs/BACKUP-STRATEGY.md §8.2, docs/RUNBOOK-BACKUP-RESTORE.md.
|
||
|
|
set -euo pipefail
|
||
|
|
umask 077
|
||
|
|
|
||
|
|
if [ "$(id -u)" -ne 0 ]; then
|
||
|
|
echo "FATAL: must run as root (sudo)." >&2
|
||
|
|
exit 2
|
||
|
|
fi
|
||
|
|
|
||
|
|
if ! command -v restic >/dev/null 2>&1; then
|
||
|
|
echo "FATAL: restic not installed. Run: apt install restic mcrcon" >&2
|
||
|
|
exit 3
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Resolve target user — restic repo lives under their home so /opt
|
||
|
|
# disk pressure doesn't matter. nullstone: 142G free on /home.
|
||
|
|
TARGET_USER="${TARGET_USER:-user}"
|
||
|
|
if ! id "$TARGET_USER" >/dev/null 2>&1; then
|
||
|
|
echo "FATAL: user '$TARGET_USER' not found" >&2
|
||
|
|
exit 4
|
||
|
|
fi
|
||
|
|
TARGET_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6)
|
||
|
|
|
||
|
|
PW_FILE="/etc/mc-backup.pw"
|
||
|
|
ENV_FILE="/etc/mc-backup.env"
|
||
|
|
REPO_FREQUENT="${TARGET_HOME}/restic/mc-frequent"
|
||
|
|
REPO_WORLD="${TARGET_HOME}/restic/mc-world"
|
||
|
|
LOG_DIR="/var/log"
|
||
|
|
SENTINEL_DIR="/var/lib/mc-backup"
|
||
|
|
|
||
|
|
# 1. Password file
|
||
|
|
if [ ! -e "$PW_FILE" ]; then
|
||
|
|
head -c 40 /dev/urandom | base64 > "$PW_FILE"
|
||
|
|
chown root:root "$PW_FILE"
|
||
|
|
chmod 600 "$PW_FILE"
|
||
|
|
echo "Generated $PW_FILE (40 bytes random)."
|
||
|
|
else
|
||
|
|
echo "$PW_FILE already exists — keeping."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# 2. Env file (only created if missing; user can edit afterwards).
|
||
|
|
if [ ! -e "$ENV_FILE" ]; then
|
||
|
|
cat > "$ENV_FILE" <<EOF
|
||
|
|
# /etc/mc-backup.env — consumed by restic-backup-playerdata.sh and the
|
||
|
|
# Class B/C/D world script (TBD). Edit as needed.
|
||
|
|
|
||
|
|
RESTIC_REPOSITORY_FREQUENT=$REPO_FREQUENT
|
||
|
|
RESTIC_REPOSITORY_WORLD=$REPO_WORLD
|
||
|
|
RESTIC_PASSWORD_FILE=$PW_FILE
|
||
|
|
MC_DATA=/opt/docker/minecraft
|
||
|
|
MC_BACKUP_LOG=$LOG_DIR/mc-backup.log
|
||
|
|
MC_BACKUP_FREQUENT_SENTINEL=$SENTINEL_DIR/last-success-frequent
|
||
|
|
MC_BACKUP_WORLD_SENTINEL=$SENTINEL_DIR/last-success-world
|
||
|
|
|
||
|
|
# RCON — used to flush MC saves before snapshotting. Pull from the live
|
||
|
|
# compose file (services.mc.environment.RCON_PASSWORD).
|
||
|
|
MC_RCON_HOST=127.0.0.1
|
||
|
|
MC_RCON_PORT=25575
|
||
|
|
MC_RCON_PASSWORD=*redacted*
|
||
|
|
|
||
|
|
# Off-host mirror destination (onyx via Tailscale). Empty = skip mirror.
|
||
|
|
TS_OFFHOST_USER=mc-backup
|
||
|
|
TS_OFFHOST_HOST=100.64.0.1
|
||
|
|
TS_OFFHOST_PATH=/backups/nullstone-mc-restic
|
||
|
|
|
||
|
|
# Alerting — fill in once ntfy.s8n.ru is up. Leave blank for now.
|
||
|
|
HEARTBEAT_URL=
|
||
|
|
ALERT_URL=
|
||
|
|
EOF
|
||
|
|
chown root:"$(id -gn "$TARGET_USER")" "$ENV_FILE"
|
||
|
|
chmod 640 "$ENV_FILE"
|
||
|
|
echo "Wrote $ENV_FILE (mode 640, group=$(id -gn "$TARGET_USER"))."
|
||
|
|
else
|
||
|
|
echo "$ENV_FILE already exists — keeping."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# 3. Log + sentinel dirs (writable by target user).
|
||
|
|
mkdir -p "$SENTINEL_DIR"
|
||
|
|
chown "$TARGET_USER":"$(id -gn "$TARGET_USER")" "$SENTINEL_DIR"
|
||
|
|
chmod 750 "$SENTINEL_DIR"
|
||
|
|
touch "$LOG_DIR/mc-backup.log"
|
||
|
|
chown "$TARGET_USER":adm "$LOG_DIR/mc-backup.log" 2>/dev/null \
|
||
|
|
|| chown "$TARGET_USER":"$(id -gn "$TARGET_USER")" "$LOG_DIR/mc-backup.log"
|
||
|
|
chmod 640 "$LOG_DIR/mc-backup.log"
|
||
|
|
|
||
|
|
# 4. Repo init (idempotent — restic init exits non-zero if repo exists).
|
||
|
|
init_repo() {
|
||
|
|
local repo=$1
|
||
|
|
install -d -o "$TARGET_USER" -g "$(id -gn "$TARGET_USER")" -m 700 \
|
||
|
|
"$(dirname "$repo")" "$repo"
|
||
|
|
if RESTIC_PASSWORD_FILE="$PW_FILE" RESTIC_REPOSITORY="$repo" \
|
||
|
|
runuser -u "$TARGET_USER" -- restic snapshots >/dev/null 2>&1; then
|
||
|
|
echo "Repo $repo: already initialised."
|
||
|
|
else
|
||
|
|
RESTIC_PASSWORD_FILE="$PW_FILE" RESTIC_REPOSITORY="$repo" \
|
||
|
|
runuser -u "$TARGET_USER" -- restic init
|
||
|
|
echo "Repo $repo: initialised."
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
init_repo "$REPO_FREQUENT"
|
||
|
|
init_repo "$REPO_WORLD"
|
||
|
|
|
||
|
|
# 5. Baseline snapshot of the frequent repo so the first timer run is fast.
|
||
|
|
echo "Taking baseline snapshot into $REPO_FREQUENT ..."
|
||
|
|
runuser -u "$TARGET_USER" -- env \
|
||
|
|
RESTIC_PASSWORD_FILE="$PW_FILE" \
|
||
|
|
RESTIC_REPOSITORY="$REPO_FREQUENT" \
|
||
|
|
restic backup \
|
||
|
|
--tag playerdata --tag baseline --host "$(hostname)" \
|
||
|
|
--exclude='*.lock' --exclude='*.tmp' \
|
||
|
|
/opt/docker/minecraft/world/playerdata \
|
||
|
|
/opt/docker/minecraft/world/stats \
|
||
|
|
/opt/docker/minecraft/world/advancements \
|
||
|
|
/opt/docker/minecraft/homestead_data.db \
|
||
|
|
/opt/docker/minecraft/plugins/AuthMe \
|
||
|
|
/opt/docker/minecraft/plugins/CoreProtect/database.db \
|
||
|
|
/opt/docker/minecraft/plugins/LuckPerms \
|
||
|
|
|| echo "Baseline snapshot returned non-zero — review output above."
|
||
|
|
|
||
|
|
cat <<'NEXT'
|
||
|
|
|
||
|
|
---------------------------------------------------------------
|
||
|
|
restic-init.sh complete.
|
||
|
|
|
||
|
|
Next steps:
|
||
|
|
1. Install systemd units:
|
||
|
|
install -m644 scripts/systemd/mc-backup-playerdata.service \
|
||
|
|
/etc/systemd/system/
|
||
|
|
install -m644 scripts/systemd/mc-backup-playerdata.timer \
|
||
|
|
/etc/systemd/system/
|
||
|
|
install -m755 scripts/restic-backup-playerdata.sh \
|
||
|
|
/usr/local/bin/
|
||
|
|
|
||
|
|
2. systemctl daemon-reload
|
||
|
|
3. systemctl enable --now mc-backup-playerdata.timer
|
||
|
|
4. Tail: journalctl -u mc-backup-playerdata.service -f
|
||
|
|
|
||
|
|
Onyx (off-host mirror) provisioning is a separate step — see
|
||
|
|
docs/RUNBOOK-BACKUP-RESTORE.md "Phase 2 deployment".
|
||
|
|
---------------------------------------------------------------
|
||
|
|
NEXT
|