- TypeScript 54%
- Go 38.7%
- Astro 4.9%
- Makefile 0.9%
- JavaScript 0.7%
- Other 0.8%
| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| nginx | ||
| scripts | ||
| .env.example | ||
| .env.stage.example | ||
| .gitignore | ||
| .golangci.yml | ||
| authentik.sync.yaml | ||
| compose.prod.yml | ||
| compose.stage.yml | ||
| compose.yml | ||
| go.work | ||
| go.work.sum | ||
| Makefile | ||
| monorepo.dockerignore | ||
| nginx.conf | ||
| README.md | ||
ArcaneDemesne HomeLab
Go BFF API + Astro frontend. Edge nginx on host 8083; API 5083; Astro 4083.
Port matrix: Gateway/PORT_MATRIX.md
Compose
# Local dev authority (localhost Authentik)
podman compose --env-file .env up -d
# Production authority (auth.arcanedemesne.com)
make up-prod
# or: podman compose --env-file .env.prod -f compose.yml -f compose.prod.yml up -d --build
# Stage (stage.arcanedemesne.com — tier-1 ports; copy .env.stage.example → .env.stage first)
make up-stage
# or: podman compose --env-file .env.stage -f compose.yml -f compose.stage.yml up -d --build
Stage hub (tunnel stage.* → :8180): from Gateway, podman compose -f compose.yml -f compose.stage.yml up -d.
Requires gateway-hub (edge only) and authentik-net (API OIDC backchannel) — create once from Gateway. Never put api/app on gateway-hub.
Local dev (host, split ports)
Run Go API and Astro on the host without the homelab-app container. See Gateway/PORT_MATRIX.md.
| Process | Directory | Env file | URL |
|---|---|---|---|
| Go API | backend/ |
.env.local (from .env.example) |
http://localhost:5083 |
| Astro | frontend/homelab/ |
src/.env.development.local (from env.development.local.example) |
http://localhost:4083 |
# Terminal 1 — from backend/
cp .env.example .env.local # fill AUTH_* (localhost:9000 Authentik)
make run
# Terminal 2 — from frontend/homelab/
cp src/env.development.local.example src/.env.development.local
bun dev
Register Authentik URIs for split-port dev:
- OIDC callback (API):
http://localhost:5083/sign-in-oidc - Post-logout (Astro):
http://localhost:4083/sign-out(and keep:5083/:8083as needed)
Auth
OIDC BFF via shared Packages/authentication module. Env vars use AUTH_* prefix (see .env.example). Authentik URIs: Authentik/docs/apps/homelab.md (quick ref: scripts/authentik-oauth-uris.txt). Manual test notes: scripts/AUTH_TEST.md.
Go modules: no vendor/. Public deps download via go mod download. Local dev uses go.work. Shared authentication uses replace in go.mod until Forgejo serves correct go-import meta tags.
Container build uses monorepo context (ArcaneDemesne/) — copies Packages/authentication into the image. compose.yml sets context: ..; keep monorepo.dockerignore at the context root or builds send the full frontend tree:
cp monorepo.dockerignore ../.dockerignore # once per clone / after pull
Migrations
One migration set for all tiers: backend/migrations/ (golang-migrate). Each environment has its own Postgres instance/volume — run migrations against that tier's database after Postgres is up.
| Tier | Postgres (host) | Volume / container | Migrate |
|---|---|---|---|
| dev / local | 6083 | homelab-postgres |
make migrate-up (uses backend/.env.local → localhost:6083) |
| stage | 6183 | homelab-stage-postgres |
make migrate-up-stage (reads .env.stage) |
| prod (co-located) | 6283 | homelab-prod-postgres |
make migrate-up-prod (reads .env.prod) |
Important: dev (homelab-proxy), stage (homelab-stage-proxy), and prod (homelab-prod-proxy) are separate compose projects with distinct Postgres volumes. Hub nginx routes prod hostnames to homelab-prod-proxy (Gateway/nginx.conf).
After recreating app/API containers, reload the matching edge: make proxy-reload-dev, make proxy-reload-stage, or make proxy-reload-prod (each also reloads gateway hubs via proxy-reload-gateway when run from make up*).
Stage is isolated: compose.stage.yml uses homelab-stage-postgres-data and tier-1 ports.
# Dev — postgres up, then:
make migrate-up
# Stage — after make up-stage:
make migrate-up-stage
# Prod — after make up-prod:
make migrate-up-prod
Forgejo deploy stage: .forgejo/workflows/deploy-stage.yaml runs make migrate-up-stage.
DATABASE_URL must include timezone=UTC (see backend/.env.example). Migrations are not run automatically on API start — always explicit make migrate-up*.
API timestamps
All audit *On fields (createdOn, modifiedOn, deletedOn) are UTC instants in JSON (RFC 3339, e.g. "2026-06-21T23:15:00Z"). Localize in the browser for display.
API routes
| Resource | Path | Auth |
|---|---|---|
| Articles | /api/v1/articles |
Public |
| System types | /api/v1/system-types |
Session required |
| Systems | /api/v1/systems |
Session required |
Filter systems by type: GET /api/v1/systems?systemTypeId=<uuid>.
Migration 000001_initial_schema creates tables (including systems.container_names). Migration 000002_seed_reference_data seeds dashboard system types, systems (with Podman container names for metrics), and a showcase article. Run make migrate-up after postgres is up. If you previously applied deleted 000003/000004 migrations, reset with make clean && make up-prod && make migrate-up.
Container metrics use the host agent Tools/ContainerMetrics: it dials Podman; homelab-api only proxies over a unix socket. Install and start the agent (make -C ../../Tools/ContainerMetrics install then systemctl --user enable --now containermetrics). Also enable systemctl --user enable --now podman.socket. Compose mounts $XDG_RUNTIME_DIR/containermetrics into the api container and sets CONTAINER_METRICS_SOCKET_PATH. security_opt: label=disable lets the container dial the socket when the external docker-compose provider drops SELinux :z bind options.
Protected routes use Authentik Subject mode = User UUID so audit actor columns receive the OIDC sub value.