Compare commits
3
Commits
43b3ab5841
...
c080c780ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c080c780ba | ||
|
|
0dd8fed645 | ||
|
|
6c4550a9ea |
+1
-5
@@ -30,9 +30,5 @@
|
||||
"solar": {
|
||||
"DB": ["solar"],
|
||||
"FS": ["/var/www/html/solar"]
|
||||
},
|
||||
"spot": {
|
||||
"DB": ["spot"],
|
||||
"FS": ["/var/www"]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+180
@@ -0,0 +1,180 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Nightly plakar backup driver. Three phases:
|
||||
# 1. dumps — incus's own DBs, plus application-consistent database
|
||||
# dumps taken with `incus exec` (container-local auth: no DB
|
||||
# users, no network exposure). Auto-discovered in every RUNNING
|
||||
# container — no manifest to go stale: native MariaDB
|
||||
# (mariadb-dump, all non-system DBs), native PostgreSQL (pg_dump
|
||||
# per DB + pg_dumpall --globals-only, peer auth as postgres),
|
||||
# and PostgreSQL inside docker containers (image name matching
|
||||
# "postgres", user from $POSTGRES_USER). Dumps are staged plain
|
||||
# (not gzipped, so plakar's chunking dedups near-identical
|
||||
# consecutive dumps) in the dump dir, then backed up as one
|
||||
# snapshot.
|
||||
# 2. sources — every fs source listed in the sources file (one
|
||||
# `plakar source add` name per line, #-comments allowed).
|
||||
# 3. retention — prune + maintenance on the kloset.
|
||||
# The kloset target is whatever `plakar at` accepts — normally the S3
|
||||
# store (`-k @s3`, direct-to-S3, no local staging); a path
|
||||
# (e.g. -k /backup/plakar-test) only for testing. -S optionally
|
||||
# replicates the kloset to a second store afterwards (`plakar sync`);
|
||||
# unused in the current single-store setup.
|
||||
#
|
||||
# Usage: plakar-backup.sh -k <kloset> [-f <sources-file>]
|
||||
# [-r <retention-days>] [-S <sync-store>] [-d <dump-dir>]
|
||||
#
|
||||
# The kloset passphrase comes from $PLAKAR_PASSPHRASE or, failing
|
||||
# that, /root/.plakar-passphrase (mode 600).
|
||||
|
||||
set -u
|
||||
|
||||
KLOSET=""
|
||||
SRC_FILE=/root/scripts/plakar-sources
|
||||
# optional DB exclude list, one dump path per line as printed by the
|
||||
# "dump" log lines (<ct>/<db> or <ct>/<docker-name>/<db>); every skip
|
||||
# is logged loudly so the list can't rot silently like a manifest
|
||||
EXCLUDE_FILE=/root/scripts/plakar-db-exclude
|
||||
RETENTION=30
|
||||
SYNC_STORE=""
|
||||
DUMP_DIR=/backup/plakar-dumps
|
||||
LOCKFILE=/run/lock/plakar-backup.lock
|
||||
PASSFILE=/root/.plakar-passphrase
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 -k <kloset> [-f <sources-file>] [-r <retention-days>] [-S <sync-store>] [-d <dump-dir>]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while getopts k:f:r:S:d: flag; do
|
||||
case "${flag}" in
|
||||
k) KLOSET=${OPTARG};;
|
||||
f) SRC_FILE=${OPTARG};;
|
||||
r) RETENTION=${OPTARG};;
|
||||
S) SYNC_STORE=${OPTARG};;
|
||||
d) DUMP_DIR=${OPTARG};;
|
||||
*) usage;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$KLOSET" ] || usage
|
||||
[ -r "$SRC_FILE" ] || { echo "sources file $SRC_FILE not readable" >&2; exit 2; }
|
||||
|
||||
exec 9>"$LOCKFILE"
|
||||
if ! flock -n 9; then
|
||||
echo "another plakar-backup run holds $LOCKFILE, aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log() { echo "[$(date '+%F %T')] $*"; }
|
||||
|
||||
excluded() {
|
||||
[ -r "$EXCLUDE_FILE" ] && grep -qx "$1" "$EXCLUDE_FILE" \
|
||||
&& log "SKIP $1 (listed in $EXCLUDE_FILE)"
|
||||
}
|
||||
|
||||
if [ -z "${PLAKAR_PASSPHRASE:-}" ]; then
|
||||
[ -r "$PASSFILE" ] || { echo "no PLAKAR_PASSPHRASE in env and $PASSFILE not readable" >&2; exit 1; }
|
||||
PLAKAR_PASSPHRASE=$(cat "$PASSFILE")
|
||||
fi
|
||||
export PLAKAR_PASSPHRASE
|
||||
|
||||
rc=0
|
||||
|
||||
# incus's own state (instance configs, profiles, devices) — needed to
|
||||
# rebuild instances, not covered by any file/DB source
|
||||
mkdir -p "$DUMP_DIR/incus"
|
||||
incus admin sql global .dump > "$DUMP_DIR/incus/incus-global-db.sql" || rc=1
|
||||
incus admin sql local .dump > "$DUMP_DIR/incus/incus-local-db.sql" || rc=1
|
||||
|
||||
# Database dumps, auto-discovered per running container: native
|
||||
# MariaDB, native PostgreSQL, and PostgreSQL inside docker containers
|
||||
# (matched on the docker image name containing "postgres").
|
||||
PG_LIST="SELECT datname FROM pg_database WHERE NOT datistemplate AND datname <> 'postgres'"
|
||||
for ct in $(incus list status=running -c n -f csv); do
|
||||
# native MariaDB/MySQL (unix-socket root auth) — binaries may carry
|
||||
# either naming (e.g. mail: hand-installed mariadb-dump + mysql client)
|
||||
mdump=$(incus exec "$ct" -- sh -c 'command -v mariadb-dump || command -v mysqldump' 2>/dev/null)
|
||||
mclient=$(incus exec "$ct" -- sh -c 'command -v mariadb || command -v mysql' 2>/dev/null)
|
||||
if [ -n "$mdump" ] && [ -n "$mclient" ]; then
|
||||
dbs=$(incus exec "$ct" -- "$mclient" -N -B -e 'SHOW DATABASES') \
|
||||
|| { echo "listing mariadb databases on $ct failed" >&2; rc=1; dbs=""; }
|
||||
for db in $(printf '%s\n' "$dbs" \
|
||||
| grep -Ev '^(information_schema|performance_schema|mysql|sys)$'); do
|
||||
excluded "$ct/$db" && continue
|
||||
log "dump $ct/$db (mariadb)"
|
||||
mkdir -p "$DUMP_DIR/mariadb/$ct"
|
||||
incus exec "$ct" -- "$mdump" --single-transaction --events --routines --triggers \
|
||||
--databases "$db" > "$DUMP_DIR/mariadb/$ct/$db.sql" \
|
||||
|| { echo "dump $ct/$db failed" >&2; rc=1; }
|
||||
done
|
||||
# users + grants (the mariadb equivalent of pg_dumpall
|
||||
# --globals-only): replayable SHOW GRANTS statements, portable
|
||||
# across versions unlike a raw mysql-schema dump
|
||||
log "dump $ct/grants (mariadb)"
|
||||
incus exec "$ct" -- sh -c "$mclient -NBe \"SELECT CONCAT('SHOW GRANTS FOR ', QUOTE(user), '@', QUOTE(host), ';') FROM mysql.user\" | $mclient -NB | sed 's/\$/;/'" \
|
||||
> "$DUMP_DIR/mariadb/$ct/grants.sql" \
|
||||
|| { echo "grants dump on $ct failed" >&2; rc=1; }
|
||||
elif [ -n "$mdump$mclient" ]; then
|
||||
echo "$ct has only one of dump/client mariadb binaries, skipping" >&2; rc=1
|
||||
fi
|
||||
|
||||
# native PostgreSQL (peer auth as the postgres user)
|
||||
if incus exec "$ct" -- sh -c 'command -v pg_dump' >/dev/null 2>&1; then
|
||||
mkdir -p "$DUMP_DIR/postgres/$ct"
|
||||
incus exec "$ct" -- su -s /bin/sh postgres -c "pg_dumpall --globals-only" \
|
||||
> "$DUMP_DIR/postgres/$ct/globals.sql" || rc=1
|
||||
dbs=$(incus exec "$ct" -- su -s /bin/sh postgres -c "psql -AtX -c \"$PG_LIST\"") \
|
||||
|| { echo "listing postgres databases on $ct failed" >&2; rc=1; dbs=""; }
|
||||
for db in $dbs; do
|
||||
excluded "$ct/$db" && continue
|
||||
log "dump $ct/$db (postgres)"
|
||||
incus exec "$ct" -- su -s /bin/sh postgres -c "pg_dump --clean --if-exists $db" \
|
||||
> "$DUMP_DIR/postgres/$ct/$db.sql" \
|
||||
|| { echo "dump $ct/$db failed" >&2; rc=1; }
|
||||
done
|
||||
fi
|
||||
|
||||
# PostgreSQL inside docker (e.g. outline, login)
|
||||
incus exec "$ct" -- sh -c 'command -v docker' >/dev/null 2>&1 || continue
|
||||
for dc in $(incus exec "$ct" -- docker ps --format '{{.Names}} {{.Image}}' 2>/dev/null \
|
||||
| awk 'tolower($2) ~ /postgres/ {print $1}'); do
|
||||
pguser=$(incus exec "$ct" -- docker exec "$dc" sh -c 'echo "${POSTGRES_USER:-postgres}"') \
|
||||
|| { echo "reading POSTGRES_USER on $ct/$dc failed" >&2; rc=1; continue; }
|
||||
mkdir -p "$DUMP_DIR/postgres/$ct/$dc"
|
||||
incus exec "$ct" -- docker exec "$dc" pg_dumpall -U "$pguser" --globals-only \
|
||||
> "$DUMP_DIR/postgres/$ct/$dc/globals.sql" || rc=1
|
||||
dbs=$(incus exec "$ct" -- docker exec "$dc" psql -U "$pguser" -AtX -c "$PG_LIST") \
|
||||
|| { echo "listing postgres databases on $ct/$dc failed" >&2; rc=1; dbs=""; }
|
||||
for db in $dbs; do
|
||||
excluded "$ct/$dc/$db" && continue
|
||||
log "dump $ct/$dc/$db (postgres)"
|
||||
incus exec "$ct" -- docker exec "$dc" pg_dump -U "$pguser" --clean --if-exists "$db" \
|
||||
> "$DUMP_DIR/postgres/$ct/$dc/$db.sql" \
|
||||
|| { echo "dump $ct/$dc/$db failed" >&2; rc=1; }
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
log "backup dumps ($DUMP_DIR)"
|
||||
plakar -quiet at "$KLOSET" backup -tag dumps "$DUMP_DIR" \
|
||||
|| { echo "backup of $DUMP_DIR failed" >&2; rc=1; }
|
||||
|
||||
for src in $(grep -Ev '^[[:space:]]*(#|$)' "$SRC_FILE"); do
|
||||
log "backup @$src"
|
||||
plakar -quiet at "$KLOSET" backup -tag "$src" "@$src" \
|
||||
|| { echo "backup @$src failed" >&2; rc=1; }
|
||||
done
|
||||
|
||||
log "prune: keep the last $RETENTION days of snapshots"
|
||||
plakar at "$KLOSET" prune -days "$RETENTION" -apply || rc=1
|
||||
plakar at "$KLOSET" maintenance || rc=1
|
||||
|
||||
if [ -n "$SYNC_STORE" ]; then
|
||||
log "sync to @$SYNC_STORE"
|
||||
plakar at "$KLOSET" sync to "@$SYNC_STORE" \
|
||||
|| { echo "sync to @$SYNC_STORE failed" >&2; rc=1; }
|
||||
fi
|
||||
|
||||
log "done (rc=$rc)"
|
||||
exit $rc
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Nightly plakar backup of the incus instances (leg 4): backs up the
|
||||
# configured incus source (the plakar incus integration walking the
|
||||
# quiesced replicas of the `backup` project) into the target kloset,
|
||||
# then applies retention. Runs CHAINED after incus-copy.sh in the same
|
||||
# cron entry — the snapshot is only as fresh as the last completed
|
||||
# local replica refresh:
|
||||
#
|
||||
# 0 1 * * * incus-copy.sh -p backup -s backup >> /var/log/incus-copy.log 2>&1 ; plakar-incus-backup.sh >> /var/log/plakar-incus.log 2>&1
|
||||
#
|
||||
# Usage: plakar-incus-backup.sh [-k <kloset>] [-s <source>] [-r <retention-days>]
|
||||
#
|
||||
# The kloset passphrase comes from $PLAKAR_PASSPHRASE or, failing
|
||||
# that, /root/.plakar-passphrase (mode 600).
|
||||
|
||||
set -u
|
||||
|
||||
KLOSET=@s3incus
|
||||
SOURCE=ks4-incus
|
||||
RETENTION=30
|
||||
LOCKFILE=/run/lock/plakar-incus-backup.lock
|
||||
PASSFILE=/root/.plakar-passphrase
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [-k <kloset>] [-s <source>] [-r <retention-days>]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while getopts k:s:r: flag; do
|
||||
case "${flag}" in
|
||||
k) KLOSET=${OPTARG};;
|
||||
s) SOURCE=${OPTARG};;
|
||||
r) RETENTION=${OPTARG};;
|
||||
*) usage;;
|
||||
esac
|
||||
done
|
||||
|
||||
exec 9>"$LOCKFILE"
|
||||
if ! flock -n 9; then
|
||||
echo "another plakar-incus-backup run holds $LOCKFILE, aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log() { echo "[$(date '+%F %T')] $*"; }
|
||||
|
||||
if [ -z "${PLAKAR_PASSPHRASE:-}" ]; then
|
||||
[ -r "$PASSFILE" ] || { echo "no PLAKAR_PASSPHRASE in env and $PASSFILE not readable" >&2; exit 1; }
|
||||
PLAKAR_PASSPHRASE=$(cat "$PASSFILE")
|
||||
fi
|
||||
export PLAKAR_PASSPHRASE
|
||||
|
||||
rc=0
|
||||
|
||||
log "backup @$SOURCE -> $KLOSET"
|
||||
plakar -quiet at "$KLOSET" backup -tag "$SOURCE" "@$SOURCE" \
|
||||
|| { echo "backup @$SOURCE failed" >&2; rc=1; }
|
||||
|
||||
log "prune: keep the last $RETENTION days of snapshots"
|
||||
plakar at "$KLOSET" prune -days "$RETENTION" -apply || rc=1
|
||||
plakar at "$KLOSET" maintenance || rc=1
|
||||
|
||||
log "done (rc=$rc)"
|
||||
exit $rc
|
||||
Reference in New Issue
Block a user