feat(forgejo): expose publicly at git.brianpooe.com with SSO-free access
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.
This commit is contained in:
parent
1c2f4a1c81
commit
fef521909b
5 changed files with 126 additions and 6 deletions
|
|
@ -173,10 +173,17 @@ koffan.{{CADDY_BASE_DOMAIN:-home.example.com}} {
|
||||||
reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.12:8080
|
reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.12:8080
|
||||||
}
|
}
|
||||||
|
|
||||||
# Forgejo (LXC 114, .18). Git-over-SSH (port 2222) is not proxyable through
|
# Forgejo (LXC 114, .18) -- publicly exposed at git.brianpooe.com (proxied via
|
||||||
# Caddy — that port is exposed directly on the LXC for SSH clone/push.
|
# the VPS over NetBird for outside visitors) as well as reachable directly on
|
||||||
git.{{CADDY_BASE_DOMAIN:-home.example.com}} {
|
# the LAN via split-horizon DNS. One canonical hostname everywhere, so this
|
||||||
|
# is NOT under CADDY_BASE_DOMAIN like other internal-only apps.
|
||||||
|
# Git-over-SSH is not proxyable through Caddy -- forgejo.brianpooe.com
|
||||||
|
# resolves straight to the LXC (10.0.10.18) instead, bypassing Caddy entirely.
|
||||||
|
# Root redirects to the .profile landing page instead of the repo explore
|
||||||
|
# list -- see the Forgejo README's "Public Exposure" section.
|
||||||
|
git.brianpooe.com {
|
||||||
import common
|
import common
|
||||||
|
redir / /brianpooe 302
|
||||||
reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.18:3000
|
reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.18:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@ FORGEJO_DOMAIN=git.home.example.com
|
||||||
# Hostname shown in the web UI's SSH clone URL. Must resolve directly to
|
# Hostname shown in the web UI's SSH clone URL. Must resolve directly to
|
||||||
# this LXC (not through Caddy — SSH can't be proxied), so it needs its own
|
# this LXC (not through Caddy — SSH can't be proxied), so it needs its own
|
||||||
# DNS A record rather than reusing FORGEJO_DOMAIN's wildcard CNAME to Caddy.
|
# DNS A record rather than reusing FORGEJO_DOMAIN's wildcard CNAME to Caddy.
|
||||||
|
#
|
||||||
|
# If this instance is publicly exposed, FORGEJO_DOMAIN/FORGEJO_SSH_DOMAIN
|
||||||
|
# should be the same public-facing names used everywhere (LAN and internet),
|
||||||
|
# resolved via split-horizon DNS: an internal DNS server answers them
|
||||||
|
# directly for LAN clients (its own zone/record, not the wildcard CNAME
|
||||||
|
# above), public DNS points them at your public edge for everyone else. See
|
||||||
|
# the Forgejo README's "Public Exposure" section for the full pattern.
|
||||||
FORGEJO_SSH_DOMAIN=forgejo.home.example.com
|
FORGEJO_SSH_DOMAIN=forgejo.home.example.com
|
||||||
|
|
||||||
# Generate with: openssl rand -base64 32
|
# Generate with: openssl rand -base64 32
|
||||||
|
|
@ -28,4 +35,13 @@ FORGEJO_DATA_ROOT=/mnt/unas/forgejo_data
|
||||||
# false = open self-signup; leave true and create accounts with
|
# false = open self-signup; leave true and create accounts with
|
||||||
# `docker exec forgejo forgejo admin user create ...` instead.
|
# `docker exec forgejo forgejo admin user create ...` instead.
|
||||||
FORGEJO_DISABLE_REGISTRATION=true
|
FORGEJO_DISABLE_REGISTRATION=true
|
||||||
|
# If this instance is ever exposed publicly with the intent of letting
|
||||||
|
# outsiders browse public repos anonymously (GitHub-style), this MUST stay
|
||||||
|
# false. true forces every page -- including /explore and any .profile
|
||||||
|
# landing page -- through /user/login for anonymous visitors, regardless of
|
||||||
|
# FORGEJO_LANDING_PAGE. It silently defeats public repo browsing.
|
||||||
FORGEJO_REQUIRE_SIGNIN_VIEW=false
|
FORGEJO_REQUIRE_SIGNIN_VIEW=false
|
||||||
|
# login = anonymous visitors land on the sign-in page (LAN-only/private use).
|
||||||
|
# explore = anonymous visitors land on the public repo listing, like GitHub's
|
||||||
|
# logged-out homepage. Use explore if this instance is publicly exposed.
|
||||||
|
FORGEJO_LANDING_PAGE=login
|
||||||
|
|
|
||||||
|
|
@ -81,11 +81,73 @@ override), freeing 22 for Forgejo's git-SSH. See `.env.sample` for the
|
||||||
one-time steps if you want to do the same elsewhere.
|
one-time steps if you want to do the same elsewhere.
|
||||||
|
|
||||||
Since SSH bypasses Caddy, this instance instead gets a plain Technitium `A`
|
Since SSH bypasses Caddy, this instance instead gets a plain Technitium `A`
|
||||||
record — `forgejo.home.brianpooe.com` → `10.0.10.18` — so clone URLs read
|
record — `forgejo.brianpooe.com` → `10.0.10.18` — so clone URLs read
|
||||||
`ssh://git@forgejo.home.brianpooe.com/<owner>/<repo>.git` instead of the raw
|
`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
|
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.
|
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
|
## Notes
|
||||||
|
|
||||||
- Pin `FORGEJO_VERSION` to a specific major (as shipped, `10`) rather than
|
- Pin `FORGEJO_VERSION` to a specific major (as shipped, `10`) rather than
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ Every policy in this table uses:
|
||||||
## 3a. IoT → CADDY Policies (reverse direction)
|
## 3a. IoT → CADDY Policies (reverse direction)
|
||||||
|
|
||||||
The Home Assistant host itself also needs to reach Caddy directly — it
|
The Home Assistant host itself also needs to reach Caddy directly — it
|
||||||
pulls `homelab-blueprint` from Forgejo (`git.home.brianpooe.com`, proxied
|
pulls `homelab-blueprint` from Forgejo (`git.brianpooe.com`, proxied
|
||||||
through Caddy) to deploy its own docker-compose stack.
|
through Caddy) to deploy its own docker-compose stack.
|
||||||
|
|
||||||
**Two independent layers enforce traffic into the Caddy host — both must
|
**Two independent layers enforce traffic into the Caddy host — both must
|
||||||
|
|
@ -176,6 +176,31 @@ to free 22 up. Direct admin SSH to this LXC now needs `-p 2200`; `pct exec
|
||||||
`ufw status` on that host is the fastest way to check which subnets can
|
`ufw status` on that host is the fastest way to check which subnets can
|
||||||
already reach it before assuming a UniFi rule is the problem.
|
already reach it before assuming a UniFi rule is the problem.
|
||||||
|
|
||||||
|
## 3c. Public Exposure of Forgejo (Hetzner VPS)
|
||||||
|
|
||||||
|
Forgejo is also reachable from the public internet, at `git.brianpooe.com`
|
||||||
|
(HTTPS, public repos only — anonymous browsing/cloning, like GitHub) and
|
||||||
|
`ssh://git@forgejo.brianpooe.com` (SSH, requires a registered account key —
|
||||||
|
self-registration is disabled, so this is effectively you-only). Full detail
|
||||||
|
lives in `docker-compose-files/forgejo/README.md` ("Public Exposure") and,
|
||||||
|
on the VPS itself, `~/netbird-vps/docs/netbird-acl-playbook.md` and
|
||||||
|
`public-app-sso.md`. Summary of what changed on the LAN side of that path:
|
||||||
|
|
||||||
|
- **DNS is split-horizon**, not a new firewall rule: `git.brianpooe.com` and
|
||||||
|
`forgejo.brianpooe.com` are each their own single-name Technitium zone
|
||||||
|
(`A` record direct to `10.0.15.5` and `10.0.10.18` respectively) so LAN
|
||||||
|
clients resolve them locally while the public `*.brianpooe.com` wildcard
|
||||||
|
sends everyone else to the VPS. No UniFi policy changes were needed for
|
||||||
|
the HTTPS path — it already flows through the existing `ALLOW CADDY to
|
||||||
|
Forgejo` rule above.
|
||||||
|
- **The public SSH path is a new NetBird route**, not a UniFi rule: the VPS
|
||||||
|
NetBird peer previously only routed to the Pi Caddy (`10.0.15.5/32`).
|
||||||
|
A second `10.0.10.18/32` resource (routing peer `pi-caddy`, "Model 2" in
|
||||||
|
the playbook) was added so the VPS can reach Forgejo's git-SSH directly.
|
||||||
|
The VPS forwards its own (now-freed) public port 22 to `10.0.10.18:22`
|
||||||
|
via a `socat` container (`~/netbird-vps/ssh-forward/`) — the VPS's own
|
||||||
|
admin sshd was relocated to port 2200 to make room.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Public DNS Block Policies (per zone)
|
## 4. Public DNS Block Policies (per zone)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,16 @@ Hetzner VPS as the edge. Currently on this path: Koffan
|
||||||
(`koffan.brianpooe.com`) and IT-Tools (`it-tools.brianpooe.com`); the setup is
|
(`koffan.brianpooe.com`) and IT-Tools (`it-tools.brianpooe.com`); the setup is
|
||||||
built to be reused for any future app.
|
built to be reused for any future app.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Forgejo (`git.brianpooe.com`) also runs through this VPS edge but is
|
||||||
|
> deliberately **not** on this SSO path — public repos need to be
|
||||||
|
> browsable/clonable with no login, like GitHub. It gets its own plain
|
||||||
|
> `reverse_proxy` vhost (`crowdsec` + `rate_limit`, no `forward_auth`) in
|
||||||
|
> `caddy/Caddyfile.template`. Forgejo enforces its own per-repo visibility
|
||||||
|
> regardless of network path, so private repos stay private without an SSO
|
||||||
|
> gate. Its public git-over-SSH path is separate again — a raw TCP forward,
|
||||||
|
> not Caddy at all — see the VPS's `docs/netbird-acl-playbook.md`.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This intentionally does **not** use NetBird's built-in reverse-proxy
|
> This intentionally does **not** use NetBird's built-in reverse-proxy
|
||||||
> ("Services") feature. Self-hosted NetBird requires running your own proxy
|
> ("Services") feature. Self-hosted NetBird requires running your own proxy
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue