#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # shellcheck source=/dev/null [ -f "${SCRIPT_DIR}/env-postgresql" ] && source "${SCRIPT_DIR}/env-postgresql" [ -f "${SCRIPT_DIR}/_credentials.env" ] && source "${SCRIPT_DIR}/_credentials.env" POSTGRESQL_NAMESPACE="${POSTGRESQL_NAMESPACE:-data}" POSTGRESQL_IMAGE="${POSTGRESQL_IMAGE:-postgres:18-bookworm}" POSTGRESQL_PORT="${POSTGRESQL_PORT:-5432}" POSTGRESQL_DATA_DIR="${POSTGRESQL_DATA_DIR:-/var/lib/data/postgresql}" POSTGRESQL_STORAGE_CLASS="${POSTGRESQL_STORAGE_CLASS:-nfs-shared}" POSTGRESQL_STORAGE_SIZE="${POSTGRESQL_STORAGE_SIZE:-10Gi}" CMD_TSK="${CMD_TSK:-${1:-install}}" _resolve_kubeconfig() { if [ -n "${KUBECONFIG:-}" ] && [ -f "$KUBECONFIG" ]; then return fi for candidate in \ /var/lib/k0s/pki/admin.conf \ /etc/kubernetes/admin.conf \ /root/.kube/config; do if [ -f "$candidate" ]; then export KUBECONFIG="$candidate" return fi done if command -v k0s >/dev/null 2>&1; then local tmp tmp="$(mktemp)" k0s kubeconfig admin > "$tmp" 2>/dev/null && export KUBECONFIG="$tmp" && return rm -f "$tmp" fi echo "ERROR: kubeconfig not found — set KUBECONFIG explicitly" >&2; exit 1 } _kubectl() { if command -v kubectl >/dev/null 2>&1; then command kubectl "$@" elif command -v k0s >/dev/null 2>&1; then k0s kubectl "$@" else echo "ERROR: no kubectl or k0s binary found" >&2; exit 127 fi } _require_env() { local var="$1" if [ -z "${!var:-}" ]; then echo "ERROR: required variable $var is not set" >&2 exit 1 fi } _namespace_exists() { _kubectl get namespace "$POSTGRESQL_NAMESPACE" >/dev/null 2>&1 } _pod_name() { _kubectl get pod \ --namespace "$POSTGRESQL_NAMESPACE" \ --selector "app.kubernetes.io/name=postgresql" \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null } _wait_for_pod() { local timeout="${1:-180}" _kubectl wait pod \ --namespace "$POSTGRESQL_NAMESPACE" \ --selector "app.kubernetes.io/name=postgresql" \ --for=condition=Ready \ --timeout="${timeout}s" } _do_install() { _require_env POSTGRES_PASSWORD if ! _namespace_exists; then _kubectl create namespace "$POSTGRESQL_NAMESPACE" fi _kubectl create secret generic postgresql-credentials \ --namespace "$POSTGRESQL_NAMESPACE" \ --from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \ --dry-run=client -o yaml | _kubectl apply -f - for manifest in storageclass.yaml namespace.yaml pvc.yaml statefulset.yaml service.yaml; do local path="$SCRIPT_DIR/manifests/${manifest}" [ -f "$path" ] && _kubectl apply -f "$path" done echo "Waiting for PostgreSQL pod to be ready..." _wait_for_pod 180 local post_install="$SCRIPT_DIR/manifests/post-install.sh" if [ -f "$post_install" ]; then bash "$post_install" fi echo "PostgreSQL installed in namespace $POSTGRESQL_NAMESPACE" } _do_update() { _kubectl rollout restart statefulset/postgresql --namespace "$POSTGRESQL_NAMESPACE" echo "Waiting for PostgreSQL to restart..." _wait_for_pod 120 echo "PostgreSQL updated" } _do_delete() { if _namespace_exists; then _kubectl delete namespace "$POSTGRESQL_NAMESPACE" --timeout=60s echo "Namespace $POSTGRESQL_NAMESPACE deleted" else echo "Namespace $POSTGRESQL_NAMESPACE does not exist, nothing to delete" fi } _do_backup() { _require_env BACKUP_DIR local pod timestamp backup_file pod=$(_pod_name) timestamp=$(date +%Y%m%d_%H%M%S) backup_file="${BACKUP_DIR}/postgresql_${timestamp}.sql.gz" mkdir -p "$BACKUP_DIR" _kubectl exec --namespace "$POSTGRESQL_NAMESPACE" "$pod" -- \ pg_dumpall -U postgres | gzip > "$backup_file" echo "Backup written to $backup_file" } _do_restore() { _require_env BACKUP_FILE local pod pod=$(_pod_name) if [[ "$BACKUP_FILE" == *.gz ]]; then gunzip -c "$BACKUP_FILE" | _kubectl exec --stdin --namespace "$POSTGRESQL_NAMESPACE" "$pod" -- \ psql -U postgres else _kubectl exec --stdin --namespace "$POSTGRESQL_NAMESPACE" "$pod" -- \ psql -U postgres < "$BACKUP_FILE" fi echo "Restore completed from $BACKUP_FILE" } _do_health() { local pod pod=$(_pod_name 2>/dev/null || true) if [ -z "$pod" ]; then echo "UNHEALTHY: no postgresql pod found in namespace $POSTGRESQL_NAMESPACE" >&2 exit 1 fi if _kubectl exec --namespace "$POSTGRESQL_NAMESPACE" "$pod" -- psql -U postgres -c "SELECT 1" >/dev/null 2>&1; then echo "HEALTHY: postgresql is accepting queries" else echo "UNHEALTHY: postgresql did not respond to SELECT 1" >&2 exit 1 fi } _do_config() { local pod pod=$(_pod_name) _kubectl exec --namespace "$POSTGRESQL_NAMESPACE" "$pod" -- \ psql -U postgres -c "SELECT name, setting, unit FROM pg_settings WHERE name IN ('max_connections','shared_buffers','work_mem','effective_cache_size') ORDER BY name;" } _resolve_kubeconfig case "$CMD_TSK" in install|reinstall) _do_install ;; update) _do_update ;; delete) _do_delete ;; backup) _do_backup ;; restore) _do_restore ;; health) _do_health ;; config) _do_config ;; *) echo "ERROR: unknown CMD_TSK='$CMD_TSK'. Valid: install|reinstall|update|delete|backup|restore|health|config" >&2 exit 1 ;; esac