prole/etc/init_prole-db-backup.sh
chrisfu 12d00c468a Configure Silent Install Test with unique logging and shared run configuration
- Updated tests/silent_install_test.sh to support unique logging via SILENT_INSTALL_LOG=true

- Created shared IntelliJ Run Configuration '.idea/runConfigurations/Silent_Install_Test.xml'

- Updated various init scripts, port mappings, and installer logic

- Added supabase.sh and init_monitoring.sh
2026-02-02 16:13:15 -08:00

204 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# init_prole-db-backup.sh
# Purpose:
# - Configure CloudNative-PG to backup to Garage (S3-compatible)
# - Create initial backup
# Initialize SCRIPT_DIR
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# Load environment and config via prole_cfg.sh
# shellcheck disable=SC1090
source "$SCRIPT_DIR/prole_cfg.sh"
ACTION=${1:-start}
NAMESPACE=${NAMESPACE:-default}
CNPG_CLUSTER_NAME=${CNPG_CLUSTER_NAME:-prole-db}
GARAGE_NAME=${GARAGE_NAME:-garage}
GARAGE_BACKUP_BUCKET=${GARAGE_BACKUP_BUCKET:-prole-db-backups}
GARAGE_BACKUP_KEY_NAME=${GARAGE_BACKUP_KEY_NAME:-prole-db-backup}
GARAGE_BACKUP_SECRET_NAME=${GARAGE_BACKUP_SECRET_NAME:-prole-db-barman-s3}
GARAGE_S3_ENDPOINT=${GARAGE_S3_ENDPOINT:-http://$GARAGE_NAME.$NAMESPACE.svc.cluster.local:3900}
RUN_FIRST_BACKUP=${RUN_FIRST_BACKUP:-1}
usage() {
cat <<USAGE
Usage: $0 [start|backup|status]
Actions:
start Configure Garage-backed backups and run initial backup
backup Trigger a new backup now
status Show backup resources
USAGE
exit 1
}
ensure_tools() {
for t in kubectl; do
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
done
}
ensure_namespace() {
if ! kubectl get namespace "$NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$NAMESPACE' ..."
kubectl create namespace "$NAMESPACE" >/dev/null 2>&1 || true
fi
}
ensure_cluster() {
if ! kubectl get cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "ERROR: CNPG cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'." >&2
exit 1
fi
}
get_garage_pod() {
kubectl get pods -n "$NAMESPACE" -l "app=$GARAGE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
}
garage_exec() {
local pod
pod=$(get_garage_pod)
if [[ -z "$pod" ]]; then
echo "ERROR: Garage pod not found in namespace '$NAMESPACE'." >&2
exit 1
fi
kubectl exec -n "$NAMESPACE" "$pod" -- /garage "$@"
}
parse_key_output() {
local output="$1"
local access_key secret_key
access_key=$(echo "$output" | sed -nE 's/^(Access key ID|Key ID):[[:space:]]+//p' | head -n1)
secret_key=$(echo "$output" | sed -nE 's/^(Secret access key|Secret key):[[:space:]]+//p' | head -n1)
if [[ -z "$access_key" || -z "$secret_key" ]]; then
return 1
fi
printf "%s\n%s" "$access_key" "$secret_key"
}
ensure_garage_ready() {
echo "Checking Garage readiness ..."
local i status_out
for i in {1..30}; do
if status_out=$(garage_exec status 2>/dev/null); then
# If layout is applied, DataAvail should eventually show something or at least the node should be healthy.
# A better check is 'layout show' to see if current layout version > 0
local layout_out version
layout_out=$(garage_exec layout show 2>/dev/null | grep -v "INFO" || true)
version=$(echo "$layout_out" | awk -F: '/Current cluster layout version/ {gsub(/[[:space:]]/,"",$2); print $2; exit}' || true)
if [[ -n "$version" && "$version" -gt 0 ]]; then
echo "Garage layout version $version is applied and ready."
return 0
fi
fi
echo "Waiting for Garage layout to be applied... ($i/30)"
sleep 5
done
echo "ERROR: Garage not ready (layout not applied) after 30 attempts." >&2
exit 1
}
ensure_garage_bucket_and_key() {
ensure_garage_ready
echo "Ensuring Garage bucket and access key for backups ..."
local key_info parsed access_key secret_key
if key_info=$(garage_exec key info --show-secret "$GARAGE_BACKUP_KEY_NAME" 2>/dev/null); then
:
else
key_info=$(garage_exec key create "$GARAGE_BACKUP_KEY_NAME")
fi
if ! parsed=$(parse_key_output "$key_info"); then
echo "ERROR: Unable to parse Garage key output." >&2
echo "$key_info" >&2
exit 1
fi
access_key=$(echo "$parsed" | sed -n '1p')
secret_key=$(echo "$parsed" | sed -n '2p')
if ! garage_exec bucket info "$GARAGE_BACKUP_BUCKET" >/dev/null 2>&1; then
garage_exec bucket create "$GARAGE_BACKUP_BUCKET"
fi
garage_exec bucket allow --read --write --owner --key "$GARAGE_BACKUP_KEY_NAME" "$GARAGE_BACKUP_BUCKET" || true
echo "Creating/updating Kubernetes secret '$GARAGE_BACKUP_SECRET_NAME' ..."
kubectl create secret generic "$GARAGE_BACKUP_SECRET_NAME" -n "$NAMESPACE" \
--from-literal=ACCESS_KEY_ID="$access_key" \
--from-literal=SECRET_ACCESS_KEY="$secret_key" \
--dry-run=client -o yaml | kubectl apply -f -
}
configure_cnpg_backup() {
echo "Configuring CNPG backup to use Garage bucket '$GARAGE_BACKUP_BUCKET' ..."
kubectl patch cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" --type merge -p "{
\"spec\": {
\"backup\": {
\"barmanObjectStore\": {
\"destinationPath\": \"s3://$GARAGE_BACKUP_BUCKET/\",
\"endpointURL\": \"$GARAGE_S3_ENDPOINT\",
\"s3Credentials\": {
\"accessKeyId\": {\"name\": \"$GARAGE_BACKUP_SECRET_NAME\", \"key\": \"ACCESS_KEY_ID\"},
\"secretAccessKey\": {\"name\": \"$GARAGE_BACKUP_SECRET_NAME\", \"key\": \"SECRET_ACCESS_KEY\"}
},
\"wal\": {\"compression\": \"gzip\"},
\"data\": {\"compression\": \"gzip\"}
},
\"retentionPolicy\": \"30d\"
}
}
}"
}
trigger_backup() {
local backup_name
backup_name="${CNPG_CLUSTER_NAME}-backup-$(date +%Y%m%d%H%M%S)"
echo "Triggering backup $backup_name ..."
kubectl apply -n "$NAMESPACE" -f - <<BACKUP
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: $backup_name
spec:
cluster:
name: $CNPG_CLUSTER_NAME
BACKUP
}
status() {
ensure_tools
echo "Backups for cluster '$CNPG_CLUSTER_NAME' in namespace '$NAMESPACE':"
kubectl get backup -n "$NAMESPACE" | grep "$CNPG_CLUSTER_NAME" || true
}
case "$ACTION" in
start)
ensure_tools
ensure_namespace
ensure_cluster
ensure_garage_bucket_and_key
configure_cnpg_backup
if [[ "$RUN_FIRST_BACKUP" == "1" ]]; then
trigger_backup
fi
;;
backup)
ensure_tools
ensure_namespace
ensure_cluster
trigger_backup
;;
status)
status
;;
*)
usage
;;
esac