mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 18:44:33 +00:00
Kong API Gateway (replacing prole nginx): - Add etc/init_kong.sh provisioning script (DB-less mode, prole-db namespace) - Add kong-deployment.yaml and kong-service.yaml manifests - Rewire ingress rules (svc/git/api.prole.org) to prole-db-kong:8000 - Update kustomization.yaml to reference kong manifests PostgREST & DB Manager in prole-db namespace: - Add etc/init_postgrest.sh and etc/init_db_manager.sh scripts - Add postgrest/db-manager deployment and service manifests - Add src/db-manager/ Node.js REST endpoint for backup triggers - Default NAMESPACE changed to prole-db in both scripts Docker image pre-load from PROLE_DATA/docker-import: - Add _preload_docker_images() to init_common_services.sh - Scan for .tar files exported by final_deployment.sh - Import via k3d image import (k3d) or ctr (k3s) before deployments - Increase rollout timeouts to 300s (configurable via ROLLOUT_TIMEOUT) in init_openbao.sh, init_opentofu.sh, init_garage_store.sh, init_registry.sh OpenTofu password resolution fix: - Add Kubernetes secret fallback in resolve_admin_password() - Change hard exit 1 to graceful return 1 with warning - Wrap call in if-guard so set -e doesn't abort the script chain Milestone fix (init scripts not running): - Add init_kong.sh, init_postgrest.sh, init_db_manager.sh to InitializationScriptsMilestone.execute() script list and arg branches - Previously only actions.py had these; milestones.py was missing them Installer integration: - Add Kong/PostgREST/DB Manager to silent installer _step_init_scripts - Add corresponding tabs and execution blocks in UI services.py
312 lines
9.1 KiB
Bash
Executable File
312 lines
9.1 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) via Barman Cloud Plugin
|
|
# - 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"
|
|
|
|
if [[ "${1:-}" == "--mode" || "${1:-}" == "-m" ]]; then
|
|
prole_set_mode "${2:-}"
|
|
shift 2
|
|
elif [[ "${1:-}" == --mode=* || "${1:-}" == -m=* ]]; then
|
|
prole_set_mode "${1#*=}"
|
|
shift
|
|
fi
|
|
|
|
ACTION=${1:-start}
|
|
|
|
NAMESPACE=${NAMESPACE:-default}
|
|
CNPG_CLUSTER_NAME=${CNPG_CLUSTER_NAME:-prole-db}
|
|
GARAGE_NAME=${GARAGE_NAME:-garage}
|
|
SERVICE_NAMESPACE=${SERVICE_NAMESPACE:-}
|
|
if [[ -z "${GARAGE_NAMESPACE:-}" ]]; then
|
|
if [[ -n "$SERVICE_NAMESPACE" ]]; then
|
|
GARAGE_NAMESPACE="$SERVICE_NAMESPACE"
|
|
else
|
|
GARAGE_NAMESPACE="$NAMESPACE"
|
|
fi
|
|
fi
|
|
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.$GARAGE_NAMESPACE.svc.cluster.local:3900}
|
|
GARAGE_S3_REGION=${GARAGE_S3_REGION:-garage}
|
|
RUN_FIRST_BACKUP=${RUN_FIRST_BACKUP:-1}
|
|
RETENTION_POLICY=${RETENTION_POLICY:-30d}
|
|
BARMAN_PLUGIN_NAME=${BARMAN_PLUGIN_NAME:-barman-cloud.cloudnative-pg.io}
|
|
BARMAN_OBJECT_NAME=${BARMAN_OBJECT_NAME:-prole-db-barman-objectstore}
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: $0 [start|backup|status]
|
|
|
|
Actions:
|
|
start Configure Garage-backed backups and run initial backup
|
|
backup [full|incr] Trigger a new backup now (default: full)
|
|
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
|
|
}
|
|
|
|
barman_crd_ready() {
|
|
kubectl get crd objectstores.barmancloud.cnpg.io >/dev/null 2>&1
|
|
}
|
|
|
|
get_garage_pod() {
|
|
kubectl get pods -n "$GARAGE_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 '$GARAGE_NAMESPACE'." >&2
|
|
exit 1
|
|
fi
|
|
kubectl exec -n "$GARAGE_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" \
|
|
--from-literal=REGION="$GARAGE_S3_REGION" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
}
|
|
|
|
apply_barman_object_store() {
|
|
if ! barman_crd_ready; then
|
|
echo "ERROR: Barman Cloud plugin CRD not found (objectstores.barmancloud.cnpg.io). Install the plugin first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Applying Barman Cloud ObjectStore '$BARMAN_OBJECT_NAME' ..."
|
|
kubectl apply -n "$NAMESPACE" -f - <<OBJECTSTORE
|
|
apiVersion: barmancloud.cnpg.io/v1
|
|
kind: ObjectStore
|
|
metadata:
|
|
name: $BARMAN_OBJECT_NAME
|
|
spec:
|
|
retentionPolicy: $RETENTION_POLICY
|
|
configuration:
|
|
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
|
|
region:
|
|
name: $GARAGE_BACKUP_SECRET_NAME
|
|
key: REGION
|
|
wal:
|
|
compression: gzip
|
|
data:
|
|
compression: gzip
|
|
OBJECTSTORE
|
|
}
|
|
|
|
ensure_barman_plugin_config() {
|
|
local plugin_names plugin_present
|
|
plugin_names=$(kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME" -o jsonpath='{.spec.plugins[*].name}' 2>/dev/null || true)
|
|
plugin_present=$(printf '%s\n' "$plugin_names" | tr ' ' '\n' | grep -F "$BARMAN_PLUGIN_NAME" || true)
|
|
|
|
if [[ -n "$plugin_present" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local plugins_json
|
|
plugins_json=$(kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME" -o jsonpath='{.spec.plugins}' 2>/dev/null || true)
|
|
|
|
echo "Configuring CNPG to use Barman Cloud plugin '$BARMAN_PLUGIN_NAME' ..."
|
|
if [[ -z "$plugins_json" || "$plugins_json" == "null" || "$plugins_json" == "[]" ]]; then
|
|
kubectl patch cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" --type merge -p "{
|
|
\"spec\": {
|
|
\"plugins\": [
|
|
{
|
|
\"name\": \"$BARMAN_PLUGIN_NAME\",
|
|
\"isWALArchiver\": true,
|
|
\"parameters\": {\"barmanObjectName\": \"$BARMAN_OBJECT_NAME\"}
|
|
}
|
|
]
|
|
}
|
|
}"
|
|
else
|
|
kubectl patch cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" --type json -p "[
|
|
{
|
|
\"op\": \"add\",
|
|
\"path\": \"/spec/plugins/-\",
|
|
\"value\": {
|
|
\"name\": \"$BARMAN_PLUGIN_NAME\",
|
|
\"isWALArchiver\": true,
|
|
\"parameters\": {\"barmanObjectName\": \"$BARMAN_OBJECT_NAME\"}
|
|
}
|
|
}
|
|
]"
|
|
fi
|
|
}
|
|
|
|
remove_native_barman_config() {
|
|
kubectl patch cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" --type merge -p "{\"spec\":{\"backup\":null}}" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
trigger_backup() {
|
|
local backup_type="${1:-full}"
|
|
local backup_name
|
|
backup_name="${CNPG_CLUSTER_NAME}-backup-$(date +%Y%m%d%H%M%S)"
|
|
echo "Triggering ${backup_type} backup $backup_name ..."
|
|
if [[ "$backup_type" == "incremental" || "$backup_type" == "incr" ]]; then
|
|
kubectl apply -n "$NAMESPACE" -f - <<BACKUP
|
|
apiVersion: postgresql.cnpg.io/v1
|
|
kind: Backup
|
|
metadata:
|
|
name: $backup_name
|
|
spec:
|
|
method: plugin
|
|
pluginConfiguration:
|
|
name: $BARMAN_PLUGIN_NAME
|
|
parameters:
|
|
barmanObjectName: $BARMAN_OBJECT_NAME
|
|
backupType: incremental
|
|
cluster:
|
|
name: $CNPG_CLUSTER_NAME
|
|
BACKUP
|
|
else
|
|
kubectl apply -n "$NAMESPACE" -f - <<BACKUP
|
|
apiVersion: postgresql.cnpg.io/v1
|
|
kind: Backup
|
|
metadata:
|
|
name: $backup_name
|
|
spec:
|
|
method: plugin
|
|
pluginConfiguration:
|
|
name: $BARMAN_PLUGIN_NAME
|
|
parameters:
|
|
barmanObjectName: $BARMAN_OBJECT_NAME
|
|
cluster:
|
|
name: $CNPG_CLUSTER_NAME
|
|
BACKUP
|
|
fi
|
|
}
|
|
|
|
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
|
|
apply_barman_object_store
|
|
ensure_barman_plugin_config
|
|
remove_native_barman_config
|
|
if [[ "$RUN_FIRST_BACKUP" == "1" ]]; then
|
|
trigger_backup
|
|
fi
|
|
;;
|
|
backup)
|
|
ensure_tools
|
|
ensure_namespace
|
|
ensure_cluster
|
|
trigger_backup "${2:-full}"
|
|
;;
|
|
status)
|
|
status
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|