Public repos are now browsable/clonable anonymously (HTTPS) and over SSH, proxied through the Hetzner VPS. Unlike other public apps this is deliberately not behind the oauth2-proxy/Zitadel gate, since Forgejo already enforces its own per-repo visibility. Root redirects to the .profile landing page. Documents the REQUIRE_SIGNIN_VIEW gotcha and the Zitadel OIDC redirect_uri fix needed whenever FORGEJO_DOMAIN changes.
172 lines
7.7 KiB
Markdown
172 lines
7.7 KiB
Markdown
# Forgejo
|
|
|
|
Self-hosted Git service (soft fork of Gitea) with Postgres.
|
|
|
|
## Persistent Data
|
|
|
|
Split between local disk and the UNAS `forgejo_data` share (see
|
|
[nfs-mounts](../../docs/unas/nfs-mounts.md) — you'll need to add this share
|
|
following that guide, it isn't in the table yet):
|
|
|
|
- `forgejo_data` named volume (local disk) — app config, SSH host keys,
|
|
session store, Bleve search index.
|
|
- `forgejo_db_data` named volume (local disk) — Postgres data directory.
|
|
Databases don't belong on NFS regardless of engine (locking/fsync
|
|
semantics), so this stays local even though Forgejo isn't using SQLite.
|
|
- Share dirs, NFS-mounted on the Proxmox host and bind-mounted into the LXC
|
|
at `FORGEJO_DATA_ROOT`:
|
|
- `repositories/` — bare git repo objects. This is what grows over time.
|
|
- `lfs/` — Git LFS objects.
|
|
- `attachments/` — issue/PR attachments.
|
|
- `avatars/` — user and repo avatars.
|
|
|
|
Git's own protocol tolerates NFS latency far better than a database does,
|
|
so this bulk data is a safe, capacity-friendly NAS candidate.
|
|
|
|
The share binds use `create_host_path: false`, so the stack refuses to start
|
|
if the share is not mounted, rather than silently writing into an empty
|
|
directory that later needs merging back in.
|
|
|
|
## Installation
|
|
|
|
1. Create `.env` if it does not exist:
|
|
|
|
```bash
|
|
cp -n docker-compose-files/forgejo/.env.sample docker-compose-files/forgejo/.env
|
|
```
|
|
|
|
2. Set the required values in `docker-compose-files/forgejo/.env` — at
|
|
minimum `FORGEJO_ROOT_URL`, `FORGEJO_DOMAIN`, `FORGEJO_DB_PASSWORD`, and
|
|
`FORGEJO_DATA_ROOT`.
|
|
|
|
3. Add the UNAS share and bind-mount it into the LXC, following
|
|
[nfs-mounts.md](../../docs/unas/nfs-mounts.md) (add `forgejo_data` to the
|
|
share list there, same `No Root Squash` treatment as `paperless_data`):
|
|
|
|
```bash
|
|
# On the Proxmox host, once the share is mounted at /mnt/unas/forgejo_data
|
|
pct set <vmid> -mp0 /mnt/unas/forgejo_data,mp=/mnt/unas/forgejo_data
|
|
mkdir -p /mnt/unas/forgejo_data/{repositories,lfs,attachments,avatars}
|
|
chown -R 101000:101000 /mnt/unas/forgejo_data
|
|
```
|
|
|
|
4. Deploy with `make`:
|
|
|
|
```bash
|
|
make deploy-forgejo
|
|
```
|
|
|
|
Manual equivalent:
|
|
|
|
```bash
|
|
./substitute_env.sh docker-compose-files/forgejo/template.yaml docker-compose.forgejo.yml .env docker-compose-files/forgejo/.env
|
|
docker compose -f docker-compose.forgejo.yml config --quiet
|
|
docker compose -f docker-compose.forgejo.yml up -d
|
|
```
|
|
|
|
5. Open `http://<host-ip>:3000`, create the first (admin) account, and
|
|
confirm repo creation/push/clone works.
|
|
|
|
## Reverse Proxy
|
|
|
|
Add a block for `git.<domain>` (port `FORGEJO_HTTP_PORT`) to
|
|
`docker-compose-files/caddy/Caddyfile_template`, then `make reload-caddy`.
|
|
Git-over-SSH (`FORGEJO_SSH_PORT`) is not proxyable through Caddy — expose that
|
|
port directly if you want SSH clone/push from outside the LAN.
|
|
|
|
`FORGEJO_SSH_PORT` defaults to `2222` because the LXC's own admin sshd
|
|
already owns port 22. This instance was later moved to the standard port —
|
|
the LXC's admin sshd got relocated to 2200 first (systemd `ssh.socket`
|
|
override), freeing 22 for Forgejo's git-SSH. See `.env.sample` for the
|
|
one-time steps if you want to do the same elsewhere.
|
|
|
|
Since SSH bypasses Caddy, this instance instead gets a plain Technitium `A`
|
|
record — `forgejo.brianpooe.com` → `10.0.10.18` — so clone URLs read
|
|
`ssh://git@forgejo.brianpooe.com/<owner>/<repo>.git` instead of the raw
|
|
LXC IP. Add this in the Technitium admin UI (or via its API) as a normal
|
|
zone record; it isn't behind the `*.home.brianpooe.com` → Caddy wildcard.
|
|
|
|
## Public Exposure
|
|
|
|
This instance is publicly reachable at `git.brianpooe.com` (HTTPS) and
|
|
`ssh://git@forgejo.brianpooe.com` (SSH), proxied through the Hetzner VPS —
|
|
see `~/netbird-vps/docs/netbird-acl-playbook.md` and `public-app-sso.md` on
|
|
the VPS for the edge side. Unlike other public apps (koffan, IT-Tools), this
|
|
one is deliberately **not** behind the oauth2-proxy/Zitadel SSO gate: public
|
|
repos need to be browsable/clonable with no login, like GitHub. Forgejo
|
|
enforces its own per-repo visibility regardless of network path, so private
|
|
repos stay private without an extra gate.
|
|
|
|
One canonical hostname everywhere — `FORGEJO_DOMAIN`/`FORGEJO_ROOT_URL` are
|
|
`git.brianpooe.com`, not a `.home.` subdomain — resolved via split-horizon
|
|
DNS: Technitium answers it directly on the LAN (own zone, `A` record ->
|
|
`10.0.15.5`), public DNS (`*.brianpooe.com` wildcard) sends it to the VPS.
|
|
Same pattern for `forgejo.brianpooe.com` (`A` record -> `10.0.10.18`
|
|
directly, bypassing Caddy, since SSH can't be proxied).
|
|
|
|
Two settings matter for a public instance and are easy to get wrong:
|
|
|
|
- `FORGEJO_REQUIRE_SIGNIN_VIEW` **must be `false`**. When `true` it forces
|
|
every page (including `/explore` and the `.profile` landing page) through
|
|
`/user/login` for anonymous visitors, regardless of `LANDING_PAGE` — it
|
|
silently defeats "outsiders can browse public repos."
|
|
- `FORGEJO_LANDING_PAGE=explore` so anonymous visitors land on the public
|
|
repo listing instead of a login page. On top of that, the Caddy vhost for
|
|
`git.brianpooe.com` (`docker-compose-files/caddy/Caddyfile_template`) has
|
|
`redir / /brianpooe 302`, so the *root* domain goes straight to the
|
|
`.profile` landing page instead of `/explore` — `/explore` is still one
|
|
click away via the nav sidebar. Update/remove that redirect if the
|
|
account name or desired landing target ever changes.
|
|
|
|
**Changing `FORGEJO_DOMAIN`/`FORGEJO_ROOT_URL` also requires updating the
|
|
Zitadel OIDC app's redirect URI**, or sign-in breaks with
|
|
`invalid_request: redirect_uri is missing in the client configuration`.
|
|
Forgejo's OIDC callback is always `<ROOT_URL>/user/oauth2/zitadel/callback`
|
|
— this is Forgejo's own direct Zitadel app (project **Homelab**, app
|
|
**Forgejo**), separate from the shared oauth2-proxy app used by
|
|
koffan/it-tools. Update it via the Zitadel console or, from the VPS:
|
|
|
|
```bash
|
|
PAT=$(cat ~/netbird-vps/zitadel/zitadel-data/admin.pat)
|
|
curl -H "Authorization: Bearer $PAT" -H "Content-Type: application/json" \
|
|
-X PUT "https://zitadel.brianpooe.com/management/v1/projects/345934587441971203/apps/382370882418376707/oidc_config" -d '{
|
|
"redirectUris": ["https://git.brianpooe.com/user/oauth2/zitadel/callback"],
|
|
"postLogoutRedirectUris": ["https://git.brianpooe.com"],
|
|
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
|
|
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"],
|
|
"appType": "OIDC_APP_TYPE_WEB",
|
|
"authMethodType": "OIDC_AUTH_METHOD_TYPE_BASIC",
|
|
"accessTokenType": "OIDC_TOKEN_TYPE_BEARER"
|
|
}'
|
|
```
|
|
|
|
Public git-over-SSH reaches the LXC via a raw TCP forward on the VPS
|
|
(`~/netbird-vps/ssh-forward/`, a `socat` container in `network_mode: host`
|
|
listening on the VPS's now-freed port 22) over a NetBird route added
|
|
specifically for this (`10.0.10.18/32` on the `homelab-lan` network, routed
|
|
through the `pi-caddy` peer — see the VPS's `netbird-acl-playbook.md`
|
|
"Model 2" pattern). The VPS's own admin sshd was relocated to port 2200 to
|
|
free port 22 for this forward.
|
|
|
|
## Notes
|
|
|
|
- Pin `FORGEJO_VERSION` to a specific major (as shipped, `10`) rather than
|
|
floating — Forgejo releases can carry DB migrations, so an unpinned tag
|
|
risks pulling a breaking bump on a routine `docker compose pull`.
|
|
- `FORGEJO__security__INSTALL_LOCK=true` is baked into the template since all
|
|
DB config is supplied via environment — this skips the web installer so a
|
|
stray first visitor can't reconfigure the instance.
|
|
|
|
## Useful Commands
|
|
|
|
```bash
|
|
# Tail logs
|
|
docker compose -f docker-compose.forgejo.yml logs -f forgejo
|
|
|
|
# Create an additional user without open registration
|
|
docker exec -it forgejo forgejo admin user create --username <name> --email <email> --random-password
|
|
|
|
# Recreate after an image update
|
|
docker compose -f docker-compose.forgejo.yml pull
|
|
docker compose -f docker-compose.forgejo.yml up -d
|
|
```
|