feat(k3s): add pg.prole.org as CNPG postgres endpoint with split-horizon DNS

- knoe-db.yaml: switch to CNPG-managed TLS cert with serverAltDNSNames
  (pg.prole.org + knoe-db-rw cluster service) — removes static serverTLSSecret/serverCASecret
- dns.yml: add pg.prole.org A record to prole_k3s_dns_records (10.0.0.3, 10.0.0.6)
  for Ansible-managed split-horizon DNS via Samba AD DC
- k3s.cfg: align KNOE_HOME paths to ~/dev/prole, add PROLE_KDC_* vars, remove
  hardcoded KUBECTL_CONTEXT (kubeconfig current-context is authoritative)
- prod.cfg: add PROLE_KDC_STORAGE_CLASS = prole-iscsi
- onepassword.py: skip vault check gracefully when no 1Password session active (non-TTY)
- CLAUDE.md: document production postgres connection string and DNS/CA cert ops

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-05-14 22:46:46 -07:00
parent f34d10908a
commit e014fd5b71
6 changed files with 74 additions and 15 deletions

View File

@ -206,3 +206,36 @@ Quick reference. Each entry links to the master index where context, owner, and
**Closed in 2026-04-29 stabilization session:** Garage on DB cluster removed; cluster pods migrated to `cnpg-backup-sa` via CNPG v1.29.0 `spec.serviceAccountName`; both operators restarted clean.
**Closed 2026-05-01:** R4 — installer env-contamination guard now live in `deploy.sh` (strict) + `install.sh` (informational notice). Helper at [`etc/preflight_kubecontext.sh`](etc/preflight_kubecontext.sh).
---
## k3s CNPG database (production)
The prole.org k3s CNPG cluster is this project's **production** PostgreSQL database.
```bash
psql "host=pg.prole.org port=5432 user=chrisfu dbname=postgres sslmode=verify-full sslrootcert=$HOME/.knoe/knoe-db-ca.crt"
```
| Detail | Value |
|---|---|
| External hostname | `pg.prole.org:5432` |
| Internal service | `knoe-db-rw.knoe-db.svc.cluster.local:5432` |
| kubectl context | `prole-service-cluster` |
| Namespace | `knoe-db` |
| CA cert | `~/.knoe/knoe-db-ca.crt` |
| sslmode | `verify-full` |
**DNS**: `pg.prole.org` resolves internally via split-horizon DNS on `myrddin.prole.org` (Samba AD DC) to the k3s ServiceLB node IPs (`10.0.0.3`, `10.0.0.6`). External DNS resolves to the public IP — do not access from outside the LAN without a VPN.
**CA cert refresh** (after CNPG cert rotation):
```bash
kubectl --context=prole-service-cluster -n knoe-db \
get secret knoe-db-ca -o jsonpath='{.data.ca\.crt}' | base64 -d > ~/.knoe/knoe-db-ca.crt
```
**Node mobility**: to move the postgres LoadBalancer to a different node, update the Samba DNS A records:
```bash
ssh myrddin.prole.org "sudo samba-tool dns delete myrddin.prole.org prole.org pg A <OLD_IP> -U Administrator"
ssh myrddin.prole.org "sudo samba-tool dns add myrddin.prole.org prole.org pg A <NEW_IP> -U Administrator"
```

View File

