Compare commits
No commits in common. "fef521909b92b22ce696ba8fd399cc2c9af24e8e" and "1549283f193e2c4e45a181bc3e7d7bcd31c43745" have entirely different histories.
fef521909b
...
1549283f19
8 changed files with 164 additions and 215 deletions
2
Makefile
2
Makefile
|
|
@ -32,7 +32,7 @@ help:
|
||||||
@echo " deploy-paperless - Deploy Paperless-ngx"
|
@echo " deploy-paperless - Deploy Paperless-ngx"
|
||||||
@echo " deploy-vaultwarden - Deploy Vaultwarden"
|
@echo " deploy-vaultwarden - Deploy Vaultwarden"
|
||||||
@echo " deploy-koffan - Deploy Koffan (grocery list)"
|
@echo " deploy-koffan - Deploy Koffan (grocery list)"
|
||||||
@echo " deploy-forgejo - Deploy Forgejo (self-hosted Git)"
|
@echo " deploy-forgejo - Deploy Forgejo (self-hosted Git + GitHub mirroring)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Utility Targets:"
|
@echo "Utility Targets:"
|
||||||
@echo " status - Show status of all deployed stacks"
|
@echo " status - Show status of all deployed stacks"
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Each stack guide in `docker-compose-files/<stack>/README.md` explains appdata pl
|
||||||
| [Beszel Hub](docker-compose-files/beszel-hub/README.md) | Beszel monitoring dashboard |
|
| [Beszel Hub](docker-compose-files/beszel-hub/README.md) | Beszel monitoring dashboard |
|
||||||
| [Beszel Socket Proxy](docker-compose-files/socket-proxy/README.md) | Restricted Docker API access for Beszel |
|
| [Beszel Socket Proxy](docker-compose-files/socket-proxy/README.md) | Restricted Docker API access for Beszel |
|
||||||
| [Caddy](docker-compose-files/caddy/README.md) | Reverse proxy with Cloudflare DNS-01 |
|
| [Caddy](docker-compose-files/caddy/README.md) | Reverse proxy with Cloudflare DNS-01 |
|
||||||
| [Forgejo](docker-compose-files/forgejo/README.md) | Self-hosted Git service |
|
| [Forgejo](docker-compose-files/forgejo/README.md) | Self-hosted Git service with automatic GitHub mirroring |
|
||||||
| [Gramps Web](docker-compose-files/gramps-web/README.md) | Genealogy web application |
|
| [Gramps Web](docker-compose-files/gramps-web/README.md) | Genealogy web application |
|
||||||
| [Home Assistant](docker-compose-files/homeassistant/README.md) | Home Assistant, Mosquitto, and Zigbee2MQTT |
|
| [Home Assistant](docker-compose-files/homeassistant/README.md) | Home Assistant, Mosquitto, and Zigbee2MQTT |
|
||||||
| [Immich](docker-compose-files/immich/README.md) | Photo and video management with GPU acceleration |
|
| [Immich](docker-compose-files/immich/README.md) | Photo and video management with GPU acceleration |
|
||||||
|
|
|
||||||
|
|
@ -173,20 +173,18 @@ 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) -- publicly exposed at git.brianpooe.com (proxied via
|
# Forgejo (LXC 114, .18). Git-over-SSH (port 2222) is not proxyable through
|
||||||
# the VPS over NetBird for outside visitors) as well as reachable directly on
|
# Caddy — that port is exposed directly on the LXC for SSH clone/push.
|
||||||
# the LAN via split-horizon DNS. One canonical hostname everywhere, so this
|
git.{{CADDY_BASE_DOMAIN:-home.example.com}} {
|
||||||
# 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
git-mirror.{{CADDY_BASE_DOMAIN:-home.example.com}} {
|
||||||
|
import common
|
||||||
|
reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.18:4321
|
||||||
|
}
|
||||||
|
|
||||||
# Hermes Agent web dashboard (LXC 113, not docker — systemd service)
|
# Hermes Agent web dashboard (LXC 113, not docker — systemd service)
|
||||||
hermes.{{CADDY_BASE_DOMAIN:-home.example.com}} {
|
hermes.{{CADDY_BASE_DOMAIN:-home.example.com}} {
|
||||||
import common
|
import common
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
# Forgejo
|
# Forgejo
|
||||||
FORGEJO_VERSION=10
|
FORGEJO_VERSION=10
|
||||||
FORGEJO_HTTP_PORT=3000
|
FORGEJO_HTTP_PORT=3000
|
||||||
# Defaults to 2222 because the LXC's own system sshd already owns port 22.
|
|
||||||
# To use the standard port 22 for git-over-SSH instead, first move the LXC's
|
|
||||||
# own admin sshd off 22 (e.g. via a systemd ssh.socket override setting
|
|
||||||
# ListenStream to another port), then set FORGEJO_SSH_PORT=22 here.
|
|
||||||
FORGEJO_SSH_PORT=2222
|
FORGEJO_SSH_PORT=2222
|
||||||
FORGEJO_BIND_IP=0.0.0.0
|
FORGEJO_BIND_IP=0.0.0.0
|
||||||
FORGEJO_MEM_LIMIT=1G
|
FORGEJO_MEM_LIMIT=1G
|
||||||
|
|
@ -13,18 +9,6 @@ FORGEJO_MEM_LIMIT=1G
|
||||||
FORGEJO_ROOT_URL=https://git.home.example.com
|
FORGEJO_ROOT_URL=https://git.home.example.com
|
||||||
FORGEJO_DOMAIN=git.home.example.com
|
FORGEJO_DOMAIN=git.home.example.com
|
||||||
|
|
||||||
# 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
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Generate with: openssl rand -base64 32
|
# Generate with: openssl rand -base64 32
|
||||||
FORGEJO_DB_PASSWORD=<random-secret>
|
FORGEJO_DB_PASSWORD=<random-secret>
|
||||||
|
|
||||||
|
|
@ -35,13 +19,35 @@ 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
|
# --- gitea-mirror: auto-syncs GitHub repos into Forgejo ---
|
||||||
# logged-out homepage. Use explore if this instance is publicly exposed.
|
GITEA_MIRROR_VERSION=latest
|
||||||
FORGEJO_LANDING_PAGE=login
|
GITEA_MIRROR_PORT=4321
|
||||||
|
GITEA_MIRROR_BIND_IP=0.0.0.0
|
||||||
|
GITEA_MIRROR_MEM_LIMIT=256m
|
||||||
|
|
||||||
|
# Full URL where gitea-mirror's own UI is reachable
|
||||||
|
GITEA_MIRROR_PUBLIC_URL=https://git-mirror.home.example.com
|
||||||
|
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
GITEA_MIRROR_AUTH_SECRET=<random-secret>
|
||||||
|
|
||||||
|
# GitHub source — a classic PAT with repo (+ read:org if mirroring orgs) scope
|
||||||
|
GITHUB_USERNAME=<your-github-username>
|
||||||
|
GITHUB_TOKEN=<github-personal-access-token>
|
||||||
|
GITEA_MIRROR_SKIP_FORKS=false
|
||||||
|
GITEA_MIRROR_PRIVATE_REPOS=true
|
||||||
|
GITEA_MIRROR_STARRED=false
|
||||||
|
GITEA_MIRROR_ORGANIZATIONS=false
|
||||||
|
GITEA_MIRROR_ISSUES=false
|
||||||
|
GITEA_MIRROR_WIKI=false
|
||||||
|
|
||||||
|
# Forgejo destination — GITEA_MIRROR_FORGEJO_TOKEN can only be created after
|
||||||
|
# Forgejo is up and you've logged in once (Settings -> Applications ->
|
||||||
|
# Generate New Token, scopes: repo, admin:org). See README "GitHub mirroring".
|
||||||
|
GITEA_MIRROR_FORGEJO_USERNAME=<your-forgejo-username>
|
||||||
|
GITEA_MIRROR_FORGEJO_TOKEN=<forgejo-api-token>
|
||||||
|
GITEA_MIRROR_ORG=github-mirrors
|
||||||
|
GITEA_MIRROR_ORG_VISIBILITY=private
|
||||||
|
GITEA_MIRROR_INTERVAL=8h
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# Forgejo
|
# Forgejo
|
||||||
|
|
||||||
Self-hosted Git service (soft fork of Gitea) with Postgres.
|
Self-hosted Git service (soft fork of Gitea) with Postgres, plus
|
||||||
|
[gitea-mirror](https://github.com/RayLabsHQ/gitea-mirror) to automatically
|
||||||
|
pull all your GitHub repositories in as mirrors.
|
||||||
|
|
||||||
## Persistent Data
|
## Persistent Data
|
||||||
|
|
||||||
|
|
@ -27,6 +29,9 @@ 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
|
if the share is not mounted, rather than silently writing into an empty
|
||||||
directory that later needs merging back in.
|
directory that later needs merging back in.
|
||||||
|
|
||||||
|
`gitea_mirror_data` (local named volume) holds gitea-mirror's own small
|
||||||
|
SQLite state DB — negligible size, not worth splitting out.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Create `.env` if it does not exist:
|
1. Create `.env` if it does not exist:
|
||||||
|
|
@ -37,7 +42,8 @@ directory that later needs merging back in.
|
||||||
|
|
||||||
2. Set the required values in `docker-compose-files/forgejo/.env` — at
|
2. Set the required values in `docker-compose-files/forgejo/.env` — at
|
||||||
minimum `FORGEJO_ROOT_URL`, `FORGEJO_DOMAIN`, `FORGEJO_DB_PASSWORD`, and
|
minimum `FORGEJO_ROOT_URL`, `FORGEJO_DOMAIN`, `FORGEJO_DB_PASSWORD`, and
|
||||||
`FORGEJO_DATA_ROOT`.
|
`FORGEJO_DATA_ROOT`. Leave the `GITEA_MIRROR_*` GitHub/Forgejo-token vars
|
||||||
|
for step 6 — Forgejo needs to exist before those tokens can be generated.
|
||||||
|
|
||||||
3. Add the UNAS share and bind-mount it into the LXC, following
|
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
|
[nfs-mounts.md](../../docs/unas/nfs-mounts.md) (add `forgejo_data` to the
|
||||||
|
|
@ -61,93 +67,53 @@ directory that later needs merging back in.
|
||||||
```bash
|
```bash
|
||||||
./substitute_env.sh docker-compose-files/forgejo/template.yaml docker-compose.forgejo.yml .env docker-compose-files/forgejo/.env
|
./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 config --quiet
|
||||||
docker compose -f docker-compose.forgejo.yml up -d
|
docker compose -f docker-compose.forgejo.yml up -d forgejo-db forgejo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`gitea-mirror` is intentionally left out of the first `up` — see step 6.
|
||||||
|
|
||||||
5. Open `http://<host-ip>:3000`, create the first (admin) account, and
|
5. Open `http://<host-ip>:3000`, create the first (admin) account, and
|
||||||
confirm repo creation/push/clone works.
|
confirm repo creation/push/clone works before wiring up mirroring.
|
||||||
|
|
||||||
|
## GitHub Mirroring
|
||||||
|
|
||||||
|
`gitea-mirror` is a separate small app, not a Forgejo built-in — Forgejo
|
||||||
|
itself has no config option to bulk-pull an entire GitHub account. Once you
|
||||||
|
have a Forgejo login:
|
||||||
|
|
||||||
|
1. In Forgejo: **Settings → Applications → Generate New Token**, scopes
|
||||||
|
`repo` + `admin:org` (needed to auto-create the `github-mirrors`
|
||||||
|
destination org). Put the token in `GITEA_MIRROR_FORGEJO_TOKEN`.
|
||||||
|
2. Create a GitHub classic PAT with `repo` scope (add `read:org` if you set
|
||||||
|
`GITEA_MIRROR_ORGANIZATIONS=true`). Put it in `GITHUB_TOKEN`.
|
||||||
|
3. Start the mirror service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.forgejo.yml up -d gitea-mirror
|
||||||
|
```
|
||||||
|
|
||||||
|
With `AUTO_IMPORT_REPOS=true`, `AUTO_MIRROR_REPOS=true`, and
|
||||||
|
`SCHEDULE_ENABLED=true` (all defaulted on in the template), it imports every
|
||||||
|
repo from `GITHUB_USERNAME` on first startup and re-syncs every
|
||||||
|
`GITEA_MIRROR_INTERVAL` (default `8h`) after that — no manual per-repo setup.
|
||||||
|
Fine-tune what gets pulled via `GITEA_MIRROR_PRIVATE_REPOS`,
|
||||||
|
`GITEA_MIRROR_STARRED`, `GITEA_MIRROR_ORGANIZATIONS`, `GITEA_MIRROR_SKIP_FORKS`.
|
||||||
|
|
||||||
|
`CLEANUP_DELETE_IF_NOT_IN_GITHUB` stays `false` by default — a repo you
|
||||||
|
delete on GitHub won't get deleted in Forgejo automatically. Flip that on
|
||||||
|
deliberately if you want mirrors pruned to match GitHub exactly.
|
||||||
|
|
||||||
|
Its own UI is at `http://<host-ip>:4321` for monitoring sync status and
|
||||||
|
tweaking mirror settings beyond what's templated here.
|
||||||
|
|
||||||
## Reverse Proxy
|
## Reverse Proxy
|
||||||
|
|
||||||
Add a block for `git.<domain>` (port `FORGEJO_HTTP_PORT`) to
|
Add blocks for `git.<domain>` (port `FORGEJO_HTTP_PORT`) and optionally
|
||||||
|
`git-mirror.<domain>` (port `GITEA_MIRROR_PORT`) to
|
||||||
`docker-compose-files/caddy/Caddyfile_template`, then `make reload-caddy`.
|
`docker-compose-files/caddy/Caddyfile_template`, then `make reload-caddy`.
|
||||||
Git-over-SSH (`FORGEJO_SSH_PORT`) is not proxyable through Caddy — expose that
|
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.
|
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
|
## 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
|
||||||
|
|
@ -162,6 +128,7 @@ free port 22 for this forward.
|
||||||
```bash
|
```bash
|
||||||
# Tail logs
|
# Tail logs
|
||||||
docker compose -f docker-compose.forgejo.yml logs -f forgejo
|
docker compose -f docker-compose.forgejo.yml logs -f forgejo
|
||||||
|
docker compose -f docker-compose.forgejo.yml logs -f gitea-mirror
|
||||||
|
|
||||||
# Create an additional user without open registration
|
# Create an additional user without open registration
|
||||||
docker exec -it forgejo forgejo admin user create --username <name> --email <email> --random-password
|
docker exec -it forgejo forgejo admin user create --username <name> --email <email> --random-password
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# Forgejo self-hosted Git service, backed by Postgres.
|
# Forgejo self-hosted Git service, backed by Postgres, with automatic
|
||||||
|
# mirroring of GitHub repositories via gitea-mirror.
|
||||||
#
|
#
|
||||||
# Storage split (see docs/unas/nfs-mounts.md):
|
# Storage split (see docs/unas/nfs-mounts.md):
|
||||||
# - forgejo_data (local named volume): app config, SSH host keys, session
|
# - forgejo_data (local named volume): app config, SSH host keys, session
|
||||||
|
|
@ -67,12 +68,6 @@ services:
|
||||||
FORGEJO__database__PASSWD: "{{FORGEJO_DB_PASSWORD}}"
|
FORGEJO__database__PASSWD: "{{FORGEJO_DB_PASSWORD}}"
|
||||||
FORGEJO__server__ROOT_URL: "{{FORGEJO_ROOT_URL}}"
|
FORGEJO__server__ROOT_URL: "{{FORGEJO_ROOT_URL}}"
|
||||||
FORGEJO__server__DOMAIN: "{{FORGEJO_DOMAIN}}"
|
FORGEJO__server__DOMAIN: "{{FORGEJO_DOMAIN}}"
|
||||||
# SSH_DOMAIN drives the SSH clone URL shown in the web UI. It defaults
|
|
||||||
# to "localhost" (not DOMAIN), which is unusable here regardless: SSH
|
|
||||||
# can't be proxied through Caddy, so FORGEJO_DOMAIN's wildcard CNAME
|
|
||||||
# to the Caddy host would be wrong too. Point it at a dedicated DNS
|
|
||||||
# record resolving directly to this LXC instead (see README).
|
|
||||||
FORGEJO__server__SSH_DOMAIN: "{{FORGEJO_SSH_DOMAIN}}"
|
|
||||||
FORGEJO__server__SSH_PORT: "{{FORGEJO_SSH_PORT:-2222}}"
|
FORGEJO__server__SSH_PORT: "{{FORGEJO_SSH_PORT:-2222}}"
|
||||||
# LANDING_PAGE controls what anonymous visitors see at "/" — set to
|
# LANDING_PAGE controls what anonymous visitors see at "/" — set to
|
||||||
# login so they land on sign-in instead of the marketing homepage.
|
# login so they land on sign-in instead of the marketing homepage.
|
||||||
|
|
@ -155,6 +150,79 @@ services:
|
||||||
max-file: "{{DOCKERLOGGING_MAXFILE}}"
|
max-file: "{{DOCKERLOGGING_MAXFILE}}"
|
||||||
max-size: "{{DOCKERLOGGING_MAXSIZE}}"
|
max-size: "{{DOCKERLOGGING_MAXSIZE}}"
|
||||||
|
|
||||||
|
# Auto-syncs GitHub repos into Forgejo as mirrors. Forgejo itself has no
|
||||||
|
# "pull all my repos" config option — this is a separate open-source tool
|
||||||
|
# (https://github.com/RayLabsHQ/gitea-mirror) that provides exactly that,
|
||||||
|
# driven entirely by env vars below plus a one-time Forgejo API token (see
|
||||||
|
# README "GitHub mirroring" section — the token can't exist until Forgejo
|
||||||
|
# has an admin account, so it can't be templated here).
|
||||||
|
gitea-mirror:
|
||||||
|
image: ghcr.io/raylabshq/gitea-mirror:{{GITEA_MIRROR_VERSION:-latest}}
|
||||||
|
container_name: gitea-mirror
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
forgejo:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
DATABASE_URL: file:data/gitea-mirror.db
|
||||||
|
HOST: 0.0.0.0
|
||||||
|
PORT: "4321"
|
||||||
|
BASE_URL: "/"
|
||||||
|
BETTER_AUTH_SECRET: "{{GITEA_MIRROR_AUTH_SECRET}}"
|
||||||
|
BETTER_AUTH_URL: "{{GITEA_MIRROR_PUBLIC_URL}}"
|
||||||
|
PUBLIC_BETTER_AUTH_URL: "{{GITEA_MIRROR_PUBLIC_URL}}"
|
||||||
|
# GitHub source
|
||||||
|
GITHUB_USERNAME: "{{GITHUB_USERNAME}}"
|
||||||
|
GITHUB_TOKEN: "{{GITHUB_TOKEN}}"
|
||||||
|
SKIP_FORKS: "{{GITEA_MIRROR_SKIP_FORKS:-false}}"
|
||||||
|
PRIVATE_REPOSITORIES: "{{GITEA_MIRROR_PRIVATE_REPOS:-true}}"
|
||||||
|
MIRROR_STARRED: "{{GITEA_MIRROR_STARRED:-false}}"
|
||||||
|
MIRROR_ORGANIZATIONS: "{{GITEA_MIRROR_ORGANIZATIONS:-false}}"
|
||||||
|
MIRROR_ISSUES: "{{GITEA_MIRROR_ISSUES:-false}}"
|
||||||
|
MIRROR_WIKI: "{{GITEA_MIRROR_WIKI:-false}}"
|
||||||
|
# Forgejo destination — internal service name, no need to hairpin
|
||||||
|
# through the reverse proxy.
|
||||||
|
GITEA_URL: "http://forgejo:3000"
|
||||||
|
GITEA_TOKEN: "{{GITEA_MIRROR_FORGEJO_TOKEN}}"
|
||||||
|
GITEA_USERNAME: "{{GITEA_MIRROR_FORGEJO_USERNAME}}"
|
||||||
|
GITEA_ORGANIZATION: "{{GITEA_MIRROR_ORG:-github-mirrors}}"
|
||||||
|
GITEA_ORG_VISIBILITY: "{{GITEA_MIRROR_ORG_VISIBILITY:-private}}"
|
||||||
|
# Auto-import every repo on startup and keep mirroring on a schedule —
|
||||||
|
# this is the "pull all my GitHub repos via config" behavior.
|
||||||
|
AUTO_IMPORT_REPOS: "true"
|
||||||
|
AUTO_MIRROR_REPOS: "true"
|
||||||
|
SCHEDULE_ENABLED: "true"
|
||||||
|
GITEA_MIRROR_INTERVAL: "{{GITEA_MIRROR_INTERVAL:-8h}}"
|
||||||
|
CLEANUP_DELETE_IF_NOT_IN_GITHUB: "false"
|
||||||
|
CLEANUP_DRY_RUN: "true"
|
||||||
|
networks:
|
||||||
|
- forgejo_network
|
||||||
|
ports:
|
||||||
|
- "{{GITEA_MIRROR_BIND_IP:-0.0.0.0}}:{{GITEA_MIRROR_PORT:-4321}}:4321"
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- gitea_mirror_data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:4321/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "{{GITEA_MIRROR_MEM_LIMIT:-256m}}"
|
||||||
|
reservations:
|
||||||
|
memory: 64M
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-file: "{{DOCKERLOGGING_MAXFILE}}"
|
||||||
|
max-size: "{{DOCKERLOGGING_MAXSIZE}}"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
forgejo_network:
|
forgejo_network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
@ -163,3 +231,4 @@ networks:
|
||||||
volumes:
|
volumes:
|
||||||
forgejo_db_data:
|
forgejo_db_data:
|
||||||
forgejo_data:
|
forgejo_data:
|
||||||
|
gitea_mirror_data:
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ Every policy in this table uses:
|
||||||
| `ALLOW CADDY to Bento PDF` | `10.0.10.6` | `8080` | Bento PDF |
|
| `ALLOW CADDY to Bento PDF` | `10.0.10.6` | `8080` | Bento PDF |
|
||||||
| `ALLOW CADDY to Koffan` | `10.0.10.12` | `8080` | Koffan (LXC 111) |
|
| `ALLOW CADDY to Koffan` | `10.0.10.12` | `8080` | Koffan (LXC 111) |
|
||||||
| `ALLOW CADDY to Hermes` | `10.0.10.19` | `9119` | Hermes Agent dashboard (LXC 113) |
|
| `ALLOW CADDY to Hermes` | `10.0.10.19` | `9119` | Hermes Agent dashboard (LXC 113) |
|
||||||
| `ALLOW CADDY to Forgejo` | `10.0.10.18` | `3000,22` | Forgejo web UI (LXC 114) and git-over-SSH for hosts pulling from raspberrypi's zone |
|
| `ALLOW CADDY to Forgejo` | `10.0.10.18` | `3000,4321` | Forgejo (LXC 114) and gitea-mirror |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -122,87 +122,6 @@ Every policy in this table uses:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3a. IoT → CADDY Policies (reverse direction)
|
|
||||||
|
|
||||||
The Home Assistant host itself also needs to reach Caddy directly — it
|
|
||||||
pulls `homelab-blueprint` from Forgejo (`git.brianpooe.com`, proxied
|
|
||||||
through Caddy) to deploy its own docker-compose stack.
|
|
||||||
|
|
||||||
**Two independent layers enforce traffic into the Caddy host — both must
|
|
||||||
allow it, and both were found misconfigured while wiring this up:**
|
|
||||||
|
|
||||||
1. **UniFi zone-based firewall** (router-level, VLAN → VLAN):
|
|
||||||
|
|
||||||
| Policy name | Source zone | Source IP | Src. Port | Destination zone | Destination IP | Destination port(s) | Purpose |
|
|
||||||
|---|---|---|---|---|---|---|---|
|
|
||||||
| `Allow HA to Caddy` | `IoT` | `10.0.30.6` (Home Assistant IP) | `Any` | `CADDY` | `10.0.15.5` (Caddy IP) | `443` | git fetch/pull of homelab-blueprint from Forgejo |
|
|
||||||
|
|
||||||
Src. Port must be `Any`, not restricted to HA's own listening ports
|
|
||||||
(8080/8123) — this is outbound traffic *from* HA, so the source port is
|
|
||||||
a random ephemeral port, not the port HA listens on. Setting Src. Port
|
|
||||||
to a "Home Assistant" port object here was the original bug: the rule
|
|
||||||
never matched, so traffic fell through to `Block All Traffic` below it.
|
|
||||||
|
|
||||||
2. **`ufw` on the Caddy host itself** (10.0.15.5) — a second, separate
|
|
||||||
enforcement point the UniFi rule doesn't touch. It allow-lists inbound
|
|
||||||
80/443 per source subnet and did not include the IoT VLAN
|
|
||||||
(`10.0.30.0/24`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo ufw allow from 10.0.30.0/24 to any port 80,443 proto tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3b. CADDY / IoT → Forgejo git-over-SSH (port 22)
|
|
||||||
|
|
||||||
Both raspberrypi (CADDY zone) and Home Assistant (IoT zone) clone
|
|
||||||
`homelab-blueprint` over SSH directly against the Forgejo LXC
|
|
||||||
(`10.0.10.18:22`), bypassing Caddy entirely — SSH can't be proxied
|
|
||||||
through Caddy (HTTP/S only), so this traffic goes straight to the TRUSTED
|
|
||||||
zone. `10.0.10.18` has no host-level firewall (`ufw` not installed,
|
|
||||||
`iptables INPUT` policy `ACCEPT`), so only the UniFi zone rule matters
|
|
||||||
here — no second enforcement layer to fight, unlike the Caddy host above.
|
|
||||||
|
|
||||||
Forgejo's git-SSH runs on the standard port 22 on this LXC, not the usual
|
|
||||||
`2222` (see `docker-compose-files/forgejo/README.md`) — the LXC's own
|
|
||||||
admin sshd was relocated to port **2200** (systemd `ssh.socket` override)
|
|
||||||
to free 22 up. Direct admin SSH to this LXC now needs `-p 2200`; `pct exec
|
|
||||||
114` from the Proxmox host is unaffected and still works normally.
|
|
||||||
|
|
||||||
| Policy name | Source zone | Source IP | Destination zone | Destination IP | Destination port(s) | Purpose |
|
|
||||||
|---|---|---|---|---|---|---|
|
|
||||||
| `ALLOW CADDY to Forgejo` (existing rule, port added) | `CADDY` | `10.0.15.5` | `TRUSTED` | `10.0.10.18` | `3000,22` | Forgejo web UI + git-over-SSH from raspberrypi |
|
|
||||||
| `Allow HA to Forgejo SSH` | `IoT` | `10.0.30.6` | `TRUSTED` | `10.0.10.18` | `22` | git-over-SSH from homeassistant |
|
|
||||||
|
|
||||||
`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.
|
|
||||||
|
|
||||||
## 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)
|
||||||
|
|
||||||
Without these, devices can bypass Technitium by querying public resolvers
|
Without these, devices can bypass Technitium by querying public resolvers
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,6 @@ 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