diff --git a/Makefile b/Makefile index fb4d1ba..dc779f7 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,9 @@ FORGEJO_DB_CLUSTER ?= forgejo-db FORGEJO_DB_NAMESPACE ?= databases FORGEJO_RUNNER_MANIFEST ?= manifests/forgejo-runner.yaml FORGEJO_RUNNER_REGISTRATION_SECRET ?= helm/forgejo-runner-registration.sops.yaml +FORGEJO_MAILER_EXTERNAL_SECRET ?= manifests/forgejo-mailer-externalsecret.yaml +PLATFORM_REPO ?= $(HOME)/railiance-platform +ESO_CHART_VERSION ?= 0.16.1 INTER_HUB_IMAGE_REF = $(INTER_HUB_IMAGE_REPOSITORY):$(INTER_HUB_IMAGE_TAG) INTER_HUB_IMAGE_SET_ARG = $(if $(strip $(INTER_HUB_IMAGE_TAG)),--set image.tag=$(INTER_HUB_IMAGE_TAG),) @@ -171,31 +174,56 @@ inter-hub-logs: check-railiance01-kubeconfig ## Tail inter-hub app logs from Rai ##@ Forgejo (forgejo.coulomb.social) -forgejo-dry-run: check-railiance01-kubeconfig check-sops ## helm template render for Forgejo +eso-deploy: check-railiance01-kubeconfig ## Install External Secrets Operator on railiance01 + KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm repo add external-secrets https://charts.external-secrets.io --force-update + KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install external-secrets external-secrets/external-secrets \ + --version $(ESO_CHART_VERSION) \ + --namespace external-secrets --create-namespace \ + --set installCRDs=true \ + --set serviceAccount.create=true \ + --set serviceAccount.name=external-secrets \ + --wait --timeout 5m + +forgejo-openbao-store-deploy: check-railiance01-kubeconfig eso-deploy ## Apply openbao-forgejo ClusterSecretStore + test -d "$(PLATFORM_REPO)/argocd/platform-addons/openbao-secretstore" + KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f "$(PLATFORM_REPO)/argocd/platform-addons/openbao-secretstore/openbao-forgejo.clustersecretstore.yaml" + +forgejo-mailer-es-deploy: check-railiance01-kubeconfig forgejo-openbao-store-deploy ## Apply Forgejo mailer ExternalSecret + KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f $(FORGEJO_MAILER_EXTERNAL_SECRET) + +forgejo-mailer-es-status: check-railiance01-kubeconfig ## Show Forgejo mailer ExternalSecret sync status + KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get externalsecret,secret -n $(FORGEJO_NAMESPACE) forgejo-mailer + +forgejo-dry-run: check-railiance01-kubeconfig ## helm template render for Forgejo KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm template $(FORGEJO_RELEASE) $(FORGEJO_CHART) \ --version $(FORGEJO_CHART_VERSION) \ --namespace $(FORGEJO_NAMESPACE) \ -f $(FORGEJO_VALUES) \ -f $(FORGEJO_REGISTRY_VALUES) \ - -f <(sops -d $(FORGEJO_SECRETS)) + -f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh) -forgejo-server-dry-run: check-railiance01-kubeconfig check-sops ## Helm server dry-run Forgejo upgrade +forgejo-server-dry-run: check-railiance01-kubeconfig ## Helm server dry-run Forgejo upgrade KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \ --version $(FORGEJO_CHART_VERSION) \ --namespace $(FORGEJO_NAMESPACE) --create-namespace \ -f $(FORGEJO_VALUES) \ -f $(FORGEJO_REGISTRY_VALUES) \ - -f <(sops -d $(FORGEJO_SECRETS)) \ + -f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh) \ --dry-run=server --timeout 5m -forgejo-deploy: check-railiance01-kubeconfig check-sops ## Deploy / upgrade Forgejo on railiance01 +forgejo-deploy: check-railiance01-kubeconfig forgejo-mailer-es-deploy ## Deploy / upgrade Forgejo on railiance01 + @test -n "$$(KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get secret forgejo-mailer -n $(FORGEJO_NAMESPACE) -o name 2>/dev/null)" || { \ + echo "Missing secret forgejo-mailer in $(FORGEJO_NAMESPACE)." >&2; \ + echo "Ensure ExternalSecret is Ready and OpenBao role external-secrets-forgejo is configured." >&2; \ + exit 1; \ + } KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm repo add gitea-charts https://dl.gitea.com/charts/ --force-update KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \ --version $(FORGEJO_CHART_VERSION) \ --namespace $(FORGEJO_NAMESPACE) --create-namespace \ -f $(FORGEJO_VALUES) \ -f $(FORGEJO_REGISTRY_VALUES) \ - -f <(sops -d $(FORGEJO_SECRETS)) \ + -f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh) \ --wait --timeout 10m forgejo-ingress-deploy: check-railiance01-kubeconfig ## Apply Forgejo HTTPS ingress @@ -268,4 +296,4 @@ help: ## Show this help /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \ /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST) -.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs help +.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret eso-deploy forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs help diff --git a/docs/forgejo-on-railiance01.md b/docs/forgejo-on-railiance01.md index 222ce6f..ef8c967 100644 --- a/docs/forgejo-on-railiance01.md +++ b/docs/forgejo-on-railiance01.md @@ -104,25 +104,28 @@ gitea: USER: forgejo@coulomb.social ``` -Mailbox password goes in `helm/forgejo-secrets.sops.yaml` (encrypt from template): +Mailbox password is stored in **OpenBao** and delivered by External Secrets: -```yaml -gitea: - config: - mailer: - PASSWD: -``` +| Item | Value | +| --- | --- | +| OpenBao path | `platform/workloads/forgejo/forgejo-mailer` | +| Field | `MAILER_PASSWD` | +| ExternalSecret | `manifests/forgejo-mailer-externalsecret.yaml` | +| Helm consumption | `gitea.additionalConfigFromEnvs` → `GITEA__mailer__PASSWD` | -Deploy path: +Bootstrap (railiance01): ```bash +cd ~/railiance-platform +KUBECONFIG=~/.kube/config-hosteurope make openbao-configure-external-secrets-forgejo + cd ~/railiance-apps -make check-sops +KUBECONFIG=~/.kube/config-hosteurope make forgejo-mailer-es-deploy +KUBECONFIG=~/.kube/config-hosteurope make forgejo-mailer-es-status # expect SecretSynced KUBECONFIG=~/.kube/config-hosteurope make forgejo-deploy -# T06: Admin → user → Send reset password; confirm email delivery ``` -Do not enable `mailer.ENABLED` until the SOPS secret is in place. +Do not put the SMTP password in `helm/forgejo-secrets.sops.yaml`. ## Coexistence with Gitea diff --git a/helm/forgejo-secrets.sops.yaml b/helm/forgejo-secrets.sops.yaml index 62f9ba5..3eb20b3 100644 --- a/helm/forgejo-secrets.sops.yaml +++ b/helm/forgejo-secrets.sops.yaml @@ -1,11 +1,9 @@ gitea: admin: - password: ENC[AES256_GCM,data:D4vjZzWdKb8FgCXcRp+Ndjya1yFqB995,iv:WzOzUMTu4TBcpEgYFkz186CnmSpmmQ+Jqnz8I4ZpVVw=,tag:hHbXEhBGfNhr63mNF/HTBA==,type:str] + password: ENC[AES256_GCM,data:/0h7UHVFAKQbOczZs880ZhroIMYeuaWM,iv:cJu65mLICZHlog65qL/7OWZbMAa+2v+zXt1e8i/veqc=,tag:FJecXS8ImotmwO/avJYQ4A==,type:str] config: database: - PASSWD: ENC[AES256_GCM,data:oHp70axstRWh4YPYeKNpt8a8eejgFjyw6azdhZG1WqA=,iv:B9acXzEmJ2tGzkIkG2X/RsrdSGwzV/dgOI3Hn8/J9is=,tag:WHs/yEIZ7upffCXaOUfPuQ==,type:str] - mailer: - PASSWD: ENC[AES256_GCM,data:W3+t8Hgn4/yPAvVWYa/Gkn4=,iv:NrL0TD3lTQWfVIzxbjJi9FrZrMmI6s4Bpo8zvMKVHLI=,tag:pBvK4wrhOOWUDI2wtg7MIQ==,type:str] + PASSWD: ENC[AES256_GCM,data:80VKXVe/XGiO2ROOjyweHVveYpnn/KnVfnF93IB1QY0=,iv:wAaNx2B1F3yvNliB2ntecPIPLh+lTwWTsLyM70YktCs=,tag:0hzr9dwlz8AyN5uv3AbylA==,type:str] sops: kms: [] gcp_kms: [] @@ -15,14 +13,14 @@ sops: - recipient: age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4 enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsbjJqRG9uOUZiU0RjSnVm - aE9ZaitSdjdjb0RNU2pzNGlsTHV5K2I1MGlBCms2RFRPelA1ODBoVTZidFZnOC9u - ZllVazZDRmZjQzg4L0E3QXpJWDFIUGcKLS0tIFViM0czZDR6dUFTRmZXRUQ0RW9w - aEF0RDMzb0o1YzduZHIxUGNBdVRnOGMKjDu/B39gqkT2JJ/7NGbFqmyUPtGAf3ST - sUNhOgGXQRs6576QSiUEzO8Prio/lnFPM96boMgXv4+Gt2dcPmXyoA== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwaWZ0cldQNDQyNUxJeUdX + ZG1jbS9HVUlzUFNuM3pMdzlLS1BHSGZmb21rCmpGRG9wOVV4bGl3VmtGRk14a1o5 + RzhDUWpPaGozZjh5YWsxdXI4NmdoRzAKLS0tIE5qTG53b2pMRGhlZzNUa3l0ejBy + eE5nRDQ4WWswdmtvNmx1OSsxZVl0eG8K0Njs3CVPwUKVM4rnriJq7EXIUg0HbT5F + qRiSwY8fmFa2nW38mbII1Bna6w6ahGMNdIeGL/u4tlt/Bt+iIAYItw== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-07-07T00:41:12Z" - mac: ENC[AES256_GCM,data:8TM3EOv3yhYfh6QTTcD2AJK1y77JJKygwoJUMULN3q3A+b/RefpXs6+h66wn13P9EvQu9FTZg0kwijLUOCktuBPfaAiz00+BK9PFtt5UsDA9g/qUVA5EUxpmq0vRzDv1njhomwmI/f9pMbKiXRvXpkXxhpMeRx6XN0D4itCEMvI=,iv:RlicLXVU7kQgfc6bM2wAvyAB8zAMX3G8ZPeToJ73g14=,tag:Det/rkDaw8LqJNqZlkGKkw==,type:str] + lastmodified: "2026-07-07T12:27:30Z" + mac: ENC[AES256_GCM,data:tGhGH9PjoTCSJlIi6Rh/n3jiUN/Heu7L3yvLWP1cKOZMg2N1G+B/mL0trAJbyzljbi1M/LgKEf7eUwLM1gAciLQG8pRBZqb3L+IwErf7/+ul1mhyZSwwL5DuzP+hxnwRh06TLBxbV1jYSdY5QmL8QmeNxmvgVA+4AC6CIlX7IzY=,iv:OVN6s2uobQ4jup2hoAB1+EIXe+yz49J1hJZRjTCbezU=,tag:Y9xKxrhq2DkHt2EgPq8ZJQ==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.9.4 diff --git a/helm/forgejo-secrets.sops.yaml.template b/helm/forgejo-secrets.sops.yaml.template index cb0a476..e2c508b 100644 --- a/helm/forgejo-secrets.sops.yaml.template +++ b/helm/forgejo-secrets.sops.yaml.template @@ -1,5 +1,7 @@ # Encrypt as helm/forgejo-secrets.sops.yaml before production deploy. -# Contains database PASSWD, admin password, and IONOS SMTP mailbox password. +# Contains database PASSWD and admin password consumed by the gitea chart. +# IONOS SMTP password lives in OpenBao platform/workloads/forgejo/forgejo-mailer +# and is synced by manifests/forgejo-mailer-externalsecret.yaml. # # gitea: # admin: @@ -7,13 +9,9 @@ # config: # database: # PASSWD: -# mailer: -# PASSWD: gitea: admin: password: REPLACE_WITH_ADMIN_PASSWORD config: database: - PASSWD: REPLACE_WITH_DB_PASSWORD - mailer: - PASSWD: REPLACE_WITH_IONOS_MAILBOX_PASSWORD \ No newline at end of file + PASSWD: REPLACE_WITH_DB_PASSWORD \ No newline at end of file diff --git a/helm/forgejo-values.yaml b/helm/forgejo-values.yaml index ac9d540..8931638 100644 --- a/helm/forgejo-values.yaml +++ b/helm/forgejo-values.yaml @@ -35,6 +35,13 @@ gitea: username: forgejo_admin email: admin@coulomb.social passwordMode: initialOnlyRequireReset + # Mailer password from OpenBao via ExternalSecret forgejo-mailer (not Helm/SOPS). + additionalConfigFromEnvs: + - name: GITEA__mailer__PASSWD + valueFrom: + secretKeyRef: + name: forgejo-mailer + key: MAILER_PASSWD config: database: DB_TYPE: postgres diff --git a/manifests/forgejo-mailer-externalsecret.yaml b/manifests/forgejo-mailer-externalsecret.yaml new file mode 100644 index 0000000..0ebbe5d --- /dev/null +++ b/manifests/forgejo-mailer-externalsecret.yaml @@ -0,0 +1,30 @@ +# Forgejo IONOS SMTP password from OpenBao (RAIL-HO-WP-0005-T06). +# +# OpenBao path (platform KV v2): workloads/forgejo/forgejo-mailer +# Field: MAILER_PASSWD (optional SMTP_USER for non-Helm consumers) +# +# Prereqs on railiance01: +# - External Secrets Operator (make eso-deploy) +# - ClusterSecretStore openbao-forgejo (railiance-platform openbao-secretstore) +# - OpenBao role external-secrets-forgejo (make openbao-configure-external-secrets-forgejo) +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: forgejo-mailer + namespace: forgejo + labels: + app.kubernetes.io/name: forgejo + app.kubernetes.io/part-of: railiance-apps +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: openbao-forgejo + target: + name: forgejo-mailer + creationPolicy: Owner + data: + - secretKey: MAILER_PASSWD + remoteRef: + key: workloads/forgejo/forgejo-mailer + property: MAILER_PASSWD \ No newline at end of file diff --git a/tools/forgejo-helm-secrets.sh b/tools/forgejo-helm-secrets.sh new file mode 100755 index 0000000..4aba1af --- /dev/null +++ b/tools/forgejo-helm-secrets.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Emit Helm values for Forgejo DB + admin secrets (no mailer — OpenBao/ESO owns SMTP). +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SECRETS_FILE="${FORGEJO_SECRETS:-$ROOT_DIR/helm/forgejo-secrets.sops.yaml}" +KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config-hosteurope}" + +emit_from_sops() { + sops -d "$SECRETS_FILE" | python3 -c " +import sys, yaml +data = yaml.safe_load(sys.stdin) or {} +mailer = data.get('gitea', {}).get('config', {}).get('mailer') +if mailer: + data['gitea']['config'].pop('mailer', None) +yaml.dump(data, sys.stdout, default_flow_style=False) +" +} + +emit_from_cluster() { + python3 -c " +import json, os, subprocess, sys, yaml +os.environ['KUBECONFIG'] = '${KUBECONFIG}' +vals = json.loads(subprocess.check_output( + ['helm', 'get', 'values', 'forgejo', '-n', 'forgejo', '-o', 'json'], text=True)) +g = vals.get('gitea', {}) +yaml.dump({ + 'gitea': { + 'admin': {'password': g.get('admin', {}).get('password', '')}, + 'config': { + 'database': {'PASSWD': g.get('config', {}).get('database', {}).get('PASSWD', '')}, + }, + } +}, sys.stdout, default_flow_style=False) +" +} + +if command -v sops >/dev/null 2>&1 && sops -d "$SECRETS_FILE" >/dev/null 2>&1; then + emit_from_sops +else + emit_from_cluster +fi \ No newline at end of file