#!/usr/bin/env bash BACKUP_SUPPORTED_SERVICES="postgres ory-postgres clickhouse ory-clickhouse config" BACKUP_SUPPORTED_DATASETS="full personnel" backup_repo_root() { if [[ -n "${BACKUP_REPO_ROOT:-}" ]]; then printf '%s\n' "$BACKUP_REPO_ROOT" return fi local script_dir script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" printf '%s\n' "$script_dir" } backup_log() { printf '==> %s\n' "$*" } backup_die() { printf 'ERROR: %s\n' "$*" >&2 return 1 } backup_require_command() { local command_name="$1" command -v "$command_name" >/dev/null 2>&1 || backup_die "required command not found: $command_name" } backup_utc_now() { date -u '+%Y-%m-%dT%H:%M:%SZ' } backup_timestamp() { date -u '+%Y%m%d-%H%M%SZ' } backup_git_commit() { local repo_root="$1" git -c "safe.directory=$repo_root" -C "$repo_root" rev-parse --short=12 HEAD 2>/dev/null || printf 'unknown' } normalize_service_filter() { local raw="${1:-all}" local normalized="" local candidate if [[ "$raw" == "all" || -z "$raw" ]]; then printf '%s\n' "$BACKUP_SUPPORTED_SERVICES" return fi raw="${raw//,/ }" for candidate in $raw; do if ! grep -qw -- "$candidate" <<<"$BACKUP_SUPPORTED_SERVICES"; then backup_die "unknown backup service: $candidate" return 1 fi if ! grep -qw -- "$candidate" <<<"$normalized"; then normalized="${normalized:+$normalized }$candidate" fi done [[ -n "$normalized" ]] || backup_die "service filter must not be empty" printf '%s\n' "$normalized" } normalize_dataset_profile() { local raw="${1:-full}" [[ -n "$raw" ]] || raw="full" if ! grep -qw -- "$raw" <<<"$BACKUP_SUPPORTED_DATASETS"; then backup_die "unknown backup dataset: $raw" return 1 fi printf '%s\n' "$raw" } service_enabled() { local service="$1" local services="$2" local candidate for candidate in $services; do [[ "$candidate" == "$service" ]] && return 0 done return 1 } backup_require_path() { local path="$1" [[ -e "$path" ]] || backup_die "required path not found: $path" } backup_container_running() { local container="$1" docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null | grep -qx 'true' } backup_require_container() { local container="$1" backup_container_running "$container" || backup_die "container is not running: $container" } backup_redact_env() { local source_file="$1" local target_file="$2" sed -E '/^[[:space:]]*#/! s/^([^=]*(SECRET|PASSWORD|TOKEN|KEY|PRIVATE|CLIENT_SECRET|COOKIE)[^=]*)=.*/\1=REDACTED/I' "$source_file" >"$target_file" } backup_checksum_file() { local backup_dir="$1" ( cd "$backup_dir" find . -type f ! -name 'checksums.sha256' -print0 \ | sort -z \ | xargs -0 sha256sum ) >"$backup_dir/checksums.sha256" } backup_verify_checksums() { local backup_dir="$1" backup_require_path "$backup_dir/checksums.sha256" ( cd "$backup_dir" sha256sum -c checksums.sha256 ) } backup_safe_tar() { local source_path="$1" local target_base="$2" local source_parent local source_name [[ -e "$source_path" ]] || return 0 source_parent="$(dirname "$source_path")" source_name="$(basename "$source_path")" if command -v zstd >/dev/null 2>&1; then tar --zstd -cf "${target_base}.tar.zst" -C "$source_parent" "$source_name" else tar -czf "${target_base}.tar.gz" -C "$source_parent" "$source_name" fi }