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