k8s/ci-cd: add forgejo gitops and argocd deployment
- Forgejo git forge + OCI registry - Argo CD pull-based GitOps - Private CA TLS (self-signed 10-year cert) - Machine credentials scoped to repositories
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# k8s/talos-ci-cd/argocd-values.yaml
|
||||
# Argo CD — single-replica homelab install (fits the 4-CPU / 32 GB RAM budget).
|
||||
# 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 —
|
||||
# never as a LoadBalancer, since argocd-server holds cluster-admin
|
||||
# credentials and that Ingress isn't reachable from the WAN.
|
||||
#
|
||||
# OIDC + RBAC declared in configs.cm / configs.rbac below — applied by Helm directly.
|
||||
# Requires: oidc-secret K8s secret (created by helmfile argocd presync hook from env vars).
|
||||
# CA trust for Forgejo repo clones injected into argocd-tls-certs-cm by postsync hook.
|
||||
|
||||
global:
|
||||
domain: argocd.riotpiao.homelab.com
|
||||
|
||||
configs:
|
||||
params:
|
||||
server.insecure: false # keep TLS on argocd-server even behind the ingress
|
||||
|
||||
cm:
|
||||
# Must match the Ingress host above and the redirect URI registered in
|
||||
# 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"
|
||||
oidc.config: |
|
||||
name: Authentik
|
||||
issuer: https://authentik.riotpiao.homelab.com/application/o/argocd/
|
||||
clientID: $oidc-secret:client-id
|
||||
clientSecret: $oidc-secret:client-secret
|
||||
requestedScopes: [openid, profile, email, groups]
|
||||
rootCA: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBbTCCARSgAwIBAgIUNa409I6cGHye4YqeiphmWDaCEXUwCgYIKoZIzj0EAwIw
|
||||
FTETMBEGA1UEAxMKaG9tZWxhYi1jYTAeFw0yNjA2MTcxNjUzMjVaFw0zNjA2MTQx
|
||||
NjUzMjVaMBUxEzARBgNVBAMTCmhvbWVsYWItY2EwWTATBgcqhkjOPQIBBggqhkjO
|
||||
PQMBBwNCAARwkubJPPdhgKcqr+3AEO2tr5I7MhC3zzeAqpmv8glngsweiGznaDhi
|
||||
Dbf8JFfilbrLEJBSuwHZQPuoNx+3fbvYo0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUod8iYq0+QyetnxfKDprIf3XbWkEwCgYI
|
||||
KoZIzj0EAwIDRwAwRAIgQ4HOLs5DOqcfAMv8NSImxoYN7TyebnlQAQXSARnIqBMC
|
||||
IB9RycFvG/rpJuz/LIKi4rf6RARjLcHM/zqhXQJvBw53
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
rbac:
|
||||
policy.csv: |
|
||||
g, homelab-admins, role:admin
|
||||
policy.default: role:readonly
|
||||
|
||||
server:
|
||||
replicas: 1
|
||||
deploymentStrategy:
|
||||
type: Recreate
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
# No Service of type LoadBalancer — port-forward only
|
||||
service:
|
||||
type: ClusterIP
|
||||
podAnnotations:
|
||||
secret.reloader.stakater.com/reload: "oidc-secret"
|
||||
configmap.reloader.stakater.com/reload: "argocd-tls-certs-cm,argocd-cm"
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
repoServer:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
applicationSet:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
controller:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
|
||||
redis:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
notifications:
|
||||
enabled: false # add back later if alert routing is needed
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: forgejo-runner
|
||||
description: Forgejo Actions runner with Docker-in-Docker sidecar for homelab CI/CD
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "6"
|
||||
@@ -0,0 +1,100 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ .Release.Name }}
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate # RWO PVCs — old pod must terminate before new one mounts them
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ .Release.Name }}
|
||||
spec:
|
||||
tolerations:
|
||||
{{- toYaml .Values.tolerations | nindent 8 }}
|
||||
|
||||
initContainers:
|
||||
- name: register
|
||||
image: {{ .Values.runner.image.repository }}:{{ .Values.runner.image.tag }}
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
test -f /data/.runner || forgejo-runner register --no-interactive \
|
||||
--instance {{ .Values.runner.forgejoUrl }} \
|
||||
--token $(RUNNER_TOKEN) \
|
||||
--name {{ .Values.runner.name }} \
|
||||
--labels "{{ .Values.runner.labels }}"
|
||||
env:
|
||||
- name: RUNNER_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.runner.tokenSecret }}
|
||||
key: token
|
||||
volumeMounts:
|
||||
- name: runner-data
|
||||
mountPath: /data
|
||||
- name: homelab-ca
|
||||
mountPath: /etc/ssl/certs/homelab-ca.pem
|
||||
subPath: ca.crt
|
||||
workingDir: /data
|
||||
|
||||
containers:
|
||||
- name: runner
|
||||
image: {{ .Values.runner.image.repository }}:{{ .Values.runner.image.tag }}
|
||||
command: ["sh", "-c", "forgejo-runner daemon"]
|
||||
workingDir: /data
|
||||
env:
|
||||
- name: DOCKER_HOST
|
||||
value: tcp://localhost:2376
|
||||
- name: DOCKER_TLS_VERIFY
|
||||
value: "1"
|
||||
- name: DOCKER_CERT_PATH
|
||||
value: /docker-certs/client
|
||||
volumeMounts:
|
||||
- name: runner-data
|
||||
mountPath: /data
|
||||
- name: docker-certs
|
||||
mountPath: /docker-certs
|
||||
- name: homelab-ca
|
||||
mountPath: /etc/ssl/certs/homelab-ca.pem
|
||||
subPath: ca.crt
|
||||
resources:
|
||||
{{- toYaml .Values.runner.resources | nindent 12 }}
|
||||
|
||||
- name: dind
|
||||
image: {{ .Values.dind.image.repository }}:{{ .Values.dind.image.tag }}
|
||||
securityContext:
|
||||
privileged: true # required for DinD; cicd namespace is labelled privileged
|
||||
env:
|
||||
- name: DOCKER_TLS_CERTDIR
|
||||
value: /docker-certs
|
||||
volumeMounts:
|
||||
- name: docker-certs
|
||||
mountPath: /docker-certs
|
||||
- name: dind-storage
|
||||
mountPath: /var/lib/docker
|
||||
- name: homelab-ca
|
||||
mountPath: /etc/ssl/certs/homelab-ca.pem
|
||||
subPath: ca.crt
|
||||
resources:
|
||||
{{- toYaml .Values.dind.resources | nindent 12 }}
|
||||
|
||||
volumes:
|
||||
- name: runner-data
|
||||
persistentVolumeClaim:
|
||||
claimName: runner-reg
|
||||
- name: dind-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: runner-dind
|
||||
- name: docker-certs
|
||||
emptyDir: {} # DinD regenerates mTLS certs on each start
|
||||
- name: homelab-ca
|
||||
secret:
|
||||
secretName: homelab-ca
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-egress
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}
|
||||
policyTypes: [Egress]
|
||||
egress:
|
||||
# Forgejo — same cicd namespace (git push, registry push/pull)
|
||||
- to:
|
||||
- podSelector: {}
|
||||
# CoreDNS
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
# Internet (action deps, base images) — never LAN or pod network
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except:
|
||||
- 192.168.1.0/24
|
||||
- 10.244.0.0/16
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: runner-reg
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ .Release.Name }}
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: {{ .Values.persistence.reg.storageClass }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.reg.size }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: runner-dind
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ .Release.Name }}
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: {{ .Values.persistence.dind.storageClass }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.dind.size }}
|
||||
@@ -0,0 +1,42 @@
|
||||
runner:
|
||||
image:
|
||||
repository: code.forgejo.org/forgejo/runner
|
||||
tag: "6" # pin exact release before apply
|
||||
name: talos-runner
|
||||
labels: "docker:docker://node:22-bookworm"
|
||||
forgejoUrl: https://forgejo.riotpiao.homelab.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
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
|
||||
dind:
|
||||
image:
|
||||
repository: docker
|
||||
tag: "27-dind" # pin exact release before apply
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
|
||||
persistence:
|
||||
reg:
|
||||
storageClass: longhorn
|
||||
size: 1Gi # .runner registration file + config — survives pod restarts
|
||||
dind:
|
||||
storageClass: longhorn
|
||||
size: 30Gi # docker layer cache — keeps rebuilds fast across restarts
|
||||
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api
|
||||
namespace: api
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: api
|
||||
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
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api
|
||||
namespace: api
|
||||
spec:
|
||||
selector:
|
||||
app: api
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: api
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "10"
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://forgejo.forge.riotpiao.homelab.com/rock/deploy.git
|
||||
targetRevision: main
|
||||
path: api
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: api
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: argocd
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://forgejo.forge.riotpiao.homelab.com/rock/deploy.git
|
||||
targetRevision: main
|
||||
path: argocd
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
# NO syncPolicy.automated — manual sync required.
|
||||
# Argo CD managing itself auto-synced is a footgun: a misconfigured commit could
|
||||
# take down the CD system before anyone can intervene. Approve manually.
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: forge
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://forgejo.forge.riotpiao.homelab.com/rock/deploy.git
|
||||
targetRevision: main
|
||||
path: forge
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: forge
|
||||
# NO syncPolicy.automated — manual sync required.
|
||||
# Forgejo is what CI uses to push commits; auto-sync would let a bad CI commit
|
||||
# break the very system CI depends on. Approve syncs manually in the Argo CD UI.
|
||||
@@ -0,0 +1,81 @@
|
||||
# .forgejo/workflows/ci.yml
|
||||
#
|
||||
# Copy to YOUR APPLICATION REPO at .forgejo/workflows/ci.yml
|
||||
# (not this infra repo — this is a template).
|
||||
#
|
||||
# What this does (on every push to main):
|
||||
# 1. Run tests — fail here and nothing ships.
|
||||
# 2. Build a Docker image and push it to the Forgejo built-in OCI registry.
|
||||
# 3. Clone rock/deploy, bump the image tag in api/deployment.yaml, push the commit.
|
||||
# 4. Argo CD sees the commit within 3 minutes and rolls out the new version.
|
||||
#
|
||||
# Required repo secrets (Forgejo UI → repo → Settings → Actions → Secrets):
|
||||
# REGISTRY_TOKEN — ci-bot's package:write Forgejo API token
|
||||
# DEPLOY_TOKEN — ci-bot's repo:write Forgejo API token (scoped to rock/deploy only)
|
||||
#
|
||||
# Prerequisites:
|
||||
# - ci-bot user created in Forgejo (see IAM section §11.5 of talos_version_control.html)
|
||||
# - ci-bot added as collaborator on rock/deploy with Write access
|
||||
# - Runner (cicd ns) is online and registered (Block 3 of build runbook)
|
||||
# - Talos nodes trust the homelab CA (Block 2 of build runbook)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-push-deploy:
|
||||
runs-on: docker
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# ── 1. Tests ────────────────────────────────────────────────────────────
|
||||
- name: test
|
||||
run: make test # replace with your test command; failure stops the pipeline
|
||||
|
||||
# ── 2. Build + push OCI image ───────────────────────────────────────────
|
||||
- name: build and push image
|
||||
env:
|
||||
REGISTRY: forgejo.forge.riotpiao.homelab.com
|
||||
OWNER: rock
|
||||
run: |
|
||||
REPO_NAME=${{ github.event.repository.name }}
|
||||
TAG=$(git rev-parse --short HEAD)
|
||||
IMAGE="${REGISTRY}/${OWNER}/${REPO_NAME}:${TAG}"
|
||||
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" \
|
||||
| docker login "${REGISTRY}" -u "${OWNER}" --password-stdin
|
||||
|
||||
docker build -t "${IMAGE}" .
|
||||
docker push "${IMAGE}"
|
||||
|
||||
# Pass values to subsequent steps
|
||||
echo "TAG=${TAG}" >> "$GITHUB_ENV"
|
||||
echo "IMAGE=${IMAGE}" >> "$GITHUB_ENV"
|
||||
echo "REPO_NAME=${REPO_NAME}" >> "$GITHUB_ENV"
|
||||
|
||||
# ── 3. Bump image tag in the deploy repo ────────────────────────────────
|
||||
# This is the ONLY write operation CI has on the cluster side.
|
||||
# Argo CD notices the commit and rolls out the new image.
|
||||
- name: bump deploy repo
|
||||
env:
|
||||
REGISTRY: forgejo.forge.riotpiao.homelab.com
|
||||
OWNER: rock
|
||||
run: |
|
||||
git clone \
|
||||
"https://ci-bot:${{ secrets.DEPLOY_TOKEN }}@${REGISTRY}/${OWNER}/deploy.git" \
|
||||
/tmp/deploy
|
||||
|
||||
TARGET_FILE="/tmp/deploy/${REPO_NAME}/deployment.yaml"
|
||||
|
||||
sed -i \
|
||||
"s|${REGISTRY}/${OWNER}/${REPO_NAME}:.*|${IMAGE}|" \
|
||||
"${TARGET_FILE}"
|
||||
|
||||
git -C /tmp/deploy \
|
||||
-c user.name="ci-bot" \
|
||||
-c user.email="[email protected]" \
|
||||
commit -am "${REPO_NAME}: deploy ${TAG}"
|
||||
|
||||
git -C /tmp/deploy push
|
||||
@@ -0,0 +1,151 @@
|
||||
# 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/).
|
||||
#
|
||||
# Chart docs: https://gitea.com/gitea/helm-chart
|
||||
|
||||
# ── Image (Forgejo replaces Gitea — drop-in compatible) ──────────────────────
|
||||
image:
|
||||
repository: codeberg.org/forgejo/forgejo
|
||||
tag: "13" # pin exact release — check codeberg.org/forgejo/forgejo/releases
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# ── Bootstrap admin (provisioned by a post-install Job inside the chart) ──────
|
||||
gitea:
|
||||
admin:
|
||||
username: rock
|
||||
email: [email protected]
|
||||
# password: injected via helmfile --set (FORGEJO_ADMIN_PASSWORD from .env)
|
||||
|
||||
config:
|
||||
server:
|
||||
PROTOCOL: http # nginx ingress handles TLS; pod serves plain HTTP
|
||||
DOMAIN: forgejo.riotpiao.homelab.com
|
||||
ROOT_URL: https://forgejo.riotpiao.homelab.com/
|
||||
HTTP_PORT: 3000
|
||||
START_SSH_SERVER: true
|
||||
SSH_DOMAIN: forgejo.riotpiao.homelab.com
|
||||
SSH_PORT: 2222
|
||||
SSH_LISTEN_PORT: 2222
|
||||
database:
|
||||
DB_TYPE: sqlite3
|
||||
PATH: /data/forgejo.db
|
||||
repository:
|
||||
ROOT: /data/git
|
||||
actions:
|
||||
ENABLED: true
|
||||
packages:
|
||||
ENABLED: true # built-in OCI registry
|
||||
metrics:
|
||||
ENABLED: true # Prometheus at /metrics
|
||||
service:
|
||||
DISABLE_REGISTRATION: true # no self-signup; Authentik OAuth2 auto-creates accounts
|
||||
oauth2:
|
||||
ENABLED: true
|
||||
PROVIDER: openidconnect
|
||||
OPENID_CONNECT_DISCOVERY_URL: https://authentik.riotpiao.homelab.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
|
||||
cache:
|
||||
ADAPTER: memory # no Redis — single-replica SQLite setup
|
||||
session:
|
||||
PROVIDER: memory
|
||||
queue:
|
||||
TYPE: channel # in-memory queue; no file lock, no LevelDB contention on rollout
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true # kube-prometheus-stack discovers ServiceMonitors cluster-wide
|
||||
|
||||
# ── Persistence (Longhorn RWO — SQLite lives here) ────────────────────────────
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: longhorn
|
||||
size: 20Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
|
||||
# ── Deployment strategy ────────────────────────────────────────────────────────
|
||||
# RWO PVC + SQLite: old pod must terminate before new one mounts the volume.
|
||||
deployment:
|
||||
strategy:
|
||||
type: Recreate
|
||||
env:
|
||||
- name: SSL_CERT_DIR
|
||||
value: /homelab-ca
|
||||
|
||||
# ── Cert / CA auto-reload ─────────────────────────────────────────────────────
|
||||
# nginx serves the wildcard-tls cert — Forgejo itself never reads a TLS secret.
|
||||
# The only reload trigger is homelab-ca: if the root CA rotates, the mounted
|
||||
# ConfigMap changes and Forgejo must restart to pick up the new CA bundle for OIDC.
|
||||
podAnnotations:
|
||||
configmap.reloader.stakater.com/reload: "homelab-ca"
|
||||
|
||||
# ── Services (Cilium LB-IPAM pins both to 192.168.1.165) ─────────────────────
|
||||
service:
|
||||
http:
|
||||
type: LoadBalancer
|
||||
port: 3000
|
||||
targetPort: 3000
|
||||
annotations:
|
||||
io.cilium/lb-ipam-ips: "192.168.1.165"
|
||||
io.cilium/lb-ipam-sharing-key: "forgejo"
|
||||
ssh:
|
||||
type: LoadBalancer
|
||||
port: 2222
|
||||
targetPort: 2222
|
||||
annotations:
|
||||
io.cilium/lb-ipam-ips: "192.168.1.165"
|
||||
io.cilium/lb-ipam-sharing-key: "forgejo"
|
||||
|
||||
# ── Resources ─────────────────────────────────────────────────────────────────
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
|
||||
# ── Node resilience ───────────────────────────────────────────────────────────
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
|
||||
# ── CA trust ─────────────────────────────────────────────────────────────────
|
||||
# Go reads SSL_CERT_DIR as an additional cert directory ON TOP OF the default
|
||||
# cert files (ca-certificates.crt stays intact — no init container needed).
|
||||
# Setting SSL_CERT_DIR=/homelab-ca makes Go also read homelab-ca.crt from there,
|
||||
# trusting both the standard Mozilla bundle and our homelab CA.
|
||||
# Required for OIDC: Forgejo fetches Authentik's discovery endpoint which
|
||||
# presents a cert signed by homelab-ca.
|
||||
extraVolumes:
|
||||
- name: homelab-ca
|
||||
configMap:
|
||||
name: homelab-ca
|
||||
|
||||
extraVolumeMounts:
|
||||
- name: homelab-ca
|
||||
mountPath: /homelab-ca
|
||||
readOnly: true
|
||||
|
||||
# ── Ingress: disabled — rule lives in k8s/ingress/ingress.yaml ───────────────
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# ── Bundled databases + cache: all disabled — SQLite + memory is the chosen backend ──
|
||||
postgresql:
|
||||
enabled: false
|
||||
postgresql-ha:
|
||||
enabled: false
|
||||
mysql:
|
||||
enabled: false
|
||||
redis-cluster:
|
||||
enabled: false # 6-node cluster is overkill for single-replica SQLite Forgejo
|
||||
|
||||
# ── Act runner subchart: disabled — managed by the forgejo-runner helmfile release
|
||||
act_runner:
|
||||
enabled: false
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
# 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"
|
||||
|
||||
# 1. Fetch the real client_id + client_secret from Authentik
|
||||
PROVIDER_PK=$(curl -s \
|
||||
-H "Authorization: Bearer ${AUTHENTIK_BOOTSTRAP_TOKEN}" \
|
||||
"${AUTHENTIK_URL}/api/v3/providers/oauth2/?name=talos-cli-shell" \
|
||||
| python3 -c "import json,sys; r=json.load(sys.stdin)['results']; print(r[0]['pk'] if r else 'NOT_FOUND')")
|
||||
|
||||
echo "Provider PK: ${PROVIDER_PK}"
|
||||
|
||||
CLIENT_ID=$(curl -s \
|
||||
-H "Authorization: Bearer ${AUTHENTIK_BOOTSTRAP_TOKEN}" \
|
||||
"${AUTHENTIK_URL}/api/v3/providers/oauth2/${PROVIDER_PK}/" \
|
||||
| python3 -c "import json,sys; p=json.load(sys.stdin); print(p['client_id'])")
|
||||
|
||||
CLIENT_SECRET=$(curl -s \
|
||||
-H "Authorization: Bearer ${AUTHENTIK_BOOTSTRAP_TOKEN}" \
|
||||
"${AUTHENTIK_URL}/api/v3/providers/oauth2/${PROVIDER_PK}/" \
|
||||
| python3 -c "import json,sys; p=json.load(sys.stdin); print(p['client_secret'])")
|
||||
|
||||
echo "client_id: ${CLIENT_ID}"
|
||||
|
||||
# 2. Get a JWT from Authentik (client credentials flow)
|
||||
# JWT=$(curl -s -X POST \
|
||||
# "${AUTHENTIK_URL}/application/o/talos-cli-shell/token/" \
|
||||
# -d "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&scope=openid" \
|
||||
# | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('access_token', d))")
|
||||
# echo "JWT: ${JWT}..."
|
||||
|
||||
curl -v -s -X POST \
|
||||
"${AUTHENTIK_URL}/application/o/homelab-mac-cli/token/" \
|
||||
-d "grant_type=client_credentials&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&scope=openid"
|
||||
|
||||
# # 3. Exchange JWT for a Vault token
|
||||
# VAULT_TOKEN=$(curl -s -X POST \
|
||||
# "${VAULT_ADDR}/v1/auth/jwt/login" \
|
||||
# -H "Content-Type: application/json" \
|
||||
# -d "{\"jwt\": \"${JWT}\", \"role\": \"shell\"}" \
|
||||
# | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('auth',{}).get('client_token', d))")
|
||||
|
||||
# echo "Vault token: ${VAULT_TOKEN:0:20}..."
|
||||
|
||||
# # 4. Verify the token works
|
||||
# curl -s -H "X-Vault-Token: ${VAULT_TOKEN}" \
|
||||
# "${VAULT_ADDR}/v1/auth/token/lookup-self" \
|
||||
# | python3 -c "import json,sys; d=json.load(sys.stdin); print('policies:', d['data']['policies'])"
|
||||
Reference in New Issue
Block a user