@ -154,13 +154,17 @@ OP_KDC_GUEST_PASSWORD_REF =
K3S_SERVER = https://myrddin.prole.org:6443
K3S_TOKEN = ${KNOE_SECRET:v1:-cslrAhG8WhxxJLY:vAx5MGOBcU1NBMjf4U-3y1djMDmwwOjiWkrutxPa4Li5P8RBjCBhEOCEdGP2CiPJJ6UQK-ietX-mu_5nO3yNNjpwDququT4U6lWHPEInSvUNH6ImU-HzPvm_diL2FwJtZm3sY5HgecIy2dwXB_vLnYaA_7VXt9zf0T70rw==}
KNOE_DB_USER = root
KNOE_HOME = $HOME/dev/knoe
KNOE_HOME = $HOME/dev/prole
KNOE_IMAGE_REGISTRY = registry.prole.org
PROLE_ENABLE_LOCAL_REGISTRY = true
MONITORING_STORAGE_CLASS = merlin-local-iscsi-prometheus
OPENTOFU_URL = http://127.0.0.1:8080
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = ${KNOE_SECRET:v1:-cslrAhG8WhxxJLY:vAx5MGOBcU1NBMjf4U-3y1djMDmwwOjiWkrutxPa4Li5P8RBjCBhEOCEdGP2CiPJJ6UQK-ietX-mu_5nO3yNNjpwDququT4U6lWHPEInSvUNH6ImU-HzPvm_diL2FwJtZm3sY5HgecIy2dwXB_vLnYaA_7VXt9zf0T70rw==}
PROLE_KDC_TRUST_REALM = PROLE.ORG
PROLE_KDC_TRUST_ADMIN = administrator@PROLE.ORG
PROLE_KDC_STORAGE_CLASS = prole-iscsi
PROTECTED_DB_HOSTS = db.prole.org
PROTECTED_GIT_HOSTS = git.prole.org
REDIS_HOST = redis-master.knoe-system.svc.cluster.local
@ -203,11 +207,11 @@ PORT_FORWARD_K3S_MAPPING_8 = id=grafana;namespace=monitoring;target=svc/promethe
PORT_FORWARD_K3S_MAPPING_9 = id=gitea-http;namespace=gitea;target=svc/gitea-http;address=0.0.0.0;hostPort=13000;servicePort=3000;protocol=TCP;description=Gitea Web
[System Environment]
KNOE_CONF = $HOME/dev/knoe/conf
KNOE_HOME = $HOME/dev/knoe
KNOE_SERVICE = $HOME/dev/knoe/etc
PROLE_DATA = $HOME/dev/knoe/data
PROLE_LOGS = $HOME/dev/knoe/logs
KNOE_CONF = $HOME/dev/prole/conf
KNOE_HOME = $HOME/dev/prole
KNOE_SERVICE = $HOME/dev/prole/etc
PROLE_DATA = $HOME/dev/prole/data
PROLE_LOGS = $HOME/dev/prole/logs
[Monitoring]
MONITORING_STORAGE_CLASS = merlin-local-iscsi-prometheus
@ -249,7 +253,8 @@ CLUSTER_ENV = knoe-service-cluster
DISPLAY_NAME = knoe-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${KNOE_SECRET:v1:-cslrAhG8WhxxJLY:vAx5MGOBcU1NBMjf4U-3y1djMDmwwOjiWkrutxPa4Li5P8RBjCBhEOCEdGP2CiPJJ6UQK-ietX-mu_5nO3yNNjpwDququT4U6lWHPEInSvUNH6ImU-HzPvm_diL2FwJtZm3sY5HgecIy2dwXB_vLnYaA_7VXt9zf0T70rw==}
KUBECTL_CONTEXT = prole-service-cluster
; k3s kubeconfig current-context is authoritative — do not set KUBECTL_CONTEXT here
; KUBECTL_CONTEXT = prole-service-cluster
MODE = k3s
PIPELINE_URL = http://127.0.0.1:8080

View File

@ -128,6 +128,8 @@ GOOGLE_CLIENT_ID =
GOOGLE_CLIENT_SECRET =
PROLE_KDC_TRUST_REALM = PROLE.ORG
PROLE_KDC_TRUST_ADMIN = administrator@PROLE.ORG
; k3s has no default StorageClass; use the static prole-iscsi SC (backed by /synology/d001/kdc)
PROLE_KDC_STORAGE_CLASS = prole-iscsi
SERVICE_INGRESS_TLS_ENABLED = 1
MONITORING_RELEASE = prometheus
@ -157,11 +159,11 @@ PORT_FORWARD_K3D_MAPPING_8 = id=grafana;namespace=monitoring;target=svc/kps-graf
PORT_FORWARD_K3D_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]
KNOE_CONF = /Users/chrisfu/dev/knoe/conf
PROLE_DATA = /Users/chrisfu/dev/knoe/data
KNOE_HOME = /Users/chrisfu/dev/knoe
PROLE_LOGS = /Users/chrisfu/dev/knoe/logs
KNOE_SERVICE = /Users/chrisfu/dev/knoe/etc
KNOE_CONF = /home/chrisfu/dev/prole/conf
PROLE_DATA = /home/chrisfu/dev/prole/data
KNOE_HOME = /home/chrisfu/dev/prole
PROLE_LOGS = /home/chrisfu/dev/prole/logs
KNOE_SERVICE = /home/chrisfu/dev/prole/etc
[Monitoring]
; No configuration values captured yet for this section.

View File

@ -206,6 +206,9 @@ prole_k3s_dns_records:
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: supabase.internal.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
# Direct postgres access (bypasses Traefik — port 5432 via CNPG ServiceLB)
- fqdn: pg.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
prole_k3s_cname_records:
- fqdn: registry.prole.org

View File

@ -92,8 +92,9 @@ spec:
- GRANT anon TO authenticator;
certificates:
serverTLSSecret: knoe-db-tls
serverCASecret: knoe-db-ca
serverAltDNSNames:
- pg.prole.org
- knoe-db-rw.knoe-db.svc.cluster.local
enableSuperuserAccess: true
env:

View File

@ -68,8 +68,23 @@ def ensure_op_signed_in() -> None:
subprocess.run(["op", "signin"], check=True)
def _op_session_active() -> bool:
"""Return True if op has an active session."""
r = subprocess.run(["op", "whoami"], capture_output=True, text=True)
return r.returncode == 0
def ensure_knoey_vault() -> None:
"""Create the 'knoey' vault if it does not already exist."""
"""Create the 'knoey' vault if it does not already exist.
Skips silently when running non-interactively without an op session.
"""
import sys
if not _op_session_active():
if not sys.stdin.isatty():
print("[WARN] No active 1Password session — skipping vault check.", flush=True)
return
result = _op("vault", "list", "--format", "json", check=True)
try:
vaults = json.loads(result.stdout or "[]")