Files
homelab-infra/ops/borg-ui/scripts/pre-backup-dumps.sh
T

171 lines
5.1 KiB
Bash

#!/bin/sh
set -eu
# Run this on the Unraid host before Borg starts.
# It refreshes the latest database dumps in a stable directory so Borg can
# version the dump artifacts instead of raw live database files.
DUMP_ROOT="${DUMP_ROOT:-/mnt/user/appdata/borg-ui/dumps}"
LATEST_DIR="$DUMP_ROOT/latest"
TMP_DIR="$DUMP_ROOT/.tmp"
log() {
printf '%s %s\n' "[borg-dumps]" "$*"
}
warn() {
printf '%s %s\n' "[borg-dumps][warn]" "$*" >&2
}
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
warn "Required command missing: $1"
exit 1
fi
}
need_container() {
docker inspect "$1" >/dev/null 2>&1
}
ensure_dirs() {
mkdir -p "$LATEST_DIR" "$TMP_DIR"
}
atomic_write() {
target="$1"
tmp="$2"
mkdir -p "$(dirname "$target")"
mv "$tmp" "$target"
}
dump_pg_db() {
container="$1"
password="$2"
user="$3"
db="$4"
output="$5"
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Dumping PostgreSQL database '$db' from $container"
docker exec -e "PGPASSWORD=$password" "$container" \
pg_dump -U "$user" -d "$db" -Fc > "$tmp"
atomic_write "$output" "$tmp"
}
dump_pg_globals() {
container="$1"
password="$2"
user="$3"
output="$4"
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Dumping PostgreSQL globals from $container"
docker exec -e "PGPASSWORD=$password" "$container" \
pg_dumpall -U "$user" --globals-only > "$tmp"
atomic_write "$output" "$tmp"
}
dump_optional_pg_db() {
container="$1"
password="$2"
user="$3"
db="$4"
output="$5"
if docker exec -e "PGPASSWORD=$password" "$container" \
psql -U "$user" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '$db'" \
| grep -q 1; then
dump_pg_db "$container" "$password" "$user" "$db" "$output"
else
warn "Skipping missing PostgreSQL database '$db' in $container"
fi
}
dump_mysql_container() {
container="$1"
output="$2"
if ! need_container "$container"; then
warn "Skipping missing container: $container"
return 0
fi
info="$(docker exec "$container" sh -lc 'printf "%s|%s|%s" "${MARIADB_DATABASE:-${MYSQL_DATABASE:-}}" "${MARIADB_USER:-${MYSQL_USER:-root}}" "${MARIADB_PASSWORD:-${MYSQL_PASSWORD:-}}"' || true)"
db="$(printf '%s' "$info" | cut -d'|' -f1)"
user="$(printf '%s' "$info" | cut -d'|' -f2)"
password="$(printf '%s' "$info" | cut -d'|' -f3)"
if [ -z "$db" ] || [ -z "$password" ]; then
warn "Skipping MySQL/MariaDB dump for $container because DB credentials were not discoverable"
return 0
fi
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Dumping MariaDB/MySQL database '$db' from $container"
docker exec "$container" sh -lc "mysqldump --single-transaction --quick -u\"$user\" -p\"$password\" \"$db\"" > "$tmp"
atomic_write "$output" "$tmp"
}
dump_mongo_container() {
container="$1"
output="$2"
if ! need_container "$container"; then
warn "Skipping missing container: $container"
return 0
fi
if ! docker exec "$container" sh -lc 'command -v mongodump >/dev/null 2>&1'; then
warn "Skipping Mongo dump for $container because mongodump is not available in the container image"
return 0
fi
tmp="$TMP_DIR/$(basename "$output").tmp"
log "Dumping MongoDB archive from $container"
docker exec "$container" sh -lc 'mongodump --archive --gzip --username "$MONGO_INITDB_ROOT_USERNAME" --password "$(cat /run/secrets/mongo_password)" --authenticationDatabase admin' > "$tmp"
atomic_write "$output" "$tmp"
}
main() {
need_cmd docker
ensure_dirs
# Shared PostgreSQL 17
if need_container "postgresql17"; then
shared_pg_password="$(cat /mnt/user/appdata/secrets/postgres_password.txt)"
dump_pg_globals "postgresql17" "$shared_pg_password" "mailarchiver" "$LATEST_DIR/postgresql17-globals.sql"
dump_pg_db "postgresql17" "$shared_pg_password" "mailarchiver" "mailarchiver" "$LATEST_DIR/postgresql17-mailarchiver.dump"
dump_pg_db "postgresql17" "$shared_pg_password" "mailarchiver" "paperless" "$LATEST_DIR/postgresql17-paperless.dump"
dump_optional_pg_db "postgresql17" "$shared_pg_password" "mailarchiver" "semaphore" "$LATEST_DIR/postgresql17-semaphore.dump"
dump_optional_pg_db "postgresql17" "$shared_pg_password" "mailarchiver" "authelia" "$LATEST_DIR/postgresql17-authelia.dump"
else
warn "Skipping shared PostgreSQL dumps because container 'postgresql17' is missing"
fi
# Dedicated PostgreSQL databases
if need_container "mealie-postgres"; then
mealie_password="$(cat /mnt/user/appdata/secrets/mealie_postgres_password.txt)"
dump_pg_db "mealie-postgres" "$mealie_password" "mealie" "mealie" "$LATEST_DIR/mealie.dump"
else
warn "Skipping missing container: mealie-postgres"
fi
if need_container "immich_postgres"; then
immich_password="$(cat /mnt/user/appdata/secrets/immich_postgres_password.txt)"
dump_pg_db "immich_postgres" "$immich_password" "immich" "immich" "$LATEST_DIR/immich.dump"
else
warn "Skipping missing container: immich_postgres"
fi
# MariaDB / MySQL
dump_mysql_container "firefly-db" "$LATEST_DIR/firefly.sql"
# MongoDB
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
log "Finished refreshing dump set in $LATEST_DIR"
}
main "$@"