Rename Prole storage objects to Synology and sync deployment updates

- Rename iSCSI storage class and PV/PVC selectors/labels from prole to synology across k8s and OpenTofu manifests\n- Update CNPG/OpenBao/Garage/monitoring init flows, render helpers, and mock scripts for synology-backed storage objects\n- Integrate related UI/core/service config/version updates and add supporting regression tests for CNPG storage/image behavior\n- Keep storage reconciliation tests aligned with current CNPG affinity output

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
chrisfu 2026-03-22 21:50:02 -07:00
parent 0262bd4be2
commit 9b9d6fdc88
60 changed files with 2148 additions and 514 deletions

83
_adopt_replica_pvcs.sh Normal file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
set -euo pipefail
NS=prole-db
CLUSTER_UID=$(kubectl get cluster prole-db -n "$NS" -o jsonpath='{.metadata.uid}' 2>/dev/null)
echo "Cluster UID: $CLUSTER_UID"
echo "=== Pausing CNPG controller ==="
kubectl scale deployment cnpg-controller-manager -n cnpg-system --replicas=0
sleep 5
echo "=== Cleaning up serial 8+ PVCs and jobs ==="
for serial in 8 9 10; do
kubectl delete pvc "prole-db-${serial}" "prole-db-${serial}-wal" -n "$NS" --ignore-not-found 2>/dev/null || true
kubectl delete job -n "$NS" -l "cnpg.io/instanceName=prole-db-${serial}" --ignore-not-found 2>/dev/null || true
done
echo "=== Patching replica PVCs with full CNPG labels/annotations ==="
for serial in 3 7; do
# Data PVC
kubectl patch pvc "prole-db-${serial}" -n "$NS" --type=merge -p "{
\"metadata\": {
\"labels\": {
\"app.kubernetes.io/component\": \"database\",
\"app.kubernetes.io/managed-by\": \"cloudnative-pg\",
\"app.kubernetes.io/name\": \"postgresql\",
\"cnpg.io/cluster\": \"prole-db\",
\"cnpg.io/instanceName\": \"prole-db-${serial}\",
\"cnpg.io/instanceRole\": \"replica\",
\"cnpg.io/pvcRole\": \"PG_DATA\",
\"role\": \"replica\"
},
\"annotations\": {
\"cnpg.io/nodeSerial\": \"${serial}\",
\"cnpg.io/operatorVersion\": \"1.28.1\",
\"cnpg.io/pvcStatus\": \"ready\"
},
\"ownerReferences\": [{
\"apiVersion\": \"postgresql.cnpg.io/v1\",
\"controller\": true,
\"kind\": \"Cluster\",
\"name\": \"prole-db\",
\"uid\": \"${CLUSTER_UID}\"
}]
}
}"
echo " Patched prole-db-${serial}"
# WAL PVC
kubectl patch pvc "prole-db-${serial}-wal" -n "$NS" --type=merge -p "{
\"metadata\": {
\"labels\": {
\"app.kubernetes.io/component\": \"database\",
\"app.kubernetes.io/managed-by\": \"cloudnative-pg\",
\"app.kubernetes.io/name\": \"postgresql\",
\"cnpg.io/cluster\": \"prole-db\",
\"cnpg.io/instanceName\": \"prole-db-${serial}\",
\"cnpg.io/instanceRole\": \"replica\",
\"cnpg.io/pvcRole\": \"PG_WAL\",
\"role\": \"replica\"
},
\"annotations\": {
\"cnpg.io/nodeSerial\": \"${serial}\",
\"cnpg.io/operatorVersion\": \"1.28.1\",
\"cnpg.io/pvcStatus\": \"ready\"
},
\"ownerReferences\": [{
\"apiVersion\": \"postgresql.cnpg.io/v1\",
\"controller\": true,
\"kind\": \"Cluster\",
\"name\": \"prole-db\",
\"uid\": \"${CLUSTER_UID}\"
}]
}
}"
echo " Patched prole-db-${serial}-wal"
done
echo "=== Resuming CNPG controller ==="
kubectl scale deployment cnpg-controller-manager -n cnpg-system --replicas=1
echo "Done — CNPG should now create instance pods for serials 3 (pi) and 7 (merlin)"
sleep 20
kubectl get pods,jobs -n "$NS" -o wide 2>/dev/null | grep -v "Completed\|initdb"
kubectl get cluster prole-db -n "$NS" -o jsonpath='Phase: {.status.phase} Ready: {.status.readyInstances}/{.spec.instances}{"\n"}' 2>/dev/null

92
_fix_replica_merlin.sh Normal file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail
NS=prole-db
echo "=== Pausing CNPG controller ==="
kubectl scale deployment cnpg-controller-manager -n cnpg-system --replicas=0
sleep 5
echo "=== Cleaning up all pending/failed replica PVCs and jobs ==="
# Keep prole-db-1 (primary) and prole-db-3 (pi replica). Delete everything else.
for serial in 2 4 5 6 7 8 9; do
kubectl delete pvc "prole-db-${serial}" "prole-db-${serial}-wal" -n "$NS" --ignore-not-found 2>/dev/null || true
kubectl delete job -n "$NS" -l "cnpg.io/instanceName=prole-db-${serial}" --ignore-not-found 2>/dev/null || true
done
echo "Cleaned up stale PVCs and jobs"
echo "=== Clearing Released PV claimRefs ==="
for pv in synology-iscsi-d002-data synology-iscsi-d002-wal; do
kubectl patch pv "$pv" --type=json \
-p '[{"op":"remove","path":"/spec/claimRef"}]' 2>/dev/null || \
kubectl patch pv "$pv" --type=merge \
-p '{"spec":{"claimRef":{"name":"","uid":"","namespace":""}}}' 2>/dev/null || true
echo " Patched $pv"
done
echo "=== Waiting for d002 PVs to become Available ==="
for pv in synology-iscsi-d002-data synology-iscsi-d002-wal; do
for i in $(seq 1 30); do
phase=$(kubectl get pv "$pv" -o jsonpath='{.status.phase}' 2>/dev/null)
echo " $pv: $phase"
if [[ "$phase" == "Available" ]]; then break; fi
sleep 3
done
done
echo "=== Getting CNPG PVC labels from existing prole-db-3 PVC ==="
CNPG_LABELS=$(kubectl get pvc prole-db-3 -n "$NS" -o jsonpath='{.metadata.labels}' 2>/dev/null || echo "{}")
echo " Labels: $CNPG_LABELS"
# Determine next serial CNPG will use (latestGeneratedNode + 1)
NEXT_SERIAL=$(kubectl get cluster prole-db -n "$NS" -o jsonpath='{.status.latestGeneratedNode}' 2>/dev/null || echo "4")
NEXT_SERIAL=$((NEXT_SERIAL + 1))
echo "=== Pre-binding prole-db-${NEXT_SERIAL} PVCs to d002 (merlin) ==="
kubectl create -f - <<YAML
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-${NEXT_SERIAL}
namespace: ${NS}
labels:
cnpg.io/cluster: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-${NEXT_SERIAL}-wal
namespace: ${NS}
labels:
cnpg.io/cluster: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-wal
YAML
echo "Created pre-bound PVCs for serial ${NEXT_SERIAL}"
echo "=== Waiting for PVCs to bind ==="
for pvc in "prole-db-${NEXT_SERIAL}" "prole-db-${NEXT_SERIAL}-wal"; do
for i in $(seq 1 20); do
phase=$(kubectl get pvc "$pvc" -n "$NS" -o jsonpath='{.status.phase}' 2>/dev/null)
echo " $pvc: $phase"
if [[ "$phase" == "Bound" ]]; then break; fi
sleep 3
done
done
echo "=== Resuming CNPG controller ==="
kubectl scale deployment cnpg-controller-manager -n cnpg-system --replicas=1
echo "Done — CNPG will create prole-db-${NEXT_SERIAL}-join targeting merlin.prole.org"
sleep 15
kubectl get jobs,pods -n "$NS" -o wide | grep -v "Completed\|initdb"

9
_import_pi.sh Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
echo "Importing knoe-db:18-140 to pi.prole.org..."
docker save myrddin.prole.org:5000/knoe-db:18-140 \
| ssh -o StrictHostKeyChecking=no -o BatchMode=yes \
-o ServerAliveInterval=30 -o ServerAliveCountMax=20 \
-i ~/.ssh/id_ed25519_ansible ansible@pi.prole.org \
"sudo k3s ctr images import -" 2>&1 | tail -5
echo "Done importing to pi."
kubectl get jobs,pods -n prole-db -o wide 2>/dev/null | grep -v "Completed\|initdb"

8
_monitor_run.sh Normal file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
for i in $(seq 1 90); do
sleep 10
if ! pgrep -f "prole.sh.*service/prole.cfg" >/dev/null 2>&1; then
break
fi
done
tail -60 /tmp/prole_run.log

9
_patch_cluster.sh Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
cp k8s/prole/prole-db.yaml deploy/opentofu/k3s/manifests/prole/prole-db.yaml
echo "Mirrored prole-db.yaml to opentofu"
kubectl patch cluster prole-db -n prole-db --type=merge -p \
'{"spec":{"imageName":"myrddin.prole.org:5000/knoe-db:18-140","affinity":{"podAntiAffinityType":"preferred","nodeAffinity":{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"node.kubernetes.io/instance-type","operator":"In","values":["k3s"]}]}]}}}}}'
echo "Patched live cluster"
sleep 5
kubectl get pods -n prole-db -o wide

80
_prebind_pvcs.sh Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Pre-bind CNPG PVCs to specific Synology iSCSI PVs so CNPG schedules replicas
# on the correct nodes (merlin=d002, pi=d003) rather than defaulting to myrddin.
set -euo pipefail
NS=prole-db
# Delete any pending (unbound) PVCs for replicas 2 and 3
for pvc in prole-db-2 prole-db-2-wal prole-db-3 prole-db-3-wal; do
phase=$(kubectl get pvc "$pvc" -n "$NS" -o jsonpath='{.status.phase}' 2>/dev/null || true)
if [[ "$phase" == "Pending" || -z "$phase" ]]; then
kubectl delete pvc "$pvc" -n "$NS" --ignore-not-found
echo "Deleted pending PVC: $pvc"
fi
done
# Pre-bind replica-2 PVCs → synology-iscsi-d002 on merlin.prole.org
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-2
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 29Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-2-wal
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 29Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-wal
YAML
# Pre-bind replica-3 PVCs → synology-iscsi-d003 on pi.prole.org
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-3
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 29Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d003-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-3-wal
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 29Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d003-wal
YAML
echo "Waiting for PVCs to bind..."
sleep 5
kubectl get pvc -n "$NS"
# Delete the stuck pod so CNPG recreates it with correct affinity
kubectl delete pod -n "$NS" -l cnpg.io/instanceName=prole-db-2 --ignore-not-found
echo "Done — CNPG will recreate replica-2 pod targeting merlin.prole.org"

56
_rebind_d002.sh Normal file
View File

@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
NS=prole-db
# Release d002 PVs by deleting old prole-db-2 PVCs
kubectl delete pvc prole-db-2 prole-db-2-wal -n "$NS" --ignore-not-found
echo "Deleted prole-db-2 PVCs"
# Wait for PVs to become Available again
for pv in synology-iscsi-d002-data synology-iscsi-d002-wal; do
for i in $(seq 1 20); do
phase=$(kubectl get pv "$pv" -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
if [[ "$phase" == "Available" ]]; then
echo " PV $pv is Available"
break
fi
sleep 2
done
done
# Pre-bind prole-db-4 PVCs to d002 PVs on merlin
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-4
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 29Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-4-wal
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 29Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-wal
YAML
echo "Created prole-db-4 PVCs pre-bound to d002 (merlin)"
# Delete the stuck prole-db-4-join Job so CNPG recreates it reading the new PVCs
kubectl delete job -n "$NS" -l cnpg.io/instanceName=prole-db-4 --ignore-not-found
echo "Deleted prole-db-4-join Job — CNPG will recreate targeting merlin"
sleep 10
kubectl get pvc,pods -n "$NS" | grep -v "Completed\|initdb"

50
_rebind_d002b.sh Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
NS=prole-db
# Delete the job first, then the PVCs CNPG created with wrong volumeName
kubectl delete job -n "$NS" -l cnpg.io/instanceName=prole-db-4 --ignore-not-found
kubectl delete pvc prole-db-4 prole-db-4-wal -n "$NS" --ignore-not-found
echo "Deleted prole-db-4 job and PVCs"
# Wait for d002 PVs to become Available
for pv in synology-iscsi-d002-data synology-iscsi-d002-wal; do
for i in $(seq 1 30); do
phase=$(kubectl get pv "$pv" -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
echo " $pv: $phase"
if [[ "$phase" == "Available" ]]; then break; fi
sleep 2
done
done
# Pre-bind with 1Gi (matching CNPG cluster spec size)
kubectl create -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-4
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prole-db-4-wal
namespace: prole-db
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
storageClassName: synology-iscsi
volumeName: synology-iscsi-d002-wal
YAML
echo "Created prole-db-4 PVCs pre-bound to d002 (merlin)"
sleep 8
kubectl get pvc -n "$NS"

View File

@ -0,0 +1,13 @@
{
"postgresql": {
"stable": "16",
"current": "17",
"latest": "18"
},
"percona": {
"stable": "16",
"current": "17",
"latest": "18",
"v18": "18"
}
}

View File

@ -1,5 +1,5 @@
; Prole Master Configuration File
; Generated by install.py on 2026-03-22 05:28:56
; Generated by install.py on 2026-03-22 21:34:24
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
@ -36,7 +36,7 @@ dependencies.opentofu.install = true
dependencies.python.install = true
dependencies.verify_all = false
disk_selection.disk_type = local
disk_selection.local_path = /Users/chrisfu
disk_selection.local_path = /Users/chrisfu/dev/prole/prole-tools-app/dist
disk_selection.removable_mount =
env_setup.NAMESPACE = ${NAMESPACE}
env_setup.PROLE_CONF = /Users/chrisfu/dev/prole/conf
@ -49,9 +49,9 @@ init_cluster.at_rest_encryption_enabled = true
init_cluster.cluster_env = dev
init_cluster.deployment_target = prole-dev-cluster
init_cluster.gitops_enabled = false
init_cluster.k3s_server_url =
init_cluster.k3s_token =
init_cluster.kerberos_enabled = false
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = ${PROLE_SECRET:v1:zaODrWdgRwk4Mn5f:5l3tyJapMmolvCe1YR8LpyfZZm_E4mApbEDdTwYdbIZ_hotg6TV6e19P60a8JAV_3CIKqgNBhGJ4wp_LY3o-5P-BJS8zs118bcPi3doRYItAQfRI9mPp0myMtKRb01xO7iWMY__t9M09m_tPsKd3LN_sobbpNNQRwxeY2w==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k3d
init_cluster.start_cluster = true
init_cluster.supabase_enabled = false
@ -60,35 +60,35 @@ init_cnpg_deploy.run_deploy = true
init_db_build.run_build = true
init_password.db_host_port = 5432
init_password.db_namespace = ${NAMESPACE}
init_password.db_password = ${PROLE_SECRET:v1:Ug4MMDdbizuLhRxp:7_zShAVRIyTwzI230RPi0DZkaYyec25z}
init_password.db_password_confirm = ${PROLE_SECRET:v1:Ug4MMDdbizuLhRxp:7_zShAVRIyTwzI230RPi0DZkaYyec25z}
init_password.db_password = ${PROLE_SECRET:v1:vb6MFP3JQ8ZA-kCH:P09DQs-Tf_unwlEZGjQiQug9imzKxk7z}
init_password.db_password_confirm = ${PROLE_SECRET:v1:vb6MFP3JQ8ZA-kCH:P09DQs-Tf_unwlEZGjQiQug9imzKxk7z}
init_password.db_username = root
init_password.generate_ssh_key = true
init_scripts.run_scripts = true
kerberos_config.enabled = false
kerberos_config.kdc =
kerberos_config.password = ${PROLE_SECRET:v1:HY4WS78rVJaooM8W:JXfyvQkXR-Pg3Hg5FCItCMNfTIsXlW_mNDTEHElowhJ9zv2z}
kerberos_config.realm =
kerberos_config.enabled = true
kerberos_config.kdc = 10.0.0.3
kerberos_config.password = ${PROLE_SECRET:v1:vYQE4jE-zNwvWosG:LB5ajSn0j84cLnbtg6x8o-jjIwdZh5ELNW1h1pp012xDcy7R}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false
kerberos_config.user = administrator
network_scan.run = true
ollama_config.model =
ollama_config.server_host =
ollama_config.server_host = fairyland.prole.org
ollama_config.server_port = 11434
[Global]
; Variables used by name in more than one place or assumed global scope
CLUSTER_ENV = dev
DB_HOST_PORT = 5432
DB_PASSWORD = ${PROLE_SECRET:v1:UfmNthRE-DSKbvW2:F64wGEHyh2iCF0MbYgKOUU_y4PQnzMhk}
DB_PASSWORD = ${PROLE_SECRET:v1:RdWFdqimNbYIof7M:rIB4trg7FLDfUWzYoXLFsU-1BUywEDE4}
DEPLOYMENT_MODE = k3d
DEPLOYMENT_TARGET = prole-dev-cluster
DOCKER_PRELOAD = false
NAMESPACE = knoe-db
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_DB_USER = root
PROLE_K3S_SERVER =
PROLE_K3S_TOKEN =
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:zaODrWdgRwk4Mn5f:5l3tyJapMmolvCe1YR8LpyfZZm_E4mApbEDdTwYdbIZ_hotg6TV6e19P60a8JAV_3CIKqgNBhGJ4wp_LY3o-5P-BJS8zs118bcPi3doRYItAQfRI9mPp0myMtKRb01xO7iWMY__t9M09m_tPsKd3LN_sobbpNNQRwxeY2w==}
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
SERVICE_NAMESPACE = knoe-system
@ -107,12 +107,18 @@ ANSIBLE_INVENTORY = /Users/chrisfu/dev/prole/infrastructure/inventory
ANSIBLE_REALM = PROLE.ORG
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","merlin.prole.org":"10.0.0.6","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_servers":["myrddin.prole.org"],"k3s_agents":["pi.prole.org","merlin.prole.org"],"k3s_hosts:children":["k3s_servers","k3s_agents"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"ssl_hosts":["myrddin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"]},"hosts":{"merlin":"10.0.0.6","merlin.prole.org":"10.0.0.6","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["k3s_agents","k3s_servers","mariadb_primary","mariadb_replica"]}
KDC_ANSIBLE_DETECTED = 10.0.0.3
KDC_AUTO_DETECTED = 10.0.0.3
KERBEROS_AUTO_ENABLED = True
[Port Forwards]
; No configuration values captured yet for this section.
[System Environment]
; No configuration values captured yet for this section.
PROLE_CONF = /Users/chrisfu/dev/prole/conf
PROLE_DATA = /Users/chrisfu/dev/prole/data
PROLE_HOME = /Users/chrisfu/dev/prole
PROLE_LOGS = /Users/chrisfu/dev/prole/logs
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
[Monitoring]
; No configuration values captured yet for this section.
@ -121,10 +127,16 @@ KDC_ANSIBLE_DETECTED = 10.0.0.3
; No configuration values captured yet for this section.
[Ollama]
; No configuration values captured yet for this section.
OLLAMA_HOST = http://fairyland.prole.org:11434
OLLAMA_SERVERS = fairyland.prole.org:11434,k3d.localhost:11434,morgoth.prole.org:11434
OLLAMA_SERVER_HOST = fairyland.prole.org
OLLAMA_SERVER_PORT = 11434
[Optional Features]
AT_REST_ENCRYPTION_ENABLED = True
GITOPS_ENABLED = False
KERBEROS_ENABLED = True
SUPABASE_ENABLED = False
[GitOps]
; No configuration values captured yet for this section.
@ -133,7 +145,9 @@ GITOPS_ENABLED = False
; No configuration values captured yet for this section.
[Initialize Cluster]
; No configuration values captured yet for this section.
ENVIRONMENT = dev
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${PROLE_SECRET:v1:zaODrWdgRwk4Mn5f:5l3tyJapMmolvCe1YR8LpyfZZm_E4mApbEDdTwYdbIZ_hotg6TV6e19P60a8JAV_3CIKqgNBhGJ4wp_LY3o-5P-BJS8zs118bcPi3doRYItAQfRI9mPp0myMtKRb01xO7iWMY__t9M09m_tPsKd3LN_sobbpNNQRwxeY2w==}
[Dev Cluster (k3d)]
CLUSTER_ENV = dev
@ -144,20 +158,21 @@ MODE = k3d
[Service Cluster (k3s)]
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL =
K3S_TOKEN =
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${PROLE_SECRET:v1:zaODrWdgRwk4Mn5f:5l3tyJapMmolvCe1YR8LpyfZZm_E4mApbEDdTwYdbIZ_hotg6TV6e19P60a8JAV_3CIKqgNBhGJ4wp_LY3o-5P-BJS8zs118bcPi3doRYItAQfRI9mPp0myMtKRb01xO7iWMY__t9M09m_tPsKd3LN_sobbpNNQRwxeY2w==}
MODE = k3s
PIPELINE_URL = http://127.0.0.1:8080
[Prod Cluster (k8s)]
ARTIFACTS_DIR = /private/var/folders/rt/pywlnmxj3dn7t5552vwdcpp80000gn/T/pytest-of-chrisfu/pytest-54/test_encryption_toggle0/data/staging
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
CLUSTER_ENV = prole-prod-cluster
DISPLAY_NAME = prole-prod-cluster
MODE = k8s
PIPELINE_URL = http://127.0.0.1:8080
[Docker Build]
; No configuration values captured yet for this section.
LOCAL_REGISTRY = localhost:5000
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000
[Initialization Scripts]
; No configuration values captured yet for this section.

View File

@ -1,180 +0,0 @@
; Prole Master Configuration File
; Generated by install.py on 2026-03-21 13:37:28
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
; User-editable values; derived values below reference these by default.
; No user values captured yet for this section.
[Inputs]
; Screen-scoped inputs used for unattended replays (-S)
build.deploy_env = Dev
build.run_build = false
database_options.distribution = percona
database_options.ext.dblink = true
database_options.ext.pg_buffercache = true
database_options.ext.pg_freespacemap = true
database_options.ext.pg_repack = true
database_options.ext.pg_stat_monitor = true
database_options.ext.pg_stat_statements = true
database_options.ext.pgaudit = true
database_options.ext.pgbadger = true
database_options.ext.pgcrypto = true
database_options.ext.pgrowlocks = true
database_options.ext.pgvector = true
database_options.ext.postgis = true
database_options.ext.postgres_fdw = true
database_options.version_type = v18
dependencies.ansible.install = true
dependencies.auto_install_missing = true
dependencies.brew.install = true
dependencies.docker.install = true
dependencies.k3d.install = true
dependencies.kubectl.install = true
dependencies.kubectx.install = true
dependencies.opentofu.install = true
dependencies.python.install = true
dependencies.verify_all = false
disk_selection.disk_type = local
disk_selection.local_path = /Users/chrisfu/dev/prole/prole-tools-app/dist
disk_selection.removable_mount =
env_setup.NAMESPACE = ${NAMESPACE}
env_setup.PROLE_CONF = /Users/chrisfu/dev/prole/conf
env_setup.PROLE_DATA = /Users/chrisfu/dev/prole/data
env_setup.PROLE_HOME = /Users/chrisfu/dev/prole
env_setup.PROLE_LOGS = /Users/chrisfu/dev/prole/logs
env_setup.PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
init_cluster.argocd_enabled = false
init_cluster.at_rest_encryption_enabled = true
init_cluster.cluster_env = service
init_cluster.deployment_target = prole-service-cluster
init_cluster.gitops_enabled = false
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k3s
init_cluster.start_cluster = 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_namespace = ${NAMESPACE}
init_password.db_password =
init_password.db_password_confirm =
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 = ${PROLE_SECRET:v1:71ROfYyYysQDJDpX:0K4cB3p9nnMb1yi2OImqUm-pa9Zg21JYTmYHzYB_7czTKMQJ}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false
kerberos_config.user = administrator
network_scan.run = true
ollama_config.model =
ollama_config.server_host = fairyland.prole.org
ollama_config.server_port = 11434
[Global]
; Variables used by name in more than one place or assumed global scope
CLUSTER_ENV = service
DB_HOST_PORT = 5432
DB_PASSWORD =
DEPLOYMENT_MODE = k3s
DEPLOYMENT_TARGET = prole-service-cluster
DOCKER_PRELOAD = false
NAMESPACE = prole-db
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_DB_USER = root
PROLE_HOME = $HOME/dev/prole
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
SERVICE_NAMESPACE = knoe-system
[Welcome]
; No configuration values captured yet for this section.
[Dependencies]
; No configuration values captured yet for this section.
[Network]
AD_DC_HOST = myrddin.prole.org
AD_DC_IP = 10.0.0.3
ANSIBLE_DOMAIN = prole.org
ANSIBLE_INFRASTRUCTURE = /Users/chrisfu/dev/prole/infrastructure
ANSIBLE_INVENTORY = /Users/chrisfu/dev/prole/infrastructure/inventory
ANSIBLE_REALM = PROLE.ORG
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","merlin.prole.org":"10.0.0.6","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_servers":["myrddin.prole.org"],"k3s_agents":["pi.prole.org","merlin.prole.org"],"k3s_hosts:children":["k3s_servers","k3s_agents"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"ssl_hosts":["myrddin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"]},"hosts":{"merlin":"10.0.0.6","merlin.prole.org":"10.0.0.6","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["k3s_agents","k3s_servers","mariadb_primary","mariadb_replica"]}
KDC_ANSIBLE_DETECTED = 10.0.0.3
KDC_AUTO_DETECTED = 10.0.0.3
KERBEROS_AUTO_ENABLED = True
[Port Forwards]
; No configuration values captured yet for this section.
[System Environment]
PROLE_CONF = /Users/chrisfu/dev/prole/conf
PROLE_DATA = /Users/chrisfu/dev/prole/data
PROLE_HOME = /Users/chrisfu/dev/prole
PROLE_LOGS = /Users/chrisfu/dev/prole/logs
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
[Monitoring]
; No configuration values captured yet for this section.
[Kerberos Authentication]
; No configuration values captured yet for this section.
[Ollama]
OLLAMA_HOST = http://fairyland.prole.org:11434
OLLAMA_SERVERS = fairyland.prole.org:11434,k3d.localhost:11434,morgoth.prole.org:11434
OLLAMA_SERVER_HOST = fairyland.prole.org
OLLAMA_SERVER_PORT = 11434
[Optional Features]
GITOPS_ENABLED = False
[GitOps]
; No configuration values captured yet for this section.
[Database Creation]
; No configuration values captured yet for this section.
[Initialize Cluster]
; No configuration values captured yet for this section.
[Dev Cluster (k3d)]
CLUSTER_ENV = dev
DISPLAY_NAME = knoe-dev-cluster
KUBECTL_CONTEXT =
MODE = k3d
[Service Cluster (k3s)]
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
MODE = k3s
PIPELINE_URL = http://127.0.0.1:8080
[Prod Cluster (k8s)]
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
CLUSTER_ENV = prole-prod-cluster
DISPLAY_NAME = prole-prod-cluster
MODE = k8s
PIPELINE_URL = http://127.0.0.1:8080
[Docker Build]
; No configuration values captured yet for this section.
[Initialization Scripts]
; No configuration values captured yet for this section.
[Deployment]
MODE = k3s
TARGET = prole-service-cluster
[Install]
; No configuration values captured yet for this section.

