# homelab A common, reusable repository for running services on a personal homelab. Every service lives in its own folder under [`services/`](services/) and is described by a `metadata.json` file (plus any extra files the service needs, such as a config file). The [`installer/`](installer/) directory contains a Bun + TypeScript terminal UI (TUI) that reads the catalog straight from GitHub (used as the CDN) and installs or updates services with Docker. ``` homelab/ ├── services/ # one folder per service │ ├── catalog.json # generated index used by the installer (CDN listing) │ ├── nginx-proxy-manager/ # reverse proxy dashboard (NPM) │ │ └── metadata.json │ ├── authelia/ # SSO / IdP (login portal, OIDC, forward-auth) │ │ ├── metadata.json │ │ ├── configuration.yml │ │ └── users_database.yml │ ├── lldap/ # lightweight LDAP user store │ │ └── metadata.json │ ├── postgres/ # shared PostgreSQL (database for Forgejo & future apps) │ │ └── metadata.json │ ├── forgejo/ # self-hosted Git (web + SSH), uses postgres │ │ └── metadata.json │ ├── multistreaming/ # self-built: OBS ingest -> multi-platform re-streaming │ │ ├── metadata.json │ │ ├── Dockerfile │ │ ├── src/ │ │ └── web/ │ ├── portainer/ # Docker + network manager (VLANs, static IPs) │ │ └── metadata.json │ └── ... ├── installer/ # Bun + TypeScript TUI │ ├── src/ │ └── scripts/ └── docs/ └── metadata-schema.json ``` ## Identity stack (SSO) Every web service logs in through the same IdP ([`authelia`](services/authelia)), which authenticates users against [`lldap`](services/lldap) and sits behind [`nginx-proxy-manager`](services/nginx-proxy-manager): ``` browser ──▶ nginx-proxy-manager ──▶ authelia ──LDAP──▶ lldap ``` Two integration modes, depending on what each app supports: 1. **OIDC (true SSO)** — the app delegates login to Authelia. [`portainer`](services/portainer) uses this (client `portainer` is pre-registered in Authelia). 2. **Forward-auth (proxy gate)** — the app has no OIDC, so NPM asks Authelia to authorize each request via an `auth_request` block before forwarding. Used by `multistreaming` and lldap's web UI. | Service | SSO mode | | --- | --- | | Portainer | OIDC (native) | | multistreaming panel | forward-auth (no OIDC) | | lldap web UI | forward-auth (no OIDC) | | Nginx Proxy Manager | its own login (no OIDC; keep port 81 restricted) | Authelia also exposes OIDC discovery at `https://auth.example.com/.well-known/openid-configuration` for any future OIDC-capable app. Install order: `lldap`, `authelia`, `nginx-proxy-manager`: ```bash bun run src/index.ts install lldap authelia nginx-proxy-manager ``` ## Git (Forgejo + PostgreSQL) [`forgejo`](services/forgejo) is the self-hosted git server, backed by the shared [`postgres`](services/postgres) service. Both join the `homelab` docker network; PostgreSQL publishes no host port (docker-internal only). - Web UI: `http://10.0.0.5:3000` (LAN only) — first visit creates the admin account. - Git over SSH: `ssh://git@10.0.0.5:2222//.git` (container SSH on host port 2222; host sshd keeps port 22). - Registration is disabled; users come through the identity stack (Authelia SSO) later. - Install order: `postgres` first, then `forgejo` with `--env PG_PASSWORD=` taken from `~/.homelab/services/postgres/.env`. - Update `FORGEJO_ROOT_URL` / `FORGEJO_SSH_DOMAIN` to the public domain once NPM proxies `git.example.com`. ## Network & DMZ topology [`portainer`](services/portainer) is the Docker/network manager: from its dashboard you can create **macvlan/ipvlan networks** (each mapped to a host VLAN via its parent interface) and assign each container a **static IP**. A typical setup keeps only the reverse proxy in the DMZ and everything else on separate VLANs: ``` internet ──▶ DMZ VLAN (e.g. 10.0.10.0/24) └── nginx-proxy-manager (the only public entrypoint) │ ├─▶ services VLAN (10.0.20.0/24) ── multistreaming, … └─▶ identity VLAN (10.0.30.0/24) ── authelia, lldap ``` To make a VLAN usable as a macvlan parent, the tagged sub-interface must first exist on the host (e.g. `eth0.10`, configured in `/etc/network/interfaces` or netplan — outside Docker's scope). Then create the network in Portainer and attach services to it with the IPs you want. ## Quick start ```bash # 1. Install Bun: https://bun.sh # 2. Install the installer's dependencies cd installer bun install # 3. Launch the TUI bun run src/index.ts ``` The TUI lets you browse the catalog, install services, update them, uninstall them, and inspect what is currently running. Services are deployed with `docker compose` into `~/.homelab/services//` (a `docker-compose.yml` and a `.env` are generated from each `metadata.json`). > **Requirement:** Docker must be installed and the `docker` command available on your `PATH`. ## Non-interactive CLI The same app works as a plain CLI: ```bash bun run src/index.ts list bun run src/index.ts info nginx-proxy-manager bun run src/index.ts install nginx-proxy-manager bun run src/index.ts update # update everything installed bun run src/index.ts update nginx-proxy-manager # update one service bun run src/index.ts uninstall nginx-proxy-manager bun run src/index.ts status ``` Useful flags: | Flag | Meaning | | --- | --- | | `--local ` | Read services from a local folder instead of GitHub (handy while developing a new service). | | `--owner/--repo/--branch` | Override the GitHub source (also available as `HOMELAB_OWNER`, `HOMELAB_REPO`, `HOMELAB_BRANCH`). | | `--env KEY=VALUE` | Provide an environment value non-interactively (repeatable). | | `--yes` | Never prompt; use defaults or provided `--env` values. | | `--dry-run` | Print what would happen (including the generated compose file) without touching Docker. | | `--force` | Re-run an update even if versions already match. | | `--verbose` | Stream `docker compose` output instead of only showing failures. | ## How the CDN works The installer treats this GitHub repository as its content delivery network: 1. It lists services from [`services/catalog.json`](services/catalog.json) (one request). If that file is missing it falls back to the GitHub `git/trees` API. 2. Each service's `metadata.json` is fetched from `https://raw.githubusercontent.com////services//metadata.json`. Pointing the installer at your own fork is just a matter of setting `--owner`/`--repo` (or the `HOMELAB_*` environment variables). For a **private** repository, export a GitHub token and the installer authenticates its API and raw requests automatically: `HOMELAB_GITHUB_TOKEN` (or `GITHUB_TOKEN` / `HOMELAB_TOKEN`). The repository must be pushed — the installer reads from GitHub, not from your local checkout. Adding a new service means adding a folder plus its `metadata.json` and regenerating the catalog: ```bash cd installer bun run catalog ``` ## Adding a service 1. Create `services//metadata.json` (the folder name **must** equal the `id`). 2. Fill in the [metadata schema](docs/metadata-schema.json) — see [`services/README.md`](services/README.md) for a walkthrough of every field. Add any extra files the service needs (a config file, a `Dockerfile`, …) alongside `metadata.json`; the installer copies them into the deploy directory, so reference them with relative bind mounts in `compose.volumes`. 3. Regenerate the catalog: `cd installer && bun run catalog`. 4. Commit and push. The installer will now offer the new service. ## Service metadata at a glance `metadata.json` is the single source of truth for a service. The essential shape: ```json { "id": "nginx-proxy-manager", "name": "Nginx Proxy Manager", "description": "Reverse proxy with a web dashboard", "version": "1.0.0", "category": "network", "compose": { "image": "jc21/nginx-proxy-manager:2.14.0", "container_name": "nginx-proxy-manager", "restart": "unless-stopped", "ports": ["80:80", "443:443", "81:81"], "volumes": ["npm_data:/data", "npm_letsencrypt:/etc/letsencrypt"] }, "volumes": { "npm_data": {}, "npm_letsencrypt": {} }, "env": [ { "name": "TZ", "label": "Timezone", "default": "UTC" } ] } ``` - `compose` is a standard Docker Compose *service* definition (image, ports, volumes, environment, …). The installer wraps it in a generated `docker-compose.yml`. - `volumes` / `networks` are optional top-level named volumes/networks to declare. - `env` declares variables the installer should resolve for you. Use `${NAME}` in `compose` to reference them — the installer writes the resolved values to a `.env` file next to the compose file. - Any other files in the service folder (e.g. `configuration.yml`, `users_database.yml`) are copied into the deploy directory, so you can mount them with relative paths (`./configuration.yml:...`) in `compose.volumes`. See [`docs/metadata-schema.json`](docs/metadata-schema.json) for the complete, machine-readable schema. ## License This repository is a personal template. Service entries reference their upstream projects; each service's own license applies to the software it deploys.