From d66db4e85eed5225759fe3f13df12255263b745c Mon Sep 17 00:00:00 2001 From: "Ezequiel C." Date: Thu, 10 Sep 2026 23:29:36 +0200 Subject: [PATCH] Add forgejo (with shared postgres), docs/ideas; fix ENOTDIR in local catalog listing Co-Authored-By: Claude Code --- README.md | 15 ++++++ docs/ideas.md | 11 +++++ installer/scripts/generate-catalog.ts | 1 + installer/src/github.ts | 1 + services/catalog.json | 12 ++++- services/forgejo/metadata.json | 67 +++++++++++++++++++++++++++ services/postgres/metadata.json | 44 ++++++++++++++++++ 7 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 docs/ideas.md create mode 100644 services/forgejo/metadata.json create mode 100644 services/postgres/metadata.json diff --git a/README.md b/README.md index 9c6f812..a2869ec 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ homelab/ │ │ └── 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 @@ -65,6 +69,17 @@ for any future OIDC-capable app. Install order: `lldap`, `authelia`, `nginx-prox 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 diff --git a/docs/ideas.md b/docs/ideas.md new file mode 100644 index 0000000..3f6384a --- /dev/null +++ b/docs/ideas.md @@ -0,0 +1,11 @@ +# Ideas / backlog + +## Analytics + server stats (analize.example.com) + +- Public (or SSO-gated) web app that receives **web events** from my sites (page views, errors, custom events) and stores **server stats** from the homelab (per-service/module CPU, RAM, network, disk). +- Read side: dashboards — e.g. how much each service/module uses, event trends over time. +- Status: idea only. If built: one ingest API + a timeseries store (Postgres works fine at this scale), the same identity stack (Authelia SSO) in front, reverse-proxied by NPM. No code written yet. + +## Autonomous AI agents (big idea) + +- Ambitious project involving autonomous AI agents. Deferred — will revisit later. diff --git a/installer/scripts/generate-catalog.ts b/installer/scripts/generate-catalog.ts index 4eae27d..14e912f 100644 --- a/installer/scripts/generate-catalog.ts +++ b/installer/scripts/generate-catalog.ts @@ -9,6 +9,7 @@ const servicesDir = join(repoRoot, "services"); const services: Catalog["services"] = []; for (const name of readdirSync(servicesDir).sort()) { + if (!statSync(join(servicesDir, name), { throwIfNoEntry: false })?.isDirectory()) continue; const metaPath = join(servicesDir, name, "metadata.json"); if (!statSync(metaPath, { throwIfNoEntry: false })?.isFile()) continue; diff --git a/installer/src/github.ts b/installer/src/github.ts index 04bc195..4d17135 100644 --- a/installer/src/github.ts +++ b/installer/src/github.ts @@ -74,6 +74,7 @@ function listLocal(dir: string): CatalogEntry[] { } for (const name of names.sort()) { + if (!statSync(join(dir, name), { throwIfNoEntry: false })?.isDirectory()) continue; const metaPath = join(dir, name, "metadata.json"); if (!statSync(metaPath, { throwIfNoEntry: false })?.isFile()) continue; try { diff --git a/services/catalog.json b/services/catalog.json index 9dc6a34..3aa22a5 100644 --- a/services/catalog.json +++ b/services/catalog.json @@ -1,11 +1,16 @@ { - "generatedAt": "2026-09-02T16:59:46.903Z", + "generatedAt": "2026-09-10T21:29:23.464Z", "services": [ { "id": "authelia", "version": "3.0.0", "path": "services/authelia/metadata.json" }, + { + "id": "forgejo", + "version": "11.0.0", + "path": "services/forgejo/metadata.json" + }, { "id": "lldap", "version": "1.0.0", @@ -25,6 +30,11 @@ "id": "portainer", "version": "1.0.0", "path": "services/portainer/metadata.json" + }, + { + "id": "postgres", + "version": "17.0.0", + "path": "services/postgres/metadata.json" } ] } diff --git a/services/forgejo/metadata.json b/services/forgejo/metadata.json new file mode 100644 index 0000000..cf04ee1 --- /dev/null +++ b/services/forgejo/metadata.json @@ -0,0 +1,67 @@ +{ + "id": "forgejo", + "name": "Forgejo", + "description": "Self-hosted Git service (web UI + git over SSH) backed by the homelab PostgreSQL.", + "version": "11.0.0", + "category": "development", + "tags": ["git", "forgejo", "vcs", "self-hosted", "codeberg"], + "author": "Forgejo contributors", + "license": "MIT", + "homepage": "https://forgejo.org/", + "documentation": "https://forgejo.org/docs/latest/", + "dependsOn": ["postgres"], + "compose": { + "image": "codeberg.org/forgejo/forgejo:11", + "container_name": "forgejo", + "restart": "unless-stopped", + "environment": [ + "FORGEJO__database__TYPE=postgres", + "FORGEJO__database__HOST=homelab-postgres:5432", + "FORGEJO__database__NAME=${PG_DB}", + "FORGEJO__database__USER=${PG_USER}", + "FORGEJO__database__PASSWD=${PG_PASSWORD}", + "FORGEJO__server__ROOT_URL=${FORGEJO_ROOT_URL}", + "FORGEJO__server__SSH_DOMAIN=${FORGEJO_SSH_DOMAIN}", + "FORGEJO__server__SSH_PORT=2222", + "FORGEJO__service__DISABLE_REGISTRATION=true", + "TZ=${TZ}" + ], + "ports": [ + "10.0.0.5:3000:3000", + "10.0.0.5:2222:22" + ], + "volumes": [ + "forgejo_data:/data", + "/etc/timezone:/etc/timezone:ro", + "/etc/localtime:/etc/localtime:ro" + ], + "networks": ["homelab"] + }, + "volumes": { "forgejo_data": {} }, + "networks": { "homelab": { "external": true } }, + "env": [ + { "name": "PG_USER", "label": "Postgres user", "default": "homelab" }, + { "name": "PG_DB", "label": "Postgres database", "default": "forgejo" }, + { + "name": "PG_PASSWORD", + "label": "Postgres password", + "description": "Must match the postgres service. Pass --env PG_PASSWORD=", + "secret": true, + "required": true + }, + { "name": "TZ", "label": "Timezone", "default": "UTC" }, + { + "name": "FORGEJO_ROOT_URL", + "label": "Root URL", + "default": "http://10.0.0.5:3000/", + "description": "Switch to https://git.example.com/ once the reverse proxy is set up" + }, + { + "name": "FORGEJO_SSH_DOMAIN", + "label": "SSH domain", + "default": "10.0.0.5", + "description": "Host used for git over SSH (port 2222)" + } + ], + "notes": "Web UI at http://10.0.0.5:3000 (LAN only) — create the first admin account there. Git over SSH: ssh://git@10.0.0.5:2222//.git. Registration disabled (users via Authelia SSO later). Repos live in the forgejo_data volume; the database in pg_data (postgres service)." +} diff --git a/services/postgres/metadata.json b/services/postgres/metadata.json new file mode 100644 index 0000000..8368b10 --- /dev/null +++ b/services/postgres/metadata.json @@ -0,0 +1,44 @@ +{ + "id": "postgres", + "name": "PostgreSQL", + "description": "Shared PostgreSQL database for homelab services (Forgejo today, future apps later).", + "version": "17.0.0", + "category": "database", + "tags": ["database", "sql", "postgres"], + "author": "PostgreSQL Global Development Group", + "license": "PostgreSQL", + "homepage": "https://www.postgresql.org/", + "documentation": "https://hub.docker.com/_/postgres", + "compose": { + "image": "postgres:17", + "container_name": "homelab-postgres", + "restart": "unless-stopped", + "environment": [ + "POSTGRES_USER=${PG_USER}", + "POSTGRES_PASSWORD=${PG_PASSWORD}", + "POSTGRES_DB=${PG_DB}" + ], + "volumes": ["pg_data:/var/lib/postgresql/data"], + "networks": ["homelab"], + "healthcheck": { + "test": ["CMD-SHELL", "pg_isready -U ${PG_USER} -d ${PG_DB}"], + "interval": "10s", + "timeout": "5s", + "retries": 5 + } + }, + "volumes": { "pg_data": {} }, + "networks": { "homelab": { "external": true } }, + "env": [ + { "name": "PG_USER", "label": "Postgres user", "default": "homelab" }, + { + "name": "PG_PASSWORD", + "label": "Postgres password", + "description": "Auto-generated on first install and stored in ~/.homelab/services/postgres/.env", + "secret": true, + "generate": true + }, + { "name": "PG_DB", "label": "Database name", "default": "forgejo" } + ], + "notes": "No host port published — reachable only inside the homelab docker network as `homelab-postgres`." +}