diff --git a/conf/prod/prole.cfg b/conf/prod/prole.cfg
index e1ee4cc..398d37b 100644
--- a/conf/prod/prole.cfg
+++ b/conf/prod/prole.cfg
@@ -1,5 +1,5 @@
; Prole Master Configuration File
-; Generated by install.py on 2026-03-27 21:57:14
+; Generated by install.py on 2026-03-31 00:08:32
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
@@ -13,10 +13,12 @@ build.run_build = false
database_options.distribution = percona
database_options.ext.dblink = true
database_options.ext.pg_buffercache = true
+database_options.ext.pg_cron = 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.pg_tde = true
database_options.ext.pgaudit = true
database_options.ext.pgbadger = true
database_options.ext.pgcrypto = true
@@ -51,7 +53,7 @@ init_cluster.cluster_env = prod
init_cluster.deployment_target = prole-prod-cluster
init_cluster.gitops_enabled = false
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
-init_cluster.k3s_token = ${PROLE_SECRET:v1:WmcrTDHDhUyzeCC4:DvGQhp_kiVfcbpTCDncA_t-xH4ktjvCka7jst-7LjWLb-k0NicaNogLRwFT8b5_pJsPnBFnNhcrz-2iD4Zb0-L3i0gILy8LIwwJajRaic9qDNd01Wm_9kRjv_z2XRhFWiwGEm7m3sd__6BYkeKhBXE9lzii8WpGJgo9bAA==}
+init_cluster.k3s_token = ${PROLE_SECRET:v1:h4uThQOAj6ASFIFf:_cS5t3-Z3q90-OWiMsX5BnLQnjC8tbomoyuv_UfnNM6XmI2H6yofzJWSyhgNfgFaWxL8ImIwYWVKf9OjvwEhxr54LVSQ9EEm3KQsRW1cADHWnkjl7AGifsi2Wa9ve6E0PueVPAyHC5cIUXJ3Njt_bdegibhjZv6_-jFkGg==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k8s
init_cluster.start_cluster = true
@@ -62,14 +64,14 @@ init_db_build.run_build = true
init_password.cluster_name = ${CLUSTER_NAME}
init_password.db_host_port = 5432
init_password.db_namespace = ${DATABASE_NAMESPACE}
-init_password.db_password = ${PROLE_SECRET:v1:eN8L5Bo1KE2xGU-Z:y4ybETpw1POYjX2tP96lWTajMLFsiTXn5DNj7s_l6gBZInnGB5MjuxQTZ-G_FBnK}
-init_password.db_password_confirm = ${PROLE_SECRET:v1:eN8L5Bo1KE2xGU-Z:y4ybETpw1POYjX2tP96lWTajMLFsiTXn5DNj7s_l6gBZInnGB5MjuxQTZ-G_FBnK}
+init_password.db_password = ${PROLE_SECRET:v1:uLCCn7fAxP1AHcv7:zahrhbz50ngH59bVmJUvbCm5OTM1Id_ksd5D61I-GixJETQVMewsr1oC7Avu8m6E}
+init_password.db_password_confirm = ${PROLE_SECRET:v1:uLCCn7fAxP1AHcv7:zahrhbz50ngH59bVmJUvbCm5OTM1Id_ksd5D61I-GixJETQVMewsr1oC7Avu8m6E}
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:1UPXXGF4GW7MIfhR:_hFj8SAVHRd60qnOw-q5MaM9W8CaxNHgPkFnyXmNXXNzCj5x}
+kerberos_config.password = ${PROLE_SECRET:v1:O-RQRUsyZ-C-oV7k:-9LlOE5KxpT8qGApgzzH2IneU76fYbCk-GwTsAII1tzxK_P3}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false
kerberos_config.user = administrator
@@ -77,25 +79,28 @@ network_scan.run = true
ollama_config.model =
ollama_config.server_host = fairyland.prole.org
ollama_config.server_port = 11434
+supabase_config.pv_base_dir = /synology/d005
+supabase_config.pv_node = gandalf.prole.org
[Global]
; Variables used by name in more than one place or assumed global scope
ARGOCD_NAMESPACE = argocd
CLUSTER_ENV = prod
CLUSTER_NAME = knoe-db
-DATABASE_NAMESPACE = knoe-db-17-7-043-18-140-17-7-043-18-140
+DATABASE_NAMESPACE = knoe-db-17-7-043-18-140-17-7-043-18-140-17-7-043-18-140
DB_HOST_PORT = 5432
-DB_PASSWORD = ${PROLE_SECRET:v1:hk7ryF9uC0HMNmE0:ZFtwyX85sqA61qZDuSLEL1gWwuC20r6qCKnTihsGvJ7PODH5XcIRSQ5Ms3Il8lMt}
+DB_PASSWORD = ${PROLE_SECRET:v1:7FWWyYsJ27QWE7Hf:E99YJFxLGoz_QyHPH7odXmCk6WBuYL3tWU0iBdpcHmb1LBph_VBAa5GyHmH6LVYP}
DEPLOYMENT_MODE = k8s
DEPLOYMENT_TARGET = prole-prod-cluster
-DOCKER_IMPORT_DIR =
DOCKER_PRELOAD = false
KNOE_DB_USER = root
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
-PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:eRF7V3BH7FObAF2t:omapQMsXQQA765nO3w8Y7LY5Hn4C1Mrglwk9xyNHEpQoKPBgrbd8aGZwtPE3UgmT_i5eeW110q0aWJtR4kwuK5q6LR9Ec2iHknIJYXA-v6isvhsPK8az1A8rE9kIVcahABT9mSEFmdNPk-UNvISsxqtOZ98fCXoFbfWegg==}
-PROLE_OPENTOFU_URL = http://127.0.0.1:8080
+PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:dcAmcZGfYyHgaY_0:wtuc48pEhK9Ha7BC3XG57MCf_4Wqgs09g3Jam2VQIc_3jNMIEIxORsDxGJsqZtyN0zmdSxs1ApZos-h6h3R5AkEZsmqdw6TTcFpixP0RwuPWCe5ObWIMyPR3ztWcwDiM69fm1QiFUNGPvARdyw6dBgyRKRqDpVSfXMpM5w==}
SERVICE_NAMESPACE = knoe-system
+SUPABASE_PV_BASE = /synology/d005
+SUPABASE_PV_BASE_DIR = /synology/d005
+SUPABASE_PV_NODE = gandalf.prole.org
[Welcome]
; No configuration values captured yet for this section.
@@ -110,7 +115,7 @@ 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"]}
+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","gandalf.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","gandalf.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","gandalf.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"],"k3s_hosts":["gandalf.prole.org"]},"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":["gandalf.prole.org","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
@@ -133,7 +138,6 @@ PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
[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
@@ -152,23 +156,20 @@ GITOPS_ENABLED = False
[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:U7foZs_vBN4-2fLv:wFtVuuFMcJ-R9zXZwdOqxfPsTmRFZSkyvKc_Kqo_kL1-ie8MxWU2M25t78O7EjW9dFxEAYRsPanjxYKDHDd8arRW17kGIqtzlcRM9sXQJimTpa-mMgNCFew28b99c-LuCZbeG3nZU72joGZN6TZ5ZjZZbvJLAOA1Hq6b0A==}
+K3S_TOKEN = ${PROLE_SECRET:v1:ZBrRQReMetk0k4X5:yl6S40jqATR45Ivg5DxoGlo_32X7Q6Zz0BpKIzWO0iKpLe5xK4H-lYTxzucn6AsivxNrZTRjLsTiQ1CrtIcmtDJzOwPjobkt5t2XGKmcBrPoeoUjqSFJolp2-0ClawERDXTnqLHOQJhNLFpqxrRc_AiU0iHiT6DuuZZ9Kg==}
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.
diff --git a/conf/service/prole.cfg b/conf/service/prole.cfg
index ecc0c89..d623d16 100644
--- a/conf/service/prole.cfg
+++ b/conf/service/prole.cfg
@@ -1,5 +1,5 @@
; Prole Master Configuration File
-; Generated by install.py on 2026-03-29 22:54:52
+; Generated by install.py on 2026-03-31 00:16:16
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
@@ -53,7 +53,7 @@ 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:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
+init_cluster.k3s_token = ${PROLE_SECRET:v1:AL731QFqjjFGqfFT:Xt95lYCIk-6iCHerFSJQpKwWGJMdr04v-4DawlGbM3OBLJ7qbHOSZ_oJm1WtHkvqpoM7BSJNwlEqHMV7R3gl7IFqVUtqJ_AoaRcLSPKE2Z6JGWkIatR6Vzw3J1cm1iZiM2M9wikI5Wqp3t4cdWTOTf7PUaQHkYbBG1PV3w==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k3s
init_cluster.start_cluster = true
@@ -64,14 +64,14 @@ init_db_build.run_build = true
init_password.cluster_name = ${CLUSTER_NAME}
init_password.db_host_port = 5432
init_password.db_namespace = ${DATABASE_NAMESPACE}
-init_password.db_password = ${PROLE_SECRET:v1:4QVLv6L_Zb6w1Zja:4PHGYWVndekQb3ZG7hjNAoIQ_T2yEZlS}
-init_password.db_password_confirm = ${PROLE_SECRET:v1:4QVLv6L_Zb6w1Zja:4PHGYWVndekQb3ZG7hjNAoIQ_T2yEZlS}
+init_password.db_password = ${PROLE_SECRET:v1:OcWBTOiijqf-bfTT:6r-ioWdDRdkPNxmbEAQP2TYEPaycWasP}
+init_password.db_password_confirm = ${PROLE_SECRET:v1:OcWBTOiijqf-bfTT:6r-ioWdDRdkPNxmbEAQP2TYEPaycWasP}
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:lDmjQ4Cj45LYBD9_:9Aes8WNTZ97oYpi1aFvS8ODHDCQJJJCUBFgNrJBZ3a-Mgwp2}
+kerberos_config.password = ${PROLE_SECRET:v1:h9mzEWvGpnpU2FRz:5W8czWfjnRxzA8tJPk3eFRtrVLD5DxREkfqmQsnKLsbuAaec}
kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false
kerberos_config.user = administrator
@@ -79,6 +79,8 @@ network_scan.run = true
ollama_config.model =
ollama_config.server_host = fairyland.prole.org
ollama_config.server_port = 11434
+supabase_config.pv_base_dir = /synology/d005
+supabase_config.pv_node = gandalf.prole.org
[Global]
; Variables used by name in more than one place or assumed global scope
@@ -92,7 +94,7 @@ CNPG_PLACEMENT_PLAN_ID = cnpg-placement-7036c5f65b6ef0db
CNPG_STAGE1_NODE = gandalf.prole.org
DATABASE_NAMESPACE = knoe-db
DB_HOST_PORT = 5432
-DB_PASSWORD = ${PROLE_SECRET:v1:FgbkzVICzbA4WX7l:t5eAnQUYrTfAiXuUUPgZ8rBJVqAp-WFA}
+DB_PASSWORD = ${PROLE_SECRET:v1:M7Rt2Xp273X8ixb5:Rjys8A6SqmBv19DvPAvgihwJ0eJ8Xvda}
DEPLOYMENT_MODE = k3s
DEPLOYMENT_TARGET = prole-service-cluster
DOCKER_PRELOAD = false
@@ -100,8 +102,11 @@ KNOE_DB_USER = root
KUBECONTEXT = knoe.dev.prole.org
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
-PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
+PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:DkOrjdIC-ji9iusP:7uc4RUXpALlyQcGfZfX6LBP7BEwOqBtbbZpbIgdbfJvMQ_1klRdLtDQrcG54p6ayHvaMQ6W7FSPHPLeKbcjDZ8eJMhNMaH9Q0kIyfmiZdbX3nN6QGCxJWjIn-4GJ4z6_gF7hjHrqAAqxReNoWkAokyoVgqKTiOoWToD0GA==}
SERVICE_NAMESPACE = knoe-system
+SUPABASE_PV_BASE = /synology/d005
+SUPABASE_PV_BASE_DIR = /synology/d005
+SUPABASE_PV_NODE = gandalf.prole.org
SYNOLOGY_ROOTS = /synology/d001,/synology/d002,/synology/d003,/synology/d004,/synology/d005
[Welcome]
@@ -133,7 +138,7 @@ PROLE_LOGS = /Users/chrisfu/dev/prole/logs
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
[Monitoring]
-; No configuration values captured yet for this section.
+GRAFANA_ADMIN_PASSWORD = ${PROLE_SECRET:v1:st2uaIMwx55ftSPt:HjzyDuKpzGJyIDVzIKWD6YDzyoqZ4iJ9}
[Kerberos Authentication]
; No configuration values captured yet for this section.
@@ -158,7 +163,7 @@ SUPABASE_ENABLED = False
[Initialize Cluster]
ENVIRONMENT = service
K3S_SERVER_URL = https://myrddin.prole.org:6443
-K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
+K3S_TOKEN = ${PROLE_SECRET:v1:sQeWAX7UamXXV4oR:O7sUJk54DE9gUtZAjmN4uiusuVl0T3SDTn-S4Ojgg844EOQV7dMhE1wncgn8kLXXz_klqkQszDzbTqQm2HdaQuFyCqnHGCtHyUAyFvXSAdhhQ3PSkggylLtbPQPPrEP6WFuFywdKf1QgLl2BOcipREupBj5XDfiNfEAigw==}
[Dev Cluster (k3d)]
CLUSTER_ENV = dev
@@ -170,7 +175,7 @@ MODE = k3d
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
-K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
+K3S_TOKEN = ${PROLE_SECRET:v1:sast5Yrr_9mB5BzC:J8Et0XHuUbZbFzAovq-lgqdu_3rR4k3FHOJ7LmzCls-qcZV9D9_NNdmtw5gOTRA8kyZeP5oAFGAXKPGWHgxMJxGO_Lh2oQ1m_68_ExKCK7oeXxlZC3bFCd_fvQ-lP0thBYA9R-GkV_67tJfluOo632xhv6i3Pl1sgBZt5w==}
MODE = k3s
[Prod Cluster (k8s)]
@@ -183,7 +188,7 @@ MODE = k8s
; No configuration values captured yet for this section.
[Initialization Scripts]
-STATUS = Attempted
+STATUS = Completed
[Deployment]
MODE = k3s
diff --git a/install.py b/install.py
index 0a871d9..f405969 100755
--- a/install.py
+++ b/install.py
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
"""
Thin launch vehicle for the Prole installer.
-
-Keeps legacy UI available while exposing a stable import surface for tests.
"""
from __future__ import annotations
@@ -13,16 +11,16 @@ from tkinter import ttk, scrolledtext, messagebox, filedialog # noqa: F401
from pathlib import Path # noqa: F401
from knoe.ui.screens import (
- ProleInstaller,
- ProleController,
+ KnoeInstaller,
+ KnoeController,
get_resource_path,
has_display,
main,
)
__all__ = [
- "ProleInstaller",
- "ProleController",
+ "KnoeInstaller",
+ "KnoeController",
"get_resource_path",
"has_display",
"main",
diff --git a/knoe-db.iml b/knoe-db.iml
index 523eece..9bd88ac 100644
--- a/knoe-db.iml
+++ b/knoe-db.iml
@@ -2,10 +2,74 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/knoe/core/actions.py b/knoe/core/actions.py
index 797b51b..e975938 100644
--- a/knoe/core/actions.py
+++ b/knoe/core/actions.py
@@ -4,17 +4,16 @@ Installer actions and unattended workflow helpers.
from __future__ import annotations
-import json
-import logging
-import secrets
import shlex
import socket
-import string
import sys
import threading
import uuid
-from typing import Callable, Sequence
+import logging
+import secrets
+import string
+from knoe import prole_conf as prole_conf_mgr
from knoe.config import (
_expand_path,
_collect_cfg_vars,
@@ -28,8 +27,13 @@ from knoe.config import (
_encrypt_cfg_secret,
_merge_kubeconfig,
)
-from knoe import prole_conf as prole_conf_mgr
-from knoe.core.controller import ProleController
+from knoe.core.build_context import copy_build_context_dir
+from knoe.core.cnpg_placement import (
+ load_cnpg_placement_plan,
+ plan_cnpg_placement,
+ save_cnpg_placement_plan,
+)
+from knoe.core.controller import KnoeController
from knoe.core.env import * # noqa: F401,F403
from knoe.core.env import (
_parse_ollama_host,
@@ -57,7 +61,6 @@ from knoe.core.env import (
_render_prole_cfg,
_k3d_prole_data_volume_args,
_sync_opentofu_pipeline,
- _normalize_k3s_token,
_read_k3s_cfg,
_safe_str,
_expand_cfg_value as _expand_env_cfg_value,
@@ -80,34 +83,29 @@ from knoe.core.milestones import (
SupabaseImagePreloadMilestone,
SupabaseMilestone,
)
-from knoe.core.stream_exec import run_streaming_cmd
-from knoe.core.build_context import copy_build_context_dir
-from knoe.core.cnpg_placement import (
- load_cnpg_placement_plan,
- plan_cnpg_placement,
- save_cnpg_placement_plan,
-)
-from knoe.core.policy import (
- POLICY_CFG_KEY,
- OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
- evaluate_optional_workloads_allowed,
-)
-from knoe.core.ops.storage import (
- ClusterStorageSpec,
- StorageProvisioningError,
- provision_cluster_storage,
-)
+from knoe.core.ops import garage_store as garage_store_ops
+from knoe.core.ops import monitoring as monitoring_ops
+from knoe.core.ops import openbao as openbao_ops
+from knoe.core.ops import opentofu as opentofu_ops
+from knoe.core.ops import registry as registry_ops
from knoe.core.ops.cloudnative_pg import (
initialize as cnpg_initialize,
deploy as cnpg_deploy,
install_barman_plugin as cnpg_install_barman_plugin,
rollout as cnpg_rollout,
)
-from knoe.core.ops import openbao as openbao_ops
-from knoe.core.ops import registry as registry_ops
-from knoe.core.ops import garage_store as garage_store_ops
-from knoe.core.ops import opentofu as opentofu_ops
-from knoe.core.ops import monitoring as monitoring_ops
+from knoe.core.ops.storage import (
+ ClusterStorageSpec,
+ StorageProvisioningError,
+ provision_cluster_storage,
+)
+from knoe.core.policy import (
+ POLICY_CFG_KEY,
+ OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
+ evaluate_optional_workloads_allowed,
+)
+from knoe.core.stream_exec import run_streaming_cmd
+from typing import Callable, Sequence
def _configure_unbuffered_io():
@@ -157,7 +155,7 @@ def _select_existing_kubeconfig(candidates: Sequence[Path]) -> Path | None:
return first_existing
-class ProleInstaller:
+class KnoeInstaller:
"""Shared business logic for both silent and interactive installer modes.
Subclasses must provide a :meth:`_get_input` implementation that bridges
@@ -3095,16 +3093,17 @@ class _TeeWriter:
return getattr(self._original, name)
-class ProleConsoleInstaller(ProleInstaller):
+class KnoeConsoleInstaller(KnoeInstaller):
"""Console-based unattended installer driven by prole.cfg inputs."""
silent = True
def __init__(
self,
- controller: ProleController,
+ controller: KnoeController,
cfg_path: str | None = None,
reset_cluster: bool = False,
+ delete_db: bool = False,
):
self.controller = controller
self.project_root = controller.project_root
@@ -3114,6 +3113,7 @@ class ProleConsoleInstaller(ProleInstaller):
self.controller.cfg_path = self.cfg_path
self.reset_cluster = bool(reset_cluster)
self.reset_requested = bool(reset_cluster)
+ self.delete_db_requested = bool(delete_db)
# Keep a persistent indicator for reset-oriented reconcile actions (e.g.
# reclaiming safe stale Released PV claimRefs). `_perform_cluster_reset()`
# consumes `reset_requested` and clears it, but we still want to run
@@ -6025,9 +6025,15 @@ class ProleConsoleInstaller(ProleInstaller):
# Ensure we have a cert-based kubeconfig (or a valid bearer token config)
# before invoking any kubectl-based reset scripts.
self._ensure_project_k3s_kubeconfig(server, token)
- self.log(
- f"[RESET] Namespaces to clean (PVCs preserved): {', '.join(reset_namespaces)}"
- )
+ if self.delete_db_requested:
+ self.log(f"[RESET] Namespaces to clean: {', '.join(reset_namespaces)}")
+ self.log(
+ f"[RESET] --delete-db enabled; database PVCs in namespace '{db_ns}' will be deleted."
+ )
+ else:
+ self.log(
+ f"[RESET] Namespaces to clean (PVCs preserved): {', '.join(reset_namespaces)}"
+ )
self.log(
f"[RESET] Preserving registry workload in namespace: {registry_ns}"
)
@@ -6038,10 +6044,13 @@ class ProleConsoleInstaller(ProleInstaller):
token,
registry_namespace=registry_ns,
clear_node_reservations=True,
+ delete_pvcs_namespaces=[db_ns] if self.delete_db_requested else None,
+ force_delete_bound_pvs=self.delete_db_requested,
)
self._cleanup_local_k3s_artifacts()
self.reset_requested = False
+ self.delete_db_requested = False
def _cleanup_local_k3s_artifacts(self) -> None:
paths = [
@@ -6571,7 +6580,7 @@ def _run_silent_install_test(project_root: Path, cfg_path: Path) -> int:
def _attempt_k3s_repair(
- controller: ProleController,
+ controller: KnoeController,
namespace: str,
server: str,
token: str,
@@ -6617,6 +6626,8 @@ def _reset_k3s_namespace(
*,
registry_namespace: str | None = None,
clear_node_reservations: bool = False,
+ delete_pvcs_namespaces: Sequence[str] | None = None,
+ force_delete_bound_pvs: bool = False,
) -> None:
script = project_root / "scripts" / "reset-ns.sh"
if not script.exists():
@@ -6649,10 +6660,82 @@ def _reset_k3s_namespace(
env["KUBECONFIG"] = str(kubeconfig_path)
reg_ns = (registry_namespace or "").strip()
+ delete_pvcs_set = {
+ str(ns or "").strip()
+ for ns in (delete_pvcs_namespaces or [])
+ if str(ns or "").strip()
+ }
+
+ def _collect_bound_pvs(target_ns: str) -> list[str]:
+ res = subprocess.run(
+ [
+ "kubectl",
+ "-n",
+ target_ns,
+ "get",
+ "pvc",
+ "-o",
+ "jsonpath={range .items[*]}{.spec.volumeName}{\"\\n\"}{end}",
+ ],
+ env=env,
+ capture_output=True,
+ text=True,
+ )
+ if res.returncode != 0:
+ print(
+ f"[WARN] Could not list PVC-bound PVs in {target_ns} (code {res.returncode}).",
+ file=sys.stderr,
+ )
+ return []
+ pvs: list[str] = []
+ for line in (res.stdout or "").splitlines():
+ pv = (line or "").strip()
+ if pv and pv not in pvs:
+ pvs.append(pv)
+ return pvs
+
+ def _set_reclaim_policy_delete(pvs: Sequence[str]) -> None:
+ for pv in pvs:
+ patch = '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
+ res = subprocess.run(
+ ["kubectl", "patch", "pv", pv, "--type=merge", "-p", patch],
+ env=env,
+ capture_output=True,
+ text=True,
+ )
+ if res.returncode != 0:
+ print(
+ f"[WARN] Failed to set reclaimPolicy=Delete for pv/{pv} (code {res.returncode}).",
+ file=sys.stderr,
+ )
+
+ def _delete_pvs(pvs: Sequence[str]) -> None:
+ for pv in pvs:
+ res = subprocess.run(
+ ["kubectl", "delete", "pv", pv, "--ignore-not-found", "--wait=false"],
+ env=env,
+ capture_output=True,
+ text=True,
+ )
+ if res.returncode != 0:
+ print(
+ f"[WARN] Failed to delete pv/{pv} (code {res.returncode}).",
+ file=sys.stderr,
+ )
+
for idx, ns in enumerate(namespaces):
+ delete_pvcs = ns in delete_pvcs_set
+ bound_pvs: list[str] = []
+ if delete_pvcs and force_delete_bound_pvs:
+ bound_pvs = _collect_bound_pvs(ns)
+ if bound_pvs:
+ _set_reclaim_policy_delete(bound_pvs)
+
cmd = ["bash", str(script), "-n", ns]
if reg_ns and ns == reg_ns:
cmd.append("--keep-registry")
+ if delete_pvcs:
+ cmd.append("--delete-pvcs")
if clear_node_reservations and idx == (len(namespaces) - 1):
cmd.append("--clear-node-reservations")
res = subprocess.run(cmd, env=env)
@@ -6661,10 +6744,13 @@ def _reset_k3s_namespace(
f"[WARN] Namespace reset failed for {ns} (code {res.returncode})",
file=sys.stderr,
)
+ continue
+ if delete_pvcs and force_delete_bound_pvs and bound_pvs:
+ _delete_pvs(bound_pvs)
def _prepare_k3s_pipeline(
- controller: ProleController, log_fn=None, skip_validation: bool = False
+ controller: KnoeController, log_fn=None, skip_validation: bool = False
) -> int:
"""Prepare OpenTofu k3s pipeline: stage manifests, tfvars, ArgoCD apps.
@@ -6702,7 +6788,7 @@ def _prepare_k3s_pipeline(
# Load config to resolve namespace and secrets for pipeline tfvars
_log("==> Loading knoe config...\n")
- knoe = ProleConsoleInstaller(controller, str(cfg_path))
+ knoe = KnoeConsoleInstaller(controller, str(cfg_path))
try:
existing_inputs = knoe._load_inputs_from_cfg()
except Exception:
diff --git a/knoe/core/controller.py b/knoe/core/controller.py
index efe14a2..c9c9fd1 100644
--- a/knoe/core/controller.py
+++ b/knoe/core/controller.py
@@ -14,14 +14,14 @@ if TYPE_CHECKING:
from knoe.milestone import Milestone
-class ProleController:
+class KnoeController:
"""Business logic for Prole installer, separated from UI."""
def __init__(self, project_root: Path, verbose: bool = False, cfg_path: Path | None = None):
self.project_root = project_root
self.verbose = verbose
self.cfg_path = cfg_path
- self.logger = logging.getLogger("ProleController")
+ self.logger = logging.getLogger("KnoeController")
self.state = InstallerState(controller=self)
def check_docker_running(self) -> bool:
diff --git a/knoe/core/monitor.py b/knoe/core/monitor.py
index 39a9d17..ca5ac0f 100644
--- a/knoe/core/monitor.py
+++ b/knoe/core/monitor.py
@@ -9,13 +9,10 @@ import sys
import time
from pathlib import Path
-from knoe.config import _expand_path, _collect_cfg_vars, _expand_cfg_value
from knoe import prole_conf
+from knoe.config import _expand_path, _collect_cfg_vars, _expand_cfg_value
from knoe.core.env import (
_build_required_port_forwards,
- _pf_extract_id,
- _pf_mapping_str,
- _pf_upsert_mapping,
)
diff --git a/knoe/core/ops/cloudnative_pg.py b/knoe/core/ops/cloudnative_pg.py
index 9786595..9771b81 100644
--- a/knoe/core/ops/cloudnative_pg.py
+++ b/knoe/core/ops/cloudnative_pg.py
@@ -21,23 +21,23 @@ Public API
rollout(namespace, cluster_name, env, log) -> None
"""
from __future__ import annotations
+
import base64
import datetime
-import json
-import logging
import subprocess
import time
from pathlib import Path
-from typing import Callable
+import json
+import logging
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.ec import (
- ECDSA,
SECP256R1,
generate_private_key,
)
from cryptography.x509.oid import NameOID
+from typing import Callable
_LOG = logging.getLogger(__name__)
_LogFn = Callable[[str], None]
diff --git a/knoe/core/repair_pipeline_cli.py b/knoe/core/repair_pipeline_cli.py
index 662558d..739463b 100644
--- a/knoe/core/repair_pipeline_cli.py
+++ b/knoe/core/repair_pipeline_cli.py
@@ -6,8 +6,8 @@ import sys
from pathlib import Path
from knoe import prole_conf
-from knoe.core.controller import ProleController
-from knoe.core.actions import ProleConsoleInstaller
+from knoe.core.controller import KnoeController
+from knoe.core.actions import KnoeConsoleInstaller
def _project_root() -> Path:
@@ -80,8 +80,8 @@ def main(argv: list[str] | None = None) -> int:
if not cfg:
cfg = str(prole_conf.entrypoint_path(prole_conf.resolve_prole_conf_dir(root)))
- controller = ProleController(project_root=root, verbose=bool(args.verbose), cfg_path=Path(cfg))
- installer = ProleConsoleInstaller(controller, cfg_path=cfg)
+ controller = KnoeController(project_root=root, verbose=bool(args.verbose), cfg_path=Path(cfg))
+ installer = KnoeConsoleInstaller(controller, cfg_path=cfg)
try:
existing_inputs = knoe._load_inputs_from_cfg()
diff --git a/knoe/ncurses_installer.py b/knoe/ncurses_installer.py
index 2575bc9..41736e0 100644
--- a/knoe/ncurses_installer.py
+++ b/knoe/ncurses_installer.py
@@ -18,22 +18,21 @@ import time
from pathlib import Path
from . import config as inst_config
-from .ncurses_ui import CursesWindow, NavFooter, InputField
-from .core.stream_exec import run_streaming_cmd
+from .config import _write_k3s_kubeconfig, _merge_kubeconfig
from .core.env import (
_resolve_k3s_connection,
_verify_k3s_services_status,
_find_kubeconfig_file,
- _read_k3s_cfg,
resolve_prole_home,
)
from .core.ops import openbao as openbao_ops
-from .core.ops import registry as registry_ops
from .core.ops import opentofu as opentofu_ops
-from .config import _write_k3s_kubeconfig, _merge_kubeconfig
+from .core.ops import registry as registry_ops
+from .core.stream_exec import run_streaming_cmd
+from .ncurses_ui import CursesWindow, NavFooter, InputField
-class ProleNcursesInstaller:
+class KnoeNcursesInstaller:
"""Ncurses-based installer matching install.py screen flow."""
def __init__(self, stdscr, controller):
@@ -974,7 +973,7 @@ def run_ncurses_installer(controller):
"""Entry point for ncurses installer."""
def _main(stdscr):
- installer = ProleNcursesInstaller(stdscr, controller)
+ installer = KnoeNcursesInstaller(stdscr, controller)
knoe.run()
try:
diff --git a/knoe/ui/screens/__init__.py b/knoe/ui/screens/__init__.py
index f67d585..824b458 100644
--- a/knoe/ui/screens/__init__.py
+++ b/knoe/ui/screens/__init__.py
@@ -1,7 +1,7 @@
"""
Prole Service Dependencies Installer — screen package.
-This package splits the monolithic ``ProleInstaller`` class into cohesive
+This package splits the monolithic ``KnoeInstaller`` class into cohesive
mixin modules, one per logical screen / concern. The composed class is
assembled here and re-exported so that external callers (``install.py``,
tests) see the same public surface as before.
@@ -10,7 +10,6 @@ tests) see the same public surface as before.
from __future__ import annotations
import configparser
-import platform
import signal
import socket
import threading
@@ -18,45 +17,18 @@ import tkinter as tk
import uuid
from tkinter import ttk, messagebox, filedialog
-from knoe import deploy as inst_deploy
-from knoe import prole_conf
+import platform
from knoe import screen as ui
-from knoe.build import get_build_command as inst_get_build_command
from knoe.config import (
_expand_cfg_value,
_collect_cfg_vars,
- _is_prole_secret,
- _decrypt_prole_secret,
- _is_openbao_ref,
- _write_k3s_kubeconfig,
- _encrypt_cfg_secret,
- _encrypt_prole_secret,
)
-from knoe.core.controller import ProleController
+from knoe.core.controller import KnoeController
from knoe.core.env import (
- _detect_ansible_topology,
- _format_ansible_topology_summary,
- _normalize_k3s_token,
_deployment_target_label,
- _cluster_env_radio_value,
- _find_kubeconfig_file,
- _bool_str,
- _looks_like_k8s_bearer_token,
- _deployment_mode_from_env,
- _pf_extract_id,
_ensure_ansible_vault_credentials,
- _openbao_placeholder,
- _pf_upsert_mapping,
- _default_opentofu_pipeline_url,
- _build_required_port_forwards,
- _render_prole_cfg,
_safe_str,
_normalize_cluster_env,
- _k3d_prole_data_volume_args,
- _push_docker_image,
- _http_ping_registry,
- _local_registry_enabled,
- _collect_images_from_files,
)
# On macOS, set process name early so the menu bar reads correctly.
@@ -70,10 +42,9 @@ if platform.system() == "Darwin":
from knoe.core.env import * # noqa: F401,F403
from knoe.core.actions import (
- ProleInstaller,
- ProleConsoleInstaller,
+ KnoeInstaller,
+ KnoeConsoleInstaller,
_prepare_k3s_pipeline,
- _reset_k3s_namespace,
)
# Keep global frames list in memory (legacy requirement).
@@ -108,8 +79,8 @@ from knoe.ui.screens.cfg import ConfigMixin
# ---------------------------------------------------------------------------
# Composed installer class
# ---------------------------------------------------------------------------
-class ProleInstaller(
- ProleInstaller,
+class KnoeInstaller(
+ KnoeInstaller,
ScreenBaseMixin,
NavigationMixin,
WelcomeScreenMixin,
@@ -212,7 +183,7 @@ class ProleInstaller(
conf_dir = prole_conf.resolve_prole_conf_dir(PROJECT_ROOT)
resolved_cfg = prole_conf.entrypoint_path(conf_dir)
- self.controller = ProleController(
+ self.controller = KnoeController(
PROJECT_ROOT, verbose=verbose, cfg_path=resolved_cfg
)
self.project_root = PROJECT_ROOT
@@ -734,6 +705,21 @@ class ProleInstaller(
self.kerberos_password = tk.StringVar()
self.kerberos_kdc = tk.StringVar()
self.supabase_enabled = tk.BooleanVar(value=False)
+ _global_cfg = self.prole_cfg_data.get("Global", {}) or {}
+ _supabase_pv_node = (
+ (_global_cfg.get("SUPABASE_PV_NODE", "") or "").strip()
+ or (os.environ.get("SUPABASE_PV_NODE") or "").strip()
+ or "gandalf.prole.org"
+ )
+ _supabase_pv_base = (
+ (_global_cfg.get("SUPABASE_PV_BASE_DIR", "") or "").strip()
+ or (_global_cfg.get("SUPABASE_PV_BASE", "") or "").strip()
+ or (os.environ.get("SUPABASE_PV_BASE_DIR") or "").strip()
+ or (os.environ.get("SUPABASE_PV_BASE") or "").strip()
+ or "/synology/d005"
+ )
+ self.supabase_pv_node = tk.StringVar(value=_supabase_pv_node)
+ self.supabase_pv_base_dir = tk.StringVar(value=_supabase_pv_base)
self.at_rest_encryption_enabled = tk.BooleanVar(value=True)
self.ollama_server_host = tk.StringVar()
self.ollama_server_port = tk.StringVar(value=DEFAULT_OLLAMA_PORT)
@@ -891,6 +877,12 @@ def main():
action="store_true",
help="Reset k3d dev cluster or k3s service cluster",
)
+ parser.add_argument(
+ "--delete-db",
+ action="store_true",
+ default=False,
+ help=argparse.SUPPRESS,
+ )
parser.add_argument(
"--no-gui",
action="store_true",
@@ -919,6 +911,9 @@ def main():
parser.add_argument("command_args", nargs="*", help="Arguments for the subcommand")
args = parser.parse_args()
+ if args.delete_db and not args.reset:
+ parser.error("--delete-db requires --reset")
+
# Set up log file tee if --log is requested
log_file_path = None
if args.log is not None:
@@ -933,7 +928,7 @@ def main():
setup_logging(verbose=args.verbose, debug=args.debug)
- controller = ProleController(
+ controller = KnoeController(
PROJECT_ROOT,
verbose=args.verbose,
cfg_path=Path(args.config).expanduser() if args.config else None,
@@ -953,8 +948,11 @@ def main():
if args.silent:
_ensure_ansible_vault_credentials(prompt_ui=False)
- installer = ProleConsoleInstaller(
- controller, args.config, reset_cluster=args.reset
+ installer = KnoeConsoleInstaller(
+ controller,
+ args.config,
+ reset_cluster=args.reset,
+ delete_db=args.delete_db,
)
if log_file_path:
installer.enable_log_file(log_file_path)
@@ -994,7 +992,7 @@ def main():
except Exception:
root.geometry(f"{window_width}x{window_height}")
_ensure_ansible_vault_credentials(prompt_ui=True, root=root)
- ProleInstaller(
+ KnoeInstaller(
root,
config_path=args.config,
reset_cluster=args.reset,
diff --git a/knoe/ui/screens/base.py b/knoe/ui/screens/base.py
index 57c822a..1e6e8a3 100644
--- a/knoe/ui/screens/base.py
+++ b/knoe/ui/screens/base.py
@@ -2,10 +2,10 @@
import os
import subprocess
-from datetime import datetime
import tkinter as tk
+from datetime import datetime
from tkinter import ttk, messagebox, filedialog
-from knoe.screen import TerminalConsole
+
from knoe import screen as ui
diff --git a/knoe/ui/screens/build.py b/knoe/ui/screens/build.py
index 7a9ca3d..ca9b973 100644
--- a/knoe/ui/screens/build.py
+++ b/knoe/ui/screens/build.py
@@ -1,20 +1,20 @@
"""Build screen, terminal console overlay and process management."""
import os
-import platform
import re
import shlex
import signal
import subprocess
import threading
import time
-import webbrowser
import tkinter as tk
+import webbrowser
from tkinter import ttk, messagebox, filedialog
-from knoe.screen import TerminalConsole
+
+import platform
from knoe import deploy as inst_deploy
-from knoe.build import get_build_command as inst_get_build_command
from knoe import screen as ui
+from knoe.build import get_build_command as inst_get_build_command
from knoe.core.env import PROJECT_ROOT
diff --git a/knoe/ui/screens/cfg.py b/knoe/ui/screens/cfg.py
index 95c0cc6..ac3da57 100644
--- a/knoe/ui/screens/cfg.py
+++ b/knoe/ui/screens/cfg.py
@@ -2,14 +2,17 @@
import configparser
import os
-import platform
import socket
-import string
import subprocess
-from pathlib import Path
import tkinter as tk
+from pathlib import Path
from tkinter import ttk, messagebox, filedialog
-from knoe import prole_conf
+
+import platform
+from knoe.config import (
+ _encrypt_cfg_secret,
+ _filter_cfg_values_for_persistence,
+)
from knoe.core.env import (
DEFAULT_ACTION_FLAGS,
DEFAULT_OLLAMA_PORT,
@@ -28,12 +31,6 @@ from knoe.core.env import (
_read_k3s_cfg,
_render_prole_cfg,
)
-from knoe.config import (
- _collect_cfg_vars,
- _encrypt_cfg_secret,
- _expand_cfg_value,
- _filter_cfg_values_for_persistence,
-)
class ConfigMixin:
@@ -112,6 +109,9 @@ class ConfigMixin:
"DATABASE_NAMESPACE": (self.db_namespace.get() or "").strip(),
"CLUSTER_NAME": (self.cnpg_cluster_name.get() or "").strip(),
"DB_HOST_PORT": (self.db_host_port.get() or "5432").strip(),
+ "SUPABASE_PV_NODE": (self.supabase_pv_node.get() or "").strip(),
+ "SUPABASE_PV_BASE": (self.supabase_pv_base_dir.get() or "").strip(),
+ "SUPABASE_PV_BASE_DIR": (self.supabase_pv_base_dir.get() or "").strip(),
"PROLE_OPENTOFU_URL": _default_opentofu_pipeline_url(),
}
if prole_home_override:
@@ -477,6 +477,8 @@ class ConfigMixin:
DEFAULT_ACTION_FLAGS.get("init_cluster.start_cluster", True),
),
)
+ _set("supabase_config.pv_node", _get_var(self.supabase_pv_node, ""))
+ _set("supabase_config.pv_base_dir", _get_var(self.supabase_pv_base_dir, ""))
# Kerberos config
_set_bool("kerberos_config.enabled", _get_var(self.kerberos_enabled, False))
diff --git a/knoe/ui/screens/cluster.py b/knoe/ui/screens/cluster.py
index a4ffc22..0271caa 100644
--- a/knoe/ui/screens/cluster.py
+++ b/knoe/ui/screens/cluster.py
@@ -1,34 +1,18 @@
"""Cluster lifecycle management (k3d / k3s / k8s)."""
-import json
import os
import re
import subprocess
import sys
import threading
import time
-import urllib.error
-import urllib.request
-from pathlib import Path
import tkinter as tk
+from pathlib import Path
from tkinter import ttk, messagebox, filedialog
-from knoe.screen import TerminalConsole
-from knoe import screen as ui
+
+import json
from knoe import prole_conf
-from knoe.core.env import (
- PROJECT_ROOT,
- _default_opentofu_pipeline_url,
- _deployment_mode_from_env,
- _deployment_target_label,
- _find_kubeconfig_file,
- _k3d_prole_data_volume_args,
- _kubectl_base_cmd_for_k3s as _kubectl_base_cmd_for_k3s_fn,
- _looks_like_k8s_bearer_token,
- _normalize_cluster_env,
- _normalize_k3s_token,
- _resolve_k3s_connection as _resolve_k3s_connection_fn,
- _safe_str,
-)
+from knoe import screen as ui
from knoe.config import (
_collect_cfg_vars_from_data,
_encrypt_cfg_secret,
@@ -37,6 +21,18 @@ from knoe.config import (
_write_k3s_kubeconfig,
)
from knoe.core.actions import _reset_k3s_namespace
+from knoe.core.env import (
+ PROJECT_ROOT,
+ _default_opentofu_pipeline_url,
+ _deployment_mode_from_env,
+ _deployment_target_label,
+ _find_kubeconfig_file,
+ _k3d_prole_data_volume_args,
+ _normalize_cluster_env,
+ _normalize_k3s_token,
+ _resolve_k3s_connection as _resolve_k3s_connection_fn,
+ _safe_str,
+)
from knoe.core.prod_config import ProdConfigApi, build_config, to_api_payload
@@ -801,7 +797,10 @@ class ClusterScreenMixin:
self._k3s_service_status_console = status_console
self._k3s_service_deploy_console = deploy_console
- self._common_services_success = bool(getattr(self, "_all_services_green", False))
+ self._common_services_success = bool(
+ getattr(self, "_common_services_success", False)
+ or getattr(self, "_all_services_green", False)
+ )
self.update_footer()
self._verify_k3s_services()
@@ -845,7 +844,7 @@ class ClusterScreenMixin:
return key
def _is_valid_k8s_namespace(self, value: str | None) -> bool:
- ns = (value or "").strip()
+ ns = _safe_str(value).strip()
if not ns or len(ns) > 63:
return False
return re.fullmatch(r"[a-z0-9]([-a-z0-9]*[a-z0-9])?", ns) is not None
diff --git a/knoe/ui/screens/database_options.py b/knoe/ui/screens/database_options.py
index 57cbdcd..e757561 100644
--- a/knoe/ui/screens/database_options.py
+++ b/knoe/ui/screens/database_options.py
@@ -1,13 +1,13 @@
"""Database Options screen for selecting distribution, version and extensions."""
-import json
import os
import re
import threading
-from pathlib import Path
import tkinter as tk
+from pathlib import Path
from tkinter import ttk, messagebox
+import json
from knoe import screen as ui
from knoe.core.build_context import copy_build_context_dir
from knoe.core.env import (
@@ -18,6 +18,7 @@ from knoe.core.env import (
resolve_prole_home,
)
+
class DatabaseOptionsScreenMixin:
"""Mixin for the Database Options screen."""
diff --git a/knoe/ui/screens/deploy.py b/knoe/ui/screens/deploy.py
index 2aa1d2b..19db267 100644
--- a/knoe/ui/screens/deploy.py
+++ b/knoe/ui/screens/deploy.py
@@ -2,12 +2,12 @@
import os
import plistlib
-import secrets
import subprocess
import threading
-from pathlib import Path
import tkinter as tk
+from pathlib import Path
from tkinter import ttk, messagebox, filedialog
+
from knoe import screen as ui
from knoe.core.env import PROJECT_ROOT
diff --git a/knoe/ui/screens/docker.py b/knoe/ui/screens/docker.py
index 6e49a8a..98b5f99 100644
--- a/knoe/ui/screens/docker.py
+++ b/knoe/ui/screens/docker.py
@@ -1,7 +1,6 @@
"""Docker image build, registry management and image pre-pull."""
import os
-import platform
import re
import shutil
import subprocess
@@ -12,7 +11,6 @@ from pathlib import Path
from knoe.config import get_docker_build_platform_args
from knoe.core.env import (
PROJECT_ROOT,
- _collect_images_from_files,
_deployment_mode_from_env,
_find_kubeconfig_file,
_host_from_url,
@@ -21,7 +19,6 @@ from knoe.core.env import (
_local_registry_enabled,
_normalize_cluster_env,
_push_docker_image,
- _resolve_supabase_home,
)
diff --git a/knoe/ui/screens/packaging.py b/knoe/ui/screens/packaging.py
index fa82d43..44587d7 100644
--- a/knoe/ui/screens/packaging.py
+++ b/knoe/ui/screens/packaging.py
@@ -1,20 +1,20 @@
"""Disk selection, installer packaging, DMG creation and build summary."""
import os
-import platform
import plistlib
import shutil
import subprocess
import sys
import threading
import time
+import tkinter as tk
import webbrowser
from pathlib import Path
-import tkinter as tk
from tkinter import ttk, messagebox, filedialog
-from knoe.screen import TerminalConsole
-from knoe import screen as ui
+
+import platform
from knoe import config as inst_config
+from knoe import screen as ui
from knoe.core.env import PROJECT_ROOT, get_resource_path
diff --git a/knoe/ui/screens/services.py b/knoe/ui/screens/services.py
index 7e0a463..33e7f74 100644
--- a/knoe/ui/screens/services.py
+++ b/knoe/ui/screens/services.py
@@ -1,18 +1,17 @@
"""Initialization scripts, CNPG deployment and service-layer overlays."""
import os
-import platform
-import secrets
import shlex
import subprocess
import threading
import time
-import webbrowser
-from pathlib import Path
-from datetime import datetime
import tkinter as tk
+import webbrowser
+from datetime import datetime
+from pathlib import Path
from tkinter import ttk, messagebox, filedialog
-from knoe.screen import TerminalConsole
+
+import platform
from knoe import screen as ui
from knoe.core.env import (
PROJECT_ROOT,
@@ -21,21 +20,21 @@ from knoe.core.env import (
_deployment_target_label,
_normalize_cluster_env,
)
-from knoe.core.policy import (
- POLICY_CFG_KEY,
- OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
- evaluate_optional_workloads_allowed,
-)
+from knoe.core.ops import garage_store as garage_store_ops
+from knoe.core.ops import monitoring as monitoring_ops
+from knoe.core.ops import openbao as openbao_ops
+from knoe.core.ops import opentofu as opentofu_ops
+from knoe.core.ops import registry as registry_ops
from knoe.core.ops.cloudnative_pg import (
initialize as cnpg_initialize,
deploy as cnpg_deploy,
rollout as cnpg_rollout,
)
-from knoe.core.ops import openbao as openbao_ops
-from knoe.core.ops import registry as registry_ops
-from knoe.core.ops import garage_store as garage_store_ops
-from knoe.core.ops import opentofu as opentofu_ops
-from knoe.core.ops import monitoring as monitoring_ops
+from knoe.core.policy import (
+ POLICY_CFG_KEY,
+ OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES,
+ evaluate_optional_workloads_allowed,
+)
class ServicesScreenMixin:
diff --git a/knoe/ui/screens/supabase.py b/knoe/ui/screens/supabase.py
index a3e81a4..3ce8e35 100644
--- a/knoe/ui/screens/supabase.py
+++ b/knoe/ui/screens/supabase.py
@@ -63,6 +63,54 @@ class SupabaseScreenMixin:
self._overlay_widgets.append(enable_cb)
y = 280
+ node_label = ui.canvas_text(
+ self,
+ 48,
+ y,
+ "Node selector:",
+ fill="black",
+ font=("SF Pro Text", 12),
+ )
+ self._canvas_items.append(node_label)
+ node_entry = tk.Entry(
+ self.bg_canvas,
+ textvariable=self.supabase_pv_node,
+ font=("SF Pro Text", 11),
+ width=38,
+ relief="solid",
+ bd=1,
+ )
+ node_entry_window = self.bg_canvas.create_window(
+ 190, y - 2, window=node_entry, anchor="nw"
+ )
+ self._canvas_items.append(node_entry_window)
+ self._overlay_widgets.append(node_entry)
+
+ y += 36
+ storage_label = ui.canvas_text(
+ self,
+ 48,
+ y,
+ "Storage directory:",
+ fill="black",
+ font=("SF Pro Text", 12),
+ )
+ self._canvas_items.append(storage_label)
+ storage_entry = tk.Entry(
+ self.bg_canvas,
+ textvariable=self.supabase_pv_base_dir,
+ font=("SF Pro Text", 11),
+ width=38,
+ relief="solid",
+ bd=1,
+ )
+ storage_entry_window = self.bg_canvas.create_window(
+ 190, y - 2, window=storage_entry, anchor="nw"
+ )
+ self._canvas_items.append(storage_entry_window)
+ self._overlay_widgets.append(storage_entry)
+
+ y += 42
initial_status = "Ready" if self.supabase_enabled.get() else "Disabled"
self._supabase_status_var = tk.StringVar(value=initial_status)
status_item = ui.canvas_text(
@@ -86,10 +134,10 @@ class SupabaseScreenMixin:
# Standardized Console Output
self._supabase_console = self._create_console_output(
- y=320, title="Deployment Output", width=880, height=380
+ y=390, title="Deployment Output", width=880, height=310
)
- y = 740
+ y = 720
# Deploy Supabase Button
self._supabase_deploy_button = tk.Button(
@@ -143,7 +191,27 @@ class SupabaseScreenMixin:
env["PROLE_SERVICE"] = str(PROJECT_ROOT)
env["NAMESPACE"] = namespace
+ pv_node = ""
+ try:
+ pv_node = (self.supabase_pv_node.get() or "").strip()
+ except Exception:
+ pv_node = ""
+ if pv_node:
+ env["SUPABASE_PV_NODE"] = pv_node
+
+ pv_base_dir = ""
+ try:
+ pv_base_dir = (self.supabase_pv_base_dir.get() or "").strip()
+ except Exception:
+ pv_base_dir = ""
+ if pv_base_dir:
+ env["SUPABASE_PV_BASE_DIR"] = pv_base_dir
+ env["SUPABASE_PV_BASE"] = pv_base_dir
+
self._supabase_console.write("Starting Supabase deployment...\n")
+ self._supabase_console.write(
+ f"Placement: node={pv_node or 'auto'} storage={pv_base_dir or 'auto'}\n"
+ )
script_path = PROJECT_ROOT / "supabase" / "deploy.sh"
if not script_path.exists():
@@ -191,6 +259,12 @@ class SupabaseScreenMixin:
cfg_path = None
if cfg_path:
args.extend(["-c", str(cfg_path)])
+
+ try:
+ self._save_prole_cfg()
+ except Exception:
+ pass
+
if _parse_bool(os.environ.get("SUPABASE_USE_DEV_COMPOSE"), False):
args.append("--with-dev-helpers")
if _parse_bool(os.environ.get("SUPABASE_FOREGROUND"), False):
diff --git a/knoe/ui/screens/validate.py b/knoe/ui/screens/validate.py
index 744961b..8577c5f 100644
--- a/knoe/ui/screens/validate.py
+++ b/knoe/ui/screens/validate.py
@@ -3,10 +3,10 @@
import subprocess
import threading
import time
-import webbrowser
import tkinter as tk
+import webbrowser
from tkinter import ttk, messagebox, filedialog
-from knoe.screen import TerminalConsole
+
from knoe import screen as ui
diff --git a/modes/k3s/knoe-db/.version b/modes/k3s/knoe-db/.version
index 4701cc7..147ea53 100644
--- a/modes/k3s/knoe-db/.version
+++ b/modes/k3s/knoe-db/.version
@@ -1 +1 @@
-150
\ No newline at end of file
+158
\ No newline at end of file
diff --git a/prole/__init__.py b/prole/__init__.py
index f5b1554..685dacf 100644
--- a/prole/__init__.py
+++ b/prole/__init__.py
@@ -1,3 +1,3 @@
-from prole.deployment import ProleDeployment
+from prole.deployment import KnoeDeployment
-__all__ = ["ProleDeployment"]
+__all__ = ["KnoeDeployment"]
diff --git a/prole/cli.py b/prole/cli.py
index dfa480a..42567bc 100644
--- a/prole/cli.py
+++ b/prole/cli.py
@@ -1,14 +1,13 @@
from __future__ import annotations
import argparse
-import logging
import os
import sys
from pathlib import Path
-from knoe.core.controller import ProleController
+from knoe.core.actions import KnoeConsoleInstaller, _prepare_k3s_pipeline
+from knoe.core.controller import KnoeController
from knoe.core.env import _ensure_ansible_vault_credentials, PROJECT_ROOT
-from knoe.core.actions import ProleConsoleInstaller, _prepare_k3s_pipeline
def main():
@@ -44,6 +43,12 @@ def main():
action="store_true",
help="Reset k3d dev cluster or k3s service cluster",
)
+ parser.add_argument(
+ "--delete-db",
+ action="store_true",
+ default=False,
+ help=argparse.SUPPRESS,
+ )
parser.add_argument(
"--no-gui",
action="store_true",
@@ -63,6 +68,9 @@ def main():
prole_conf = os.environ.get("PROLE_CONF")
args = parser.parse_args()
+ if args.delete_db and not args.reset:
+ parser.error("--delete-db requires --reset")
+
if args.config is None and prole_conf:
potential_cfg = Path(prole_conf) / "prole.cfg"
if potential_cfg.exists():
@@ -72,16 +80,16 @@ def main():
setup_logging(verbose=args.verbose, debug=args.debug)
- controller = ProleController(
+ controller = KnoeController(
PROJECT_ROOT,
verbose=args.verbose,
cfg_path=Path(args.config).expanduser() if args.config else None,
)
if args.command == "deploy":
- from prole.deployment import ProleDeployment
+ from prole.deployment import KnoeDeployment
- deployment = ProleDeployment(controller, PROJECT_ROOT)
+ deployment = KnoeDeployment(controller, PROJECT_ROOT)
if deployment.duplicate_k3d_to_k3s():
if deployment.apply():
sys.exit(0)
@@ -89,8 +97,11 @@ def main():
if args.silent:
_ensure_ansible_vault_credentials(prompt_ui=False)
- installer = ProleConsoleInstaller(
- controller, args.config, reset_cluster=args.reset
+ installer = KnoeConsoleInstaller(
+ controller,
+ args.config,
+ reset_cluster=args.reset,
+ delete_db=args.delete_db,
)
if args.log:
log_path = (
diff --git a/prole/deployment.py b/prole/deployment.py
index 298d8e5..8bf8643 100644
--- a/prole/deployment.py
+++ b/prole/deployment.py
@@ -1,17 +1,16 @@
from __future__ import annotations
import configparser
-import logging
import os
import subprocess
from pathlib import Path
+import logging
from knoe import prole_conf
-from knoe.core.actions import ProleInstaller
+from knoe.core.actions import KnoeInstaller
from knoe.core.env import (
_detect_ansible_topology,
_sync_opentofu_pipeline,
- _normalize_cluster_env,
_parse_bool,
)
from knoe.core.ops.cloudnative_pg import initialize as cnpg_initialize
@@ -40,7 +39,7 @@ _CFG_KEY_MAP = {
}
-class ProleDeployment(ProleInstaller):
+class KnoeDeployment(KnoeInstaller):
"""
Top-level class for Prole infrastructure deployment via OpenTofu.
Duplicate k3d stack and apply to k3s defined in Ansible inventory.
diff --git a/scan/network_description.txt b/scan/network_description.txt
index 745348f..4b2b53b 100644
--- a/scan/network_description.txt
+++ b/scan/network_description.txt
@@ -1,58 +1,53 @@
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
+DNS Servers: 10.0.0.4, 10.0.0.5, 100.100.100.100
Detected Devices:
- 10.0.0.35 [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.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
- 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.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.35\ [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [], Services: []
+- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 80, 2049, 445, 5900, 88, 389, 636], Services: ['SSH', 'NFS', 'VNC', 'Web Server', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
+- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
+- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443], Services: ['DNS', 'SSH', 'Web Server']
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
-- 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.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
- 10.0.0.32 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): Ports [], Services: []
-- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
-- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
-- 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.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): Ports [], Services: []
-- 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.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
-- 10.0.0.34 [3a:b4:ce:79:b0:55] (3a:b4:ce:79:b0:55): Ports [], Services: []
+- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 443], Services: ['SSH', 'Web Server']
+- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
+- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
+- 10.0.0.22 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22], Services: ['SSH']
+- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [443], Services: ['Web Server']
+- 10.0.0.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): 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.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): Ports [], Services: []
-- 10.0.0.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
-- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 53, 445, 5900, 88], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
-- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
-- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
-- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
-- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
+- 10.0.0.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [], Services: []
+- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [], Services: []
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
-- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
-- 10.0.0.94 [f0:20:ff:bf:9a:19] (Intel_bf:9a:19): Ports [], Services: []
- Note: Potential host for NVIDIA cards (PC Hardware vendor)
-- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
+- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
+- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [2049], Services: ['NFS']
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
+- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
- 10.0.0.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
+- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): 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.112 [f8:33:31:40:58:c0] (TexasInstrum_40:58:c0): Ports [80, 443], Services: ['Web Server']
-- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
- 10.0.0.127 [58:55:ca:4a:60:6e] (Apple_4a:60:6e): Ports [22], Services: ['SSH']
-- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [80], Services: ['Web Server']
+- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
+- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [], Services: []
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
+- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 53, 445, 5900, 88], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
+- 10.0.0.128 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [], Services: []
- 10.0.0.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
-- 10.0.0.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
-- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
-- 10.0.0.160 [96:c3:fd:58:02:6c] (96:c3:fd:58:02:6c): Ports [], Services: []
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
-- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
+- 10.0.0.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
+- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): 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.58 [7e:46:74:2a:b0:b2] (7e:46:74:2a:b0:b2): Ports [], Services: []
-- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
-- 10.0.0.60 [c6:d0:1a:97:9f:e9] (c6:d0:1a:97:9f:e9): Ports [], Services: []
+- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
+- 10.0.0.204 [50:eb:f6:56:f3:26] (ASUSTekCOMPU_56:f3:26): Ports [22, 3389, 11434], Services: ['SSH', 'RDP (Windows)', 'Ollama']
+- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
+- 10.0.0.1\ [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
Ollama Instances found at: 10.0.0.35, 10.0.0.204
Ansible Inventory Summary:
diff --git a/supabase/helm/prole-supabase/templates/storage/deployment.yaml b/supabase/helm/prole-supabase/templates/storage/deployment.yaml
index b2add45..e0578e2 100644
--- a/supabase/helm/prole-supabase/templates/storage/deployment.yaml
+++ b/supabase/helm/prole-supabase/templates/storage/deployment.yaml
@@ -90,7 +90,8 @@ spec:
image: "{{ .Values.image.storage.repository }}:{{ .Values.image.storage.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.storage.pullPolicy }}
env:
- {{- range $key, $value := .Values.environment.storage }}
+ {{- $storageEnv := (.Values.environment.storage | default dict) }}
+ {{- range $key, $value := $storageEnv }}
{{- if and (ne $key "DB_HOST") (ne $key "DB_PORT") (ne $key "DB_SSL") }}
- name: {{ $key }}
value: {{ $value | quote }}
@@ -193,16 +194,24 @@ spec:
value: {{ printf "http://%s:%v" (include "supabase.imgproxy.fullname" .) (.Values.service.imgproxy.port | int) | quote }}
{{- end }}
+ {{- if not (hasKey $storageEnv "STORAGE_BACKEND") }}
- name: STORAGE_BACKEND
value: {{ if .Values.deployment.minio.enabled }}s3{{ else }}file{{ end }}
+ {{- end }}
{{- if .Values.deployment.minio.enabled }}
+ {{- if not (hasKey $storageEnv "GLOBAL_S3_ENDPOINT") }}
- name: GLOBAL_S3_ENDPOINT
value: {{ printf "http://%s:%v" (include "supabase.minio.fullname" .) (default 9000 .Values.service.minio.port) | quote }}
+ {{- end }}
+ {{- if not (hasKey $storageEnv "GLOBAL_S3_PROTOCOl") }}
- name: GLOBAL_S3_PROTOCOl
value: http
+ {{- end }}
+ {{- if not (hasKey $storageEnv "GLOBAL_S3_FORCE_PATH_STYLE") }}
- name: GLOBAL_S3_FORCE_PATH_STYLE
value: "true"
+ {{- end }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
diff --git a/supabase/helm/render_supabase.py b/supabase/helm/render_supabase.py
index a30bd97..fb26794 100755
--- a/supabase/helm/render_supabase.py
+++ b/supabase/helm/render_supabase.py
@@ -67,6 +67,45 @@ def _is_placeholder(value: str) -> bool:
return v.startswith("${") and v.endswith("}")
+def _as_bool(value: str | bool | None, default: bool = False) -> bool:
+ if isinstance(value, bool):
+ return value
+ if value is None:
+ return default
+ normalized = str(value).strip().lower()
+ if not normalized:
+ return default
+ if normalized in {"1", "true", "yes", "on", "y"}:
+ return True
+ if normalized in {"0", "false", "no", "off", "n"}:
+ return False
+ return default
+
+
+def _parse_hosts(raw_hosts: str, fallback: str = "db.prole.org") -> list[str]:
+ entries: list[str] = []
+ seen: set[str] = set()
+ for entry in (raw_hosts or "").split(","):
+ token = (entry or "").strip()
+ if not token or _is_placeholder(token):
+ continue
+
+ if "://" in token:
+ parsed = urllib.parse.urlparse(token)
+ host = (parsed.hostname or "").strip()
+ else:
+ host = token
+
+ if not host or host in seen:
+ continue
+ seen.add(host)
+ entries.append(host)
+
+ if entries:
+ return entries
+ return [fallback]
+
+
def _discover_k8s_service_namespace(service_name: str) -> str:
try:
raw = subprocess.check_output(["kubectl", "get", "svc", "-A", "-o", "json"], text=True)
@@ -161,7 +200,9 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
anon_key = _jwt(anon_payload, jwt_secret)
service_key = _jwt(service_payload, jwt_secret)
- supabase_hostname = _first(
+ supabase_hostname_raw = _first(
+ os.environ.get("SUPABASE_STUDIO_URL", ""),
+ _cfg_get(cfg, "Inputs", "init_cluster.supabase_studio_url"),
_cfg_get(cfg, "User", "supabase_hostname"),
_cfg_get(cfg, "Global", "supabase_hostname"),
_cfg_get(cfg, "User", "SUPABASE_HOSTNAME"),
@@ -171,16 +212,73 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
default="db.prole.org",
).strip()
- if _is_placeholder(supabase_hostname):
- supabase_hostname = ""
- if not supabase_hostname:
- supabase_hostname = "db.prole.org"
+ if _is_placeholder(supabase_hostname_raw):
+ supabase_hostname_raw = ""
+ if not supabase_hostname_raw:
+ supabase_hostname_raw = "db.prole.org"
+
+ studio_enabled = _as_bool(
+ _first(
+ os.environ.get("SUPABASE_STUDIO_ENABLED", ""),
+ _cfg_get(cfg, "Inputs", "init_cluster.supabase_studio_enabled"),
+ _cfg_get(cfg, "Global", "SUPABASE_STUDIO_ENABLED"),
+ default="true",
+ ),
+ default=True,
+ )
+ auth_enabled = _as_bool(
+ _first(
+ os.environ.get("SUPABASE_AUTH_ENABLED", ""),
+ _cfg_get(cfg, "Inputs", "init_cluster.supabase_auth_enabled"),
+ _cfg_get(cfg, "Global", "SUPABASE_AUTH_ENABLED"),
+ default="true",
+ ),
+ default=True,
+ )
+ realtime_enabled = _as_bool(
+ _first(
+ os.environ.get("SUPABASE_REALTIME_ENABLED", ""),
+ _cfg_get(cfg, "Inputs", "init_cluster.supabase_realtime_enabled"),
+ _cfg_get(cfg, "Global", "SUPABASE_REALTIME_ENABLED"),
+ default="true",
+ ),
+ default=True,
+ )
+ meta_enabled = _as_bool(
+ _first(
+ os.environ.get("SUPABASE_META_ENABLED", ""),
+ _cfg_get(cfg, "Inputs", "init_cluster.supabase_meta_enabled"),
+ _cfg_get(cfg, "Global", "SUPABASE_META_ENABLED"),
+ default="true",
+ ),
+ default=True,
+ )
+ analytics_enabled = _as_bool(
+ _first(
+ os.environ.get("SUPABASE_ANALYTICS_ENABLED", ""),
+ _cfg_get(cfg, "Inputs", "init_cluster.supabase_analytics_enabled"),
+ _cfg_get(cfg, "Global", "SUPABASE_ANALYTICS_ENABLED"),
+ default="true",
+ ),
+ default=True,
+ )
+
+ raw_host_entries: list[str] = []
+ for entry in supabase_hostname_raw.split(","):
+ token = (entry or "").strip()
+ if token and not _is_placeholder(token):
+ raw_host_entries.append(token)
+ if not raw_host_entries:
+ raw_host_entries = ["db.prole.org"]
+
+ host_entries = _parse_hosts(",".join(raw_host_entries), fallback="db.prole.org")
+ ingress_host = host_entries[0]
# Canonical public URL for Supabase (used for auth callbacks + Studio links).
public_url = ""
- ingress_host = supabase_hostname
- if "://" in supabase_hostname:
- parsed = urllib.parse.urlparse(supabase_hostname)
+ primary_raw_host = (raw_host_entries[0] or "").strip()
+ if "://" in primary_raw_host:
+ parsed = urllib.parse.urlparse(primary_raw_host)
ingress_host = (parsed.hostname or "").strip() or ingress_host
scheme = (parsed.scheme or "https").strip()
if parsed.netloc:
@@ -220,9 +318,13 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
).strip().lower()
supabase_primary_node = _first(
os.environ.get("SUPABASE_PRIMARY_NODE", ""),
+ os.environ.get("SUPABASE_NODE_SELECTOR", ""),
+ os.environ.get("SUPABASE_PV_NODE", ""),
_cfg_get(cfg, "Global", "SUPABASE_PRIMARY_NODE"),
+ _cfg_get(cfg, "Global", "SUPABASE_NODE_SELECTOR"),
+ _cfg_get(cfg, "Global", "SUPABASE_PV_NODE"),
_cfg_get(cfg, "Supabase", "PRIMARY_NODE"),
- default="merlin.prole.org",
+ default="gandalf.prole.org",
).strip()
if _is_placeholder(supabase_primary_node):
supabase_primary_node = ""
@@ -233,7 +335,7 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
os.environ.get("SUPABASE_STORAGE_CLASS", ""),
_cfg_get(cfg, "Global", "SUPABASE_STORAGE_CLASS"),
_cfg_get(cfg, "Supabase", "STORAGE_CLASS"),
- default="merlin-local-iscsi-d002",
+ default="synology-iscsi",
).strip()
if _is_placeholder(supabase_storage_class):
supabase_storage_class = "merlin-local-iscsi-d002"
@@ -243,20 +345,20 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
"fullnameOverride": "supabase",
"deployment": {
"db": {"enabled": False},
- "functions": {"enabled": False},
- "vector": {"enabled": False},
- "kong": {"enabled": False},
- "storage": {"enabled": False},
- "minio": {"enabled": False},
- "imgproxy": {"enabled": False},
+ "functions": {"enabled": True},
+ "vector": {"enabled": True},
+ "kong": {"enabled": True},
+ "storage": {"enabled": True},
+ "minio": {"enabled": True},
+ "imgproxy": {"enabled": True},
# Explicit fullnameOverride per component strips the chart name
# from pod names (avoids 'supabase-prole-supabase-').
- "analytics": {"fullnameOverride": "supabase-analytics"},
- "auth": {"fullnameOverride": "supabase-auth"},
- "meta": {"fullnameOverride": "supabase-meta"},
- "realtime": {"fullnameOverride": "supabase-realtime"},
- "rest": {"fullnameOverride": "supabase-rest"},
- "studio": {"fullnameOverride": "supabase-studio"},
+ "analytics": {"enabled": analytics_enabled, "fullnameOverride": "supabase-analytics"},
+ "auth": {"enabled": auth_enabled, "fullnameOverride": "supabase-auth"},
+ "meta": {"enabled": meta_enabled, "fullnameOverride": "supabase-meta"},
+ "realtime": {"enabled": realtime_enabled, "fullnameOverride": "supabase-realtime"},
+ "rest": {"enabled": True, "fullnameOverride": "supabase-rest"},
+ "studio": {"enabled": studio_enabled, "fullnameOverride": "supabase-studio"},
},
"externalDatabase": {
"enabled": True,
@@ -287,9 +389,12 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
"ingress": {
"enabled": True,
"className": "traefik",
- "hosts": [
- {"host": ingress_host, "paths": [{"path": "/", "pathType": "Prefix"}]}
- ],
+ "hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in host_entries],
+ },
+ "studioIngress": {
+ "enabled": True,
+ "className": "traefik",
+ "hosts": [{"host": host, "paths": [{"path": "/", "pathType": "Prefix"}]} for host in host_entries],
},
}
@@ -304,20 +409,24 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
)
# Write to persistence.*.storageClassName — the path the Helm chart actually uses
persistence = overlay.setdefault("persistence", {})
- # Only set storageClass for components that are still deployed; storage/minio/imgproxy are removed.
- for pvc_key in ("functions", "snippets", "deno"):
+ for pvc_key in ("functions", "snippets", "deno", "imgproxy", "minio", "storage"):
persistence.setdefault(pvc_key, {})["storageClassName"] = supabase_storage_class
if mode == "k3s" and supabase_node_selector:
deployment = overlay.setdefault("deployment", {})
- # Only pin active control-plane components; storage/minio/imgproxy/kong are disabled.
for component in (
"analytics",
"auth",
+ "functions",
+ "imgproxy",
+ "kong",
"meta",
+ "minio",
"realtime",
"rest",
+ "storage",
"studio",
+ "vector",
):
component_cfg = deployment.setdefault(component, {})
component_cfg.setdefault("nodeSelector", {})
diff --git a/tests/installer/test_actions_env_setup_expands_shell_vars.py b/tests/installer/test_actions_env_setup_expands_shell_vars.py
index 06e48b5..ef98724 100644
--- a/tests/installer/test_actions_env_setup_expands_shell_vars.py
+++ b/tests/installer/test_actions_env_setup_expands_shell_vars.py
@@ -23,9 +23,9 @@ def test_actions_save_env_to_file_expands_shell_vars(monkeypatch, tmp_path: Path
)
(proj / "etc" / "dummy.txt").write_text("ok\n")
- from knoe.core.actions import ProleInstaller
+ from knoe.core.actions import KnoeInstaller
- class Dummy(ProleInstaller):
+ class Dummy(KnoeInstaller):
def __init__(self, project_root: Path):
self.project_root = project_root
self.inputs = {"init_cluster.cluster_env": "dev"}
diff --git a/tests/installer/test_actions_helpers.py b/tests/installer/test_actions_helpers.py
index 55d4f6d..6033ae8 100644
--- a/tests/installer/test_actions_helpers.py
+++ b/tests/installer/test_actions_helpers.py
@@ -1,6 +1,7 @@
"""Tests for installer/core/actions.py – ProleInstallerBase helper methods."""
from __future__ import annotations
+
import os
import subprocess
import sys
@@ -8,11 +9,9 @@ from pathlib import Path
from unittest import mock
from unittest.mock import MagicMock, patch
-import pytest
-
from knoe.core.actions import (
- ProleInstaller,
- ProleConsoleInstaller,
+ KnoeInstaller,
+ KnoeConsoleInstaller,
_configure_unbuffered_io,
_select_existing_kubeconfig,
)
@@ -23,7 +22,7 @@ from knoe.core.actions import (
# ---------------------------------------------------------------------------
-class _TestableInstaller(ProleInstaller):
+class _TestableInstaller(KnoeInstaller):
"""Minimal concrete subclass for testing base-class helpers."""
def __init__(self, inputs=None, project_root=None):
@@ -329,8 +328,8 @@ spec:
# ---------------------------------------------------------------------------
-class _TestableSilentInstaller(ProleConsoleInstaller):
- """Testable subclass of ProleConsoleInstaller."""
+class _TestableSilentInstaller(KnoeConsoleInstaller):
+ """Testable subclass of KnoeConsoleInstaller."""
def __init__(self, inputs=None, project_root=None):
self._inputs_dict = inputs or {}
diff --git a/tests/installer/test_config.py b/tests/installer/test_config.py
index d8a887b..018e224 100644
--- a/tests/installer/test_config.py
+++ b/tests/installer/test_config.py
@@ -1,5 +1,5 @@
-import pytest
from unittest.mock import patch, MagicMock
+
from knoe.config import (
normalize_version,
is_apple_silicon,
diff --git a/tests/installer/test_config_extended.py b/tests/installer/test_config_extended.py
index bc415ac..5562d83 100644
--- a/tests/installer/test_config_extended.py
+++ b/tests/installer/test_config_extended.py
@@ -1,15 +1,13 @@
"""Extended tests for installer/config.py – covering encryption, properties, helpers."""
from __future__ import annotations
+
import configparser
import os
-import platform
from pathlib import Path
from unittest import mock
from unittest.mock import MagicMock, patch
-import pytest
-
from knoe.config import (
_is_prole_secret,
_is_openbao_ref,
diff --git a/tests/installer/test_config_helpers.py b/tests/installer/test_config_helpers.py
index e9c4d41..8a0c6f1 100644
--- a/tests/installer/test_config_helpers.py
+++ b/tests/installer/test_config_helpers.py
@@ -13,7 +13,6 @@ import os
import subprocess
from pathlib import Path
from unittest.mock import MagicMock, patch
-import pytest
from knoe.config import (
_get_secret_key_file,
diff --git a/tests/installer/test_controller.py b/tests/installer/test_controller.py
index 2f349d1..5f7e06e 100644
--- a/tests/installer/test_controller.py
+++ b/tests/installer/test_controller.py
@@ -1,7 +1,7 @@
"""
Unit tests for installer/core/controller.py
-Covers ProleController: check_docker_running, get_knoe_db_version, run_script.
+Covers KnoeController: check_docker_running, get_knoe_db_version, run_script.
"""
from __future__ import annotations
@@ -9,17 +9,16 @@ import os
import subprocess
from pathlib import Path
from unittest.mock import MagicMock, patch
-import pytest
-from knoe.core.controller import ProleController
+from knoe.core.controller import KnoeController
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
-def _make_controller(tmp_path: Path, **kwargs) -> ProleController:
- return ProleController(project_root=tmp_path, **kwargs)
+def _make_controller(tmp_path: Path, **kwargs) -> KnoeController:
+ return KnoeController(project_root=tmp_path, **kwargs)
def _scaffold_project(tmp_path: Path) -> None:
diff --git a/tests/installer/test_controller_run_script_expands_shell_vars.py b/tests/installer/test_controller_run_script_expands_shell_vars.py
index 50ba64e..80e10c5 100644
--- a/tests/installer/test_controller_run_script_expands_shell_vars.py
+++ b/tests/installer/test_controller_run_script_expands_shell_vars.py
@@ -20,9 +20,9 @@ def test_controller_run_script_expands_shell_vars(monkeypatch, tmp_path: Path):
(proj / "etc").mkdir(parents=True, exist_ok=True)
(proj / "etc" / "dummy.sh").write_text("#!/usr/bin/env bash\necho ok\n")
- from knoe.core.controller import ProleController
+ from knoe.core.controller import KnoeController
- controller = ProleController(project_root=proj)
+ controller = KnoeController(project_root=proj)
rc = controller.run_script("dummy.sh", env={"PROLE_HOME": "$HOME/prole"})
assert rc == 0
diff --git a/tests/installer/test_core_classes.py b/tests/installer/test_core_classes.py
index 862c3c4..3af2c32 100644
--- a/tests/installer/test_core_classes.py
+++ b/tests/installer/test_core_classes.py
@@ -6,13 +6,11 @@ import os
from pathlib import Path
from unittest import mock
-import pytest
-
-from knoe.state import InstallerState
-from knoe.milestone import Milestone, ProgressCallback
from knoe.build import get_build_command, BuildMilestone
-from knoe.core.controller import ProleController
-from knoe.core.actions import ProleConsoleInstaller
+from knoe.core.actions import KnoeConsoleInstaller
+from knoe.core.controller import KnoeController
+from knoe.milestone import Milestone
+from knoe.state import InstallerState
# ===== InstallerState =====
@@ -262,31 +260,31 @@ class TestBuildMilestone:
assert m.next(InstallerState()) is None
-# ===== ProleController =====
+# ===== KnoeController =====
-class TestProleController:
+class TestKnoeController:
def test_init(self, tmp_path):
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
assert c.project_root == tmp_path
assert c.verbose is False
assert c.state is not None
- installer = ProleConsoleInstaller(c)
+ installer = KnoeConsoleInstaller(c)
defaults = installer._default_inputs()
assert defaults.get("kerberos_config.user") == "administrator"
assert defaults.get("kerberos_config.password") == ""
def test_init_verbose(self, tmp_path):
- c = ProleController(tmp_path, verbose=True)
+ c = KnoeController(tmp_path, verbose=True)
assert c.verbose is True
def test_check_docker_running(self, tmp_path):
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
result = c.check_docker_running()
assert isinstance(result, bool)
def test_get_knoe_db_version_defaults(self, tmp_path):
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
version = c.get_knoe_db_version()
assert "17.7" in version # default pg version
assert "-" in version
@@ -298,12 +296,12 @@ class TestProleController:
db_dir = tmp_path / "knoe-db"
db_dir.mkdir()
(db_dir / ".version").write_text("42")
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
version = c.get_knoe_db_version()
assert version == "16.2-042"
def test_run_milestones(self, tmp_path):
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
m1 = _DummyMilestone("step1", "Step 1")
m2 = _DummyMilestone("step2", "Step 2")
msgs = []
@@ -312,21 +310,21 @@ class TestProleController:
assert "step2" in c.state.completed
def test_run_milestones_no_callback(self, tmp_path):
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
m = _DummyMilestone("s1", "S1")
c.run_milestones([m])
assert "s1" in c.state.completed
def test_run_script_missing(self, tmp_path):
- c = ProleController(tmp_path)
+ c = KnoeController(tmp_path)
# Script doesn't exist but should not crash
rc = c.run_script("nonexistent_script.sh", args=[])
# Will fail since the script doesn't exist
assert rc != 0 or rc is None
def test_silent_installer_runs_supabase_preload_before_deploy(self, tmp_path):
- c = ProleController(tmp_path)
- installer = ProleConsoleInstaller(c)
+ c = KnoeController(tmp_path)
+ installer = KnoeConsoleInstaller(c)
with mock.patch.object(installer, "_load_inputs_from_cfg", return_value={}), \
mock.patch.object(installer, "_write_cfg"), \
@@ -348,8 +346,8 @@ class TestProleController:
def test_silent_installer_does_not_generate_db_password_when_openbao_ref_present(
self, tmp_path
):
- c = ProleController(tmp_path)
- installer = ProleConsoleInstaller(c)
+ c = KnoeController(tmp_path)
+ installer = KnoeConsoleInstaller(c)
openbao_ref = "${OPENBAO:kv/prole/test/db#password}"
diff --git a/tests/installer/test_deployment.py b/tests/installer/test_deployment.py
index fa77830..53e44a8 100644
--- a/tests/installer/test_deployment.py
+++ b/tests/installer/test_deployment.py
@@ -1,6 +1,7 @@
-"""Tests for prole/deployment.py – ProleDeployment class."""
+"""Tests for prole/deployment.py – KnoeDeployment class."""
from __future__ import annotations
+
import configparser
import os
import subprocess
@@ -8,10 +9,8 @@ from pathlib import Path
from unittest import mock
from unittest.mock import MagicMock, patch, call
-import pytest
-
from prole.deployment import (
- ProleDeployment,
+ KnoeDeployment,
_CFG_KEY_MAP,
KUBECONFIG_FILENAME,
FETCH_PLAYBOOK,
@@ -59,7 +58,7 @@ def _make_deployment(tmp_path, sections=None):
cfg_path = _make_cfg_file(tmp_path, sections)
ctrl = _make_controller(tmp_path)
with mock.patch.dict(os.environ, {"PROLE_CONF": str(tmp_path / "conf")}):
- dep = ProleDeployment(ctrl, tmp_path)
+ dep = KnoeDeployment(ctrl, tmp_path)
return dep, ctrl
@@ -68,7 +67,7 @@ def _make_deployment(tmp_path, sections=None):
# ---------------------------------------------------------------------------
-class TestProleDeploymentInit:
+class TestKnoeDeploymentInit:
def test_init_loads_cfg(self, tmp_path):
dep, _ = _make_deployment(tmp_path)
assert dep.prole_cfg_data["Global"]["NAMESPACE"] == "test-ns"
@@ -86,14 +85,14 @@ class TestProleDeploymentInit:
cfg.write(f)
ctrl = _make_controller(tmp_path)
with mock.patch.dict(os.environ, {"PROLE_CONF": str(conf_dir)}):
- dep = ProleDeployment(ctrl, tmp_path)
+ dep = KnoeDeployment(ctrl, tmp_path)
assert dep.prole_cfg_data["Global"]["NAMESPACE"] == "knoe-db"
def test_init_no_cfg_file(self, tmp_path):
"""When no prole.cfg exists, it still initialises."""
ctrl = _make_controller(tmp_path)
with mock.patch.dict(os.environ, {"PROLE_CONF": ""}, clear=False):
- dep = ProleDeployment(ctrl, tmp_path)
+ dep = KnoeDeployment(ctrl, tmp_path)
assert dep.prole_cfg_data["Global"]["NAMESPACE"] == "knoe-db"
def test_load_cfg_from_directory(self, tmp_path):
@@ -101,7 +100,7 @@ class TestProleDeploymentInit:
_make_cfg_file(tmp_path)
ctrl = _make_controller(tmp_path)
with mock.patch.dict(os.environ, {"PROLE_CONF": str(tmp_path / "conf")}):
- dep = ProleDeployment(ctrl, tmp_path)
+ dep = KnoeDeployment(ctrl, tmp_path)
assert dep.cfg_path == tmp_path / "conf" / "prole.cfg"
@@ -284,7 +283,7 @@ class TestApply:
dep.prole_cfg_data["Deployment"] = {"OPENTOFU_PIPELINE_DIR": str(pipeline)}
assert dep.apply() is False
- @patch.object(ProleDeployment, "_run_post_apply_scripts", return_value=True)
+ @patch.object(KnoeDeployment, "_run_post_apply_scripts", return_value=True)
@patch("subprocess.run")
def test_apply_success(self, mock_run, mock_post, tmp_path):
pipeline = tmp_path / "pipeline"
@@ -416,7 +415,7 @@ class TestRunSupabaseDeploy:
class TestDuplicateK3dToK3s:
- @patch.object(ProleDeployment, "_fetch_kubeconfig", return_value=None)
+ @patch.object(KnoeDeployment, "_fetch_kubeconfig", return_value=None)
@patch(
"prole.deployment._detect_ansible_topology",
return_value={"k3s_server_url": "https://example.com:6443"},
@@ -431,7 +430,7 @@ class TestDuplicateK3dToK3s:
assert dep.duplicate_k3d_to_k3s() is False
@patch("prole.deployment._sync_opentofu_pipeline", side_effect=Exception("boom"))
- @patch.object(ProleDeployment, "_fetch_kubeconfig")
+ @patch.object(KnoeDeployment, "_fetch_kubeconfig")
@patch(
"prole.deployment._detect_ansible_topology",
return_value={
@@ -447,7 +446,7 @@ class TestDuplicateK3dToK3s:
assert dep.duplicate_k3d_to_k3s() is False
@patch("prole.deployment._sync_opentofu_pipeline")
- @patch.object(ProleDeployment, "_fetch_kubeconfig")
+ @patch.object(KnoeDeployment, "_fetch_kubeconfig")
@patch(
"prole.deployment._detect_ansible_topology",
return_value={
diff --git a/tests/installer/test_env_helpers.py b/tests/installer/test_env_helpers.py
index 640f467..4b9de92 100644
--- a/tests/installer/test_env_helpers.py
+++ b/tests/installer/test_env_helpers.py
@@ -9,8 +9,33 @@ from pathlib import Path
from unittest import mock
import pytest
-
-
+# ---------------------------------------------------------------------------
+# config.py helpers
+# ---------------------------------------------------------------------------
+from knoe.config import (
+ _parse_bool as cfg_parse_bool,
+ _expand_path as cfg_expand_path,
+ _expand_cfg_value as cfg_expand_cfg_value,
+ _collect_cfg_vars as cfg_collect_cfg_vars,
+ _filter_cfg_values_for_persistence,
+ _normalize_cfg_value_for_persistence,
+ _extract_yaml_scalar_from_text as cfg_extract_yaml,
+ _extract_inline_vault_block as cfg_extract_vault,
+ _load_properties,
+ _write_k3s_kubeconfig,
+ _merge_kubeconfig,
+ normalize_version,
+ get_resource_path as cfg_get_resource_path,
+ get_docker_build_platform_args as cfg_get_docker_args,
+ _is_prole_secret as cfg_is_prole_secret,
+ _is_openbao_ref as cfg_is_openbao_ref,
+ _encrypt_cfg_secret,
+ _resolve_secret_value,
+ _update_prole_cfg_value,
+ get_properties,
+ get_config_value,
+ setup_logging,
+)
# ---------------------------------------------------------------------------
# env.py helpers
# ---------------------------------------------------------------------------
@@ -52,36 +77,6 @@ from knoe.core.env import (
_collect_cfg_vars as env_collect_cfg_vars,
get_resource_path,
is_apple_silicon,
- get_docker_build_platform_args,
-)
-
-# ---------------------------------------------------------------------------
-# config.py helpers
-# ---------------------------------------------------------------------------
-from knoe.config import (
- _parse_bool as cfg_parse_bool,
- _expand_path as cfg_expand_path,
- _expand_cfg_value as cfg_expand_cfg_value,
- _collect_cfg_vars as cfg_collect_cfg_vars,
- _filter_cfg_values_for_persistence,
- _normalize_cfg_value_for_persistence,
- _extract_yaml_scalar_from_text as cfg_extract_yaml,
- _extract_inline_vault_block as cfg_extract_vault,
- _load_properties,
- _write_k3s_kubeconfig,
- _merge_kubeconfig,
- normalize_version,
- get_resource_path as cfg_get_resource_path,
- is_apple_silicon as cfg_is_apple_silicon,
- get_docker_build_platform_args as cfg_get_docker_args,
- _is_prole_secret as cfg_is_prole_secret,
- _is_openbao_ref as cfg_is_openbao_ref,
- _encrypt_cfg_secret,
- _resolve_secret_value,
- _update_prole_cfg_value,
- get_properties,
- get_config_value,
- setup_logging,
)
@@ -894,7 +889,7 @@ class TestProperties:
# ===== _update_prole_cfg_value =====
-class TestUpdateProleCfgValue:
+class TestUpdateKnoeCfgValue:
def test_creates_section(self, tmp_path):
cfg_file = tmp_path / "conf" / "prole.cfg"
cfg_file.parent.mkdir(parents=True)
@@ -1008,7 +1003,7 @@ class TestPlatformHelpers:
# ===== _render_prole_cfg =====
-class TestRenderProleCfg:
+class TestRenderKnoeCfg:
def test_minimal(self):
from knoe.core.env import _render_prole_cfg
diff --git a/tests/installer/test_environment_screen_expands_shell_vars.py b/tests/installer/test_environment_screen_expands_shell_vars.py
index 26fcfe5..939e61d 100644
--- a/tests/installer/test_environment_screen_expands_shell_vars.py
+++ b/tests/installer/test_environment_screen_expands_shell_vars.py
@@ -3,8 +3,6 @@ from __future__ import annotations
import os
from pathlib import Path
-import pytest
-
def test_environment_screen_expands_shell_vars_for_filesystem(monkeypatch, tmp_path: Path):
"""Regression: `$HOME`/`$PROLE_HOME` must be expanded before mkdir/copy.
diff --git a/tests/installer/test_gitops_choice_screen.py b/tests/installer/test_gitops_choice_screen.py
index 3e7ecd9..8956d24 100644
--- a/tests/installer/test_gitops_choice_screen.py
+++ b/tests/installer/test_gitops_choice_screen.py
@@ -1,9 +1,9 @@
"""Tests for the GitOps provider choice screen (_render_gitops_choice_page)."""
from __future__ import annotations
+
+import sys
import tkinter as tk
from pathlib import Path
-import sys
-import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
diff --git a/tests/installer/test_milestones.py b/tests/installer/test_milestones.py
index 676d26b..998f890 100644
--- a/tests/installer/test_milestones.py
+++ b/tests/installer/test_milestones.py
@@ -11,7 +11,7 @@ from knoe.core.milestones import (
DeploymentMilestone,
)
from knoe.state import InstallerState
-from knoe.core.controller import ProleController
+from knoe.core.controller import KnoeController
class TestMilestones(unittest.TestCase):
@@ -19,7 +19,7 @@ class TestMilestones(unittest.TestCase):
self.state = InstallerState()
self.state.inputs["dependencies.auto_install_missing"] = "True"
self.state.inputs["network_scan.run"] = "True"
- self.state.controller = MagicMock(spec=ProleController)
+ self.state.controller = MagicMock(spec=KnoeController)
@patch("knoe.config.get_dep_info")
@patch("subprocess.run")
diff --git a/tests/installer/test_milestones_extended.py b/tests/installer/test_milestones_extended.py
index 6b3ed7c..ccf350d 100644
--- a/tests/installer/test_milestones_extended.py
+++ b/tests/installer/test_milestones_extended.py
@@ -24,7 +24,7 @@ from knoe.core.milestones import (
InitializationScriptsMilestone,
)
from knoe.state import InstallerState
-from knoe.core.controller import ProleController
+from knoe.core.controller import KnoeController
# ---------------------------------------------------------------------------
@@ -34,7 +34,7 @@ from knoe.core.controller import ProleController
def _make_state(**inputs) -> InstallerState:
state = InstallerState()
state.inputs.update(inputs)
- state.controller = MagicMock(spec=ProleController)
+ state.controller = MagicMock(spec=KnoeController)
state.controller.project_root = Path("/tmp/fake-prole")
state.controller.get_knoe_db_version.return_value = "18-001"
state.controller.run_script.return_value = 0
diff --git a/tests/installer/test_monitor.py b/tests/installer/test_monitor.py
index 5a77546..13ddf41 100644
--- a/tests/installer/test_monitor.py
+++ b/tests/installer/test_monitor.py
@@ -10,7 +10,6 @@ from __future__ import annotations
import subprocess
from pathlib import Path
from unittest.mock import MagicMock, patch, call
-import pytest
from knoe.core.monitor import (
_parse_pf_cfg,
diff --git a/tests/installer/test_registry_k3s_selection.py b/tests/installer/test_registry_k3s_selection.py
index 8fdf96f..a28acfe 100644
--- a/tests/installer/test_registry_k3s_selection.py
+++ b/tests/installer/test_registry_k3s_selection.py
@@ -1,14 +1,9 @@
from __future__ import annotations
-
from pathlib import Path
-
-import pytest
-
-
-from knoe.ui.screens.docker import DockerScreenMixin
import knoe.ui.screens.docker as docker_mod
+from knoe.ui.screens.docker import DockerScreenMixin
class _DummyDockerScreen(DockerScreenMixin):
diff --git a/tests/test_database_options.py b/tests/test_database_options.py
index ac7b485..7e96867 100644
--- a/tests/test_database_options.py
+++ b/tests/test_database_options.py
@@ -1,9 +1,11 @@
import sys
-from unittest.mock import patch, MagicMock
-from contextlib import ExitStack
-import pytest
-from pathlib import Path
import tkinter as tk
+from contextlib import ExitStack
+from pathlib import Path
+from unittest.mock import patch, MagicMock
+
+import pytest
+
class MockVar:
def __init__(self, value=None):
@@ -25,8 +27,7 @@ sys.modules["PIL"] = MagicMock()
sys.modules["PIL.Image"] = MagicMock()
sys.modules["PIL.ImageTk"] = MagicMock()
-import install
-from install import ProleInstaller
+from install import KnoeInstaller
def _generated_dockerfile_path(installer, tmp_path):
@@ -43,8 +44,8 @@ def mock_installer(tmp_path):
patch("knoe.ui.screens.database_options.PROJECT_ROOT", tmp_path),
patch("knoe.ui.screens.database_options.resolve_prole_home", return_value=tmp_path),
patch("knoe.ui.screens.database_options.get_resource_path", side_effect=lambda name: tmp_path / name),
- patch("knoe.core.controller.ProleController.run_script"),
- patch.object(ProleInstaller, "_load_database_versions"),
+ patch("knoe.core.controller.KnoeController.run_script"),
+ patch.object(KnoeInstaller, "_load_database_versions"),
patch("knoe.ui.screens.database_options.tk.BooleanVar", side_effect=lambda value=None: MockVar(value)),
patch("knoe.ui.screens.database_options.tk.StringVar", side_effect=lambda value=None: MockVar(value)),
patch("knoe.ui.screens.database_options.tk.ttk.Treeview"),
@@ -67,7 +68,7 @@ def mock_installer(tmp_path):
(tmp_path / "conf" / "postgresql").mkdir(parents=True)
(tmp_path / "etc").mkdir()
- app = ProleInstaller(root)
+ app = KnoeInstaller(root)
app.bg_canvas = MagicMock()
app.root = root
yield app
diff --git a/tests/test_install_logic.py b/tests/test_install_logic.py
index 7802312..7db13a5 100644
--- a/tests/test_install_logic.py
+++ b/tests/test_install_logic.py
@@ -17,17 +17,16 @@ import pytest
from pathlib import Path
# Now import install.py after mocking
-import install
-from install import ProleInstaller
+from install import KnoeInstaller
@pytest.fixture
def installer(tmp_path):
# Mocking os.environ to avoid messing with real env
with patch.dict(os.environ, {}, clear=True):
- # We need to mock ProleInstaller.__init__ because it creates GUI elements
- with patch.object(ProleInstaller, "__init__", return_value=None):
- ins = ProleInstaller()
+ # We need to mock KnoeInstaller.__init__ because it creates GUI elements
+ with patch.object(KnoeInstaller, "__init__", return_value=None):
+ ins = KnoeInstaller()
# Core helpers expect `inputs` to exist; bypassing `__init__` means
# we must seed minimal state for method calls.
ins.inputs = {}
diff --git a/tests/test_k3s_common_core_namespace_and_repair.py b/tests/test_k3s_common_core_namespace_and_repair.py
index 2bbc7a7..f39a614 100644
--- a/tests/test_k3s_common_core_namespace_and_repair.py
+++ b/tests/test_k3s_common_core_namespace_and_repair.py
@@ -2,8 +2,8 @@ from __future__ import annotations
from pathlib import Path
-from knoe.core.actions import ProleConsoleInstaller
-from knoe.core.controller import ProleController
+from knoe.core.actions import KnoeConsoleInstaller
+from knoe.core.controller import KnoeController
def test_silent_installer_service_namespace_defaults_to_knoe_system_for_k3s(tmp_path, monkeypatch):
@@ -11,8 +11,8 @@ def test_silent_installer_service_namespace_defaults_to_knoe_system_for_k3s(tmp_
monkeypatch.delenv("SERVICE_NAMESPACE", raising=False)
- c = ProleController(tmp_path)
- installer = ProleConsoleInstaller(c)
+ c = KnoeController(tmp_path)
+ installer = KnoeConsoleInstaller(c)
installer.inputs["init_cluster.cluster_env"] = "service"
installer.prole_cfg_data = {"Global": {}}
diff --git a/tests/test_navigation.py b/tests/test_navigation.py
index 295405a..90a7c93 100644
--- a/tests/test_navigation.py
+++ b/tests/test_navigation.py
@@ -1,7 +1,8 @@
import sys
-from unittest.mock import patch, MagicMock
-import pytest
from pathlib import Path
+from unittest.mock import patch, MagicMock
+
+import pytest
# Mock tkinter and other GUI/macOS specific imports
sys.modules["tkinter"] = MagicMock()
@@ -15,8 +16,7 @@ sys.modules["PIL"] = MagicMock()
sys.modules["PIL.Image"] = MagicMock()
sys.modules["PIL.ImageTk"] = MagicMock()
-import install
-from install import ProleInstaller
+from install import KnoeInstaller
@pytest.fixture
@@ -35,13 +35,13 @@ def mock_installer(tmp_path):
), patch(
"install.Path.mkdir"
), patch.object(
- ProleInstaller, "_save_env_to_file"
+ KnoeInstaller, "_save_env_to_file"
), patch.object(
- ProleInstaller, "_save_prole_cfg"
+ KnoeInstaller, "_save_prole_cfg"
), patch.object(
- ProleInstaller, "_save_ansible_prole_vault"
+ KnoeInstaller, "_save_ansible_prole_vault"
), patch.object(
- ProleInstaller, "_after_env_saved"
+ KnoeInstaller, "_after_env_saved"
), patch(
"threading.Thread"
):
@@ -52,7 +52,7 @@ def mock_installer(tmp_path):
root.winfo_screenwidth.return_value = 1920
root.winfo_screenheight.return_value = 1080
- app = ProleInstaller(root, config_path=str(cfg_path))
+ app = KnoeInstaller(root, config_path=str(cfg_path))
return app
diff --git a/tests/test_reconcile_blocked_cluster_state.py b/tests/test_reconcile_blocked_cluster_state.py
index 70df316..2db4d6b 100644
--- a/tests/test_reconcile_blocked_cluster_state.py
+++ b/tests/test_reconcile_blocked_cluster_state.py
@@ -5,13 +5,13 @@ from types import SimpleNamespace
import pytest
import knoe.core.actions as actions
-from knoe.core.actions import ProleConsoleInstaller
-from knoe.core.controller import ProleController
+from knoe.core.actions import KnoeConsoleInstaller
+from knoe.core.controller import KnoeController
-def _mk_installer(tmp_path) -> ProleConsoleInstaller:
- c = ProleController(tmp_path)
- return ProleConsoleInstaller(c)
+def _mk_installer(tmp_path) -> KnoeConsoleInstaller:
+ c = KnoeController(tmp_path)
+ return KnoeConsoleInstaller(c)
def test_repair_flow_classifies_blockers_attempts_reclaim_and_avoids_blind_health_wait(
diff --git a/tests/test_repair_stale_released_pv_claimref.py b/tests/test_repair_stale_released_pv_claimref.py
index ba2eb06..3699224 100644
--- a/tests/test_repair_stale_released_pv_claimref.py
+++ b/tests/test_repair_stale_released_pv_claimref.py
@@ -6,8 +6,8 @@ import os
from types import SimpleNamespace
import knoe.core.actions as actions
-from knoe.core.actions import ProleConsoleInstaller
-from knoe.core.controller import ProleController
+from knoe.core.actions import KnoeConsoleInstaller
+from knoe.core.controller import KnoeController
@contextmanager
@@ -30,9 +30,9 @@ def _preserve_namespace_env():
os.environ[k] = v
-def _mk_installer(tmp_path) -> ProleConsoleInstaller:
- c = ProleController(tmp_path)
- return ProleConsoleInstaller(c)
+def _mk_installer(tmp_path) -> KnoeConsoleInstaller:
+ c = KnoeController(tmp_path)
+ return KnoeConsoleInstaller(c)
def test_match_stale_released_pv_happy_path(tmp_path):
diff --git a/tests/test_repair_update_and_supabase_flags.py b/tests/test_repair_update_and_supabase_flags.py
index 9405e16..f70f5dc 100644
--- a/tests/test_repair_update_and_supabase_flags.py
+++ b/tests/test_repair_update_and_supabase_flags.py
@@ -67,19 +67,19 @@ NAMESPACE = test-ns
def _make_installer(tmp_path: Path):
- """Return a minimal ProleConsoleInstaller with mocked controller."""
- from knoe.core.actions import ProleConsoleInstaller
- from knoe.core.controller import ProleController
+ """Return a minimal KnoeConsoleInstaller with mocked controller."""
+ from knoe.core.actions import KnoeConsoleInstaller
+ from knoe.core.controller import KnoeController
cfg_path = tmp_path / "prole.cfg"
if not cfg_path.exists():
_write_cfg(cfg_path, "[Inputs]\n[Global]\n[Install]\nSTATUS = New\n")
- controller = MagicMock(spec=ProleController)
+ controller = MagicMock(spec=KnoeController)
controller.project_root = tmp_path
controller.state = MagicMock()
- installer = ProleConsoleInstaller.__new__(ProleConsoleInstaller)
+ installer = KnoeConsoleInstaller.__new__(KnoeConsoleInstaller)
installer.controller = controller
installer.cfg_path = cfg_path
installer.inputs = {}
@@ -92,14 +92,14 @@ def _make_installer(tmp_path: Path):
def _defaults(installer):
"""Call _default_inputs() with all internal deps stubbed."""
- from knoe.core.actions import ProleConsoleInstaller
+ from knoe.core.actions import KnoeConsoleInstaller
with (
- patch.object(ProleConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
- patch.object(ProleConsoleInstaller, "_initial_db_namespace", return_value="test-ns"),
- patch.object(ProleConsoleInstaller, "_cnpg_cluster_name", return_value="knoe-db"),
- patch.object(ProleConsoleInstaller, "_get_local_owner", return_value="root"),
- patch.object(ProleConsoleInstaller, "_env_defaults", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_initial_db_namespace", return_value="test-ns"),
+ patch.object(KnoeConsoleInstaller, "_cnpg_cluster_name", return_value="knoe-db"),
+ patch.object(KnoeConsoleInstaller, "_get_local_owner", return_value="root"),
+ patch.object(KnoeConsoleInstaller, "_env_defaults", return_value={}),
):
return installer._default_inputs()
@@ -151,8 +151,8 @@ def test_main_update_flag_dispatches_to_run_update(tmp_path, monkeypatch):
monkeypatch.setattr(sys, "argv", ["install.py", "--update", "-c", str(cfg_path)])
monkeypatch.setattr(screens_mod, "_ensure_ansible_vault_credentials", lambda **_: None)
- monkeypatch.setattr(screens_mod, "ProleController", MagicMock(return_value=MagicMock()))
- monkeypatch.setattr(screens_mod, "ProleConsoleInstaller", MagicMock(return_value=installer))
+ monkeypatch.setattr(screens_mod, "KnoeController", MagicMock(return_value=MagicMock()))
+ monkeypatch.setattr(screens_mod, "KnoeConsoleInstaller", MagicMock(return_value=installer))
with pytest.raises(SystemExit) as ex:
screens_mod.main()
@@ -198,7 +198,7 @@ def test_main_rejects_delete_db_without_reset(tmp_path, monkeypatch):
def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch):
- """install.py --delete-db must be forwarded to ProleConsoleInstaller in silent mode."""
+ """install.py --delete-db must be forwarded to KnoeConsoleInstaller in silent mode."""
import knoe.ui.screens as screens_mod
cfg_path = tmp_path / "prole.cfg"
@@ -217,8 +217,8 @@ def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch)
["install.py", "--silent", "--reset", "--delete-db", "-c", str(cfg_path)],
)
monkeypatch.setattr(screens_mod, "_ensure_ansible_vault_credentials", lambda **_: None)
- monkeypatch.setattr(screens_mod, "ProleController", MagicMock(return_value=MagicMock()))
- monkeypatch.setattr(screens_mod, "ProleConsoleInstaller", installer_factory)
+ monkeypatch.setattr(screens_mod, "KnoeController", MagicMock(return_value=MagicMock()))
+ monkeypatch.setattr(screens_mod, "KnoeConsoleInstaller", installer_factory)
with pytest.raises(SystemExit) as ex:
screens_mod.main()
@@ -233,7 +233,7 @@ def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch)
def test_repair_flag_does_not_trigger_silent_install(tmp_path):
"""--repair must dispatch to run_repair(), NOT run() (full install)."""
- from knoe.core.actions import ProleConsoleInstaller
+ from knoe.core.actions import KnoeConsoleInstaller
installer = _make_installer(tmp_path)
@@ -241,9 +241,9 @@ def test_repair_flag_does_not_trigger_silent_install(tmp_path):
run_repair_called = []
with (
- patch.object(ProleConsoleInstaller, "run", side_effect=lambda: run_called.append(1) or 0),
+ patch.object(KnoeConsoleInstaller, "run", side_effect=lambda: run_called.append(1) or 0),
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"run_repair",
side_effect=lambda: run_repair_called.append(1) or 0,
),
@@ -256,38 +256,38 @@ def test_repair_flag_does_not_trigger_silent_install(tmp_path):
def test_update_calls_repair_first(tmp_path):
"""run_update() must invoke run_repair() before applying config changes."""
- from knoe.core.actions import ProleConsoleInstaller
+ from knoe.core.actions import KnoeConsoleInstaller
installer = _make_installer(tmp_path)
call_order: list[str] = []
with (
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"run_repair",
side_effect=lambda: call_order.append("repair") or 0,
),
- patch.object(ProleConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
- patch.object(ProleConsoleInstaller, "_default_inputs", return_value={}),
- patch.object(ProleConsoleInstaller, "_service_namespace", return_value="test-ns"),
- patch.object(ProleConsoleInstaller, "_script_env_for_namespace", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_load_inputs_from_cfg", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_default_inputs", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_service_namespace", return_value="test-ns"),
+ patch.object(KnoeConsoleInstaller, "_script_env_for_namespace", return_value={}),
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_reconcile_supabase",
side_effect=lambda env: call_order.append("reconcile"),
),
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_step_db_build",
side_effect=lambda: call_order.append("db_build"),
),
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_step_cnpg_deploy",
side_effect=lambda: call_order.append("cnpg_deploy"),
),
- patch.object(ProleConsoleInstaller, "_write_cfg", return_value=None),
- patch.object(ProleConsoleInstaller, "_close_log_file", return_value=None),
+ patch.object(KnoeConsoleInstaller, "_write_cfg", return_value=None),
+ patch.object(KnoeConsoleInstaller, "_close_log_file", return_value=None),
):
installer.run_update()
@@ -298,15 +298,15 @@ def test_update_calls_repair_first(tmp_path):
def test_update_forces_cnpg_rollout_only_for_deploy_step(tmp_path):
"""run_update() must force rollout for CNPG deploy, then restore configured value."""
- from knoe.core.actions import ProleConsoleInstaller
+ from knoe.core.actions import KnoeConsoleInstaller
installer = _make_installer(tmp_path)
seen_force_rollout: list[bool] = []
with (
- patch.object(ProleConsoleInstaller, "run_repair", return_value=0),
+ patch.object(KnoeConsoleInstaller, "run_repair", return_value=0),
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_load_inputs_from_cfg",
return_value={
"init_cnpg_deploy.run_deploy": "true",
@@ -315,13 +315,13 @@ def test_update_forces_cnpg_rollout_only_for_deploy_step(tmp_path):
"init_cluster.supabase_enabled": "false",
},
),
- patch.object(ProleConsoleInstaller, "_default_inputs", return_value={}),
- patch.object(ProleConsoleInstaller, "_service_namespace", return_value="test-ns"),
- patch.object(ProleConsoleInstaller, "_script_env_for_namespace", return_value={}),
- patch.object(ProleConsoleInstaller, "_write_cfg", return_value=None),
- patch.object(ProleConsoleInstaller, "_close_log_file", return_value=None),
+ patch.object(KnoeConsoleInstaller, "_default_inputs", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_service_namespace", return_value="test-ns"),
+ patch.object(KnoeConsoleInstaller, "_script_env_for_namespace", return_value={}),
+ patch.object(KnoeConsoleInstaller, "_write_cfg", return_value=None),
+ patch.object(KnoeConsoleInstaller, "_close_log_file", return_value=None),
patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_step_cnpg_deploy",
side_effect=lambda: seen_force_rollout.append(
installer._get_input_bool("init_cnpg_deploy.force_rollout", False)
@@ -490,7 +490,7 @@ def test_render_supabase_component_flags_default_to_enabled(tmp_path, monkeypatc
def test_reconcile_supabase_skips_studio_when_disabled(tmp_path):
"""_reconcile_supabase() must skip Studio when supabase_studio_enabled=false."""
- from knoe.core.actions import ProleConsoleInstaller
+ from knoe.core.actions import KnoeConsoleInstaller
installer = _make_installer(tmp_path)
base = _defaults(installer)
@@ -502,7 +502,7 @@ def test_reconcile_supabase_skips_studio_when_disabled(tmp_path):
studio_reconcile_called = []
with patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_reconcile_supabase_studio",
side_effect=lambda env: studio_reconcile_called.append(1),
):
@@ -515,7 +515,7 @@ def test_reconcile_supabase_skips_studio_when_disabled(tmp_path):
def test_reconcile_supabase_calls_studio_when_enabled(tmp_path):
"""_reconcile_supabase() must invoke Studio reconcile when enabled."""
- from knoe.core.actions import ProleConsoleInstaller
+ from knoe.core.actions import KnoeConsoleInstaller
installer = _make_installer(tmp_path)
base = _defaults(installer)
@@ -527,7 +527,7 @@ def test_reconcile_supabase_calls_studio_when_enabled(tmp_path):
studio_reconcile_called = []
with patch.object(
- ProleConsoleInstaller,
+ KnoeConsoleInstaller,
"_reconcile_supabase_studio",
side_effect=lambda env: studio_reconcile_called.append(1),
):
diff --git a/tests/test_service_layer_navigation.py b/tests/test_service_layer_navigation.py
index 45c023c..02b897f 100644
--- a/tests/test_service_layer_navigation.py
+++ b/tests/test_service_layer_navigation.py
@@ -1,8 +1,9 @@
import sys
from contextlib import ExitStack
-from unittest.mock import patch, MagicMock
-import pytest
from pathlib import Path
+from unittest.mock import patch, MagicMock
+
+import pytest
# Mock tkinter and other GUI/macOS specific imports
sys.modules["tkinter"] = MagicMock()
@@ -15,8 +16,7 @@ sys.modules["PIL"] = MagicMock()
sys.modules["PIL.Image"] = MagicMock()
sys.modules["PIL.ImageTk"] = MagicMock()
-import install
-from install import ProleInstaller
+from install import KnoeInstaller
@pytest.fixture
@@ -38,10 +38,10 @@ def mock_installer(tmp_path):
stack.enter_context(patch("install.ttk.Style"))
stack.enter_context(patch("install.Path.exists", return_value=True))
stack.enter_context(patch("install.Path.mkdir"))
- stack.enter_context(patch.object(ProleInstaller, "_save_env_to_file"))
- stack.enter_context(patch.object(ProleInstaller, "_save_prole_cfg"))
- stack.enter_context(patch.object(ProleInstaller, "_save_ansible_prole_vault"))
- stack.enter_context(patch.object(ProleInstaller, "_after_env_saved"))
+ stack.enter_context(patch.object(KnoeInstaller, "_save_env_to_file"))
+ stack.enter_context(patch.object(KnoeInstaller, "_save_prole_cfg"))
+ stack.enter_context(patch.object(KnoeInstaller, "_save_ansible_prole_vault"))
+ stack.enter_context(patch.object(KnoeInstaller, "_after_env_saved"))
stack.enter_context(patch("threading.Thread"))
mock_entry.return_value.get.return_value.strip.return_value = "mock_val"
@@ -49,7 +49,7 @@ def mock_installer(tmp_path):
root.winfo_screenwidth.return_value = 1920
root.winfo_screenheight.return_value = 1080
- app = ProleInstaller(root, config_path=str(cfg_path))
+ app = KnoeInstaller(root, config_path=str(cfg_path))
try:
app.bg_canvas.winfo_width.return_value = 1000
except Exception: