Role: forgejo¶
Self-hosted git server on a dedicated LXC, mirrored to GitHub. Single-user homelab pattern: Forgejo is the source of truth, GitHub is the public-facing mirror.
Host: forgejo — dedicated LXC (CT 109, 192.168.1.249).
Phase 6A complete — 2026-05-04 / 2026-05-05
CT 109 live, Forgejo 11.0.13 (Gitea-API-compat 1.22.0) at https://git.rampancy.cloud, role idempotent. App.ini was patched in-place during 6A.2 (DOMAIN, ROOT_URL, DISABLE_SSH); the role does not template app.ini — wizard generates and operator/runbook patches as needed. All 4 source repos imported and push-mirroring to GitHub.
Why Forgejo, not Gitea / GitLab¶
- Governance: hard fork from Gitea (early 2024) under Codeberg e.V. non-profit, vs Gitea Ltd. for-profit. Velocity on Forgejo's side has been higher post-fork.
- Footprint: ~400 MB idle as a single Go binary. GitLab CE is 4–8 GB just to start.
- APT-managed:
forgejo-contrib/forgejo-debshipsforgejo-sqliteon the official-adjacent contrib repo — drops cleanly into the existingauto_updatesflow. - Push mirrors are first-class: per-repo config in the UI, no plugin dance.
Architecture¶
flowchart LR
subgraph homelab["homelab"]
subgraph forgejoct["forgejo · CT 109 · 192.168.1.249"]
F[forgejo-sqlite<br/>:3000<br/>SQLite + repos]
end
subgraph edgect["edge · CT 107"]
C[Caddy reverse proxy]
end
end
subgraph github["github.com"]
GH[private repo mirrors]
end
L[laptop git client] -->|push https| C
C -->|git.rampancy.cloud → :3000| F
F -->|push-mirror per repo<br/>fine-grained PAT| GH
GH -->|Actions on schedule<br/>e.g. meat-helmet| GH
Install method¶
Forgejo APT repo on code.forgejo.org, signed with the forgejo-contrib keyring. Channel: lts (matches the auto_updates fleet's "security-only" stance better than latest or next).
deb [signed-by=/etc/apt/keyrings/forgejo-apt.asc] \
https://code.forgejo.org/api/packages/apt/debian lts main
The forgejo-sqlite package brings in:
| Path | Purpose |
|---|---|
/usr/bin/forgejo |
binary |
/etc/forgejo/app.ini |
config (created by install wizard, not the package) |
/var/lib/forgejo/ |
data, repos, sqlite DB |
/etc/systemd/system/forgejo.service |
unit |
forgejo user/group |
service account |
Defaults¶
| Var | Default | Notes |
|---|---|---|
forgejo_apt_channel |
lts |
lts / latest / next |
forgejo_package |
forgejo-sqlite |
switch to forgejo + postgres only at scale |
forgejo_listen_port |
3000 |
LAN-only until 6B fronts it via Caddy |
forgejo_external_host |
git.rampancy.cloud |
used by 6B Caddy vhost; ROOT_URL set in wizard |
Per-host overrides in inventory/host_vars/forgejo.yml — currently just auto_updates_origins_extra to pick up Forgejo lts security updates via the daily timer.
Bootstrap pattern¶
Web-UI driven on first visit, matching the beszel_hub precedent. The role installs the package and starts the service; the operator visits http://192.168.1.249:3000 and runs the install wizard:
- Database type: SQLite (default).
- Defaults for paths.
- Forgejo Base URL:
http://192.168.1.249:3000/initially; flipped tohttps://git.rampancy.cloud/after Phase 6B. - Admin account created via wizard using
vault_forgejo_admin_password(vault stores for rebuild documentation; the role doesn't consume it).
Once /etc/forgejo/app.ini exists, subsequent role runs become idempotent (changed=0 under --check --diff).
What this role does NOT do¶
- Does not template
app.ini. The wizard generates it; later phases may layer config on top. - Does not create the admin account programmatically. Web UI handles it.
- Does not configure push mirrors. Per-repo, set up in Phase 6C.
- Does not run a Forgejo Actions runner. Deferred — meat-helmet's GH Actions keep firing on the GitHub side after push-mirror.
Related¶
- Forgejo Setup runbook — execution checklist for 6A–6D.
- Forgejo service page — operational view (URLs, auth, repos, mirror health).
auto_updatesrole — picks up Forgejo lts security updates via the daily timer.caddyrole — fronts the LAN service via TLS in 6B.