mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 12:03:59 +00:00
Migrate install.py logic to install.sh and restructure installer flow. Update core references, tests, and navigation to reflect the new Knoe installer architecture.
This commit is contained in:
parent
c8505e6976
commit
e9e53aac40
@ -2,11 +2,6 @@
|
||||
# Format: key: local=... remote=... ns=... svc=... address=...
|
||||
|
||||
argocd: local=8081 remote=80 ns=argocd svc=argocd-server address=0.0.0.0
|
||||
supabase-studio: local=18080 remote=3000 ns=supabase svc=studio address=0.0.0.0
|
||||
supabase-auth: local=9999 remote=9999 ns=supabase svc=auth address=127.0.0.1
|
||||
supabase-rest: local=3001 remote=3000 ns=supabase svc=rest address=0.0.0.0
|
||||
supabase-realtime: local=4000 remote=4000 ns=supabase svc=realtime address=0.0.0.0
|
||||
gitea-http: local=13000 remote=3000 ns=gitea svc=gitea-http address=0.0.0.0
|
||||
gitea-ssh: local=22 remote=22 ns=gitea svc=gitea-ssh address=0.0.0.0
|
||||
garage: local=3900 remote=3900 ns=knoe-system svc=garage address=0.0.0.0
|
||||
openbao: local=8200 remote=8200 ns=knoe-system svc=openbao address=0.0.0.0
|
||||
@ -15,4 +10,4 @@ dashboard: local=8443 remote=443 ns=kubernetes-dashboard svc=kubernetes-dashboar
|
||||
postgres: local=5432 remote=5432 ns=knoe-db svc=knoe-db-rw address=0.0.0.0
|
||||
prometheus: local=9090 remote=9090 ns=monitoring svc=kps-kube-prometheus-stack-prometheus address=127.0.0.1
|
||||
grafana: local=3000 remote=80 ns=monitoring svc=kps-grafana address=0.0.0.0
|
||||
supabase-kong: local=8000 remote=8000 ns=supabase svc=kong address=0.0.0.0
|
||||
gitea-http: local=13000 remote=3000 ns=gitea svc=gitea-http address=0.0.0.0
|
||||
|
||||
16
conf/prod/gcp.cfg
Normal file
16
conf/prod/gcp.cfg
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by knoe/config.py --mode k8s --provider gcp
|
||||
# Copy org_id, billing_account, billing_project into:
|
||||
# deploy/gcp/terraform/cloud-setup.auto.tfvars
|
||||
# Loaded automatically by the installer into conf/prod/prole.cfg [GCP]
|
||||
|
||||
# Org: knoey.com
|
||||
org_id = "584001916389"
|
||||
|
||||
# Billing account: My Billing Account
|
||||
billing_account = "01193C-25783B-3211AD"
|
||||
|
||||
# Billing project (used for API quota / billing attribution)
|
||||
billing_project = "plenary-truck-485623-p7"
|
||||
|
||||
# Selected project: Knoey Auth
|
||||
project_id = "plenary-truck-485623-p7"
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-03-31 18:38:57
|
||||
; Generated by install.py on 2026-04-01 00:50:45
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -48,6 +48,7 @@ env_setup.PROLE_DATA = /Users/chrisfu/dev/prole/data
|
||||
env_setup.PROLE_HOME = /Users/chrisfu/dev/prole
|
||||
env_setup.PROLE_LOGS = /Users/chrisfu/dev/prole/logs
|
||||
env_setup.PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
|
||||
gitops.git_provider = Gitea
|
||||
gitops.node_selector = gandalf.prole.org
|
||||
init_cluster.argocd_enabled = false
|
||||
init_cluster.at_rest_encryption_enabled = true
|
||||
@ -55,17 +56,11 @@ init_cluster.cluster_env = service
|
||||
init_cluster.deployment_target = prole-service-cluster
|
||||
init_cluster.gitops_enabled = true
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:m0esKgX4z-UxYI3V:Rtau88iOMq7zuzT4A9y5PnaWy80PrSqFmoo9vqqXAsOORqFIM1CdcDyFdkHxmIPKTsSEnj91LXmRr3mie83lq4Y-qRj1PMmDD5f3yAtvFA33i709FunDECChkJLVoZJrDHGnRuSaA5ZHwIvqNXRY36S4LxCKluHk6scy8A==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:-5rud_XHzHRKoZIU:Dxv2nKbY883uDrJlmjRIqUKTtF42KPwXmzONy1s0-cIsJ2TLpTkaSWjfYx0tXNnz65lPnwIR16kXVtICYHhOxhfSIaz-lQiNGwZ_hm1tGMjp9BvpANt_l5Ie6swbOHVw8QfpGLtiXuIv7ur2ocSX4xAYSdCKtYWWV58U0Q==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k3s
|
||||
init_cluster.start_cluster = true
|
||||
init_cluster.supabase_analytics_enabled = true
|
||||
init_cluster.supabase_auth_enabled = true
|
||||
init_cluster.supabase_enabled = true
|
||||
init_cluster.supabase_meta_enabled = true
|
||||
init_cluster.supabase_realtime_enabled = true
|
||||
init_cluster.supabase_studio_enabled = false
|
||||
init_cluster.supabase_studio_url = db.prole.org
|
||||
init_cluster.supabase_enabled = false
|
||||
init_cnpg_deploy.force_rollout = false
|
||||
init_cnpg_deploy.run_deploy = true
|
||||
init_db_build.run_build = true
|
||||
@ -78,7 +73,6 @@ init_password.db_username = root
|
||||
init_password.generate_ssh_key = true
|
||||
init_scripts.run_scripts = true
|
||||
kerberos_config.enabled = true
|
||||
kerberos_config.init_authority = false
|
||||
kerberos_config.kdc = 10.0.0.3
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:U-EPMl7qv4heEB1k:BEcXGbI_LT4lCXwBuvFEGgPbLFa9MpVzECObdH0pbLtClDHf}
|
||||
kerberos_config.realm = PROLE.ORG
|
||||
@ -107,7 +101,6 @@ DB_HOST_PORT = 5432
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:t7NgRHfXTXH-mHcx:CRbKP9b6ccuxrTMMeJ742Q48_vegoUY4}
|
||||
DEPLOYMENT_MODE = k3s
|
||||
DEPLOYMENT_TARGET = prole-service-cluster
|
||||
DOCKER_IMPORT_DIR =
|
||||
DOCKER_PRELOAD = false
|
||||
GITEA_NODE_SELECTOR = gandalf.prole.org
|
||||
K3S_SERVER = https://myrddin.prole.org:6443
|
||||
@ -116,9 +109,8 @@ KNOE_DB_USER = root
|
||||
KUBECONTEXT = knoe.dev.prole.org
|
||||
OPENTOFU_URL = http://myrddin.prole.org:8080
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
|
||||
PROLE_HOME = /Users/chrisfu/dev/prole
|
||||
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
|
||||
PROLE_K3S_TOKEN = K107c8c6000488eca4a067d8a73119bbae2f07b4ea1bac7d8d3dc9c500cbb8acb18::server:04572345810eae2f9619a6ed4239702b
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:-5rud_XHzHRKoZIU:Dxv2nKbY883uDrJlmjRIqUKTtF42KPwXmzONy1s0-cIsJ2TLpTkaSWjfYx0tXNnz65lPnwIR16kXVtICYHhOxhfSIaz-lQiNGwZ_hm1tGMjp9BvpANt_l5Ie6swbOHVw8QfpGLtiXuIv7ur2ocSX4xAYSdCKtYWWV58U0Q==}
|
||||
REGISTRY_NAMESPACE = knoe-system
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
@ -145,21 +137,7 @@ KDC_AUTO_DETECTED = 10.0.0.3
|
||||
KERBEROS_AUTO_ENABLED = True
|
||||
|
||||
[Port Forwards]
|
||||
PORT_FORWARD_K3S_MAPPING_1 = id=argocd;namespace=argocd;target=svc/argocd-server;address=0.0.0.0;hostPort=8081;servicePort=80;protocol=TCP;description=ArgoCD
|
||||
PORT_FORWARD_K3S_MAPPING_10 = id=supabase-studio;namespace=supabase;target=svc/studio;address=0.0.0.0;hostPort=18080;servicePort=3000;protocol=TCP;description=Supabase Studio
|
||||
PORT_FORWARD_K3S_MAPPING_11 = id=supabase-auth;namespace=supabase;target=svc/auth;address=127.0.0.1;hostPort=9999;servicePort=9999;protocol=TCP;description=Supabase Auth (GoTrue)
|
||||
PORT_FORWARD_K3S_MAPPING_12 = id=supabase-rest;namespace=supabase;target=svc/rest;address=0.0.0.0;hostPort=3001;servicePort=3000;protocol=TCP;description=Supabase REST (PostgREST)
|
||||
PORT_FORWARD_K3S_MAPPING_13 = id=supabase-realtime;namespace=supabase;target=svc/realtime;address=0.0.0.0;hostPort=4000;servicePort=4000;protocol=TCP;description=Supabase Realtime
|
||||
PORT_FORWARD_K3S_MAPPING_14 = id=gitea-http;namespace=gitea;target=svc/gitea-http;address=0.0.0.0;hostPort=13000;servicePort=3000;protocol=TCP;description=Gitea Web
|
||||
PORT_FORWARD_K3S_MAPPING_15 = id=gitea-ssh;namespace=gitea;target=svc/gitea-ssh;address=0.0.0.0;hostPort=22;servicePort=22;protocol=TCP;description=Gitea SSH
|
||||
PORT_FORWARD_K3S_MAPPING_2 = id=garage;namespace=knoe-system;target=svc/garage;address=0.0.0.0;hostPort=3900;servicePort=3900;protocol=TCP;description=Garage S3
|
||||
PORT_FORWARD_K3S_MAPPING_3 = id=openbao;namespace=knoe-system;target=svc/openbao;address=0.0.0.0;hostPort=8200;servicePort=8200;protocol=TCP;description=OpenBao
|
||||
PORT_FORWARD_K3S_MAPPING_4 = id=opentofu;namespace=knoe-system;target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
|
||||
PORT_FORWARD_K3S_MAPPING_5 = id=dashboard;namespace=kubernetes-dashboard;target=svc/kubernetes-dashboard-kong-proxy;address=127.0.0.1;hostPort=8443;servicePort=443;protocol=TCP;description=Kubernetes Dashboard
|
||||
PORT_FORWARD_K3S_MAPPING_6 = id=postgres;namespace=${DATABASE_NAMESPACE};target=svc/knoe-db-rw;address=0.0.0.0;hostPort=5432;servicePort=5432;protocol=TCP;description=PostgreSQL (primary)
|
||||
PORT_FORWARD_K3S_MAPPING_7 = id=prometheus;namespace=monitoring;target=svc/kps-kube-prometheus-stack-prometheus;address=127.0.0.1;hostPort=9090;servicePort=9090;protocol=TCP;description=Prometheus UI
|
||||
PORT_FORWARD_K3S_MAPPING_8 = id=grafana;namespace=monitoring;target=svc/kps-grafana;address=0.0.0.0;hostPort=3000;servicePort=80;protocol=TCP;description=Grafana UI
|
||||
PORT_FORWARD_K3S_MAPPING_9 = id=supabase-kong;namespace=supabase;target=svc/kong;address=0.0.0.0;hostPort=8000;servicePort=8000;protocol=TCP;description=Supabase API (Kong)
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[System Environment]
|
||||
PROLE_CONF = /Users/chrisfu/dev/prole/conf
|
||||
@ -180,7 +158,7 @@ AD_TCP_PORTS = 88 389 445 464 636
|
||||
AD_UDP_PORTS = 88 464
|
||||
ENABLED = True
|
||||
KDC = 10.0.0.3
|
||||
PASSWORD = ${PROLE_SECRET:v1:jJ4tvCF8ef8xxQ-o:82chD7iVrlUNaDDA8K5DgYMWO2DN9FUSE7Rrm8IVhwuapK2P}
|
||||
PASSWORD = ${PROLE_SECRET:v1:bQj9RtiWj7fnkThC:2lYvxy8AwfMwM17P23R3QchdYbueGw4FNzeTsWTjohJJNAX9}
|
||||
REALM = PROLE.ORG
|
||||
SERVER = 10.0.0.3
|
||||
STATUS = Initialized
|
||||
@ -193,14 +171,14 @@ OLLAMA_SERVER_PORT = 11434
|
||||
|
||||
[Optional Features]
|
||||
ARGOCD_ENABLED = False
|
||||
AT_REST_ENCRYPTION_ENABLED = true
|
||||
GITOPS_ENABLED = true
|
||||
AT_REST_ENCRYPTION_ENABLED = True
|
||||
GITOPS_ENABLED = True
|
||||
GITOPS_PROVIDER = gitea
|
||||
KERBEROS_ENABLED = true
|
||||
SUPABASE_ENABLED = true
|
||||
KERBEROS_ENABLED = True
|
||||
SUPABASE_ENABLED = False
|
||||
|
||||
[GitOps]
|
||||
STATUS = Deployed
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Database Creation]
|
||||
DB_USER = root
|
||||
@ -208,12 +186,12 @@ DB_USER = root
|
||||
[Initialize Cluster]
|
||||
ENVIRONMENT = service
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:HAOwKPWbbBvJ7JUN:fuseXaNaQzGdzklTyEelMLldsma1MX7Ciq2P-nzrlsDXpJy9DXv4i4siVOa4aPIYvKP66fAh8IGfk_gwX0JwGQorSgGrgRpaEKjBtZ-0sh48AJPFkP7VkpQw4QZr5xzoDgAA96Q3r70TgDFHlo81OV8_riptpqYp5BO5nA==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:-5rud_XHzHRKoZIU:Dxv2nKbY883uDrJlmjRIqUKTtF42KPwXmzONy1s0-cIsJ2TLpTkaSWjfYx0tXNnz65lPnwIR16kXVtICYHhOxhfSIaz-lQiNGwZ_hm1tGMjp9BvpANt_l5Ie6swbOHVw8QfpGLtiXuIv7ur2ocSX4xAYSdCKtYWWV58U0Q==}
|
||||
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = k3d-knoe-dev-cluster
|
||||
CLUSTER_ENV = dev
|
||||
DISPLAY_NAME = knoe-dev-cluster
|
||||
KUBECTL_CONTEXT = service
|
||||
KUBECTL_CONTEXT = knoe.dev.prole.org
|
||||
MODE = k3d
|
||||
|
||||
[Service Cluster (k3s)]
|
||||
@ -222,17 +200,18 @@ DISPLAY_NAME = prole-service-cluster
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:-5rud_XHzHRKoZIU:Dxv2nKbY883uDrJlmjRIqUKTtF42KPwXmzONy1s0-cIsJ2TLpTkaSWjfYx0tXNnz65lPnwIR16kXVtICYHhOxhfSIaz-lQiNGwZ_hm1tGMjp9BvpANt_l5Ie6swbOHVw8QfpGLtiXuIv7ur2ocSX4xAYSdCKtYWWV58U0Q==}
|
||||
MODE = k3s
|
||||
PIPELINE_URL = http://myrddin.prole.org:8080
|
||||
|
||||
[GCP]
|
||||
; No configuration values captured yet for this section.
|
||||
BILLING_ACCOUNT = 01193C-25783B-3211AD
|
||||
BILLING_PROJECT = plenary-truck-485623-p7
|
||||
ORG_ID = 584001916389
|
||||
PROJECT_ID = plenary-truck-485623-p7
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
ARTIFACTS_DIR =
|
||||
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||
CLUSTER_ENV = prole-prod-cluster
|
||||
DISPLAY_NAME = prole-prod-cluster
|
||||
MODE = k8s
|
||||
PIPELINE_URL = http://myrddin.prole.org:8080
|
||||
|
||||
[Docker Build]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
98
demo/gcp-config.cast
Normal file
98
demo/gcp-config.cast
Normal file
@ -0,0 +1,98 @@
|
||||
{"version":3,"term":{"cols":80,"rows":24,"type":"xterm-256color","theme":{"fg":"#ebebeb","bg":"#000000","palette":"#000000:#990000:#00a600:#999900:#0000b3:#b300b3:#00a6b3:#bfbfbf:#666666:#e60000:#00d900:#e6e600:#0000ff:#e600e6:#00e6e6:#e6e6e6"}},"timestamp":1775025588,"command":"./config.sh --mode k8s --provider gcp","env":{"SHELL":"/bin/bash"}}
|
||||
[0.218, "o", "\u001b[?1049h\u001b[1;24r\u001b(B\u001b[m\u001b[4l\u001b[?7h\u001b[?1h\u001b="]
|
||||
[0.000, "o", "\u001b[39;49m\u001b[?1h\u001b="]
|
||||
[0.671, "o", "\u001b[?12l\u001b[?25h"]
|
||||
[0.000, "o", "\u001b[39;49m\u001b(B\u001b[m\u001b[H\u001b[2J\u001b(B\u001b[0;1mGCP Authentication\r\n\u001b(B\u001b[m───────────────────────────────────────────────────────────────────────────────\r\n\n \u001b(B\u001b[0;2mEnter your Google account email and a valid access token.\n\u001b[3GTab to move between fields. Enter on the token field to continue.\n\n\u001b[3G\u001b(B\u001b[0;1mEmail: \n\n\u001b[3G\u001b(B\u001b[mAccess token:\r\u001b[24d\u001b(B\u001b[0;2mTab next field Enter submit Esc cancel\u001b(B\u001b[m"]
|
||||
[3.258, "o", "\u001b[7;3HEmail:\u001b[K\r\n\n \u001b(B\u001b[0;1mAccess token:\u001b[24;43H\u001b(B\u001b[m"]
|
||||
[0.483, "o", "\u001b[7;3H\u001b(B\u001b[0;1mEmail: \n\n\u001b[3G\u001b(B\u001b[mAccess token:\u001b[24;43H"]
|
||||
[1.202, "o", "\u001b[7;17Hc\u001b[24;43H"]
|
||||
[0.137, "o", "\u001b[7;18Hh\u001b[24;43H"]
|
||||
[0.125, "o", "\u001b[7;19Hr\u001b[24;43H"]
|
||||
[0.103, "o", "\u001b[7;20Hi\u001b[24;43H"]
|
||||
[0.087, "o", "\u001b[7;21Hs\u001b[24;43H"]
|
||||
[0.058, "o", "\u001b[7;22Hf\u001b[24;43H"]
|
||||
[0.055, "o", "\u001b[7;23Hu\u001b[24;43H"]
|
||||
[0.304, "o", "\u001b[7;24H@\u001b[24;43H"]
|
||||
[0.729, "o", "\u001b[7;25Hk\u001b[24;43H"]
|
||||
[0.232, "o", "\u001b[7;26Hn\u001b[24;43H"]
|
||||
[0.236, "o", "\u001b[7;27Ho\u001b[24;43H"]
|
||||
[0.335, "o", "\u001b[7;28He\u001b[24;43H"]
|
||||
[0.109, "o", "\u001b[7;29Hy\u001b[24;43H"]
|
||||
[0.342, "o", "\u001b[7;30H.\u001b[24;43H"]
|
||||
[0.317, "o", "\u001b[7;31Hc\u001b[24;43H"]
|
||||
[0.109, "o", "\u001b[7;32Ho\u001b[24;43H"]
|
||||
[0.086, "o", "\u001b[7;33Hm\u001b[24;43H"]
|
||||
[0.274, "o", "\u001b[7;3HEmail: \r\n\n \u001b(B\u001b[0;1mAccess token:\u001b[24;43H\u001b(B\u001b[m"]
|
||||
[16.098, "o", "\u001b[9;17H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;18H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;19H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;20H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;21H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;22H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;23H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;24H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;25H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;26H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;27H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;28H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;29H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;30H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;31H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;32H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;33H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;34H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;35H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;36H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;37H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;38H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;39H*\u001b[24;43H\u001b[9;40H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;41H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;42H*\u001b[24;43H\u001b[9;43H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;44H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;45H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;46H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;47H*\u001b[24;43H\u001b[9;48H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;49H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;50H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;51H*\u001b[24;43H"]
|
||||
[0.001, "o", "\u001b[9;52H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;53H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;54H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;55H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;56H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;57H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;58H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;59H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;60H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;61H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;62H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;63H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;64H*\u001b[24;43H\u001b[9;65H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;66H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;67H*\u001b[24;43H\u001b[9;68H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;69H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;70H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;71H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;72H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;73H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;74H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;75H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;76H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;77H*\u001b[24;43H"]
|
||||
[0.000, "o", "\u001b[9;78H*\u001b[24;43H"]
|
||||
[1.231, "o", "\u001b[?25l"]
|
||||
[0.000, "o", "\r\u001b[4d\u001b[J\u001b[HAuthenticated as: chrisfu@knoey.com\r\n\u001b[K\nFetching GCP organizations…"]
|
||||
[1.056, "o", "\u001b[H\u001b(B\u001b[0;1mSelect GCP Organization\u001b(B\u001b[m\u001b[K\r\n───────────────────────────────────────────────────────────────────────────────\r\n \u001b(B\u001b[0;2mAuthenticated as: chrisfu@knoey.com\n\u001b[3G\u001b(B\u001b[0;7m knoey.com 584001916389 \r\u001b[24d\u001b(B\u001b[0;2m↑↓/jk navigate Enter select q quit 1/1\u001b(B\u001b[m"]
|
||||
[2.787, "o", "\u001b[H\n\u001b[J\u001b[AFetching projects…\u001b[K"]
|
||||
[1.020, "o", "\r\u001b(B\u001b[0;1mSelect GCP Project\r\n\u001b(B\u001b[m───────────────────────────────────────────────────────────────────────────────\r\n \u001b(B\u001b[0;2mOrg: knoey.com (2 projects)\n\u001b[3G\u001b(B\u001b[0;7m Knoey Auth plenary-truck-485623-p7 #507759 \u001b[5;3H\u001b(B\u001b[mTest New Project\u001b[5;40Htest-new-project-491616\u001b[5;72H#826674\r\u001b[24d\u001b(B\u001b[0;2m↑↓/jk navigate Enter select q quit 1/2\u001b(B\u001b[m"]
|
||||
[1.171, "o", "\u001b[4;3HKnoey Auth\u001b[27X\u001b[4;40Hplenary-truck-485623-p7 #507759\u001b[K\r\n \u001b(B\u001b[0;7m Test New Project test-new-project-491616 #826674 \u001b[24;41H\u001b(B\u001b[0;2m2/2\u001b(B\u001b[m"]
|
||||
[0.549, "o", "\u001b[4;3H\u001b(B\u001b[0;7m Knoey Auth plenary-truck-485623-p7 #507759 \u001b[5;3H\u001b(B\u001b[mTest New Project\u001b[21X\u001b[5;40Htest-new-project-491616 #826674\u001b[K\u001b[24;41H\u001b(B\u001b[0;2m1/2\u001b(B\u001b[m"]
|
||||
[0.672, "o", "\u001b[H\n\u001b[J\u001b[AFetching billing accounts…"]
|
||||
[31.063, "o", "\r\u001b(B\u001b[0;1mSelect Billing Account\u001b(B\u001b[m\u001b[K\r\n───────────────────────────────────────────────────────────────────────────────\r\n \u001b(B\u001b[0;2mProject: plenary-truck-485623-p7\n\u001b[3G\u001b(B\u001b[0;7m My Billing Account 01193C-25783B-3211AD \r\u001b[24d\u001b(B\u001b[0;2m↑↓/jk navigate Enter select q quit 1/1\u001b(B\u001b[m"]
|
||||
[2.113, "o", "\u001b[?12l\u001b[?25h"]
|
||||
[0.001, "o", "\u001b[H\u001b(B\u001b[0;1mConfig: --mode k8s --provider gcp — Confirm & Save\n\n\u001b[3G\u001b(B\u001b[mOrg ID: 584001916389\u001b[K\r\n Org Name: knoey.com\u001b[K\r\n Project ID:\u001b[5;22Hplenary-truck-485623-p7\r\n Project Name:\u001b[22GKnoey Auth\r\n Project Number: 507759242125\r\n Billing Account: 01193C-25783B-3211AD\r\n Billing Name:\u001b[22GMy Billing Account\r\n\n \u001b(B\u001b[0;1mOutput file: \u001b(B\u001b[m/Users/chrisfu/dev/prole/conf/prod/gcp.cfg\r\u001b[24d\u001b(B\u001b[0;2mEdit path above Enter to write Esc to cancel\u001b(B\u001b[m"]
|
||||
[4.691, "o", "\u001b[?25l"]
|
||||
[0.002, "o", "\u001b[HConfiguration written.\u001b[K\r\n\u001b[K\n /Users/chrisfu/dev/prole/conf/prod/gcp.cfg\r\n\u001b[K\nNext steps:\u001b[K\r\n 1. Copy org_id / billing_account / billing_project into:\n\u001b[7G\u001b[1K deploy/gcp/terraform/cloud-setup.auto.tfvars\r\n 2. Open the installer — Prod Cluster → Cloud tab will be pre-filled\r\n (or click 'Load from gcp.cfg' to refresh on demand)\r\n\n\u001b[K\u001b[24d\u001b(B\u001b[0;2mPress any key to continue…\u001b(B\u001b[m\u001b[K"]
|
||||
[2.724, "o", "\u001b[?1l\u001b>"]
|
||||
[0.000, "o", "\u001b[24;1H\u001b[?12l\u001b[?25h\u001b[?1049l\r\u001b[?1l\u001b>"]
|
||||
[0.011, "x", "0"]
|
||||
BIN
demo/gcp-config.gif
Normal file
BIN
demo/gcp-config.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
@ -11,6 +11,7 @@ MODE="$(prole_normalize_mode "${PROLE_MODE:-${DEPLOYMENT_MODE:-k3d}}")"
|
||||
NAMESPACE="${GITLAB_NAMESPACE:-}"
|
||||
CFG_PATH=""
|
||||
FORCE=0
|
||||
NODE_SELECTOR="${GITLAB_NODE_SELECTOR:-${NODE_SELECTOR:-gandalf.prole.org}}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
@ -19,36 +20,46 @@ Usage:
|
||||
|
||||
Options:
|
||||
--mode <k3d|k3s|k8s|local> Deployment mode (default: ${MODE:-k3d})
|
||||
-n, --namespace <name> Target namespace (default: gitlab)
|
||||
-c, --config <prole.cfg> Path to prole.cfg (defaults to detected)
|
||||
--force Uninstall existing GitLab release before deploy
|
||||
--help Show this help
|
||||
-n, --namespace <name> Target namespace (default: gitlab)
|
||||
-c, --config <prole.cfg> Path to prole.cfg (defaults to detected)
|
||||
--node-selector <node> Node to pin GitLab workloads (default: gandalf.prole.org)
|
||||
--force Remove existing GitLab and Gitea releases before deploy
|
||||
--help Show this help
|
||||
|
||||
Behavior:
|
||||
- Installs self-hosted GitLab via the official Helm chart.
|
||||
- Uses CloudNativePG (CNPG) Postgres as the database backend (external DB).
|
||||
- Removes any pre-existing git.prole.org Gitea configurations (helm release,
|
||||
namespace, Kong routes) when --force is specified or when gitea is detected.
|
||||
- Installs the GitLab Operator via Helm into the gitlab namespace.
|
||||
- Provisions a GitLab CR that uses the knoe-db CloudNativePG cluster as its
|
||||
external PostgreSQL data store.
|
||||
- Configures git.prole.org as a Kong-managed ingress pointing to GitLab.
|
||||
- Pins all GitLab workloads to the selected node (default: gandalf.prole.org).
|
||||
EOF
|
||||
}
|
||||
|
||||
die() { echo "[ERROR] $*" >&2; exit 2; }
|
||||
log() { echo "[INFO] $*"; }
|
||||
warn() { echo "[WARN] $*" >&2; }
|
||||
die() { echo "[ERROR] $*" >&2; exit 2; }
|
||||
log() { echo "[INFO] $*"; }
|
||||
warn() { echo "[WARN] $*" >&2; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mode) MODE="$(prole_normalize_mode "${2:-}")"; shift 2 ;;
|
||||
--mode=*) MODE="$(prole_normalize_mode "${1#*=}")"; shift 1 ;;
|
||||
-n|--namespace) NAMESPACE="${2:-}"; shift 2 ;;
|
||||
--namespace=*) NAMESPACE="${1#*=}"; shift 1 ;;
|
||||
-c|--config) CFG_PATH="${2:-}"; shift 2 ;;
|
||||
--config=*) CFG_PATH="${1#*=}"; shift 1 ;;
|
||||
--force) FORCE=1; shift 1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--mode) MODE="$(prole_normalize_mode "${2:-}")"; shift 2 ;;
|
||||
--mode=*) MODE="$(prole_normalize_mode "${1#*=}")"; shift 1 ;;
|
||||
-n|--namespace) NAMESPACE="${2:-}"; shift 2 ;;
|
||||
--namespace=*) NAMESPACE="${1#*=}"; shift 1 ;;
|
||||
-c|--config) CFG_PATH="${2:-}"; shift 2 ;;
|
||||
--config=*) CFG_PATH="${1#*=}"; shift 1 ;;
|
||||
--node-selector) NODE_SELECTOR="${2:-}"; shift 2 ;;
|
||||
--node-selector=*) NODE_SELECTOR="${1#*=}"; shift 1 ;;
|
||||
--force) FORCE=1; shift 1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Resolve config path and namespace defaults from prole.cfg when present
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resolve config path
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ -z "$CFG_PATH" && -n "${PROLE_CONF:-}" && -f "${PROLE_CONF}/prole.cfg" ]]; then
|
||||
CFG_PATH="${PROLE_CONF}/prole.cfg"
|
||||
elif [[ -z "$CFG_PATH" && -f "$SCRIPT_DIR/../conf/prole.cfg" ]]; then
|
||||
@ -56,8 +67,8 @@ elif [[ -z "$CFG_PATH" && -f "$SCRIPT_DIR/../conf/prole.cfg" ]]; then
|
||||
fi
|
||||
|
||||
if [[ -z "$NAMESPACE" && -n "$CFG_PATH" ]]; then
|
||||
maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITOPS_NAMESPACE")"
|
||||
[[ -z "$maybe_ns" ]] && maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITLAB_NAMESPACE")"
|
||||
maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITLAB_NAMESPACE")"
|
||||
[[ -z "$maybe_ns" ]] && maybe_ns="$(_prole_cfg_extract_key "$CFG_PATH" "GITOPS_NAMESPACE")"
|
||||
NAMESPACE="$maybe_ns"
|
||||
fi
|
||||
NAMESPACE="${NAMESPACE:-gitlab}"
|
||||
@ -70,105 +81,375 @@ esac
|
||||
export PROLE_MODE="$MODE"
|
||||
|
||||
command -v kubectl >/dev/null || die "kubectl not found"
|
||||
command -v helm >/dev/null || die "helm not found (required for GitLab install)"
|
||||
command -v helm >/dev/null || die "helm not found (required for GitLab Operator install)"
|
||||
|
||||
DB_NAMESPACE="${PROLE_NAMESPACE:-}"
|
||||
if [[ -z "$DB_NAMESPACE" && -n "$CFG_PATH" ]]; then
|
||||
DB_NAMESPACE="$(_prole_cfg_extract_key "$CFG_PATH" "NAMESPACE")"
|
||||
fi
|
||||
DB_NAMESPACE="${DB_NAMESPACE:-default}"
|
||||
|
||||
CNPG_CLUSTER_NAME="${CNPG_CLUSTER_NAME:-knoe-db}"
|
||||
# ---------------------------------------------------------------------------
|
||||
# knoe-db (CNPG) settings — reuse the same cluster gitea uses
|
||||
# ---------------------------------------------------------------------------
|
||||
DB_NAMESPACE="${KNOE_DB_NAMESPACE:-${DATABASE_NAMESPACE:-knoe-db}}"
|
||||
CNPG_CLUSTER_NAME="${CNPG_CLUSTER_NAME:-${CLUSTER_NAME:-knoe-db}}"
|
||||
GITLAB_DB_NAME="${GITLAB_DB_NAME:-gitlabhq_production}"
|
||||
GITLAB_DB_USER="${GITLAB_DB_USER:-gitlab}"
|
||||
GITLAB_DB_PASSWORD="${GITLAB_DB_PASSWORD:-}"
|
||||
if [[ -z "$GITLAB_DB_PASSWORD" ]]; then
|
||||
GITLAB_DB_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | head -c 32 || true)
|
||||
fi
|
||||
|
||||
DB_HOST="${CNPG_CLUSTER_NAME}-rw.${DB_NAMESPACE}.svc.cluster.local"
|
||||
DB_PORT="${GITLAB_DB_PORT:-5432}"
|
||||
|
||||
kubectl get ns "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" >/dev/null 2>&1
|
||||
# GitLab domain (reuses the git.prole.org shared hostname)
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-git.prole.org}"
|
||||
GITLAB_RELEASE="gitlab"
|
||||
|
||||
ensure_db() {
|
||||
local primary_pod=""
|
||||
primary_pod=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME},role=primary" \
|
||||
# ---------------------------------------------------------------------------
|
||||
# Operator chart coordinates
|
||||
# ---------------------------------------------------------------------------
|
||||
OPERATOR_REPO_NAME="gitlab-operator"
|
||||
OPERATOR_REPO_URL="https://gitlab.com/api/v4/projects/18899486/packages/helm/stable"
|
||||
OPERATOR_CHART="gitlab-operator/gitlab-operator"
|
||||
OPERATOR_RELEASE="gitlab-operator"
|
||||
OPERATOR_NAMESPACE="${GITLAB_OPERATOR_NAMESPACE:-${NAMESPACE}}"
|
||||
# GitLab chart version pinned inside the CR; leave empty to use operator default
|
||||
GITLAB_CHART_VERSION="${GITLAB_CHART_VERSION:-}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper: resolve knoe-db primary pod (same pattern as init_gitea.sh)
|
||||
# ---------------------------------------------------------------------------
|
||||
resolve_knoe_db_primary_pod() {
|
||||
local primary
|
||||
primary=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME},cnpg.io/instanceRole=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
if [[ -z "$primary_pod" ]]; then
|
||||
primary_pod=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
if [[ -z "$primary" ]]; then
|
||||
primary=$(kubectl -n "$DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${CNPG_CLUSTER_NAME}" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -z "$primary_pod" ]]; then
|
||||
die "Could not find CNPG pod for cluster '${CNPG_CLUSTER_NAME}' in namespace '${DB_NAMESPACE}'"
|
||||
fi
|
||||
|
||||
log "Ensuring database '${GITLAB_DB_NAME}' and role '${GITLAB_DB_USER}' exist in CNPG cluster '${CNPG_CLUSTER_NAME}' (ns=${DB_NAMESPACE})"
|
||||
|
||||
kubectl -n "$DB_NAMESPACE" exec "$primary_pod" -- bash -lc "psql -v ON_ERROR_STOP=1 -U postgres -d postgres" <<SQL
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${GITLAB_DB_USER}') THEN
|
||||
CREATE ROLE ${GITLAB_DB_USER} LOGIN PASSWORD '${GITLAB_DB_PASSWORD}';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '${GITLAB_DB_NAME}') THEN
|
||||
CREATE DATABASE ${GITLAB_DB_NAME} OWNER ${GITLAB_DB_USER};
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
SQL
|
||||
|
||||
kubectl -n "$NAMESPACE" create secret generic gitlab-db \
|
||||
--from-literal=host="$DB_HOST" \
|
||||
--from-literal=port="$DB_PORT" \
|
||||
--from-literal=database="$GITLAB_DB_NAME" \
|
||||
--from-literal=username="$GITLAB_DB_USER" \
|
||||
--from-literal=password="$GITLAB_DB_PASSWORD" \
|
||||
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
|
||||
printf '%s' "$primary"
|
||||
}
|
||||
|
||||
ensure_db
|
||||
sql_escape_literal() {
|
||||
printf '%s' "${1:-}" | sed "s/'/''/g"
|
||||
}
|
||||
|
||||
RELEASE_NAME="${GITLAB_RELEASE_NAME:-gitlab}"
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resolve GitLab DB password (pull from knoe-db-superuser secret as fallback)
|
||||
# ---------------------------------------------------------------------------
|
||||
resolve_gitlab_db_password() {
|
||||
if [[ -n "${GITLAB_DB_PASSWORD:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local resolved=""
|
||||
if kubectl -n "$DB_NAMESPACE" get secret knoe-db-superuser >/dev/null 2>&1; then
|
||||
resolved=$(kubectl -n "$DB_NAMESPACE" get secret knoe-db-superuser \
|
||||
-o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -n "$resolved" ]]; then
|
||||
GITLAB_DB_PASSWORD="$resolved"
|
||||
else
|
||||
# Generate a random password when none is available
|
||||
GITLAB_DB_PASSWORD="$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom 2>/dev/null | head -c 32 || true)"
|
||||
warn "Generated random GitLab DB password; store it in GITLAB_DB_PASSWORD for future runs."
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provision GitLab role + database in knoe-db
|
||||
# ---------------------------------------------------------------------------
|
||||
setup_knoe_db_for_gitlab() {
|
||||
local primary
|
||||
primary="$(resolve_knoe_db_primary_pod)"
|
||||
if [[ -z "$primary" ]]; then
|
||||
warn "No knoe-db pod found in namespace '${DB_NAMESPACE}'; skipping GitLab DB setup."
|
||||
return 0
|
||||
fi
|
||||
|
||||
resolve_gitlab_db_password
|
||||
|
||||
local admin_user=""
|
||||
local candidate
|
||||
for candidate in postgres root; do
|
||||
if kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$candidate" -d postgres -tAc "SELECT 1" >/dev/null 2>&1; then
|
||||
admin_user="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$admin_user" ]]; then
|
||||
warn "Unable to connect to knoe-db as admin; skipping GitLab DB setup."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local esc_pw
|
||||
esc_pw="$(sql_escape_literal "$GITLAB_DB_PASSWORD")"
|
||||
|
||||
kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$admin_user" -d postgres -c "
|
||||
DO \$\$ BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='${GITLAB_DB_USER}') THEN
|
||||
CREATE ROLE ${GITLAB_DB_USER} LOGIN PASSWORD '${esc_pw}';
|
||||
ELSE
|
||||
ALTER ROLE ${GITLAB_DB_USER} WITH PASSWORD '${esc_pw}';
|
||||
END IF;
|
||||
END \$\$;
|
||||
" >/dev/null 2>&1 || warn "Could not create/update role '${GITLAB_DB_USER}'."
|
||||
|
||||
local db_exists
|
||||
db_exists=$(kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$admin_user" -d postgres -tAc \
|
||||
"SELECT 1 FROM pg_database WHERE datname='${GITLAB_DB_NAME}';" 2>/dev/null || true)
|
||||
|
||||
if [[ "$db_exists" != "1" ]]; then
|
||||
kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$admin_user" -d postgres -c \
|
||||
"CREATE DATABASE ${GITLAB_DB_NAME} OWNER ${GITLAB_DB_USER};" \
|
||||
>/dev/null 2>&1 || warn "Could not create database '${GITLAB_DB_NAME}'."
|
||||
fi
|
||||
|
||||
log "GitLab database '${GITLAB_DB_NAME}' prepared in knoe-db (ns=${DB_NAMESPACE})."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Clean up any pre-existing Gitea / git.prole.org configurations
|
||||
# ---------------------------------------------------------------------------
|
||||
cleanup_gitea() {
|
||||
log "Checking for pre-existing Gitea deployment to remove before GitLab install..."
|
||||
|
||||
local gitea_ns="${GITEA_NAMESPACE:-gitea}"
|
||||
|
||||
# Remove Gitea Helm release
|
||||
if helm -n "$gitea_ns" status gitea >/dev/null 2>&1; then
|
||||
log "Uninstalling Gitea Helm release from namespace '$gitea_ns'..."
|
||||
helm -n "$gitea_ns" uninstall gitea >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Remove any leftover raw Gitea resources
|
||||
kubectl -n "$gitea_ns" delete deploy/gitea svc/gitea-http svc/gitea-ssh \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
# Remove Kong routes/services registered for git.prole.org (best-effort)
|
||||
local kong_ns="${KONG_NAMESPACE:-${NAMESPACE:-kong}}"
|
||||
for res_type in kongplugins kongingresses; do
|
||||
kubectl -n "$gitea_ns" delete "$res_type" --all >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
# Remove gitea namespace Ingress objects that route git.prole.org
|
||||
kubectl -n "$gitea_ns" delete ingress \
|
||||
-l "app.kubernetes.io/name=gitea" >/dev/null 2>&1 || true
|
||||
kubectl -n "$gitea_ns" delete ingress \
|
||||
--field-selector="metadata.name=gitea" >/dev/null 2>&1 || true
|
||||
|
||||
log "Gitea cleanup complete."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ensure gitlab namespace exists
|
||||
# ---------------------------------------------------------------------------
|
||||
kubectl get ns "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" >/dev/null
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --force: remove existing GitLab operator and release first
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "$FORCE" -eq 1 ]]; then
|
||||
warn "--force specified; uninstalling existing release '$RELEASE_NAME' in namespace '$NAMESPACE' (if present)"
|
||||
helm -n "$NAMESPACE" uninstall "$RELEASE_NAME" >/dev/null 2>&1 || true
|
||||
warn "--force: removing existing GitLab resources in namespace '$NAMESPACE'..."
|
||||
if kubectl get crd gitlabs.apps.gitlab.com >/dev/null 2>&1; then
|
||||
kubectl -n "$NAMESPACE" delete gitlab "$GITLAB_RELEASE" >/dev/null 2>&1 || true
|
||||
# Wait briefly for operator to clean up managed resources
|
||||
sleep 10
|
||||
fi
|
||||
helm -n "$NAMESPACE" uninstall "$GITLAB_RELEASE" >/dev/null 2>&1 || true
|
||||
helm -n "$NAMESPACE" uninstall "$OPERATOR_RELEASE" >/dev/null 2>&1 || true
|
||||
cleanup_gitea
|
||||
fi
|
||||
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-${DOMAIN:-${PROLE_DOMAIN:-}}}"
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-example.local}"
|
||||
# ---------------------------------------------------------------------------
|
||||
# Always remove gitea if its helm release exists (non-destructive path)
|
||||
# gitea and gitlab both claim git.prole.org; they cannot coexist.
|
||||
# ---------------------------------------------------------------------------
|
||||
if helm -n "${GITEA_NAMESPACE:-gitea}" status gitea >/dev/null 2>&1; then
|
||||
warn "Gitea release detected — removing to free git.prole.org for GitLab..."
|
||||
cleanup_gitea
|
||||
fi
|
||||
|
||||
log "Adding/updating GitLab Helm repo"
|
||||
helm repo add gitlab https://charts.gitlab.io/ >/dev/null 2>&1 || true
|
||||
helm repo update >/dev/null
|
||||
# ---------------------------------------------------------------------------
|
||||
# Prepare knoe-db
|
||||
# ---------------------------------------------------------------------------
|
||||
setup_knoe_db_for_gitlab
|
||||
|
||||
log "Installing GitLab (release=$RELEASE_NAME, ns=$NAMESPACE, domain=$GITLAB_DOMAIN)"
|
||||
# Persist the DB password as a k8s Secret the operator CR can reference
|
||||
resolve_gitlab_db_password
|
||||
kubectl -n "$NAMESPACE" create secret generic gitlab-db-password \
|
||||
--from-literal=password="$GITLAB_DB_PASSWORD" \
|
||||
--dry-run=client -o yaml | kubectl apply -f - >/dev/null
|
||||
log "gitlab-db-password secret applied in namespace '$NAMESPACE'."
|
||||
|
||||
# Notes:
|
||||
# - We keep the chart install minimal to avoid assuming ingress/cert-manager setup.
|
||||
# - We disable the bundled PostgreSQL and point GitLab to CNPG.
|
||||
helm upgrade --install "$RELEASE_NAME" gitlab/gitlab \
|
||||
-n "$NAMESPACE" \
|
||||
--timeout 30m \
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install / upgrade the GitLab Operator
|
||||
# ---------------------------------------------------------------------------
|
||||
log "Adding/updating GitLab Operator Helm repo..."
|
||||
helm repo add "$OPERATOR_REPO_NAME" "$OPERATOR_REPO_URL" >/dev/null 2>&1 || true
|
||||
helm repo update >/dev/null 2>&1
|
||||
|
||||
log "Installing GitLab Operator (release=${OPERATOR_RELEASE}, ns=${OPERATOR_NAMESPACE})..."
|
||||
|
||||
# The operator needs cluster-scoped RBAC; it watches all namespaces by default.
|
||||
helm upgrade --install "$OPERATOR_RELEASE" "$OPERATOR_CHART" \
|
||||
-n "$OPERATOR_NAMESPACE" \
|
||||
--create-namespace \
|
||||
--timeout 10m \
|
||||
--wait \
|
||||
--set global.hosts.domain="$GITLAB_DOMAIN" \
|
||||
--set global.hosts.https=false \
|
||||
--set certmanager.install=false \
|
||||
--set prometheus.install=false \
|
||||
--set postgresql.install=false \
|
||||
--set global.psql.host="$DB_HOST" \
|
||||
--set global.psql.port="$DB_PORT" \
|
||||
--set global.psql.username="$GITLAB_DB_USER" \
|
||||
--set global.psql.database="$GITLAB_DB_NAME" \
|
||||
--set global.psql.password.secret=gitlab-db \
|
||||
--set global.psql.password.key=password
|
||||
--set watchNamespace="$NAMESPACE"
|
||||
|
||||
log "Done. Inspect services/pods with: kubectl -n $NAMESPACE get pods,svc"
|
||||
log "GitLab Operator ready."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Determine node selector block for the GitLab CR
|
||||
# ---------------------------------------------------------------------------
|
||||
NODE_SELECTOR_YAML=""
|
||||
if [[ -n "$NODE_SELECTOR" ]]; then
|
||||
log "Pinning GitLab workloads to node: ${NODE_SELECTOR}"
|
||||
NODE_SELECTOR_YAML="kubernetes.io/hostname: ${NODE_SELECTOR}"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional chart version pin inside the CR
|
||||
# ---------------------------------------------------------------------------
|
||||
CHART_VERSION_YAML=""
|
||||
if [[ -n "$GITLAB_CHART_VERSION" ]]; then
|
||||
CHART_VERSION_YAML="version: \"${GITLAB_CHART_VERSION}\""
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Create the GitLab CR (operator reconciles this into the full deployment)
|
||||
# ---------------------------------------------------------------------------
|
||||
log "Applying GitLab CR (domain=${GITLAB_DOMAIN}, db=${GITLAB_DB_NAME}@${DB_HOST})..."
|
||||
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: apps.gitlab.com/v1beta1
|
||||
kind: GitLab
|
||||
metadata:
|
||||
name: ${GITLAB_RELEASE}
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
chart:
|
||||
${CHART_VERSION_YAML}
|
||||
values:
|
||||
global:
|
||||
hosts:
|
||||
domain: prole.org
|
||||
gitlab:
|
||||
name: ${GITLAB_DOMAIN}
|
||||
ssh:
|
||||
name: ${GITLAB_DOMAIN}
|
||||
ingress:
|
||||
class: kong
|
||||
configureCertmanager: false
|
||||
tls:
|
||||
enabled: false
|
||||
psql:
|
||||
host: ${DB_HOST}
|
||||
port: ${DB_PORT}
|
||||
username: ${GITLAB_DB_USER}
|
||||
database: ${GITLAB_DB_NAME}
|
||||
password:
|
||||
secret: gitlab-db-password
|
||||
key: password
|
||||
# Reuse knoe-system namespace conventions; the operator deploys into
|
||||
# the gitlab namespace but can reference services in knoe-db.
|
||||
serviceAccount:
|
||||
create: true
|
||||
# Disable bundled sub-charts — we provide our own data stores
|
||||
postgresql:
|
||||
install: false
|
||||
redis:
|
||||
install: false
|
||||
certmanager:
|
||||
install: false
|
||||
prometheus:
|
||||
install: false
|
||||
gitlab-runner:
|
||||
install: false
|
||||
# Minimal webservice config
|
||||
gitlab:
|
||||
webservice:
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: kong
|
||||
$(if [[ -n "$NODE_SELECTOR_YAML" ]]; then
|
||||
cat <<NODE
|
||||
nodeSelector:
|
||||
${NODE_SELECTOR_YAML}
|
||||
NODE
|
||||
fi)
|
||||
sidekiq:
|
||||
$(if [[ -n "$NODE_SELECTOR_YAML" ]]; then
|
||||
cat <<NODE
|
||||
nodeSelector:
|
||||
${NODE_SELECTOR_YAML}
|
||||
NODE
|
||||
fi)
|
||||
gitaly:
|
||||
$(if [[ -n "$NODE_SELECTOR_YAML" ]]; then
|
||||
cat <<NODE
|
||||
nodeSelector:
|
||||
${NODE_SELECTOR_YAML}
|
||||
NODE
|
||||
fi)
|
||||
registry:
|
||||
ingress:
|
||||
enabled: false
|
||||
EOF
|
||||
|
||||
log "GitLab CR applied — operator is reconciling (this may take 10-20 minutes)."
|
||||
log "Monitor progress: kubectl -n ${NAMESPACE} get gitlab ${GITLAB_RELEASE} -w"
|
||||
log "Watch pods: kubectl -n ${NAMESPACE} get pods -w"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optionally wait for GitLab to become available
|
||||
# ---------------------------------------------------------------------------
|
||||
WAIT_TIMEOUT="${GITLAB_WAIT_TIMEOUT:-1200}" # default 20 minutes
|
||||
if [[ "${GITLAB_NO_WAIT:-0}" != "1" ]]; then
|
||||
log "Waiting up to ${WAIT_TIMEOUT}s for GitLab CR to reach Ready status..."
|
||||
if ! kubectl -n "$NAMESPACE" wait gitlab/"$GITLAB_RELEASE" \
|
||||
--for=condition=Available \
|
||||
--timeout="${WAIT_TIMEOUT}s" 2>/dev/null; then
|
||||
warn "Timed out waiting for GitLab CR condition=Available."
|
||||
warn "The deployment may still be in progress. Check: kubectl -n ${NAMESPACE} describe gitlab ${GITLAB_RELEASE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kong ingress for git.prole.org -> gitlab-webservice
|
||||
# The GitLab Operator creates the Ingress; this block patches it to Kong class
|
||||
# or creates a fallback Kong-native ingress if the CR-managed one is absent.
|
||||
# ---------------------------------------------------------------------------
|
||||
WEBSERVICE_SVC="${GITLAB_RELEASE}-webservice-default"
|
||||
|
||||
log "Ensuring Kong ingress for ${GITLAB_DOMAIN} -> ${WEBSERVICE_SVC}:8181 ..."
|
||||
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitlab-kong-ingress
|
||||
namespace: ${NAMESPACE}
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: kong
|
||||
konghq.com/strip-path: "false"
|
||||
spec:
|
||||
ingressClassName: kong
|
||||
rules:
|
||||
- host: ${GITLAB_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ${WEBSERVICE_SVC}
|
||||
port:
|
||||
number: 8181
|
||||
EOF
|
||||
|
||||
log "Done."
|
||||
log "GitLab will be reachable at http://${GITLAB_DOMAIN}/ once pods are Running."
|
||||
log "Initial root password: kubectl -n ${NAMESPACE} get secret ${GITLAB_RELEASE}-gitlab-initial-root-password -o jsonpath='{.data.password}' | base64 -d"
|
||||
|
||||
31
install.py
31
install.py
@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Thin launch vehicle for the Prole installer.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# Keep these imports for backward-compatibility with tests that patch them.
|
||||
import tkinter as tk # noqa: F401
|
||||
from tkinter import ttk, scrolledtext, messagebox, filedialog # noqa: F401
|
||||
from pathlib import Path # noqa: F401
|
||||
|
||||
from knoe.ui.screens import (
|
||||
KnoeInstaller,
|
||||
KnoeController,
|
||||
get_resource_path,
|
||||
has_display,
|
||||
main,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"KnoeInstaller",
|
||||
"KnoeController",
|
||||
"get_resource_path",
|
||||
"has_display",
|
||||
"main",
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
44
install.sh
Executable file
44
install.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# Thin launch vehicle for the Prole installer.
|
||||
#
|
||||
# Local execution (from a repo checkout):
|
||||
# ./install.sh [args...]
|
||||
#
|
||||
# Bootstrap via curl (first-time install):
|
||||
# curl -fsSL https://knoey.com/install.sh | bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Repo URL used only during bootstrap (curl-pipe) installs.
|
||||
PROLE_REPO_URL="${PROLE_REPO_URL:-https://gitlab.knoey.com/prole/prole.git}"
|
||||
# Default install directory for bootstrap installs.
|
||||
_INSTALL_DIR="${PROLE_HOME:-$HOME/prole}"
|
||||
|
||||
_bootstrap() {
|
||||
echo "==> Bootstrapping Prole installer..."
|
||||
command -v git >/dev/null 2>&1 || { echo "Error: git is required." >&2; exit 1; }
|
||||
command -v python3 >/dev/null 2>&1 || { echo "Error: python3 is required." >&2; exit 1; }
|
||||
|
||||
if [[ -d "${_INSTALL_DIR}/.git" ]]; then
|
||||
echo "==> Updating existing checkout at ${_INSTALL_DIR} ..."
|
||||
git -C "${_INSTALL_DIR}" pull --ff-only
|
||||
else
|
||||
echo "==> Cloning to ${_INSTALL_DIR} ..."
|
||||
git clone "${PROLE_REPO_URL}" "${_INSTALL_DIR}"
|
||||
fi
|
||||
|
||||
exec "${_INSTALL_DIR}/install.sh" "$@"
|
||||
}
|
||||
|
||||
# Resolve script directory; empty when running via curl pipe.
|
||||
_src="${BASH_SOURCE[0]:-}"
|
||||
_script_dir="$(cd "$(dirname "${_src:-/}")" 2>/dev/null && pwd || echo "")"
|
||||
|
||||
if [[ -n "${_script_dir}" && -d "${_script_dir}/knoe" ]]; then
|
||||
# Local checkout: delegate to the Python installer module.
|
||||
cd "${_script_dir}"
|
||||
exec python3 -m knoe.ui.screens "$@"
|
||||
else
|
||||
# No local checkout found — bootstrap from the remote repo.
|
||||
_bootstrap "$@"
|
||||
fi
|
||||
@ -223,7 +223,7 @@ class KnoeNcursesInstaller:
|
||||
def _render_header(self):
|
||||
"""Render the header."""
|
||||
try:
|
||||
title = "Prole Database Installer"
|
||||
title = "Knoe System Installer"
|
||||
self.header_win.addstr(0, 2, title, curses.A_BOLD)
|
||||
self.header_win.addstr(1, 2, "─" * (self.width - 4))
|
||||
if self.status_message:
|
||||
@ -686,7 +686,7 @@ class KnoeNcursesInstaller:
|
||||
def _render_welcome(self):
|
||||
"""Render the welcome screen."""
|
||||
win = CursesWindow(self.main_content_win)
|
||||
win.render_title("Welcome to Prole Database Installer", y=2)
|
||||
win.render_title("Welcome to Knoe System Installer", y=2)
|
||||
win.render_paragraph(
|
||||
"This installer will guide you through setting up the Prole database system, "
|
||||
"including Docker containers, dependencies, and deployment configuration.",
|
||||
|
||||
@ -38,7 +38,7 @@ if platform.system() == "Darwin":
|
||||
try:
|
||||
from Foundation import NSProcessInfo
|
||||
|
||||
NSProcessInfo.processInfo().setProcessName_("Prole Database Installer")
|
||||
NSProcessInfo.processInfo().setProcessName_("Knoe System Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -211,7 +211,7 @@ class KnoeInstaller(
|
||||
self._reset_cluster = bool(reset_cluster)
|
||||
_ensure_ansible_vault_credentials(prompt_ui=True, root=root)
|
||||
self._action_flags = {}
|
||||
self.root.title("Prole Database Installer")
|
||||
self.root.title("Knoe System Installer")
|
||||
try:
|
||||
self._set_app_identity()
|
||||
except Exception:
|
||||
@ -331,9 +331,8 @@ class KnoeInstaller(
|
||||
("Database Creation", "init_password"),
|
||||
("Initialization Scripts", "init_scripts"),
|
||||
("Kerberos Authentication", "kerberos_config"),
|
||||
("GitOps Provider", "gitops_choice"),
|
||||
("ArgoCD", "argocd_config"),
|
||||
("GitOps / Gitea", "gitops_config"),
|
||||
("GitOps", "gitops_config"),
|
||||
("Supabase", "supabase_config"),
|
||||
("Deployment", "init_cnpg_deploy"),
|
||||
("Post Install", "create_installer"),
|
||||
@ -415,6 +414,7 @@ class KnoeInstaller(
|
||||
self.gitops_enabled = tk.BooleanVar(value=False)
|
||||
self.gitops_namespace = tk.StringVar(value="gitea")
|
||||
self.gitops_node_selector = tk.StringVar(value="")
|
||||
self.gitops_git_provider = tk.StringVar(value="Gitea")
|
||||
self.argocd_enabled = tk.BooleanVar(value=False)
|
||||
self.argocd_namespace = tk.StringVar(value="argocd")
|
||||
self.argocd_node_selector = tk.StringVar(value="")
|
||||
@ -830,7 +830,6 @@ class KnoeInstaller(
|
||||
self._register_canvas_renderer(
|
||||
"supabase_config", self._render_supabase_config_page
|
||||
)
|
||||
self._register_canvas_renderer("gitops_choice", self._render_gitops_choice_page)
|
||||
self._register_canvas_renderer("argocd_config", self._render_argocd_config_page)
|
||||
self._register_canvas_renderer("gitops_config", self._render_gitops_config_page)
|
||||
self._register_canvas_renderer("init_cluster", self._render_init_cluster_page)
|
||||
@ -869,7 +868,6 @@ class KnoeInstaller(
|
||||
self._register_page("init_password", None)
|
||||
self._register_page("init_scripts", None)
|
||||
self._register_page("kerberos_config", None)
|
||||
self._register_page("gitops_choice", None)
|
||||
self._register_page("argocd_config", None)
|
||||
self._register_page("gitops_config", None)
|
||||
self._register_page("supabase_config", None)
|
||||
@ -910,7 +908,7 @@ def has_display():
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Prole Database Installer")
|
||||
parser = argparse.ArgumentParser(description="Knoe System Installer")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
@ -1048,7 +1046,7 @@ def main():
|
||||
if use_gui:
|
||||
try:
|
||||
root = tk.Tk()
|
||||
root.title("Prole Database Installer")
|
||||
root.title("Knoe System Installer")
|
||||
window_width, window_height = 1000, 700
|
||||
try:
|
||||
sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
|
||||
|
||||
3
knoe/ui/screens/__main__.py
Normal file
3
knoe/ui/screens/__main__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from knoe.ui.screens import main
|
||||
|
||||
main()
|
||||
@ -25,7 +25,7 @@ class ArgoCDScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -34,7 +34,7 @@ class ArgoCDScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -110,7 +110,7 @@ class ScreenBaseMixin:
|
||||
x_options=190,
|
||||
option_spacing=175,
|
||||
):
|
||||
"""Render a reusable k3s node selector and return the next y offset."""
|
||||
"""Render a reusable k3s node selector dropdown and return the next y offset."""
|
||||
if not self._is_k3s_mode_active():
|
||||
return y
|
||||
|
||||
@ -124,35 +124,26 @@ class ScreenBaseMixin:
|
||||
)
|
||||
self._canvas_items.append(item)
|
||||
|
||||
selected = (target_var.get() or "").strip()
|
||||
nodes = self._fetch_k3s_nodes()
|
||||
x = x_options
|
||||
y_radio = y - 2
|
||||
for node in nodes:
|
||||
disabled = self._is_tainted_node(node)
|
||||
rb = tk.Radiobutton(
|
||||
self.bg_canvas,
|
||||
text=node,
|
||||
variable=target_var,
|
||||
value=node,
|
||||
bg="white",
|
||||
fg="#8c8c8c" if disabled else "black",
|
||||
activebackground="white",
|
||||
selectcolor="white",
|
||||
state="disabled" if disabled else "normal",
|
||||
font=("SF Pro Text", 10),
|
||||
)
|
||||
window = self.bg_canvas.create_window(x, y_radio, window=rb, anchor="nw")
|
||||
self._canvas_items.append(window)
|
||||
self._overlay_widgets.append(rb)
|
||||
x += option_spacing
|
||||
|
||||
if (not selected or selected not in nodes or self._is_tainted_node(selected)) and nodes:
|
||||
selected = (target_var.get() or "").strip()
|
||||
if (not selected or selected not in nodes) and nodes:
|
||||
for node in nodes:
|
||||
if not self._is_tainted_node(node):
|
||||
target_var.set(node)
|
||||
break
|
||||
|
||||
combo = ttk.Combobox(
|
||||
self.bg_canvas,
|
||||
textvariable=target_var,
|
||||
values=nodes,
|
||||
state="readonly",
|
||||
font=("SF Pro Text", 11),
|
||||
width=30,
|
||||
)
|
||||
window = self.bg_canvas.create_window(x_options, y - 2, window=combo, anchor="nw")
|
||||
self._canvas_items.append(window)
|
||||
self._overlay_widgets.append(combo)
|
||||
|
||||
return y + 36
|
||||
|
||||
def safe_after(self, func, delay=0):
|
||||
|
||||
@ -30,7 +30,7 @@ class BuildScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -39,7 +39,7 @@ class BuildScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -537,6 +537,7 @@ class ConfigMixin:
|
||||
# GitOps
|
||||
_set("gitops.namespace", _get_var(self.gitops_namespace, "gitea"))
|
||||
_set("gitops.node_selector", _get_var(self.gitops_node_selector, ""))
|
||||
_set("gitops.git_provider", _get_var(self.gitops_git_provider, "Gitea"))
|
||||
|
||||
# ArgoCD
|
||||
_set("argocd.namespace", _get_var(self.argocd_namespace, "argocd"))
|
||||
|
||||
@ -49,7 +49,7 @@ class ClusterScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -58,7 +58,7 @@ class ClusterScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -608,7 +608,7 @@ class ClusterScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -617,7 +617,7 @@ class ClusterScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -37,7 +37,7 @@ class DatabaseScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -46,7 +46,7 @@ class DatabaseScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -1008,7 +1008,7 @@ class DatabaseScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -1017,7 +1017,7 @@ class DatabaseScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -106,7 +106,7 @@ class EnvironmentScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -115,7 +115,7 @@ class EnvironmentScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Personal Infrastructure.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""GitOps (Gitea) configuration and deployment screen."""
|
||||
"""GitOps (Gitea / GitLab) configuration and deployment screen."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -6,6 +6,7 @@ import os
|
||||
import subprocess
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from knoe import prole_conf
|
||||
from knoe import screen as ui
|
||||
@ -26,7 +27,7 @@ class GitOpsScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -35,7 +36,7 @@ class GitOpsScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -52,7 +53,8 @@ class GitOpsScreenMixin:
|
||||
y = 310
|
||||
|
||||
for label, value in [
|
||||
("Gitea — self-hosted Git + CI (recommended)", "gitea"),
|
||||
("Gitea — self-hosted Git + CI (lightweight)", "gitea"),
|
||||
("GitLab — full DevOps platform via GitLab Operator", "gitlab"),
|
||||
("ArgoCD — continuous delivery / GitOps sync", "argocd"),
|
||||
("None — skip GitOps tooling", "none"),
|
||||
]:
|
||||
@ -82,7 +84,7 @@ class GitOpsScreenMixin:
|
||||
self.prole_cfg_data["Optional Features"]["GITOPS_PROVIDER"] = provider
|
||||
# Keep legacy flags in sync so downstream steps remain consistent.
|
||||
self.prole_cfg_data["Optional Features"]["GITOPS_ENABLED"] = str(
|
||||
provider == "gitea"
|
||||
provider in ("gitea", "gitlab")
|
||||
)
|
||||
self.prole_cfg_data["Optional Features"]["ARGOCD_ENABLED"] = str(
|
||||
provider == "argocd"
|
||||
@ -91,7 +93,7 @@ class GitOpsScreenMixin:
|
||||
except Exception:
|
||||
pass
|
||||
# Sync boolean vars used by the individual feature screens.
|
||||
self.gitops_enabled.set(provider == "gitea")
|
||||
self.gitops_enabled.set(provider in ("gitea", "gitlab"))
|
||||
self.argocd_enabled.set(provider == "argocd")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@ -105,7 +107,7 @@ class GitOpsScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -114,7 +116,7 @@ class GitOpsScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -122,15 +124,25 @@ class GitOpsScreenMixin:
|
||||
|
||||
self._render_title("GitOps", y=150)
|
||||
|
||||
description = (
|
||||
"Deploys self-hosted GitOps (Gitea) via etc/init_gitea.sh.\n"
|
||||
"Defaults to the 'gitea' namespace; update below to override."
|
||||
)
|
||||
provider = getattr(self, "gitops_provider", None)
|
||||
provider_name = (provider.get() if provider else None) or "gitea"
|
||||
if provider_name == "gitlab":
|
||||
deploy_description = (
|
||||
"Deploys self-hosted GitLab via the GitLab Operator (etc/init_gitlab.sh).\n"
|
||||
"Removes any pre-existing Gitea/git.prole.org config before installing.\n"
|
||||
"Uses knoe-db (CloudNativePG) as the PostgreSQL data store."
|
||||
)
|
||||
else:
|
||||
deploy_description = (
|
||||
"Deploys self-hosted GitOps via etc/init_gitea.sh.\n"
|
||||
"Select your GitOps provider and node placement below."
|
||||
)
|
||||
description = deploy_description
|
||||
self._render_paragraph(description, y=210)
|
||||
|
||||
enable_cb = tk.Checkbutton(
|
||||
self.bg_canvas,
|
||||
text="Enable GitOps (Gitea)",
|
||||
text="Enable GitOps",
|
||||
variable=self.gitops_enabled,
|
||||
command=self._on_gitops_toggle,
|
||||
bg="white",
|
||||
@ -145,48 +157,43 @@ class GitOpsScreenMixin:
|
||||
self._canvas_items.append(enable_window)
|
||||
self._overlay_widgets.append(enable_cb)
|
||||
|
||||
# Namespace input
|
||||
# GitOps provider dropdown
|
||||
ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
290,
|
||||
"Kubernetes Namespace",
|
||||
"GitOps Provider",
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12, "bold"),
|
||||
)
|
||||
ns_entry = tk.Entry(
|
||||
provider_combo = ttk.Combobox(
|
||||
self.bg_canvas,
|
||||
textvariable=self.gitops_namespace,
|
||||
bg="white",
|
||||
fg="black",
|
||||
insertbackground="black",
|
||||
highlightbackground="#CCCCCC",
|
||||
highlightthickness=1,
|
||||
relief="flat",
|
||||
textvariable=self.gitops_git_provider,
|
||||
values=["Gitea", "GitLab", "Forgejo"],
|
||||
state="readonly",
|
||||
font=("SF Pro Text", 11),
|
||||
width=20,
|
||||
)
|
||||
ns_window = self.bg_canvas.create_window(
|
||||
48, 315, window=ns_entry, anchor="nw", width=220, height=30
|
||||
provider_window = self.bg_canvas.create_window(
|
||||
48, 315, window=provider_combo, anchor="nw", width=220, height=30
|
||||
)
|
||||
self._canvas_items.append(ns_window)
|
||||
self._overlay_widgets.append(ns_entry)
|
||||
self._canvas_items.append(provider_window)
|
||||
self._overlay_widgets.append(provider_combo)
|
||||
|
||||
node_selector_var = getattr(self, "gitops_node_selector", None)
|
||||
if hasattr(self, "_render_k3s_node_selector") and node_selector_var is not None:
|
||||
y = self._render_k3s_node_selector(
|
||||
self._render_k3s_node_selector(
|
||||
y=360,
|
||||
target_var=node_selector_var,
|
||||
title="Node selector:",
|
||||
)
|
||||
else:
|
||||
y = 360
|
||||
|
||||
initial_status = "Ready" if self.gitops_enabled.get() else "Disabled"
|
||||
self._gitops_status_var = tk.StringVar(value=initial_status)
|
||||
status_item = ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
y,
|
||||
415,
|
||||
f"Status: {initial_status}",
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12),
|
||||
@ -204,13 +211,13 @@ class GitOpsScreenMixin:
|
||||
|
||||
# Console output
|
||||
self._gitops_console = self._create_console_output(
|
||||
y=400, title="Deployment Output", width=880, height=330
|
||||
y=455, title="Deployment Output", width=880, height=280
|
||||
)
|
||||
|
||||
# Deploy button
|
||||
self._gitops_deploy_button = tk.Button(
|
||||
self.bg_canvas,
|
||||
text="Deploy Gitea",
|
||||
text="Deploy GitOps",
|
||||
command=self.run_gitops_deploy,
|
||||
bg="#F5F5DC",
|
||||
fg="black",
|
||||
@ -249,20 +256,41 @@ class GitOpsScreenMixin:
|
||||
self.update_footer()
|
||||
|
||||
def worker():
|
||||
namespace = (
|
||||
(self.gitops_namespace.get() or "").strip()
|
||||
or os.environ.get("GITEA_NAMESPACE")
|
||||
or "gitea"
|
||||
)
|
||||
# gitops_git_provider is the combobox on the config page (Gitea/GitLab/Forgejo).
|
||||
# gitops_provider is the radio-button choice page (gitea/gitlab/argocd/none).
|
||||
# Prefer gitops_git_provider since that's what the user sees and sets.
|
||||
git_provider = (
|
||||
getattr(self, "gitops_git_provider", None) and
|
||||
self.gitops_git_provider.get()
|
||||
) or ""
|
||||
if not git_provider:
|
||||
git_provider = (
|
||||
getattr(self, "gitops_provider", None) and
|
||||
self.gitops_provider.get()
|
||||
) or "Gitea"
|
||||
is_gitlab = git_provider.lower() == "gitlab"
|
||||
|
||||
if is_gitlab:
|
||||
namespace = os.environ.get("GITLAB_NAMESPACE") or "gitlab"
|
||||
else:
|
||||
namespace = os.environ.get("GITEA_NAMESPACE") or "gitea"
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PROLE_HOME"] = str(PROJECT_ROOT)
|
||||
env["PROLE_SERVICE"] = str(PROJECT_ROOT)
|
||||
env["NAMESPACE"] = namespace
|
||||
env["GITEA_NAMESPACE"] = namespace
|
||||
if is_gitlab:
|
||||
env["GITLAB_NAMESPACE"] = namespace
|
||||
else:
|
||||
env["GITEA_NAMESPACE"] = namespace
|
||||
|
||||
self._gitops_console.write("Starting Gitea deployment...\n")
|
||||
if is_gitlab:
|
||||
self._gitops_console.write("Starting GitLab deployment via GitLab Operator...\n")
|
||||
script_path = PROJECT_ROOT / "etc" / "init_gitlab.sh"
|
||||
else:
|
||||
self._gitops_console.write("Starting Gitea deployment...\n")
|
||||
script_path = PROJECT_ROOT / "etc" / "init_gitea.sh"
|
||||
|
||||
script_path = PROJECT_ROOT / "etc" / "init_gitea.sh"
|
||||
if not script_path.exists():
|
||||
self._gitops_console.write(f"Error: {script_path} not found.\n")
|
||||
self.safe_after(
|
||||
@ -285,9 +313,14 @@ class GitOpsScreenMixin:
|
||||
mode = "k3d"
|
||||
args = ["--mode", mode, "--namespace", namespace]
|
||||
node_selector = (self.gitops_node_selector.get() or "").strip()
|
||||
if mode == "k3s" and node_selector:
|
||||
env["GITEA_NODE_SELECTOR"] = node_selector
|
||||
env["NODE_SELECTOR"] = node_selector
|
||||
if node_selector:
|
||||
if is_gitlab:
|
||||
env["GITLAB_NODE_SELECTOR"] = node_selector
|
||||
env["NODE_SELECTOR"] = node_selector
|
||||
args.extend(["--node-selector", node_selector])
|
||||
elif mode == "k3s":
|
||||
env["GITEA_NODE_SELECTOR"] = node_selector
|
||||
env["NODE_SELECTOR"] = node_selector
|
||||
self._gitops_console.write(f"Node selector: {node_selector}\n")
|
||||
|
||||
cfg_path = None
|
||||
@ -349,16 +382,18 @@ class GitOpsScreenMixin:
|
||||
self.safe_after(
|
||||
lambda: self._gitops_status_var.set("Deployed Successfully")
|
||||
)
|
||||
label = "GitLab" if is_gitlab else "GitOps"
|
||||
self._gitops_console.write(
|
||||
"\nGitOps deployment completed successfully.\n"
|
||||
f"\n{label} deployment completed successfully.\n"
|
||||
)
|
||||
else:
|
||||
self._gitops_success = False
|
||||
self.safe_after(
|
||||
lambda: self._gitops_status_var.set(f"Failed (Code {rc})")
|
||||
)
|
||||
label = "GitLab" if is_gitlab else "GitOps"
|
||||
self._gitops_console.write(
|
||||
f"\nGitOps deployment failed with exit code {rc}.\n"
|
||||
f"\n{label} deployment failed with exit code {rc}.\n"
|
||||
)
|
||||
|
||||
self._gitops_deploying = False
|
||||
|
||||
@ -18,13 +18,13 @@ class NavigationMixin:
|
||||
|
||||
Notes:
|
||||
- We set Tk's appname for consistency.
|
||||
- On macOS, attempt to set the process/menu name to 'Prole Database Installer'
|
||||
- On macOS, attempt to set the process/menu name to 'Knoe System Installer'
|
||||
via NSProcessInfo if PyObjC is available.
|
||||
- Prefer the Prole Tools.app .icns from the built app; fallback to local PNG/GIF.
|
||||
"""
|
||||
# Set Tk application name
|
||||
try:
|
||||
self.root.tk.call("tk", "appname", "Prole Database Installer")
|
||||
self.root.tk.call("tk", "appname", "Knoe System Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -34,11 +34,11 @@ class NavigationMixin:
|
||||
try:
|
||||
from Foundation import NSProcessInfo
|
||||
|
||||
NSProcessInfo.processInfo().setProcessName_("Prole Database Installer")
|
||||
NSProcessInfo.processInfo().setProcessName_("Knoe System Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Also try to retitle the first main menu item so the menu next to the Apple logo reads 'Prole Database Installer'
|
||||
# Also try to retitle the first main menu item so the menu next to the Apple logo reads 'Knoe System Installer'
|
||||
try:
|
||||
from AppKit import NSApplication
|
||||
|
||||
@ -47,7 +47,7 @@ class NavigationMixin:
|
||||
if main_menu is not None and main_menu.numberOfItems() > 0:
|
||||
first_item = main_menu.itemAtIndex_(0)
|
||||
if first_item is not None:
|
||||
first_item.setTitle_("Prole Database Installer")
|
||||
first_item.setTitle_("Knoe System Installer")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -349,11 +349,8 @@ class NavigationMixin:
|
||||
if current_id == "kerberos_config":
|
||||
self.show_page("init_scripts")
|
||||
return
|
||||
if current_id == "gitops_choice":
|
||||
self.show_page("kerberos_config")
|
||||
return
|
||||
if current_id == "argocd_config":
|
||||
self.show_page("gitops_choice")
|
||||
self.show_page("kerberos_config")
|
||||
return
|
||||
if current_id == "gitops_config":
|
||||
self.show_page("argocd_config")
|
||||
@ -619,7 +616,7 @@ class NavigationMixin:
|
||||
if self.kerberos_enabled.get():
|
||||
self.show_page("kerberos_config")
|
||||
else:
|
||||
self.show_page("gitops_choice")
|
||||
self.show_page("argocd_config")
|
||||
return
|
||||
|
||||
if current_id == "kerberos_config":
|
||||
@ -661,15 +658,6 @@ class NavigationMixin:
|
||||
os.environ.get("KRB5_AD_SERVICE_NAME", "prole-kerberos-ad-dc")
|
||||
)
|
||||
self._save_prole_cfg()
|
||||
self.show_page("gitops_choice")
|
||||
return
|
||||
if current_id == "gitops_choice":
|
||||
if "Optional Features" not in self.prole_cfg_data:
|
||||
self.prole_cfg_data["Optional Features"] = {}
|
||||
self.prole_cfg_data["Optional Features"]["GITOPS_PROVIDER"] = (
|
||||
self.gitops_provider.get()
|
||||
)
|
||||
self._save_prole_cfg()
|
||||
self.show_page("argocd_config")
|
||||
return
|
||||
if current_id == "argocd_config":
|
||||
@ -708,11 +696,29 @@ class NavigationMixin:
|
||||
else "Attempted"
|
||||
)
|
||||
)
|
||||
# Derive the correct namespace from the chosen provider.
|
||||
# gitops_git_provider (combobox: Gitea/GitLab/Forgejo) is the active selector.
|
||||
git_provider = (
|
||||
getattr(self, "gitops_git_provider", None) and
|
||||
self.gitops_git_provider.get()
|
||||
) or ""
|
||||
if not git_provider:
|
||||
git_provider = (
|
||||
getattr(self, "gitops_provider", None) and
|
||||
self.gitops_provider.get()
|
||||
) or "Gitea"
|
||||
is_gitlab = git_provider.lower() == "gitlab"
|
||||
if is_gitlab:
|
||||
gitops_ns = (self.gitops_namespace.get() or "gitlab").strip() or "gitlab"
|
||||
else:
|
||||
gitops_ns = (self.gitops_namespace.get() or "gitea").strip() or "gitea"
|
||||
self.prole_cfg_data["GitOps"] = {
|
||||
"STATUS": gitops_status,
|
||||
"GITOPS_NAMESPACE": (self.gitops_namespace.get() or "gitea").strip()
|
||||
or "gitea",
|
||||
"GITOPS_PROVIDER": git_provider.lower(),
|
||||
"GITOPS_NAMESPACE": gitops_ns,
|
||||
}
|
||||
if is_gitlab:
|
||||
self.prole_cfg_data["GitOps"]["GITLAB_NAMESPACE"] = gitops_ns
|
||||
self._save_prole_cfg()
|
||||
self.show_page("supabase_config")
|
||||
return
|
||||
@ -822,8 +828,6 @@ class NavigationMixin:
|
||||
self.next_button.configure(state="disabled")
|
||||
else:
|
||||
self.next_button.configure(state="normal")
|
||||
elif pid == "gitops_choice":
|
||||
self.next_button.configure(state="normal")
|
||||
elif pid == "common_services":
|
||||
if getattr(self, "_common_services_success", False):
|
||||
self.next_button.configure(state="normal")
|
||||
|
||||
@ -47,7 +47,7 @@ class NetworkScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -56,7 +56,7 @@ class NetworkScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -29,7 +29,7 @@ class OllamaScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -38,7 +38,7 @@ class OllamaScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -30,7 +30,7 @@ class PackagingScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -307,7 +307,7 @@ class PackagingScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -316,7 +316,7 @@ class PackagingScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -40,7 +40,7 @@ class SecurityScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -49,7 +49,7 @@ class SecurityScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -40,40 +40,171 @@ from knoe.core.policy import (
|
||||
class ServicesScreenMixin:
|
||||
"""Initialization scripts, CNPG deployment and service-layer overlays."""
|
||||
|
||||
# Traffic light color constants
|
||||
_TL_YELLOW = "#ff9f0a"
|
||||
_TL_GREEN = "#34c759"
|
||||
_TL_RED = "#ff3b30"
|
||||
|
||||
def _set_init_light(self, key: str, state: str) -> None:
|
||||
"""Update a traffic light on the Initialization Scripts page.
|
||||
|
||||
state: 'yellow' | 'green' | 'red'
|
||||
"""
|
||||
color = {
|
||||
"yellow": self._TL_YELLOW,
|
||||
"green": self._TL_GREEN,
|
||||
"red": self._TL_RED,
|
||||
}.get(state, self._TL_YELLOW)
|
||||
light = getattr(self, "_init_traffic_lights", {}).get(key)
|
||||
if light is None:
|
||||
return
|
||||
try:
|
||||
self.bg_canvas.itemconfig(light, fill=color)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _render_init_scripts_page(self):
|
||||
# Letterhead at top right
|
||||
content_width = self.bg_canvas.winfo_width() or 975
|
||||
right_margin = content_width - 48
|
||||
|
||||
ui.canvas_text(
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
self, right_margin, 40, "knoe.dev",
|
||||
fill="#6e6e73", font=("SF Pro Text", 32, "bold"), anchor="ne",
|
||||
)
|
||||
ui.canvas_text(
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
self, right_margin, 85, "infrastructure.auto()",
|
||||
fill="#6e6e73", font=("SF Pro Text", 18), anchor="ne",
|
||||
)
|
||||
|
||||
self._render_title("Initialization Scripts", y=150)
|
||||
self._render_paragraph(
|
||||
"Running initialization scripts to set up Garage, CloudNative-PG, and backups.",
|
||||
y=200,
|
||||
|
||||
x_label = 48
|
||||
gy = 198 # top of traffic-light section
|
||||
|
||||
# --- Traffic light section ---
|
||||
# Column x positions
|
||||
col_common = x_label
|
||||
col_db = 330
|
||||
col_plat = 645
|
||||
|
||||
# Group headers
|
||||
for col_x, header in (
|
||||
(col_common, "Common Services"),
|
||||
(col_db, "Database Services"),
|
||||
(col_plat, "Platform Services"),
|
||||
):
|
||||
ui.canvas_text(
|
||||
self, col_x, gy, header,
|
||||
fill="#1d1d1f", font=("SF Pro Text", 11, "bold"), anchor="nw",
|
||||
)
|
||||
|
||||
# Common Services — one indicator
|
||||
lights: dict[str, int] = {}
|
||||
ly = gy + 24
|
||||
indicator = self.bg_canvas.create_oval(
|
||||
col_common, ly, col_common + 14, ly + 14,
|
||||
fill=self._TL_YELLOW, outline="#8e8e93", width=1,
|
||||
)
|
||||
self._canvas_items.append(indicator)
|
||||
ui.canvas_text(
|
||||
self, col_common + 20, ly + 7, "Services Ready",
|
||||
fill="black", font=("SF Pro Text", 10), anchor="w",
|
||||
)
|
||||
lights["common_services"] = indicator
|
||||
|
||||
# Database Services — knoe-db-1/2/3 + barman-cloud
|
||||
db_items = [
|
||||
("knoe_db_1", "knoe-db-1"),
|
||||
("knoe_db_2", "knoe-db-2"),
|
||||
("knoe_db_3", "knoe-db-3"),
|
||||
("barman_cloud", "barman-cloud (backup)"),
|
||||
]
|
||||
for i, (key, label) in enumerate(db_items):
|
||||
ly = gy + 24 + i * 24
|
||||
ind = self.bg_canvas.create_oval(
|
||||
col_db, ly, col_db + 14, ly + 14,
|
||||
fill=self._TL_YELLOW, outline="#8e8e93", width=1,
|
||||
)
|
||||
self._canvas_items.append(ind)
|
||||
ui.canvas_text(
|
||||
self, col_db + 20, ly + 7, label,
|
||||
fill="black", font=("SF Pro Text", 10), anchor="w",
|
||||
)
|
||||
lights[key] = ind
|
||||
|
||||
# Platform Services — monitoring + kong/ingress
|
||||
plat_items = [
|
||||
("monitoring", "Monitoring"),
|
||||
("kong", "Kong / Ingress"),
|
||||
]
|
||||
for i, (key, label) in enumerate(plat_items):
|
||||
ly = gy + 24 + i * 24
|
||||
ind = self.bg_canvas.create_oval(
|
||||
col_plat, ly, col_plat + 14, ly + 14,
|
||||
fill=self._TL_YELLOW, outline="#8e8e93", width=1,
|
||||
)
|
||||
self._canvas_items.append(ind)
|
||||
ui.canvas_text(
|
||||
self, col_plat + 20, ly + 7, label,
|
||||
fill="black", font=("SF Pro Text", 10), anchor="w",
|
||||
)
|
||||
lights[key] = ind
|
||||
|
||||
self._init_traffic_lights = lights
|
||||
|
||||
# Thin separator below lights
|
||||
sep_y = gy + 125
|
||||
self._canvas_items.append(
|
||||
self.bg_canvas.create_line(
|
||||
x_label, sep_y, right_margin, sep_y,
|
||||
fill="#e0e0e0", width=1,
|
||||
)
|
||||
)
|
||||
|
||||
y = 260
|
||||
y = self._render_init_scripts_section(48, y, 900, 450, title="Execution Output")
|
||||
# --- Tabbed output section ---
|
||||
nb_y = sep_y + 10
|
||||
nb_height = 415
|
||||
|
||||
# Use tk.Button
|
||||
notebook_bg = tk.Frame(self.bg_canvas, bg="white", highlightthickness=0, bd=0)
|
||||
self.script_tabs = ttk.Notebook(notebook_bg, style="TNotebook")
|
||||
self.script_tabs.pack(fill="both", expand=True, padx=1, pady=1)
|
||||
|
||||
tab_win = self.bg_canvas.create_window(
|
||||
x_label, nb_y, window=notebook_bg, anchor="nw",
|
||||
width=right_margin - x_label, height=nb_height,
|
||||
)
|
||||
self._canvas_items.append(tab_win)
|
||||
self._overlay_widgets.extend([notebook_bg, self.script_tabs])
|
||||
|
||||
self.script_consoles = {}
|
||||
self._script_tab_index = {}
|
||||
|
||||
tab_defs = [
|
||||
("Common Services", "tab_common"),
|
||||
("Database Services", "tab_database"),
|
||||
("Platform Services", "tab_platform"),
|
||||
("Kubectl Status", self.KUBECTL_STATUS_TAB),
|
||||
]
|
||||
for idx, (tab_title, key) in enumerate(tab_defs):
|
||||
console_bg = tk.Frame(self.script_tabs, bg="white", highlightthickness=0, bd=0)
|
||||
self.script_tabs.add(console_bg, text=tab_title)
|
||||
if key == self.KUBECTL_STATUS_TAB:
|
||||
mono = ("Menlo", 9) if platform.system() == "Darwin" else ("Consolas", 9)
|
||||
console = ui.TerminalConsole(
|
||||
console_bg, highlightthickness=0, bd=0,
|
||||
font=mono, wrap="none", show_horizontal=True,
|
||||
)
|
||||
else:
|
||||
console = ui.TerminalConsole(console_bg, highlightthickness=0, bd=0)
|
||||
console.pack(fill="both", expand=True, padx=1, pady=1)
|
||||
self.script_consoles[key] = console
|
||||
self._script_tab_index[key] = idx
|
||||
self._overlay_widgets.extend([console_bg, console])
|
||||
|
||||
self.script_tabs.bind("<<NotebookTabChanged>>", self._on_init_scripts_tab_changed)
|
||||
self.safe_after(self._refresh_kubectl_status_tab, delay=50)
|
||||
|
||||
# Run button + status label
|
||||
btn_y = nb_y + nb_height + 12
|
||||
self._init_scripts_button = tk.Button(
|
||||
self.bg_canvas,
|
||||
text="Run Scripts",
|
||||
@ -89,105 +220,42 @@ class ServicesScreenMixin:
|
||||
pady=8,
|
||||
)
|
||||
btn_window = self.bg_canvas.create_window(
|
||||
48, y + 20, window=self._init_scripts_button, anchor="nw", width=180
|
||||
x_label, btn_y, window=self._init_scripts_button, anchor="nw", width=180
|
||||
)
|
||||
self._canvas_items.append(btn_window)
|
||||
self._overlay_widgets.append(self._init_scripts_button)
|
||||
|
||||
# Status Label
|
||||
self._init_scripts_status_label = ui.canvas_text(
|
||||
self, 240, y + 32, "", fill="black", font=("SF Pro Text", 12)
|
||||
self, x_label + 196, btn_y + 12, "", fill="black", font=("SF Pro Text", 12)
|
||||
)
|
||||
self._canvas_items.append(self._init_scripts_status_label)
|
||||
|
||||
def _render_init_scripts_section(
|
||||
self,
|
||||
x: int,
|
||||
y: int,
|
||||
width: int,
|
||||
height: int,
|
||||
title: str | None = "Execution Output",
|
||||
) -> int:
|
||||
if title:
|
||||
title_item = ui.canvas_text(
|
||||
self, x, y, title, fill="#1d1d1f", font=("SF Pro Text", 12, "bold")
|
||||
)
|
||||
self._canvas_items.append(title_item)
|
||||
y += 30
|
||||
|
||||
# Use a background frame for the notebook to hide potential system borders
|
||||
notebook_bg = tk.Frame(self.bg_canvas, bg="white", highlightthickness=0, bd=0)
|
||||
self.script_tabs = ttk.Notebook(notebook_bg, style="TNotebook")
|
||||
self.script_tabs.pack(fill="both", expand=True, padx=1, pady=1)
|
||||
|
||||
tab_window = self.bg_canvas.create_window(
|
||||
x, y, window=notebook_bg, anchor="nw", width=width, height=height
|
||||
# On page load: if previously completed check live state; else stay yellow
|
||||
already_done = getattr(self, "_scripts_success", False) or (
|
||||
(self.prole_cfg_data.get("Initialization Scripts") or {}).get("STATUS") == "Completed"
|
||||
)
|
||||
self._canvas_items.append(tab_window)
|
||||
self._overlay_widgets.append(notebook_bg)
|
||||
self._overlay_widgets.append(self.script_tabs)
|
||||
|
||||
self.script_consoles = {}
|
||||
scripts = self._init_scripts_list()
|
||||
self._script_tab_index = {}
|
||||
|
||||
for idx, (title, fname) in enumerate(scripts):
|
||||
# Use a background frame to ensure NO borders are visible around the console
|
||||
console_bg = tk.Frame(
|
||||
self.script_tabs, bg="white", highlightthickness=0, bd=0
|
||||
)
|
||||
self.script_tabs.add(console_bg, text=title)
|
||||
# Use TerminalConsole for consistent styling
|
||||
if fname == self.KUBECTL_STATUS_TAB:
|
||||
mono = (
|
||||
("Menlo", 9) if platform.system() == "Darwin" else ("Consolas", 9)
|
||||
)
|
||||
console = ui.TerminalConsole(
|
||||
console_bg,
|
||||
highlightthickness=0,
|
||||
bd=0,
|
||||
font=mono,
|
||||
wrap="none",
|
||||
show_horizontal=True,
|
||||
)
|
||||
else:
|
||||
console = ui.TerminalConsole(console_bg, highlightthickness=0, bd=0)
|
||||
console.pack(fill="both", expand=True, padx=1, pady=1)
|
||||
self.script_consoles[fname] = console
|
||||
self._script_tab_index[fname] = idx
|
||||
self._overlay_widgets.append(console_bg)
|
||||
self._overlay_widgets.append(console)
|
||||
|
||||
self.script_tabs.bind(
|
||||
"<<NotebookTabChanged>>", self._on_init_scripts_tab_changed
|
||||
)
|
||||
self.safe_after(self._refresh_kubectl_status_tab, delay=50)
|
||||
|
||||
return y + height
|
||||
|
||||
def _init_scripts_list(self):
|
||||
scripts = [
|
||||
("Kubectl Status", self.KUBECTL_STATUS_TAB),
|
||||
("Common Services", "init_common_services.sh"),
|
||||
("Common Services Log", "common_services_log"),
|
||||
("CloudNative-PG", "init_cloudnative_pg.sh"),
|
||||
]
|
||||
scripts.append(("Prole DB Backup", "init_cnpg_backup.sh"))
|
||||
scripts.append(("Kong API Gateway", "init_kong.sh"))
|
||||
scripts.append(("Monitoring", "init_monitoring.sh"))
|
||||
mode = self._deployment_mode()
|
||||
if mode != "k3d":
|
||||
scripts.append(("Nginx Ingress", "init_nginx_ingress.sh"))
|
||||
if mode != "k3s":
|
||||
scripts.append(("Port Forwards", "init_port_forwards.sh"))
|
||||
return scripts
|
||||
if already_done:
|
||||
self.safe_after(self._check_init_scripts_status_async)
|
||||
|
||||
def run_init_scripts(self):
|
||||
self._action_flags["init_scripts.run_scripts"] = True
|
||||
|
||||
def worker():
|
||||
# Map script filenames → category tab key
|
||||
_SCRIPT_TO_TAB = {
|
||||
"init_common_services.sh": "tab_common",
|
||||
"common_services_log": "tab_common",
|
||||
"init_cloudnative_pg.sh": "tab_database",
|
||||
"init_cnpg_backup.sh": "tab_database",
|
||||
"init_kong.sh": "tab_platform",
|
||||
"init_monitoring.sh": "tab_platform",
|
||||
"init_nginx_ingress.sh": "tab_platform",
|
||||
"init_port_forwards.sh": "tab_platform",
|
||||
}
|
||||
|
||||
def _select_tab(script_name):
|
||||
idx = getattr(self, "_script_tab_index", {}).get(script_name)
|
||||
cat = _SCRIPT_TO_TAB.get(script_name, script_name)
|
||||
idx = getattr(self, "_script_tab_index", {}).get(cat)
|
||||
if idx is None:
|
||||
return
|
||||
self.safe_after(
|
||||
@ -198,6 +266,16 @@ class ServicesScreenMixin:
|
||||
)
|
||||
)
|
||||
|
||||
def _con(script_name):
|
||||
"""Return the console for a script (mapped to category tab)."""
|
||||
cat = _SCRIPT_TO_TAB.get(script_name, script_name)
|
||||
return self.script_consoles.get(cat) or self.script_consoles.get(script_name)
|
||||
|
||||
# Reset all traffic lights to yellow at start of run
|
||||
for _lk in ("common_services", "knoe_db_1", "knoe_db_2", "knoe_db_3",
|
||||
"barman_cloud", "monitoring", "kong"):
|
||||
self.safe_after(lambda k=_lk: self._set_init_light(k, "yellow"))
|
||||
|
||||
self.safe_after(
|
||||
lambda: (
|
||||
self._init_scripts_button.configure(state="disabled")
|
||||
@ -304,18 +382,14 @@ class ServicesScreenMixin:
|
||||
if overall_success:
|
||||
script = "init_common_services.sh"
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} start...\n")
|
||||
_con(script).clear()
|
||||
_con(script).write(f"Running {script} start...\n")
|
||||
|
||||
svc_log_path = _log_path_for(script)
|
||||
self._last_common_services_log_path = svc_log_path
|
||||
env["COMMON_SERVICES_INIT_LOG"] = str(svc_log_path)
|
||||
self._record_install_log(svc_log_path)
|
||||
self.script_consoles["common_services_log"].clear()
|
||||
self.script_consoles["common_services_log"].write(
|
||||
f"Log file: {svc_log_path}\n\n"
|
||||
)
|
||||
self.script_consoles[script].write(f"Log file: {svc_log_path}\n\n")
|
||||
_con(script).write(f"Log file: {svc_log_path}\n\n")
|
||||
|
||||
try:
|
||||
svc_fp = svc_log_path.open("a", encoding="utf-8")
|
||||
@ -323,8 +397,7 @@ class ServicesScreenMixin:
|
||||
svc_fp = None
|
||||
|
||||
def _svc_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
self.script_consoles["common_services_log"].write(line)
|
||||
_con("init_common_services.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
if svc_fp:
|
||||
try:
|
||||
@ -388,27 +461,27 @@ class ServicesScreenMixin:
|
||||
pass
|
||||
|
||||
if rc_svc != 0:
|
||||
self.script_consoles[script].write(
|
||||
_con(script).write(
|
||||
f"\nERROR: {script} start failed with code {rc_svc}\n"
|
||||
)
|
||||
overall_success = False
|
||||
self.safe_after(lambda: self._set_init_light("common_services", "red"))
|
||||
else:
|
||||
self.script_consoles[script].write(
|
||||
f"\n{script} completed successfully.\n"
|
||||
)
|
||||
_con(script).write(f"\n{script} completed successfully.\n")
|
||||
self.safe_after(lambda: self._set_init_light("common_services", "green"))
|
||||
else:
|
||||
self.script_consoles["init_common_services.sh"].write(
|
||||
_con("init_common_services.sh").write(
|
||||
"Skipping common services initialization because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 2. CloudNative-PG initialization (Python)
|
||||
cnpg_tab = "init_cloudnative_pg.sh"
|
||||
cnpg_script = "init_cloudnative_pg.sh"
|
||||
if overall_success:
|
||||
_select_tab(cnpg_tab)
|
||||
self.script_consoles[cnpg_tab].clear()
|
||||
self.script_consoles[cnpg_tab].write("Running CloudNative-PG initialization...\n")
|
||||
_select_tab(cnpg_script)
|
||||
_con(cnpg_script).clear()
|
||||
_con(cnpg_script).write("Running CloudNative-PG initialization...\n")
|
||||
|
||||
log_path = _log_path_for(cnpg_tab)
|
||||
log_path = _log_path_for(cnpg_script)
|
||||
self._record_install_log(log_path)
|
||||
try:
|
||||
log_fp = log_path.open("a", encoding="utf-8")
|
||||
@ -417,7 +490,7 @@ class ServicesScreenMixin:
|
||||
|
||||
def _cnpg_log(msg):
|
||||
line = msg if msg.endswith("\n") else msg + "\n"
|
||||
self.script_consoles[cnpg_tab].write(line)
|
||||
_con("init_cloudnative_pg.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
@ -438,14 +511,18 @@ class ServicesScreenMixin:
|
||||
log=_cnpg_log,
|
||||
mode=mode,
|
||||
)
|
||||
self.script_consoles[cnpg_tab].write(
|
||||
_con(cnpg_script).write(
|
||||
"\nCloudNative-PG initialization completed successfully.\n"
|
||||
)
|
||||
for _lk in ("knoe_db_1", "knoe_db_2", "knoe_db_3"):
|
||||
self.safe_after(lambda k=_lk: self._set_init_light(k, "green"))
|
||||
except Exception as _cnpg_exc:
|
||||
self.script_consoles[cnpg_tab].write(
|
||||
_con(cnpg_script).write(
|
||||
f"\nERROR: CloudNative-PG initialization failed: {_cnpg_exc}\n"
|
||||
)
|
||||
overall_success = False
|
||||
for _lk in ("knoe_db_1", "knoe_db_2", "knoe_db_3"):
|
||||
self.safe_after(lambda k=_lk: self._set_init_light(k, "red"))
|
||||
finally:
|
||||
if log_fp:
|
||||
try:
|
||||
@ -453,17 +530,16 @@ class ServicesScreenMixin:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if cnpg_tab in self.script_consoles:
|
||||
self.script_consoles[cnpg_tab].write(
|
||||
"Skipping CloudNative-PG initialization because previous steps failed.\n"
|
||||
)
|
||||
_con("init_cloudnative_pg.sh").write(
|
||||
"Skipping CloudNative-PG initialization because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 4. init_cnpg_backup.sh start
|
||||
if overall_success:
|
||||
script = "init_cnpg_backup.sh"
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} start...\n")
|
||||
_con(script).clear()
|
||||
_con(script).write(f"Running {script} start...\n")
|
||||
|
||||
log_path = _log_path_for(script)
|
||||
self._record_install_log(log_path)
|
||||
@ -473,7 +549,7 @@ class ServicesScreenMixin:
|
||||
log_fp = None
|
||||
|
||||
def _backup_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
_con("init_cnpg_backup.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
@ -492,75 +568,65 @@ class ServicesScreenMixin:
|
||||
pass
|
||||
|
||||
if rc_backup != 0:
|
||||
self.script_consoles[script].write(
|
||||
f"\nERROR: {script} start failed with code {rc_backup}\n"
|
||||
)
|
||||
_con(script).write(f"\nERROR: {script} start failed with code {rc_backup}\n")
|
||||
overall_success = False
|
||||
self.safe_after(lambda: self._set_init_light("barman_cloud", "red"))
|
||||
else:
|
||||
self.script_consoles[script].write(
|
||||
f"\n{script} completed successfully.\n"
|
||||
)
|
||||
_con(script).write(f"\n{script} completed successfully.\n")
|
||||
self.safe_after(lambda: self._set_init_light("barman_cloud", "green"))
|
||||
else:
|
||||
self.script_consoles["init_cnpg_backup.sh"].write(
|
||||
_con("init_cnpg_backup.sh").write(
|
||||
"Skipping Prole DB backup because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 5. init_kong.sh start
|
||||
if overall_success:
|
||||
script = "init_kong.sh"
|
||||
if script in self.script_consoles:
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} start...\n")
|
||||
_select_tab(script)
|
||||
_con(script).clear()
|
||||
_con(script).write(f"Running {script} start...\n")
|
||||
|
||||
log_path = _log_path_for(script)
|
||||
self._record_install_log(log_path)
|
||||
try:
|
||||
log_fp = log_path.open("a", encoding="utf-8")
|
||||
except Exception:
|
||||
log_fp = None
|
||||
log_path = _log_path_for(script)
|
||||
self._record_install_log(log_path)
|
||||
try:
|
||||
log_fp = log_path.open("a", encoding="utf-8")
|
||||
except Exception:
|
||||
log_fp = None
|
||||
|
||||
def _kong_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
log_fp.write(line)
|
||||
log_fp.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
kong_ns = env.get("NAMESPACE") or "knoe-db"
|
||||
rc_kong = self.controller.run_script(
|
||||
script, args=mode_args + ["-n", kong_ns, "start"], env=env, on_line=_kong_line
|
||||
)
|
||||
def _kong_line(line):
|
||||
_con("init_kong.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
log_fp.close()
|
||||
log_fp.write(line)
|
||||
log_fp.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if rc_kong != 0:
|
||||
self.script_consoles[script].write(
|
||||
f"\nERROR: {script} start failed with code {rc_kong}\n"
|
||||
)
|
||||
overall_success = False
|
||||
else:
|
||||
self.script_consoles[script].write(
|
||||
f"\n{script} completed successfully.\n"
|
||||
)
|
||||
kong_ns = env.get("NAMESPACE") or "knoe-db"
|
||||
rc_kong = self.controller.run_script(
|
||||
script, args=mode_args + ["-n", kong_ns, "start"], env=env, on_line=_kong_line
|
||||
)
|
||||
if log_fp:
|
||||
try:
|
||||
log_fp.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if rc_kong != 0:
|
||||
_con(script).write(f"\nERROR: {script} start failed with code {rc_kong}\n")
|
||||
overall_success = False
|
||||
else:
|
||||
if "init_kong.sh" in self.script_consoles:
|
||||
self.script_consoles["init_kong.sh"].write(
|
||||
"Skipping Kong because previous steps failed.\n"
|
||||
)
|
||||
_con("init_kong.sh").write(
|
||||
"Skipping Kong because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 6. init_monitoring.sh initialize
|
||||
if overall_success and opt_allowed:
|
||||
script = "init_monitoring.sh"
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} initialize...\n")
|
||||
_con(script).clear()
|
||||
_con(script).write(f"Running {script} initialize...\n")
|
||||
|
||||
log_path = _log_path_for(script)
|
||||
self._record_install_log(log_path)
|
||||
@ -570,7 +636,7 @@ class ServicesScreenMixin:
|
||||
log_fp = None
|
||||
|
||||
def _monitoring_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
_con("init_monitoring.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
@ -612,52 +678,46 @@ class ServicesScreenMixin:
|
||||
pass
|
||||
|
||||
if rc_mon != 0:
|
||||
self.script_consoles[script].write(
|
||||
_con(script).write(
|
||||
f"\nERROR: {script} initialize failed with code {rc_mon}\n"
|
||||
)
|
||||
overall_success = False
|
||||
self.safe_after(lambda: self._set_init_light("monitoring", "red"))
|
||||
else:
|
||||
mon = self.prole_cfg_data.get("Monitoring", {}) or {}
|
||||
if not str(mon.get("GRAFANA_ADMIN_PASSWORD") or "").strip():
|
||||
mon["GRAFANA_ADMIN_PASSWORD"] = env.get(
|
||||
"GRAFANA_ADMIN_PASSWORD", ""
|
||||
)
|
||||
mon["GRAFANA_ADMIN_PASSWORD"] = env.get("GRAFANA_ADMIN_PASSWORD", "")
|
||||
self.prole_cfg_data["Monitoring"] = mon
|
||||
try:
|
||||
self._save_prole_cfg()
|
||||
except Exception:
|
||||
pass
|
||||
self.script_consoles[script].write(
|
||||
f"\n{script} completed successfully.\n"
|
||||
)
|
||||
_con(script).write(f"\n{script} completed successfully.\n")
|
||||
self.safe_after(lambda: self._set_init_light("monitoring", "green"))
|
||||
elif overall_success and not opt_allowed:
|
||||
script = "init_monitoring.sh"
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
msg = f"[SKIP] Monitoring disabled by policy: {opt_reason}\n"
|
||||
self.script_consoles[script].write(msg)
|
||||
|
||||
mon = (getattr(self, "prole_cfg_data", {}) or {}).get("Monitoring")
|
||||
if mon is None:
|
||||
mon = {}
|
||||
_con(script).clear()
|
||||
_con(script).write(f"[SKIP] Monitoring disabled by policy: {opt_reason}\n")
|
||||
mon = (getattr(self, "prole_cfg_data", {}) or {}).get("Monitoring") or {}
|
||||
mon["STATUS"] = "Skipped"
|
||||
self.prole_cfg_data["Monitoring"] = mon
|
||||
try:
|
||||
self._save_prole_cfg()
|
||||
except Exception:
|
||||
pass
|
||||
self.safe_after(lambda: self._set_init_light("monitoring", "green"))
|
||||
else:
|
||||
if "init_monitoring.sh" in self.script_consoles:
|
||||
self.script_consoles["init_monitoring.sh"].write(
|
||||
"Skipping Monitoring initialization because previous steps failed.\n"
|
||||
)
|
||||
_con("init_monitoring.sh").write(
|
||||
"Skipping Monitoring initialization because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 7. init_nginx_ingress.sh initialize (k3s only — not used in k3d)
|
||||
if overall_success and mode != "k3d":
|
||||
script = "init_nginx_ingress.sh"
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} initialize...\n")
|
||||
_con(script).clear()
|
||||
_con(script).write(f"Running {script} initialize...\n")
|
||||
log_path = _log_path_for(script)
|
||||
self._record_install_log(log_path)
|
||||
try:
|
||||
@ -666,7 +726,7 @@ class ServicesScreenMixin:
|
||||
log_fp = None
|
||||
|
||||
def _ingress_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
_con("init_nginx_ingress.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
@ -676,10 +736,7 @@ class ServicesScreenMixin:
|
||||
pass
|
||||
|
||||
rc_ing = self.controller.run_script(
|
||||
script,
|
||||
args=mode_args + ["initialize"],
|
||||
env=env,
|
||||
on_line=_ingress_line,
|
||||
script, args=mode_args + ["initialize"], env=env, on_line=_ingress_line,
|
||||
)
|
||||
if log_fp:
|
||||
try:
|
||||
@ -687,80 +744,70 @@ class ServicesScreenMixin:
|
||||
except Exception:
|
||||
pass
|
||||
if rc_ing != 0:
|
||||
self.script_consoles[script].write(
|
||||
f"\nERROR: {script} initialize failed with code {rc_ing}\n"
|
||||
)
|
||||
_con(script).write(f"\nERROR: {script} initialize failed with code {rc_ing}\n")
|
||||
overall_success = False
|
||||
self.safe_after(lambda: self._set_init_light("kong", "red"))
|
||||
else:
|
||||
self.script_consoles[script].write(
|
||||
f"\n{script} completed successfully.\n"
|
||||
)
|
||||
_con(script).write(f"\n{script} completed successfully.\n")
|
||||
self.safe_after(lambda: self._set_init_light("kong", "green"))
|
||||
elif mode != "k3d":
|
||||
self.script_consoles["init_nginx_ingress.sh"].write(
|
||||
_con("init_nginx_ingress.sh").write(
|
||||
"Skipping Nginx Ingress because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 8. init_port_forwards.sh start (k3d only — not used in k3s)
|
||||
if overall_success and mode != "k3s":
|
||||
script = "init_port_forwards.sh"
|
||||
if script in self.script_consoles:
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} start...\n")
|
||||
_select_tab(script)
|
||||
_con(script).clear()
|
||||
_con(script).write(f"Running {script} start...\n")
|
||||
|
||||
def _pf_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
self._process_script_output_line(line)
|
||||
def _pf_line(line):
|
||||
_con("init_port_forwards.sh").write(line)
|
||||
self._process_script_output_line(line)
|
||||
|
||||
env_pf = dict(env)
|
||||
env_pf.setdefault("PORT_FORWARD_SKIP_VALIDATE", "1")
|
||||
env_pf.setdefault("PORT_FORWARD_SKIP_WAIT", "1")
|
||||
env_pf.setdefault("PORT_FORWARD_WAIT_TIMEOUT", "20")
|
||||
env_pf.setdefault("PORT_FORWARD_WAIT_INTERVAL", "2")
|
||||
env_pf = dict(env)
|
||||
env_pf.setdefault("PORT_FORWARD_SKIP_VALIDATE", "1")
|
||||
env_pf.setdefault("PORT_FORWARD_SKIP_WAIT", "1")
|
||||
env_pf.setdefault("PORT_FORWARD_WAIT_TIMEOUT", "20")
|
||||
env_pf.setdefault("PORT_FORWARD_WAIT_INTERVAL", "2")
|
||||
|
||||
rc_pf = self.controller.run_script(
|
||||
script,
|
||||
args=["--force", "--mode", mode, "start"],
|
||||
env=env_pf,
|
||||
on_line=_pf_line,
|
||||
)
|
||||
if rc_pf != 0:
|
||||
self.script_consoles[script].write(
|
||||
f"\nERROR: {script} start failed with code {rc_pf}\n"
|
||||
rc_pf = self.controller.run_script(
|
||||
script, args=["--force", "--mode", mode, "start"],
|
||||
env=env_pf, on_line=_pf_line,
|
||||
)
|
||||
if rc_pf != 0:
|
||||
_con(script).write(f"\nERROR: {script} start failed with code {rc_pf}\n")
|
||||
overall_success = False
|
||||
self.safe_after(lambda: self._set_init_light("kong", "red"))
|
||||
else:
|
||||
running = False
|
||||
for _ in range(10):
|
||||
status_lines = []
|
||||
|
||||
def _status_line(line):
|
||||
status_lines.append(line)
|
||||
_con("init_port_forwards.sh").write(line)
|
||||
|
||||
rc_status = self.controller.run_script(
|
||||
script, args=["--mode", mode, "status", "grafana"],
|
||||
env=env_pf, on_line=_status_line,
|
||||
)
|
||||
running = any(
|
||||
l.lstrip().startswith("RUNNING") and "grafana" in l
|
||||
for l in status_lines
|
||||
)
|
||||
if rc_status == 0 and running:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if not running:
|
||||
_con(script).write("\nERROR: Grafana port-forward not running after start.\n")
|
||||
overall_success = False
|
||||
self.safe_after(lambda: self._set_init_light("kong", "red"))
|
||||
else:
|
||||
running = False
|
||||
for _ in range(10):
|
||||
status_lines = []
|
||||
|
||||
def _status_line(line):
|
||||
status_lines.append(line)
|
||||
self.script_consoles[script].write(line)
|
||||
|
||||
rc_status = self.controller.run_script(
|
||||
script,
|
||||
args=["--mode", mode, "status", "grafana"],
|
||||
env=env_pf,
|
||||
on_line=_status_line,
|
||||
)
|
||||
running = any(
|
||||
l.lstrip().startswith("RUNNING") and "grafana" in l
|
||||
for l in status_lines
|
||||
)
|
||||
if rc_status == 0 and running:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if not running:
|
||||
self.script_consoles[script].write(
|
||||
"\nERROR: Grafana port-forward not running after start.\n"
|
||||
)
|
||||
overall_success = False
|
||||
else:
|
||||
self.script_consoles[script].write(
|
||||
"\nPort-forwards running (grafana ok).\n"
|
||||
)
|
||||
_con(script).write("\nPort-forwards running (grafana ok).\n")
|
||||
self.safe_after(lambda: self._set_init_light("kong", "green"))
|
||||
|
||||
# Verify critical secrets
|
||||
ns = (
|
||||
@ -774,7 +821,7 @@ class ServicesScreenMixin:
|
||||
kubectl_base_cmd = resolved_base_cmd
|
||||
except Exception:
|
||||
pass
|
||||
self.script_consoles["init_cloudnative_pg.sh"].write(
|
||||
_con("init_cloudnative_pg.sh").write(
|
||||
f"\nVerifying critical secrets in namespace {ns}...\n"
|
||||
)
|
||||
critical_secrets = ["knoe-db-user", "knoe-db-superuser", "cnpg-admin-key"]
|
||||
@ -789,10 +836,10 @@ class ServicesScreenMixin:
|
||||
has_secret_check_warnings = False
|
||||
if missing_secrets:
|
||||
has_secret_check_warnings = True
|
||||
self.script_consoles["init_cloudnative_pg.sh"].write(
|
||||
_con("init_cloudnative_pg.sh").write(
|
||||
f"WARNING: Missing secrets in namespace '{ns}': {', '.join(missing_secrets)}.\n"
|
||||
)
|
||||
self.script_consoles["init_cloudnative_pg.sh"].write(
|
||||
_con("init_cloudnative_pg.sh").write(
|
||||
"Continuing because initialization scripts succeeded; verify CNPG secrets before production use.\n"
|
||||
)
|
||||
|
||||
@ -991,6 +1038,96 @@ class ServicesScreenMixin:
|
||||
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _check_init_scripts_status_async(self):
|
||||
"""Query the live cluster and flip traffic lights to reflect current state.
|
||||
|
||||
Called on page load when a previous successful run is recorded, so the
|
||||
user sees green lights without re-running the scripts.
|
||||
"""
|
||||
def worker():
|
||||
ns = (
|
||||
(self.db_namespace.get() or "").strip()
|
||||
or os.environ.get("NAMESPACE")
|
||||
or "default"
|
||||
)
|
||||
base_cmd = list(self._kubectl_base_cmd())
|
||||
|
||||
def _kubectl(*args):
|
||||
try:
|
||||
res = subprocess.run(
|
||||
base_cmd + list(args),
|
||||
capture_output=True, text=True,
|
||||
env=os.environ.copy(),
|
||||
)
|
||||
return res.returncode, res.stdout, res.stderr
|
||||
except Exception as exc:
|
||||
return 1, "", str(exc)
|
||||
|
||||
# Common Services: any running pod in the service namespace
|
||||
svc_ns = (self._get_service_namespace() or ns).strip() or ns
|
||||
rc, out, _ = _kubectl(
|
||||
"get", "pods", "-n", svc_ns,
|
||||
"--field-selector=status.phase=Running", "-o", "name",
|
||||
)
|
||||
cs_ok = rc == 0 and bool(out.strip())
|
||||
self.safe_after(
|
||||
lambda ok=cs_ok: self._set_init_light("common_services", "green" if ok else "yellow")
|
||||
)
|
||||
|
||||
# Database Services: knoe-db-1/2/3 pods
|
||||
cnpg_cluster = (
|
||||
os.environ.get("CNPG_CLUSTER_NAME")
|
||||
or os.environ.get("CLUSTER_NAME")
|
||||
or "knoe-db"
|
||||
)
|
||||
for i, key in enumerate(("knoe_db_1", "knoe_db_2", "knoe_db_3"), 1):
|
||||
pod_name = f"{cnpg_cluster}-{i}"
|
||||
rc_p, out_p, _ = _kubectl("get", "pod", pod_name, "-n", ns)
|
||||
is_ready = rc_p == 0 and "Running" in out_p
|
||||
self.safe_after(
|
||||
lambda ok=is_ready, k=key: self._set_init_light(k, "green" if ok else "yellow")
|
||||
)
|
||||
|
||||
# Barman-cloud: any completed backup
|
||||
rc_b, out_b, _ = _kubectl(
|
||||
"get", "backup", "-n", ns, "-o",
|
||||
"jsonpath={.items[*].status.phase}",
|
||||
)
|
||||
barman_ok = rc_b == 0 and "completed" in out_b.lower()
|
||||
self.safe_after(
|
||||
lambda ok=barman_ok: self._set_init_light("barman_cloud", "green" if ok else "yellow")
|
||||
)
|
||||
|
||||
# Monitoring: any running pod in monitoring namespace
|
||||
mon_ns = os.environ.get("MONITORING_NAMESPACE") or "monitoring"
|
||||
rc_m, out_m, _ = _kubectl(
|
||||
"get", "pods", "-n", mon_ns,
|
||||
"--field-selector=status.phase=Running", "-o", "name",
|
||||
)
|
||||
mon_ok = rc_m == 0 and bool(out_m.strip())
|
||||
self.safe_after(
|
||||
lambda ok=mon_ok: self._set_init_light("monitoring", "green" if ok else "yellow")
|
||||
)
|
||||
|
||||
# Kong / Ingress: check kong or ingress-nginx namespace
|
||||
kong_ok = False
|
||||
for ns_check in ("kong", "ingress-nginx", ns):
|
||||
rc_k, out_k, _ = _kubectl(
|
||||
"get", "pods", "-n", ns_check,
|
||||
"--field-selector=status.phase=Running", "-o", "name",
|
||||
)
|
||||
if rc_k == 0 and any(
|
||||
"kong" in line or "ingress" in line
|
||||
for line in out_k.splitlines()
|
||||
):
|
||||
kong_ok = True
|
||||
break
|
||||
self.safe_after(
|
||||
lambda ok=kong_ok: self._set_init_light("kong", "green" if ok else "yellow")
|
||||
)
|
||||
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _render_init_cnpg_deploy_page(self):
|
||||
# Letterhead at top right
|
||||
content_width = self.bg_canvas.winfo_width() or 975
|
||||
@ -1000,7 +1137,7 @@ class ServicesScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -1009,7 +1146,7 @@ class ServicesScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -1412,7 +1549,7 @@ class ServicesScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -1421,7 +1558,7 @@ class ServicesScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -1586,7 +1723,7 @@ class ServicesScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -1595,7 +1732,7 @@ class ServicesScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -21,7 +21,7 @@ class SupabaseScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -30,7 +30,7 @@ class SupabaseScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
@ -95,30 +95,8 @@ class SupabaseScreenMixin:
|
||||
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
|
||||
y += 16
|
||||
initial_status = "Ready" if self.supabase_enabled.get() else "Disabled"
|
||||
self._supabase_status_var = tk.StringVar(value=initial_status)
|
||||
status_item = ui.canvas_text(
|
||||
|
||||
@ -19,7 +19,7 @@ class WelcomeScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"Prole",
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
@ -28,7 +28,7 @@ class WelcomeScreenMixin:
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"Infrastructure Automated.",
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
|
||||
@ -1 +1 @@
|
||||
159
|
||||
160
|
||||
32
prole.sh
32
prole.sh
@ -44,11 +44,11 @@ usage() {
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " -l, --log Create a datestamped logfile in prole/logs including all stdout/stderr"
|
||||
echo " -v, --verbose Pass a verbose switch to all functions called by ansible and install.py"
|
||||
echo " -d, --debug Enable a debug logfile in prole/logs with high verbosity from install.py"
|
||||
echo " -v, --verbose Pass a verbose switch to all functions called by ansible and install.sh"
|
||||
echo " -d, --debug Enable a debug logfile in prole/logs with high verbosity from install.sh"
|
||||
echo " -c, --config Path to prole.cfg config file (default: conf/prole.cfg)"
|
||||
echo " --coverage Create a coverage report for the run of install.py"
|
||||
echo " -s, --silent Run unattended install (passes -S to install.py)"
|
||||
echo " --coverage Create a coverage report for the run of install.sh"
|
||||
echo " -s, --silent Run unattended install (passes -S to install.sh)"
|
||||
echo " -r, --reset Perform a reset before action (site/deploy/install); for canonical full reset run use './prole.sh --reset'"
|
||||
echo
|
||||
echo "Commands:"
|
||||
@ -108,7 +108,7 @@ if [[ "${VERBOSE}" -eq 1 ]]; then
|
||||
export ANSIBLE_FORCE_COLOR=1
|
||||
fi
|
||||
|
||||
# We separate arguments that are only for install.py
|
||||
# We separate arguments that are only for install.sh
|
||||
INSTALL_ONLY_ARGS=()
|
||||
if [[ "${DEBUG}" -eq 1 ]]; then
|
||||
INSTALL_ONLY_ARGS+=("--debug")
|
||||
@ -711,7 +711,7 @@ PY
|
||||
fi
|
||||
if [[ $_next_is_config -eq 1 ]]; then
|
||||
_next_is_config=0
|
||||
# Pass -c <path> through to install.py
|
||||
# Pass -c <path> through to install.sh
|
||||
_INSTALL_PASSTHRU+=("-c" "$_arg")
|
||||
continue
|
||||
fi
|
||||
@ -753,7 +753,7 @@ PY
|
||||
fi
|
||||
CMD_ARGS=("${_INSTALL_PASSTHRU[@]}")
|
||||
|
||||
# Ensure OpenBao URL is set appropriately for the active mode before running install.py.
|
||||
# Ensure OpenBao URL is set appropriately for the active mode before running install.sh.
|
||||
# Honour top-level -c / --config if provided (CONFIG_PATH), falling back to default.
|
||||
_cfg_for_env="${CONFIG_PATH:-${PROLE_CFG_DEFAULT}}"
|
||||
for ((i=0; i<${#CMD_ARGS[@]}; i++)); do
|
||||
@ -771,16 +771,14 @@ PY
|
||||
ensure_openbao_url_for_mode "${_mode_for_env}"
|
||||
unset _cfg_for_env _mode_for_env
|
||||
|
||||
PY_CMD=("python3")
|
||||
if [[ "${COVERAGE}" -eq 1 ]]; then
|
||||
PY_CMD=("coverage" "run" "--source=installer")
|
||||
PY_CMD=("python3" "-m" "coverage" "run" "--source=knoe" "-m" "knoe.ui.screens" "--no-gui")
|
||||
else
|
||||
PY_CMD=("${ROOT_DIR}/install.sh" "--no-gui")
|
||||
fi
|
||||
PY_CMD+=("${ROOT_DIR}/install.py" "--no-gui")
|
||||
|
||||
|
||||
if [[ "${LOG}" -eq 1 ]]; then
|
||||
run_with_log "${LOGFILE}" "${PY_CMD[@]}" "${INSTALL_ONLY_ARGS[@]}" "${CMD_ARGS[@]}"
|
||||
elif [[ "${COVERAGE}" -eq 1 ]]; then
|
||||
"${PY_CMD[@]}" "${INSTALL_ONLY_ARGS[@]}" "${CMD_ARGS[@]}"
|
||||
else
|
||||
"${PY_CMD[@]}" "${INSTALL_ONLY_ARGS[@]}" "${CMD_ARGS[@]}"
|
||||
fi
|
||||
@ -880,7 +878,7 @@ PY
|
||||
|
||||
echo "Step 3/3: Running silent installer..."
|
||||
# INSTALL_ONLY_ARGS includes -S and --reset if flags were set
|
||||
python3 "${ROOT_DIR}/install.py" "--no-gui" "-c" "${ROOT_DIR}/conf/prole.cfg" "${INSTALL_ONLY_ARGS[@]}" "${CMD_ARGS[@]}"
|
||||
"${ROOT_DIR}/install.sh" "--no-gui" "-c" "${ROOT_DIR}/conf/prole.cfg" "${INSTALL_ONLY_ARGS[@]}" "${CMD_ARGS[@]}"
|
||||
|
||||
echo "Deployment completed successfully!"
|
||||
) | tee "${LOGFILE}"
|
||||
@ -936,11 +934,11 @@ PY
|
||||
install_args+=("--reset")
|
||||
fi
|
||||
|
||||
PY_CMD=("python3")
|
||||
if [[ "${COVERAGE}" -eq 1 ]]; then
|
||||
PY_CMD=("coverage" "run" "--source=installer")
|
||||
python3 -m coverage run --source=knoe -m knoe.ui.screens --no-gui -S -c "${PROLE_CFG_DEFAULT}" "${install_args[@]}"
|
||||
else
|
||||
"${ROOT_DIR}/install.sh" --no-gui -S -c "${PROLE_CFG_DEFAULT}" "${install_args[@]}"
|
||||
fi
|
||||
"${PY_CMD[@]}" "${ROOT_DIR}/install.py" --no-gui -S -c "${PROLE_CFG_DEFAULT}" "${install_args[@]}"
|
||||
|
||||
if [[ "${COVERAGE}" -eq 1 ]]; then
|
||||
echo ""
|
||||
|
||||
@ -73,7 +73,7 @@ def _run_coverage(mode: str, namespace: str, cluster: str, project_root: Path) -
|
||||
f"--source={_CNPG_MODULE}",
|
||||
"--append",
|
||||
f"--data-file={cov_file}",
|
||||
str(project_root / "install.py"),
|
||||
"-m", "knoe.ui.screens",
|
||||
"--mode", mode,
|
||||
"--non-interactive",
|
||||
]
|
||||
|
||||
@ -9,7 +9,7 @@ PYTHON="$SCRIPT_DIR/bin/python"
|
||||
|
||||
export PYTHONPATH="$SCRIPT_DIR:${PYTHONPATH:-}"
|
||||
|
||||
"$PYTHON" -m coverage run "$SCRIPT_DIR/install.py" "$@"
|
||||
"$PYTHON" -m coverage run -m knoe.ui.screens "$@"
|
||||
"$PYTHON" -m coverage report -m
|
||||
"$PYTHON" -m coverage html
|
||||
echo "Execution instrumentation report generated in $SCRIPT_DIR/htmlcov/index.html"
|
||||
|
||||
@ -1,59 +1,55 @@
|
||||
Network Discovery Summary:
|
||||
Primary Router: 10.0.0.1 (eero_5d:50:f2)
|
||||
DNS Servers: 100.100.100.100, 10.0.0.4, 10.0.0.5
|
||||
DNS Servers: 100.100.100.100, 10.0.0.5, 10.0.0.4
|
||||
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.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [443], Services: ['Web Server']
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
|
||||
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
|
||||
- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 53, 80, 443, 2049, 5900, 88, 389, 636], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server', 'Active Directory Related']
|
||||
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049], Services: ['SSH', 'NFS']
|
||||
- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 2049], Services: ['DNS', 'SSH', 'NFS']
|
||||
- 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.32 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): 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.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): 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, 443], Services: ['SSH', '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.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.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.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.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): Ports [], Services: []
|
||||
- 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.32 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): 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.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
|
||||
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
|
||||
- 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.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.37 [54:07:7d:22:c0:b2] (Netgear_22:c0:b2): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
|
||||
- 10.0.0.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): Ports [], Services: []
|
||||
- 10.0.0.45 [f8:b4:6a:30:a4:1f] (HewlettPacka_30:a4:1f): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
|
||||
- 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.35\ [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): 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.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
|
||||
- 10.0.0.208 [a0:ad:9f:30:84:5f] (ASUSTekCOMPU_30:84:5f): Ports [], Services: []
|
||||
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): Ports [], Services: []
|
||||
- 10.0.0.58 [7e:46:74:2a:b0:b2] (7e:46:74:2a:b0:b2): Ports [], Services: []
|
||||
- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
|
||||
- 10.0.0.95 [b8:27:eb:dd:6f:14] (RaspberryPiF_dd:6f:14): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
|
||||
- 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.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.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.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
|
||||
- 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.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
|
||||
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): Ports [], Services: []
|
||||
- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
|
||||
- 10.0.0.123 [3c:ef:8c:96:e3:3c] (ZhejiangDahu_96:e3:3c): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.1\ [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
|
||||
- 10.0.0.124 [9c:8e:cd:02:73:c4] (AmcrestTechn_02:73:c4): 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.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
|
||||
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
|
||||
- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [], Services: []
|
||||
- 10.0.0.128 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [], Services: []
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.128 [20:c9:d0:94:30:dd] (Apple_94:30:dd): Ports [22, 53, 445, 5900], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
|
||||
- 10.0.0.160 [96:c3:fd:58:02:6c] (96:c3:fd:58:02:6c): Ports [], Services: []
|
||||
- 10.0.0.180 [4c:e1:73:42:1a:be] (HuizhouDehon_2:1a:be): Ports [445], Services: ['SMB/CIFS (Possible Windows/AD)']
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): 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.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): 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.196 [60:5f:8d:88:08:f2] (eero_88:08:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
|
||||
- 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:
|
||||
|
||||
@ -45,7 +45,7 @@ hiddenimports = [
|
||||
]
|
||||
|
||||
a = Analysis(
|
||||
['install.py'],
|
||||
['knoe/ui/screens/__main__.py'],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
|
||||
@ -24,9 +24,9 @@ run_test() {
|
||||
}
|
||||
|
||||
# Core functionality tests
|
||||
run_test "Install.py imports" python3 -c "from install import ProleController, get_resource_path"
|
||||
run_test "Installer imports" python3 -c "from knoe.ui.screens import KnoeController, get_resource_path"
|
||||
run_test "Ncurses modules" python3 -c "from knoe.ncurses_installer import run_ncurses_installer"
|
||||
run_test "Command-line interface" python3 install.py --help
|
||||
run_test "Command-line interface" python3 -m knoe.ui.screens --help
|
||||
run_test "Makefile" make help
|
||||
|
||||
# Resource tests
|
||||
@ -34,7 +34,7 @@ run_test "Image resources" python3 -c "
|
||||
from pathlib import Path
|
||||
import sys
|
||||
sys.path.insert(0, str(Path.cwd()))
|
||||
from install import get_resource_path
|
||||
from knoe.ui.screens import get_resource_path
|
||||
for img in ['img/proleIcon.png', 'img/proleLogo.png', 'img/proleLogoSepia.png']:
|
||||
if not get_resource_path(img).exists():
|
||||
sys.exit(1)
|
||||
|
||||
@ -25,10 +25,10 @@ test_item() {
|
||||
|
||||
# Core Features
|
||||
echo "CORE FEATURES"
|
||||
test_item "Install.py imports" python3 -c "from install import ProleController, get_resource_path"
|
||||
test_item "Installer imports" python3 -c "from knoe.ui.screens import KnoeController, get_resource_path"
|
||||
test_item "Ncurses interface" python3 -c "from knoe.ncurses_installer import run_ncurses_installer"
|
||||
test_item "Display auto-detection" python3 -c "from install import has_display"
|
||||
test_item "CLI arguments" python3 install.py --help
|
||||
test_item "Display auto-detection" python3 -c "from knoe.ui.screens import has_display"
|
||||
test_item "CLI arguments" python3 -m knoe.ui.screens --help
|
||||
|
||||
# Build System
|
||||
echo ""
|
||||
|
||||
@ -200,7 +200,7 @@ PY
|
||||
|
||||
echo ""
|
||||
echo "2. Running silent install..."
|
||||
if python3 "$ROOT_DIR/install.py" -S -c "$CFG_PATH"; then
|
||||
if "$ROOT_DIR/install.sh" -S -c "$CFG_PATH"; then
|
||||
echo "OK: Silent install completed"
|
||||
else
|
||||
echo "ERROR: Silent install failed" >&2
|
||||
|
||||
@ -13,13 +13,13 @@ test -f img/proleIcon.png && echo " ✓ Icon file exists"
|
||||
|
||||
echo ""
|
||||
echo "3. Testing Python modules..."
|
||||
python3 -c "from install import ProleController; ProleController('.')" && echo " ✓ ProleController works"
|
||||
python3 -c "from knoe.ui.screens import KnoeController; KnoeController('.')" && echo " ✓ KnoeController works"
|
||||
python3 -c "from knoe.ncurses_installer import run_ncurses_installer" && echo " ✓ Ncurses installer imports"
|
||||
python3 -c "from knoe.ncurses_ui import CursesWindow" && echo " ✓ Ncurses UI imports"
|
||||
|
||||
echo ""
|
||||
echo "4. Testing command-line interface..."
|
||||
python3 install.py --help > /dev/null && echo " ✓ --help works"
|
||||
python3 -m knoe.ui.screens --help > /dev/null && echo " ✓ --help works"
|
||||
|
||||
echo ""
|
||||
echo "5. Testing spec generator..."
|
||||
|
||||
@ -16,8 +16,8 @@ import os
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
# Now import install.py after mocking
|
||||
from install import KnoeInstaller
|
||||
# Now import after mocking
|
||||
from knoe.ui.screens import KnoeInstaller
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -65,8 +65,8 @@ cd "$PROLE_ROOT"
|
||||
|
||||
# Test 5: Code uses scan directory
|
||||
echo "5. Testing code uses scan directory..."
|
||||
if grep -q "scan_dir = prole_home / \"scan\"" install.py && \
|
||||
grep -q "cwd=str(scan_dir)" install.py; then
|
||||
if grep -rq "scan_dir = prole_home / \"scan\"" knoe/ && \
|
||||
grep -rq "cwd=str(scan_dir)" knoe/; then
|
||||
echo " ✓ Code properly uses scan directory"
|
||||
else
|
||||
echo " ✗ Code not updated to use scan directory"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user