Refactor project structure and update initialization scripts

- Moved files from 'prole/' subdirectory to root level or appropriate subdirectories (tests, authority, infrastructure) to flatten the project structure.

- Updated 'install.py' and initialization scripts in 'etc/' to reflect the new directory layout.

- Added 'etc/repair_pipeline.sh' for automated pipeline repairs.

- Updated configuration files including 'conf/prole.cfg' and 'env.sh'.

- Integrated ArgoCD manifests in 'k8s/argocd/'.

- Updated 'prole-app' environment and properties.

- Moved and updated test scripts for better organization and reliability.

- Added 'tests/silent_install_test.sh' for automated installation testing.
This commit is contained in:
chrisfu 2026-02-14 13:44:49 -08:00
parent f2c9012cce
commit d2efb835b0
49 changed files with 34738 additions and 372 deletions

1
.gitignore vendored
View File

@ -43,7 +43,6 @@
# Secrets and local config
*.kubeconfig
etc/secrets/
prole/conf/
*-password.txt
*secret.yaml
.vault_pass

13
conf/port-mapping.cfg Normal file
View File

@ -0,0 +1,13 @@
# Port mappings for Prole Tools (read by scripts).
# Format examples:
# grafana: local=3000 remote=80 ns=default svc=prometheus-community-grafana address=0.0.0.0
# db: local=5432 remote=5432 ns=default svc=prole-db-rw address=0.0.0.0
# Add your mappings below. One mapping per line as key=value tokens.
argocd: local=8081 remote=80 ns=default svc=argocd-server address=0.0.0.0
prometheus: local=9090 remote=9090 ns=default svc=prometheus-community-kube-prometheus address=0.0.0.0
dashboard: local=8443 remote=443 ns=kubernetes-dashboard svc=kubernetes-dashboard-kong-proxy address=0.0.0.0
openbao: local=8200 remote=8200 ns=default svc=openbao address=0.0.0.0
opentofu: local=8080 remote=8080 ns=default svc=opentofu address=0.0.0.0
db: local=5432 remote=5432 ns=prole-db-a0e0d0d0 svc=prole-db-rw address=0.0.0.0
grafana: local=3000 remote=80 ns=default svc=prometheus-community-grafana address=0.0.0.0

View File

@ -1,13 +0,0 @@
# Port mappings for Prole Tools (read by scripts).
# Format examples:
# grafana: local=3000 remote=80 ns=default svc=prometheus-community-grafana address=0.0.0.0
# db: local=5432 remote=5432 ns=default svc=prole-db-rw address=0.0.0.0
# Add your mappings below. One mapping per line as key=value tokens.
registry: local=5000 remote=80 ns=default svc=registry address=0.0.0.0
dashboard: local=8000 remote=80 ns=kubernetes-dashboard svc=kubernetes-dashboard-web address=0.0.0.0
openbao: local=8200 remote=80 ns=default svc=openbao address=0.0.0.0
garage: local=8200 remote=80 ns=default svc=garage address=0.0.0.0
opentofu: local=8080 remote=80 ns=default svc=opentofu address=0.0.0.0
db: local=5432 remote=5432 ns=prole-db-a0e0d0d0 svc=prole-db-rw address=0.0.0.0
grafana: local=3000 remote=80 ns=prole-db-a0e0d0d0 svc=prometheus-community-grafana address=0.0.0.0

View File

@ -1,10 +1,10 @@
; Prole Master Configuration File
; Generated by install.py on 2026-02-13 20:39:34
; Generated by install.py on 2026-02-14 11:50:44
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
; User-editable values; derived values below reference these by default.
NAMESPACE = prole-db-a0e0d0d0
NAMESPACE = prole-db-101
PROLE_CONF = ${PROLE_HOME}/conf
PROLE_DATA = /opt/prole/data/chrisfu
PROLE_HOME = /Users/chrisfu/dev/prole
@ -36,26 +36,26 @@ init_cluster.at_rest_encryption_enabled = true
init_cluster.cluster_env = k3d-prole-dev-cluster
init_cluster.deployment_target = prole-dev-cluster
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = ${OPENBAO:REDACTED}
init_cluster.k3s_token =
init_cluster.kerberos_enabled = true
init_cluster.mode = k3d
init_cluster.start_cluster = true
init_cluster.supabase_enabled = true
init_cluster.supabase_enabled = false
init_cnpg_deploy.force_rollout = false
init_cnpg_deploy.run_deploy = true
init_db_build.run_build = true
init_password.db_host_port = 5432
init_password.db_password = ${OPENBAO:kv/prole/prole-db-a0e0d0d0/db#password}
init_password.db_password_confirm = ${OPENBAO:kv/prole/prole-db-a0e0d0d0/db#password}
init_password.db_username = chrisfu
init_password.db_password = ${OPENBAO:kv/prole/prole-db-101/db#password}
init_password.db_password_confirm = ${OPENBAO:kv/prole/prole-db-101/db#password}
init_password.db_username = root
init_password.generate_ssh_key = true
init_scripts.run_scripts = true
kerberos_config.enabled = true
kerberos_config.kdc = 10.0.0.3
kerberos_config.password = ${OPENBAO:kv/prole/prole-db-a0e0d0d0/kerberos#password}
kerberos_config.password = ${OPENBAO:kv/prole/prole-db-101/kerberos#password}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = true
kerberos_config.user = chrisfu
kerberos_config.test_connection = false
kerberos_config.user =
network_scan.run = true
ollama_config.model =
ollama_config.server_host =
@ -65,12 +65,12 @@ ollama_config.server_port = 11434
; Variables used by name in more than one place or assumed global scope
CLUSTER_ENV = k3d-prole-dev-cluster
DB_HOST_PORT = 5432
DB_PASSWORD = ${OPENBAO:kv/prole/prole-db-a0e0d0d0/db#password}
DB_PASSWORD = ${OPENBAO:kv/prole/prole-db-101/db#password}
DEPLOYMENT_MODE = k3d
DEPLOYMENT_TARGET = prole-dev-cluster
DOCKER_IMPORT_DIR =
NAMESPACE = ${NAMESPACE}
PROLE_DB_USER = chrisfu
PROLE_DB_USER = root
PROLE_HOME = ${PROLE_HOME}
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = ${OPENBAO:REDACTED}
@ -81,7 +81,7 @@ SERVICE_NAMESPACE = ${SERVICE_NAMESPACE}
; No configuration values captured yet for this section.
[Dependencies]
; No configuration values captured yet for this section.
STATUS = All installed
[Network]
AD_DC_HOST = myrddin.prole.org
@ -96,17 +96,25 @@ KDC_AUTO_DETECTED = 10.0.0.3
KERBEROS_AUTO_ENABLED = True
[Port Forwards]
; No configuration values captured yet for this section.
PORT_FORWARD_K3D_MAPPING_1 = id=registry;namespace=default;target=svc/registry;address=0.0.0.0;hostPort=5000;servicePort=5000;protocol=TCP;description=Docker registry
PORT_FORWARD_K3D_MAPPING_2 = id=garage;namespace=default;target=svc/garage;address=0.0.0.0;hostPort=3900;servicePort=3900;protocol=TCP;description=Garage S3
PORT_FORWARD_K3D_MAPPING_3 = id=openbao;namespace=default;target=svc/openbao;address=127.0.0.1;hostPort=8200;servicePort=8200;protocol=TCP;description=OpenBao
PORT_FORWARD_K3D_MAPPING_4 = id=opentofu;namespace=default;target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
PORT_FORWARD_K3D_MAPPING_5 = id=dashboard;namespace=kubernetes-dashboard;target=svc/kubernetes-dashboard-kong-proxy;address=127.0.0.1;hostPort=8443;servicePort=443;protocol=TCP;description=Kubernetes Dashboard
PORT_FORWARD_K3D_MAPPING_6 = id=postgres;namespace=${NAMESPACE};target=svc/prole-db-rw;address=0.0.0.0;hostPort=5432;servicePort=5432;protocol=TCP;description=PostgreSQL (primary)
PORT_FORWARD_K3D_MAPPING_7 = id=grafana;namespace=${NAMESPACE};target=svc/grafana;address=0.0.0.0;hostPort=3000;servicePort=3000;protocol=TCP;description=Grafana UI
PORT_FORWARD_K3D_MAPPING_8 = id=argocd;namespace=default;target=svc/argocd-server;address=0.0.0.0;hostPort=8081;servicePort=80;protocol=TCP;description=ArgoCD
[System Environment]
PROLE_CONF = ${PROLE_CONF}
PROLE_DATA = ${PROLE_DATA}
PROLE_HOME = ${PROLE_HOME}
PROLE_LOGS = ${PROLE_LOGS}
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
PROLE_SERVICE = ${PROLE_SERVICE}
[Monitoring]
GRAFANA_ADMIN_PASSWORD = ${OPENBAO:kv/prole/prole-db-a0e0d0d0/monitoring#grafana_admin_password}
GRAFANA_ADMIN_PASSWORD = ${OPENBAO:kv/prole/prole-db-101/monitoring#grafana_admin_password}
[Kerberos Authentication]
AD_PORT_FORWARD = 1
@ -115,27 +123,27 @@ AD_PROXY_IMAGE = alpine/socat
AD_PROXY_SERVICE = prole-kerberos-ad-dc
AD_TCP_PORTS = 88 389 445 464 636
AD_UDP_PORTS = 88 464
ENABLED = True
ENABLED = true
KDC = 10.0.0.3
PASSWORD = ${OPENBAO:kv/prole/prole-db-a0e0d0d0/kerberos#password}
PASSWORD = ${OPENBAO:kv/prole/prole-db-101/kerberos#password}
REALM = PROLE.ORG
SERVER = 10.0.0.3
USER = chrisfu
USER =
[Ollama]
; No configuration values captured yet for this section.
[Optional Features]
AT_REST_ENCRYPTION_ENABLED = True
KERBEROS_ENABLED = True
SUPABASE_ENABLED = False
AT_REST_ENCRYPTION_ENABLED = true
KERBEROS_ENABLED = true
SUPABASE_ENABLED = false
[Database Creation]
[Initialize Cluster]
ENVIRONMENT = k3d-prole-dev-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${OPENBAO:REDACTED}
K3S_TOKEN =
[Dev Cluster (k3d)]
CLUSTER_ENV = k3d-prole-dev-cluster
@ -147,12 +155,12 @@ MODE = k3d
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${OPENBAO:REDACTED}
K3S_TOKEN = ${OPENBAO:kv/prole/prole-db-101/k3s#token}
MODE = k3s
PIPELINE_URL = http://127.0.0.1:8080
[Prod Cluster (k8s)]
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
ARTIFACTS_DIR = ${PROLE_DATA}/staging
CLUSTER_ENV = prole-prod-cluster
DISPLAY_NAME = prole-prod-cluster
MODE = k8s
@ -161,9 +169,10 @@ PIPELINE_URL = http://127.0.0.1:8080
[Docker Build]
LOCAL_REGISTRY = localhost:5000
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000
STATUS = Built
[Initialization Scripts]
STATUS = Attempted
; No configuration values captured yet for this section.
[Deployment]
MODE = k3d

2
env.sh
View File

@ -8,7 +8,7 @@ export PROLE_CONF="/Users/chrisfu/dev/prole/conf"
export PROLE_DATA="/opt/prole/data/chrisfu"
export PROLE_LOGS="/opt/prole/logs/chrisfu"
export PROLE_SERVICE="/Users/chrisfu/dev/prole/etc"
export NAMESPACE="prole-db-a0e0d0d0"
export NAMESPACE="prole-db-101"
# Ensure PATH works for GUI-launched shells (Docker, etc.)
_prole_add_path() { case ":${PATH}:" in *":$1:"*) ;; *) PATH="$1:${PATH:-}" ;; esac; }

View File

@ -1,25 +1,79 @@
#!/bin/bash
# usage: etc/final_deployment.sh [-e|--docker-export]
# usage: etc/final_deployment.sh [-e|--docker-export] [-c|--helm-chart] [-k|--kustomize] [-o|--output-dir DIR]
#
# This script handles post-installation deployment tasks for Prole.
# 1st use case: -e|--docker-export
# Runs docker image export for each image required to deploy Prole.
# Artifacts are written to DOCKER_IMPORT_DIR.
# 2nd use case: -c|--helm-chart
# Creates a Helm chart from the current k8s manifests.
# Artifacts are written to HELM_CHART_DIR.
# 3rd use case: -k|--kustomize
# Creates kustomize deployments (one per component).
# Artifacts are written to KUSTOMIZE_DIR.
usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -e, --docker-export Export required docker images to DOCKER_IMPORT_DIR"
echo " -c, --helm-chart Create Helm chart from k8s manifests"
echo " -k, --kustomize Create kustomize deployments from k8s manifests"
echo " -o, --output-dir DIR Base directory for docker-import/, helm-chart/, and kustomize/"
echo ""
}
do_docker_export=0
do_helm_chart=0
do_kustomize=0
export_dir=""
if [[ $# -eq 0 ]]; then
usage
exit 1
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-e|--docker-export)
do_docker_export=1
shift
;;
-c|--helm-chart)
do_helm_chart=1
shift
;;
-k|--kustomize)
do_kustomize=1
shift
;;
-o|--output-dir)
if [[ -z "${2:-}" ]]; then
echo "Missing value for $1"
usage
exit 1
fi
export_dir="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
if [[ $do_docker_export -eq 0 && $do_helm_chart -eq 0 && $do_kustomize -eq 0 ]]; then
usage
exit 1
fi
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export PROJECT_ROOT
@ -31,7 +85,17 @@ fi
default_owner="$(id -un 2>/dev/null || echo prole)"
prole_data="${PROLE_DATA:-/opt/prole/data/${default_owner}}"
prole_data="${prole_data%/}"
if [[ -n "$export_dir" ]]; then
export_dir="${export_dir%/}"
DOCKER_IMPORT_DIR="${export_dir}/docker-import"
HELM_CHART_DIR="${export_dir}/helm-chart"
KUSTOMIZE_DIR="${export_dir}/kustomize"
fi
DOCKER_IMPORT_DIR="${DOCKER_IMPORT_DIR:-${prole_data}/docker-import}"
HELM_CHART_DIR="${HELM_CHART_DIR:-${prole_data}/helm-chart}"
KUSTOMIZE_DIR="${KUSTOMIZE_DIR:-${prole_data}/kustomize}"
export_docker_images() {
echo "Exporting Docker images to: ${DOCKER_IMPORT_DIR}"
@ -87,12 +151,117 @@ export_docker_images() {
echo "Docker export complete."
}
case "$1" in
-e|--docker-export)
export_docker_images
;;
*)
usage
exit 1
;;
esac
create_helm_chart() {
local chart_dir="${HELM_CHART_DIR}"
local chart_name="prole"
local chart_version="0.1.0"
local app_version
app_version="$(date -u +%Y.%m.%d)"
echo "Creating Helm chart in: ${chart_dir}"
mkdir -p "${chart_dir}/templates"
# Clean managed template subdirs to avoid stale manifests.
rm -rf "${chart_dir}/templates/prole" "${chart_dir}/templates/openbao" "${chart_dir}/templates/opentofu"
cat > "${chart_dir}/Chart.yaml" <<EOF
apiVersion: v2
name: ${chart_name}
description: Prole deployment manifests
type: application
version: ${chart_version}
appVersion: "${app_version}"
EOF
cat > "${chart_dir}/values.yaml" <<'EOF'
# Values are unused by default. Manifests are installed as-is.
EOF
copy_manifests() {
local src_dir="$1"
local dest_subdir="$2"
if [[ ! -d "$src_dir" ]]; then
echo "Skipping missing manifest dir: $src_dir"
return
fi
local dest_dir="${chart_dir}/templates/${dest_subdir}"
mkdir -p "${dest_dir}"
local found=0
while IFS= read -r -d '' file; do
if [[ "$(basename "$file")" == "kustomization.yaml" ]]; then
continue
fi
cp "$file" "${dest_dir}/"
found=1
done < <(find "$src_dir" -maxdepth 1 -type f -name "*.yaml" -print0)
if [[ $found -eq 0 ]]; then
echo "No manifests found in $src_dir"
fi
}
copy_manifests "${PROJECT_ROOT}/k8s/prole" "prole"
copy_manifests "${PROJECT_ROOT}/k8s/openbao" "openbao"
copy_manifests "${PROJECT_ROOT}/k8s/opentofu" "opentofu"
echo "Helm chart creation complete."
}
create_kustomize_deployments() {
local base_dir="${KUSTOMIZE_DIR}"
echo "Creating kustomize deployments in: ${base_dir}"
mkdir -p "${base_dir}"
create_kustomize_package() {
local name="$1"
local src_dir="$2"
if [[ ! -d "$src_dir" ]]; then
echo "Skipping missing manifest dir: $src_dir"
return
fi
local dest_dir="${base_dir}/${name}"
mkdir -p "${dest_dir}"
# Copy manifests except kustomization.yaml
local files=()
while IFS= read -r -d '' file; do
if [[ "$(basename "$file")" == "kustomization.yaml" ]]; then
continue
fi
cp "$file" "${dest_dir}/"
files+=("$(basename "$file")")
done < <(find "$src_dir" -maxdepth 1 -type f -name "*.yaml" -print0)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No manifests found in $src_dir"
return
fi
{
echo "apiVersion: kustomize.config.k8s.io/v1beta1"
echo "kind: Kustomization"
echo "resources:"
for f in "${files[@]}"; do
echo " - ${f}"
done
} > "${dest_dir}/kustomization.yaml"
}
create_kustomize_package "prole" "${PROJECT_ROOT}/k8s/prole"
create_kustomize_package "openbao" "${PROJECT_ROOT}/k8s/openbao"
create_kustomize_package "opentofu" "${PROJECT_ROOT}/k8s/opentofu"
echo "Kustomize deployments creation complete."
}
if [[ $do_docker_export -eq 1 ]]; then
export_docker_images
fi
if [[ $do_helm_chart -eq 1 ]]; then
create_helm_chart
fi
if [[ $do_kustomize -eq 1 ]]; then
create_kustomize_deployments
fi

View File

@ -9,7 +9,7 @@ usage() {
cat <<EOF
Usage: init_common_services.sh [-n|--namespace NS] [-k|--kerberos] <update|start|status>
Deploys common infrastructure services (OpenBao, OpenTofu, registry if available)
Deploys common infrastructure services (ArgoCD, OpenBao, OpenTofu, Garage)
into the given Kubernetes namespace. Use -k to include the Kerberos/KDC service.
EOF
}
@ -82,7 +82,7 @@ fi
OPENBAO_NAME=${OPENBAO_NAME:-openbao}
OPENTOFU_NAME=${OPENTOFU_NAME:-opentofu}
REGISTRY_NAME=${REGISTRY_NAME:-registry}
ARGOCD_SERVER_NAME=${ARGOCD_SERVER_NAME:-argocd-server}
GARAGE_NAME=${GARAGE_NAME:-garage}
OPENTOFU_CONFIGMAP=${OPENTOFU_CONFIGMAP:-opentofu-nginx}
OPENTOFU_SECRET=${OPENTOFU_SECRET:-opentofu-admin}
@ -132,13 +132,13 @@ migrate_common_services() {
kubectl delete -n "$old_ns" secret "$OPENTOFU_SECRET" --ignore-not-found >/dev/null 2>&1 || true
done
for old_ns in $(collect_other_namespaces "$REGISTRY_NAME" deployment service); do
echo "Found registry in namespace '$old_ns'; removing before deploy to '$NS' ..."
for old_ns in $(collect_other_namespaces "$ARGOCD_SERVER_NAME" deployment service); do
echo "Found ArgoCD in namespace '$old_ns'; removing before deploy to '$NS' ..."
if [ -x "$SCRIPT_DIR/init_registry.sh" ]; then
"$SCRIPT_DIR/init_registry.sh" -n "$old_ns" stop || true
else
kubectl delete -n "$old_ns" deploy "$REGISTRY_NAME" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete -n "$old_ns" svc "$REGISTRY_NAME" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete -n "$old_ns" deploy "$ARGOCD_SERVER_NAME" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete -n "$old_ns" svc "$ARGOCD_SERVER_NAME" --ignore-not-found >/dev/null 2>&1 || true
fi
done
@ -176,7 +176,7 @@ fi
if [ -x "$SCRIPT_DIR/init_registry.sh" ]; then
"$SCRIPT_DIR/init_registry.sh" -n "$NS" "$ACTION" || rc=$?
else
echo "WARN: init_registry.sh not found; registry deploy skipped."
echo "WARN: init_registry.sh not found; ArgoCD deploy skipped."
fi
if [ -x "$SCRIPT_DIR/init_garage_store.sh" ]; then

View File

@ -281,10 +281,18 @@ resolve_kdc_docker_dir() {
echo "$PROLE_HOME/prole/authority"
return 0
fi
if [[ -n "${PROLE_HOME:-}" && -d "$PROLE_HOME/authority" ]]; then
echo "$PROLE_HOME/authority"
return 0
fi
if [[ -d "$SCRIPT_DIR/../prole/authority" ]]; then
echo "$SCRIPT_DIR/../prole/authority"
return 0
fi
if [[ -d "$SCRIPT_DIR/../authority" ]]; then
echo "$SCRIPT_DIR/../authority"
return 0
fi
return 1
}

View File

@ -205,16 +205,18 @@ scan_pairs() {
if ! json=$(curl -fsS --connect-timeout "$timeout" --max-time "$timeout" "$url" 2>/dev/null); then
continue
fi
models=$(printf '%s' "$json" | python3 - <<'PY'
if ! models=$(printf '%s' "$json" | python3 - <<'PY'
import json
import sys
data = sys.stdin.read().strip()
if not data:
sys.exit(0)
sys.exit(1)
try:
payload = json.loads(data)
except Exception:
sys.exit(0)
sys.exit(1)
if not isinstance(payload, dict) or "models" not in payload:
sys.exit(1)
models = []
for entry in payload.get("models", []):
name = entry.get("name") or entry.get("model") or ""
@ -222,7 +224,9 @@ for entry in payload.get("models", []):
models.append(name)
print(",".join(models))
PY
)
); then
continue
fi
printf '%s\t%s\t%s\n' "$host" "$port_val" "$models"
count=$((count + 1))
done

View File

@ -42,6 +42,8 @@ Examples:
Config (prole.cfg or port-mapping.cfg):
PORT_FORWARD_K3D_MAPPING_1 = id=dashboard;namespace=kubernetes-dashboard;target=svc/kubernetes-dashboard-kong-proxy;address=127.0.0.1;hostPort=8443;servicePort=443;protocol=TCP;description=Kubernetes Dashboard
PORT_FORWARD_K3S_MAPPING_1 = id=opentofu;namespace=\${NAMESPACE};target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
Legacy port-mapping.cfg:
dashboard: local=8000 remote=80 ns=kubernetes-dashboard svc=kubernetes-dashboard-web address=0.0.0.0
EOF
}
@ -190,11 +192,33 @@ collect_cfg_mappings() {
if (key ~ ("^" p "[0-9]+$")) {
num=substr(key, length(p)+1)+0
print num "\t" val
count++
}
}
END { if (count == 0) exit 1 }
' "$file" | sort -n
}
collect_cfg_mappings_for_mode() {
local file="$1"
local prefix
prefix="$(mapping_prefix_for_mode)"
if collect_cfg_mappings "$prefix" "$file"; then
return 0
fi
# Fallback to generic mappings if mode-specific ones are absent
if collect_cfg_mappings "PORT_FORWARD_MAPPING_" "$file"; then
return 0
fi
return 1
}
has_ini_mappings() {
local file="$1"
[ -f "$file" ] || return 1
grep -Eq '^[[:space:]]*PORT_FORWARD_.*_MAPPING_[0-9]+' "$file"
}
parse_cfg_mapping() {
local line="$1"
local id="" ns="" target="" address="" hostPort="" servicePort="" protocol="" description=""
@ -496,6 +520,28 @@ docker_is_running() {
docker info >/dev/null 2>&1
}
docker_port_owner() {
local port="$1"
have docker || return 1
docker ps --format '{{.ID}}\t{{.Names}}\t{{.Ports}}' 2>/dev/null | \
awk -v p=":"$port"->" 'index($0, p) {print $1 "\t" $2; exit 0}'
}
should_skip_mapping() {
local id="$1" hostPort="$2"
case "$id" in
registry|openbao)
local owner
owner="$(docker_port_owner "$hostPort")"
if [ -n "$owner" ]; then
log "Skipping $id port-forward; docker is already publishing port $hostPort ($owner)."
return 0
fi
;;
esac
return 1
}
# Hard-fail with clean guidance when Docker is not up (used for start/restart).
ensure_docker_running() {
if docker_is_running; then
@ -579,6 +625,19 @@ is_pid_running() {
[ -n "$pid" ] && kill -0 "$pid" >/dev/null 2>&1
}
is_matching_port_forward() {
local pid="$1" target="$2" hostPort="$3" servicePort="$4"
local cmd
cmd="$(ps -p "$pid" -o command= 2>/dev/null || true)"
if [ -z "$cmd" ]; then
return 1
fi
case "$cmd" in
*port-forward*"$target"*"$hostPort:$servicePort"*) return 0 ;;
esac
return 1
}
read_pid() {
local pf="$1"
[ -f "$pf" ] || return 1
@ -597,48 +656,111 @@ remove_pidfile() {
rm -f "$pf" >/dev/null 2>&1 || true
}
handle_mapping_parsed() {
local callback="$1"
local parsed="$2"
local id ns target address hostPort servicePort protocol description
IFS=$'\t' read -r id ns target address hostPort servicePort protocol description <<<"$parsed"
if [ -z "$id" ] || [ -z "$ns" ] || [ -z "$target" ] || [ -z "$hostPort" ] || [ -z "$servicePort" ]; then
err "Invalid mapping (missing required fields): $parsed"
exit 1
fi
if [ -n "$TARGET_ID" ] && [ "$id" != "$TARGET_ID" ]; then
return 0
fi
vlog "mapping: id=$id ns=$ns target=$target address=$address hostPort=$hostPort servicePort=$servicePort protocol=$protocol"
"$callback" "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
return 0
}
foreach_mapping_cfg() {
local callback="$1"
local file="${2:-$PORT_MAPPING_FILE_PATH}"
local prefix mappings line parsed
local mappings line parsed count=0
[ -n "$file" ] || return 1
prefix="$(mapping_prefix_for_mode)"
if ! mappings="$(collect_cfg_mappings "$prefix" "$file")"; then
if ! mappings="$(collect_cfg_mappings_for_mode "$file")"; then
return 1
fi
while IFS= read -r line; do
[ -z "$line" ] && continue
parsed="$(parse_cfg_mapping "${line#*$'\t'}")"
local id ns target address hostPort servicePort protocol description
IFS=$'\t' read -r id ns target address hostPort servicePort protocol description <<<"$parsed"
if [ -z "$id" ] || [ -z "$ns" ] || [ -z "$target" ] || [ -z "$hostPort" ] || [ -z "$servicePort" ]; then
err "Invalid mapping (missing required fields): ${line#*$'\t'}"
exit 1
if handle_mapping_parsed "$callback" "$parsed"; then
count=$((count + 1))
fi
done <<<"$mappings"
[ "$count" -gt 0 ] || return 1
return 0
}
if [ -n "$TARGET_ID" ] && [ "$id" != "$TARGET_ID" ]; then
foreach_mapping_legacy() {
local callback="$1"
local file="${2:-$PORT_MAPPING_FILE_PATH}"
local line id rest token key value
local hostPort servicePort ns target address protocol description
local count=0
[ -f "$file" ] || return 1
while IFS= read -r line; do
line="$(trim "$line")"
[ -z "$line" ] && continue
case "$line" in \#*|\;*) continue ;; esac
id="${line%%:*}"
rest="${line#*:}"
if [ "$id" = "$line" ]; then
continue
fi
id="$(trim "$id")"
rest="$(trim "$rest")"
hostPort="" servicePort="" ns="" target="" address="" protocol="" description=""
for token in $rest; do
key="$(trim "${token%%=*}")"
value="$(trim "${token#*=}")"
case "$key" in
local|host|hostPort|host_port) hostPort="$value" ;;
remote|servicePort|service_port) servicePort="$value" ;;
ns|namespace) ns="$value" ;;
svc|service) target="svc/$value" ;;
target) target="$value" ;;
address|addr) address="$value" ;;
protocol|proto) protocol="$value" ;;
desc|description) description="$value" ;;
esac
done
vlog "mapping: id=$id ns=$ns target=$target address=$address hostPort=$hostPort servicePort=$servicePort protocol=$protocol"
"$callback" "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
done <<<"$mappings"
if [ -z "$address" ]; then address="127.0.0.1"; fi
if [ -z "$protocol" ]; then protocol="TCP"; fi
line="id=$id;namespace=$ns;target=$target;address=$address;hostPort=$hostPort;servicePort=$servicePort;protocol=$protocol;description=$description"
parsed="$(parse_cfg_mapping "$line")"
if handle_mapping_parsed "$callback" "$parsed"; then
count=$((count + 1))
fi
done <"$file"
[ "$count" -gt 0 ] || return 1
return 0
}
foreach_mapping() {
local callback="$1"
if foreach_mapping_cfg "$callback" "$CFG_FILE"; then
return 0
fi
if [ -n "${PORT_MAPPING_FILE_PATH:-}" ] && [ -f "$PORT_MAPPING_FILE_PATH" ]; then
if foreach_mapping_cfg "$callback" "$PORT_MAPPING_FILE_PATH"; then
if [ -n "${CFG_FILE:-}" ] && [ -f "$CFG_FILE" ]; then
if foreach_mapping_cfg "$callback" "$CFG_FILE"; then
return 0
fi
fi
err "No port-forward mappings found in ${CFG_FILE:-prole.cfg}."
exit 1
if [ -n "${PORT_MAPPING_FILE_PATH:-}" ] && [ -f "$PORT_MAPPING_FILE_PATH" ]; then
if has_ini_mappings "$PORT_MAPPING_FILE_PATH"; then
if foreach_mapping_cfg "$callback" "$PORT_MAPPING_FILE_PATH"; then
return 0
fi
else
if foreach_mapping_legacy "$callback" "$PORT_MAPPING_FILE_PATH"; then
return 0
fi
fi
fi
return 1
}
build_port_forward_cmd() {
@ -653,6 +775,11 @@ start_port_forward() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
local pidfile logfile cmd pid
if should_skip_mapping "$id" "$hostPort"; then
stop_port_forward "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
return 0
fi
# Aggressively stop existing processes before starting to avoid port conflicts
stop_port_forward "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
@ -749,6 +876,10 @@ status_one() {
scan_for_collisions() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
if should_skip_mapping "$id" "$hostPort"; then
return 0
fi
if port_in_use "$hostPort"; then
# find which process is using it
local pid_info=""
@ -757,6 +888,10 @@ scan_for_collisions() {
fi
if [ -n "$pid_info" ]; then
if is_matching_port_forward "$pid_info" "$target" "$hostPort" "$servicePort"; then
vlog "Port $hostPort already forwarded by kubectl for $id (pid=$pid_info)."
return 0
fi
# Check if this PID is already managed by us
local pidfile pid_managed
pidfile="$(pid_file_for "$id")"
@ -810,15 +945,23 @@ do_start() {
ensure_supabase_ports
vlog "Scanning for port collisions..."
foreach_mapping scan_for_collisions
if ! foreach_mapping scan_for_collisions; then
err "No port-forward mappings found. Check ${CFG_FILE:-prole.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
exit 1
fi
foreach_mapping start_port_forward
if ! foreach_mapping start_port_forward; then
err "No port-forward mappings started. Check ${CFG_FILE:-prole.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
exit 1
fi
}
do_stop() {
# stop doesn't require cluster access, but it does need state dirs
ensure_dirs
foreach_mapping stop_port_forward
if ! foreach_mapping stop_port_forward; then
log "No port-forward mappings found to stop."
fi
}
do_restart() {
@ -840,9 +983,18 @@ do_restart() {
vlog "Scanning for port collisions (excluding our own)..."
# During restart, we'll stop them first anyway, but let's be safe.
foreach_mapping stop_port_forward
foreach_mapping scan_for_collisions
foreach_mapping start_port_forward
if ! foreach_mapping stop_port_forward; then
err "No port-forward mappings found. Check ${CFG_FILE:-prole.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
exit 1
fi
if ! foreach_mapping scan_for_collisions; then
err "No port-forward mappings found. Check ${CFG_FILE:-prole.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
exit 1
fi
if ! foreach_mapping start_port_forward; then
err "No port-forward mappings started. Check ${CFG_FILE:-prole.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
exit 1
fi
}
do_status() {
@ -881,7 +1033,9 @@ do_status() {
log ""
log "== Port-forward processes =="
foreach_mapping status_one
if ! foreach_mapping status_one; then
log " (no port-forward mappings found)"
fi
log ""
log "== Quick k3d awareness checks (best-effort) =="

View File

@ -4,7 +4,7 @@ set -euo pipefail
# init_registry.sh
# Purpose:
# - Deploy a Docker registry into Kubernetes
# - Deploy ArgoCD into Kubernetes (replaces in-cluster Docker registry)
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
@ -20,19 +20,22 @@ fi
ACTION=""
NAMESPACE_OVERRIDE=""
REGISTRY_NAME=${REGISTRY_NAME:-registry}
if [[ -d "$SCRIPT_DIR/../k8s/registry" ]]; then
REGISTRY_MANIFEST_DIR="$SCRIPT_DIR/../k8s/registry"
elif [[ -n "${PROLE_HOME:-}" && -d "$PROLE_HOME/k8s/registry" ]]; then
REGISTRY_MANIFEST_DIR="$PROLE_HOME/k8s/registry"
if [[ -d "$SCRIPT_DIR/../k8s/argocd" ]]; then
ARGOCD_MANIFEST_DIR="$SCRIPT_DIR/../k8s/argocd"
elif [[ -n "${PROLE_HOME:-}" && -d "$PROLE_HOME/k8s/argocd" ]]; then
ARGOCD_MANIFEST_DIR="$PROLE_HOME/k8s/argocd"
else
REGISTRY_MANIFEST_DIR="$SCRIPT_DIR/../k8s/registry"
ARGOCD_MANIFEST_DIR="$SCRIPT_DIR/../k8s/argocd"
fi
ARGOCD_MANIFEST_FILE=${ARGOCD_MANIFEST_FILE:-"$ARGOCD_MANIFEST_DIR/install.yaml"}
usage() {
cat <<EOF
Usage: init_registry.sh [-n|--namespace NS] <start|stop|status|restart|initialize|update|reload>
Deploys ArgoCD into the target namespace (common services layer).
Note: The local Docker registry (port 5000) is managed separately.
EOF
}
@ -77,44 +80,64 @@ while [[ $# -gt 0 ]]; do
done
if [[ -n "$NAMESPACE_OVERRIDE" ]]; then
NAMESPACE="$NAMESPACE_OVERRIDE"
export NAMESPACE
elif [[ -n "${REGISTRY_NAMESPACE:-}" ]]; then
NAMESPACE="$REGISTRY_NAMESPACE"
export NAMESPACE
ARGOCD_NAMESPACE="$NAMESPACE_OVERRIDE"
elif [[ -n "${ARGOCD_NAMESPACE:-}" ]]; then
ARGOCD_NAMESPACE="$ARGOCD_NAMESPACE"
elif [[ -n "${SERVICE_NAMESPACE:-}" ]]; then
NAMESPACE="$SERVICE_NAMESPACE"
export NAMESPACE
elif [[ -z "${NAMESPACE:-}" ]]; then
NAMESPACE="default"
export NAMESPACE
ARGOCD_NAMESPACE="$SERVICE_NAMESPACE"
elif [[ -n "${NAMESPACE:-}" ]]; then
ARGOCD_NAMESPACE="$NAMESPACE"
else
ARGOCD_NAMESPACE="default"
fi
export ARGOCD_NAMESPACE
export NAMESPACE="$ARGOCD_NAMESPACE"
ARGOCD_LABEL_SELECTOR=${ARGOCD_LABEL_SELECTOR:-"app.kubernetes.io/part-of=argocd"}
ARGOCD_SERVER_SERVICE=${ARGOCD_SERVER_SERVICE:-argocd-server}
ARGOCD_PORT_FORWARD_LOCAL=${ARGOCD_PORT_FORWARD_LOCAL:-8081}
ARGOCD_PORT_FORWARD_REMOTE=${ARGOCD_PORT_FORWARD_REMOTE:-80}
ensure_tools() {
command -v kubectl >/dev/null || { echo "Missing required tool: kubectl" >&2; exit 1; }
}
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
if ! kubectl get namespace "$ARGOCD_NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$ARGOCD_NAMESPACE' ..."
kubectl create namespace "$ARGOCD_NAMESPACE" >/dev/null 2>&1 || true
fi
}
render_manifest() {
if [[ ! -f "$ARGOCD_MANIFEST_FILE" ]]; then
echo "ERROR: ArgoCD manifest not found: $ARGOCD_MANIFEST_FILE" >&2
exit 1
fi
sed "s|\\${ARGOCD_NAMESPACE}|$ARGOCD_NAMESPACE|g" "$ARGOCD_MANIFEST_FILE"
}
apply_k8s() {
echo "Applying registry manifest to namespace '$NAMESPACE' ..."
prole_render_manifest "$REGISTRY_MANIFEST_DIR/deployment.yaml" | kubectl apply --validate=false -n "$NAMESPACE" -f -
kubectl rollout status deploy/"$REGISTRY_NAME" -n "$NAMESPACE" --timeout=120s || true
echo "Applying ArgoCD manifest to namespace '$ARGOCD_NAMESPACE' ..."
render_manifest | kubectl apply --server-side --force-conflicts --field-manager=prole-installer --validate=false -n "$ARGOCD_NAMESPACE" -f -
kubectl rollout status deploy/argocd-server -n "$ARGOCD_NAMESPACE" --timeout=180s || true
kubectl rollout status deploy/argocd-repo-server -n "$ARGOCD_NAMESPACE" --timeout=180s || true
kubectl rollout status deploy/argocd-dex-server -n "$ARGOCD_NAMESPACE" --timeout=180s || true
kubectl rollout status deploy/argocd-applicationset-controller -n "$ARGOCD_NAMESPACE" --timeout=180s || true
kubectl rollout status deploy/argocd-notifications-controller -n "$ARGOCD_NAMESPACE" --timeout=180s || true
kubectl rollout status deploy/argocd-redis -n "$ARGOCD_NAMESPACE" --timeout=180s || true
kubectl rollout status statefulset/argocd-application-controller -n "$ARGOCD_NAMESPACE" --timeout=180s || true
}
delete_k8s() {
echo "Removing registry resources from namespace '$NAMESPACE' ..."
kubectl delete -n "$NAMESPACE" -f "$REGISTRY_MANIFEST_DIR/deployment.yaml" --ignore-not-found
echo "Removing ArgoCD resources from namespace '$ARGOCD_NAMESPACE' ..."
render_manifest | kubectl delete -n "$ARGOCD_NAMESPACE" -f - --ignore-not-found
}
status_k8s() {
kubectl -n "$NAMESPACE" get deploy "$REGISTRY_NAME" 2>/dev/null || true
kubectl -n "$NAMESPACE" get svc "$REGISTRY_NAME" 2>/dev/null || true
kubectl -n "$ARGOCD_NAMESPACE" get deploy,statefulset,svc -l "$ARGOCD_LABEL_SELECTOR" 2>/dev/null || true
kubectl -n "$ARGOCD_NAMESPACE" get pods -l "$ARGOCD_LABEL_SELECTOR" 2>/dev/null || true
}
case "${ACTION:-}" in
@ -122,6 +145,8 @@ case "${ACTION:-}" in
ensure_tools
ensure_namespace
apply_k8s
prole_register_port_forward "argocd" "$ARGOCD_NAMESPACE" "svc/${ARGOCD_SERVER_SERVICE}" \
"$ARGOCD_PORT_FORWARD_LOCAL" "$ARGOCD_PORT_FORWARD_REMOTE" "0.0.0.0" "TCP" "ArgoCD"
;;
stop)
ensure_tools

View File

@ -4,9 +4,9 @@ set -euo pipefail
# init_service_layer.sh
# Purpose:
# - Deploy the Prole service layer (registry, OpenBao, OpenTofu, Garage; Kerberos optional)
# - Deploy the Prole service layer (ArgoCD, OpenBao, OpenTofu, Garage; Kerberos optional)
# - Keep service-layer resources grouped in SERVICE_NAMESPACE
# - Migrate service layer to a new namespace with best-effort registry data copy
# - Migrate service layer to a new namespace
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck disable=SC1090
@ -140,35 +140,6 @@ cleanup_openbao() {
kubectl -n "$ns" delete configmap prole-krb5-conf --ignore-not-found >/dev/null 2>&1 || true
}
copy_registry_data() {
local from_ns="$1"
local to_ns="$2"
local src_pod dst_pod
src_pod=$(kubectl -n "$from_ns" get pods -l app=registry -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
dst_pod=$(kubectl -n "$to_ns" get pods -l app=registry -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
if [[ -z "$src_pod" || -z "$dst_pod" ]]; then
log "Registry pods not found for copy ($from_ns -> $to_ns); skipping."
return 0
fi
if ! kubectl -n "$from_ns" exec "$src_pod" -- sh -c 'command -v tar >/dev/null 2>&1'; then
log "Registry source pod lacks tar; skipping data copy."
return 0
fi
if ! kubectl -n "$to_ns" exec "$dst_pod" -- sh -c 'command -v tar >/dev/null 2>&1'; then
log "Registry destination pod lacks tar; skipping data copy."
return 0
fi
log "Copying registry data from '$from_ns' to '$to_ns' (best-effort)..."
if kubectl -n "$from_ns" exec "$src_pod" -- tar -C /var/lib/registry -cf - . \
| kubectl -n "$to_ns" exec -i "$dst_pod" -- tar -C /var/lib/registry -xf -; then
log "Registry data copy complete."
else
log "Registry data copy failed; continuing."
fi
}
deploy_service_layer() {
local action="$1"
@ -178,13 +149,13 @@ deploy_service_layer() {
ensure_namespace "$ns"
label_namespace "$ns"
local registry_action openbao_action opentofu_action garage_action kdc_action
local argocd_action openbao_action opentofu_action garage_action kdc_action
case "$action" in
start|initialize|update|reload) registry_action="update" ;;
restart) registry_action="restart" ;;
stop) registry_action="stop" ;;
status) registry_action="status" ;;
*) registry_action="update" ;;
start|initialize|update|reload) argocd_action="update" ;;
restart) argocd_action="restart" ;;
stop) argocd_action="stop" ;;
status) argocd_action="status" ;;
*) argocd_action="update" ;;
esac
case "$action" in
@ -214,8 +185,8 @@ deploy_service_layer() {
*) kdc_action="update" ;;
esac
REGISTRY_NAMESPACE="$ns" SERVICE_NAMESPACE="$ns" \
"$SCRIPT_DIR/init_registry.sh" -n "$ns" "$registry_action" || rc=$?
ARGOCD_NAMESPACE="$ns" SERVICE_NAMESPACE="$ns" \
"$SCRIPT_DIR/init_registry.sh" -n "$ns" "$argocd_action" || rc=$?
if [[ "$action" == "stop" ]]; then
cleanup_openbao "$ns" || true
@ -241,7 +212,7 @@ deploy_service_layer() {
cleanup_old_namespace() {
local ns="$1"
log "Cleaning up service layer in old namespace '$ns' ..."
REGISTRY_NAMESPACE="$ns" SERVICE_NAMESPACE="$ns" \
ARGOCD_NAMESPACE="$ns" SERVICE_NAMESPACE="$ns" \
"$SCRIPT_DIR/init_registry.sh" -n "$ns" stop || true
OPENTOFU_NAMESPACE="$ns" SERVICE_NAMESPACE="$ns" \
"$SCRIPT_DIR/init_opentofu.sh" -n "$ns" stop || true
@ -275,7 +246,6 @@ migrate_service_layer() {
log "Migrating service layer from '$from_ns' to '$to_ns' ..."
deploy_service_layer update "$to_ns"
copy_registry_data "$from_ns" "$to_ns" || true
cleanup_old_namespace "$from_ns"
write_last_namespace "$to_ns"
}

423
etc/repair_pipeline.sh Executable file
View File

@ -0,0 +1,423 @@
#!/usr/bin/env bash
set -uo pipefail
# repair_pipeline.sh
# Purpose:
# - Validate cluster readiness and common service health
# - Repair or re-deploy safe-to-recreate services when anomalies are detected
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck disable=SC1090
source "$SCRIPT_DIR/prole_cfg.sh"
ACTION="repair"
NAMESPACE_OVERRIDE=""
DB_NAMESPACE_OVERRIDE=""
usage() {
cat <<'USAGE'
Usage: repair_pipeline.sh [-n|--namespace NS] [--db-namespace NS] [-m|--mode MODE] [repair]
Runs a best-effort repair pass across common services and cluster add-ons.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--mode)
shift
prole_set_mode "${1:-}"
;;
-m=*|--mode=*)
prole_set_mode "${1#*=}"
;;
-n|--namespace)
shift
NAMESPACE_OVERRIDE="${1:-}"
;;
--db-namespace)
shift
DB_NAMESPACE_OVERRIDE="${1:-}"
;;
--db-namespace=*)
DB_NAMESPACE_OVERRIDE="${1#*=}"
;;
-n=*|--namespace=*)
NAMESPACE_OVERRIDE="${1#*=}"
;;
repair)
ACTION="repair"
;;
-h|--help)
usage
exit 0
;;
*)
usage
exit 2
;;
esac
shift
done
BASE_NAMESPACE="${NAMESPACE:-}"
if [[ -n "$NAMESPACE_OVERRIDE" ]]; then
SERVICE_NAMESPACE="$NAMESPACE_OVERRIDE"
elif [[ -n "${SERVICE_NAMESPACE:-}" ]]; then
SERVICE_NAMESPACE="$SERVICE_NAMESPACE"
elif [[ -n "${NAMESPACE:-}" ]]; then
SERVICE_NAMESPACE="$NAMESPACE"
else
SERVICE_NAMESPACE="default"
fi
if [[ -n "$DB_NAMESPACE_OVERRIDE" ]]; then
DB_NAMESPACE="$DB_NAMESPACE_OVERRIDE"
elif [[ -n "${PROLE_DB_NAMESPACE:-}" ]]; then
DB_NAMESPACE="$PROLE_DB_NAMESPACE"
elif [[ -n "$BASE_NAMESPACE" ]]; then
DB_NAMESPACE="$BASE_NAMESPACE"
else
DB_NAMESPACE="$SERVICE_NAMESPACE"
fi
export SERVICE_NAMESPACE
export DB_NAMESPACE
export NAMESPACE="$SERVICE_NAMESPACE"
log() { printf '%s\n' "$*"; }
warn() { printf 'WARN: %s\n' "$*" >&2; }
err() { printf 'ERROR: %s\n' "$*" >&2; }
have() { command -v "$1" >/dev/null 2>&1; }
run_with_timeout() {
local timeout_s="$1"
shift
local cmd=( "$@" )
"${cmd[@]}" &
local pid=$!
local start=$SECONDS
while kill -0 "$pid" >/dev/null 2>&1; do
if (( SECONDS - start > timeout_s )); then
warn "Command timed out after ${timeout_s}s: ${cmd[*]}"
kill "$pid" >/dev/null 2>&1 || true
return 124
fi
sleep 2
done
wait "$pid"
return $?
}
ensure_kubectl() {
have kubectl || { err "kubectl not found"; exit 1; }
}
detect_mode() {
local mode
mode="${PROLE_MODE:-}"
if have prole_normalize_mode; then
mode="$(prole_normalize_mode "$mode")"
fi
printf '%s' "$mode"
}
ensure_kubeconfig_if_needed() {
local mode
mode="$(detect_mode)"
if [[ "$mode" == "k3s" || -n "${PROLE_K3S_SERVER:-}" || -n "${K3S_SERVER_URL:-}" ]]; then
prole_ensure_kubeconfig >/dev/null 2>&1 || true
fi
}
cluster_ready() {
ensure_kubectl
if ! kubectl cluster-info >/dev/null 2>&1; then
return 1
fi
return 0
}
k3d_cluster_ready() {
local cluster="${K3D_CLUSTER:-prole-dev-cluster}"
if have k3d; then
if k3d cluster list --no-headers 2>/dev/null | grep -q "^${cluster}[[:space:]]"; then
return 0
fi
return 1
fi
return 0
}
resource_exists() {
local kind="$1" name="$2" ns="$3"
kubectl -n "$ns" get "$kind" "$name" >/dev/null 2>&1
}
resource_ready() {
local kind="$1" name="$2" ns="$3"
local desired ready
if ! resource_exists "$kind" "$name" "$ns"; then
return 2
fi
desired=$(kubectl -n "$ns" get "$kind" "$name" -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "")
ready=$(kubectl -n "$ns" get "$kind" "$name" -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "")
desired=${desired:-1}
ready=${ready:-0}
if [[ "$ready" -ge "$desired" && "$desired" -gt 0 ]]; then
return 0
fi
return 1
}
authority_context_exists() {
local base
if [[ -n "${PROLE_HOME:-}" ]]; then
if [[ -d "$PROLE_HOME/authority" || -d "$PROLE_HOME/prole/authority" ]]; then
return 0
fi
fi
base="$SCRIPT_DIR/.."
if [[ -d "$base/authority" || -d "$base/prole/authority" ]]; then
return 0
fi
return 1
}
repair_dashboard() {
local ns="kubernetes-dashboard"
if ! kubectl get ns "$ns" >/dev/null 2>&1; then
warn "Dashboard namespace missing; installing via Helm"
if have helm; then
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/ >/dev/null 2>&1 || true
helm repo update >/dev/null 2>&1 || true
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard \
--create-namespace --namespace "$ns"
else
warn "helm not found; cannot install kubernetes-dashboard"
return 1
fi
fi
local kong_pods
kong_pods=$(kubectl -n "$ns" get pods --no-headers 2>/dev/null | awk '{print $1}' | grep -E 'kong' || true)
if [[ -z "$kong_pods" ]]; then
warn "Dashboard Kong pod missing; re-applying Helm release"
if have helm; then
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard \
--create-namespace --namespace "$ns"
else
warn "helm not found; cannot reinstall kubernetes-dashboard"
return 1
fi
else
local unhealthy
unhealthy=$(kubectl -n "$ns" get pods --no-headers 2>/dev/null | grep -E 'kong' | awk '$3 != "Running" {print $1}' || true)
if [[ -n "$unhealthy" ]]; then
warn "Dashboard Kong pod unhealthy; deleting pods for restart"
kubectl -n "$ns" delete pod $unhealthy --wait=false >/dev/null 2>&1 || true
fi
fi
}
repair_argocd() {
local ns="$1"
local need_fix=0
local items=(
"deploy argocd-server"
"deploy argocd-repo-server"
"deploy argocd-dex-server"
"deploy argocd-applicationset-controller"
"deploy argocd-notifications-controller"
"deploy argocd-redis"
"statefulset argocd-application-controller"
)
local item kind name status
for item in "${items[@]}"; do
kind="${item%% *}"
name="${item##* }"
resource_ready "$kind" "$name" "$ns"; status=$?
if [[ "$status" -ne 0 ]]; then
need_fix=1
break
fi
done
if [[ "$need_fix" -eq 1 ]]; then
warn "ArgoCD not ready; re-deploying"
"$SCRIPT_DIR/init_registry.sh" -n "$ns" stop || true
"$SCRIPT_DIR/init_registry.sh" -n "$ns" update || true
else
log "ArgoCD: OK"
fi
}
repair_openbao() {
local ns="$1"
local status
resource_ready deploy openbao "$ns"; status=$?
if [[ "$status" -eq 2 ]]; then
resource_ready statefulset openbao "$ns"; status=$?
fi
if [[ "$status" -ne 0 ]]; then
warn "OpenBao not ready; restarting pods and re-applying"
kubectl -n "$ns" delete pod -l app=openbao --wait=false >/dev/null 2>&1 || true
"$SCRIPT_DIR/init_openbao.sh" -n "$ns" update || true
else
log "OpenBao: OK"
fi
}
repair_opentofu() {
local ns="$1"
local status
resource_ready deploy opentofu "$ns"; status=$?
if [[ "$status" -ne 0 ]]; then
warn "OpenTofu not ready; re-deploying"
kubectl -n "$ns" delete deploy opentofu --ignore-not-found >/dev/null 2>&1 || true
"$SCRIPT_DIR/init_opentofu.sh" -n "$ns" update || true
else
log "OpenTofu: OK"
fi
}
repair_garage() {
local ns="$1"
local status
resource_ready statefulset garage "$ns"; status=$?
if [[ "$status" -ne 0 ]]; then
warn "Garage not ready; restarting pods (PVCs preserved)"
kubectl -n "$ns" delete pod -l app=garage --wait=false >/dev/null 2>&1 || true
"$SCRIPT_DIR/init_garage_store.sh" start || true
else
log "Garage: OK"
fi
}
repair_cnpg() {
local ns="$1"
local cluster="${CNPG_CLUSTER_NAME:-prole-db}"
if ! kubectl -n "$ns" get cluster "$cluster" >/dev/null 2>&1; then
warn "CNPG cluster '$cluster' not found in namespace '$ns'"
return 0
fi
local total ready bad
total=$(kubectl -n "$ns" get pods -l "cnpg.io/cluster=$cluster" --no-headers 2>/dev/null | wc -l | tr -d ' ')
if [[ "$total" -eq 0 ]]; then
warn "CNPG cluster '$cluster' has no pods; skipping rollout"
return 0
fi
ready=$(kubectl -n "$ns" get pods -l "cnpg.io/cluster=$cluster" --no-headers 2>/dev/null | awk '$2 ~ /^([0-9]+)\/\\1$/ {c++} END{print c+0}')
bad=$(kubectl -n "$ns" get pods -l "cnpg.io/cluster=$cluster" --no-headers 2>/dev/null | awk '$3 ~ /CrashLoopBackOff|Error|ImagePullBackOff|ErrImagePull/ {c++} END{print c+0}')
if [[ "$ready" -lt "$total" || "$bad" -gt 0 ]]; then
warn "CNPG pods unhealthy ($ready/$total ready, $bad bad); running rollout"
run_with_timeout 360 env NAMESPACE="$ns" "$SCRIPT_DIR/init_cloudnative_pg.sh" rollout || true
else
log "CNPG: OK"
fi
}
repair_barman_plugin() {
local ns="cnpg-system"
if ! kubectl -n "$ns" get deploy barman-cloud >/dev/null 2>&1; then
warn "Barman Cloud plugin missing; reinstalling"
run_with_timeout 300 env NAMESPACE="$DB_NAMESPACE" "$SCRIPT_DIR/init_cloudnative_pg.sh" install-barman-plugin || true
return 0
fi
local ready
ready=$(kubectl -n "$ns" get deploy barman-cloud -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
if [[ -z "$ready" || "$ready" == "0" ]]; then
warn "Barman Cloud plugin not ready; reinstalling and restarting"
run_with_timeout 300 env NAMESPACE="$DB_NAMESPACE" "$SCRIPT_DIR/init_cloudnative_pg.sh" install-barman-plugin || true
kubectl -n "$ns" rollout restart deploy/barman-cloud >/dev/null 2>&1 || true
else
log "Barman Cloud plugin: OK"
fi
}
repair_kdc() {
local ns="$1"
local status
if ! authority_context_exists; then
warn "Authority Docker context missing; skipping KDC rebuild"
return 0
fi
resource_ready deploy auth "$ns"; status=$?
if [[ "$status" -ne 0 ]]; then
warn "KDC (auth) not ready; re-deploying"
kubectl -n "$ns" delete deploy auth --ignore-not-found >/dev/null 2>&1 || true
kubectl -n "$ns" delete svc auth --ignore-not-found >/dev/null 2>&1 || true
"$SCRIPT_DIR/init_kdc.sh" update || true
else
log "KDC (auth): OK"
fi
}
case "${ACTION}" in
repair)
ensure_kubectl
ensure_kubeconfig_if_needed
log "== Repair Pipeline =="
log "Namespace: $NAMESPACE"
if ! cluster_ready; then
err "Cluster not reachable; aborting repair"
exit 2
fi
if [[ "$(detect_mode)" == "k3d" ]]; then
if ! k3d_cluster_ready; then
err "k3d cluster not running"
exit 2
fi
fi
log "-- Dashboard (Kong) --"
repair_dashboard || true
log "-- ArgoCD --"
repair_argocd "$SERVICE_NAMESPACE"
log "-- OpenBao --"
repair_openbao "$SERVICE_NAMESPACE"
log "-- OpenTofu --"
repair_opentofu "$SERVICE_NAMESPACE"
log "-- Garage --"
repair_garage "$SERVICE_NAMESPACE"
if [[ "${KERBEROS_ENABLED:-}" == "0" || "${KERBEROS_ENABLED:-}" == "false" || "${KERBEROS_ENABLED:-}" == "False" ]]; then
log "KDC (auth): skipped (Kerberos disabled)"
elif [[ "${PROLE_KDC_ENABLED:-1}" != "0" ]]; then
log "-- KDC (auth) --"
repair_kdc "$SERVICE_NAMESPACE"
else
log "KDC (auth): skipped (disabled)"
fi
log "-- Barman Cloud Plugin --"
repair_barman_plugin
log "-- CNPG --"
repair_cnpg "$DB_NAMESPACE"
log "-- Common Services Status --"
if [[ "${KERBEROS_ENABLED:-}" == "1" || "${KERBEROS_ENABLED:-}" == "true" || "${KERBEROS_ENABLED:-}" == "True" ]]; then
"$SCRIPT_DIR/status_common_services.sh" -n "$SERVICE_NAMESPACE" -k || true
else
"$SCRIPT_DIR/status_common_services.sh" -n "$SERVICE_NAMESPACE" || true
fi
log "Repair pipeline complete."
;;
*)
usage
exit 2
;;
esac

View File

@ -9,7 +9,7 @@ usage() {
cat <<EOF
Usage: status_common_services.sh [-n|--namespace NS] [-k|--kerberos]
Checks common infrastructure services (Docker registry, OpenBao, OpenTofu, Garage).
Checks common infrastructure services (ArgoCD, OpenBao, OpenTofu, Garage).
Use -k to include the Prole KDC (auth) checks.
EOF
}
@ -76,24 +76,24 @@ run_cmd() {
echo "== Services =="
if [[ "$ENABLE_KERBEROS" == "1" ]]; then
run_cmd kubectl -n "$NS" get svc registry openbao opentofu garage auth
run_cmd kubectl -n "$NS" get svc argocd-server openbao opentofu garage auth
else
run_cmd kubectl -n "$NS" get svc registry openbao opentofu garage
run_cmd kubectl -n "$NS" get svc argocd-server openbao opentofu garage
fi
echo "== Workloads =="
if [[ "$ENABLE_KERBEROS" == "1" ]]; then
run_cmd kubectl -n "$NS" get deploy registry opentofu auth
run_cmd kubectl -n "$NS" get deploy argocd-server argocd-repo-server argocd-dex-server argocd-applicationset-controller argocd-notifications-controller argocd-redis opentofu auth
else
run_cmd kubectl -n "$NS" get deploy registry opentofu
run_cmd kubectl -n "$NS" get deploy argocd-server argocd-repo-server argocd-dex-server argocd-applicationset-controller argocd-notifications-controller argocd-redis opentofu
fi
run_cmd kubectl -n "$NS" get statefulset openbao garage
run_cmd kubectl -n "$NS" get statefulset argocd-application-controller openbao garage
echo "== Pods =="
if [[ "$ENABLE_KERBEROS" == "1" ]]; then
run_cmd kubectl -n "$NS" get pods | grep -Ei "registry|openbao|opentofu|garage|auth" || true
run_cmd kubectl -n "$NS" get pods | grep -Ei "argocd|openbao|opentofu|garage|auth" || true
else
run_cmd kubectl -n "$NS" get pods | grep -Ei "registry|openbao|opentofu|garage" || true
run_cmd kubectl -n "$NS" get pods | grep -Ei "argocd|openbao|opentofu|garage" || true
fi
echo "== Health Check =="
@ -161,7 +161,7 @@ check_resource() {
fi
}
check_resource svc registry
check_resource svc argocd-server
check_resource svc openbao
check_resource svc opentofu
check_resource svc garage
@ -169,17 +169,23 @@ if [[ "$ENABLE_KERBEROS" == "1" ]]; then
check_resource svc auth
fi
check_resource deploy registry
check_resource deploy argocd-server
check_resource deploy argocd-repo-server
check_resource deploy argocd-dex-server
check_resource deploy argocd-applicationset-controller
check_resource deploy argocd-notifications-controller
check_resource deploy argocd-redis
check_resource deploy opentofu
if [[ "$ENABLE_KERBEROS" == "1" ]]; then
check_resource deploy auth
fi
check_resource statefulset argocd-application-controller
check_resource statefulset openbao
check_resource statefulset garage
pod_filter="registry|openbao|opentofu|garage"
pod_filter="argocd|openbao|opentofu|garage"
if [[ "$ENABLE_KERBEROS" == "1" ]]; then
pod_filter="registry|openbao|opentofu|garage|auth"
pod_filter="argocd|openbao|opentofu|garage|auth"
fi
analyze_pods
@ -200,7 +206,7 @@ fi
if [ $blocked -gt 0 ]; then
repairable=()
fail_all=()
for comp in registry openbao opentofu garage auth; do
for comp in argocd openbao opentofu garage auth; do
if [[ "$comp" == "auth" && "$ENABLE_KERBEROS" != "1" ]]; then
continue
fi

View File

@ -1632,6 +1632,9 @@ class ProleInstaller:
self._managed_kubeconfig = None
self._kong_last_repair_at = 0.0
self._kong_repair_cooldown_s = 120
self._repair_last_run_at = 0.0
self._repair_cooldown_s = 600
self._repair_inflight = False
_ensure_ansible_vault_credentials(prompt_ui=True, root=root)
self._action_flags = {}
self.root.title("Prole Database Installer")
@ -3953,8 +3956,8 @@ class ProleInstaller:
# messagebox.showerror("Invalid Port", "Please enter a valid numeric port.")
# return
# Determine path: prole/conf/prole.cfg
# If PROLE_CONF is set, use it. Otherwise fallback to PROJECT_ROOT/prole/conf
# Determine path: conf/prole.cfg
# If PROLE_CONF is set, use it. Otherwise fallback to PROJECT_ROOT/conf
if self._cfg_path_override is not None:
cfg_path = self._cfg_path_override
if cfg_path.is_dir():
@ -3967,7 +3970,8 @@ class ProleInstaller:
if prole_conf:
conf_dir = Path(prole_conf).expanduser()
else:
conf_dir = PROJECT_ROOT / 'prole' / 'conf'
conf_dir = PROJECT_ROOT / 'conf'
os.environ.setdefault('PROLE_CONF', str(conf_dir))
cfg_path = conf_dir / 'prole.cfg'
conf_dir.mkdir(parents=True, exist_ok=True)
@ -4487,6 +4491,8 @@ class ProleInstaller:
combo = getattr(self, '_ollama_model_combo', None)
if not combo:
return
if getattr(self, '_refreshing_ollama_table', False):
return
values = models or []
try:
combo.configure(values=values)
@ -4544,6 +4550,13 @@ class ProleInstaller:
if getattr(self, '_refreshing_ollama_table', False):
return
self._refreshing_ollama_table = True
self._ollama_model_options = {}
combo = getattr(self, '_ollama_model_combo', None)
if combo:
try:
combo.configure(values=[], state='disabled')
except Exception:
pass
if getattr(self, '_ollama_status_var', None):
self._ollama_status_var.set("Scanning...")
@ -4587,8 +4600,16 @@ class ProleInstaller:
notice = f"Scan error: {e}"
self.safe_after(lambda: self._ollama_status_var.set("Scan failed") if getattr(self, '_ollama_status_var', None) else None)
finally:
self.safe_after(lambda: self._update_ollama_table(rows, notice))
self._refreshing_ollama_table = False
def finish():
self._refreshing_ollama_table = False
self._update_ollama_table(rows, notice)
combo = getattr(self, '_ollama_model_combo', None)
if combo:
try:
combo.configure(state='normal')
except Exception:
pass
self.safe_after(finish)
threading.Thread(target=worker, daemon=True).start()
@ -4696,6 +4717,8 @@ class ProleInstaller:
self.bg_canvas.itemconfig(preview_item, text=text)
except Exception:
pass
if getattr(self, '_refreshing_ollama_table', False):
return
current_key = self._ollama_current_key()
models = self._ollama_model_options.get(current_key, [])
if models:
@ -5596,7 +5619,7 @@ class ProleInstaller:
font=('SF Pro Text', 18), anchor='ne')
self._render_title('Cluster Environment', y=150)
self._render_paragraph('Select a cluster environment and ensure the cluster (k3d/k3s/prod), OpenBao, and required services are running.', y=200)
self._render_paragraph('Select a cluster environment and ensure the cluster is running and common services can be deployed.', y=200)
# Cluster Selection (Radio Buttons)
x_label = 48
@ -5775,23 +5798,15 @@ class ProleInstaller:
ui.canvas_text(self, x_label + 20, y, '(Used by etc/deploy_pipeline.sh --mode gcp)', fill='#6e6e73', font=('SF Pro Text', 10))
y += 30
# Docker/K3D Status (only if dev)
# Cluster Status (only if dev)
if selected_env_key == 'dev':
y += 10
self.docker_status_label = ui.canvas_text(self, x_label, y, 'Docker: Checking...', fill='#6e6e73', font=('SF Pro Text', 12))
self._canvas_items.append(self.docker_status_label)
y += 30
self.k3d_status_label = ui.canvas_text(self, x_label, y, 'Cluster: Checking...', fill='#6e6e73', font=('SF Pro Text', 12))
self._canvas_items.append(self.k3d_status_label)
y += 30
self.registry_status_label = ui.canvas_text(self, x_label, y, 'Local Registry: Checking...', fill='#6e6e73', font=('SF Pro Text', 12))
self._canvas_items.append(self.registry_status_label)
y += 30
self.openbao_status_label = ui.canvas_text(self, x_label, y, 'OpenBao: Checking...', fill='#6e6e73', font=('SF Pro Text', 12))
self._canvas_items.append(self.openbao_status_label)
self.common_services_status_label = ui.canvas_text(self, x_label, y, 'Common Services: Checking...', fill='#6e6e73', font=('SF Pro Text', 12))
self._canvas_items.append(self.common_services_status_label)
y += 30
self._cluster_env_message_label = ui.canvas_text(self, x_label, y, '', fill='#34c759', font=('SF Pro Text', 11))
@ -5883,9 +5898,9 @@ class ProleInstaller:
rc_ns, out_ns = self._run_cmd_capture(base_cmd + ['get', 'ns', ns, '-o', 'name'], timeout=6)
if rc_ns != 0 or ns not in (out_ns or ''):
return {
'ok': True,
'msg': 'Dashboard (Kong): Not installed',
'fill': '#6e6e73',
'ok': False,
'msg': 'Dashboard (Kong): Missing',
'fill': '#ff9f0a',
'reset_pods': [],
}
@ -5916,9 +5931,9 @@ class ProleInstaller:
if not kong_pods:
return {
'ok': True,
'msg': 'Dashboard (Kong): Not installed',
'fill': '#6e6e73',
'ok': False,
'msg': 'Dashboard (Kong): Missing',
'fill': '#ff9f0a',
'reset_pods': [],
}
@ -5969,6 +5984,79 @@ class ProleInstaller:
pass
self.safe_after(_clear, delay=clear_after_ms)
def _authority_context_missing(self) -> bool:
"""Detect missing authority Docker context when Kerberos is enabled."""
try:
if not self.kerberos_enabled.get():
return False
except Exception:
return False
candidates = []
env_home = (os.environ.get('PROLE_HOME') or '').strip()
if env_home:
candidates.append(Path(env_home))
candidates.append(PROJECT_ROOT)
for base in candidates:
try:
if (base / 'authority').is_dir():
return False
if (base / 'prole' / 'authority').is_dir():
return False
except Exception:
continue
return True
def _maybe_run_repair_pipeline(self, status: dict, kong_status: dict | None):
"""Run repair pipeline when cluster is ready and anomalies are detected."""
if not status.get('cluster_ok'):
return
anomalies = []
if kong_status and not kong_status.get('ok', True):
anomalies.append('dashboard')
if self._authority_context_missing():
anomalies.append('authority')
if not anomalies:
return
now = time.time()
if self._repair_inflight:
return
if now - self._repair_last_run_at < self._repair_cooldown_s:
return
self._repair_last_run_at = now
self._repair_inflight = True
def worker():
try:
env_key = status.get('env') or self._cluster_env_key()
if env_key == 'service':
ns = self._get_service_namespace()
else:
ns = (self.db_namespace.get() or '').strip() or 'default'
env = self._script_env_for_namespace(ns)
if env_key == 'service':
env["PROLE_MODE"] = "k3s"
elif env_key == 'dev':
env["PROLE_MODE"] = "k3d"
self._set_cluster_env_message("Repair pipeline started", '#34c759', clear_after_ms=3000)
self.controller.run_script(
"repair_pipeline.sh",
args=["-n", ns],
env=env,
)
self._set_cluster_env_message("Repair pipeline complete", '#34c759', clear_after_ms=3000)
except Exception as e:
self._set_cluster_env_message(f"Repair pipeline failed: {e}", '#ff3b30', clear_after_ms=6000)
finally:
self._repair_inflight = False
threading.Thread(target=worker, daemon=True).start()
def _check_opentofu_health(self) -> bool:
"""Check OpenTofu readiness in the selected namespace."""
try:
@ -6115,7 +6203,7 @@ class ProleInstaller:
threading.Thread(target=worker, daemon=True).start()
def _deploy_k3s_services(self):
"""Deploy common services (registry/OpenBao/OpenTofu) to remote k3s cluster."""
"""Deploy common services (ArgoCD/OpenBao/OpenTofu/Garage) to remote k3s cluster."""
def _deploy():
console = self._k3s_service_deploy_console
if console:
@ -6228,39 +6316,6 @@ class ProleInstaller:
def _cluster_status_snapshot(self) -> dict:
env = self._cluster_env_key()
docker_ok = self.controller.check_docker_running()
docker_msg = 'Docker: Running' if docker_ok else 'Docker: Not running'
docker_fill = '#34c759' if docker_ok else '#ff3b30'
openbao_ok = self._check_openbao_health()
openbao_msg = 'OpenBao: Running' if openbao_ok else 'OpenBao: Not reachable'
openbao_fill = '#34c759' if openbao_ok else '#ff9f0a'
registry_ok = False
registry_msg = ''
registry_fill = '#6e6e73'
if env == 'dev':
if docker_ok:
try:
ps = subprocess.run(['docker', 'ps', '--format', '{{.Names}} {{.Image}}'], capture_output=True, text=True)
for line in (ps.stdout or '').splitlines():
parts = line.split()
if not parts:
continue
name = parts[0]
image = parts[1] if len(parts) > 1 else ''
if name == 'k3d-prole-registry' or image.startswith('registry:2'):
registry_ok = True
break
except Exception:
registry_ok = False
registry_msg = 'Local Registry: Running' if registry_ok else 'Local Registry: Not running'
registry_fill = '#34c759' if registry_ok else '#ff9f0a'
else:
registry_ok = True
registry_msg = 'Local Registry: Not required'
registry_fill = '#6e6e73'
cluster_ok = False
if env == 'dev':
cluster_name = "prole-dev-cluster"
@ -6274,33 +6329,24 @@ class ProleInstaller:
cluster_ok, cluster_msg = self._check_k8s_cluster(env)
cluster_fill = '#34c759' if cluster_ok else '#ff9f0a'
common_ok = cluster_ok
common_msg = 'Common Services: Ready to deploy' if common_ok else 'Common Services: Not ready'
common_fill = '#34c759' if common_ok else '#ff9f0a'
return {
'env': env,
'docker_ok': docker_ok,
'docker_msg': docker_msg,
'docker_fill': docker_fill,
'cluster_ok': cluster_ok,
'cluster_msg': cluster_msg,
'cluster_fill': cluster_fill,
'registry_ok': registry_ok,
'registry_msg': registry_msg,
'registry_fill': registry_fill,
'openbao_ok': openbao_ok,
'openbao_msg': openbao_msg,
'openbao_fill': openbao_fill,
'common_ok': common_ok,
'common_msg': common_msg,
'common_fill': common_fill,
}
def _cluster_ready_for_navigation(self) -> bool:
status = self._cluster_status_snapshot()
env = status['env']
if env == 'dev' and not status['docker_ok']:
try:
messagebox.showerror('Docker', 'Docker is not running. Please start Docker and try again.')
except Exception:
pass
return False
if not status['cluster_ok']:
try:
messagebox.showerror('Cluster', 'Cluster is not reachable. Please verify your cluster and try again.')
@ -6308,48 +6354,6 @@ class ProleInstaller:
pass
return False
if env == 'dev' and not status['registry_ok']:
try:
messagebox.showerror('Registry', 'Local registry is not running. Start the registry and try again.')
except Exception:
pass
return False
if not status['openbao_ok']:
if env == 'dev':
# Attempt to start OpenBao locally
if status['docker_ok']:
env_vars = self._script_env_for_namespace((self.db_namespace.get() or '').strip())
env_vars["PROLE_DB_USER"] = self.db_username.get().strip()
env_vars["AT_REST_ENCRYPTION_ENABLED"] = _bool_str(self.at_rest_encryption_enabled.get())
rc_openbao = self.controller.run_script("init_openbao.sh", args=["start"], env=env_vars)
if rc_openbao != 0:
try:
messagebox.showerror('OpenBao', f'Failed to start OpenBao (code {rc_openbao}).')
except Exception:
pass
return False
# Re-check after start
status = self._cluster_status_snapshot()
if not status['openbao_ok']:
try:
messagebox.showerror('OpenBao', 'OpenBao is not reachable after startup.')
except Exception:
pass
return False
else:
try:
messagebox.showerror('OpenBao', 'OpenBao is not reachable and Docker is not running.')
except Exception:
pass
return False
else:
try:
messagebox.showerror('OpenBao', 'OpenBao is not reachable for the selected cluster.')
except Exception:
pass
return False
# Refresh status display for any changes
self.check_cluster_status_async()
return True
@ -6387,15 +6391,17 @@ class ProleInstaller:
else:
self._set_cluster_env_message("Execute Actions (REPAIR): none needed", '#34c759', clear_after_ms=1500)
# Run the repair pipeline if anomalies are detected on a ready cluster
try:
self._maybe_run_repair_pipeline(status, kong_status)
except Exception:
pass
def update_ui():
if hasattr(self, 'docker_status_label'):
self.bg_canvas.itemconfig(self.docker_status_label, text=status['docker_msg'], fill=status['docker_fill'])
if hasattr(self, 'k3d_status_label'):
self.bg_canvas.itemconfig(self.k3d_status_label, text=status['cluster_msg'], fill=status['cluster_fill'])
if hasattr(self, 'registry_status_label'):
self.bg_canvas.itemconfig(self.registry_status_label, text=status['registry_msg'], fill=status['registry_fill'])
if hasattr(self, 'openbao_status_label'):
self.bg_canvas.itemconfig(self.openbao_status_label, text=status['openbao_msg'], fill=status['openbao_fill'])
if hasattr(self, 'common_services_status_label'):
self.bg_canvas.itemconfig(self.common_services_status_label, text=status['common_msg'], fill=status['common_fill'])
if hasattr(self, '_init_cluster_button'):
try:
self._init_cluster_button.configure(text='Save')
@ -6461,25 +6467,7 @@ class ProleInstaller:
self.root.after(0, lambda: messagebox.showerror('Docker', 'Could not start Docker. Please start it manually.'))
return
# 2. Ensure OpenBao container is running (local-only)
env = self._script_env_for_namespace((self.db_namespace.get() or '').strip())
env["PROLE_DB_USER"] = self.db_username.get().strip()
env["AT_REST_ENCRYPTION_ENABLED"] = _bool_str(self.at_rest_encryption_enabled.get())
rc_openbao = self.controller.run_script("init_openbao.sh", args=["start"], env=env)
if rc_openbao != 0:
self.root.after(0, lambda: messagebox.showerror('OpenBao', f'Failed to start OpenBao (code {rc_openbao}).'))
return
# 3. Ensure local registry is available before cluster creation (dev only)
try:
reg_info = self.ensure_local_registry_available()
except Exception:
reg_info = None
if not reg_info:
self.root.after(0, lambda: messagebox.showerror('Registry', 'Local registry is not running. Please start the registry and try again.'))
return
# 4. Manage or verify cluster
# 2. Manage or verify cluster
cluster_name = 'prole-dev-cluster'
res = subprocess.run(['k3d', 'cluster', 'list', '--no-headers'], capture_output=True, text=True)
if cluster_name not in (res.stdout or ''):
@ -6716,7 +6704,7 @@ class ProleInstaller:
self._overlay_widgets.append(owner_entry)
y += 42
self._canvas_items.append(ui.canvas_text(self, x_label, y, 'Root Password:', fill='black', font=('SF Pro Text', 12, 'bold')))
self._canvas_items.append(ui.canvas_text(self, x_label, y, 'Master Password:', fill='black', font=('SF Pro Text', 12, 'bold')))
# Use tk.Entry on canvas
p1 = tk.Entry(self.bg_canvas, textvariable=self.db_password, show='*', bg='white', fg='black',
insertbackground='black', highlightbackground='#CCCCCC', highlightthickness=1,
@ -6895,20 +6883,26 @@ class ProleInstaller:
# Output Console
self._cnpg_deploy_console = self._create_console_output(y=260, title="Deployment Output", width=900, height=520)
# Deployment mode selector
mode_label = ui.canvas_text(self, 48, 782, "Mode", fill='#6e6e73', font=('SF Pro Text', 10, 'bold'))
self._canvas_items.append(mode_label)
mode_values = ['prole-dev-cluster', 'prole-service-cluster', 'prole-prod-cluster']
mode_combo = ttk.Combobox(self.bg_canvas, textvariable=self.deploy_target, values=mode_values, state='readonly', width=24)
mode_combo_window = self.bg_canvas.create_window(48, 800, window=mode_combo, anchor='nw', width=180)
self._canvas_items.append(mode_combo_window)
self._overlay_widgets.append(mode_combo)
# Use tk.Button
self._cnpg_deploy_button = tk.Button(self.bg_canvas, text='Run Deployment', command=self.run_cnpg_deploy,
bg='#F5F5DC', fg='black', activebackground='#E5E5D5',
highlightbackground='#F5F5DC', highlightthickness=0,
relief='flat', font=('SF Pro Text', 11), padx=16, pady=8)
self._cnpg_deploy_button.update_idletasks()
deploy_btn_height = self._cnpg_deploy_button.winfo_reqheight() or 32
# Deployment mode selector
mode_label = ui.canvas_text(self, 48, 782, "Mode", fill='#6e6e73', font=('SF Pro Text', 10, 'bold'))
self._canvas_items.append(mode_label)
mode_values = ['prole-dev-cluster', 'prole-service-cluster', 'prole-prod-cluster']
mode_frame = tk.Frame(self.bg_canvas, bg='#F5F5DC', width=180, height=deploy_btn_height)
mode_frame.pack_propagate(False)
mode_combo = ttk.Combobox(mode_frame, textvariable=self.deploy_target, values=mode_values, state='readonly', width=24)
mode_combo.pack(fill='both', expand=True)
mode_combo_window = self.bg_canvas.create_window(48, 800, window=mode_frame, anchor='nw', width=180, height=deploy_btn_height)
self._canvas_items.append(mode_combo_window)
self._overlay_widgets.append(mode_frame)
btn_window = self.bg_canvas.create_window(240, 800, window=self._cnpg_deploy_button, anchor='nw', width=180)
self._canvas_items.append(btn_window)
self._overlay_widgets.append(self._cnpg_deploy_button)
@ -6922,9 +6916,20 @@ class ProleInstaller:
self._canvas_items.append(rollout_btn_window)
self._overlay_widgets.append(self._cnpg_rollout_button)
# Status Label
self._cnpg_deploy_status_label = ui.canvas_text(self, 600, 812, "", fill='black', font=('SF Pro Text', 12))
# Save Deployment Button (moved from Post Install)
self._save_deployment_button = tk.Button(self.bg_canvas, text='Save Deployment', command=self.run_final_deployment,
bg='#F5F5DC', fg='black', activebackground='#E5E5D5',
highlightbackground='#F5F5DC', highlightthickness=0,
relief='flat', font=('SF Pro Text', 11), padx=16, pady=8)
save_btn_window = self.bg_canvas.create_window(600, 800, window=self._save_deployment_button, anchor='nw', width=180)
self._canvas_items.append(save_btn_window)
self._overlay_widgets.append(self._save_deployment_button)
# Status Labels (below the buttons row to avoid overlap)
self._cnpg_deploy_status_label = ui.canvas_text(self, 48, 840, "", fill='black', font=('SF Pro Text', 12))
self._canvas_items.append(self._cnpg_deploy_status_label)
self._save_deployment_status_label = ui.canvas_text(self, 600, 840, "", fill='black', font=('SF Pro Text', 12))
self._canvas_items.append(self._save_deployment_status_label)
def run_cnpg_deploy(self):
self._action_flags['init_cnpg_deploy.run_deploy'] = True
@ -9177,6 +9182,30 @@ echo "-------------------------------------------------------------------";
# ---------------- Build Summary page ----------------
def run_final_deployment(self):
initial_dir = str(self._resolve_env_dir('PROLE_DATA', 'data'))
try:
if not Path(initial_dir).exists():
initial_dir = str(Path.home())
except Exception:
initial_dir = str(Path.home())
export_base = filedialog.askdirectory(
initialdir=initial_dir,
title="Select deployment export directory"
)
if not export_base:
self.safe_after(lambda: self.bg_canvas.itemconfig(
self._save_deployment_status_label,
text="Save canceled.",
fill='#6e6e73'
) if hasattr(self, '_save_deployment_status_label') and self.bg_canvas.winfo_exists() else None)
return
export_base = str(Path(export_base).expanduser())
docker_export_dir = str(Path(export_base) / "docker-import")
helm_export_dir = str(Path(export_base) / "helm-chart")
kustomize_export_dir = str(Path(export_base) / "kustomize")
def worker():
self.safe_after(lambda: self._save_deployment_button.configure(state='disabled') if hasattr(self, '_save_deployment_button') and self._save_deployment_button.winfo_exists() else None)
self.safe_after(lambda: self.bg_canvas.itemconfig(self._save_deployment_status_label, text="Saving deployment...", fill='blue') if hasattr(self, '_save_deployment_status_label') and self.bg_canvas.winfo_exists() else None)
@ -9187,14 +9216,18 @@ echo "-------------------------------------------------------------------";
env = os.environ.copy()
env["PROLE_HOME"] = str(PROJECT_ROOT)
import_dir = self.docker_import_dir.get().strip()
if import_dir:
env["DOCKER_IMPORT_DIR"] = import_dir
env["DOCKER_IMPORT_DIR"] = docker_export_dir
env["HELM_CHART_DIR"] = helm_export_dir
env["KUSTOMIZE_DIR"] = kustomize_export_dir
console = getattr(self, 'install_consoles', {}).get(script_name)
if console:
console.clear()
console.write(f"Running {script_name} --docker-export...\n")
console.write("Saving deployment artifacts...\n")
console.write(f"Export base: {export_base}\n")
console.write(f"Docker images: {docker_export_dir}\n")
console.write(f"Helm chart: {helm_export_dir}\n")
console.write(f"Kustomize: {kustomize_export_dir}\n\n")
def _on_line(line):
if console:
@ -9202,7 +9235,7 @@ echo "-------------------------------------------------------------------";
rc = self.controller.run_script(
script_name,
args=['--docker-export'],
args=['--docker-export', '--helm-chart', '--kustomize', '--output-dir', export_base],
env=env,
on_line=_on_line
)
@ -9385,20 +9418,9 @@ echo "-------------------------------------------------------------------";
self._canvas_items.append(bao_btn_window)
self._overlay_widgets.append(self._build_a_bao_button)
# Save Deployment Button
self._save_deployment_button = tk.Button(self.bg_canvas, text='Save Deployment', command=self.run_final_deployment,
bg='#F5F5DC', fg='black', activebackground='#E5E5D5',
highlightbackground='#F5F5DC', highlightthickness=0,
relief='flat', font=('SF Pro Text', 11), padx=16, pady=8)
btn_window = self.bg_canvas.create_window(240, 760, window=self._save_deployment_button, anchor='nw', width=180)
self._canvas_items.append(btn_window)
self._overlay_widgets.append(self._save_deployment_button)
# Status Labels
# Status Label
self._build_a_bao_status_label = ui.canvas_text(self, 48, 802, "", fill='black', font=('SF Pro Text', 12))
self._canvas_items.append(self._build_a_bao_status_label)
self._save_deployment_status_label = ui.canvas_text(self, 240, 802, "", fill='black', font=('SF Pro Text', 12))
self._canvas_items.append(self._save_deployment_status_label)
def _render_build_summary_page(self):
# Ensure slide_area is visible for the build log console
@ -11023,6 +11045,7 @@ class ProleSilentInstaller:
self._cnpg_success = False
self._managed_kubeconfig = None
self.docker_import_dir = None
self._repair_ran = False
# ---------------- Logging helpers ----------------
def log(self, msg: str):
@ -11041,7 +11064,7 @@ class ProleSilentInstaller:
prole_conf = os.environ.get('PROLE_CONF')
if prole_conf:
return Path(_expand_path(prole_conf)) / 'prole.cfg'
return self.project_root / 'prole' / 'conf' / 'prole.cfg'
return self.project_root / 'conf' / 'prole.cfg'
def _load_inputs_from_cfg(self) -> dict:
cfg = configparser.ConfigParser(interpolation=None)
@ -11488,6 +11511,77 @@ class ProleSilentInstaller:
return env
def _authority_context_missing(self) -> bool:
enabled = self._get_input_bool('kerberos_config.enabled', False) or \
self._get_input_bool('init_cluster.kerberos_enabled', False)
if not enabled:
return False
candidates = []
env_home = (os.environ.get('PROLE_HOME') or '').strip()
if env_home:
candidates.append(Path(env_home))
candidates.append(self.project_root)
for base in candidates:
try:
if (base / 'authority').is_dir():
return False
if (base / 'prole' / 'authority').is_dir():
return False
except Exception:
continue
return True
def _dashboard_kong_missing(self, env: dict) -> bool:
ns = "kubernetes-dashboard"
try:
res_ns = subprocess.run(
['kubectl', 'get', 'ns', ns, '-o', 'name'],
env=env, capture_output=True, text=True, timeout=6
)
if res_ns.returncode != 0 or ns not in (res_ns.stdout or ''):
return True
res = subprocess.run(
['kubectl', '-n', ns, 'get', 'pods', '--no-headers'],
env=env, capture_output=True, text=True, timeout=6
)
if res.returncode != 0:
return True
kong_lines = [l for l in (res.stdout or '').splitlines() if 'kong' in l.lower()]
if not kong_lines:
return True
for line in kong_lines:
parts = line.split()
if len(parts) < 3:
return True
if parts[2] != 'Running':
return True
return False
except Exception:
return True
def _maybe_run_repair_pipeline(self, env: dict) -> None:
if self._repair_ran:
return
anomalies = []
if self._dashboard_kong_missing(env):
anomalies.append('dashboard')
if self._authority_context_missing():
anomalies.append('authority')
if not anomalies:
return
self._repair_ran = True
service_ns = self._service_namespace()
db_ns = (self._get_input('init_password.db_namespace', '') or '').strip() or 'default'
self.log(f"[INFO] Detected anomalies ({', '.join(anomalies)}); running repair pipeline...")
self._run_script(
"repair_pipeline.sh",
args=["-n", service_ns, "--db-namespace", db_ns],
env=env
)
# ---------------- Namespace helpers ----------------
def _get_local_owner(self) -> str:
try:
@ -12576,6 +12670,14 @@ class ProleSilentInstaller:
raise Exception("Cluster is not reachable.")
self.log(f"[OK] Cluster ready: {env_key}")
# Run repair pipeline if anomalies are detected on a ready cluster
try:
service_ns = self._service_namespace()
env = self._script_env_for_namespace(service_ns)
self._maybe_run_repair_pipeline(env)
except Exception:
pass
if env_key == 'service':
self._deploy_common_services(env_key)
else:

33493
k8s/argocd/install.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,8 @@ enum ProleEnv {
for dir in [proleHome(), logsDir(), confDir()] {
try? fm.createDirectory(at: dir, withIntermediateDirectories: true)
}
// Ensure conf/port-mappings.conf exists
let pm = confDir().appendingPathComponent("port-mappings.conf")
// Ensure conf/port-mapping.cfg exists
let pm = confDir().appendingPathComponent("port-mapping.cfg")
if !fm.fileExists(atPath: pm.path) {
let tpl = """
# Port mappings for Prole Tools (read by scripts).

View File

@ -13,7 +13,7 @@ ui.background=img/proleLogoSepia.png
ui.welcometxt=installer/welcome.txt
# Dev port-forward supervision
# Note: Port mappings are now sourced from $PROLE_HOME/conf/port-mappings.conf.
# Note: Port mappings are now sourced from $PROLE_HOME/conf/port-mapping.cfg.
# The legacy pf.* keys are intentionally omitted from the bundled defaults.
pf.enabled=true

View File

@ -13,7 +13,7 @@ ui.background=img/proleLogoSepia.png
ui.welcometxt=installer/welcome.txt
# Dev port-forward supervision
# Note: Port mappings are now sourced from $PROLE_HOME/conf/port-mappings.conf.
# Note: Port mappings are now sourced from $PROLE_HOME/conf/port-mapping.cfg.
# The legacy pf.* keys are intentionally omitted from the bundled defaults.
pf.enabled=true

0
prole/tests/etc/run_all.sh → tests/etc/run_all.sh Executable file → Normal file
View File

View File

@ -2,7 +2,7 @@
# Unit test for etc/build-a-bao.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/build-a-bao.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/deploy_pipeline.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/deploy_pipeline.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/final_deployment.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/final_deployment.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_ansible.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_ansible.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_cloudnative_pg.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_cloudnative_pg.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_common_services.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_common_services.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_garage_store.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_garage_store.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_k3s_registry.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_k3s_registry.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_k8s.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_k8s.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_kdc.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_kdc.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_kerberos.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_kerberos.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_kerberos_test.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_kerberos_test.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_monitoring.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_monitoring.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_ollama.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_ollama.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_openbao.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_openbao.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_opentofu.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_opentofu.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_port_forwards.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_port_forwards.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_prole-db-backup.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_prole-db-backup.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_prole-db-reset.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_prole-db-reset.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_registry.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_registry.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_service_layer.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_service_layer.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/init_supabase_ports.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_supabase_ports.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/render_manifest.py
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/render_manifest.py"

View File

@ -2,7 +2,7 @@
# Unit test for etc/set-k3s-token-vault.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/set-k3s-token-vault.sh"

View File

@ -2,7 +2,7 @@
# Unit test for etc/status_common_services.sh
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../../../" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$PROLE_HOME/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/status_common_services.sh"

View File

@ -30,14 +30,18 @@ if [[ -z "$CFG_PATH" ]]; then
CFG_PATH="$PROLE_CONF/prole.cfg"
elif [[ -f "$ROOT_DIR/conf/prole.cfg" ]]; then
CFG_PATH="$ROOT_DIR/conf/prole.cfg"
elif [[ -f "$ROOT_DIR/prole/conf/prole.cfg" ]]; then
CFG_PATH="$ROOT_DIR/prole/conf/prole.cfg"
else
echo "ERROR: prole.cfg not found. Pass a path or set PROLE_CONF." >&2
exit 1
fi
fi
CFG_DIR="$(cd "$(dirname "$CFG_PATH")" && pwd)"
if [[ -z "${PROLE_CONF:-}" ]]; then
PROLE_CONF="$CFG_DIR"
export PROLE_CONF
fi
TMP_CFG=""
if [[ "${PROLE_CFG_INPLACE:-}" != "true" ]]; then
TMP_CFG="$(mktemp -t prole.cfg.XXXXXX)"
@ -205,7 +209,7 @@ fi
echo ""
echo "3. Blessing sanitized gold config..."
python3 - "$CFG_PATH" "$ROOT_DIR/prole/conf/prole-db/prole.cfg" <<'PY'
python3 - "$CFG_PATH" "$PROLE_CONF/prole-db/prole.cfg" <<'PY'
import re
import sys
from pathlib import Path