feat(gitops): migrate domain to riotpiao.com, add CNPG + Forgejo HA on Redis/Postgres, wire ArgoCD apps — enables cluster rebuild after etcd wipe and unblocks the git-source chicken-egg via standalone Helm-source Applications

This commit is contained in:
Story Crater Bot
2026-07-19 09:29:17 -07:00
parent c6493f14ae
commit 578a707867
86 changed files with 889 additions and 446 deletions
+4 -4
View File
@@ -3,7 +3,7 @@
# Deployed via helmfile (name=argocd) so it sits alongside Authentik, MinIO, etc.
#
# UI is exposed via the cluster Ingress (k8s/ingress/ingress.yaml,
# argocd.riotpiao.homelab.com) over the WireGuard/LAN-only nginx ingress —
# argocd.riotpiao.com) over the WireGuard/LAN-only nginx ingress —
# never as a LoadBalancer, since argocd-server holds cluster-admin
# credentials and that Ingress isn't reachable from the WAN.
#
@@ -12,7 +12,7 @@
# CA trust for Forgejo repo clones injected into argocd-tls-certs-cm by postsync hook.
global:
domain: argocd.riotpiao.homelab.com
domain: argocd.riotpiao.com
configs:
params:
@@ -23,10 +23,10 @@ configs:
# Authentik (provision_oidc.py argocd_url) exactly — ArgoCD builds its
# OIDC redirect_uri as "{url}/auth/callback", so any mismatch here is
# what Authentik's "Invalid redirect URL" error means.
url: "https://argocd.riotpiao.homelab.com"
url: "https://argocd.riotpiao.com"
oidc.config: |
name: Authentik
issuer: https://authentik.riotpiao.homelab.com/application/o/argocd/
issuer: https://authentik.riotpiao.com/application/o/argocd/
clientID: $oidc-secret:client-id
clientSecret: $oidc-secret:client-secret
requestedScopes: [openid, profile, email, groups]
@@ -4,7 +4,7 @@ runner:
tag: "6" # pin exact release before apply
name: talos-runner
labels: "docker:docker://node:22-bookworm"
forgejoUrl: https://forgejo.riotpiao.homelab.com
forgejoUrl: https://forgejo.riotpiao.com
# tokenSecret: name of the K8s Secret that holds the runner registration token
# created automatically by the helmfile presync hook (see helmfile.yaml.gotmpl)
tokenSecret: runner-token
@@ -16,7 +16,7 @@ spec:
containers:
- name: api
# CI bumps this tag on every push to main (ci.yml step "bump deploy repo")
image: forgejo.riotpiao.homelab.com/rock/api:latest
image: forgejo.riotpiao.com/rock/api:latest
ports:
- containerPort: 8080
resources:
@@ -8,7 +8,7 @@ metadata:
spec:
project: default
source:
repoURL: https://forgejo.forge.riotpiao.homelab.com/rock/deploy.git
repoURL: https://forgejo.forge.riotpiao.com/rock/deploy.git
targetRevision: main
path: api
destination:
@@ -8,7 +8,7 @@ metadata:
spec:
project: default
source:
repoURL: https://forgejo.forge.riotpiao.homelab.com/rock/deploy.git
repoURL: https://forgejo.forge.riotpiao.com/rock/deploy.git
targetRevision: main
path: argocd
destination:
@@ -8,7 +8,7 @@ metadata:
spec:
project: default
source:
repoURL: https://forgejo.forge.riotpiao.homelab.com/rock/deploy.git
repoURL: https://forgejo.forge.riotpiao.com/rock/deploy.git
targetRevision: main
path: forge
destination:
+3 -3
View File
@@ -37,7 +37,7 @@ jobs:
# ── 2. Build + push OCI image ───────────────────────────────────────────
- name: build and push image
env:
REGISTRY: forgejo.forge.riotpiao.homelab.com
REGISTRY: forgejo.forge.riotpiao.com
OWNER: rock
run: |
REPO_NAME=${{ github.event.repository.name }}
@@ -60,7 +60,7 @@ jobs:
# Argo CD notices the commit and rolls out the new image.
- name: bump deploy repo
env:
REGISTRY: forgejo.forge.riotpiao.homelab.com
REGISTRY: forgejo.forge.riotpiao.com
OWNER: rock
run: |
git clone \
@@ -75,7 +75,7 @@ jobs:
git -C /tmp/deploy \
-c user.name="ci-bot" \
-c user.email="[email protected].homelab.com" \
-c user.email="[email protected]" \
commit -am "${REPO_NAME}: deploy ${TAG}"
git -C /tmp/deploy push
+39
View File
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo-redis
namespace: cicd
spec:
replicas: 1
selector:
matchLabels:
app: forgejo-redis
template:
metadata:
labels:
app: forgejo-redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: forgejo-redis
namespace: cicd
spec:
selector:
app: forgejo-redis
ports:
- port: 6379
targetPort: 6379
+35 -18
View File
@@ -1,7 +1,10 @@
# k8s/talos-ci-cd/forgejo-values.yaml
# Forgejo deployed via the gitea-charts/gitea Helm chart with image override.
# Admin password injected via helmfile --set (FORGEJO_ADMIN_PASSWORD in .env).
# Runner is managed by a separate helmfile release (charts/forgejo-runner/).
# k8s/security/ci-cd/forgejo-values.yaml
# Forgejo deployed via the gitea-charts/gitea Helm chart with image override,
# as an ArgoCD Application with an inline Helm source (k8s/argocd/apps/forgejo.yaml).
# Admin password set via --set at install time (FORGEJO_ADMIN_PASSWORD in .env).
# DB: CNPG (k8s/data/ddb-cluster.yaml + forgejo-database.yaml), not SQLite.
# Cache/session/queue: dedicated Redis (forgejo-redis.yaml), not in-memory —
# required once replicaCount > 1, since in-memory state isn't shared across pods.
#
# Chart docs: https://gitea.com/gitea/helm-chart
@@ -21,16 +24,18 @@ gitea:
config:
server:
PROTOCOL: http # nginx ingress handles TLS; pod serves plain HTTP
DOMAIN: forgejo.riotpiao.homelab.com
ROOT_URL: https://forgejo.riotpiao.homelab.com/
DOMAIN: forgejo.riotpiao.com
ROOT_URL: https://forgejo.riotpiao.com/
HTTP_PORT: 3000
START_SSH_SERVER: true
SSH_DOMAIN: forgejo.riotpiao.homelab.com
SSH_DOMAIN: forgejo.riotpiao.com
SSH_PORT: 2222
SSH_LISTEN_PORT: 2222
database:
DB_TYPE: sqlite3
PATH: /data/forgejo.db
DB_TYPE: postgres
HOST: ddb-cluster-rw.ddb.svc:5432
NAME: forgejo
USER: app
repository:
ROOT: /data/git
actions:
@@ -44,37 +49,49 @@ gitea:
oauth2:
ENABLED: true
PROVIDER: openidconnect
OPENID_CONNECT_DISCOVERY_URL: https://authentik.riotpiao.homelab.com/application/o/forgejo/.well-known/openid-configuration
OPENID_CONNECT_DISCOVERY_URL: https://authentik.riotpiao.com/application/o/forgejo/.well-known/openid-configuration
CLIENT_ID: forgejo
AUTO_DISCOVER_URL: https://authentik.riotpiao.homelab.com/application/o/forgejo/.well-known/openid-configuration
AUTO_DISCOVER_URL: https://authentik.riotpiao.com/application/o/forgejo/.well-known/openid-configuration
cache:
ADAPTER: memory # no Redis — single-replica SQLite setup
ADAPTER: redis
HOST: redis://forgejo-redis.cicd.svc:6379/0
session:
PROVIDER: memory
PROVIDER: redis
PROVIDER_CONFIG: redis://forgejo-redis.cicd.svc:6379/1
queue:
TYPE: channel # in-memory queue; no file lock, no LevelDB contention on rollout
TYPE: redis
CONN_STR: redis://forgejo-redis.cicd.svc:6379/2
metrics:
enabled: true
serviceMonitor:
enabled: true # kube-prometheus-stack discovers ServiceMonitors cluster-wide
# ── Persistence (Longhorn RWOSQLite lives here) ────────────────────────────
# ── Persistence (Longhorn RWXgit repo data shared across replicas) ────────
persistence:
enabled: true
storageClass: longhorn
size: 20Gi
accessModes:
- ReadWriteOnce
- ReadWriteMany
# ── Replicas ──────────────────────────────────────────────────────────────────
# Postgres (CNPG) + Redis (session/cache/queue) + RWX storage means replicas
# no longer need to serialize on a single pod like the old SQLite setup did.
replicaCount: 2
# ── Deployment strategy ────────────────────────────────────────────────────────
# RWO PVC + SQLite: old pod must terminate before new one mounts the volume.
deployment:
strategy:
type: Recreate
type: RollingUpdate
env:
- name: SSL_CERT_DIR
value: /homelab-ca
- name: GITEA__database__PASSWD
valueFrom:
secretKeyRef:
name: ddb-cluster-app
key: password
# ── Cert / CA auto-reload ─────────────────────────────────────────────────────
# nginx serves the wildcard-tls cert — Forgejo itself never reads a TLS secret.
+6 -2
View File
@@ -1,5 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ci-cd
resources: []
# Helm charts (forgejo-runner) deployed via ArgoCD Helm source
resources:
- forgejo-redis.yaml
# Helm charts (forgejo, forgejo-runner) deployed via standalone ArgoCD
# Applications with Helm sources (k8s/argocd/apps/forgejo.yaml) — not through
# this kustomization, since layer-4-security syncs from Forgejo's own repo
# (chicken-egg: Forgejo can't be the source of truth for deploying itself).
+2 -2
View File
@@ -1,7 +1,7 @@
# 0. Source your env (Authentik bootstrap token + Vault addr)
set -a && source ~/.authentik/.env && set +a
VAULT_ADDR="http://vault.riotpiao.homelab.com"
AUTHENTIK_URL="http://authentik.riotpiao.homelab.com"
VAULT_ADDR="http://vault.riotpiao.com"
AUTHENTIK_URL="http://authentik.riotpiao.com"
# 1. Fetch the real client_id + client_secret from Authentik
PROVIDER_PK=$(curl -s \
+6 -6
View File
@@ -78,7 +78,7 @@ cd /Users/rockliang/workplace/homelab
python3 k8s/talos-iam/register_oauth_app.py \
--service-name my-app \
--namespace my-namespace \
--redirect-uri "https://my-app.riotpiao.homelab.com/oauth2/callback" \
--redirect-uri "https://my-app.riotpiao.com/oauth2/callback" \
--service-name-in-vault "MY_SERVICE" \
--vault-jwt-policy "shell-secrets"
```
@@ -127,7 +127,7 @@ kubectl port-forward -n iam svc/authentik-server 7000:80 &
- **Name:** `yourservice`
- **Client ID:** `yourservice` (or custom)
- **Client Secret:** (paste from `$CLIENT_SECRET` above)
- **Redirect URIs:** `https://your-app.riotpiao.homelab.com/oauth2/callback` (or your app's callback URL)
- **Redirect URIs:** `https://your-app.riotpiao.com/oauth2/callback` (or your app's callback URL)
- **Sub Mode:** `Hashed User ID`
- **Include claims in ID Token:** ✓ Enabled
- **Grant Types:** `Authorization Code`, `Refresh Token`
@@ -143,7 +143,7 @@ kubectl port-forward -n iam svc/authentik-server 7000:80 &
- **Name:** `Your App Display Name`
- **Slug:** `yourservice` (must match provider slug)
- **Provider:** (select the provider created above)
- **Meta Launch URL:** `https://your-app.riotpiao.homelab.com` (optional, for app launcher)
- **Meta Launch URL:** `https://your-app.riotpiao.com` (optional, for app launcher)
3. **Save**
@@ -160,7 +160,7 @@ OIDC_CLIENT_ID=yourservice
OIDC_CLIENT_SECRET=$CLIENT_SECRET
# Redirect URI (must match what you configured in step 2)
OIDC_REDIRECT_URI=https://your-app.riotpiao.homelab.com/oauth2/callback
OIDC_REDIRECT_URI=https://your-app.riotpiao.com/oauth2/callback
# Optionally, token validation endpoint
OIDC_TOKEN_URL=http://authentik-server.iam.svc.cluster.local/application/o/yourservice/token/
@@ -179,7 +179,7 @@ talos put cluster/YOURSERVICE_OIDC_CLIENT_SECRET YOURSERVICE_OIDC_CLIENT_SECRET=
talos put cluster/yourservice-oidc \
client_id=yourservice \
issuer_url="http://authentik-server.iam.svc.cluster.local/application/o/yourservice/" \
callback_uri="https://your-app.riotpiao.homelab.com/oauth2/callback"
callback_uri="https://your-app.riotpiao.com/oauth2/callback"
```
### Step 5: Mount Credentials in Your Service
@@ -378,7 +378,7 @@ kubectl get secret yourservice-oidc -n your-namespace -o jsonpath='{.data}' | ba
vault read auth/jwt/role/yourservice
# 5. Test OAuth login (requires port-forward to your app)
# Open http://your-app.riotpiao.homelab.com/login
# Open http://your-app.riotpiao.com/login
# Should redirect to Authentik, then back to your app
# 6. Test JWT auth to Vault (if applicable)
+3 -3
View File
@@ -227,7 +227,7 @@ The homelab provides **automated OAuth registration** for new services via `regi
python3 k8s/talos-iam/register_oauth_app.py \
--service-name my-service \
--namespace my-ns \
--redirect-uri "https://my-service.riotpiao.homelab.com/oauth2/callback" \
--redirect-uri "https://my-service.riotpiao.com/oauth2/callback" \
--service-name-in-vault MY_SERVICE \
--vault-jwt-policy shell-secrets \
--add-group my-service-admins
@@ -254,7 +254,7 @@ export DASHBOARD_OIDC_CLIENT_SECRET
python3 k8s/talos-iam/register_oauth_app.py \
--service-name dashboard-service \
--namespace apps \
--redirect-uri "https://dashboard.riotpiao.homelab.com/oauth2/callback" \
--redirect-uri "https://dashboard.riotpiao.com/oauth2/callback" \
--service-name-in-vault DASHBOARD \
--vault-jwt-policy shell-secrets \
--add-group dashboard-admins \
@@ -282,7 +282,7 @@ kubectl get secret my-service-oidc -n my-ns -o yaml
vault read auth/jwt/role/my-service
# Test OAuth login
# Browser: https://my-service.riotpiao.homelab.com/login
# Browser: https://my-service.riotpiao.com/login
# Should redirect to Authentik → back to service with session
```
+3 -3
View File
@@ -17,7 +17,7 @@ authentik:
# Must match what the browser sees — if it returns an internal svc URL,
# the browser's redirect after login will fail (can't reach svc DNS externally).
# HTTP (not HTTPS) because the Authentik ingress has no TLS cert configured.
host: "https://authentik.riotpiao.homelab.com"
host: "https://authentik.riotpiao.com"
error_reporting:
enabled: false # do not phone home to Sentry
@@ -103,7 +103,7 @@ _caEnv: &caEnv
# ── Authentik server (UI + API) ───────────────────────────────────────────────
# Handles all browser traffic: login flows, admin UI, OAuth2 authorize/token endpoints.
# NodePort 32172 is a fallback for direct node access during troubleshooting;
# normal access is via nginx ingress (authentik.riotpiao.homelab.com → svc:80).
# normal access is via nginx ingress (authentik.riotpiao.com → svc:80).
# Recreate: single replica + RWO-adjacent state — avoids split-brain on redeploy.
server:
replicas: 1
@@ -221,5 +221,5 @@ redis:
operator: Exists
architecture: standalone
# Ingress disabled — rule lives in k8s/ingress/ingress.yaml (authentik.riotpiao.homelab.com).
# Ingress disabled — rule lives in k8s/ingress/ingress.yaml (authentik.riotpiao.com).
# For direct access during bootstrap: kubectl -n iam port-forward svc/authentik-server 7000:80
@@ -23,7 +23,7 @@ To use this example:
python3 register_oauth_app.py \\
--service-name dashboard-service \\
--namespace apps \\
--redirect-uri "https://dashboard.riotpiao.homelab.com/oauth2/callback" \\
--redirect-uri "https://dashboard.riotpiao.com/oauth2/callback" \\
--service-name-in-vault DASHBOARD \\
--vault-jwt-policy shell-secrets \\
--add-group dashboard-admins \\
@@ -34,7 +34,7 @@ To use this example:
http://localhost:7000/api/v3/core/applications/?slug=dashboard-service | jq .
# 5. Test OAuth callback (requires app running)
# Browser: http://dashboard.riotpiao.homelab.com/login
# Browser: http://dashboard.riotpiao.com/login
# Should redirect to Authentik → back to dashboard with session
"""
@@ -68,7 +68,7 @@ def example_dashboard_service():
"register_oauth_app.py",
"--service-name", "dashboard-service",
"--namespace", "apps",
"--redirect-uri", "https://dashboard.riotpiao.homelab.com/oauth2/callback",
"--redirect-uri", "https://dashboard.riotpiao.com/oauth2/callback",
"--service-name-in-vault", "DASHBOARD",
"--vault-jwt-policy", "shell-secrets",
"--add-group", "dashboard-admins",
@@ -122,7 +122,7 @@ def verify_example():
print()
print("✅ Test OAuth redirect (requires app running):")
print(" curl -L https://dashboard.riotpiao.homelab.com/login")
print(" curl -L https://dashboard.riotpiao.com/login")
print(" # Should redirect to Authentik, then back to dashboard")
print()
+5 -5
View File
@@ -10,7 +10,7 @@ Usage:
python3 register_oauth_app.py \\
--service-name my-service \\
--namespace my-ns \\
--redirect-uri "https://my-service.riotpiao.homelab.com/oauth2/callback" \\
--redirect-uri "https://my-service.riotpiao.com/oauth2/callback" \\
[--service-name-in-vault MY_SERVICE] \\
[--vault-jwt-policy shell-secrets]
@@ -28,13 +28,13 @@ Examples:
python3 register_oauth_app.py \\
--service-name myapp \\
--namespace apps \\
--redirect-uri "https://myapp.riotpiao.homelab.com/callback"
--redirect-uri "https://myapp.riotpiao.com/callback"
# Advanced: full service with Vault JWT auth
python3 register_oauth_app.py \\
--service-name myservice \\
--namespace my-namespace \\
--redirect-uri "https://myservice.riotpiao.homelab.com/oauth2/callback" \\
--redirect-uri "https://myservice.riotpiao.com/oauth2/callback" \\
--service-name-in-vault MYSERVICE \\
--vault-jwt-policy shell-secrets \\
--add-group myservice-admins \\
@@ -328,7 +328,7 @@ def main() -> None:
parser.add_argument(
"--redirect-uri",
required=True,
help="OAuth2 redirect URI (e.g., https://myapp.riotpiao.homelab.com/callback)"
help="OAuth2 redirect URI (e.g., https://myapp.riotpiao.com/callback)"
)
parser.add_argument(
"--service-name-in-vault",
@@ -420,7 +420,7 @@ def main() -> None:
display_name=service_name.replace("-", " ").title(),
slug=service_name,
provider_pk=provider_pk,
launch_url=f"https://{service_name}.riotpiao.homelab.com"
launch_url=f"https://{service_name}.riotpiao.com"
)
print()
+7 -7
View File
@@ -238,7 +238,7 @@ _vault_browser_payload=$(jq -n \
invalidation_flow: $inval_flow,
signing_key: $signing_key,
redirect_uris: [
{"matching_mode": "strict", "url": "http://vault.riotpiao.homelab.com/ui/vault/auth/oidc/oidc/callback"},
{"matching_mode": "strict", "url": "http://vault.riotpiao.com/ui/vault/auth/oidc/oidc/callback"},
{"matching_mode": "strict", "url": "http://localhost:8250/oidc/callback"}
]
}')
@@ -465,7 +465,7 @@ step oidc_enable "Enabling OIDC auth method for browser login..."
vault auth enable oidc 2>/dev/null || echo "OIDC auth already enabled."
vault write auth/oidc/config \
oidc_discovery_url="http://authentik.riotpiao.homelab.com/application/o/vault-browser/" \
oidc_discovery_url="http://authentik.riotpiao.com/application/o/vault-browser/" \
oidc_client_id="vault-browser" \
oidc_client_secret="${VAULT_BROWSER_CLIENT_SECRET}" \
default_role="homelab"
@@ -475,7 +475,7 @@ vault write auth/oidc/config \
vault write auth/oidc/role/homelab \
role_type=oidc \
bound_audiences="vault-browser" \
allowed_redirect_uris="http://vault.riotpiao.homelab.com/ui/vault/auth/oidc/oidc/callback,http://localhost:8250/oidc/callback" \
allowed_redirect_uris="http://vault.riotpiao.com/ui/vault/auth/oidc/oidc/callback,http://localhost:8250/oidc/callback" \
user_claim="sub" \
oidc_scopes="openid,profile,email" \
token_policies="shell-secrets,cluster-admin" \
@@ -483,8 +483,8 @@ vault write auth/oidc/role/homelab \
token_max_ttl=12h
echo "OIDC auth configured."
echo " Browser login: http://vault.riotpiao.homelab.com → sign in with Authentik"
echo " CLI login: VAULT_ADDR=http://vault.riotpiao.homelab.com vault login -method=oidc"
echo " Browser login: http://vault.riotpiao.com → sign in with Authentik"
echo " CLI login: VAULT_ADDR=http://vault.riotpiao.com vault login -method=oidc"
# ── 14. Seed initial secrets ──────────────────────────────────────────────────
step seed_secrets "Seeding initial secrets from environment (accessible after OIDC login)..."
@@ -519,8 +519,8 @@ echo "==> Vault bootstrap complete."
echo ""
echo "Next steps:"
echo " 1. Move ${UNSEAL_KEYS_FILE} OFFLINE (USB / password manager)."
echo " 2. Browser login: open http://vault.riotpiao.homelab.com → choose OIDC → sign in with Authentik"
echo " 3. CLI login: export VAULT_ADDR=http://vault.riotpiao.homelab.com"
echo " 2. Browser login: open http://vault.riotpiao.com → choose OIDC → sign in with Authentik"
echo " 3. CLI login: export VAULT_ADDR=http://vault.riotpiao.com"
echo " vault login -method=oidc"
echo " 4. Read MinIO credentials from Vault after login:"
echo " vault kv get secret/cluster/minio"
+3 -3
View File
@@ -9,7 +9,7 @@
# once after first install to initialize and store the keys).
# ── Global ────────────────────────────────────────────────────────────────────
# tlsDisable: true — TLS terminated at the nginx ingress (vault.riotpiao.homelab.com)
# tlsDisable: true — TLS terminated at the nginx ingress (vault.riotpiao.com)
# or at port-forward. In-cluster traffic to Vault is plain HTTP; this is acceptable
# because all clients are on the pod network (not crossing node boundaries).
global:
@@ -137,7 +137,7 @@ server:
# ── Service ─────────────────────────────────────────────────────────────────
# NodePort 32171 — fallback for direct node access during bootstrap before
# the ingress is up. Normal access is via nginx ingress (vault.riotpiao.homelab.com).
# the ingress is up. Normal access is via nginx ingress (vault.riotpiao.com).
service:
type: NodePort
port: 8200
@@ -155,7 +155,7 @@ server:
# ── UI ────────────────────────────────────────────────────────────────────────
# Vault's web UI is used for the OIDC browser login flow (Vault as an OIDC
# provider, if configured) and for manual operator inspection.
# Accessible at http://vault.riotpiao.homelab.com or via port-forward.
# Accessible at http://vault.riotpiao.com or via port-forward.
ui:
enabled: true