1
conf/service/prole.cfg Symbolic link
View File

@ -0,0 +1 @@
service/prole.cfg

View File

@ -0,0 +1,195 @@
; Prole Master Configuration File
; Generated by install.py on 2026-03-22 16:46:41
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
; User-editable values; derived values below reference these by default.
; No user values captured yet for this section.
[Inputs]
; Screen-scoped inputs used for unattended replays (-S)
build.deploy_env = Dev
build.run_build = false
database_options.distribution = percona
database_options.ext.dblink = true
database_options.ext.pg_buffercache = true
database_options.ext.pg_freespacemap = true
database_options.ext.pg_repack = true
database_options.ext.pg_stat_monitor = true
database_options.ext.pg_stat_statements = true
database_options.ext.pgaudit = true
database_options.ext.pgbadger = true
database_options.ext.pgcrypto = true
database_options.ext.pgrowlocks = true
database_options.ext.pgvector = true
database_options.ext.postgis = true
database_options.ext.postgres_fdw = true
database_options.version_type = v18
dependencies.ansible.install = true
dependencies.auto_install_missing = true
dependencies.brew.install = true
dependencies.docker.install = true
dependencies.k3d.install = true
dependencies.kubectl.install = true
dependencies.kubectx.install = true
dependencies.opentofu.install = true
dependencies.python.install = true
dependencies.verify_all = false
disk_selection.disk_type = local
disk_selection.local_path = /Users/chrisfu/dev/prole/prole-tools-app/dist
disk_selection.removable_mount =
env_setup.NAMESPACE = ${NAMESPACE}
env_setup.PROLE_CONF = /Users/chrisfu/dev/prole/conf
env_setup.PROLE_DATA = /Users/chrisfu/dev/prole/data
env_setup.PROLE_HOME = /Users/chrisfu/dev/prole
env_setup.PROLE_LOGS = /Users/chrisfu/dev/prole/logs
env_setup.PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
init_cluster.argocd_enabled = false
init_cluster.at_rest_encryption_enabled = true
init_cluster.cluster_env = service
init_cluster.deployment_target = prole-service-cluster
init_cluster.gitops_enabled = false
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k3s
init_cluster.start_cluster = 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_namespace = ${NAMESPACE}
init_password.db_password = ${PROLE_SECRET:v1:LtY6hRqgZOeWP8OX:wWt4cT0CS2c4GZPB1RpSAo9cIFhLYvhw}
init_password.db_password_confirm = ${PROLE_SECRET:v1:LtY6hRqgZOeWP8OX:wWt4cT0CS2c4GZPB1RpSAo9cIFhLYvhw}
init_password.db_username = root
init_password.generate_ssh_key = true
init_scripts.run_scripts = true
kerberos_config.enabled = true
kerberos_config.init_authority = false
kerberos_config.kdc = 10.0.0.3
kerberos_config.password = ${PROLE_SECRET:v1:71ROfYyYysQDJDpX:0K4cB3p9nnMb1yi2OImqUm-pa9Zg21JYTmYHzYB_7czTKMQJ}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false
kerberos_config.user = administrator
network_scan.run = true
ollama_config.model =
ollama_config.server_host = fairyland.prole.org
ollama_config.server_port = 11434
[Global]
; Variables used by name in more than one place or assumed global scope
ARGOCD_NAMESPACE = argocd
CLUSTER_ENV = service
DB_HOST_PORT = 5432
DB_PASSWORD = ${PROLE_SECRET:v1:NmgNZUhCsk-p0_DF:VB1tKREj45eEWcnN1GAlcGV6b_Lrl9sx}
DEPLOYMENT_MODE = k3s
DEPLOYMENT_TARGET = prole-service-cluster
DOCKER_IMPORT_DIR =
DOCKER_PRELOAD = false
NAMESPACE = prole-db
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_DB_USER = root
PROLE_HOME = /Users/chrisfu/dev/prole
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
REGISTRY_NAMESPACE = knoe-system
SERVICE_NAMESPACE = knoe-system
[Welcome]
; No configuration values captured yet for this section.
[Dependencies]
STATUS = All installed
[Network]
AD_DC_HOST = myrddin.prole.org
AD_DC_IP = 10.0.0.3
ANSIBLE_DOMAIN = prole.org
ANSIBLE_INFRASTRUCTURE = /Users/chrisfu/dev/prole/infrastructure
ANSIBLE_INVENTORY = /Users/chrisfu/dev/prole/infrastructure/inventory
ANSIBLE_REALM = PROLE.ORG
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","merlin.prole.org":"10.0.0.6","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_servers":["myrddin.prole.org"],"k3s_agents":["pi.prole.org","merlin.prole.org"],"k3s_hosts:children":["k3s_servers","k3s_agents"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"ssl_hosts":["myrddin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"]},"hosts":{"merlin":"10.0.0.6","merlin.prole.org":"10.0.0.6","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["k3s_agents","k3s_servers","mariadb_primary","mariadb_replica"]}
KDC_ANSIBLE_DETECTED = 10.0.0.3
KDC_AUTO_DETECTED = 10.0.0.3
KERBEROS_AUTO_ENABLED = True
[Port Forwards]
PORT_FORWARD_K3S_MAPPING_1 = id=argocd;namespace=argocd;target=svc/argocd-server;address=0.0.0.0;hostPort=8081;servicePort=80;protocol=TCP;description=ArgoCD
PORT_FORWARD_K3S_MAPPING_2 = id=garage;namespace=knoe-system;target=svc/garage;address=0.0.0.0;hostPort=3900;servicePort=3900;protocol=TCP;description=Garage S3
PORT_FORWARD_K3S_MAPPING_3 = id=openbao;namespace=knoe-system;target=svc/openbao;address=0.0.0.0;hostPort=8200;servicePort=8200;protocol=TCP;description=OpenBao
PORT_FORWARD_K3S_MAPPING_4 = id=opentofu;namespace=knoe-system;target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
PORT_FORWARD_K3S_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_K3S_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_K3S_MAPPING_7 = id=prometheus;namespace=monitoring;target=svc/kps-kube-prometheus-stack-prometheus;address=127.0.0.1;hostPort=9090;servicePort=9090;protocol=TCP;description=Prometheus UI
PORT_FORWARD_K3S_MAPPING_8 = id=grafana;namespace=monitoring;target=svc/kps-grafana;address=0.0.0.0;hostPort=3000;servicePort=80;protocol=TCP;description=Grafana UI
[System Environment]
NAMESPACE = prole-db
PROLE_CONF = /Users/chrisfu/dev/prole/conf
PROLE_DATA = /Users/chrisfu/dev/prole/data
PROLE_HOME = /Users/chrisfu/dev/prole
PROLE_LOGS = /Users/chrisfu/dev/prole/logs
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
[Monitoring]
; No configuration values captured yet for this section.
[Kerberos Authentication]
; No configuration values captured yet for this section.
[Ollama]
OLLAMA_HOST = http://fairyland.prole.org:11434
OLLAMA_SERVERS = fairyland.prole.org:11434,k3d.localhost:11434,morgoth.prole.org:11434
OLLAMA_SERVER_HOST = fairyland.prole.org
OLLAMA_SERVER_PORT = 11434
[Optional Features]
AT_REST_ENCRYPTION_ENABLED = true
GITOPS_ENABLED = false
KERBEROS_ENABLED = true
SUPABASE_ENABLED = false
[GitOps]
[Database Creation]
DB_NAME = prole-db
DB_USER = root
[Initialize Cluster]
; No configuration values captured yet for this section.
[Dev Cluster (k3d)]
CLUSTER_ENV = k3d-knoe-dev-cluster
DISPLAY_NAME = knoe-dev-cluster
KUBECTL_CONTEXT = service
MODE = k3d
[Service Cluster (k3s)]
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
MODE = k3s
PIPELINE_URL = http://127.0.0.1:8080
[Prod Cluster (k8s)]
ARTIFACTS_DIR =
CLUSTER_ENV = prole-prod-cluster
DISPLAY_NAME = prole-prod-cluster
MODE = k8s
PIPELINE_URL = http://127.0.0.1:8080
[Docker Build]
; No configuration values captured yet for this section.
[Initialization Scripts]
; No configuration values captured yet for this section.
[Deployment]
MODE = k3s
TARGET = prole-service-cluster
[Install]
STATUS = Failed

View File

