backup/stages/pg-dump.sh

#!/usr/bin/env bash
# backup/stages/pg-dump.sh
# pg_dump each logical database running in the compose `postgres` service.
# Output is written compressed to DEST_DIR/<db>.sql.zst then symlinked
# into ../latest/pg/.

set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
. "${HERE}/../../scripts/lib/log.sh"

DEST_ROOT="${DEST_ROOT:-/srv/homelab/backup/snapshots/pg}"
LATEST="${LATEST:-/srv/homelab/backup/latest/pg}"
TS="$(date +%Y%m%dT%H%M%S)"
DEST="${DEST_ROOT}/${TS}"
CONTAINER="${CONTAINER:-homelab-postgres-1}"
PG_USER="${PG_USER:-postgres}"

install -d "${DEST}"

list_dbs() {
    docker exec -u "${PG_USER}" "${CONTAINER}" \
        psql -Atqc "SELECT datname FROM pg_database WHERE datistemplate = false AND datname <> 'postgres'"
}

dump_one() {
    local db="$1"
    local out="${DEST}/${db}.sql.zst"
    log_info "pg_dump ${db}"
    if docker exec -u "${PG_USER}" "${CONTAINER}" \
        pg_dump --format=plain --no-owner --no-privileges "${db}" \
        | zstd -q -T0 -3 -o "${out}"; then
        return 0
    fi
    log_err "pg_dump failed for ${db}"
    return 1
}

main() {
    if ! docker inspect "${CONTAINER}" >/dev/null 2>&1; then
        log_err "container ${CONTAINER} not running"
        exit 2
    fi
    local failed=0
    while IFS= read -r db; do
        [[ -z "${db}" ]] && continue
        dump_one "${db}" || failed=$((failed + 1))
    done < <(list_dbs)

    if (( failed > 0 )); then
        exit 1
    fi

    install -d "$(dirname "${LATEST}")"
    rm -f "${LATEST}"
    ln -s "${DEST}" "${LATEST}"
    log_info "latest -> ${DEST}"
}

main "$@"