mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
chore: checkpoint k3s deployment and knoe updates
- update Kong/Supabase ingress and service manifests for k3s - expand init scripts and runtime config/topology handling - refresh KNØE UI screens and Supabase deployment/render logic - include related env helper test adjustments Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
c0a7d0c5dc
commit
93f7259722
@ -2,6 +2,12 @@
|
||||
# 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
|
||||
opentofu: local=8080 remote=8080 ns=knoe-system svc=opentofu address=0.0.0.0
|
||||
@ -9,3 +15,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
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-03-31 00:16:16
|
||||
; Generated by install.py on 2026-03-31 18:38:57
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
[Inputs]
|
||||
; Screen-scoped inputs used for unattended replays (-S)
|
||||
argocd.node_selector = gandalf.prole.org
|
||||
build.deploy_env = Dev
|
||||
build.run_build = false
|
||||
database_options.distribution = percona
|
||||
@ -47,33 +48,41 @@ 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.node_selector = gandalf.prole.org
|
||||
init_cluster.argocd_enabled = false
|
||||
init_cluster.at_rest_encryption_enabled = true
|
||||
init_cluster.cluster_env = service
|
||||
init_cluster.deployment_target = prole-service-cluster
|
||||
init_cluster.gitops_enabled = false
|
||||
init_cluster.gitops_enabled = true
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:AL731QFqjjFGqfFT:Xt95lYCIk-6iCHerFSJQpKwWGJMdr04v-4DawlGbM3OBLJ7qbHOSZ_oJm1WtHkvqpoM7BSJNwlEqHMV7R3gl7IFqVUtqJ_AoaRcLSPKE2Z6JGWkIatR6Vzw3J1cm1iZiM2M9wikI5Wqp3t4cdWTOTf7PUaQHkYbBG1PV3w==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:m0esKgX4z-UxYI3V:Rtau88iOMq7zuzT4A9y5PnaWy80PrSqFmoo9vqqXAsOORqFIM1CdcDyFdkHxmIPKTsSEnj91LXmRr3mie83lq4Y-qRj1PMmDD5f3yAtvFA33i709FunDECChkJLVoZJrDHGnRuSaA5ZHwIvqNXRY36S4LxCKluHk6scy8A==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k3s
|
||||
init_cluster.start_cluster = true
|
||||
init_cluster.supabase_enabled = false
|
||||
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_cnpg_deploy.force_rollout = false
|
||||
init_cnpg_deploy.run_deploy = true
|
||||
init_db_build.run_build = true
|
||||
init_password.cluster_name = ${CLUSTER_NAME}
|
||||
init_password.db_host_port = 5432
|
||||
init_password.db_namespace = ${DATABASE_NAMESPACE}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:OcWBTOiijqf-bfTT:6r-ioWdDRdkPNxmbEAQP2TYEPaycWasP}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:OcWBTOiijqf-bfTT:6r-ioWdDRdkPNxmbEAQP2TYEPaycWasP}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:iC68SlzGpoPl9nma:iQEsd-2BzbTXdxTnSldMjHmrFrij_hWJ}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:iC68SlzGpoPl9nma:iQEsd-2BzbTXdxTnSldMjHmrFrij_hWJ}
|
||||
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:h9mzEWvGpnpU2FRz:5W8czWfjnRxzA8tJPk3eFRtrVLD5DxREkfqmQsnKLsbuAaec}
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:U-EPMl7qv4heEB1k:BEcXGbI_LT4lCXwBuvFEGgPbLFa9MpVzECObdH0pbLtClDHf}
|
||||
kerberos_config.realm = PROLE.ORG
|
||||
kerberos_config.test_connection = false
|
||||
kerberos_config.test_connection = true
|
||||
kerberos_config.user = administrator
|
||||
network_scan.run = true
|
||||
ollama_config.model =
|
||||
@ -85,6 +94,7 @@ supabase_config.pv_node = gandalf.prole.org
|
||||
[Global]
|
||||
; Variables used by name in more than one place or assumed global scope
|
||||
ARGOCD_NAMESPACE = argocd
|
||||
ARGOCD_NODE_SELECTOR = gandalf.prole.org
|
||||
CLUSTER_ENV = service
|
||||
CLUSTER_NAME = knoe-db
|
||||
CNPG_ELIGIBLE_NODES = gandalf.prole.org,merlin.prole.org,myrddin.prole.org,pi.prole.org
|
||||
@ -94,15 +104,22 @@ CNPG_PLACEMENT_PLAN_ID = cnpg-placement-7036c5f65b6ef0db
|
||||
CNPG_STAGE1_NODE = gandalf.prole.org
|
||||
DATABASE_NAMESPACE = knoe-db
|
||||
DB_HOST_PORT = 5432
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:M7Rt2Xp273X8ixb5:Rjys8A6SqmBv19DvPAvgihwJ0eJ8Xvda}
|
||||
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
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:gnQjvO_dhM7qkU_g:kFb7E6IQ40RM64JfsrJWBPzkJsMaLXNpNPxfho6t8w5KAeWCAyrw2bMsQtJ8n7cPc_SLWh_zX53Sa1pvEOclmd79e-c_Qff0gZleba1PzzfkjsYRfVlpzQ34gdAHrNHQG2mKnpkcyrNFZ0j6J-w4c3m_m6bTN1cV1KQvMQ==}
|
||||
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 = ${PROLE_SECRET:v1:DkOrjdIC-ji9iusP:7uc4RUXpALlyQcGfZfX6LBP7BEwOqBtbbZpbIgdbfJvMQ_1klRdLtDQrcG54p6ayHvaMQ6W7FSPHPLeKbcjDZ8eJMhNMaH9Q0kIyfmiZdbX3nN6QGCxJWjIn-4GJ4z6_gF7hjHrqAAqxReNoWkAokyoVgqKTiOoWToD0GA==}
|
||||
PROLE_K3S_TOKEN = K107c8c6000488eca4a067d8a73119bbae2f07b4ea1bac7d8d3dc9c500cbb8acb18::server:04572345810eae2f9619a6ed4239702b
|
||||
REGISTRY_NAMESPACE = knoe-system
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SUPABASE_PV_BASE = /synology/d005
|
||||
SUPABASE_PV_BASE_DIR = /synology/d005
|
||||
@ -113,7 +130,7 @@ SYNOLOGY_ROOTS = /synology/d001,/synology/d002,/synology/d003,/synology/d004,/sy
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Dependencies]
|
||||
; No configuration values captured yet for this section.
|
||||
STATUS = All installed
|
||||
|
||||
[Network]
|
||||
AD_DC_HOST = myrddin.prole.org
|
||||
@ -128,7 +145,21 @@ KDC_AUTO_DETECTED = 10.0.0.3
|
||||
KERBEROS_AUTO_ENABLED = True
|
||||
|
||||
[Port Forwards]
|
||||
; No configuration values captured yet for this section.
|
||||
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)
|
||||
|
||||
[System Environment]
|
||||
PROLE_CONF = /Users/chrisfu/dev/prole/conf
|
||||
@ -138,10 +169,22 @@ PROLE_LOGS = /Users/chrisfu/dev/prole/logs
|
||||
PROLE_SERVICE = /Users/chrisfu/dev/prole/etc
|
||||
|
||||
[Monitoring]
|
||||
GRAFANA_ADMIN_PASSWORD = ${PROLE_SECRET:v1:st2uaIMwx55ftSPt:HjzyDuKpzGJyIDVzIKWD6YDzyoqZ4iJ9}
|
||||
GRAFANA_ADMIN_PASSWORD = ${PROLE_SECRET:v1:L2BhAyHRKVei4cGM:-J83-HR_CpnPFpiB6NbOXKdmmagVW2qL}
|
||||
|
||||
[Kerberos Authentication]
|
||||
; No configuration values captured yet for this section.
|
||||
AD_PORT_FORWARD = 1
|
||||
AD_PROXY_HOST_NETWORK = 1
|
||||
AD_PROXY_IMAGE = alpine/socat
|
||||
AD_PROXY_SERVICE = prole-kerberos-ad-dc
|
||||
AD_TCP_PORTS = 88 389 445 464 636
|
||||
AD_UDP_PORTS = 88 464
|
||||
ENABLED = True
|
||||
KDC = 10.0.0.3
|
||||
PASSWORD = ${PROLE_SECRET:v1:jJ4tvCF8ef8xxQ-o:82chD7iVrlUNaDDA8K5DgYMWO2DN9FUSE7Rrm8IVhwuapK2P}
|
||||
REALM = PROLE.ORG
|
||||
SERVER = 10.0.0.3
|
||||
STATUS = Initialized
|
||||
USER = administrator
|
||||
|
||||
[Ollama]
|
||||
OLLAMA_HOST = http://fairyland.prole.org:11434
|
||||
@ -149,40 +192,47 @@ OLLAMA_SERVER_HOST = fairyland.prole.org
|
||||
OLLAMA_SERVER_PORT = 11434
|
||||
|
||||
[Optional Features]
|
||||
AT_REST_ENCRYPTION_ENABLED = True
|
||||
GITOPS_ENABLED = False
|
||||
KERBEROS_ENABLED = True
|
||||
SUPABASE_ENABLED = False
|
||||
ARGOCD_ENABLED = False
|
||||
AT_REST_ENCRYPTION_ENABLED = true
|
||||
GITOPS_ENABLED = true
|
||||
GITOPS_PROVIDER = gitea
|
||||
KERBEROS_ENABLED = true
|
||||
SUPABASE_ENABLED = true
|
||||
|
||||
[GitOps]
|
||||
; No configuration values captured yet for this section.
|
||||
STATUS = Deployed
|
||||
|
||||
[Database Creation]
|
||||
; No configuration values captured yet for this section.
|
||||
DB_USER = root
|
||||
|
||||
[Initialize Cluster]
|
||||
ENVIRONMENT = service
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:sQeWAX7UamXXV4oR:O7sUJk54DE9gUtZAjmN4uiusuVl0T3SDTn-S4Ojgg844EOQV7dMhE1wncgn8kLXXz_klqkQszDzbTqQm2HdaQuFyCqnHGCtHyUAyFvXSAdhhQ3PSkggylLtbPQPPrEP6WFuFywdKf1QgLl2BOcipREupBj5XDfiNfEAigw==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:HAOwKPWbbBvJ7JUN:fuseXaNaQzGdzklTyEelMLldsma1MX7Ciq2P-nzrlsDXpJy9DXv4i4siVOa4aPIYvKP66fAh8IGfk_gwX0JwGQorSgGrgRpaEKjBtZ-0sh48AJPFkP7VkpQw4QZr5xzoDgAA96Q3r70TgDFHlo81OV8_riptpqYp5BO5nA==}
|
||||
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = dev
|
||||
CLUSTER_ENV = k3d-knoe-dev-cluster
|
||||
DISPLAY_NAME = knoe-dev-cluster
|
||||
KUBECTL_CONTEXT = knoe.dev.prole.org
|
||||
KUBECTL_CONTEXT = service
|
||||
MODE = k3d
|
||||
|
||||
[Service Cluster (k3s)]
|
||||
CLUSTER_ENV = prole-service-cluster
|
||||
DISPLAY_NAME = prole-service-cluster
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:sast5Yrr_9mB5BzC:J8Et0XHuUbZbFzAovq-lgqdu_3rR4k3FHOJ7LmzCls-qcZV9D9_NNdmtw5gOTRA8kyZeP5oAFGAXKPGWHgxMJxGO_Lh2oQ1m_68_ExKCK7oeXxlZC3bFCd_fvQ-lP0thBYA9R-GkV_67tJfluOo632xhv6i3Pl1sgBZt5w==}
|
||||
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.
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||
ARTIFACTS_DIR =
|
||||
CLUSTER_ENV = prole-prod-cluster
|
||||
DISPLAY_NAME = prole-prod-cluster
|
||||
MODE = k8s
|
||||
PIPELINE_URL = http://myrddin.prole.org:8080
|
||||
|
||||
[Docker Build]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -195,4 +245,4 @@ MODE = k3s
|
||||
TARGET = prole-service-cluster
|
||||
|
||||
[Install]
|
||||
; No configuration values captured yet for this section.
|
||||
STATUS = Finished
|
||||
|
||||
@ -66,3 +66,13 @@ spec:
|
||||
name: kong
|
||||
port:
|
||||
number: 8000
|
||||
- host: supabase.prole.org
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: kong
|
||||
port:
|
||||
number: 8000
|
||||
|
||||
@ -10,10 +10,63 @@ data:
|
||||
_transform: true
|
||||
|
||||
services:
|
||||
- name: prole-service
|
||||
url: http://prole-svc.knoe-db.svc.cluster.local:8080
|
||||
routes:
|
||||
- name: prole-k3s-kubeconfig
|
||||
hosts:
|
||||
- svc.prole.org
|
||||
paths:
|
||||
- /k3s/kube_config.sh
|
||||
strip_path: false
|
||||
|
||||
- name: db-manager
|
||||
url: http://knoe-db-manager:80
|
||||
routes:
|
||||
- name: backup-route
|
||||
hosts:
|
||||
- svc.prole.org
|
||||
paths:
|
||||
- /backup
|
||||
strip_path: false
|
||||
|
||||
- name: grafana
|
||||
url: http://kps-grafana.monitoring.svc.cluster.local:80
|
||||
routes:
|
||||
- name: grafana-root
|
||||
hosts:
|
||||
- svc.prole.org
|
||||
paths:
|
||||
- /
|
||||
strip_path: false
|
||||
|
||||
- name: knoe-auth
|
||||
url: http://knoe-auth.knoe-system.svc.cluster.local:8080
|
||||
routes:
|
||||
- name: knoe-auth-root
|
||||
hosts:
|
||||
- api.prole.org
|
||||
paths:
|
||||
- /
|
||||
strip_path: false
|
||||
|
||||
- name: gitea-http
|
||||
url: http://gitea-http.gitea.svc.cluster.local:3000
|
||||
routes:
|
||||
- name: gitea-root
|
||||
hosts:
|
||||
- git.prole.org
|
||||
paths:
|
||||
- /
|
||||
strip_path: false
|
||||
|
||||
- name: gitea-ssh
|
||||
host: gitea-ssh.gitea.svc.cluster.local
|
||||
port: 22
|
||||
protocol: tcp
|
||||
routes:
|
||||
- name: gitea-ssh-tcp
|
||||
protocols:
|
||||
- tcp
|
||||
destinations:
|
||||
- port: 3022
|
||||
|
||||
@ -25,6 +25,8 @@ spec:
|
||||
name: proxy
|
||||
- containerPort: 8001
|
||||
name: admin
|
||||
- containerPort: 3022
|
||||
name: ssh-proxy
|
||||
env:
|
||||
- name: KONG_DATABASE
|
||||
value: "off"
|
||||
@ -32,6 +34,8 @@ spec:
|
||||
value: "0.0.0.0:8000"
|
||||
- name: KONG_ADMIN_LISTEN
|
||||
value: "0.0.0.0:8001"
|
||||
- name: KONG_STREAM_LISTEN
|
||||
value: "0.0.0.0:3022 reuseport backlog=16384"
|
||||
- name: KONG_DECLARATIVE_CONFIG
|
||||
value: "/etc/kong/declarative/kong.yml"
|
||||
volumeMounts:
|
||||
|
||||
@ -16,4 +16,7 @@ spec:
|
||||
- name: admin
|
||||
port: 8001
|
||||
targetPort: admin
|
||||
- name: ssh-proxy
|
||||
port: 3022
|
||||
targetPort: ssh-proxy
|
||||
type: ClusterIP
|
||||
|
||||
@ -17,3 +17,13 @@ spec:
|
||||
name: kong
|
||||
port:
|
||||
number: 8000
|
||||
- host: supabase.prole.org
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: kong
|
||||
port:
|
||||
number: 8000
|
||||
|
||||
290
etc/init_gitea.sh
Normal file → Executable file
290
etc/init_gitea.sh
Normal file → Executable file
@ -81,6 +81,118 @@ RELEASE_NAME="gitea"
|
||||
IMAGE_REPO_DEFAULT="gitea/gitea"
|
||||
IMAGE_TAG="${GITEA_IMAGE_TAG:-1.22.3}"
|
||||
IMAGE_REPO="$IMAGE_REPO_DEFAULT"
|
||||
NODE_SELECTOR="${GITEA_NODE_SELECTOR:-${NODE_SELECTOR:-}}"
|
||||
GITEA_PV_NODE="${GITEA_PV_NODE:-${GITEA_NODE_SELECTOR:-gandalf.prole.org}}"
|
||||
GITEA_PV_BASE_DIR="${GITEA_PV_BASE_DIR:-/synology/d005}"
|
||||
GITEA_STORAGE_CLASS="${GITEA_STORAGE_CLASS:-gitea-local-d005}"
|
||||
GITEA_DOMAIN="${GITEA_DOMAIN:-git.prole.org}"
|
||||
GITEA_SSH_DOMAIN="${GITEA_SSH_DOMAIN:-$GITEA_DOMAIN}"
|
||||
KNOE_DB_NAMESPACE="${KNOE_DB_NAMESPACE:-${DATABASE_NAMESPACE:-knoe-db}}"
|
||||
KNOE_DB_CLUSTER="${KNOE_DB_CLUSTER:-${CLUSTER_NAME:-knoe-db}}"
|
||||
KNOE_DB_SERVICE="${KNOE_DB_SERVICE:-${KNOE_DB_CLUSTER}-rw}"
|
||||
KNOE_DB_PORT="${KNOE_DB_PORT:-${DB_HOST_PORT:-5432}}"
|
||||
KNOE_DB_ADMIN_USER="${KNOE_DB_ADMIN_USER:-${KNOE_DB_USER:-postgres}}"
|
||||
GITEA_DB_NAME="${GITEA_DB_NAME:-gitea}"
|
||||
GITEA_DB_USER="${GITEA_DB_USER:-gitea}"
|
||||
GITEA_DB_PASSWORD="${GITEA_DB_PASSWORD:-${DB_PASSWORD:-}}"
|
||||
|
||||
is_secret_placeholder() {
|
||||
case "${1:-}" in
|
||||
'${PROLE_SECRET:'*|'${OPENBAO:'*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
resolve_gitea_db_password() {
|
||||
if [[ -n "${GITEA_DB_PASSWORD:-}" ]] && ! is_secret_placeholder "${GITEA_DB_PASSWORD}"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if kubectl -n "$KNOE_DB_NAMESPACE" get secret knoe-db-superuser >/dev/null 2>&1; then
|
||||
local resolved
|
||||
resolved=$(kubectl -n "$KNOE_DB_NAMESPACE" get secret knoe-db-superuser \
|
||||
-o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true)
|
||||
if [[ -n "$resolved" ]]; then
|
||||
GITEA_DB_PASSWORD="$resolved"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${GITEA_DB_PASSWORD:-}" ]] || is_secret_placeholder "${GITEA_DB_PASSWORD}"; then
|
||||
warn "Could not resolve a concrete Gitea DB password; database bootstrap may be skipped."
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_knoe_db_primary_pod() {
|
||||
local primary
|
||||
primary=$(kubectl -n "$KNOE_DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${KNOE_DB_CLUSTER},cnpg.io/instanceRole=primary" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
if [[ -z "$primary" ]]; then
|
||||
primary=$(kubectl -n "$KNOE_DB_NAMESPACE" get pods \
|
||||
-l "cnpg.io/cluster=${KNOE_DB_CLUSTER}" \
|
||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
|
||||
fi
|
||||
printf '%s' "$primary"
|
||||
}
|
||||
|
||||
sql_escape_literal() {
|
||||
printf '%s' "${1:-}" | sed "s/'/''/g"
|
||||
}
|
||||
|
||||
setup_knoe_db_for_gitea() {
|
||||
local primary
|
||||
primary="$(resolve_knoe_db_primary_pod)"
|
||||
if [[ -z "$primary" ]]; then
|
||||
warn "No knoe-db pod found in namespace '${KNOE_DB_NAMESPACE}'; skipping Gitea DB setup."
|
||||
return 0
|
||||
fi
|
||||
|
||||
resolve_gitea_db_password
|
||||
if [[ -z "${GITEA_DB_PASSWORD:-}" ]] || is_secret_placeholder "${GITEA_DB_PASSWORD}"; then
|
||||
warn "Skipping Gitea DB setup due to unresolved GITEA_DB_PASSWORD."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local admin_user=""
|
||||
local candidate
|
||||
for candidate in "$KNOE_DB_ADMIN_USER" postgres root; do
|
||||
[[ -z "$candidate" ]] && continue
|
||||
if kubectl -n "$KNOE_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 user; skipping Gitea DB setup."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local escaped_password
|
||||
escaped_password="$(sql_escape_literal "$GITEA_DB_PASSWORD")"
|
||||
|
||||
kubectl -n "$KNOE_DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$admin_user" -d postgres -c "
|
||||
DO \$\$ BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='${GITEA_DB_USER}') THEN
|
||||
CREATE ROLE ${GITEA_DB_USER} LOGIN PASSWORD '${escaped_password}';
|
||||
END IF;
|
||||
END \$\$;
|
||||
" >/dev/null 2>&1 || warn "Could not create role '${GITEA_DB_USER}'."
|
||||
|
||||
local db_exists
|
||||
db_exists=$(kubectl -n "$KNOE_DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$admin_user" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${GITEA_DB_NAME}';" 2>/dev/null || true)
|
||||
|
||||
if [[ "$db_exists" != "1" ]]; then
|
||||
kubectl -n "$KNOE_DB_NAMESPACE" exec "$primary" -c postgres -- \
|
||||
psql -U "$admin_user" -d postgres -c "CREATE DATABASE ${GITEA_DB_NAME} OWNER ${GITEA_DB_USER};" \
|
||||
>/dev/null 2>&1 || warn "Could not create database '${GITEA_DB_NAME}'."
|
||||
fi
|
||||
|
||||
log "Gitea database '${GITEA_DB_NAME}' prepared in namespace '${KNOE_DB_NAMESPACE}'."
|
||||
}
|
||||
|
||||
# Optional reset
|
||||
if [[ "$FORCE" -eq 1 ]]; then
|
||||
@ -119,20 +231,177 @@ deploy_with_helm() {
|
||||
|
||||
# Minimal config: NodePort or ClusterIP + Ingress depending on environment.
|
||||
# Avoid heavy persistence defaults; users can override via values later.
|
||||
local -a helm_args=(
|
||||
--set "image.repository=$IMAGE_REPO"
|
||||
--set "image.tag=$IMAGE_TAG"
|
||||
--set "image.rootless=false"
|
||||
--set "image.fullOverride=${IMAGE_REPO}:${IMAGE_TAG}"
|
||||
--set "service.http.type=ClusterIP"
|
||||
--set "service.ssh.type=ClusterIP"
|
||||
--set "service.ssh.port=22"
|
||||
--set "gitea.admin.username=${GITEA_ADMIN_USER:-gitea_admin}"
|
||||
--set "gitea.admin.password=${GITEA_ADMIN_PASSWORD:-gitea_admin}"
|
||||
--set "gitea.admin.email=${GITEA_ADMIN_EMAIL:-gitea_admin@example.local}"
|
||||
--set "gitea.config.server.DOMAIN=${GITEA_DOMAIN}"
|
||||
--set "gitea.config.server.SSH_DOMAIN=${GITEA_SSH_DOMAIN}"
|
||||
--set "gitea.config.server.ROOT_URL=http://${GITEA_DOMAIN}/"
|
||||
--set "gitea.config.server.HTTP_PORT=3000"
|
||||
--set "gitea.config.server.SSH_PORT=22"
|
||||
--set "gitea.config.database.DB_TYPE=postgres"
|
||||
--set "gitea.config.database.HOST=${KNOE_DB_SERVICE}.${KNOE_DB_NAMESPACE}.svc.cluster.local:${KNOE_DB_PORT}"
|
||||
--set "gitea.config.database.NAME=${GITEA_DB_NAME}"
|
||||
--set "gitea.config.database.USER=${GITEA_DB_USER}"
|
||||
--set-string "gitea.config.database.PASSWD=${GITEA_DB_PASSWORD}"
|
||||
--set "gitea.config.database.SSL_MODE=disable"
|
||||
--set "gitea.config.session.PROVIDER=db"
|
||||
--set "postgresql.enabled=false"
|
||||
--set "postgresql-ha.enabled=false"
|
||||
--set "valkey-cluster.enabled=false"
|
||||
--set "persistence.enabled=true"
|
||||
)
|
||||
|
||||
if [[ "$MODE" == "k3s" ]]; then
|
||||
helm_args+=(
|
||||
--set "gitea.config.cache.ADAPTER=memory"
|
||||
--set "global.storageClass=$GITEA_STORAGE_CLASS"
|
||||
--set "persistence.storageClass=$GITEA_STORAGE_CLASS"
|
||||
--set "nodeSelector.kubernetes\\.io/hostname=$GITEA_PV_NODE"
|
||||
--set "deployment.strategy=Recreate"
|
||||
)
|
||||
fi
|
||||
|
||||
helm upgrade --install "$RELEASE_NAME" "$CHART_NAME" \
|
||||
-n "$NAMESPACE" \
|
||||
--set image.repository="$IMAGE_REPO" \
|
||||
--set image.tag="$IMAGE_TAG" \
|
||||
--set service.http.type=ClusterIP \
|
||||
--set service.ssh.type=ClusterIP \
|
||||
--set gitea.admin.username="${GITEA_ADMIN_USER:-gitea_admin}" \
|
||||
--set gitea.admin.password="${GITEA_ADMIN_PASSWORD:-gitea_admin}" \
|
||||
--set gitea.admin.email="${GITEA_ADMIN_EMAIL:-gitea_admin@example.local}" \
|
||||
"${helm_args[@]}" \
|
||||
--wait --timeout 10m
|
||||
}
|
||||
|
||||
ensure_k3s_storage_layout() {
|
||||
[[ "$MODE" == "k3s" ]] || return 0
|
||||
|
||||
log "Ensuring Gitea k3s storage on node '${GITEA_PV_NODE}' at '${GITEA_PV_BASE_DIR}' (storageClass=${GITEA_STORAGE_CLASS})"
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: ${GITEA_STORAGE_CLASS}
|
||||
provisioner: kubernetes.io/no-provisioner
|
||||
reclaimPolicy: Retain
|
||||
volumeBindingMode: WaitForFirstConsumer
|
||||
EOF
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: gitea-d005-shared-storage
|
||||
spec:
|
||||
capacity:
|
||||
storage: 10Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ${GITEA_STORAGE_CLASS}
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
hostPath:
|
||||
path: ${GITEA_PV_BASE_DIR}/gitea/shared
|
||||
type: DirectoryOrCreate
|
||||
claimRef:
|
||||
namespace: ${NAMESPACE}
|
||||
name: gitea-shared-storage
|
||||
nodeAffinity:
|
||||
required:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: kubernetes.io/hostname
|
||||
operator: In
|
||||
values:
|
||||
- ${GITEA_PV_NODE}
|
||||
EOF
|
||||
}
|
||||
|
||||
prepare_k3s_host_permissions() {
|
||||
[[ "$MODE" == "k3s" ]] || return 0
|
||||
|
||||
local prep_pod="gitea-storage-perms"
|
||||
kubectl -n "$NAMESPACE" delete pod "$prep_pod" --ignore-not-found >/dev/null 2>&1 || true
|
||||
|
||||
cat <<EOF | kubectl apply -n "$NAMESPACE" -f -
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: ${prep_pod}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: ${GITEA_PV_NODE}
|
||||
containers:
|
||||
- name: perms
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
args:
|
||||
- |
|
||||
set -eu
|
||||
mkdir -p \
|
||||
/host/gitea/shared
|
||||
chmod -R 0777 /host/gitea
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
volumeMounts:
|
||||
- name: host-root
|
||||
mountPath: /host
|
||||
volumes:
|
||||
- name: host-root
|
||||
hostPath:
|
||||
path: ${GITEA_PV_BASE_DIR}
|
||||
type: DirectoryOrCreate
|
||||
EOF
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded pod/${prep_pod} --timeout=120s >/dev/null
|
||||
kubectl -n "$NAMESPACE" delete pod "$prep_pod" --ignore-not-found >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
reset_stuck_k3s_release() {
|
||||
[[ "$MODE" == "k3s" ]] || return 0
|
||||
local pending
|
||||
pending=$(kubectl -n "$NAMESPACE" get pvc -o jsonpath='{range .items[*]}{.spec.storageClassName}:{.status.phase}{"\n"}{end}' 2>/dev/null | grep '^:Pending$' || true)
|
||||
if [[ -z "$pending" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Detected pending PVCs without storageClass in namespace ${NAMESPACE}; resetting stuck Gitea release"
|
||||
if command -v helm >/dev/null 2>&1; then
|
||||
helm uninstall "$RELEASE_NAME" -n "$NAMESPACE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
kubectl -n "$NAMESPACE" delete pvc gitea-shared-storage data-gitea-postgresql-ha-postgresql-0 data-gitea-postgresql-ha-postgresql-1 data-gitea-postgresql-ha-postgresql-2 valkey-data-gitea-valkey-cluster-0 valkey-data-gitea-valkey-cluster-1 valkey-data-gitea-valkey-cluster-2 >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
release_exists() {
|
||||
command -v helm >/dev/null 2>&1 && helm status "$RELEASE_NAME" -n "$NAMESPACE" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
gitea_workload_exists() {
|
||||
kubectl -n "$NAMESPACE" get deploy "$RELEASE_NAME" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
apply_manifest_fallback() {
|
||||
warn "Helm unavailable; applying fallback manifest"
|
||||
if release_exists || gitea_workload_exists; then
|
||||
warn "Helm resources detected for release '$RELEASE_NAME'; skipping fallback manifest to avoid immutable selector/port conflicts"
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Applying fallback manifest"
|
||||
local node_selector_block=""
|
||||
if [[ -n "$NODE_SELECTOR" ]]; then
|
||||
node_selector_block=$(cat <<EOF
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: ${NODE_SELECTOR}
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
cat <<EOF | kubectl apply -n "$NAMESPACE" -f -
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -148,6 +417,7 @@ spec:
|
||||
labels:
|
||||
app: gitea
|
||||
spec:
|
||||
${node_selector_block}
|
||||
containers:
|
||||
- name: gitea
|
||||
image: ${IMAGE_REPO}:${IMAGE_TAG}
|
||||
@ -193,6 +463,10 @@ case "$ACTION" in
|
||||
esac
|
||||
|
||||
log "Deploying Gitea to namespace '$NAMESPACE' (mode=$MODE)"
|
||||
reset_stuck_k3s_release
|
||||
ensure_k3s_storage_layout
|
||||
prepare_k3s_host_permissions
|
||||
setup_knoe_db_for_gitea
|
||||
if deploy_with_helm; then
|
||||
log "Gitea deployed via Helm"
|
||||
else
|
||||
|
||||
@ -59,6 +59,7 @@ KONG_IMAGE="${KONG_IMAGE:-kong:3.9}"
|
||||
KONG_NAME="${KONG_NAME:-prole-svc-kong}"
|
||||
KONG_PROXY_PORT="${KONG_PROXY_PORT:-8000}"
|
||||
KONG_ADMIN_PORT="${KONG_ADMIN_PORT:-8001}"
|
||||
KONG_GITEA_SSH_PORT="${KONG_GITEA_SSH_PORT:-3022}"
|
||||
KONG_CONFIG_NAME="${KONG_CONFIG_NAME:-prole-svc-kong-config}"
|
||||
|
||||
# Public service entrypoint (single source of truth from prole.cfg via prole_cfg.sh)
|
||||
@ -83,6 +84,10 @@ DB_MANAGER_SERVICE="${DB_MANAGER_SERVICE:-knoe-db-manager}"
|
||||
DB_MANAGER_PORT="${DB_MANAGER_PORT:-80}"
|
||||
PROLE_SERVICE_UPSTREAM_URL="${PROLE_SERVICE_UPSTREAM_URL:-http://prole-svc.knoe-db.svc.cluster.local:8080}"
|
||||
GRAFANA_UPSTREAM_URL="${GRAFANA_UPSTREAM_URL:-http://kps-grafana.monitoring.svc.cluster.local:80}"
|
||||
GITEA_HOSTNAME="${GITEA_HOSTNAME:-${GITEA_DOMAIN:-git.prole.org}}"
|
||||
GITEA_HTTP_UPSTREAM_URL="${GITEA_HTTP_UPSTREAM_URL:-http://gitea-http.gitea.svc.cluster.local:3000}"
|
||||
GITEA_SSH_UPSTREAM_HOST="${GITEA_SSH_UPSTREAM_HOST:-gitea-ssh.gitea.svc.cluster.local}"
|
||||
GITEA_SSH_UPSTREAM_PORT="${GITEA_SSH_UPSTREAM_PORT:-22}"
|
||||
|
||||
# SSO wiring knobs
|
||||
PROLE_GRAFANA_SSO_ENABLED="${PROLE_GRAFANA_SSO_ENABLED:-0}"
|
||||
@ -165,6 +170,8 @@ services:
|
||||
url: ${PROLE_SERVICE_UPSTREAM_URL}
|
||||
routes:
|
||||
- name: prole-k3s-kubeconfig
|
||||
hosts:
|
||||
- ${SERVICE_HOSTNAME}
|
||||
paths:
|
||||
- /k3s/kube_config.sh
|
||||
strip_path: false
|
||||
@ -173,6 +180,8 @@ services:
|
||||
url: http://${DB_MANAGER_SERVICE}.${NAMESPACE}.svc.cluster.local:${DB_MANAGER_PORT}
|
||||
routes:
|
||||
- name: backup-route
|
||||
hosts:
|
||||
- ${SERVICE_HOSTNAME}
|
||||
paths:
|
||||
- /backup
|
||||
strip_path: false
|
||||
@ -196,6 +205,27 @@ services:
|
||||
paths:
|
||||
- /
|
||||
strip_path: false
|
||||
|
||||
- name: gitea-http
|
||||
url: ${GITEA_HTTP_UPSTREAM_URL}
|
||||
routes:
|
||||
- name: gitea-root
|
||||
hosts:
|
||||
- ${GITEA_HOSTNAME}
|
||||
paths:
|
||||
- /
|
||||
strip_path: false
|
||||
|
||||
- name: gitea-ssh
|
||||
host: ${GITEA_SSH_UPSTREAM_HOST}
|
||||
port: ${GITEA_SSH_UPSTREAM_PORT}
|
||||
protocol: tcp
|
||||
routes:
|
||||
- name: gitea-ssh-tcp
|
||||
protocols:
|
||||
- tcp
|
||||
destinations:
|
||||
- port: ${KONG_GITEA_SSH_PORT}
|
||||
KONGEOF
|
||||
)
|
||||
|
||||
@ -243,6 +273,7 @@ apply_service_ingress() {
|
||||
fi
|
||||
|
||||
local auth_host="${AUTH_HOSTNAME:-}"
|
||||
local gitea_host="${GITEA_HOSTNAME:-}"
|
||||
local tls_hosts_extra=""
|
||||
local rules_extra=""
|
||||
if [[ -n "$auth_host" && "$auth_host" != "$host" ]]; then
|
||||
@ -262,6 +293,23 @@ EOF
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ -n "$gitea_host" && "$gitea_host" != "$host" && "$gitea_host" != "$auth_host" ]]; then
|
||||
tls_hosts_extra+=$'\n - '"${gitea_host}"
|
||||
rules_extra+=$'\n'$(cat <<EOF
|
||||
- host: ${gitea_host}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ${KONG_NAME}
|
||||
port:
|
||||
number: ${KONG_PROXY_PORT}
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
echo "Applying service Ingress for host '${host}' -> ${KONG_NAME}:${KONG_PROXY_PORT} (namespace=${NAMESPACE}) ..."
|
||||
(
|
||||
tmp="$(mktemp)"
|
||||
|
||||
@ -709,6 +709,20 @@ def _expand_cfg_value(val: str, variables: dict) -> str:
|
||||
return result
|
||||
|
||||
|
||||
def _preserve_cfg_expr_for_persistence(
|
||||
raw_value: str | None,
|
||||
expanded_value: str | None = None,
|
||||
) -> str:
|
||||
"""Prefer raw tokenized value (e.g. `${HOME}`) when persisting config."""
|
||||
|
||||
raw = "" if raw_value is None else str(raw_value).strip()
|
||||
if "$" in raw:
|
||||
return raw
|
||||
if expanded_value is None:
|
||||
return raw
|
||||
return str(expanded_value).strip()
|
||||
|
||||
|
||||
def _parse_bool(val: any, default: bool | None = False) -> bool | None:
|
||||
if val is None:
|
||||
return default
|
||||
@ -1202,3 +1216,28 @@ def load_dependencies(refresh: bool = True) -> list[dict]:
|
||||
except Exception:
|
||||
dep["installed"] = False
|
||||
return deps
|
||||
|
||||
|
||||
def _parse_gcp_cfg(path) -> dict:
|
||||
"""Parse flat tfvars-style file written by etc/config.py --provider gcp.
|
||||
Handles: key = "value" and key = value
|
||||
Skips lines starting with # or ;
|
||||
Returns empty dict if file missing or unreadable.
|
||||
"""
|
||||
result = {}
|
||||
try:
|
||||
for raw in path.read_text().splitlines():
|
||||
line = raw.strip()
|
||||
if not line or line.startswith("#") or line.startswith(";"):
|
||||
continue
|
||||
if "=" not in line:
|
||||
continue
|
||||
k, _, v = line.partition("=")
|
||||
key = k.strip()
|
||||
val = v.strip().strip('"').strip("'")
|
||||
if key:
|
||||
result[key] = val
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
@ -2326,6 +2326,7 @@ def _render_prole_cfg(
|
||||
"Initialize Cluster",
|
||||
"Dev Cluster (k3d)",
|
||||
"Service Cluster (k3s)",
|
||||
"GCP",
|
||||
"Prod Cluster (k8s)",
|
||||
"Docker Build",
|
||||
"Initialization Scripts",
|
||||
|
||||
@ -333,6 +333,9 @@ def canonical_yaml(config: KnoeProductionConfig) -> str:
|
||||
|
||||
def opentofu_vars(config: KnoeProductionConfig) -> dict[str, Any]:
|
||||
return {
|
||||
# GCP identity (populated from conf/prod/gcp.cfg via installer)
|
||||
"gcp_project_id": config.cloud.projectId,
|
||||
"gcp_region": config.cloud.region,
|
||||
"ecosystem_id": config.metadata.ecosystemId,
|
||||
"environment": config.metadata.environment,
|
||||
"project_id": config.cloud.projectId,
|
||||
|
||||
@ -588,6 +588,36 @@ def _extract_inventory_from_log(log_text: str) -> dict | None:
|
||||
return data if isinstance(data, dict) else None
|
||||
|
||||
|
||||
def cleanup_topology_collectors(
|
||||
*,
|
||||
kubectl_base_cmd: Sequence[str],
|
||||
namespace: str = TOPOLOGY_NAMESPACE,
|
||||
env: dict[str, str] | None = None,
|
||||
collector_name: str | None = None,
|
||||
) -> None:
|
||||
selector = (
|
||||
f"prole.io/topology-collector={collector_name}"
|
||||
if collector_name
|
||||
else "app.kubernetes.io/name=prole-topology-collector"
|
||||
)
|
||||
for kind in ("daemonset", "pod"):
|
||||
_run_kubectl(
|
||||
kubectl_base_cmd,
|
||||
[
|
||||
"-n",
|
||||
namespace,
|
||||
"delete",
|
||||
kind,
|
||||
"-l",
|
||||
selector,
|
||||
"--ignore-not-found=true",
|
||||
"--wait=false",
|
||||
],
|
||||
timeout=15,
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
def _merge_node(
|
||||
k8s_node: dict,
|
||||
inventory: dict | None,
|
||||
@ -1078,19 +1108,11 @@ def discover_cluster_topology(
|
||||
_progress(f"Timed out waiting for node {node_name}")
|
||||
|
||||
if collector_applied:
|
||||
_run_kubectl(
|
||||
kubectl_base_cmd,
|
||||
[
|
||||
"-n",
|
||||
cfg.namespace,
|
||||
"delete",
|
||||
"daemonset",
|
||||
collector_name,
|
||||
"--ignore-not-found=true",
|
||||
"--wait=false",
|
||||
],
|
||||
timeout=15,
|
||||
cleanup_topology_collectors(
|
||||
kubectl_base_cmd=kubectl_base_cmd,
|
||||
namespace=cfg.namespace,
|
||||
env=env,
|
||||
collector_name=collector_name,
|
||||
)
|
||||
|
||||
merged_nodes: list[NodeTopology] = []
|
||||
|
||||
@ -22,6 +22,8 @@ from knoe import screen as ui
|
||||
from knoe.config import (
|
||||
_expand_cfg_value,
|
||||
_collect_cfg_vars,
|
||||
_parse_gcp_cfg,
|
||||
_preserve_cfg_expr_for_persistence,
|
||||
)
|
||||
from knoe.core.controller import KnoeController
|
||||
from knoe.core.env import (
|
||||
@ -412,8 +414,10 @@ class KnoeInstaller(
|
||||
self.service_namespace = tk.StringVar(value="default")
|
||||
self.gitops_enabled = tk.BooleanVar(value=False)
|
||||
self.gitops_namespace = tk.StringVar(value="gitea")
|
||||
self.gitops_node_selector = tk.StringVar(value="")
|
||||
self.argocd_enabled = tk.BooleanVar(value=False)
|
||||
self.argocd_namespace = tk.StringVar(value="argocd")
|
||||
self.argocd_node_selector = tk.StringVar(value="")
|
||||
|
||||
# Load existing docker_import_dir from prole.cfg
|
||||
try:
|
||||
@ -458,6 +462,12 @@ class KnoeInstaller(
|
||||
saved_argocd_ns = _expand_cfg_value(
|
||||
cfg["Global"].get("ARGOCD_NAMESPACE", ""), cfg_vars
|
||||
).strip()
|
||||
saved_gitops_node = _expand_cfg_value(
|
||||
cfg["Global"].get("GITEA_NODE_SELECTOR", ""), cfg_vars
|
||||
).strip()
|
||||
saved_argocd_node = _expand_cfg_value(
|
||||
cfg["Global"].get("ARGOCD_NODE_SELECTOR", ""), cfg_vars
|
||||
).strip()
|
||||
saved_gitops_ns = (
|
||||
_expand_cfg_value(
|
||||
cfg.get("GitOps", "GITOPS_NAMESPACE", fallback=""),
|
||||
@ -547,6 +557,22 @@ class KnoeInstaller(
|
||||
self.prole_cfg_data.setdefault("GitOps", {})[
|
||||
"NAMESPACE"
|
||||
] = saved_gitops_ns
|
||||
if saved_gitops_node:
|
||||
try:
|
||||
self.gitops_node_selector.set(saved_gitops_node)
|
||||
except Exception:
|
||||
pass
|
||||
self.prole_cfg_data.setdefault("Global", {})[
|
||||
"GITEA_NODE_SELECTOR"
|
||||
] = saved_gitops_node
|
||||
if saved_argocd_node:
|
||||
try:
|
||||
self.argocd_node_selector.set(saved_argocd_node)
|
||||
except Exception:
|
||||
pass
|
||||
self.prole_cfg_data.setdefault("Global", {})[
|
||||
"ARGOCD_NODE_SELECTOR"
|
||||
] = saved_argocd_node
|
||||
if saved_argocd_enabled:
|
||||
val = saved_argocd_enabled.lower() in ("true", "1", "yes")
|
||||
try:
|
||||
@ -575,6 +601,33 @@ class KnoeInstaller(
|
||||
mon[k] = v
|
||||
if mon:
|
||||
self.prole_cfg_data["Monitoring"] = mon
|
||||
|
||||
if cfg.has_section("Prod Cluster (k8s)"):
|
||||
prod_sec = {}
|
||||
for k, v in cfg.items("Prod Cluster (k8s)"):
|
||||
try:
|
||||
prod_sec[k.upper()] = _expand_cfg_value(v, cfg_vars)
|
||||
except Exception:
|
||||
prod_sec[k.upper()] = v
|
||||
if prod_sec:
|
||||
self.prole_cfg_data["Prod Cluster (k8s)"].update(prod_sec)
|
||||
|
||||
gcp_cfg_path = conf_dir / "prod" / "gcp.cfg"
|
||||
gcp_values = _parse_gcp_cfg(gcp_cfg_path)
|
||||
if gcp_values:
|
||||
self.prole_cfg_data.setdefault("GCP", {}).update(
|
||||
{k.upper(): v for k, v in gcp_values.items()}
|
||||
)
|
||||
|
||||
# Preserve raw shell/config expressions for persistence so paths
|
||||
# like `${HOME}/...` remain portable across hosts.
|
||||
for section in cfg.sections():
|
||||
section_data = self.prole_cfg_data.setdefault(section, {})
|
||||
for key, raw_value in cfg.items(section):
|
||||
section_data[key] = _preserve_cfg_expr_for_persistence(
|
||||
raw_value,
|
||||
section_data.get(key, raw_value),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -638,6 +691,18 @@ class KnoeInstaller(
|
||||
self.prod_artifacts_path = tk.StringVar(
|
||||
value=str(PROJECT_ROOT / "data" / "staging")
|
||||
)
|
||||
saved_artifacts_dir = (
|
||||
self.prole_cfg_data.get("Prod Cluster (k8s)", {})
|
||||
.get("ARTIFACTS_DIR", "")
|
||||
.strip()
|
||||
)
|
||||
if saved_artifacts_dir:
|
||||
try:
|
||||
self.prod_artifacts_path.set(saved_artifacts_dir)
|
||||
except Exception:
|
||||
pass
|
||||
# Stash GCP values so _ensure_prod_config_state can apply them lazily.
|
||||
self._gcp_cfg_values = dict(self.prole_cfg_data.get("GCP", {}))
|
||||
# GitOps provider choice (gitea / argocd / none)
|
||||
self.gitops_provider = tk.StringVar(value="gitea")
|
||||
# Google Workspace
|
||||
|
||||
@ -92,7 +92,16 @@ class ArgoCDScreenMixin:
|
||||
self._canvas_items.append(ns_window)
|
||||
self._overlay_widgets.append(ns_entry)
|
||||
|
||||
y = 360
|
||||
node_selector_var = getattr(self, "argocd_node_selector", None)
|
||||
if hasattr(self, "_render_k3s_node_selector") and node_selector_var is not None:
|
||||
y = self._render_k3s_node_selector(
|
||||
y=360,
|
||||
target_var=node_selector_var,
|
||||
title="Node selector:",
|
||||
)
|
||||
else:
|
||||
y = 360
|
||||
|
||||
initial_status = "Ready" if self.argocd_enabled.get() else "Disabled"
|
||||
self._argocd_status_var = tk.StringVar(value=initial_status)
|
||||
status_item = ui.canvas_text(
|
||||
@ -196,6 +205,11 @@ class ArgoCDScreenMixin:
|
||||
else:
|
||||
mode = "k3d"
|
||||
args = ["--mode", mode, "--namespace", namespace, "update"]
|
||||
node_selector = (self.argocd_node_selector.get() or "").strip()
|
||||
if mode == "k3s" and node_selector:
|
||||
env["ARGOCD_NODE_SELECTOR"] = node_selector
|
||||
env["NODE_SELECTOR"] = node_selector
|
||||
self._argocd_console.write(f"Node selector: {node_selector}\n")
|
||||
|
||||
cfg_path = None
|
||||
try:
|
||||
|
||||
@ -39,6 +39,122 @@ class ScreenBaseMixin:
|
||||
self._overlay_widgets.append(console)
|
||||
return console
|
||||
|
||||
def _is_k3s_mode_active(self):
|
||||
"""Return True when current mode resolves to k3s/service cluster."""
|
||||
mode = (os.environ.get("PROLE_MODE") or "").strip().lower()
|
||||
if mode == "k3s":
|
||||
return True
|
||||
try:
|
||||
return (self.cluster_env.get() or "").strip().lower() == "service"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _fetch_k3s_nodes(self):
|
||||
"""Return a list of node names from kubectl get nodes in k3s mode."""
|
||||
cmd = ["kubectl", "get", "nodes", "-o", "name"]
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=20,
|
||||
)
|
||||
names = []
|
||||
for line in (proc.stdout or "").splitlines():
|
||||
line = (line or "").strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("node/"):
|
||||
line = line.split("/", 1)[1]
|
||||
names.append(line)
|
||||
if not names:
|
||||
raise ValueError("no nodes returned")
|
||||
return names
|
||||
except Exception:
|
||||
fallback = []
|
||||
for candidate in ("gandalf.prole.org", "myrddin.prole.org"):
|
||||
if candidate not in fallback:
|
||||
fallback.append(candidate)
|
||||
return fallback
|
||||
|
||||
def _is_tainted_node(self, node_name):
|
||||
"""Return True if the node has taints and should be visually disabled."""
|
||||
cmd = [
|
||||
"kubectl",
|
||||
"get",
|
||||
"node",
|
||||
node_name,
|
||||
"-o",
|
||||
"jsonpath={.spec.taints[*].key}",
|
||||
]
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15,
|
||||
)
|
||||
return bool((proc.stdout or "").strip())
|
||||
except Exception:
|
||||
return node_name == "myrddin.prole.org"
|
||||
|
||||
def _render_k3s_node_selector(
|
||||
self,
|
||||
*,
|
||||
y,
|
||||
target_var,
|
||||
title="Node selector:",
|
||||
x_label=48,
|
||||
x_options=190,
|
||||
option_spacing=175,
|
||||
):
|
||||
"""Render a reusable k3s node selector and return the next y offset."""
|
||||
if not self._is_k3s_mode_active():
|
||||
return y
|
||||
|
||||
item = ui.canvas_text(
|
||||
self,
|
||||
x_label,
|
||||
y,
|
||||
title,
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12),
|
||||
)
|
||||
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:
|
||||
for node in nodes:
|
||||
if not self._is_tainted_node(node):
|
||||
target_var.set(node)
|
||||
break
|
||||
|
||||
return y + 36
|
||||
|
||||
def safe_after(self, func, delay=0):
|
||||
"""Run a function in the main thread if the root window still exists."""
|
||||
if not self.root or not self.root.winfo_exists():
|
||||
|
||||
@ -112,6 +112,8 @@ class ConfigMixin:
|
||||
"SUPABASE_PV_NODE": (self.supabase_pv_node.get() or "").strip(),
|
||||
"SUPABASE_PV_BASE": (self.supabase_pv_base_dir.get() or "").strip(),
|
||||
"SUPABASE_PV_BASE_DIR": (self.supabase_pv_base_dir.get() or "").strip(),
|
||||
"GITEA_NODE_SELECTOR": (self.gitops_node_selector.get() or "").strip(),
|
||||
"ARGOCD_NODE_SELECTOR": (self.argocd_node_selector.get() or "").strip(),
|
||||
"PROLE_OPENTOFU_URL": _default_opentofu_pipeline_url(),
|
||||
}
|
||||
if prole_home_override:
|
||||
@ -216,6 +218,7 @@ class ConfigMixin:
|
||||
"Initialize Cluster",
|
||||
"Docker Build",
|
||||
"Initialization Scripts",
|
||||
"GCP",
|
||||
"Deployment",
|
||||
"Install",
|
||||
]
|
||||
@ -533,9 +536,11 @@ class ConfigMixin:
|
||||
|
||||
# GitOps
|
||||
_set("gitops.namespace", _get_var(self.gitops_namespace, "gitea"))
|
||||
_set("gitops.node_selector", _get_var(self.gitops_node_selector, ""))
|
||||
|
||||
# ArgoCD
|
||||
_set("argocd.namespace", _get_var(self.argocd_namespace, "argocd"))
|
||||
_set("argocd.node_selector", _get_var(self.argocd_node_selector, ""))
|
||||
|
||||
# Disk selection (installer packaging)
|
||||
_set("disk_selection.disk_type", _get_var(self.selected_disk_type, "local"))
|
||||
|
||||
@ -18,6 +18,7 @@ from knoe.config import (
|
||||
_encrypt_cfg_secret,
|
||||
_expand_path_expr,
|
||||
_merge_kubeconfig,
|
||||
_parse_gcp_cfg,
|
||||
_write_k3s_kubeconfig,
|
||||
)
|
||||
from knoe.core.actions import _reset_k3s_namespace
|
||||
@ -404,6 +405,35 @@ class ClusterScreenMixin:
|
||||
)
|
||||
_add_row(cloud_tab, cloud_row, "DNS Zone", "cloud.dnsZone")
|
||||
|
||||
def _on_load_gcp_cfg():
|
||||
gcp_cfg_path = self._resolve_prole_conf_dir() / "prod" / "gcp.cfg"
|
||||
loaded = _parse_gcp_cfg(gcp_cfg_path)
|
||||
if not loaded:
|
||||
import tkinter.messagebox as _mb
|
||||
_mb.showwarning(
|
||||
"GCP Setup",
|
||||
f"No values found at:\n{gcp_cfg_path}\n\n"
|
||||
"Run: python3 etc/config.py --mode k8s --provider gcp",
|
||||
)
|
||||
return
|
||||
upper = {k.upper(): v for k, v in loaded.items()}
|
||||
if upper.get("PROJECT_ID"):
|
||||
self.prod_form_vars["cloud.projectId"].set(upper["PROJECT_ID"])
|
||||
if upper.get("REGION"):
|
||||
self.prod_form_vars["cloud.region"].set(upper["REGION"])
|
||||
self.prole_cfg_data.setdefault("GCP", {}).update(upper)
|
||||
self._gcp_cfg_values = dict(self.prole_cfg_data["GCP"])
|
||||
|
||||
load_gcp_btn = tk.Button(
|
||||
cloud_tab,
|
||||
text="Load from gcp.cfg",
|
||||
command=_on_load_gcp_cfg,
|
||||
font=("SF Pro Text", 10),
|
||||
)
|
||||
load_gcp_btn.grid(
|
||||
row=cloud_row + 1, column=0, columnspan=2, sticky="w", padx=8, pady=(6, 2)
|
||||
)
|
||||
|
||||
db_row = 0
|
||||
db_row = _add_row(
|
||||
db_tab, db_row, "Namespace", "kubernetes.namespace", readonly=True
|
||||
@ -1815,7 +1845,24 @@ class ClusterScreenMixin:
|
||||
self._prod_preview_text_widget = None
|
||||
self._prod_logs_text_widget = None
|
||||
|
||||
gcp = getattr(self, "_gcp_cfg_values", {}) or {}
|
||||
if gcp.get("PROJECT_ID"):
|
||||
self.prod_form_vars["cloud.projectId"].set(gcp["PROJECT_ID"])
|
||||
if gcp.get("REGION") and self.prod_form_vars["cloud.region"].get() in ("", "us-central1"):
|
||||
self.prod_form_vars["cloud.region"].set(gcp["REGION"])
|
||||
|
||||
try:
|
||||
saved_yaml = (
|
||||
(getattr(self, "prole_cfg_data", None) or {})
|
||||
.get("Prod Cluster (k8s)", {})
|
||||
.get("PRODUCTION_CONFIG_YAML", "")
|
||||
.strip()
|
||||
)
|
||||
if saved_yaml:
|
||||
import yaml as _yaml
|
||||
saved_doc = _yaml.safe_load(saved_yaml) or {}
|
||||
restored = build_config(saved_doc)
|
||||
self.prod_config_api.put_prod_config(to_api_payload(restored))
|
||||
payload = self.prod_config_api.get_prod_config()
|
||||
self._prod_apply_payload_to_vars(payload)
|
||||
except Exception:
|
||||
@ -2199,7 +2246,7 @@ class ClusterScreenMixin:
|
||||
|
||||
if env_key == "prod":
|
||||
self.prole_cfg_data["Prod Cluster (k8s)"][
|
||||
"STAGING_DIRECTORY"
|
||||
"ARTIFACTS_DIR"
|
||||
] = self.prod_artifacts_path.get()
|
||||
try:
|
||||
prod_payload = self._prod_payload_from_vars()
|
||||
|
||||
@ -13,7 +13,7 @@ from knoe.core.env import PROJECT_ROOT, _normalize_cluster_env, _parse_bool
|
||||
|
||||
|
||||
class GitOpsScreenMixin:
|
||||
"""GitOps / Gitea deployment screen."""
|
||||
"""GitOps deployment screen."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GitOps provider choice screen
|
||||
@ -120,7 +120,7 @@ class GitOpsScreenMixin:
|
||||
anchor="ne",
|
||||
)
|
||||
|
||||
self._render_title("GitOps / Gitea", y=150)
|
||||
self._render_title("GitOps", y=150)
|
||||
|
||||
description = (
|
||||
"Deploys self-hosted GitOps (Gitea) via etc/init_gitea.sh.\n"
|
||||
@ -171,7 +171,16 @@ class GitOpsScreenMixin:
|
||||
self._canvas_items.append(ns_window)
|
||||
self._overlay_widgets.append(ns_entry)
|
||||
|
||||
y = 360
|
||||
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(
|
||||
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(
|
||||
@ -275,6 +284,11 @@ class GitOpsScreenMixin:
|
||||
else:
|
||||
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
|
||||
self._gitops_console.write(f"Node selector: {node_selector}\n")
|
||||
|
||||
cfg_path = None
|
||||
try:
|
||||
|
||||
@ -6,6 +6,7 @@ from pathlib import Path
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe import config as inst_config
|
||||
from knoe.core.topology import cleanup_topology_collectors
|
||||
from knoe.core.env import get_resource_path, _deployment_mode_from_env
|
||||
|
||||
|
||||
@ -582,6 +583,13 @@ class NavigationMixin:
|
||||
if hasattr(self, "_validate_and_save_cluster_nodes_policy"):
|
||||
if not self._validate_and_save_cluster_nodes_policy(policy):
|
||||
return
|
||||
try:
|
||||
env_key = self._cluster_env_key()
|
||||
cleanup_topology_collectors(
|
||||
kubectl_base_cmd=self._kubectl_base_cmd(env_key),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
self.show_page("common_services")
|
||||
return
|
||||
|
||||
|
||||
@ -62,31 +62,39 @@ class SupabaseScreenMixin:
|
||||
self._canvas_items.append(enable_window)
|
||||
self._overlay_widgets.append(enable_cb)
|
||||
|
||||
y = 280
|
||||
node_label = ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
y,
|
||||
"Node selector:",
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12),
|
||||
)
|
||||
self._canvas_items.append(node_label)
|
||||
node_entry = tk.Entry(
|
||||
self.bg_canvas,
|
||||
textvariable=self.supabase_pv_node,
|
||||
font=("SF Pro Text", 11),
|
||||
width=38,
|
||||
relief="solid",
|
||||
bd=1,
|
||||
)
|
||||
node_entry_window = self.bg_canvas.create_window(
|
||||
190, y - 2, window=node_entry, anchor="nw"
|
||||
)
|
||||
self._canvas_items.append(node_entry_window)
|
||||
self._overlay_widgets.append(node_entry)
|
||||
|
||||
y += 36
|
||||
node_selector_var = getattr(self, "supabase_pv_node", None)
|
||||
if hasattr(self, "_render_k3s_node_selector") and node_selector_var is not None:
|
||||
y = self._render_k3s_node_selector(
|
||||
y=280,
|
||||
target_var=node_selector_var,
|
||||
title="Node selector:",
|
||||
)
|
||||
else:
|
||||
y = 280
|
||||
if y == 280:
|
||||
node_label = ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
y,
|
||||
"Node selector:",
|
||||
fill="black",
|
||||
font=("SF Pro Text", 12),
|
||||
)
|
||||
self._canvas_items.append(node_label)
|
||||
node_entry = tk.Entry(
|
||||
self.bg_canvas,
|
||||
textvariable=self.supabase_pv_node,
|
||||
font=("SF Pro Text", 11),
|
||||
width=38,
|
||||
relief="solid",
|
||||
bd=1,
|
||||
)
|
||||
node_entry_window = self.bg_canvas.create_window(
|
||||
190, y - 2, window=node_entry, anchor="nw"
|
||||
)
|
||||
self._canvas_items.append(node_entry_window)
|
||||
self._overlay_widgets.append(node_entry)
|
||||
y += 36
|
||||
storage_label = ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
|
||||
@ -1 +1 @@
|
||||
158
|
||||
159
|
||||
@ -1,53 +1,59 @@
|
||||
Network Discovery Summary:
|
||||
Primary Router: 10.0.0.1 (eero_5d:50:f2)
|
||||
DNS Servers: 10.0.0.4, 10.0.0.5, 100.100.100.100
|
||||
DNS Servers: 100.100.100.100, 10.0.0.4, 10.0.0.5
|
||||
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.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.35\ [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [], Services: []
|
||||
- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 80, 2049, 445, 5900, 88, 389, 636], Services: ['SSH', 'NFS', 'VNC', 'Web Server', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.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.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443], Services: ['DNS', 'SSH', 'Web Server']
|
||||
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.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.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): 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], Services: ['SSH']
|
||||
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [443], Services: ['Web Server']
|
||||
- 10.0.0.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): Ports [], Services: []
|
||||
- 10.0.0.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.37 [54:07:7d:22:c0:b2] (Netgear_22:c0:b2): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.45 [f8:b4:6a:30:a4:1f] (HewlettPacka_30:a4:1f): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
|
||||
- 10.0.0.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [], Services: []
|
||||
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [], Services: []
|
||||
- 10.0.0.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.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.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.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
|
||||
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [2049], Services: ['NFS']
|
||||
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
|
||||
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.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.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.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
|
||||
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): Ports [], Services: []
|
||||
- 10.0.0.123 [3c:ef:8c:96:e3:3c] (ZhejiangDahu_96:e3:3c): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.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.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [], Services: []
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 53, 445, 5900, 88], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.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.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): 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.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.196 [60:5f:8d:88:08:f2] (eero_88:08:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.204 [50:eb:f6:56:f3:26] (ASUSTekCOMPU_56:f3:26): Ports [22, 3389, 11434], Services: ['SSH', 'RDP (Windows)', 'Ollama']
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.1\ [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
|
||||
- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
|
||||
Ollama Instances found at: 10.0.0.35, 10.0.0.204
|
||||
|
||||
Ansible Inventory Summary:
|
||||
|
||||
@ -1154,6 +1154,10 @@ setup_knoe_db_for_supabase() {
|
||||
CREATE SCHEMA IF NOT EXISTS knoe AUTHORIZATION knoe;
|
||||
CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_auth_admin;
|
||||
CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION supabase_storage_admin;
|
||||
-- Idempotent: if storage schema pre-existed with a different owner (e.g. postgres),
|
||||
-- CREATE SCHEMA IF NOT EXISTS is a no-op and ownership is NOT transferred.
|
||||
-- Explicitly grant so supabase_storage_admin can create its migration tables there.
|
||||
GRANT USAGE, CREATE ON SCHEMA storage TO supabase_storage_admin;
|
||||
CREATE SCHEMA IF NOT EXISTS graphql_public;
|
||||
CREATE SCHEMA IF NOT EXISTS _realtime;
|
||||
ALTER SCHEMA _realtime OWNER TO postgres;
|
||||
@ -1298,6 +1302,27 @@ helm_render_values() {
|
||||
export DB_PASSWORD="$_live_pg_pw"
|
||||
fi
|
||||
|
||||
# Resolve Garage S3 credentials from the live k8s secret so the renderer
|
||||
# always has concrete values regardless of prole.cfg state.
|
||||
if [[ -z "${GARAGE_S3_KEY_ID:-}" ]]; then
|
||||
local _live_garage_key_id
|
||||
_live_garage_key_id=$(kubectl get secret knoe-db-barman-s3 \
|
||||
-n "${DATABASE_NAMESPACE:-knoe-db}" \
|
||||
-o jsonpath='{.data.ACCESS_KEY_ID}' 2>/dev/null | base64 --decode 2>/dev/null || true)
|
||||
if [[ -n "$_live_garage_key_id" ]]; then
|
||||
export GARAGE_S3_KEY_ID="$_live_garage_key_id"
|
||||
fi
|
||||
fi
|
||||
if [[ -z "${GARAGE_S3_ACCESS_KEY:-}" ]]; then
|
||||
local _live_garage_secret
|
||||
_live_garage_secret=$(kubectl get secret knoe-db-barman-s3 \
|
||||
-n "${DATABASE_NAMESPACE:-knoe-db}" \
|
||||
-o jsonpath='{.data.SECRET_ACCESS_KEY}' 2>/dev/null | base64 --decode 2>/dev/null || true)
|
||||
if [[ -n "$_live_garage_secret" ]]; then
|
||||
export GARAGE_S3_ACCESS_KEY="$_live_garage_secret"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always regenerate — never reuse stale cached values from a previous deploy attempt
|
||||
rm -f "$PROJECT_ROOT/supabase/helm/generated/values.generated.json"
|
||||
|
||||
|
||||
@ -91,6 +91,7 @@ spec:
|
||||
imagePullPolicy: {{ .Values.image.storage.pullPolicy }}
|
||||
env:
|
||||
{{- $storageEnv := (.Values.environment.storage | default dict) }}
|
||||
{{- $storageBackend := (default (ternary "s3" "file" .Values.deployment.minio.enabled) (get $storageEnv "STORAGE_BACKEND")) }}
|
||||
{{- range $key, $value := $storageEnv }}
|
||||
{{- if and (ne $key "DB_HOST") (ne $key "DB_PORT") (ne $key "DB_SSL") }}
|
||||
- name: {{ $key }}
|
||||
@ -138,7 +139,7 @@ spec:
|
||||
key: database
|
||||
{{- end }}
|
||||
- name: DATABASE_URL
|
||||
value: $(DB_DRIVER)://$(DB_USER):$(DB_PASSWORD_ENC)@{{ include "supabase.database.host" . }}:{{ include "supabase.database.port" . }}/{{ include "supabase.database.name" . }}?search_path=auth&sslmode={{ include "supabase.database.ssl" . }}
|
||||
value: $(DB_DRIVER)://$(DB_USER):$(DB_PASSWORD_ENC)@{{ include "supabase.database.host" . }}:{{ include "supabase.database.port" . }}/{{ include "supabase.database.name" . }}?search_path=storage,public&sslmode={{ include "supabase.database.ssl" . }}
|
||||
- name: PGRST_JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@ -196,7 +197,7 @@ spec:
|
||||
|
||||
{{- if not (hasKey $storageEnv "STORAGE_BACKEND") }}
|
||||
- name: STORAGE_BACKEND
|
||||
value: {{ if .Values.deployment.minio.enabled }}s3{{ else }}file{{ end }}
|
||||
value: {{ $storageBackend }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.deployment.minio.enabled }}
|
||||
@ -212,25 +213,27 @@ spec:
|
||||
- name: GLOBAL_S3_FORCE_PATH_STYLE
|
||||
value: "true"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq $storageBackend "s3" }}
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
{{- if .Values.secret.minio.secretRef }}
|
||||
name: {{ .Values.secret.minio.secretRef }}
|
||||
key: {{ .Values.secret.minio.secretRefKey.user | default "user" }}
|
||||
{{- if .Values.secret.s3.secretRef }}
|
||||
name: {{ .Values.secret.s3.secretRef }}
|
||||
key: {{ .Values.secret.s3.secretRefKey.keyId | default "keyId" }}
|
||||
{{- else }}
|
||||
name: {{ include "supabase.secret.minio" . }}
|
||||
key: user
|
||||
name: {{ include "supabase.secret.s3" . }}
|
||||
key: keyId
|
||||
{{- end }}
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
{{- if .Values.secret.s3.secretRef }}
|
||||
name: {{ .Values.secret.s3.secretRef }}
|
||||
key: {{ .Values.secret.s3.secretRefKey.password | default "password" }}
|
||||
key: {{ .Values.secret.s3.secretRefKey.accessKey | default "accessKey" }}
|
||||
{{- else }}
|
||||
name: {{ include "supabase.secret.minio" . }}
|
||||
key: password
|
||||
name: {{ include "supabase.secret.s3" . }}
|
||||
key: accessKey
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.deployment.storage.livenessProbe }}
|
||||
|
||||
@ -340,17 +340,62 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
if _is_placeholder(supabase_storage_class):
|
||||
supabase_storage_class = "merlin-local-iscsi-d002"
|
||||
|
||||
storage_backend = _first(
|
||||
os.environ.get("SUPABASE_STORAGE_BACKEND", ""),
|
||||
os.environ.get("STORAGE_BACKEND", ""),
|
||||
_cfg_get(cfg, "Supabase", "STORAGE_BACKEND"),
|
||||
_cfg_get(cfg, "Global", "STORAGE_BACKEND"),
|
||||
default="s3",
|
||||
).strip().lower()
|
||||
if _is_placeholder(storage_backend):
|
||||
storage_backend = ""
|
||||
|
||||
global_s3_endpoint = _first(
|
||||
os.environ.get("SUPABASE_GLOBAL_S3_ENDPOINT", ""),
|
||||
os.environ.get("GLOBAL_S3_ENDPOINT", ""),
|
||||
_cfg_get(cfg, "Supabase", "GLOBAL_S3_ENDPOINT"),
|
||||
_cfg_get(cfg, "Global", "GLOBAL_S3_ENDPOINT"),
|
||||
default="http://garage.knoe-system.svc.cluster.local:3900",
|
||||
).strip()
|
||||
if _is_placeholder(global_s3_endpoint):
|
||||
global_s3_endpoint = ""
|
||||
|
||||
garage_s3_key_id = _first(
|
||||
os.environ.get("GARAGE_S3_KEY_ID", ""),
|
||||
_cfg_get(cfg, "Supabase", "GARAGE_S3_KEY_ID"),
|
||||
_cfg_get(cfg, "Global", "GARAGE_S3_KEY_ID"),
|
||||
default="",
|
||||
).strip()
|
||||
garage_s3_access_key = _first(
|
||||
os.environ.get("GARAGE_S3_ACCESS_KEY", ""),
|
||||
_cfg_get(cfg, "Supabase", "GARAGE_S3_ACCESS_KEY"),
|
||||
_cfg_get(cfg, "Global", "GARAGE_S3_ACCESS_KEY"),
|
||||
default="",
|
||||
).strip()
|
||||
if _is_placeholder(garage_s3_key_id):
|
||||
garage_s3_key_id = ""
|
||||
if _is_placeholder(garage_s3_access_key):
|
||||
garage_s3_access_key = ""
|
||||
|
||||
use_garage_s3 = storage_backend == "s3" and "garage" in global_s3_endpoint.lower()
|
||||
if use_garage_s3 and (not garage_s3_key_id or not garage_s3_access_key):
|
||||
raise SystemExit(
|
||||
"Garage S3 credentials are required for Supabase storage. "
|
||||
"Set GARAGE_S3_KEY_ID and GARAGE_S3_ACCESS_KEY in [Supabase] or [Global], "
|
||||
"or via environment variables."
|
||||
)
|
||||
|
||||
overlay: dict[str, Any] = {
|
||||
"nameOverride": "supabase",
|
||||
"fullnameOverride": "supabase",
|
||||
"deployment": {
|
||||
"db": {"enabled": False},
|
||||
"functions": {"enabled": True},
|
||||
"vector": {"enabled": True},
|
||||
"kong": {"enabled": True},
|
||||
"storage": {"enabled": True},
|
||||
"minio": {"enabled": True},
|
||||
"imgproxy": {"enabled": True},
|
||||
"functions": {"enabled": True, "fullnameOverride": "supabase-functions"},
|
||||
"vector": {"enabled": True, "fullnameOverride": "supabase-vector"},
|
||||
"kong": {"enabled": True, "fullnameOverride": "supabase-kong"},
|
||||
"storage": {"enabled": True, "fullnameOverride": "supabase-storage"},
|
||||
"minio": {"enabled": not use_garage_s3, "fullnameOverride": "supabase-minio"},
|
||||
"imgproxy": {"enabled": True, "fullnameOverride": "supabase-imgproxy"},
|
||||
# Explicit fullnameOverride per component strips the chart name
|
||||
# from pod names (avoids 'supabase-prole-supabase-<component>').
|
||||
"analytics": {"enabled": analytics_enabled, "fullnameOverride": "supabase-analytics"},
|
||||
@ -385,6 +430,7 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
|
||||
"secret": {
|
||||
"db": {"password": db_password, "database": db_name},
|
||||
"jwt": {"secret": jwt_secret, "anonKey": anon_key, "serviceKey": service_key},
|
||||
"s3": {"keyId": garage_s3_key_id, "accessKey": garage_s3_access_key},
|
||||
},
|
||||
"ingress": {
|
||||
"enabled": True,
|
||||
@ -468,7 +514,7 @@ def render(args: argparse.Namespace) -> None:
|
||||
cmd = [
|
||||
"helm",
|
||||
"template",
|
||||
"prole-supabase",
|
||||
"supabase",
|
||||
str(REPO_ROOT / "supabase" / "helm" / "prole-supabase"),
|
||||
"-n",
|
||||
meta["supabase_namespace"],
|
||||
|
||||
@ -16,6 +16,7 @@ from knoe.config import (
|
||||
_parse_bool as cfg_parse_bool,
|
||||
_expand_path as cfg_expand_path,
|
||||
_expand_cfg_value as cfg_expand_cfg_value,
|
||||
_preserve_cfg_expr_for_persistence,
|
||||
_collect_cfg_vars as cfg_collect_cfg_vars,
|
||||
_filter_cfg_values_for_persistence,
|
||||
_normalize_cfg_value_for_persistence,
|
||||
@ -324,6 +325,20 @@ class TestExpandHelpers:
|
||||
assert cfg_expand_cfg_value("", {}) == ""
|
||||
assert cfg_expand_cfg_value(None, {}) is None
|
||||
|
||||
def test_preserve_cfg_expr_for_persistence_keeps_home_token(self):
|
||||
value = _preserve_cfg_expr_for_persistence(
|
||||
"${HOME}/dev/prole/data/staging",
|
||||
"/Users/chrisfu/dev/prole/data/staging",
|
||||
)
|
||||
assert value == "${HOME}/dev/prole/data/staging"
|
||||
|
||||
def test_preserve_cfg_expr_for_persistence_uses_expanded_without_tokens(self):
|
||||
value = _preserve_cfg_expr_for_persistence(
|
||||
"/Users/chrisfu/dev/prole/data/staging",
|
||||
"/Users/chrisfu/dev/prole/data/staging",
|
||||
)
|
||||
assert value == "/Users/chrisfu/dev/prole/data/staging"
|
||||
|
||||
|
||||
# ===== _collect_cfg_vars =====
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user