summaryrefslogtreecommitdiff
path: root/f3s/forgejo
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-26 10:08:45 +0300
committerPaul Buetow <paul@buetow.org>2026-07-26 10:08:45 +0300
commita3692f68bace2dc171dbceed274bc26eeadd38aa (patch)
tree37c4419b4418977e8ebdf2a2df81b1cf819ba852 /f3s/forgejo
parenta3f216fc2cce2299cf66e174fb927cc05dde97dd (diff)
forgejo: fix admin creation and reverse-proxy handling from review
Review of the initial chart turned up one blocker and several correctness gaps: - `just create-admin` could never work. `admin user create` does not prompt; it exits with "must set either password or random-password flag". With the installer locked and registration disabled that recipe was the only way into the instance, so first login was impossible. Use --random-password, which prints the generated password once, rather than passing one on the command line where it would reach shell history and ps. - SSH_DOMAIN advertised code.f3s.buetow.org, but that name resolves to the OpenBSD gateways (443 only) and *.f3s.lan points at the CARP storage VIP, not a k3s node. Every clone URL Forgejo rendered would have been unreachable. Advertise r0.lan.buetow.org, which actually answers on the NodePort. - No REVERSE_PROXY_TRUSTED_PROXIES, so behind relayd -> Traefik every request was attributed to the Traefik pod IP: real client IPs absent from the audit trail and per-IP rate limiting defeated, on an internet-facing instance. - Pin both PVCs with volumeName. The 1Gi config claim also matched the 20Gi data PV, leaving the choice to the binder's smallest-sufficient heuristic. README: add the gogios task to the frontends deploy (the new host otherwise gets no TLS/HTTP checks); drop the false claim that pushing alone deploys the Application, since nothing watches f3s/argocd-apps/; keep .nfs-sentinel at 0644 instead of sweeping it into a recursive 0750; note that the real certificate is only issued on the gateway holding the DNS master IP. Still not activated. Re-verified: helm template renders, and all 9 objects pass kubectl apply --dry-run=server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'f3s/forgejo')
-rw-r--r--f3s/forgejo/Justfile11
-rw-r--r--f3s/forgejo/README.md28
-rw-r--r--f3s/forgejo/helm-chart/templates/deployment.yaml16
-rw-r--r--f3s/forgejo/helm-chart/templates/persistent-volume.yaml5
4 files changed, 50 insertions, 10 deletions
diff --git a/f3s/forgejo/Justfile b/f3s/forgejo/Justfile
index ee78a62..e7c4cad 100644
--- a/f3s/forgejo/Justfile
+++ b/f3s/forgejo/Justfile
@@ -34,10 +34,17 @@ restart:
@echo "Restarting forgejo..."
kubectl rollout restart -n {{NAMESPACE}} deployment/forgejo
-# One-time after the first successful start. Prompts for the password.
+# One-time after the first successful start. This is the ONLY way in: the web
+# installer is locked and registration is disabled.
+# `admin user create` does not prompt -- without --password or --random-password
+# it exits with "must set either password or random-password flag". Use a
+# generated password (printed once, to stdout) rather than passing one on the
+# command line where it would land in shell history and ps output.
create-admin username="paul" email="paul.buetow@gmail.com":
+ @echo "The generated password is printed below -- copy it now, it is shown only once."
kubectl exec -n {{NAMESPACE}} -it deploy/forgejo -- \
- forgejo admin user create --admin --username {{username}} --email {{email}}
+ forgejo admin user create --admin --username {{username}} --email {{email}} \
+ --random-password
# Open a shell as the git user inside the running instance.
shell:
diff --git a/f3s/forgejo/README.md b/f3s/forgejo/README.md
index ecc35a6..9758b29 100644
--- a/f3s/forgejo/README.md
+++ b/f3s/forgejo/README.md
@@ -49,9 +49,14 @@ doas mkdir -p /data/nfs/k3svolumes/forgejo/data /data/nfs/k3svolumes/forgejo/con
doas touch /data/nfs/k3svolumes/forgejo/data/.nfs-sentinel \
/data/nfs/k3svolumes/forgejo/config/.nfs-sentinel
doas chown -R 1000:1000 /data/nfs/k3svolumes/forgejo
-doas chmod -R 0750 /data/nfs/k3svolumes/forgejo
+doas chmod 0750 /data/nfs/k3svolumes/forgejo/data /data/nfs/k3svolumes/forgejo/config
+doas chmod 0644 /data/nfs/k3svolumes/forgejo/data/.nfs-sentinel \
+ /data/nfs/k3svolumes/forgejo/config/.nfs-sentinel
```
+The sentinel files are 0644 per `f3s/docs/nfs-sentinel-initcontainer.md` — do not
+sweep them up in a recursive chmod of the directories.
+
The PVs use `type: Directory`, so the pod will not schedule until these exist.
### 2. Publish the hostname
@@ -65,14 +70,21 @@ so the certificate has to exist first:
```sh
cd frontends
-rex -H blowfish.buetow.org:2 nsd httpd acme acme_invoke relayd
-rex -H fishfinger.buetow.org:2 nsd httpd acme acme_invoke relayd
+rex -H blowfish.buetow.org:2 nsd httpd acme acme_invoke relayd gogios
+rex -H fishfinger.buetow.org:2 nsd httpd acme acme_invoke relayd gogios
```
`acme.sh` copies the `foo.zone` cert as a placeholder for any host that has none
-yet, so relayd will still start on the first pass; the real certificate arrives
-on the same run. Deploying one gateway at a time avoids restarting both public
-frontends simultaneously.
+yet, so relayd will still start on the first pass. The *real* certificate is only
+issued on the gateway currently holding the DNS master IP — `acme.sh` skips
+`acme-client` on the standby, which keeps the placeholder until a failover. That
+is normal; the standby is not serving the name yet.
+
+`gogios` is included because the TLS and HTTP checks for the new host are
+rendered from `@acme_hosts`; without it `code.f3s.buetow.org` gets no monitoring.
+
+Deploying one gateway at a time avoids restarting both public frontends
+simultaneously.
### 3. Deploy
@@ -80,7 +92,9 @@ frontends simultaneously.
kubectl apply -f ../argocd-apps/services/forgejo.yaml
```
-Or just push — ArgoCD picks it up automatically.
+This apply is required and cannot be skipped: there is no app-of-apps or
+ApplicationSet watching `f3s/argocd-apps/`, so pushing the repo alone does
+nothing. Once the Application exists, later edits to the chart do auto-sync.
### 4. Create the admin user
diff --git a/f3s/forgejo/helm-chart/templates/deployment.yaml b/f3s/forgejo/helm-chart/templates/deployment.yaml
index 132fed4..ba19dfa 100644
--- a/f3s/forgejo/helm-chart/templates/deployment.yaml
+++ b/f3s/forgejo/helm-chart/templates/deployment.yaml
@@ -96,11 +96,25 @@ spec:
value: "true"
- name: FORGEJO__server__SSH_LISTEN_PORT
value: "2222"
+ # SSH_DOMAIN is deliberately NOT code.f3s.buetow.org: that name resolves
+ # to the OpenBSD gateways, which forward 443 only, and *.f3s.lan points
+ # at the CARP storage VIP rather than a k3s node. Advertise a node that
+ # actually answers on the NodePort, so the clone URLs Forgejo renders
+ # are ones that work.
- name: FORGEJO__server__SSH_DOMAIN
- value: "code.f3s.buetow.org"
+ value: "r0.lan.buetow.org"
- name: FORGEJO__server__SSH_PORT
value: "30222"
+ # Behind relayd -> Traefik, Forgejo's default trusts only 127.0.0.0/8,
+ # so every request would be attributed to the Traefik pod IP: real client
+ # IPs lost from the audit trail and per-IP rate limiting defeated. That
+ # matters here because the instance is internet-facing. 10.42.0.0/16 is
+ # the k3s pod CIDR; Traefik is already configured to pass the correct
+ # X-Forwarded-For (see f3s/traefik-config).
+ - name: FORGEJO__security__REVERSE_PROXY_TRUSTED_PROXIES
+ value: "10.42.0.0/16"
+
# 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.
diff --git a/f3s/forgejo/helm-chart/templates/persistent-volume.yaml b/f3s/forgejo/helm-chart/templates/persistent-volume.yaml
index 40c8788..c17446d 100644
--- a/f3s/forgejo/helm-chart/templates/persistent-volume.yaml
+++ b/f3s/forgejo/helm-chart/templates/persistent-volume.yaml
@@ -46,6 +46,10 @@ metadata:
namespace: services
spec:
storageClassName: ""
+ # Pinned explicitly: without volumeName the 1Gi config claim below would also
+ # be a valid match for this 20Gi volume, leaving the binding to the
+ # smallest-sufficient-PV heuristic.
+ volumeName: forgejo-data-pv
accessModes:
- ReadWriteOnce
resources:
@@ -59,6 +63,7 @@ metadata:
namespace: services
spec:
storageClassName: ""
+ volumeName: forgejo-config-pv
accessModes:
- ReadWriteOnce
resources: