Add borg backup scope and database dump workflow
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Borg dump scripts
|
||||
|
||||
These scripts are intended to run on the Unraid host before a Borg backup starts.
|
||||
|
||||
## Current script
|
||||
|
||||
- `pre-backup-dumps.sh`
|
||||
|
||||
## Output
|
||||
|
||||
Fresh dump artifacts are written to:
|
||||
|
||||
- `/mnt/user/appdata/borg-ui/dumps/latest`
|
||||
|
||||
Borg UI should include `/local/borg-dumps` as a backup source.
|
||||
|
||||
## Notes
|
||||
|
||||
- The script is written for host execution where `docker` is available.
|
||||
- It does not assume Backrest.
|
||||
- It keeps only the latest dump set because Borg itself provides history.
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user