@ -0,0 +1,195 @@
; Prole Master Configuration File
; Generated by install.py on 2026-03-22 20:46:43
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
; User-editable values; derived values below reference these by default.
; No user values captured yet for this section.
[Inputs]
; Screen-scoped inputs used for unattended replays (-S)
build.deploy_env = Dev
build.run_build = false
database_options.distribution = percona
database_options.ext.dblink = true
database_options.ext.pg_buffercache = true
database_options.ext.pg_freespacemap = true
database_options.ext.pg_repack = true
database_options.ext.pg_stat_monitor = true
database_options.ext.pg_stat_statements = true
database_options.ext.pgaudit = true
database_options.ext.pgbadger = true
database_options.ext.pgcrypto = true
database_options.ext.pgrowlocks = true
database_options.ext.pgvector = true
database_options.ext.postgis = true
database_options.ext.postgres_fdw = true
database_options.version_type = v18
dependencies.ansible.install = true
dependencies.auto_install_missing = true
dependencies.brew.install = true
dependencies.docker.install = true
dependencies.k3d.install = true
dependencies.kubectl.install = true
dependencies.kubectx.install = true
dependencies.opentofu.install = true
dependencies.python.install = true
dependencies.verify_all = false
disk_selection.disk_type = local
disk_selection.local_path = /Users/chrisfu/dev/prole/prole-tools-app/dist
disk_selection.removable_mount =
env_setup.NAMESPACE = ${NAMESPACE}
env_setup.PROLE_CONF = /Users/chrisfu/dev/prole/conf
env_setup.PROLE_DATA = /Users/chrisfu/dev/prole/data
env_setup.PROLE_HOME = /Users/chrisfu/dev/prole
env_setup.PROLE_LOGS = /Users/chrisfu/dev/prole/logs
env_setup.PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
init_cluster.argocd_enabled = false
init_cluster.at_rest_encryption_enabled = true
init_cluster.cluster_env = service
init_cluster.deployment_target = prole-service-cluster
init_cluster.gitops_enabled = false
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k3s
init_cluster.start_cluster = 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_namespace = ${NAMESPACE}
init_password.db_password = ${PROLE_SECRET:v1:vz4oaR5QA3zIJWDs:blYR6vqpY0e_GqGWFufDwlY4yxXWXrGF}
init_password.db_password_confirm = ${PROLE_SECRET:v1:vz4oaR5QA3zIJWDs:blYR6vqpY0e_GqGWFufDwlY4yxXWXrGF}
init_password.db_username = root
init_password.generate_ssh_key = true
init_scripts.run_scripts = true
kerberos_config.enabled = true
kerberos_config.init_authority = false
kerberos_config.kdc = 10.0.0.3
kerberos_config.password = ${PROLE_SECRET:v1:71ROfYyYysQDJDpX:0K4cB3p9nnMb1yi2OImqUm-pa9Zg21JYTmYHzYB_7czTKMQJ}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false
kerberos_config.user = administrator
network_scan.run = true
ollama_config.model =
ollama_config.server_host = fairyland.prole.org
ollama_config.server_port = 11434
[Global]
; Variables used by name in more than one place or assumed global scope
ARGOCD_NAMESPACE = argocd
CLUSTER_ENV = service
DB_HOST_PORT = 5432
DB_PASSWORD = ${PROLE_SECRET:v1:NmgNZUhCsk-p0_DF:VB1tKREj45eEWcnN1GAlcGV6b_Lrl9sx}
DEPLOYMENT_MODE = k3s
DEPLOYMENT_TARGET = prole-service-cluster
DOCKER_IMPORT_DIR =
DOCKER_PRELOAD = false
NAMESPACE = prole-db
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_DB_USER = root
PROLE_HOME = /Users/chrisfu/dev/prole
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
PROLE_OPENTOFU_URL = http://myrddin.prole.org:8080
REGISTRY_NAMESPACE = knoe-system
SERVICE_NAMESPACE = knoe-system
[Welcome]
; No configuration values captured yet for this section.
[Dependencies]
STATUS = All installed
[Network]
AD_DC_HOST = myrddin.prole.org
AD_DC_IP = 10.0.0.3
ANSIBLE_DOMAIN = prole.org
ANSIBLE_INFRASTRUCTURE = /Users/chrisfu/dev/prole/infrastructure
ANSIBLE_INVENTORY = /Users/chrisfu/dev/prole/infrastructure/inventory
ANSIBLE_REALM = PROLE.ORG
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","merlin.prole.org":"10.0.0.6","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_servers":["myrddin.prole.org"],"k3s_agents":["pi.prole.org","merlin.prole.org"],"k3s_hosts:children":["k3s_servers","k3s_agents"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"ssl_hosts":["myrddin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"]},"hosts":{"merlin":"10.0.0.6","merlin.prole.org":"10.0.0.6","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["k3s_agents","k3s_servers","mariadb_primary","mariadb_replica"]}
KDC_ANSIBLE_DETECTED = 10.0.0.3
KDC_AUTO_DETECTED = 10.0.0.3
KERBEROS_AUTO_ENABLED = True
[Port Forwards]
PORT_FORWARD_K3S_MAPPING_1 = id=argocd;namespace=argocd;target=svc/argocd-server;address=0.0.0.0;hostPort=8081;servicePort=80;protocol=TCP;description=ArgoCD
PORT_FORWARD_K3S_MAPPING_2 = id=garage;namespace=knoe-system;target=svc/garage;address=0.0.0.0;hostPort=3900;servicePort=3900;protocol=TCP;description=Garage S3
PORT_FORWARD_K3S_MAPPING_3 = id=openbao;namespace=knoe-system;target=svc/openbao;address=0.0.0.0;hostPort=8200;servicePort=8200;protocol=TCP;description=OpenBao
PORT_FORWARD_K3S_MAPPING_4 = id=opentofu;namespace=knoe-system;target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
PORT_FORWARD_K3S_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_K3S_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_K3S_MAPPING_7 = id=prometheus;namespace=monitoring;target=svc/kps-kube-prometheus-stack-prometheus;address=127.0.0.1;hostPort=9090;servicePort=9090;protocol=TCP;description=Prometheus UI
PORT_FORWARD_K3S_MAPPING_8 = id=grafana;namespace=monitoring;target=svc/kps-grafana;address=0.0.0.0;hostPort=3000;servicePort=80;protocol=TCP;description=Grafana UI
[System Environment]
NAMESPACE = prole-db
PROLE_CONF = /Users/chrisfu/dev/prole/conf
PROLE_DATA = /Users/chrisfu/dev/prole/data
PROLE_HOME = /Users/chrisfu/dev/prole
PROLE_LOGS = /Users/chrisfu/dev/prole/logs
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
[Monitoring]
; No configuration values captured yet for this section.
[Kerberos Authentication]
; No configuration values captured yet for this section.
[Ollama]
OLLAMA_HOST = http://fairyland.prole.org:11434
OLLAMA_SERVERS = fairyland.prole.org:11434,k3d.localhost:11434,morgoth.prole.org:11434
OLLAMA_SERVER_HOST = fairyland.prole.org
OLLAMA_SERVER_PORT = 11434
[Optional Features]
AT_REST_ENCRYPTION_ENABLED = true
GITOPS_ENABLED = false
KERBEROS_ENABLED = true
SUPABASE_ENABLED = false
[GitOps]
[Database Creation]
DB_NAME = prole-db
DB_USER = root
[Initialize Cluster]
; No configuration values captured yet for this section.
[Dev Cluster (k3d)]
CLUSTER_ENV = k3d-knoe-dev-cluster
DISPLAY_NAME = knoe-dev-cluster
KUBECTL_CONTEXT = service
MODE = k3d
[Service Cluster (k3s)]
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${PROLE_SECRET:v1:_r5tIPt5VZcr0SrV:e2_sHY49gG2i-vhQZr-42M1kekln4D4ZHq6tJrDRCn8f4KOVSn59rndLt4PuHmsebPSPP4tGHZV0MaVtdBMm4RJmk9IXbqV7BjSNruK2SXi62NOXIIUWZGNcVIOFjmv7EovfOifo3nye8142zKDspTeyo5Y2KY6qGLtS2g==}
MODE = k3s
PIPELINE_URL = http://myrddin.prole.org:8080
[Prod Cluster (k8s)]
ARTIFACTS_DIR =
CLUSTER_ENV = prole-prod-cluster
DISPLAY_NAME = prole-prod-cluster
MODE = k8s
PIPELINE_URL = http://myrddin.prole.org:8080
[Docker Build]
; No configuration values captured yet for this section.
[Initialization Scripts]
; No configuration values captured yet for this section.
[Deployment]
MODE = k3s
TARGET = prole-service-cluster
[Install]
STATUS = Failed

View File

@ -71,8 +71,8 @@ spec:
resources:
requests:
storage: 29Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: garage
prole.storage/volume: d001
synology.storage/role: garage
synology.storage/volume: d001

View File

@ -1,17 +1,17 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-data
name: synology-iscsi-d001-data
labels:
prole.storage/role: data
prole.storage/volume: d001
synology.storage/role: data
synology.storage/volume: d001
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/data
@ -27,17 +27,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-wal
name: synology-iscsi-d001-wal
labels:
prole.storage/role: wal
prole.storage/volume: d001
synology.storage/role: wal
synology.storage/volume: d001
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/wal
@ -53,17 +53,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-garage
name: synology-iscsi-d001-garage
labels:
prole.storage/role: garage
prole.storage/volume: d001
synology.storage/role: garage
synology.storage/volume: d001
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/garage
@ -79,17 +79,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d002-data
name: synology-iscsi-d002-data
labels:
prole.storage/role: data
prole.storage/volume: d002
synology.storage/role: data
synology.storage/volume: d002
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d002/data
@ -105,17 +105,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d002-wal
name: synology-iscsi-d002-wal
labels:
prole.storage/role: wal
prole.storage/volume: d002
synology.storage/role: wal
synology.storage/volume: d002
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d002/wal
@ -131,17 +131,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d003-data
name: synology-iscsi-d003-data
labels:
prole.storage/role: data
prole.storage/volume: d003
synology.storage/role: data
synology.storage/volume: d003
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d003/data
@ -157,17 +157,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d003-wal
name: synology-iscsi-d003-wal
labels:
prole.storage/role: wal
prole.storage/volume: d003
synology.storage/role: wal
synology.storage/volume: d003
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d003/wal
@ -183,17 +183,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d003-grafana
name: synology-iscsi-d003-grafana
labels:
prole.storage/role: grafana
prole.storage/volume: d003
synology.storage/role: grafana
synology.storage/volume: d003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d003/grafana
@ -209,17 +209,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d002-prometheus
name: synology-iscsi-d002-prometheus
labels:
prole.storage/role: prometheus
prole.storage/volume: d002
synology.storage/role: prometheus
synology.storage/volume: d002
spec:
capacity:
storage: 20Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d002/prometheus
@ -235,17 +235,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-openbao
name: synology-iscsi-d001-openbao
labels:
prole.storage/role: openbao
prole.storage/volume: d001
synology.storage/role: openbao
synology.storage/volume: d001
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/openbao

View File

@ -51,7 +51,7 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: openbao
synology.storage/role: openbao

View File

@ -4,27 +4,23 @@ metadata:
name: prole-db
spec:
instances: 3
imageName: k3d-prole-registry:5000/knoe-db:18-138
imageName: k3d-prole-registry:5000/knoe-db:18-140
postgresUID: 100
postgresGID: 101
maxSyncReplicas: 1
affinity:
# CloudNativePG uses its own affinity schema (not corev1.Affinity). See: `kubectl explain cluster.spec.affinity`
enablePodAntiAffinity: true
podAntiAffinityType: required
podAntiAffinityType: preferred
topologyKey: kubernetes.io/hostname
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: prole.org/node-role
- key: node.kubernetes.io/instance-type
operator: In
values:
- db
- k3s
postgresql:
parameters:
@ -92,10 +88,10 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: data
synology.storage/role: data
walStorage:
size: 1Gi
pvcTemplate:
@ -104,9 +100,9 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: wal
synology.storage/role: wal
monitoring:
enablePodMonitor: false

View File

@ -1,7 +1,7 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: prole-iscsi
name: synology-iscsi
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer

View File

