summaryrefslogtreecommitdiff
path: root/f3s/forgejo/helm-chart/templates
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-26 09:57:28 +0300
committerPaul Buetow <paul@buetow.org>2026-07-26 09:57:28 +0300
commita3f216fc2cce2299cf66e174fb927cc05dde97dd (patch)
treec8b89ccbd7c81c3a11a6a6e7872efc77b0b7dfb9 /f3s/forgejo/helm-chart/templates
parentaaa786fcea8de25be1bd2eff8bc35afd1bc7482a (diff)
forgejo: add a standalone Forgejo install at code.f3s.buetow.org
Deliberately independent of the cgit git-server: separate namespace (services), separate NFS volumes, separate SSH NodePort (30222 vs 30022), and no shared storage. cgit keeps serving the existing 80 bare repos at c-git.f3s.buetow.org and is not touched. Forgejo starts empty; repos get migrated by hand later. ArgoCD deliberately keeps reading conf.git from the existing git-server, so Forgejo has no consumers and cannot take cluster deploys down with it. SQLite rather than a PostgreSQL pod: single writer (replicas 1 + Recreate) and NFSv4.2 does real byte-range locking, so the usual SQLite-on-NFS failure mode does not apply. Uses the -rootless image so the pod runs wholly as UID 1000 with all capabilities dropped, and both volumes carry the .nfs-sentinel guard. The installer is locked and registration disabled because the instance is reachable from the internet; the admin account is created via the CLI. code.f3s.buetow.org added to @f3s_hosts, which drives the DNS zone, the relayd route, the ACME cert and the gogios checks. Not yet activated: the ArgoCD Application still needs applying, the NFS directories creating, and the frontends deploying. See f3s/forgejo/README.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'f3s/forgejo/helm-chart/templates')
-rw-r--r--f3s/forgejo/helm-chart/templates/deployment.yaml152
-rw-r--r--f3s/forgejo/helm-chart/templates/ingress.yaml54
-rw-r--r--f3s/forgejo/helm-chart/templates/persistent-volume.yaml66
-rw-r--r--f3s/forgejo/helm-chart/templates/service.yaml36
4 files changed, 308 insertions, 0 deletions
diff --git a/f3s/forgejo/helm-chart/templates/deployment.yaml b/f3s/forgejo/helm-chart/templates/deployment.yaml
new file mode 100644
index 0000000..132fed4
--- /dev/null
+++ b/f3s/forgejo/helm-chart/templates/deployment.yaml
@@ -0,0 +1,152 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: forgejo
+ namespace: services
+ labels:
+ app: forgejo
+spec:
+ replicas: 1
+ # Recreate so the old pod fully terminates before the new one starts —
+ # avoids NFS-lock races on the hostPath-backed PVC during rolling updates.
+ # This also matters for SQLite: exactly one process may hold the database.
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: forgejo
+ template:
+ metadata:
+ labels:
+ app: forgejo
+ spec:
+ securityContext:
+ # The -rootless image runs entirely as the unprivileged git user (1000).
+ runAsUser: 1000
+ runAsGroup: 1000
+ fsGroup: 1000
+ initContainers:
+ - name: nfs-check-data
+ image: busybox:stable
+ command:
+ - sh
+ - -c
+ - |
+ test -f /mnt/.nfs-sentinel || (
+ echo "ERROR: NFS sentinel missing at /mnt/.nfs-sentinel"
+ echo "refusing to start; node likely has NFS unmounted"
+ echo "pod would otherwise bind-mount the local-XFS shadow"
+ exit 1
+ )
+ volumeMounts:
+ - name: forgejo-data
+ mountPath: /mnt
+ readOnly: true
+ - name: nfs-check-config
+ image: busybox:stable
+ command:
+ - sh
+ - -c
+ - |
+ test -f /mnt/.nfs-sentinel || (
+ echo "ERROR: NFS sentinel missing at /mnt/.nfs-sentinel"
+ echo "refusing to start; node likely has NFS unmounted"
+ echo "pod would otherwise bind-mount the local-XFS shadow"
+ exit 1
+ )
+ volumeMounts:
+ - name: forgejo-config
+ mountPath: /mnt
+ readOnly: true
+
+ containers:
+ - name: forgejo
+ image: codeberg.org/forgejo/forgejo:16.0.1-rootless
+ imagePullPolicy: IfNotPresent
+ ports:
+ - containerPort: 3000
+ name: http
+ protocol: TCP
+ - containerPort: 2222
+ name: ssh
+ protocol: TCP
+ env:
+ # SQLite rather than a separate PostgreSQL pod: this is a single-writer
+ # instance (replicas 1 + Recreate), and NFSv4.2 does real byte-range
+ # locking, so the usual SQLite-on-NFS corruption mode does not apply.
+ # Revisit if this ever needs more than one replica.
+ - name: FORGEJO__database__DB_TYPE
+ value: "sqlite3"
+ - name: FORGEJO__database__PATH
+ value: "/var/lib/gitea/data/forgejo.db"
+
+ # Public identity. ROOT_URL must match what relayd terminates TLS for,
+ # otherwise Forgejo generates clone URLs and redirects on the wrong host.
+ - name: FORGEJO__server__DOMAIN
+ value: "code.f3s.buetow.org"
+ - name: FORGEJO__server__ROOT_URL
+ value: "https://code.f3s.buetow.org/"
+ - name: FORGEJO__server__HTTP_PORT
+ value: "3000"
+
+ # Built-in SSH server. SSH_LISTEN_PORT is what the container binds;
+ # SSH_PORT is what Forgejo advertises in clone URLs, i.e. the NodePort
+ # users actually reach. git-server already owns 30022, so this is 30222.
+ - name: FORGEJO__server__START_SSH_SERVER
+ value: "true"
+ - name: FORGEJO__server__SSH_LISTEN_PORT
+ value: "2222"
+ - name: FORGEJO__server__SSH_DOMAIN
+ value: "code.f3s.buetow.org"
+ - name: FORGEJO__server__SSH_PORT
+ value: "30222"
+
+ # This instance is reachable from the public internet through relayd.
+ # Lock the installer (otherwise the first visitor gets the setup wizard)
+ # and keep signups closed; create the admin with the CLI, see README.
+ - name: FORGEJO__security__INSTALL_LOCK
+ value: "true"
+ - name: FORGEJO__service__DISABLE_REGISTRATION
+ value: "true"
+
+ # Catches stale NFS file handles (ESTALE) after an NFS server restart,
+ # which a plain HTTP probe would not notice until a request touched disk.
+ livenessProbe:
+ exec:
+ command: ["test", "-f", "/var/lib/gitea/.nfs-sentinel"]
+ initialDelaySeconds: 60
+ periodSeconds: 30
+ failureThreshold: 3
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /api/healthz
+ port: 3000
+ initialDelaySeconds: 20
+ periodSeconds: 15
+ failureThreshold: 3
+ timeoutSeconds: 5
+ volumeMounts:
+ - name: forgejo-data
+ mountPath: /var/lib/gitea
+ - name: forgejo-config
+ mountPath: /etc/gitea
+ securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop: ["ALL"]
+ resources:
+ requests:
+ cpu: 100m
+ memory: 256Mi
+ limits:
+ cpu: 1000m
+ memory: 1Gi
+
+ volumes:
+ - name: forgejo-data
+ persistentVolumeClaim:
+ claimName: forgejo-data-pvc
+ - name: forgejo-config
+ persistentVolumeClaim:
+ claimName: forgejo-config-pvc
diff --git a/f3s/forgejo/helm-chart/templates/ingress.yaml b/f3s/forgejo/helm-chart/templates/ingress.yaml
new file mode 100644
index 0000000..4916098
--- /dev/null
+++ b/f3s/forgejo/helm-chart/templates/ingress.yaml
@@ -0,0 +1,54 @@
+# Forgejo web UI ingress.
+#
+# code.f3s.buetow.org must also be listed in @f3s_hosts in frontends/Rexfile --
+# that array drives the DNS zone, the relayd routing rule and the ACME cert.
+# Adding it here alone is not enough to make the name resolve or serve TLS.
+#
+# cgit stays where it is, at c-git.f3s.buetow.org; the two are unrelated.
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: forgejo-ingress
+ namespace: services
+ annotations:
+ spec.ingressClassName: traefik
+ traefik.ingress.kubernetes.io/router.entrypoints: web
+spec:
+ rules:
+ - host: code.f3s.buetow.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: forgejo
+ port:
+ number: 80
+---
+# LAN ingress. *.f3s.lan resolves to the storage VIP via Pi-hole, and Traefik
+# terminates TLS here with the shared f3s-lan-tls cert.
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: forgejo-ingress-lan
+ namespace: services
+ annotations:
+ spec.ingressClassName: traefik
+ traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
+spec:
+ tls:
+ - hosts:
+ - code.f3s.lan.buetow.org
+ secretName: f3s-lan-tls
+ rules:
+ - host: code.f3s.lan.buetow.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: forgejo
+ port:
+ number: 80
diff --git a/f3s/forgejo/helm-chart/templates/persistent-volume.yaml b/f3s/forgejo/helm-chart/templates/persistent-volume.yaml
new file mode 100644
index 0000000..40c8788
--- /dev/null
+++ b/f3s/forgejo/helm-chart/templates/persistent-volume.yaml
@@ -0,0 +1,66 @@
+# Forgejo storage. Deliberately separate from the git-server/cgit volume:
+# Forgejo owns its own repositories under /data/nfs/k3svolumes/forgejo/data and
+# never touches the 80 bare repos cgit serves out of
+# /data/nfs/k3svolumes/git-server/repos.
+#
+# Both directories must exist and contain a .nfs-sentinel file before the pod
+# starts -- see the initContainers in deployment.yaml and the README.
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: forgejo-data-pv
+spec:
+ capacity:
+ storage: 20Gi
+ volumeMode: Filesystem
+ accessModes:
+ - ReadWriteOnce
+ persistentVolumeReclaimPolicy: Retain
+ hostPath:
+ path: /data/nfs/k3svolumes/forgejo/data
+ type: Directory
+---
+# app.ini lives here, and with it the SECRET_KEY and INTERNAL_TOKEN that Forgejo
+# generates on first start. Losing this volume invalidates existing sessions and
+# any stored credentials, so it is kept on NFS (ZFS-snapshotted) rather than in
+# an emptyDir.
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: forgejo-config-pv
+spec:
+ capacity:
+ storage: 1Gi
+ volumeMode: Filesystem
+ accessModes:
+ - ReadWriteOnce
+ persistentVolumeReclaimPolicy: Retain
+ hostPath:
+ path: /data/nfs/k3svolumes/forgejo/config
+ type: Directory
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: forgejo-data-pvc
+ namespace: services
+spec:
+ storageClassName: ""
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 20Gi
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: forgejo-config-pvc
+ namespace: services
+spec:
+ storageClassName: ""
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 1Gi
diff --git a/f3s/forgejo/helm-chart/templates/service.yaml b/f3s/forgejo/helm-chart/templates/service.yaml
new file mode 100644
index 0000000..7c3bfe7
--- /dev/null
+++ b/f3s/forgejo/helm-chart/templates/service.yaml
@@ -0,0 +1,36 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: forgejo
+ namespace: services
+ labels:
+ app: forgejo
+spec:
+ selector:
+ app: forgejo
+ ports:
+ - name: http
+ protocol: TCP
+ port: 80
+ targetPort: 3000
+ type: ClusterIP
+---
+# SSH for git clone/push. NodePort 30222 -- git-server/cgit already owns 30022,
+# and the two installs are intentionally independent.
+apiVersion: v1
+kind: Service
+metadata:
+ name: forgejo-ssh
+ namespace: services
+ labels:
+ app: forgejo
+spec:
+ selector:
+ app: forgejo
+ ports:
+ - name: ssh
+ protocol: TCP
+ port: 2222
+ targetPort: 2222
+ nodePort: 30222
+ type: NodePort