1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
# Forgejo
Self-hosted git forge at `https://code.f3s.buetow.org`, running in the `services`
namespace of the f3s k3s cluster.
## Relationship to the cgit git-server
**These two installs are deliberately independent.** Nothing here touches
`f3s/git-server/`:
| | cgit / git-server | Forgejo |
|---|---|---|
| Web UI | `c-git.f3s.buetow.org` | `code.f3s.buetow.org` |
| Namespace | `cicd` | `services` |
| Repo storage | `/data/nfs/k3svolumes/git-server/repos` (80 bare repos) | `/data/nfs/k3svolumes/forgejo/data` (starts empty) |
| SSH NodePort | 30022 | 30222 |
ArgoCD keeps reading `conf.git` from the existing git-server
(`http://git-server.cicd.svc.cluster.local/conf.git`). Forgejo has no consumers,
so if it breaks, cluster deploys are unaffected. Repositories are migrated by
hand, one at a time, whenever you feel like it — there is no bulk import.
## Architecture
```
Internet -> relayd (OpenBSD GW, TLS) -> WireGuard -> Traefik -> forgejo svc:80 -> pod:3000
git+ssh -> NodePort 30222 -----------------------------------> pod:2222
|
/var/lib/gitea (repos, SQLite) NFS -> ZFS
/etc/gitea (app.ini, secrets)
```
- Image `codeberg.org/forgejo/forgejo:16.0.1-rootless` — the rootless variant, so
the whole pod runs as UID/GID 1000 with all capabilities dropped.
- SQLite, not PostgreSQL. Single writer (`replicas: 1` + `Recreate`), and NFSv4.2
does real byte-range locking, so the classic SQLite-on-NFS corruption mode does
not apply. Revisit if this ever needs to scale out.
- Both volumes carry the standard `.nfs-sentinel` guard so the pod refuses to
start against the local-XFS shadow if a node has NFS unmounted.
## Initial setup
### 1. Create the storage directories
On the current CARP storage master (check with `ifconfig | grep MASTER` on f0/f1):
```sh
doas mkdir -p /data/nfs/k3svolumes/forgejo/data /data/nfs/k3svolumes/forgejo/config
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 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
`code.f3s.buetow.org` must be added to `@f3s_hosts` in `frontends/Rexfile`. That
one array drives the DNS zone, the relayd routing rule, the ACME certificate and
the gogios monitoring checks.
Deploy order matters — relayd loads `tls keypair code.f3s.buetow.org` at startup,
so the certificate has to exist first:
```sh
cd frontends
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 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
```sh
kubectl apply -f ../argocd-apps/services/forgejo.yaml
```
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
The web installer is locked (`INSTALL_LOCK=true`) and registration is disabled,
because this instance is reachable from the public internet. Create the first
account from the CLI:
```sh
just create-admin
```
## Repository URLs
```sh
# HTTPS
git clone https://code.f3s.buetow.org/<user>/<repo>.git
# SSH (NodePort; LAN only unless you forward it)
git clone ssh://git@r0.lan.buetow.org:30222/<user>/<repo>.git
```
## Operations
```sh
just status # pods, services, ingress, PVCs, ArgoCD sync
just logs # follow logs
just restart # rollout restart
just shell # shell inside the pod
just port-forward # reach the UI on localhost:3000
```
## Backup
Covered by the ZFS snapshots and zrepl replication of `/data/nfs`. The SQLite
database and `app.ini` both live on that volume. To restore: roll back the ZFS
snapshot and restart the deployment.
Note that `/etc/gitea/app.ini` holds `SECRET_KEY` and `INTERNAL_TOKEN`, generated
on first start. Restoring the data volume without the matching config volume
invalidates sessions and stored credentials.
|