@ -101,9 +101,9 @@ CERT_MANAGER_MANIFEST_URL=${CERT_MANAGER_MANIFEST_URL:-}
CERT_MANAGER_FALLBACK_VERSION=${CERT_MANAGER_FALLBACK_VERSION:-1.19.3}
# Protected storage requirements (k3s only)
PROLE_CNPG_STORAGE_CLASS=${PROLE_CNPG_STORAGE_CLASS:-prole-iscsi}
PROLE_PROTECTED_DATA_PATH=${PROLE_PROTECTED_DATA_PATH:-/prole/d001/data}
PROLE_PROTECTED_WAL_PATH=${PROLE_PROTECTED_WAL_PATH:-/prole/d001/wal}
PROLE_CNPG_STORAGE_CLASS=${PROLE_CNPG_STORAGE_CLASS:-synology-iscsi}
PROLE_PROTECTED_DATA_PATH=${PROLE_PROTECTED_DATA_PATH:-/synology/d001/data}
PROLE_PROTECTED_WAL_PATH=${PROLE_PROTECTED_WAL_PATH:-/synology/d001/wal}
if [[ "$ACTION" != "deploy" && "$ACTION" != "rollout" && "$ACTION" != "force-rollout" ]]; then
if [[ -n "${2:-}" ]]; then
@ -265,9 +265,9 @@ validate_cnpg_manifest_storage() {
wal_sc="$v"
fi
fi
if [[ -n "$block" && "$line" =~ prole\.storage/role: ]]; then
if [[ -n "$block" && "$line" =~ synology\.storage/role: ]]; then
local r
r=$(printf '%s' "$line" | sed -E 's/^.*prole\.storage\/role:[[:space:]]*//; s/[[:space:]]+$//')
r=$(printf '%s' "$line" | sed -E 's/^.*synology\.storage\/role:[[:space:]]*//; s/[[:space:]]+$//')
if [[ "$block" == "storage" && -z "$data_role" ]]; then
data_role="$r"
elif [[ "$block" == "wal" && -z "$wal_role" ]]; then
@ -283,7 +283,7 @@ validate_cnpg_manifest_storage() {
fi
if [[ "$data_role" != "data" || "$wal_role" != "wal" ]]; then
echo "ERROR: CNPG Cluster manifest must include explicit PV selectors for Prole storage (prole.storage/role=data and prole.storage/role=wal)." >&2
echo "ERROR: CNPG Cluster manifest must include explicit PV selectors for Prole storage (synology.storage/role=data and synology.storage/role=wal)." >&2
echo "Got selector roles: data='${data_role:-<missing>}', wal='${wal_role:-<missing>}'" >&2
return 1
fi
@ -307,8 +307,8 @@ validate_cnpg_runtime_storage() {
local data_sc wal_sc data_role wal_role
data_sc=$(printf '%s' "$cluster_json" | jq -r '.spec.storage.pvcTemplate.storageClassName // empty')
wal_sc=$(printf '%s' "$cluster_json" | jq -r '.spec.walStorage.pvcTemplate.storageClassName // empty')
data_role=$(printf '%s' "$cluster_json" | jq -r '.spec.storage.pvcTemplate.selector.matchLabels["prole.storage/role"] // empty')
wal_role=$(printf '%s' "$cluster_json" | jq -r '.spec.walStorage.pvcTemplate.selector.matchLabels["prole.storage/role"] // empty')
data_role=$(printf '%s' "$cluster_json" | jq -r '.spec.storage.pvcTemplate.selector.matchLabels["synology.storage/role"] // empty')
wal_role=$(printf '%s' "$cluster_json" | jq -r '.spec.walStorage.pvcTemplate.selector.matchLabels["synology.storage/role"] // empty')
if [[ "$data_sc" != "$sc" || "$wal_sc" != "$sc" ]]; then
echo "ERROR: Live CNPG Cluster storageClassName must be '${sc}' for both data and walStorage (got data='${data_sc:-<missing>}', wal='${wal_sc:-<missing>}')." >&2
@ -366,9 +366,9 @@ validate_cnpg_runtime_storage() {
return 1
fi
case "$pv_path" in
/prole/*) ;;
/synology/*) ;;
*)
echo "ERROR: Bound PV '${pv}' path '${pv_path}' is not under /prole/. Refusing deployment." >&2
echo "ERROR: Bound PV '${pv}' path '${pv_path}' is not under /synology/. Refusing deployment." >&2
return 1
;;
esac
@ -448,16 +448,20 @@ wait_for_apiserver_ready() {
}
ensure_namespace() {
if [[ -z "${PROLE_NAMESPACE:-}" ]]; then
echo "ERROR: PROLE_NAMESPACE is empty. Set NAMESPACE in conf/prole.cfg." >&2
exit 1
fi
require_namespace_configured
if ! kubectl get namespace "$PROLE_NAMESPACE" >/dev/null 2>&1; then
echo "Creating namespace '$PROLE_NAMESPACE' ..."
kubectl create namespace "$PROLE_NAMESPACE" >/dev/null 2>&1 || true
fi
}
require_namespace_configured() {
if [[ -z "${PROLE_NAMESPACE:-}" ]]; then
echo "ERROR: PROLE_NAMESPACE is empty. Set NAMESPACE in conf/prole.cfg." >&2
exit 1
fi
}
kubectl_apply_retry() {
local namespace="${1:-}"
local attempts=${2:-8}
@ -540,6 +544,17 @@ get_latest_image() {
echo "knoe-db:${pg_version}-${release}"
}
_desired_cnpg_image() {
local image
if [[ "$VERSION" == "latest" || -z "$VERSION" ]]; then
image=$(get_latest_image)
else
image="knoe-db:$VERSION"
fi
image=$(resolve_cnpg_image "$image")
printf '%s' "$image"
}
_cnpg_current_image_name() {
if ! kubectl -n "$NAMESPACE" get cluster "$CNPG_CLUSTER_NAME" >/dev/null 2>&1; then
echo ""
@ -1287,6 +1302,151 @@ apply_prole_manifest_file() {
return 1
}
_prole_k3s_registry_host_for_cache() {
# Best-effort host:port for the k3s registry, used as an optional cache source in k3d mode.
# Intentionally avoids k3d registry names and localhost-ish endpoints.
local r="${PROLE_K3S_REGISTRY:-${K3S_REGISTRY_HOST:-${LOCAL_REGISTRY_INTERNAL:-${LOCAL_REGISTRY:-}}}}"
r="${r#http://}"
r="${r#https://}"
case "${r}" in
"")
r="myrddin.prole.org:5000"
;;
localhost:5000|127.0.0.1:5000|*.localhost|*.localhost:5000)
r="myrddin.prole.org:5000"
;;
k3d-*|*/k3d-*)
r="myrddin.prole.org:5000"
;;
esac
printf '%s' "$r"
}
_docker_build_knoe_db_image() {
# Build the knoe-db image with BuildKit and optional cache source.
# Args: <tag> <context_dir> [cache_ref]
local tag="$1"
local context_dir="$2"
local cache_ref="${3:-}"
local ssh_args=()
if [[ -n "${SSH_AUTH_SOCK:-}" ]]; then
ssh_args+=(--ssh default)
fi
if docker buildx version >/dev/null 2>&1; then
# Prefer an isolated docker-container builder to avoid host snapshot/cache corruption.
local builder_name="${PROLE_BUILDX_BUILDER:-prole-buildkit}"
if ! docker buildx inspect "$builder_name" >/dev/null 2>&1; then
docker buildx create --name "$builder_name" --driver docker-container --use >/dev/null 2>&1 || true
else
docker buildx use "$builder_name" >/dev/null 2>&1 || true
fi
local cache_args=()
if [[ -n "$cache_ref" ]]; then
cache_args+=(--cache-from "type=registry,ref=${cache_ref}")
fi
if docker buildx build \
--pull \
--tag "$tag" \
--load \
"${ssh_args[@]}" \
"${cache_args[@]}" \
"$context_dir"; then
return 0
fi
fi
# Fallback to docker build (still BuildKit-enabled); cache-from here only works if the cache image is present locally.
local cache_from_args=()
if [[ -n "$cache_ref" ]]; then
docker pull "$cache_ref" >/dev/null 2>&1 || true
cache_from_args+=(--cache-from "$cache_ref")
fi
DOCKER_BUILDKIT=1 docker build \
--pull \
"${ssh_args[@]}" \
"${cache_from_args[@]}" \
-t "$tag" \
"$context_dir"
}
# Import a Docker image directly to each k3s node via SSH + 'k3s ctr images import'.
# Used as a last-resort fallback when the in-cluster registry is unreachable and
# the kubectl port-forward push also fails (e.g. registry service not yet deployed).
# Respects optional PROLE_SSH_KEY (path to private key) and K3S_SSH_USER env vars.
_import_image_to_k3s_nodes() {
local image="$1"
local ssh_user="${K3S_SSH_USER:-${ANSIBLE_USER:-ansible}}"
local ssh_key_args=()
# Auto-detect the SSH key: explicit override → ~/.ssh/id_ed25519_ansible (ansible user key)
# → prole ssh-keys dir → ssh-agent default.
local _auto_key_ansible="${HOME}/.ssh/id_ed25519_ansible"
local _auto_key_prole="${PROLE_HOME:-$SCRIPT_DIR/..}/ssh-keys/id_prole_ed25519"
local _ssh_key="${PROLE_SSH_KEY:-${K3S_SSH_KEY:-}}"
if [[ -z "$_ssh_key" && -f "$_auto_key_ansible" ]]; then
_ssh_key="$_auto_key_ansible"
elif [[ -z "$_ssh_key" && -f "$_auto_key_prole" ]]; then
_ssh_key="$_auto_key_prole"
fi
if [[ -n "$_ssh_key" && -f "$_ssh_key" ]]; then
ssh_key_args=(-i "$_ssh_key")
fi
local nodes=()
mapfile -t nodes < <(kubectl get nodes \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
if [[ ${#nodes[@]} -eq 0 ]]; then
echo " WARN: No k3s nodes found for direct image import." >&2
return 1
fi
local tar_file
tar_file=$(mktemp /tmp/prole-img-XXXXXX.tar)
echo " Saving image '$image' for k3s node import ..."
if ! docker save "$image" -o "$tar_file" 2>/dev/null; then
echo " WARN: docker save failed for '$image'." >&2
rm -f "$tar_file" 2>/dev/null || true
return 1
fi
local imported=0
for node in "${nodes[@]}"; do
# Skip nodes that already have the image in k3s containerd.
if ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=10 \
"${ssh_key_args[@]}" "${ssh_user}@${node}" \
"sudo k3s ctr images ls 2>/dev/null | grep -qF '${plain_image}'" 2>/dev/null; then
echo " ✓ Image already in k3s containerd on '$node'; skipping import."
imported=$((imported + 1))
continue
fi
echo " Importing '$image' to k3s node '$node' ..."
if ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=30 \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
"${ssh_key_args[@]}" "${ssh_user}@${node}" \
"sudo k3s ctr images import -" < "$tar_file" >/dev/null 2>&1; then
echo " ✓ Image imported on node '$node'."
imported=$((imported + 1))
else
echo " WARN: Failed to import image on node '$node'; skipping." >&2
fi
done
rm -f "$tar_file" 2>/dev/null || true
if [[ $imported -eq 0 ]]; then
echo " WARN: Image import failed on all k3s nodes." >&2
return 1
fi
echo " ✓ Image '$image' imported on $imported/${#nodes[@]} k3s node(s)."
return 0
}
_push_to_k3s_registry() {
local image="$1"
local push_host="${LOCAL_REGISTRY:-${LOCAL_REGISTRY_INTERNAL:-myrddin.prole.org:5000}}"
@ -1434,6 +1594,7 @@ _ensure_prole_db_image() {
local prole_db_dir="${PROLE_DB_BUILD_CONTEXT_DIR:-${runtime_home}/build/${mode_key}/knoe-db}"
local source_db_dir="${PROLE_HOME:-$SCRIPT_DIR/..}/prole-db"
local plain_image="${image##*/}" # e.g. knoe-db:18-088
local k3s_cache_ref=""
if [[ ! -f "$prole_db_dir/Dockerfile" ]]; then
if [[ -d "$source_db_dir" ]]; then
@ -1446,6 +1607,32 @@ _ensure_prole_db_image() {
if [[ "${PROLE_MODE:-}" == "k3s" ]]; then
echo "Pre-flight: ensuring image '$image' is available in k3s registry/import path ..."
# Step 1 (k3s): if the CNPG cluster already exists and is running with this exact image
# the image is already present in k3s containerd on the nodes — skip push entirely.
local _k3s_current_image _k3s_ready
_k3s_current_image=$(kubectl get cluster "${CNPG_CLUSTER_NAME:-prole-db}" \
-n "${CNPG_NAMESPACE:-${NAMESPACE:-prole-db}}" \
-o jsonpath='{.spec.imageName}' 2>/dev/null || true)
_k3s_ready=$(kubectl get cluster "${CNPG_CLUSTER_NAME:-prole-db}" \
-n "${CNPG_NAMESPACE:-${NAMESPACE:-prole-db}}" \
-o jsonpath='{.status.readyInstances}' 2>/dev/null || true)
if [[ "$_k3s_current_image" == "$image" && "${_k3s_ready:-0}" -gt 0 ]]; then
echo " ✓ CNPG cluster already running with image '$image'; no push needed."
return 0
fi
# Step 1b: check if image already exists in the registry via a quick manifest HEAD request.
local _reg_host="${LOCAL_REGISTRY:-${LOCAL_REGISTRY_INTERNAL:-myrddin.prole.org:5000}}"
_reg_host="${_reg_host#http://}"; _reg_host="${_reg_host#https://}"
local _img_name="${plain_image%%:*}" _img_tag="${plain_image##*:}"
if command -v curl >/dev/null 2>&1; then
if curl -k -fsS -m 3 \
"https://${_reg_host}/v2/${_img_name}/manifests/${_img_tag}" >/dev/null 2>&1 \
|| curl -fsS -m 3 \
"http://${_reg_host}/v2/${_img_name}/manifests/${_img_tag}" >/dev/null 2>&1; then
echo " ✓ Image '$image' already present in registry at '${_reg_host}'; no push needed."
return 0
fi
fi
else
# Auto-detect active k3d cluster name
local cluster_name="${K3D_CLUSTER_NAME:-}"
@ -1454,6 +1641,13 @@ _ensure_prole_db_image() {
fi
cluster_name="${cluster_name:-knoe-dev-cluster}"
# In k3d mode, optionally consult the k3s registry as a cache/source of truth before building.
local k3s_host
k3s_host=$(_prole_k3s_registry_host_for_cache)
if [[ -n "${k3s_host:-}" ]]; then
k3s_cache_ref="${k3s_host}/${plain_image}"
fi
echo "Pre-flight: verifying image '$image' is available in k3d cluster '$cluster_name' ..."
# Step 1: check if already present in k3d containerd with matching digest
@ -1526,6 +1720,18 @@ _ensure_prole_db_image() {
return $?
fi
# Step 3b (k3d only): try to reuse a previously-built image from the k3s registry.
# If present, retag it to the desired image name and push/import into the k3d cluster.
if [[ "${PROLE_MODE:-}" == "k3d" && -n "${k3s_cache_ref:-}" && "${k3s_cache_ref}" != "${image}" ]]; then
if docker image inspect "$k3s_cache_ref" >/dev/null 2>&1 || docker pull "$k3s_cache_ref" >/dev/null 2>&1; then
echo " ✓ Found '$plain_image' in k3s registry (${k3s_cache_ref}); re-tagging for k3d as '$image' ..."
docker tag "$k3s_cache_ref" "$image" 2>/dev/null || true
_push_to_k3d_registry "$image" "$cluster_name"
return $?
fi
echo " (cache) '$plain_image' not present in k3s registry (${k3s_cache_ref}); building locally ..."
fi
# Step 4: image not found anywhere — build from source then push + import
if [[ ! -f "$prole_db_dir/Dockerfile" ]]; then
echo "ERROR: Dockerfile not found in '$prole_db_dir'; cannot build knoe-db image." >&2
@ -1537,7 +1743,7 @@ _ensure_prole_db_image() {
echo " Image '$image' not found in k3d, Docker daemon, or docker-import dir."
fi
echo " Building knoe-db image from '$prole_db_dir' ..."
if ! docker build -t "$plain_image" "$prole_db_dir"; then
if ! _docker_build_knoe_db_image "$plain_image" "$prole_db_dir" "${k3s_cache_ref:-}"; then
echo "ERROR: docker build failed for image '$plain_image'." >&2
return 1
fi
@ -2538,14 +2744,14 @@ restore_cnpg_cluster() {
}
recycle_released_prole_iscsi_pvs() {
# StorageClass `prole-iscsi` uses `Retain` PV reclaim policy. After a CNPG reset, PVs can remain
# StorageClass `synology-iscsi` uses `Retain` PV reclaim policy. After a CNPG reset, PVs can remain
# in `Released` with a stale `claimRef`, which prevents new PVCs (same names) from binding.
# In that case, CNPG init jobs remain `Pending` with "didn't find available persistent volumes".
echo "Recycling Released prole-iscsi PVs (clearing stale claimRefs) for namespace '$NAMESPACE' ..."
echo "Recycling Released synology-iscsi PVs (clearing stale claimRefs) for namespace '$NAMESPACE' ..."
local pvs
pvs=$(kubectl get pv -o json \
| jq -r --arg ns "$NAMESPACE" '.items[]
| select(.spec.storageClassName == "prole-iscsi")
| select(.spec.storageClassName == "synology-iscsi")
| select(.status.phase == "Released")
| select((.spec.claimRef.namespace // "") == $ns)
| .metadata.name' 2>/dev/null || true)
@ -2827,8 +3033,22 @@ get_latest_cnpg_version() {
initialize() {
ensure_tools
ensure_namespace
require_namespace_configured
wait_for_apiserver_ready 180
local desired_image current_image
desired_image=$(_desired_cnpg_image)
current_image=$(_cnpg_current_image_name)
if [[ -n "$current_image" && "$current_image" == "$desired_image" ]]; then
echo "✓ Namespace '$NAMESPACE' already contains CNPG cluster '$CNPG_CLUSTER_NAME' using image '$desired_image'; no re-deploy needed."
echo " (Use 'update'/'reload' if you intended to re-apply manifests.)"
return 0
fi
_enforce_namespace_image_policy "$desired_image" || return 1
ensure_namespace
attempt_backup_if_active
ensure_cnpg_operator
pin_cnpg_controller
@ -2906,6 +3126,21 @@ update_reload() {
deploy_cluster() {
ensure_tools
require_namespace_configured
wait_for_apiserver_ready 180
local image current_image_before
image=$(_desired_cnpg_image)
current_image_before=$(_cnpg_current_image_name)
if [[ -n "$current_image_before" && "$current_image_before" == "$image" ]]; then
echo "✓ Namespace '$NAMESPACE' already contains CNPG cluster '$CNPG_CLUSTER_NAME' using image '$image'; skipping deploy."
return 0
fi
_enforce_namespace_image_policy "$image" || return 1
ensure_namespace
ensure_cnpg_operator
pin_cnpg_controller
@ -2913,13 +3148,6 @@ deploy_cluster() {
ensure_prole_protected_storage
local image
if [[ "$VERSION" == "latest" || -z "$VERSION" ]]; then
image=$(get_latest_image)
else
image="knoe-db:$VERSION"
fi
image=$(resolve_cnpg_image "$image")
sync_manifest_image "$image"
generate_tls_if_missing
@ -2949,11 +3177,20 @@ deploy_cluster() {
kubectl cnpg restart "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" || true
fi
fi
if [[ "${PROLE_UPGRADE:-0}" == "1" && -n "$current_image_before" && "$current_image_before" != "$image" ]]; then
echo "Upgrade requested; forcing CNPG rollout to ensure pods pick up image '$image' ..."
if kubectl cnpg version >/dev/null 2>&1; then
kubectl cnpg restart "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" || true
else
force_rollout || true
fi
fi
}
preflight_image() {
ensure_tools
ensure_namespace
require_namespace_configured
_ensure_prole_db_image
}

View File

@ -60,7 +60,7 @@ else
fi
GARAGE_FILES=(
"$GARAGE_MANIFEST_DIR/storageclass-prole-iscsi.yaml"
"$GARAGE_MANIFEST_DIR/storageclass-synology-iscsi.yaml"
"$GARAGE_MANIFEST_DIR/iscsi-pvs.yaml"
"$GARAGE_MANIFEST_DIR/garage-configmap.yaml"
"$GARAGE_MANIFEST_DIR/garage-statefulset.yaml"
@ -133,7 +133,7 @@ k3d_cleanup_pending_pvc() {
return 0
fi
selector=$(kubectl get pvc data-garage-0 -n "$NAMESPACE" -o jsonpath='{.spec.selector}' 2>/dev/null || true)
if printf '%s' "$selector" | grep -q "prole.storage/"; then
if printf '%s' "$selector" | grep -q "synology.storage/"; then
echo "Removing pending Garage PVC with selector for k3d local-path ..."
kubectl delete statefulset "$GARAGE_NAME" -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete pvc data-garage-0 -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
@ -160,6 +160,24 @@ apply_manifests() {
continue
fi
if [[ $diff_rc -ne 1 ]]; then
# For immutable StatefulSet VolumeClaimTemplates, kubectl diff itself returns an
# error (diff_rc != 1 but contains the immutable spec error). Handle it here.
if [[ "$(basename "$f")" == "garage-statefulset.yaml" ]] \
&& echo "$diff_out" | grep -q "updates to statefulset spec"; then
echo "WARN: Garage StatefulSet VolumeClaimTemplates changed (diff-stage); deleting and recreating ..."
kubectl delete statefulset "$GARAGE_NAME" -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
local _pvc_sc_d
_pvc_sc_d=$(kubectl get pvc "data-garage-0" -n "$NAMESPACE" \
-o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)
if [[ -n "$_pvc_sc_d" && "$_pvc_sc_d" != "synology-iscsi" ]]; then
echo " Removing stale PVC 'data-garage-0' (storageClass: $_pvc_sc_d) ..."
kubectl delete pvc "data-garage-0" -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
fi
printf '%s' "$rendered" | kubectl apply --validate=false -n "$NAMESPACE" -f -
GARAGE_APPLY_CHANGED=1
GARAGE_STATEFULSET_CHANGED=1
continue
fi
echo "$diff_out" >&2
exit 1
fi
@ -175,6 +193,23 @@ apply_manifests() {
&& echo "$output" | grep -q "updates to statefulset spec"; then
echo "WARN: Garage StatefulSet immutable in k3d; skipping apply."
continue
elif [[ "$(basename "$f")" == "garage-statefulset.yaml" ]] \
&& echo "$output" | grep -q "updates to statefulset spec"; then
# VolumeClaimTemplates are immutable; delete the StatefulSet (PVCs are orphaned/preserved)
# and recreate so the new storageClass name takes effect.
echo "WARN: Garage StatefulSet VolumeClaimTemplates changed; deleting and recreating ..."
kubectl delete statefulset "$GARAGE_NAME" -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
local _pvc_sc
_pvc_sc=$(kubectl get pvc "data-garage-0" -n "$NAMESPACE" \
-o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)
if [[ -n "$_pvc_sc" && "$_pvc_sc" != "synology-iscsi" ]]; then
echo " Removing stale PVC 'data-garage-0' (storageClass: $_pvc_sc) ..."
kubectl delete pvc "data-garage-0" -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
fi
printf '%s' "$rendered" | kubectl apply --validate=false -n "$NAMESPACE" -f -
GARAGE_APPLY_CHANGED=1
GARAGE_STATEFULSET_CHANGED=1
continue
fi
echo "$output" >&2
exit 1
@ -283,7 +318,7 @@ dump_debug() {
}
recycle_released_prole_iscsi_pv_for_pvc() {
# StorageClass `prole-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# StorageClass `synology-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# in `Released` with a stale `claimRef`, which prevents a same-named PVC from binding.
local ns="$1"
local pvc="$2"
@ -301,7 +336,7 @@ recycle_released_prole_iscsi_pv_for_pvc() {
local pv sc rp phase claim_ns claim_name claim_uid
while IFS=$'\t' read -r pv sc rp phase claim_ns claim_name claim_uid; do
[[ "$sc" == "prole-iscsi" ]] || continue
[[ "$sc" == "synology-iscsi" ]] || continue
[[ "$rp" == "Retain" ]] || continue
[[ "$phase" == "Released" ]] || continue
[[ "$claim_ns" == "$ns" ]] || continue
@ -322,7 +357,7 @@ wait_ready() {
local initial_timeout="${PVC_REPAIR_WAIT_TIMEOUT:-30s}"
if ! kubectl rollout status statefulset/$GARAGE_NAME -n "$NAMESPACE" --timeout="$initial_timeout"; then
echo "WARN: Garage not ready after $initial_timeout; checking for Released prole-iscsi PVs with stale claimRefs ..." >&2
echo "WARN: Garage not ready after $initial_timeout; checking for Released synology-iscsi PVs with stale claimRefs ..." >&2
recycle_released_prole_iscsi_pv_for_pvc "$NAMESPACE" "data-garage-0" || true
fi

View File

@ -313,8 +313,8 @@ kind: PersistentVolume
metadata:
name: merlin-local-iscsi-${volume_id}-prometheus
labels:
prole.storage/role: prometheus
prole.storage/volume: ${volume_id}
synology.storage/role: prometheus
synology.storage/volume: ${volume_id}
spec:
capacity:
storage: 30Gi
@ -339,8 +339,8 @@ kind: PersistentVolume
metadata:
name: merlin-local-iscsi-${volume_id}-alertmanager
labels:
prole.storage/role: alertmanager
prole.storage/volume: ${volume_id}
synology.storage/role: alertmanager
synology.storage/volume: ${volume_id}
spec:
capacity:
storage: 5Gi
@ -365,8 +365,8 @@ kind: PersistentVolume
metadata:
name: merlin-local-iscsi-${volume_id}-grafana
labels:
prole.storage/role: grafana
prole.storage/volume: ${volume_id}
synology.storage/role: grafana
synology.storage/volume: ${volume_id}
spec:
capacity:
storage: 10Gi

View File

@ -423,7 +423,7 @@ iscsi_pv_names_from_manifest() {
awk '
$1=="kind:" && $2=="PersistentVolume" {in_pv=1; next}
in_pv && $1=="name:" {print $2; in_pv=0}
' "$manifest" | sed 's/["\r]//g' | grep -v '^$' || true
' "$manifest" | tr -d $'"\r' | grep -v '^$' || true
}
desired_pv_path_from_manifest() {
@ -435,7 +435,7 @@ desired_pv_path_from_manifest() {
$1=="kind:" && $2=="PersistentVolume" {is_pv=1}
is_pv && $1=="name:" && $2==pv {hit=1}
hit && $1=="path:" {print $2; exit}
' "$manifest" | sed 's/["\r]//g' | head -1 || true
' "$manifest" | tr -d $'"\r' | head -1 || true
}
desired_pv_node_from_manifest() {
@ -448,7 +448,7 @@ desired_pv_node_from_manifest() {
is_pv && $1=="name:" && $2==pv {hit=1}
hit && $1=="values:" {in_values=1; next}
in_values && $1=="-" {print $2; exit}
' "$manifest" | sed 's/["\r]//g' | head -1 || true
' "$manifest" | tr -d $'"\r' | head -1 || true
}
ensure_iscsi_pvs() {
@ -503,9 +503,9 @@ apply_k8s() {
fi
if [[ "$use_statefulset" == "1" ]]; then
# Ensure storage class and PVs exist for k3s
if [[ -f "$SCRIPT_DIR/../k8s/prole/storageclass-prole-iscsi.yaml" ]]; then
echo "Applying StorageClass 'prole-iscsi' ..."
kubectl apply -f "$SCRIPT_DIR/../k8s/prole/storageclass-prole-iscsi.yaml"
if [[ -f "$SCRIPT_DIR/../k8s/prole/storageclass-synology-iscsi.yaml" ]]; then
echo "Applying StorageClass 'synology-iscsi' ..."
kubectl apply -f "$SCRIPT_DIR/../k8s/prole/storageclass-synology-iscsi.yaml"
fi
if [[ -f "$SCRIPT_DIR/../k8s/prole/iscsi-pvs.yaml" ]]; then
ensure_iscsi_pvs "$SCRIPT_DIR/../k8s/prole/iscsi-pvs.yaml"
@ -518,6 +518,20 @@ apply_k8s() {
else
if [[ "${PROLE_MODE:-}" == "k3d" && "$output" == *"updates to statefulset spec"* ]]; then
echo "WARN: OpenBao StatefulSet immutable in k3d; skipping apply."
elif [[ "$output" == *"updates to statefulset spec"* ]]; then
# VolumeClaimTemplates are immutable; delete the StatefulSet (PVCs are orphaned/preserved)
# and recreate so the new storageClass name takes effect.
echo "WARN: OpenBao StatefulSet VolumeClaimTemplates changed; deleting and recreating ..."
kubectl delete statefulset "$OPENBAO_NAME" -n "$OPENBAO_RESOURCE_NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
local _pvc_sc
_pvc_sc=$(kubectl get pvc "data-openbao-0" -n "$OPENBAO_RESOURCE_NAMESPACE" \
-o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)
if [[ -n "$_pvc_sc" && "$_pvc_sc" != "synology-iscsi" ]]; then
echo " Removing stale PVC 'data-openbao-0' (storageClass: $_pvc_sc) ..."
kubectl delete pvc "data-openbao-0" -n "$OPENBAO_RESOURCE_NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
fi
prole_render_manifest "$SCRIPT_DIR/../k8s/prole/openbao-statefulset.yaml" \
| kubectl apply --validate=false -n "$OPENBAO_RESOURCE_NAMESPACE" -f -
else
echo "$output" >&2
return 1
@ -554,7 +568,7 @@ k3d_cleanup_pending_openbao_pvc() {
return 0
fi
selector=$(kubectl get pvc data-openbao-0 -n "$OPENBAO_RESOURCE_NAMESPACE" -o jsonpath='{.spec.selector}' 2>/dev/null || true)
if printf '%s' "$selector" | grep -q "prole.storage/"; then
if printf '%s' "$selector" | grep -q "synology.storage/"; then
echo "Removing pending OpenBao PVC with selector for k3d local-path ..."
kubectl delete statefulset "$OPENBAO_NAME" -n "$OPENBAO_RESOURCE_NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete pvc data-openbao-0 -n "$OPENBAO_RESOURCE_NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
@ -562,7 +576,7 @@ k3d_cleanup_pending_openbao_pvc() {
}
recycle_released_prole_iscsi_pv_for_pvc() {
# StorageClass `prole-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# StorageClass `synology-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# in `Released` with a stale `claimRef`, which prevents a same-named PVC from binding.
local ns="$1"
local pvc="$2"
@ -580,7 +594,7 @@ recycle_released_prole_iscsi_pv_for_pvc() {
local pv sc rp phase claim_ns claim_name claim_uid
while IFS=$'\t' read -r pv sc rp phase claim_ns claim_name claim_uid; do
[[ "$sc" == "prole-iscsi" ]] || continue
[[ "$sc" == "synology-iscsi" ]] || continue
[[ "$rp" == "Retain" ]] || continue
[[ "$phase" == "Released" ]] || continue
[[ "$claim_ns" == "$ns" ]] || continue
@ -621,13 +635,13 @@ wait_for_openbao() {
echo "Waiting for OpenBao to become ready in namespace '$wait_ns' ..."
if kubectl get statefulset/$OPENBAO_NAME -n "$wait_ns" >/dev/null 2>&1; then
if ! kubectl rollout status statefulset/$OPENBAO_NAME -n "$wait_ns" --timeout="$initial_timeout"; then
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released prole-iscsi PVs with stale claimRefs ..." >&2
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released synology-iscsi PVs with stale claimRefs ..." >&2
recycle_released_prole_iscsi_pv_for_pvc "$wait_ns" "data-openbao-0" || true
fi
kubectl rollout status statefulset/$OPENBAO_NAME -n "$wait_ns" --timeout=${ROLLOUT_TIMEOUT:-300s}
else
if ! kubectl rollout status deploy/$OPENBAO_NAME -n "$wait_ns" --timeout="$initial_timeout"; then
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released prole-iscsi PVs with stale claimRefs ..." >&2
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released synology-iscsi PVs with stale claimRefs ..." >&2
recycle_released_prole_iscsi_pv_for_pvc "$wait_ns" "data-openbao-0" || true
fi
kubectl rollout status deploy/$OPENBAO_NAME -n "$wait_ns" --timeout=${ROLLOUT_TIMEOUT:-300s}

View File

@ -246,7 +246,7 @@ deploy_service_layer() {
SERVICE_NAMESPACE="$ns" PROLE_KDC_NAMESPACE="$ns" \
"$SCRIPT_DIR/init_kdc.sh" "$kdc_action" || rc=$?
else
log "[INFO] Kerberos enabled: skipping standalone KDC deploy (KDC runs as `kdc` sidecar in `prole-auth`)."
log "[INFO] Kerberos enabled: skipping standalone KDC deploy (KDC runs as 'kdc' sidecar in 'prole-auth')."
fi
fi

View File

@ -42,8 +42,17 @@ common_core_preparse_config() {
fi
local cfg_dir
cfg_dir=$(cd "$(dirname "$COMMON_CORE_CONFIG_PATH")" && pwd)
PROLE_CONF="$cfg_dir"
export PROLE_CONF
# Preserve an already-set PROLE_CONF when it is a more-specific env subdir of
# cfg_dir (e.g. conf/service/ vs the injected-default conf/). This allows the
# parent shell / Python milestone to pass the correct env-specific conf dir and
# have it survive even when init scripts inject a generic fallback -c path.
local _current_conf="${PROLE_CONF:-}"
if [[ -n "$_current_conf" && "$_current_conf" == "${cfg_dir}/"* && -f "$_current_conf/prole.cfg" ]]; then
: # keep inherited PROLE_CONF — it is already more specific than cfg_dir
else
PROLE_CONF="$cfg_dir"
export PROLE_CONF
fi
fi
}

View File

@ -518,7 +518,7 @@ fi
# Safety: prole.cfg must override any pre-set PROLE_MODE from the environment.
# Otherwise a leaked PROLE_MODE=k3d can trigger k3d-only manifest stripping (e.g., removing
# `storageClassName: prole-iscsi`) while targeting a k3s cluster.
# `storageClassName: synology-iscsi`) while targeting a k3s cluster.
_prole_cfg_mode_hint=""
if [[ -n "${prole_mode:-}" ]]; then
_prole_cfg_mode_hint=$(prole_normalize_mode "$prole_mode")

View File

@ -65,7 +65,7 @@ def _strip_blocks(text: str) -> str:
break
block_lines.append(next_line)
j += 1
if any("prole.storage/" in bl for bl in block_lines):
if any("synology.storage/" in bl for bl in block_lines):
i = j
continue
out.extend(block_lines)
@ -78,7 +78,7 @@ def _strip_blocks(text: str) -> str:
i += 1
continue
if stripped.startswith("storageClassName:") and "prole-iscsi" in stripped:
if stripped.startswith("storageClassName:") and "synology-iscsi" in stripped:
i += 1
continue

View File

@ -71,8 +71,8 @@ spec:
resources:
requests:
storage: 29Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: garage
prole.storage/volume: d001
synology.storage/role: garage
synology.storage/volume: d001

View File

@ -1,17 +1,17 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-data
name: synology-iscsi-d001-data
labels:
prole.storage/role: data
prole.storage/volume: d001
synology.storage/role: data
synology.storage/volume: d001
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/data
@ -27,17 +27,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-wal
name: synology-iscsi-d001-wal
labels:
prole.storage/role: wal
prole.storage/volume: d001
synology.storage/role: wal
synology.storage/volume: d001
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/wal
@ -53,17 +53,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-garage
name: synology-iscsi-d001-garage
labels:
prole.storage/role: garage
prole.storage/volume: d001
synology.storage/role: garage
synology.storage/volume: d001
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/garage
@ -79,17 +79,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d002-data
name: synology-iscsi-d002-data
labels:
prole.storage/role: data
prole.storage/volume: d002
synology.storage/role: data
synology.storage/volume: d002
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d002/data
@ -105,17 +105,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d002-wal
name: synology-iscsi-d002-wal
labels:
prole.storage/role: wal
prole.storage/volume: d002
synology.storage/role: wal
synology.storage/volume: d002
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d002/wal
@ -131,17 +131,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d003-data
name: synology-iscsi-d003-data
labels:
prole.storage/role: data
prole.storage/volume: d003
synology.storage/role: data
synology.storage/volume: d003
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d003/data
@ -157,17 +157,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d003-wal
name: synology-iscsi-d003-wal
labels:
prole.storage/role: wal
prole.storage/volume: d003
synology.storage/role: wal
synology.storage/volume: d003
spec:
capacity:
storage: 29Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d003/wal
@ -183,17 +183,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d003-grafana
name: synology-iscsi-d003-grafana
labels:
prole.storage/role: grafana
prole.storage/volume: d003
synology.storage/role: grafana
synology.storage/volume: d003
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d003/grafana
@ -209,17 +209,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d002-prometheus
name: synology-iscsi-d002-prometheus
labels:
prole.storage/role: prometheus
prole.storage/volume: d002
synology.storage/role: prometheus
synology.storage/volume: d002
spec:
capacity:
storage: 20Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d002/prometheus
@ -235,17 +235,17 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-iscsi-d001-openbao
name: synology-iscsi-d001-openbao
labels:
prole.storage/role: openbao
prole.storage/volume: d001
synology.storage/role: openbao
synology.storage/volume: d001
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
storageClassName: prole-iscsi
storageClassName: synology-iscsi
persistentVolumeReclaimPolicy: Retain
local:
path: /synology/d001/openbao

View File

@ -3,8 +3,8 @@ kind: PersistentVolume
metadata:
name: prole-monitoring-d004-prometheus
labels:
prole.storage/role: prometheus
prole.storage/volume: d004
synology.storage/role: prometheus
synology.storage/volume: d004
spec:
capacity:
storage: 30Gi
@ -29,8 +29,8 @@ kind: PersistentVolume
metadata:
name: prole-monitoring-d004-alertmanager
labels:
prole.storage/role: alertmanager
prole.storage/volume: d004
synology.storage/role: alertmanager
synology.storage/volume: d004
spec:
capacity:
storage: 5Gi
@ -55,8 +55,8 @@ kind: PersistentVolume
metadata:
name: prole-monitoring-d004-grafana
labels:
prole.storage/role: grafana
prole.storage/volume: d004
synology.storage/role: grafana
synology.storage/volume: d004
spec:
capacity:
storage: 10Gi

View File

@ -51,7 +51,7 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: openbao
synology.storage/role: openbao

View File

@ -5,7 +5,7 @@ metadata:
name: prole-db
spec:
instances: 3
imageName: k3d-prole-registry:5000/knoe-db:18-138
imageName: example.invalid:5000/knoe-db:0.0.0
postgresUID: 100
postgresGID: 101
maxSyncReplicas: 1
@ -61,10 +61,10 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: data
synology.storage/role: data
walStorage:
size: 1Gi
pvcTemplate:
@ -73,10 +73,10 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: wal
synology.storage/role: wal
monitoring:
enablePodMonitor: false

View File

@ -4,23 +4,23 @@ metadata:
name: prole-db
spec:
instances: 3
imageName: myrddin.prole.org:5000/knoe-db:18-125
imageName: myrddin.prole.org:5000/knoe-db:18-140
postgresUID: 100
postgresGID: 101
maxSyncReplicas: 1
affinity:
# CloudNativePG uses its own affinity schema (not corev1.Affinity). See: `kubectl explain cluster.spec.affinity`
enablePodAntiAffinity: true
podAntiAffinityType: required
podAntiAffinityType: preferred
topologyKey: kubernetes.io/hostname
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: prole.org/node-role
- key: node.kubernetes.io/instance-type
operator: In
values:
- db
- k3s
postgresql:
parameters:
@ -88,10 +88,10 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: data
synology.storage/role: data
walStorage:
size: 1Gi
pvcTemplate:
@ -100,9 +100,9 @@ spec:
resources:
requests:
storage: 1Gi
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: wal
synology.storage/role: wal
monitoring:
enablePodMonitor: false

View File

@ -1,7 +1,7 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: prole-iscsi
name: synology-iscsi
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer

View File

@ -856,6 +856,64 @@ class ProleInstaller:
return self._ensure_namespace_prefix(ns)
return self._ensure_namespace_prefix(self._generate_namespace_name())
def _initial_db_namespace(self) -> str:
"""Initial namespace suggestion for CNPG/Postgres.
Unlike `_initial_namespace()`, this default is version-aware to encourage
parallel installs (e.g. `knoe-db-18-142`).
"""
# Explicit overrides must win.
base = (
os.environ.get("PROLE_DB_NAMESPACE")
or os.environ.get("DB_NAMESPACE")
or os.environ.get("PROLE_NAMESPACE")
or os.environ.get("NAMESPACE")
or ""
).strip()
if not base:
try:
existing = self._read_existing_env()
base = (
(existing.get("NAMESPACE") or "")
or (existing.get("PROLE_NAMESPACE") or "")
).strip()
except Exception:
base = ""
if not base:
base = (self._read_existing_cfg_namespace() or "").strip() or "knoe-db"
base = self._sanitize_namespace(self._ensure_namespace_prefix(base))
version = ""
try:
controller = getattr(self, "controller", None)
if controller is not None:
version = (controller.get_prole_db_version() or "").strip()
except Exception:
version = ""
if not version:
return base
version_ns = self._sanitize_namespace(version.replace(".", "-"))
if base.endswith(f"-{version_ns}"):
return base
combined = f"{base}-{version_ns}"
if len(combined) > 63:
# Keep the version suffix intact and trim the base to fit.
max_base_len = 63 - len(version_ns) - 1
if max_base_len < 1:
return self._sanitize_namespace(f"knoe-db-{version_ns}")
base_trim = base[:max_base_len].rstrip("-")
combined = f"{base_trim}-{version_ns}"
return self._sanitize_namespace(combined)
def _is_valid_namespace(self, name: str) -> bool:
if not name or len(name) > 63:
return False
@ -5613,8 +5671,8 @@ class ProleConsoleInstaller(ProleInstaller):
NetworkScanMilestone(),
EnvSetupMilestone(),
SecretManagementMilestone(),
DatabaseCreationMilestone(),
DockerBuildMilestone(),
DatabaseCreationMilestone(),
ClusterLifecycleMilestone(),
InitializationScriptsMilestone(),
KerberosMilestone(),
@ -5768,8 +5826,8 @@ def _prepare_k3s_pipeline(
_log(f" k3s_token: {'detected' if k3s_token else '(not detected)'}\n\n")
# Load config to resolve namespace and secrets for pipeline tfvars
_log("==> Loading installer config...\n")
installer = ProleConsoleInstaller(controller, str(cfg_path))
_log("==> Loading knoe config...\n")
knoe = ProleConsoleInstaller(controller, str(cfg_path))
try:
existing_inputs = knoe._load_inputs_from_cfg()
except Exception:

View File

@ -85,10 +85,26 @@ class Milestone(ABC):
namespace = (state.inputs.get("init_password.db_namespace", "") or "").strip()
env["NAMESPACE"] = namespace or "default"
# Paths
env["PROLE_CONF"] = state.inputs.get(
"env_setup.PROLE_CONF", str(project_root / "conf")
# Paths — prefer the env-specific conf subdir (e.g. conf/service/) so that
# init scripts source the correct prole.cfg and don't fall back to the
# conf/prole.cfg symlink which may point to a different cluster env.
# Priority: (1) inherited PROLE_CONF from parent shell when it points to a
# valid env-specific subdir, (2) cluster_env-derived subdir, (3) stored value.
_inherited_conf = os.environ.get("PROLE_CONF", "")
_cluster_env_for_conf = state.inputs.get("init_cluster.cluster_env", "")
_env_conf_dir = (
Path(project_root) / "conf" / _cluster_env_for_conf
if _cluster_env_for_conf
else None
)
if _inherited_conf and (Path(_inherited_conf) / "prole.cfg").exists():
env["PROLE_CONF"] = _inherited_conf
elif _env_conf_dir and _env_conf_dir.is_dir():
env["PROLE_CONF"] = str(_env_conf_dir)
else:
env["PROLE_CONF"] = state.inputs.get(
"env_setup.PROLE_CONF", str(project_root / "conf")
)
env["PROLE_DATA"] = state.inputs.get(
"env_setup.PROLE_DATA", str(project_root / "prole-db" / "data")
)

View File

@ -348,10 +348,10 @@ class ProleInstaller(
("Dependencies", "deps_summary"),
("Network", "network_scan"),
("System Environment", "env_setup"),
("Cluster Environment", "init_cluster"),
("Cluster Nodes", "cluster_nodes"),
("Database Options", "database_options"),
("Docker Build", "init_db_build"),
("Cluster Environment", "init_cluster"),
("Cluster Nodes", "cluster_nodes"),
("Database Creation", "init_password"),
("Initialization Scripts", "init_scripts"),
("Kerberos Authentication", "kerberos_config"),
@ -655,7 +655,7 @@ class ProleInstaller(
self.db_password = tk.StringVar()
self.db_password_confirm = tk.StringVar()
self.namespace_owner = self._get_local_owner()
self.db_namespace = tk.StringVar(value=self._initial_namespace())
self.db_namespace = tk.StringVar(value=self._initial_db_namespace())
try:
_ns_from_cfg = (
(self.prole_cfg_data.get("Global", {}) or {})
@ -663,7 +663,21 @@ class ProleInstaller(
.strip()
)
if _ns_from_cfg:
self.db_namespace.set(self._ensure_namespace_prefix(_ns_from_cfg))
base = self._ensure_namespace_prefix(_ns_from_cfg)
ver = ""
try:
ver = (self.controller.get_prole_db_version() or "").strip()
except Exception:
ver = ""
if ver:
ver_ns = self._sanitize_namespace(ver.replace(".", "-"))
base_ns = self._sanitize_namespace(base)
if base_ns.endswith(f"-{ver_ns}"):
self.db_namespace.set(base_ns)
else:
self.db_namespace.set(self._sanitize_namespace(f"{base_ns}-{ver_ns}"))
else:
self.db_namespace.set(self._sanitize_namespace(base))
except Exception:
pass
self.db_host_port = tk.StringVar(value="5432")

View File

@ -1420,6 +1420,7 @@ class DatabaseScreenMixin:
# Use $PROLE_HOME/build for Docker build context
# This avoids issues with PyInstaller's temporary _MEIPASS directory
prole_home = resolve_prole_home()
env_key = self._cluster_env_key()
mode_key = _deployment_mode_from_env(env_key) or "default"
build_dir = prole_home / "build" / mode_key / "knoe-db"
build_dir.mkdir(parents=True, exist_ok=True)
@ -1442,7 +1443,6 @@ class DatabaseScreenMixin:
pub_key = pub_key_path.read_text().strip()
username = self.db_username.get()
env_key = self._cluster_env_key()
cmd = ["docker", "build", "--progress=plain"]
cmd.extend(get_docker_build_platform_args(env_key))
cmd += [

View File

@ -615,22 +615,48 @@ class DockerScreenMixin:
raise Exception(f"Unknown environment: {env}")
def create_or_recreate_k3d_dev_cluster(self, cluster_name="knoe-dev-cluster"):
"""Create or restart local k3d cluster and wire it to the chosen registry."""
"""Create or restart local k3d cluster and wire it to the local k3d registry."""
result = subprocess.run(
["k3d", "cluster", "list"], capture_output=True, text=True
)
if cluster_name in (result.stdout or ""):
subprocess.run(["k3d", "cluster", "delete", cluster_name], check=True)
# Determine registry integration args
reg_args = []
if getattr(self, "registry_url", None):
# If using the local k3d registry, we want to create or use it
if self.registry_url.startswith("localhost:5000"):
# Creating with --registry-create ensures it's available and integrated
reg_args = ["--registry-create", f"prole-registry:0.0.0.0:5000"]
else:
reg_args = ["--registry-use", self.registry_url]
# Always ensure the local k3d registry exists before creating the cluster so
# that we can wire it via --registry-use. Using --registry-use (rather than
# --registry-create) is required when the registry container already exists,
# which is the common case after the first install run.
reg_name = "prole-registry"
registry_container = f"k3d-{reg_name}"
lst = subprocess.run(
["k3d", "registry", "list"], capture_output=True, text=True
)
if registry_container not in (lst.stdout or "") and reg_name not in (
lst.stdout or ""
):
subprocess.run(
["k3d", "registry", "create", reg_name, "--port", "0.0.0.0:5000"],
check=True,
)
# Ensure the registry container is actually running
running = subprocess.run(
[
"docker",
"ps",
"--filter",
f"name={registry_container}",
"--format",
"{{.Names}}",
],
capture_output=True,
text=True,
)
if not (running.stdout or "").strip():
subprocess.run(["docker", "start", registry_container], check=True)
# Wire the cluster to the registry so containerd inside each node resolves
# k3d-prole-registry.localhost:5000 correctly.
reg_args = ["--registry-use", f"{registry_container}:5000"]
prole_data = str(self._resolve_env_dir("PROLE_DATA", "data"))
volume_args = _k3d_prole_data_volume_args(prole_data)

View File

@ -61,9 +61,9 @@ CERT_MANAGER_MANIFEST_URL=${CERT_MANAGER_MANIFEST_URL:-}
CERT_MANAGER_FALLBACK_VERSION=${CERT_MANAGER_FALLBACK_VERSION:-1.19.3}
# Protected storage requirements (k3s only)
PROLE_CNPG_STORAGE_CLASS=${PROLE_CNPG_STORAGE_CLASS:-prole-iscsi}
PROLE_PROTECTED_DATA_PATH=${PROLE_PROTECTED_DATA_PATH:-/prole/d001/data}
PROLE_PROTECTED_WAL_PATH=${PROLE_PROTECTED_WAL_PATH:-/prole/d001/wal}
PROLE_CNPG_STORAGE_CLASS=${PROLE_CNPG_STORAGE_CLASS:-synology-iscsi}
PROLE_PROTECTED_DATA_PATH=${PROLE_PROTECTED_DATA_PATH:-/synology/d001/data}
PROLE_PROTECTED_WAL_PATH=${PROLE_PROTECTED_WAL_PATH:-/synology/d001/wal}
if [[ "$ACTION" != "deploy" && "$ACTION" != "rollout" && "$ACTION" != "force-rollout" ]]; then
if [[ -n "${2:-}" ]]; then
@ -225,9 +225,9 @@ validate_cnpg_manifest_storage() {
wal_sc="$v"
fi
fi
if [[ -n "$block" && "$line" =~ prole\.storage/role: ]]; then
if [[ -n "$block" && "$line" =~ synology\.storage/role: ]]; then
local r
r=$(printf '%s' "$line" | sed -E 's/^.*prole\.storage\/role:[[:space:]]*//; s/[[:space:]]+$//')
r=$(printf '%s' "$line" | sed -E 's/^.*synology\.storage\/role:[[:space:]]*//; s/[[:space:]]+$//')
if [[ "$block" == "storage" && -z "$data_role" ]]; then
data_role="$r"
elif [[ "$block" == "wal" && -z "$wal_role" ]]; then
@ -243,7 +243,7 @@ validate_cnpg_manifest_storage() {
fi
if [[ "$data_role" != "data" || "$wal_role" != "wal" ]]; then
echo "ERROR: CNPG Cluster manifest must include explicit PV selectors for Prole storage (prole.storage/role=data and prole.storage/role=wal)." >&2
echo "ERROR: CNPG Cluster manifest must include explicit PV selectors for Prole storage (synology.storage/role=data and synology.storage/role=wal)." >&2
echo "Got selector roles: data='${data_role:-<missing>}', wal='${wal_role:-<missing>}'" >&2
return 1
fi
@ -267,8 +267,8 @@ validate_cnpg_runtime_storage() {
local data_sc wal_sc data_role wal_role
data_sc=$(printf '%s' "$cluster_json" | jq -r '.spec.storage.pvcTemplate.storageClassName // empty')
wal_sc=$(printf '%s' "$cluster_json" | jq -r '.spec.walStorage.pvcTemplate.storageClassName // empty')
data_role=$(printf '%s' "$cluster_json" | jq -r '.spec.storage.pvcTemplate.selector.matchLabels["prole.storage/role"] // empty')
wal_role=$(printf '%s' "$cluster_json" | jq -r '.spec.walStorage.pvcTemplate.selector.matchLabels["prole.storage/role"] // empty')
data_role=$(printf '%s' "$cluster_json" | jq -r '.spec.storage.pvcTemplate.selector.matchLabels["synology.storage/role"] // empty')
wal_role=$(printf '%s' "$cluster_json" | jq -r '.spec.walStorage.pvcTemplate.selector.matchLabels["synology.storage/role"] // empty')
if [[ "$data_sc" != "$sc" || "$wal_sc" != "$sc" ]]; then
echo "ERROR: Live CNPG Cluster storageClassName must be '${sc}' for both data and walStorage (got data='${data_sc:-<missing>}', wal='${wal_sc:-<missing>}')." >&2
@ -326,9 +326,9 @@ validate_cnpg_runtime_storage() {
return 1
fi
case "$pv_path" in
/prole/*) ;;
/synology/*) ;;
*)
echo "ERROR: Bound PV '${pv}' path '${pv_path}' is not under /prole/. Refusing deployment." >&2
echo "ERROR: Bound PV '${pv}' path '${pv_path}' is not under /synology/. Refusing deployment." >&2
return 1
;;
esac
@ -1168,6 +1168,81 @@ apply_prole_manifest_file() {
return 1
}
_prole_k3s_registry_host_for_cache() {
# Best-effort host:port for the k3s registry, used as an optional cache source in k3d mode.
# Intentionally avoids k3d registry names and localhost-ish endpoints.
local r="${PROLE_K3S_REGISTRY:-${K3S_REGISTRY_HOST:-${LOCAL_REGISTRY_INTERNAL:-${LOCAL_REGISTRY:-}}}}"
r="${r#http://}"
r="${r#https://}"
case "${r}" in
"")
r="myrddin.prole.org:5000"
;;
localhost:5000|127.0.0.1:5000|*.localhost|*.localhost:5000)
r="myrddin.prole.org:5000"
;;
k3d-*|*/k3d-*)
r="myrddin.prole.org:5000"
;;
esac
printf '%s' "$r"
}
_docker_build_knoe_db_image() {
# Build the knoe-db image with BuildKit and optional cache source.
# Args: <tag> <context_dir> [cache_ref]
local tag="$1"
local context_dir="$2"
local cache_ref="${3:-}"
local ssh_args=()
if [[ -n "${SSH_AUTH_SOCK:-}" ]]; then
ssh_args+=(--ssh default)
fi
if docker buildx version >/dev/null 2>&1; then
# Prefer an isolated docker-container builder to avoid host snapshot/cache corruption.
local builder_name="${PROLE_BUILDX_BUILDER:-prole-buildkit}"
if ! docker buildx inspect "$builder_name" >/dev/null 2>&1; then
docker buildx create --name "$builder_name" --driver docker-container --use >/dev/null 2>&1 || true
else
docker buildx use "$builder_name" >/dev/null 2>&1 || true
fi
local cache_args=()
if [[ -n "$cache_ref" ]]; then
cache_args+=(--cache-from "type=registry,ref=${cache_ref}")
fi
if docker buildx build \
--pull \
--tag "$tag" \
--load \
"${ssh_args[@]}" \
"${cache_args[@]}" \
"$context_dir"; then
return 0
fi
fi
# Fallback to docker build (still BuildKit-enabled); cache-from here only works if the cache image is present locally.
local cache_from_args=()
if [[ -n "$cache_ref" ]]; then
docker pull "$cache_ref" >/dev/null 2>&1 || true
cache_from_args+=(--cache-from "$cache_ref")
fi
DOCKER_BUILDKIT=1 docker build \
--pull \
"${ssh_args[@]}" \
"${cache_from_args[@]}" \
-t "$tag" \
"$context_dir"
}
_push_to_k3s_registry() {
local image="$1"
local push_host="${LOCAL_REGISTRY:-${LOCAL_REGISTRY_INTERNAL:-myrddin.prole.org:5000}}"
@ -1310,6 +1385,7 @@ _ensure_prole_db_image() {
image=$(resolve_cnpg_image "$image")
local prole_db_dir="${PROLE_HOME:-$SCRIPT_DIR/..}/prole-db"
local plain_image="${image##*/}" # e.g. knoe-db:18-088
local k3s_cache_ref=""
if [[ "${PROLE_MODE:-}" == "k3s" ]]; then
echo "Pre-flight: ensuring image '$image' is available in k3s registry/import path ..."
@ -1321,6 +1397,13 @@ _ensure_prole_db_image() {
fi
cluster_name="${cluster_name:-knoe-dev-cluster}"
# In k3d mode, optionally consult the k3s registry as a cache/source of truth before building.
local k3s_host
k3s_host=$(_prole_k3s_registry_host_for_cache)
if [[ -n "${k3s_host:-}" ]]; then
k3s_cache_ref="${k3s_host}/${plain_image}"
fi
echo "Pre-flight: verifying image '$image' is available in k3d cluster '$cluster_name' ..."
# Step 1: check if already present in k3d containerd with matching digest
@ -1393,6 +1476,17 @@ _ensure_prole_db_image() {
return $?
fi
# Step 3b (k3d only): try to reuse a previously-built image from the k3s registry.
if [[ "${PROLE_MODE:-}" == "k3d" && -n "${k3s_cache_ref:-}" && "${k3s_cache_ref}" != "${image}" ]]; then
if docker image inspect "$k3s_cache_ref" >/dev/null 2>&1 || docker pull "$k3s_cache_ref" >/dev/null 2>&1; then
echo " ✓ Found '$plain_image' in k3s registry (${k3s_cache_ref}); re-tagging for k3d as '$image' ..."
docker tag "$k3s_cache_ref" "$image" 2>/dev/null || true
_push_to_k3d_registry "$image" "$cluster_name"
return $?
fi
echo " (cache) '$plain_image' not present in k3s registry (${k3s_cache_ref}); building locally ..."
fi
# Step 4: image not found anywhere — build from source then push + import
if [[ ! -f "$prole_db_dir/Dockerfile" ]]; then
echo "ERROR: Dockerfile not found in '$prole_db_dir'; cannot build knoe-db image." >&2
@ -1404,7 +1498,7 @@ _ensure_prole_db_image() {
echo " Image '$image' not found in k3d, Docker daemon, or docker-import dir."
fi
echo " Building knoe-db image from '$prole_db_dir' ..."
if ! docker build -t "$plain_image" "$prole_db_dir"; then
if ! _docker_build_knoe_db_image "$plain_image" "$prole_db_dir" "${k3s_cache_ref:-}"; then
echo "ERROR: docker build failed for image '$plain_image'." >&2
return 1
fi
@ -2148,14 +2242,14 @@ reset_and_reinit() {
}
recycle_released_prole_iscsi_pvs() {
# StorageClass `prole-iscsi` uses `Retain` PV reclaim policy. After a CNPG reset, PVs can remain
# StorageClass `synology-iscsi` uses `Retain` PV reclaim policy. After a CNPG reset, PVs can remain
# in `Released` with a stale `claimRef`, which prevents new PVCs (same names) from binding.
# In that case, CNPG init jobs remain `Pending` with "didn't find available persistent volumes".
echo "Recycling Released prole-iscsi PVs (clearing stale claimRefs) for namespace '$NAMESPACE' ..."
echo "Recycling Released synology-iscsi PVs (clearing stale claimRefs) for namespace '$NAMESPACE' ..."
local pvs
pvs=$(kubectl get pv -o json \
| jq -r --arg ns "$NAMESPACE" '.items[]
| select(.spec.storageClassName == "prole-iscsi")
| select(.spec.storageClassName == "synology-iscsi")
| select(.status.phase == "Released")
| select((.spec.claimRef.namespace // "") == $ns)
| .metadata.name' 2>/dev/null || true)

View File

@ -60,7 +60,7 @@ else
fi
GARAGE_FILES=(
"$GARAGE_MANIFEST_DIR/storageclass-prole-iscsi.yaml"
"$GARAGE_MANIFEST_DIR/storageclass-synology-iscsi.yaml"
"$GARAGE_MANIFEST_DIR/iscsi-pvs.yaml"
"$GARAGE_MANIFEST_DIR/garage-configmap.yaml"
"$GARAGE_MANIFEST_DIR/garage-statefulset.yaml"
@ -133,7 +133,7 @@ k3d_cleanup_pending_pvc() {
return 0
fi
selector=$(kubectl get pvc data-garage-0 -n "$NAMESPACE" -o jsonpath='{.spec.selector}' 2>/dev/null || true)
if printf '%s' "$selector" | grep -q "prole.storage/"; then
if printf '%s' "$selector" | grep -q "synology.storage/"; then
echo "Removing pending Garage PVC with selector for k3d local-path ..."
kubectl delete statefulset "$GARAGE_NAME" -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete pvc data-garage-0 -n "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
@ -283,7 +283,7 @@ dump_debug() {
}
recycle_released_prole_iscsi_pv_for_pvc() {
# StorageClass `prole-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# StorageClass `synology-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# in `Released` with a stale `claimRef`, which prevents a same-named PVC from binding.
local ns="$1"
local pvc="$2"
@ -301,7 +301,7 @@ recycle_released_prole_iscsi_pv_for_pvc() {
local pv sc rp phase claim_ns claim_name claim_uid
while IFS=$'\t' read -r pv sc rp phase claim_ns claim_name claim_uid; do
[[ "$sc" == "prole-iscsi" ]] || continue
[[ "$sc" == "synology-iscsi" ]] || continue
[[ "$rp" == "Retain" ]] || continue
[[ "$phase" == "Released" ]] || continue
[[ "$claim_ns" == "$ns" ]] || continue
@ -322,7 +322,7 @@ wait_ready() {
local initial_timeout="${PVC_REPAIR_WAIT_TIMEOUT:-30s}"
if ! kubectl rollout status statefulset/$GARAGE_NAME -n "$NAMESPACE" --timeout="$initial_timeout"; then
echo "WARN: Garage not ready after $initial_timeout; checking for Released prole-iscsi PVs with stale claimRefs ..." >&2
echo "WARN: Garage not ready after $initial_timeout; checking for Released synology-iscsi PVs with stale claimRefs ..." >&2
recycle_released_prole_iscsi_pv_for_pvc "$NAMESPACE" "data-garage-0" || true
fi

View File

@ -313,8 +313,8 @@ kind: PersistentVolume
metadata:
name: merlin-local-iscsi-${volume_id}-prometheus
labels:
prole.storage/role: prometheus
prole.storage/volume: ${volume_id}
synology.storage/role: prometheus
synology.storage/volume: ${volume_id}
spec:
capacity:
storage: 30Gi
@ -339,8 +339,8 @@ kind: PersistentVolume
metadata:
name: merlin-local-iscsi-${volume_id}-alertmanager
labels:
prole.storage/role: alertmanager
prole.storage/volume: ${volume_id}
synology.storage/role: alertmanager
synology.storage/volume: ${volume_id}
spec:
capacity:
storage: 5Gi
@ -365,8 +365,8 @@ kind: PersistentVolume
metadata:
name: merlin-local-iscsi-${volume_id}-grafana
labels:
prole.storage/role: grafana
prole.storage/volume: ${volume_id}
synology.storage/role: grafana
synology.storage/volume: ${volume_id}
spec:
capacity:
storage: 10Gi

View File

@ -424,9 +424,9 @@ apply_k8s() {
fi
if [[ "$use_statefulset" == "1" ]]; then
# Ensure storage class and PVs exist for k3s
if [[ -f "$SCRIPT_DIR/../k8s/prole/storageclass-prole-iscsi.yaml" ]]; then
echo "Applying StorageClass 'prole-iscsi' ..."
kubectl apply -f "$SCRIPT_DIR/../k8s/prole/storageclass-prole-iscsi.yaml"
if [[ -f "$SCRIPT_DIR/../k8s/prole/storageclass-synology-iscsi.yaml" ]]; then
echo "Applying StorageClass 'synology-iscsi' ..."
kubectl apply -f "$SCRIPT_DIR/../k8s/prole/storageclass-synology-iscsi.yaml"
fi
if [[ -f "$SCRIPT_DIR/../k8s/prole/iscsi-pvs.yaml" ]]; then
echo "Applying iSCSI PersistentVolumes ..."
@ -476,7 +476,7 @@ k3d_cleanup_pending_openbao_pvc() {
return 0
fi
selector=$(kubectl get pvc data-openbao-0 -n "$OPENBAO_RESOURCE_NAMESPACE" -o jsonpath='{.spec.selector}' 2>/dev/null || true)
if printf '%s' "$selector" | grep -q "prole.storage/"; then
if printf '%s' "$selector" | grep -q "synology.storage/"; then
echo "Removing pending OpenBao PVC with selector for k3d local-path ..."
kubectl delete statefulset "$OPENBAO_NAME" -n "$OPENBAO_RESOURCE_NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
kubectl delete pvc data-openbao-0 -n "$OPENBAO_RESOURCE_NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true
@ -484,7 +484,7 @@ k3d_cleanup_pending_openbao_pvc() {
}
recycle_released_prole_iscsi_pv_for_pvc() {
# StorageClass `prole-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# StorageClass `synology-iscsi` uses `Retain` PV reclaim policy. On restart, PVs can remain
# in `Released` with a stale `claimRef`, which prevents a same-named PVC from binding.
local ns="$1"
local pvc="$2"
@ -502,7 +502,7 @@ recycle_released_prole_iscsi_pv_for_pvc() {
local pv sc rp phase claim_ns claim_name claim_uid
while IFS=$'\t' read -r pv sc rp phase claim_ns claim_name claim_uid; do
[[ "$sc" == "prole-iscsi" ]] || continue
[[ "$sc" == "synology-iscsi" ]] || continue
[[ "$rp" == "Retain" ]] || continue
[[ "$phase" == "Released" ]] || continue
[[ "$claim_ns" == "$ns" ]] || continue
@ -543,13 +543,13 @@ wait_for_openbao() {
echo "Waiting for OpenBao to become ready in namespace '$wait_ns' ..."
if kubectl get statefulset/$OPENBAO_NAME -n "$wait_ns" >/dev/null 2>&1; then
if ! kubectl rollout status statefulset/$OPENBAO_NAME -n "$wait_ns" --timeout="$initial_timeout"; then
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released prole-iscsi PVs with stale claimRefs ..." >&2
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released synology-iscsi PVs with stale claimRefs ..." >&2
recycle_released_prole_iscsi_pv_for_pvc "$wait_ns" "data-openbao-0" || true
fi
kubectl rollout status statefulset/$OPENBAO_NAME -n "$wait_ns" --timeout=${ROLLOUT_TIMEOUT:-300s}
else
if ! kubectl rollout status deploy/$OPENBAO_NAME -n "$wait_ns" --timeout="$initial_timeout"; then
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released prole-iscsi PVs with stale claimRefs ..." >&2
echo "WARN: OpenBao not ready after $initial_timeout; checking for Released synology-iscsi PVs with stale claimRefs ..." >&2
recycle_released_prole_iscsi_pv_for_pvc "$wait_ns" "data-openbao-0" || true
fi
kubectl rollout status deploy/$OPENBAO_NAME -n "$wait_ns" --timeout=${ROLLOUT_TIMEOUT:-300s}

View File

@ -488,7 +488,7 @@ fi
# Safety: prole.cfg must override any pre-set PROLE_MODE from the environment.
# Otherwise a leaked PROLE_MODE=k3d can trigger k3d-only manifest stripping (e.g., removing
# `storageClassName: prole-iscsi`) while targeting a k3s cluster.
# `storageClassName: synology-iscsi`) while targeting a k3s cluster.
_prole_cfg_mode_hint=""
if [[ -n "${prole_mode:-}" ]]; then
_prole_cfg_mode_hint=$(prole_normalize_mode "$prole_mode")

View File

@ -65,7 +65,7 @@ def _strip_blocks(text: str) -> str:
break
block_lines.append(next_line)
j += 1
if any("prole.storage/" in bl for bl in block_lines):
if any("synology.storage/" in bl for bl in block_lines):
i = j
continue
out.extend(block_lines)
@ -78,7 +78,7 @@ def _strip_blocks(text: str) -> str:
i += 1
continue
if stripped.startswith("storageClassName:") and "prole-iscsi" in stripped:
if stripped.startswith("storageClassName:") and "synology-iscsi" in stripped:
i += 1
continue

View File

@ -0,0 +1 @@
18

View File

@ -0,0 +1 @@
3

View File

@ -1 +1 @@
138
140

View File

@ -145,15 +145,19 @@ err_msg() {
load_prole_cfg() {
local cfg="${1:-$PROLE_CFG_DEFAULT}"
# Set PROLE_CONF before sourcing prole_cfg.sh so it is preserved (prole_cfg.sh
# honours a pre-set PROLE_CONF) and the correct config file is auto-detected.
if [[ -f "$cfg" ]]; then
PROLE_CONF="$(cd "$(dirname "$cfg")" && pwd)"
export PROLE_CONF
PROLE_CFG_FILE="$cfg"
export PROLE_CFG_FILE
fi
if [[ -f "${ROOT_DIR}/etc/prole_cfg.sh" ]]; then
# shellcheck disable=SC1090
source "${ROOT_DIR}/etc/prole_cfg.sh"
fi
if [[ -f "$cfg" ]]; then
export PROLE_CONF
PROLE_CONF="$(cd "$(dirname "$cfg")" && pwd)"
fi
export PROLE_CFG_FILE="$cfg"
export PROLE_CFG_FILE="${cfg}"
}
resolve_prole_mode() {
@ -594,7 +598,8 @@ case "${CMD}" in
CMD_ARGS=("${_INSTALL_PASSTHRU[@]}")
# Ensure OpenBao URL is set appropriately for the active mode before running install.py.
_cfg_for_env="${PROLE_CFG_DEFAULT}"
# Honour top-level -c / --config if provided (CONFIG_PATH), falling back to default.
_cfg_for_env="${CONFIG_PATH:-${PROLE_CFG_DEFAULT}}"
for ((i=0; i<${#CMD_ARGS[@]}; i++)); do
if [[ "${CMD_ARGS[$i]}" == "-c" || "${CMD_ARGS[$i]}" == "--config" ]]; then
if [[ $((i+1)) -lt ${#CMD_ARGS[@]} ]]; then

View File

@ -0,0 +1,7 @@
host,ip,roles
merlin,10.0.0.36,merlin_bootstrap
merlin.prole.org,,iscsi,k3s_agents,k3s_hosts,linux_hosts,mariadb,mariadb_primary
myrddin.prole.org,,ad_dc,iscsi,k3s_hosts,k3s_servers,linux_hosts,ssl_hosts
pi.prole.org,,iscsi,k3s_agents,k3s_hosts,linux_hosts,pihole
raspberry.prole.org,,iscsi,linux_hosts,mariadb,mariadb_replica,pihole
retropie.prole.org,,iscsi,linux_hosts

View File

@ -0,0 +1,65 @@
Network Discovery Summary:
Primary Router: 10.0.0.1 (eero_5d:50:f2)
DNS Servers: 10.0.0.4, 100.100.100.100, 10.0.0.5
Detected Devices:
- 10.0.0.38 [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [22, 445, 5900, 11434, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Ollama', 'Active Directory Related']
- 10.0.0.22 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443, 2049], Services: ['DNS', 'SSH', 'NFS', 'Web Server']
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 53, 80, 443, 2049, 445, 5900, 88, 389, 636], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 53, 443, 2049, 5900], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server']
- 10.0.0.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): Ports [], Services: []
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
- 10.0.0.37 [54:07:7d:22:c0:b2] (Netgear_22:c0:b2): Ports [80], Services: ['Web Server']
- 10.0.0.45 [f8:b4:6a:30:a4:1f] (HewlettPacka_30:a4:1f): Ports [80, 443], Services: ['Web Server']
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
- 10.0.0.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): Ports [], Services: []
- 10.0.0.55 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
- 10.0.0.58 [7e:46:74:2a:b0:b2] (7e:46:74:2a:b0:b2): Ports [], Services: []
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
- 10.0.0.203 [00:11:32:3b:2f:08] (Synology_3b:2f:08): Ports [22, 80, 443, 2049, 445], Services: ['SSH', 'NFS', 'Web Server', 'SMB/CIFS (Possible Windows/AD)']
- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 53, 443, 2049, 5900], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server']
- 10.0.0.204 [50:eb:f6:56:f3:26] (ASUSTekCOMPU_56:f3:26): Ports [22, 3389, 445, 5900, 11434], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'RDP (Windows)', 'Ollama']
- 10.0.0.1\ [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
- 10.0.0.123 [3c:ef:8c:96:e3:3c] (ZhejiangDahu_96:e3:3c): Ports [80], Services: ['Web Server']
- 10.0.0.124 [9c:8e:cd:02:73:c4] (AmcrestTechn_02:73:c4): Ports [80], Services: ['Web Server']
- 10.0.0.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
- 10.0.0.127 [58:55:ca:4a:60:6e] (Apple_4a:60:6e): Ports [22], Services: ['SSH']
- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): Ports [], Services: []
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
- 10.0.0.112 [f8:33:31:40:58:c0] (TexasInstrum_40:58:c0): Ports [80, 443], Services: ['Web Server']
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [80], Services: ['Web Server']
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 445, 5900, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
- 10.0.0.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
- 10.0.0.128 [20:c9:d0:94:30:dd] (Apple_94:30:dd): Ports [], Services: []
- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
- 10.0.0.179 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): Ports [], Services: []
- 10.0.0.180 [4c:e1:73:42:1a:be] (HuizhouDehon_2:1a:be): Ports [445], Services: ['SMB/CIFS (Possible Windows/AD)']
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
- 10.0.0.196 [60:5f:8d:88:08:f2] (eero_88:08:f2): Ports [53], Services: ['DNS']
- 10.0.0.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
- 10.0.0.208 [a0:ad:9f:30:84:5f] (ASUSTekCOMPU_30:84:5f): Ports [], Services: []
Ollama Instances found at: 10.0.0.38, 10.0.0.204
Ansible Inventory Summary:
Inventory directory: /Users/chrisfu/dev/prole/infrastructure
Hosts detected: 6
- merlin (10.0.0.36): roles [merlin_bootstrap]
- merlin.prole.org (unknown): roles [iscsi, k3s_agents, k3s_hosts, linux_hosts, mariadb, mariadb_primary]
- myrddin.prole.org (unknown): roles [ad_dc, iscsi, k3s_hosts, k3s_servers, linux_hosts, ssl_hosts]
- pi.prole.org (unknown): roles [iscsi, k3s_agents, k3s_hosts, linux_hosts, pihole]
- raspberry.prole.org (unknown): roles [iscsi, linux_hosts, mariadb, mariadb_replica, pihole]
- retropie.prole.org (unknown): roles [iscsi, linux_hosts]
Flat inventory file: /Users/chrisfu/dev/prole/scan/ansible_inventory_flat.txt

View File

@ -1219,7 +1219,7 @@ ensure_k8s_supabase_static_pvs() {
if [[ "${MODE:-}" != "k8s" ]]; then
return 0
fi
if [[ -z "${storage_class:-}" || "$storage_class" != "prole-iscsi" ]]; then
if [[ -z "${storage_class:-}" || "$storage_class" != "synology-iscsi" ]]; then
return 0
fi
@ -1227,7 +1227,7 @@ ensure_k8s_supabase_static_pvs() {
local node base parent base_name
node="${SUPABASE_PV_NODE:-myrddin.prole.org}"
base="${SUPABASE_PV_BASE_DIR:-/prole/d002/supabase}"
base="${SUPABASE_PV_BASE_DIR:-/synology/d002/supabase}"
base="${base%/}"
parent="$(dirname "$base")"
base_name="$(basename "$base")"
@ -1298,12 +1298,12 @@ EOF
# Static local PVs with Retain reclaimPolicy can get stuck in Released after a namespace reset.
# Delete Released PV objects so they can be recreated and rebound to the new PVCs.
local pvs=(
prole-supabase-db
prole-supabase-deno
prole-supabase-functions
prole-supabase-imgproxy
prole-supabase-snippets
prole-supabase-storage
synology-supabase-db
synology-supabase-deno
synology-supabase-functions
synology-supabase-imgproxy
synology-supabase-snippets
synology-supabase-storage
)
local pv
for pv in "${pvs[@]}"; do
@ -1320,9 +1320,9 @@ EOF
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-supabase-db
name: synology-supabase-db
labels:
prole.storage/pvc: supabase-db
synology.storage/pvc: supabase-db
spec:
capacity:
storage: 5Gi
@ -1345,9 +1345,9 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-supabase-deno
name: synology-supabase-deno
labels:
prole.storage/pvc: supabase-deno
synology.storage/pvc: supabase-deno
spec:
capacity:
storage: 1Gi
@ -1370,9 +1370,9 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-supabase-functions
name: synology-supabase-functions
labels:
prole.storage/pvc: supabase-functions
synology.storage/pvc: supabase-functions
spec:
capacity:
storage: 1Gi
@ -1395,9 +1395,9 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-supabase-imgproxy
name: synology-supabase-imgproxy
labels:
prole.storage/pvc: supabase-imgproxy
synology.storage/pvc: supabase-imgproxy
spec:
capacity:
storage: 1Gi
@ -1420,9 +1420,9 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-supabase-snippets
name: synology-supabase-snippets
labels:
prole.storage/pvc: supabase-snippets
synology.storage/pvc: supabase-snippets
spec:
capacity:
storage: 1Gi
@ -1445,9 +1445,9 @@ spec:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prole-supabase-storage
name: synology-supabase-storage
labels:
prole.storage/pvc: supabase-storage
synology.storage/pvc: supabase-storage
spec:
capacity:
storage: 1Gi
@ -1513,7 +1513,7 @@ PY
local storage_class
storage_class="${SUPABASE_STORAGE_CLASS:-}"
if [[ -z "$storage_class" && "$MODE" == "k8s" ]]; then
storage_class="prole-iscsi"
storage_class="synology-iscsi"
fi
ensure_k8s_supabase_static_pvs "$storage_class"

View File

@ -34,7 +34,7 @@ metadata:
name: {{ $pvcName }}
labels:
{{- include "supabase.labels" $root | nindent 4 }}
prole.storage/pvc: {{ $pvcName | quote }}
synology.storage/pvc: {{ $pvcName | quote }}
{{- with $persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
@ -43,10 +43,10 @@ spec:
{{- if $persistence.storageClassName }}
storageClassName: {{ $persistence.storageClassName }}
{{- end }}
{{- if eq ($persistence.storageClassName | default "") "prole-iscsi" }}
{{- if eq ($persistence.storageClassName | default "") "synology-iscsi" }}
selector:
matchLabels:
prole.storage/pvc: {{ $pvcName | quote }}
synology.storage/pvc: {{ $pvcName | quote }}
{{- end }}
accessModes:
{{- range $persistence.accessModes }}

View File

@ -4,7 +4,7 @@ set -euo pipefail
# Regression tests for k3s CNPG storage guardrails in etc/init_cloudnative_pg.sh
# - must fail if protected mounts are root-backed
# - must fail if manifest lacks explicit prole-iscsi + selector labels (prevents local-path fallback)
# - must fail if manifest lacks explicit synology-iscsi + selector labels (prevents local-path fallback)
# - must succeed (with mocks) when mounts + manifest + runtime objects are correct
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
@ -139,9 +139,13 @@ if [[ "$args" == *"get nodes"*"--no-headers"* ]]; then
fi
# Runtime validation calls
if [[ "$args" == *" get cluster prole-db -o jsonpath="* ]]; then
# Return empty imageName so _cnpg_current_image_name sees no prior image.
exit 0
fi
if [[ "$args" == *" get cluster prole-db -o json"* ]]; then
cat <<JSON
{"apiVersion":"postgresql.cnpg.io/v1","kind":"Cluster","metadata":{"name":"prole-db"},"spec":{"storage":{"pvcTemplate":{"storageClassName":"prole-iscsi","selector":{"matchLabels":{"prole.storage/role":"data"}}}},"walStorage":{"pvcTemplate":{"storageClassName":"prole-iscsi","selector":{"matchLabels":{"prole.storage/role":"wal"}}}}}}
{"apiVersion":"postgresql.cnpg.io/v1","kind":"Cluster","metadata":{"name":"prole-db"},"spec":{"storage":{"pvcTemplate":{"storageClassName":"synology-iscsi","selector":{"matchLabels":{"synology.storage/role":"data"}}}},"walStorage":{"pvcTemplate":{"storageClassName":"synology-iscsi","selector":{"matchLabels":{"synology.storage/role":"wal"}}}}}}
JSON
exit 0
fi
@ -149,19 +153,19 @@ fi
if [[ "$args" == *" get pvc"*"cnpg.io/cluster=prole-db"*"-o json"* ]]; then
cat <<JSON
{"items":[
{"metadata":{"name":"prole-db-1"},"spec":{"storageClassName":"prole-iscsi","volumeName":"pv-data"}},
{"metadata":{"name":"prole-db-wal"},"spec":{"storageClassName":"prole-iscsi","volumeName":"pv-wal"}}
{"metadata":{"name":"prole-db-1"},"spec":{"storageClassName":"synology-iscsi","volumeName":"pv-data"}},
{"metadata":{"name":"prole-db-wal"},"spec":{"storageClassName":"synology-iscsi","volumeName":"pv-wal"}}
]}
JSON
exit 0
fi
if [[ "$args" == "get pv pv-data -o json"* ]]; then
echo '{"spec":{"storageClassName":"prole-iscsi","local":{"path":"/prole/d001/data"}}}'
echo '{"spec":{"storageClassName":"synology-iscsi","local":{"path":"/synology/d001/data"}}}'
exit 0
fi
if [[ "$args" == "get pv pv-wal -o json"* ]]; then
echo '{"spec":{"storageClassName":"prole-iscsi","local":{"path":"/prole/d001/wal"}}}'
echo '{"spec":{"storageClassName":"synology-iscsi","local":{"path":"/synology/d001/wal"}}}'
exit 0
fi
@ -248,16 +252,16 @@ metadata:
spec:
storage:
pvcTemplate:
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: data
synology.storage/role: data
walStorage:
pvcTemplate:
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: wal
synology.storage/role: wal
EOF
export PROLE_TEST_FINDMNT_DATA_SRC="/dev/root"
@ -300,19 +304,19 @@ spec:
instances: 1
storage:
pvcTemplate:
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: data
synology.storage/role: data
resources:
requests:
storage: 29Gi
walStorage:
pvcTemplate:
storageClassName: prole-iscsi
storageClassName: synology-iscsi
selector:
matchLabels:
prole.storage/role: wal
synology.storage/role: wal
resources:
requests:
storage: 29Gi

View File

@ -0,0 +1,266 @@
#!/usr/bin/env bash
# Unit tests for etc/init_cloudnative_pg.sh image policy behavior:
# - no-op when desired image already deployed
# - fail early on version mismatch without --upgrade (before namespace creation)
# - on --upgrade + mismatch, trigger a rollout/restart hook
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
SCRIPT_UNDER_TEST="$REPO_ROOT/etc/init_cloudnative_pg.sh"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$BIN_DIR"
MOCK_LOG="$TMP_DIR/mock_calls.log"
cat <<'K_EOF' > "$BIN_DIR/kubectl"
#!/usr/bin/env bash
set -euo pipefail
echo "Mocked kubectl called with $@" >> "${MOCK_LOG:-/tmp/mock_calls.log}"
args="$*"
if [[ "$args" == *"get --raw=/readyz"* || "$args" == *"get --raw='/readyz'"* || "$args" == *"get --raw=\"/readyz\""* ]]; then
echo "ok"
exit 0
fi
if [[ "$args" == *"version --short"* ]]; then
echo "Client Version: v0.0.0"
echo "Server Version: v0.0.0"
exit 0
fi
# CNPG webhook wait: return an endpoint IP so the wait loop passes.
if [[ "$args" == *"get endpoints"*"cnpg-webhook-service"* ]]; then
echo "10.42.0.10"
exit 0
fi
if [[ "$args" == *"get endpoints barman-cloud"* ]]; then
echo "10.42.0.11"
exit 0
fi
# Barman plugin service registration: return a service name for label-based lookups.
if [[ "$args" == *"get svc"*"-l cnpg.io/pluginName=barman-cloud.cloudnative-pg.io"*"-o jsonpath="* ]]; then
echo "barman-cloud"
exit 0
fi
if [[ "$args" == *"get svc barman-cloud"*"-o jsonpath="*".metadata.name"* ]]; then
echo "barman-cloud"
exit 0
fi
if [[ "$args" == *"get svc barman-cloud"*"-o jsonpath="*"pluginClientSecret"* ]]; then
echo "barman-cloud-client-tls"
exit 0
fi
if [[ "$args" == *"get svc barman-cloud"*"-o jsonpath="*"pluginServerSecret"* ]]; then
echo "barman-cloud-server-tls"
exit 0
fi
if [[ "$args" == *"get svc barman-cloud"*"-o jsonpath="*"pluginPort"* ]]; then
echo "443"
exit 0
fi
if [[ "$args" == *"get namespace"* ]]; then
exit 0
fi
if [[ "$args" == *"create namespace"* ]]; then
exit 0
fi
if [[ "$args" == *"get cluster"*"-o jsonpath="*".spec.imageName"* ]]; then
echo "${MOCK_CLUSTER_IMAGE:-}"
exit 0
fi
if [[ "$args" == *"get cluster"* ]]; then
exit 0
fi
if [[ "$args" == *"patch cluster"* ]]; then
exit 0
fi
if [[ "$args" == *"cnpg version"* ]]; then
exit 0
fi
if [[ "$args" == *"cnpg restart"* ]]; then
exit 0
fi
if [[ "$args" == *"apply"* ]]; then
exit 0
fi
if [[ "$args" == *"get secret"* ]]; then
exit 0
fi
exit 0
K_EOF
chmod +x "$BIN_DIR/kubectl"
mock_ok_tool() {
cat <<M_EOF > "$BIN_DIR/$1"
#!/usr/bin/env bash
echo "Mocked $1 called with \$@" >> "${MOCK_LOG:-/tmp/mock_calls.log}"
exit 0
M_EOF
chmod +x "$BIN_DIR/$1"
}
# Prevent accidental real calls during tests.
mock_ok_tool docker
mock_ok_tool k3d
mock_ok_tool curl
mock_ok_tool jq
mock_ok_tool skopeo
export PATH="$BIN_DIR:$PATH"
export MOCK_LOG
CONF_DIR="$TMP_DIR/conf"
mkdir -p "$CONF_DIR"
SERVICE_DIR="$TMP_DIR/service"
mkdir -p "$SERVICE_DIR/secrets"
printf '%s' "dummy-private-key" > "$SERVICE_DIR/secrets/admin.key"
printf '%s' "dummy-public-key" > "$SERVICE_DIR/secrets/admin.pub"
write_cfg() {
local prole_home_val="$1"
cat <<C_EOF > "$CONF_DIR/prole.cfg"
[User]
NAMESPACE = test-ns
SERVICE_NAMESPACE = test-system
PROLE_HOME = ${prole_home_val}
PROLE_SERVICE = ${SERVICE_DIR}
[Global]
DEPLOYMENT_MODE = k3d
[Docker Build]
LOCAL_REGISTRY = localhost:5000
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry:5000
C_EOF
}
reset_log() {
: > "$MOCK_LOG"
}
assert_not_called() {
local needle="$1"
if grep -q "$needle" "$MOCK_LOG"; then
echo "FAILURE: expected not to see '$needle' in mock log"
sed -n '1,200p' "$MOCK_LOG" || true
exit 1
fi
}
assert_called() {
local needle="$1"
if ! grep -q "$needle" "$MOCK_LOG"; then
echo "FAILURE: expected to see '$needle' in mock log"
sed -n '1,200p' "$MOCK_LOG" || true
exit 1
fi
}
run_case() {
local name="$1"; shift
local expected_rc="$1"; shift
reset_log
local out="$TMP_DIR/${name}.out"
local err="$TMP_DIR/${name}.err"
set +e
"$@" >"$out" 2>"$err"
local rc=$?
set -e
if [[ "$rc" != "$expected_rc" ]]; then
echo "FAILURE: $name expected RC=$expected_rc got RC=$rc"
echo "--- stdout ---"
sed -n '1,200p' "$out" || true
echo "--- stderr ---"
sed -n '1,200p' "$err" || true
echo "--- mock log ---"
sed -n '1,200p' "$MOCK_LOG" || true
exit 1
fi
}
###############################################
# Case 1: no-op on same image (initialize)
###############################################
export PROLE_CONF="$CONF_DIR"
export PROLE_SERVICE="$SERVICE_DIR"
write_cfg "$REPO_ROOT"
export MOCK_CLUSTER_IMAGE="k3d-prole-registry:5000/knoe-db:18-140"
run_case "noop_initialize" 0 \
bash "$SCRIPT_UNDER_TEST" --mode k3d --namespace test-ns initialize prole-db 18-140
assert_not_called "create namespace"
###############################################
# Case 2: mismatch without --upgrade fails early
###############################################
export MOCK_CLUSTER_IMAGE="k3d-prole-registry:5000/knoe-db:18-138"
run_case "mismatch_initialize" 1 \
bash "$SCRIPT_UNDER_TEST" --mode k3d --namespace test-ns initialize prole-db 18-140
assert_not_called "create namespace"
###############################################
# Case 3: mismatch with --upgrade triggers restart hook (deploy)
###############################################
PROLE_HOME_UPGRADE="$TMP_DIR/prole_home"
mkdir -p "$PROLE_HOME_UPGRADE/deploy/opentofu/k3s/manifests/prole"
cat <<'Y_EOF' > "$PROLE_HOME_UPGRADE/deploy/opentofu/k3s/manifests/prole/prole-db.yaml"
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: prole-db
spec:
instances: 1
imageName: knoe-db:dummy
Y_EOF
cat <<'Y_EOF' > "$PROLE_HOME_UPGRADE/deploy/opentofu/k3s/manifests/prole/prole-db-barman-objectstore.yaml"
apiVersion: postgresql.cnpg.io/v1
kind: ObjectStore
metadata:
name: prole-db
spec: {}
Y_EOF
write_cfg "$PROLE_HOME_UPGRADE"
export MOCK_CLUSTER_IMAGE="k3d-prole-registry:5000/knoe-db:18-138"
run_case "upgrade_deploy" 0 \
bash "$SCRIPT_UNDER_TEST" --mode k3d --namespace test-ns --upgrade deploy 18-140
assert_called "cnpg restart"
echo "SUCCESS"

View File

@ -136,25 +136,25 @@ fi
# Runtime storage validation (k3s) expects live Cluster + PVC/PV objects.
if [[ "${args}" == *"-n test-ns"*"get cluster"*"prole-db"*"-o json"* ]]; then
cat <<'JSON'
{"apiVersion":"postgresql.cnpg.io/v1","kind":"Cluster","metadata":{"name":"prole-db"},"spec":{"storage":{"pvcTemplate":{"storageClassName":"prole-iscsi","selector":{"matchLabels":{"prole.storage/role":"data"}}}},"walStorage":{"pvcTemplate":{"storageClassName":"prole-iscsi","selector":{"matchLabels":{"prole.storage/role":"wal"}}}}}}
{"apiVersion":"postgresql.cnpg.io/v1","kind":"Cluster","metadata":{"name":"prole-db"},"spec":{"storage":{"pvcTemplate":{"storageClassName":"synology-iscsi","selector":{"matchLabels":{"synology.storage/role":"data"}}}},"walStorage":{"pvcTemplate":{"storageClassName":"synology-iscsi","selector":{"matchLabels":{"synology.storage/role":"wal"}}}}}}
JSON
exit 0
fi
if [[ "${args}" == *"-n test-ns"*"get pvc"*"cnpg.io/cluster=prole-db"*"-o json"* ]]; then
cat <<'JSON'
{"items":[
{"metadata":{"name":"prole-db-1"},"spec":{"storageClassName":"prole-iscsi","volumeName":"pv-data"}},
{"metadata":{"name":"prole-db-1-wal"},"spec":{"storageClassName":"prole-iscsi","volumeName":"pv-wal"}}
{"metadata":{"name":"prole-db-1"},"spec":{"storageClassName":"synology-iscsi","volumeName":"pv-data"}},
{"metadata":{"name":"prole-db-1-wal"},"spec":{"storageClassName":"synology-iscsi","volumeName":"pv-wal"}}
]}
JSON
exit 0
fi
if [[ "${args}" == "get pv pv-data -o json"* ]]; then
echo '{"spec":{"storageClassName":"prole-iscsi","local":{"path":"/prole/d001/data"}}}'
echo '{"spec":{"storageClassName":"synology-iscsi","local":{"path":"/synology/d001/data"}}}'
exit 0
fi
if [[ "${args}" == "get pv pv-wal -o json"* ]]; then
echo '{"spec":{"storageClassName":"prole-iscsi","local":{"path":"/prole/d001/wal"}}}'
echo '{"spec":{"storageClassName":"synology-iscsi","local":{"path":"/synology/d001/wal"}}}'
exit 0
fi
@ -279,13 +279,18 @@ if grep -qE '^\s*- key:\s*kubernetes\\.io/hostname\s*$|myrddin\\.prole\\.org' "$
sed -n '1,160p' "$TMP_DIR/applied.yaml" || true
exit 1
fi
if ! grep -qE '^\s*- key:\s*prole\.org/node-role\s*$' "$TMP_DIR/applied.yaml"; then
echo "FAILURE: expected node-role label affinity in applied manifest (default)"
if ! grep -qE '^\s*- key:\s*node\.kubernetes\.io/instance-type\s*$' "$TMP_DIR/applied.yaml"; then
echo "FAILURE: expected k3s instance-type node affinity in applied manifest (default)"
sed -n '1,160p' "$TMP_DIR/applied.yaml" || true
exit 1
fi
if ! grep -qE '^\s*podAntiAffinityType:\s*required\s*$' "$TMP_DIR/applied.yaml"; then
echo "FAILURE: expected podAntiAffinityType=required in applied manifest (default)"
if ! grep -qE '^\s*- k3s\s*$' "$TMP_DIR/applied.yaml"; then
echo "FAILURE: expected node affinity value k3s in applied manifest (default)"
sed -n '1,160p' "$TMP_DIR/applied.yaml" || true
exit 1
fi
if ! grep -qE '^\s*podAntiAffinityType:\s*preferred\s*$' "$TMP_DIR/applied.yaml"; then
echo "FAILURE: expected podAntiAffinityType=preferred in applied manifest (default)"
sed -n '1,160p' "$TMP_DIR/applied.yaml" || true
exit 1
fi

View File

@ -0,0 +1,155 @@
#!/usr/bin/env bash
# Regression test: in k3d mode, init_cloudnative_pg.sh should consult the k3s registry
# as an optional cache/source before falling back to a local build.
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
ETC_DIR="$REPO_ROOT/etc"
SCRIPT_UNDER_TEST="$ETC_DIR/init_cloudnative_pg.sh"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$BIN_DIR"
_log_file="$TMP_DIR/mock_calls.log"
export MOCK_LOG_FILE="$_log_file"
# Minimal kubectl mock so preflight-image path doesn't hang.
cat <<'K_EOF' > "$BIN_DIR/kubectl"
#!/usr/bin/env bash
echo "Mocked kubectl called with $*" >> "${MOCK_LOG_FILE}"
args="$*"
if [[ "${args}" == *"get --raw=/readyz"* || "${args}" == *"get --raw='/readyz'"* || "${args}" == *"get --raw=\"/readyz\""* ]]; then
echo "ok"
exit 0
fi
if [[ "${args}" == *"version --short"* ]]; then
echo "Client Version: v0.0.0"
echo "Server Version: v0.0.0"
exit 0
fi
exit 0
K_EOF
chmod +x "$BIN_DIR/kubectl"
# docker mock: pretend local images are missing, but the k3s registry has the image.
cat <<'D_EOF' > "$BIN_DIR/docker"
#!/usr/bin/env bash
echo "Mocked docker called with $*" >> "${MOCK_LOG_FILE}"
cmd1="${1:-}"
cmd2="${2:-}"
cmd3="${3:-}"
if [[ "$cmd1" == "image" && "$cmd2" == "inspect" ]]; then
# Force local inspect misses so the script tries the k3s registry.
exit 1
fi
if [[ "$cmd1" == "pull" ]]; then
# Succeed only for the expected k3s registry ref.
if [[ "$cmd2" == myrddin.prole.org:5000/knoe-db:* ]]; then
exit 0
fi
exit 1
fi
if [[ "$cmd1" == "build" || ( "$cmd1" == "buildx" && "$cmd2" == "build" ) ]]; then
echo "FAILURE: docker build should not be invoked when k3s registry contains the image" >&2
exit 99
fi
exit 0
D_EOF
chmod +x "$BIN_DIR/docker"
# k3d mock: allow cluster list without failing; we assert we don't need image import when push succeeds.
cat <<'K3D_EOF' > "$BIN_DIR/k3d"
#!/usr/bin/env bash
echo "Mocked k3d called with $*" >> "${MOCK_LOG_FILE}"
if [[ "${1:-}" == "cluster" && "${2:-}" == "list" ]]; then
echo "knoe-dev-cluster"
exit 0
fi
exit 0
K3D_EOF
chmod +x "$BIN_DIR/k3d"
# Other mocks used by the script in various branches.
for t in curl skopeo ansible-playbook tofu terraform ollama jq; do
cat <<M_EOF > "$BIN_DIR/$t"
#!/usr/bin/env bash
echo "Mocked $t called with \$@" >> "${_log_file}"
exit 0
M_EOF
chmod +x "$BIN_DIR/$t"
done
# Mock prole.cfg for k3d mode.
mkdir -p "$TMP_DIR/conf"
cat <<C_EOF > "$TMP_DIR/conf/prole.cfg"
[User]
NAMESPACE = test-ns
SERVICE_NAMESPACE = test-system
PROLE_HOME = $REPO_ROOT
PROLE_SERVICE = $TMP_DIR/service
[Global]
DEPLOYMENT_MODE = k3d
[Docker Build]
LOCAL_REGISTRY = localhost:5000
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000
C_EOF
mkdir -p "$TMP_DIR/service/secrets"
printf '%s' "dummy-private-key" > "$TMP_DIR/service/secrets/admin.key"
printf '%s' "dummy-public-key" > "$TMP_DIR/service/secrets/admin.pub"
export PATH="$BIN_DIR:$PATH"
export PROLE_HOME="$REPO_ROOT"
export PROLE_CONF="$TMP_DIR/conf"
export PROLE_PASSWD="test-password"
export PROLE_SERVICE="$TMP_DIR/service"
# Run a path that triggers image preflight.
bash "$SCRIPT_UNDER_TEST" --mode k3d preflight-image > "$TMP_DIR/stdout" 2> "$TMP_DIR/stderr"
RC=$?
# Assertions
if ! grep -q "Mocked docker called with pull myrddin.prole.org:5000/knoe-db:" "${_log_file}"; then
echo "FAILURE: expected a k3s-registry pull attempt in k3d mode" >&2
echo "--- mock calls ---" >&2
sed -n '1,200p' "${_log_file}" >&2 || true
exit 1
fi
if grep -q "Mocked docker called with build" "${_log_file}"; then
echo "FAILURE: did not expect docker build to run when cache image is present" >&2
echo "--- mock calls ---" >&2
sed -n '1,200p' "${_log_file}" >&2 || true
exit 1
fi
if grep -q "Mocked k3d called with image import" "${_log_file}"; then
echo "FAILURE: expected registry push path, not k3d image import fallback" >&2
echo "--- mock calls ---" >&2
sed -n '1,200p' "${_log_file}" >&2 || true
exit 1
fi
if [[ $RC -eq 0 || $RC -eq 1 || $RC -eq 2 ]]; then
echo "SUCCESS (RC=$RC)"
exit 0
fi
echo "FAILURE with RC $RC" >&2
echo "--- stdout ---" >&2
sed -n '1,200p' "$TMP_DIR/stdout" >&2 || true
echo "--- stderr ---" >&2
sed -n '1,200p' "$TMP_DIR/stderr" >&2 || true
exit 1

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Regression test: Garage startup should auto-recycle a Released prole-iscsi PV with a stale
# Regression test: Garage startup should auto-recycle a Released synology-iscsi PV with a stale
# claimRef when the PVC is Pending and rollout is stuck beyond the initial timeout.
set -euo pipefail
@ -23,7 +23,7 @@ cp "$SOURCE_ETC_DIR/prole_cfg.sh" "$WORK_DIR/etc/prole_cfg.sh"
chmod +x "$WORK_DIR/etc/init_garage_store.sh"
# Dummy manifests required by init_garage_store.sh
for f in storageclass-prole-iscsi.yaml iscsi-pvs.yaml garage-configmap.yaml garage-statefulset.yaml garage-service.yaml; do
for f in storageclass-synology-iscsi.yaml iscsi-pvs.yaml garage-configmap.yaml garage-statefulset.yaml garage-service.yaml; do
printf '%s\n' "apiVersion: v1" > "$WORK_DIR/k8s/prole/$f"
done
@ -86,7 +86,7 @@ case "$sub" in
# PV list for recycle helper
if [[ "$*" == *"get pv"*"-o jsonpath="* ]]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"prole-iscsi-d001-garage" "prole-iscsi" "Retain" "Released" "test-ns" "data-garage-0" "olduid"
"synology-iscsi-d001-garage" "synology-iscsi" "Retain" "Released" "test-ns" "data-garage-0" "olduid"
exit 0
fi
# Pod lookup for init_layout
@ -148,7 +148,7 @@ else
exit 1
fi
if ! grep -q "patch pv prole-iscsi-d001-garage" "$KUBECTL_LOG"; then
if ! grep -q "patch pv synology-iscsi-d001-garage" "$KUBECTL_LOG"; then
echo "FAILURE: expected PV claimRef recycle (kubectl patch pv) was not invoked" >&2
echo "--- kubectl log ---" >&2
sed -n '1,200p' "$KUBECTL_LOG" >&2 || true

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Regression test: OpenBao restart should auto-recycle a Released prole-iscsi PV with a stale
# Regression test: OpenBao restart should auto-recycle a Released synology-iscsi PV with a stale
# claimRef when the PVC is Pending and rollout is stuck beyond the initial timeout.
set -euo pipefail
@ -77,7 +77,7 @@ case "$sub" in
# PV list for recycle helper
if [[ "$*" == *"get pv"*"-o jsonpath="* ]]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"prole-iscsi-d001-openbao" "prole-iscsi" "Retain" "Released" "knoe-system" "data-openbao-0" "olduid"
"synology-iscsi-d001-openbao" "synology-iscsi" "Retain" "Released" "knoe-system" "data-openbao-0" "olduid"
exit 0
fi
exit 0
@ -116,7 +116,7 @@ else
exit 1
fi
if ! grep -q "patch pv prole-iscsi-d001-openbao" "$KUBECTL_LOG"; then
if ! grep -q "patch pv synology-iscsi-d001-openbao" "$KUBECTL_LOG"; then
echo "FAILURE: expected PV claimRef recycle (kubectl patch pv) was not invoked" >&2
echo "--- kubectl log ---" >&2
sed -n '1,200p' "$KUBECTL_LOG" >&2 || true

View File

@ -53,7 +53,7 @@ def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_healt
"uid": "new-garage-uid",
},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "20Gi"}},
},
@ -66,7 +66,7 @@ def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_healt
"uid": "new-bao-uid",
},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "20Gi"}},
},
@ -78,9 +78,9 @@ def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_healt
return {
"items": [
{
"metadata": {"name": "prole-iscsi-d001-garage"},
"metadata": {"name": "synology-iscsi-d001-garage"},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"capacity": {"storage": "20Gi"},
"accessModes": ["ReadWriteOnce"],
"claimRef": {
@ -92,9 +92,9 @@ def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_healt
"status": {"phase": "Released"},
},
{
"metadata": {"name": "prole-iscsi-d001-openbao"},
"metadata": {"name": "synology-iscsi-d001-openbao"},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"capacity": {"storage": "20Gi"},
"accessModes": ["ReadWriteOnce"],
"claimRef": {
@ -184,8 +184,8 @@ def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_healt
# Reclaim was attempted for matching stale Released PVs.
patched = [c for c in calls if c[:3] == ["kubectl", "patch", "pv"]]
assert any("prole-iscsi-d001-garage" in c for c in patched)
assert any("prole-iscsi-d001-openbao" in c for c in patched)
assert any("synology-iscsi-d001-garage" in c for c in patched)
assert any("synology-iscsi-d001-openbao" in c for c in patched)
# Explicit scheduling/storage classification is surfaced.
assert "Storage blockers" in msg

View File

@ -155,7 +155,7 @@ def test_reset_reclaim_stale_released_pvs_patches_concrete_garage_and_openbao(
"uid": "new-garage-uid",
},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "10Gi"}},
},
@ -168,7 +168,7 @@ def test_reset_reclaim_stale_released_pvs_patches_concrete_garage_and_openbao(
"uid": "new-bao-uid",
},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"accessModes": ["ReadWriteOnce"],
"resources": {"requests": {"storage": "10Gi"}},
},
@ -178,9 +178,9 @@ def test_reset_reclaim_stale_released_pvs_patches_concrete_garage_and_openbao(
released_pvs = [
{
"metadata": {"name": "prole-iscsi-d001-garage"},
"metadata": {"name": "synology-iscsi-d001-garage"},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"capacity": {"storage": "20Gi"},
"accessModes": ["ReadWriteOnce"],
"claimRef": {
@ -192,9 +192,9 @@ def test_reset_reclaim_stale_released_pvs_patches_concrete_garage_and_openbao(
"status": {"phase": "Released"},
},
{
"metadata": {"name": "prole-iscsi-d001-openbao"},
"metadata": {"name": "synology-iscsi-d001-openbao"},
"spec": {
"storageClassName": "prole-iscsi",
"storageClassName": "synology-iscsi",
"capacity": {"storage": "20Gi"},
"accessModes": ["ReadWriteOnce"],
"claimRef": {
@ -237,8 +237,8 @@ def test_reset_reclaim_stale_released_pvs_patches_concrete_garage_and_openbao(
assert changed is True
patched = [c for c in calls if c[:3] == ["kubectl", "patch", "pv"]]
assert any("prole-iscsi-d001-garage" in c for c in patched)
assert any("prole-iscsi-d001-openbao" in c for c in patched)
assert any("synology-iscsi-d001-garage" in c for c in patched)
assert any("synology-iscsi-d001-openbao" in c for c in patched)
assert all('{"spec":{"claimRef":null}}' in c for c in patched)
# Reset policy: never delete PVs.