mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
- Refactored installer UI with updated canvas rendering, sidebar navigation, and footer buttons. - Enhanced styling for macOS compatibility and consistent design across controls. - Added Pytest-based unit tests for `screen.py` and `config.py`. - Expanded dependency catalog with new tools like `tshark` and `pyshark`. - Improved error tolerance for background rendering and added placeholders for Kerberos configuration.
327 lines
11 KiB
Bash
Executable File
327 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
set -euo pipefail
|
||
|
||
# init_cloudnative_pg.sh
|
||
# Purpose:
|
||
# - Distribute administrator ed25519 key pair to CloudNative‑PG as a Kubernetes Secret for cert auth
|
||
# - Configure Kerberos (GSSAPI) using external Kerberos KDC
|
||
# - Patch CNPG cluster to enable TLS and GSSAPI where possible
|
||
#
|
||
# Usage:
|
||
# ./init_cloudnative_pg.sh start|stop|status|restart
|
||
# ./init_cloudnative_pg.sh initialize # create/update k8s secrets/configs and patch CNPG
|
||
# ./init_cloudnative_pg.sh update|reload # re-apply/patch
|
||
#
|
||
# Requirements:
|
||
# - init_openbao.sh has been run (OpenBao running in k8s)
|
||
# - $PROLE_HOME/env.sh or $HOME/.prole/env.sh defining PROLE_SERVICE
|
||
|
||
# Initialize SCRIPT_DIR
|
||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||
|
||
# Load env without leaking our positional args to the env script (some env.sh may `exec "$@"`).
|
||
__PROLE_SAVED_ARGS=("$@")
|
||
if [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/env.sh" ]]; then
|
||
set --
|
||
# shellcheck disable=SC1090
|
||
source "$PROLE_HOME/env.sh"
|
||
elif [[ -f "$HOME/.prole/env.sh" ]]; then
|
||
set --
|
||
# shellcheck disable=SC1090
|
||
source "$HOME/.prole/env.sh"
|
||
else
|
||
echo "ERROR: Missing env. Provide PROLE_HOME/env.sh or ~/.prole/env.sh" >&2
|
||
exit 1
|
||
fi
|
||
set -- "${__PROLE_SAVED_ARGS[@]}"
|
||
unset __PROLE_SAVED_ARGS
|
||
|
||
if [[ -z "${PROLE_SERVICE:-}" ]]; then
|
||
echo "ERROR: PROLE_SERVICE is not defined in env." >&2
|
||
exit 1
|
||
fi
|
||
|
||
ACTION=${1:-}
|
||
CNPG_CLUSTER_NAME=${2:-${CNPG_CLUSTER_NAME:-prole-db}}
|
||
NAMESPACE=${NAMESPACE:-prole}
|
||
OPENBAO_NAME=${OPENBAO_NAME:-openbao}
|
||
REALM=${REALM:-PROLE.ORG}
|
||
DOMAIN=${DOMAIN:-prole.org}
|
||
KRB5_KDC=${KRB5_KDC:-}
|
||
KRB5_ADMIN=${KRB5_ADMIN:-}
|
||
|
||
CNPG_MANIFEST="$SCRIPT_DIR/../k8s/prole/prole-db.yaml"
|
||
|
||
SECRETS_DIR="$PROLE_SERVICE/secrets"
|
||
ADMIN_PRIV="$SECRETS_DIR/admin_ed25519.key"
|
||
ADMIN_PUB="$SECRETS_DIR/admin_ed25519.pub"
|
||
OPENBAO_TOKEN_FILE="$SECRETS_DIR/openbao-root-token"
|
||
|
||
ensure_tools() {
|
||
for t in kubectl curl openssl base64 jq; do
|
||
command -v "$t" >/dev/null || { echo "Missing required tool: $t" >&2; exit 1; }
|
||
done
|
||
}
|
||
|
||
# Resolve OpenBao URL: prefer explicit env, then localhost port-forward, then cluster DNS
|
||
bao_service_url() {
|
||
if [[ -n "${PROLE_OPENBAO_URL:-}" ]]; then
|
||
echo "$PROLE_OPENBAO_URL"
|
||
return 0
|
||
fi
|
||
# Prefer standard local port-forward managed by etc/init_port_forwards.sh
|
||
if curl -sS "http://127.0.0.1:18200/v1/sys/health" >/dev/null 2>&1; then
|
||
echo "http://127.0.0.1:18200"
|
||
return 0
|
||
fi
|
||
echo "http://$OPENBAO_NAME.$NAMESPACE.svc.cluster.local:8200"
|
||
}
|
||
|
||
fetch_admin_keys_and_db_pass_from_bao_or_local() {
|
||
local token url
|
||
if [[ -f "$OPENBAO_TOKEN_FILE" ]]; then
|
||
token=$(cat "$OPENBAO_TOKEN_FILE")
|
||
else
|
||
token=""
|
||
fi
|
||
url=$(bao_service_url)
|
||
if [[ -n "$token" ]]; then
|
||
echo "Attempting to read admin key pair from OpenBao kv/prole/admin ..."
|
||
if curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/prole/admin" | jq -e '.data.data' >/dev/null 2>&1; then
|
||
local priv_b64 pub_b64
|
||
priv_b64=$(curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/prole/admin" | jq -r '.data.data.admin_private_key_b64')
|
||
pub_b64=$(curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/prole/admin" | jq -r '.data.data.admin_public_key_b64')
|
||
printf "%s" "$priv_b64" | base64 -d >"$ADMIN_PRIV"
|
||
printf "%s" "$pub_b64" | base64 -d >"$ADMIN_PUB"
|
||
chmod 0600 "$ADMIN_PRIV"
|
||
fi
|
||
|
||
echo "Attempting to read database password from OpenBao kv/prole/db ..."
|
||
if curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/prole/db" | jq -e '.data.data' >/dev/null 2>&1; then
|
||
local db_pass
|
||
db_pass=$(curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/prole/db" | jq -r '.data.data.password')
|
||
if [[ -n "$db_pass" ]]; then
|
||
echo "Updating database user secret 'prole-db-user' from OpenBao ..."
|
||
kubectl create secret generic prole-db-user -n "$NAMESPACE" \
|
||
--from-literal=username=prole \
|
||
--from-literal=password="$db_pass" \
|
||
--dry-run=client -o yaml | kubectl apply -f -
|
||
|
||
echo "Updating database superuser secret 'prole-db-superuser' from OpenBao ..."
|
||
kubectl create secret generic prole-db-superuser -n "$NAMESPACE" \
|
||
--from-literal=username=postgres \
|
||
--from-literal=password="$db_pass" \
|
||
--dry-run=client -o yaml | kubectl apply -f -
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if [[ -f "$ADMIN_PRIV" && -f "$ADMIN_PUB" ]]; then
|
||
echo "Using local admin key pair at $SECRETS_DIR"
|
||
return 0
|
||
fi
|
||
echo "ERROR: Could not obtain admin key pair from OpenBao and no local files found." >&2
|
||
exit 1
|
||
}
|
||
|
||
apply_cnpg_admin_secret() {
|
||
echo "Creating/updating Secret cnpg-admin-key ..."
|
||
kubectl create secret generic cnpg-admin-key -n "$NAMESPACE" \
|
||
--from-file=admin.key="$ADMIN_PRIV" \
|
||
--from-file=admin.pub="$ADMIN_PUB" \
|
||
--dry-run=client -o yaml | kubectl apply -f -
|
||
}
|
||
|
||
ensure_krb5_conf_configmap() {
|
||
echo "Creating/updating ConfigMap prole-krb5-conf ..."
|
||
|
||
local kdc_val admin_val
|
||
if [[ -n "$KRB5_KDC" ]]; then
|
||
kdc_val="$KRB5_KDC"
|
||
admin_val=${KRB5_ADMIN:-$(echo "$KRB5_KDC" | cut -d, -f1)}
|
||
else
|
||
kdc_val="kdc.$DOMAIN"
|
||
admin_val="kdc.$DOMAIN"
|
||
fi
|
||
|
||
local TMP
|
||
TMP=$(mktemp)
|
||
cat >"$TMP" <<EOF
|
||
[libdefaults]
|
||
default_realm = $REALM
|
||
dns_lookup_realm = true
|
||
dns_lookup_kdc = true
|
||
|
||
[realms]
|
||
$REALM = {
|
||
kdc = $kdc_val
|
||
admin_server = $admin_val
|
||
}
|
||
|
||
[domain_realm]
|
||
.$DOMAIN = $REALM
|
||
$DOMAIN = $REALM
|
||
EOF
|
||
kubectl -n "$NAMESPACE" create configmap prole-krb5-conf --from-file=krb5.conf="$TMP" --dry-run=client -o yaml | kubectl apply -f -
|
||
rm -f "$TMP"
|
||
}
|
||
|
||
generate_tls_if_missing() {
|
||
local ca_secret_name="${CNPG_CLUSTER_NAME}-ca"
|
||
if kubectl -n "$NAMESPACE" get secret "$ca_secret_name" >/dev/null 2>&1; then
|
||
echo "CA secret $ca_secret_name already exists; skipping generation."
|
||
return 0
|
||
fi
|
||
echo "Generating self-signed CA (RSA 4096) for CNPG ..."
|
||
local TMPD
|
||
TMPD=$(mktemp -d)
|
||
openssl genrsa -out "$TMPD/ca.key" 4096
|
||
openssl req -x509 -new -key "$TMPD/ca.key" -out "$TMPD/ca.crt" -days 3650 -subj "/CN=Prole CNPG CA"
|
||
kubectl -n "$NAMESPACE" create secret generic "$ca_secret_name" \
|
||
--from-file=ca.crt="$TMPD/ca.crt" \
|
||
--from-file=ca.key="$TMPD/ca.key" \
|
||
--dry-run=client -o yaml | kubectl apply -f -
|
||
rm -rf "$TMPD"
|
||
}
|
||
|
||
patch_cnpg_cluster_for_auth() {
|
||
echo "Patching CNPG Cluster $CNPG_CLUSTER_NAME to enable GSSAPI and fix config (best-effort) ..."
|
||
# Best-effort patch; schema may vary with CNPG version.
|
||
# We remove krb_srvname as it is unrecognized in some PG 17 builds.
|
||
# We revert certificates to default to avoid operator TLS issues.
|
||
kubectl -n "$NAMESPACE" patch cluster "$CNPG_CLUSTER_NAME" --type merge -p "{
|
||
\"spec\": {
|
||
\"postgresql\": {
|
||
\"parameters\": {\"krb_srvname\": null},
|
||
\"pg_hba\": [
|
||
\"local all postgres trust\",
|
||
\"host all postgres all scram-sha-256\",
|
||
\"host all all all gss include_realm=1 krb_realm=$REALM\",
|
||
\"host all all all scram-sha-256\"
|
||
]
|
||
},
|
||
\"certificates\": {
|
||
\"serverCASecret\": null,
|
||
\"clientCASecret\": null
|
||
}
|
||
}
|
||
}" >/dev/null || echo "Note: patch may need adjustment for your CNPG version."
|
||
}
|
||
|
||
initialize() {
|
||
ensure_tools
|
||
|
||
# Ensure port-forward is running for OpenBao (dependency)
|
||
echo "Ensuring port-forward for OpenBao is active ..."
|
||
"$SCRIPT_DIR/init_port_forwards.sh" restart openbao
|
||
|
||
fetch_admin_keys_and_db_pass_from_bao_or_local
|
||
apply_cnpg_admin_secret
|
||
ensure_krb5_conf_configmap
|
||
# generate_tls_if_missing
|
||
patch_cnpg_cluster_for_auth
|
||
|
||
# Ensure port-forward is running for Postgres (local access)
|
||
echo "Ensuring port-forward for Postgres is active ..."
|
||
"$SCRIPT_DIR/init_port_forwards.sh" restart postgres
|
||
|
||
echo "Initialization complete for CNPG + Kerberos + cert artifacts."
|
||
}
|
||
|
||
update_reload() {
|
||
initialize
|
||
}
|
||
|
||
case "$ACTION" in
|
||
recreate)
|
||
ensure_tools
|
||
"$0" delete "$CNPG_CLUSTER_NAME"
|
||
"$0" create "$CNPG_CLUSTER_NAME"
|
||
;;
|
||
create)
|
||
ensure_tools
|
||
echo "Installing CloudNative-PG operator ..."
|
||
kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.27/releases/cnpg-1.27.0.yaml
|
||
|
||
echo "Creating CloudNative-PG cluster and resources for '$CNPG_CLUSTER_NAME' ..."
|
||
|
||
kubectl apply -k "$SCRIPT_DIR/../k8s/prole"
|
||
|
||
# Ensure prole-index-html exists for prole deployment readiness probe
|
||
if ! kubectl get configmap prole-index-html -n prole >/dev/null 2>&1; then
|
||
echo "Creating prole-index-html configmap..."
|
||
printf "<html><body><h1>Prole</h1></body></html>" > /tmp/index.html
|
||
kubectl create configmap prole-index-html --from-file=/tmp/index.html -n prole
|
||
rm /tmp/index.html
|
||
fi
|
||
|
||
# Ensure prole-nginx-tls exists (self-signed for dev)
|
||
if ! kubectl get secret prole-nginx-tls -n prole >/dev/null 2>&1; then
|
||
echo "Generating self-signed prole-nginx-tls for development..."
|
||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||
-keyout /tmp/nginx-tls.key -out /tmp/nginx-tls.crt \
|
||
-subj "/CN=prole.org" >/dev/null 2>&1
|
||
kubectl create secret tls prole-nginx-tls --key /tmp/nginx-tls.key --cert /tmp/nginx-tls.crt -n prole
|
||
rm /tmp/nginx-tls.key /tmp/nginx-tls.crt
|
||
fi
|
||
|
||
echo "Initializing and patching cluster ..."
|
||
initialize
|
||
;;
|
||
delete)
|
||
ensure_tools
|
||
echo "Deleting all resources for '$CNPG_CLUSTER_NAME' ..."
|
||
kubectl delete -k "$SCRIPT_DIR/../k8s/prole" --ignore-not-found
|
||
;;
|
||
start)
|
||
ensure_tools
|
||
if [[ ! -f "$CNPG_MANIFEST" ]]; then
|
||
echo "ERROR: CNPG manifest not found at $CNPG_MANIFEST" >&2
|
||
exit 1
|
||
fi
|
||
echo "Starting CloudNative-PG cluster from $CNPG_MANIFEST in namespace $NAMESPACE..."
|
||
kubectl apply -n "$NAMESPACE" -f "$CNPG_MANIFEST"
|
||
;;
|
||
stop)
|
||
ensure_tools
|
||
if [[ ! -f "$CNPG_MANIFEST" ]]; then
|
||
echo "ERROR: CNPG manifest not found at $CNPG_MANIFEST" >&2
|
||
exit 1
|
||
fi
|
||
echo "Stopping CloudNative-PG cluster using $CNPG_MANIFEST ..."
|
||
kubectl delete -f "$CNPG_MANIFEST" --ignore-not-found
|
||
;;
|
||
status)
|
||
ensure_tools
|
||
echo "--- CloudNative-PG Cluster Status ($CNPG_CLUSTER_NAME) ---"
|
||
if kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME" >/dev/null 2>&1; then
|
||
kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME"
|
||
echo ""
|
||
echo "CNPG Plugin Status:"
|
||
if kubectl cnpg version >/dev/null 2>&1; then
|
||
kubectl cnpg status "$CNPG_CLUSTER_NAME" -n "$NAMESPACE"
|
||
else
|
||
echo "Note: 'kubectl cnpg' plugin not found; skipping detailed status."
|
||
fi
|
||
else
|
||
echo "Cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'."
|
||
fi
|
||
;;
|
||
restart)
|
||
ensure_tools
|
||
"$0" stop
|
||
"$0" start
|
||
;;
|
||
initialize)
|
||
initialize
|
||
;;
|
||
update|reload)
|
||
update_reload
|
||
;;
|
||
*)
|
||
echo "Usage: $0 {create|delete|recreate|start|stop|status|restart|initialize|update|reload} [dbname]" >&2
|
||
exit 2
|
||
;;
|
||
esac
|