From 0a72ee91ea7444835eda81383ccc1006da225bc2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 29 Apr 2026 18:06:44 +0200 Subject: [PATCH] feat(WP-0018/R6): Helm chart and runbook for Railiance01 deployment Helm chart at deploy/helm/inter-hub/ with Deployment, Service, Ingress (Traefik + letsencrypt-prod), and migration init container. Runbook at deploy/railiance/RUNBOOK.md with build, push, rotate, rollback procedures. Co-Authored-By: Claude Sonnet 4.6 --- deploy/helm/inter-hub/Chart.yaml | 6 + .../helm/inter-hub/templates/deployment.yaml | 52 ++++++++ deploy/helm/inter-hub/templates/ingress.yaml | 30 +++++ deploy/helm/inter-hub/templates/service.yaml | 15 +++ deploy/helm/inter-hub/values.yaml | 33 +++++ deploy/railiance/RUNBOOK.md | 124 ++++++++++++++++++ 6 files changed, 260 insertions(+) create mode 100644 deploy/helm/inter-hub/Chart.yaml create mode 100644 deploy/helm/inter-hub/templates/deployment.yaml create mode 100644 deploy/helm/inter-hub/templates/ingress.yaml create mode 100644 deploy/helm/inter-hub/templates/service.yaml create mode 100644 deploy/helm/inter-hub/values.yaml create mode 100644 deploy/railiance/RUNBOOK.md diff --git a/deploy/helm/inter-hub/Chart.yaml b/deploy/helm/inter-hub/Chart.yaml new file mode 100644 index 0000000..85f90c7 --- /dev/null +++ b/deploy/helm/inter-hub/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: inter-hub +description: Interaction Hub Framework — reference implementation +type: application +version: 0.1.0 +appVersion: "0.2.0-alpha.1" diff --git a/deploy/helm/inter-hub/templates/deployment.yaml b/deploy/helm/inter-hub/templates/deployment.yaml new file mode 100644 index 0000000..8afddb7 --- /dev/null +++ b/deploy/helm/inter-hub/templates/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Release.Name }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }} + spec: + initContainers: + {{- if .Values.runMigrations }} + - name: migrate + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + command: ["/bin/App", "migrate"] + envFrom: + - secretRef: + name: {{ .Values.envFrom.secretRef }} + {{- end }} + containers: + - name: inter-hub + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 8000 + protocol: TCP + envFrom: + - secretRef: + name: {{ .Values.envFrom.secretRef }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + readinessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 30 + failureThreshold: 3 diff --git a/deploy/helm/inter-hub/templates/ingress.yaml b/deploy/helm/inter-hub/templates/ingress.yaml new file mode 100644 index 0000000..61ca4e2 --- /dev/null +++ b/deploy/helm/inter-hub/templates/ingress.yaml @@ -0,0 +1,30 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Release.Name }} + annotations: + {{- toYaml .Values.ingress.annotations | nindent 4 }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls }} + tls: + - hosts: + - {{ .Values.ingress.host }} + secretName: {{ .Release.Name }}-tls + {{- end }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/deploy/helm/inter-hub/templates/service.yaml b/deploy/helm/inter-hub/templates/service.yaml new file mode 100644 index 0000000..8ac6b06 --- /dev/null +++ b/deploy/helm/inter-hub/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Release.Name }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ .Release.Name }} + ports: + - port: {{ .Values.service.port }} + targetPort: 8000 + protocol: TCP diff --git a/deploy/helm/inter-hub/values.yaml b/deploy/helm/inter-hub/values.yaml new file mode 100644 index 0000000..1d25474 --- /dev/null +++ b/deploy/helm/inter-hub/values.yaml @@ -0,0 +1,33 @@ +replicaCount: 1 + +image: + repository: 92.205.130.254:32166/coulomb/inter-hub + tag: "latest" + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 8000 + +ingress: + enabled: true + className: traefik + host: hub.coulomb.social + tls: true + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + +resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1000m" + +envFrom: + secretRef: inter-hub-env + +runMigrations: true diff --git a/deploy/railiance/RUNBOOK.md b/deploy/railiance/RUNBOOK.md new file mode 100644 index 0000000..76d6bbf --- /dev/null +++ b/deploy/railiance/RUNBOOK.md @@ -0,0 +1,124 @@ +# inter-hub on Railiance01 — Runbook + +## Architecture + +- **Cluster:** Railiance01 (K3s, 92.205.62.239) +- **Namespace:** `inter-hub` +- **Image registry:** `92.205.130.254:32166/coulomb/inter-hub:` (Gitea on CoulombCore) +- **Database:** CloudNativePG cluster `net-kingdom-pg` in `databases` namespace + - RW endpoint: `net-kingdom-pg-rw.databases.svc.cluster.local:5432` + - Database: `interhub`, User: `interhub` +- **Ingress:** Traefik → `hub.coulomb.social` (TLS via letsencrypt-prod) +- **Secrets:** `inter-hub-env` Secret in `inter-hub` namespace + +## Deployment + +```bash +# From workstation (image already built and pushed): +helm upgrade --install inter-hub deploy/helm/inter-hub \ + --namespace inter-hub --create-namespace \ + --set image.tag= +``` + +## Image Build (on haskelseed) + +```bash +ssh root@192.168.178.135 +cd /root/inter-hub +git pull # (requires Gitea auth — see Gitea credentials section) +nix build .#docker --accept-flake-config --option lazy-trees false +# Push to Gitea registry: +skopeo copy docker-archive:result \ + docker://92.205.130.254:32166/coulomb/inter-hub: \ + --dest-creds "tegwick:" \ + --dest-tls-verify=false +``` + +## Gitea Registry Credentials + +The Gitea token for registry push is stored in `~/.config/tea/config.yml` on the +workstation. If the token has expired, generate a new one: +1. Go to http://92.205.130.254:32166 → Settings → Applications → Generate new token +2. Scope: `package:write` +3. Update `~/.config/tea/config.yml` on the workstation +4. Update the `GITEA_TOKEN` in any CI/CD secrets + +## Database Migration + +IHP migrations run automatically on startup via the init container in the Deployment. +To run migrations manually: + +```bash +kubectl exec -n inter-hub deploy/inter-hub -- /bin/App migrate +``` + +To check migration status: +```bash +kubectl exec -n databases net-kingdom-pg-1 -- psql -U postgres interhub -c "\dt" +``` + +## Logs + +```bash +kubectl logs -n inter-hub -l app=inter-hub --tail=100 -f +# Previous pod logs: +kubectl logs -n inter-hub -l app=inter-hub --previous --tail=50 +``` + +## Restart / Rollback + +```bash +# Restart: +kubectl rollout restart deployment/inter-hub -n inter-hub +kubectl rollout status deployment/inter-hub -n inter-hub + +# Rollback to previous image: +kubectl rollout undo deployment/inter-hub -n inter-hub + +# Rollback to specific version: +helm rollback inter-hub 1 --namespace inter-hub +``` + +## Secret Rotation + +To rotate the session secret: +```bash +kubectl create secret generic inter-hub-env \ + --namespace inter-hub \ + --from-literal=DATABASE_URL='...' \ + --from-literal=IHP_SESSION_SECRET='' \ + --from-literal=IHP_BASEURL='https://hub.coulomb.social' \ + --from-literal=PORT='8000' \ + --from-literal=IHP_ENV='Production' \ + --dry-run=client -o yaml | kubectl apply -f - +kubectl rollout restart deployment/inter-hub -n inter-hub +``` + +To rotate the database password: +1. Update the password in PostgreSQL (via kubectl exec to the CNPG pod) +2. Update the `inter-hub-env` secret +3. Restart the deployment + +## Smoke Test + +```bash +curl -s https://hub.coulomb.social/ | grep "Inter-Hub" # Landing 200 +curl -s https://hub.coulomb.social/capabilities | grep "Capabilities" +curl -s https://hub.coulomb.social/api/v2/hubs # 401 expected +curl -H "Authorization: Bearer " https://hub.coulomb.social/api/v2/hubs # 200 +``` + +## Database Connection Check + +```bash +kubectl exec -n inter-hub deploy/inter-hub -- \ + /bin/sh -c 'psql $DATABASE_URL -c "SELECT version();"' +``` + +## haskelseed Build VM + +- **Host:** 192.168.178.135 +- **Access:** `ssh root@192.168.178.135` (password in team secrets) +- **Repo:** `/root/inter-hub` (git initialized locally; pull requires Gitea token) +- **Build logs:** `/tmp/nix-build-docker.log` +- **Nix store:** `/dev/sdb1` (100 GB, mounted at `/nix`)