diff --git a/dankclaw/Caddyfile b/dankclaw/Caddyfile index 33f006d..9aa239b 100644 --- a/dankclaw/Caddyfile +++ b/dankclaw/Caddyfile @@ -48,7 +48,7 @@ hermes.dankclaw.local { # --- Gitea (migrated) --- gitea.dankclaw.local { - reverse_proxy gitea:3000 + reverse_proxy gitea-server:3000 } # --- Grafana --- diff --git a/dankclaw/compose.yaml b/dankclaw/compose.yaml index b8135fb..471e4d8 100644 --- a/dankclaw/compose.yaml +++ b/dankclaw/compose.yaml @@ -129,7 +129,7 @@ services: restart: unless-stopped environment: DATABASE_TYPE: postgres - DATABASE_HOST: postgres + DATABASE_HOST: gitea-postgres DATABASE_NAME: gitea DATABASE_USER: gitea DATABASE_PASSWD: "${GITEA_DB_PASSWORD}" diff --git a/dankclaw/gitea/.env.example b/dankclaw/gitea/.env.example new file mode 100644 index 0000000..8ca581e --- /dev/null +++ b/dankclaw/gitea/.env.example @@ -0,0 +1,4 @@ +# Gitea stack secrets. Copy to /opt/gitea/.env on dankclaw (mode 0600). +# The live password never enters this repo. + +GITEA_DB_PASSWORD=changeme diff --git a/dankclaw/gitea/Caddyfile b/dankclaw/gitea/Caddyfile new file mode 100644 index 0000000..5a0696d --- /dev/null +++ b/dankclaw/gitea/Caddyfile @@ -0,0 +1,7 @@ +# Gitea stack Caddyfile (dedicated Caddy container, quadlet-managed). +# Lives on the host at /opt/gitea/data/caddy/Caddyfile. +# Upstream is the quadlet container name gitea-server (port 3000 inside). + +gitea.franksdank.cloud { + reverse_proxy gitea-server:3000 +} diff --git a/dankclaw/gitea/quadlet/gitea-caddy.container b/dankclaw/gitea/quadlet/gitea-caddy.container new file mode 100644 index 0000000..a39a7ca --- /dev/null +++ b/dankclaw/gitea/quadlet/gitea-caddy.container @@ -0,0 +1,31 @@ +# Quadlet unit: Gitea Caddy reverse proxy (rootless, boot-persistent). +# +# Install path on dankclaw: ~/.config/containers/systemd/gitea-caddy.container +# Unit name: gitea-caddy.service (podman 5.x drops the container- prefix) +# +# Serves gitea.franksdank.cloud (public domain, DNS pending). The LAN +# reaches Gitea directly on port 3000. This file holds no secrets. +# See docs/Dankclaw/Gitea.md. + +[Unit] +Description=Gitea Caddy reverse proxy container +Wants=gitea-server.service +After=gitea-server.service network.target + +[Container] +ContainerName=gitea-caddy +Network=gitea.network +Image=docker.io/library/caddy:latest +PublishPort=80:80 +PublishPort=443:443 +Volume=/opt/gitea/data/caddy/Caddyfile:/etc/caddy/Caddyfile +Volume=/opt/gitea/data/caddy/site:/srv +Volume=/opt/gitea/data/caddy/config:/config +Volume=/opt/gitea/data/caddy/data:/root/.local/share/caddy + +[Service] +TimeoutStartSec=900 +Restart=always + +[Install] +WantedBy=default.target diff --git a/dankclaw/gitea/quadlet/gitea-postgres.container b/dankclaw/gitea/quadlet/gitea-postgres.container new file mode 100644 index 0000000..a186ea2 --- /dev/null +++ b/dankclaw/gitea/quadlet/gitea-postgres.container @@ -0,0 +1,31 @@ +# Quadlet unit: Gitea PostgreSQL (rootless, boot-persistent). +# +# Install path on dankclaw: ~/.config/containers/systemd/gitea-postgres.container +# Unit name: gitea-postgres.service (podman 5.x drops the container- prefix) +# +# The DB password lives in /opt/gitea/.env on the host (mode 0600). +# This file holds no secrets. See docs/Dankclaw/Gitea.md. +# The [Service] EnvironmentFile= line lets systemd expand +# ${GITEA_DB_PASSWORD} in the generated ExecStart. + +[Unit] +Description=Gitea PostgreSQL container +After=network.target + +[Container] +ContainerName=gitea-postgres +Network=gitea.network +Image=docker.io/library/postgres:16-alpine +EnvironmentFile=/opt/gitea/.env +Environment=POSTGRES_USER=gitea +Environment=POSTGRES_PASSWORD=${GITEA_DB_PASSWORD} +Environment=POSTGRES_DB=gitea +Volume=/opt/gitea/data/gitea-postgres:/var/lib/postgresql/data + +[Service] +EnvironmentFile=/opt/gitea/.env +TimeoutStartSec=900 +Restart=always + +[Install] +WantedBy=default.target diff --git a/dankclaw/gitea/quadlet/gitea-server.container b/dankclaw/gitea/quadlet/gitea-server.container new file mode 100644 index 0000000..7ee1e5f --- /dev/null +++ b/dankclaw/gitea/quadlet/gitea-server.container @@ -0,0 +1,44 @@ +# Quadlet unit: Gitea web server (rootless, boot-persistent). +# +# Install path on dankclaw: ~/.config/containers/systemd/gitea-server.container +# Unit name: gitea-server.service (podman 5.x drops the container- prefix) +# +# The [Service] EnvironmentFile= line lets systemd expand +# ${GITEA_DB_PASSWORD} in the generated ExecStart. +# The DB password lives in /opt/gitea/.env on the host (mode 0600). +# This file holds no secrets. See docs/Dankclaw/Gitea.md. +# +# Ports (live): 3000 = HTTP UI/API, 2222 = git SSH. The docs table in +# Dankclaw Stack.md historically said 3001; the live host port is 3000. + +[Unit] +Description=Gitea web server container +Wants=gitea-postgres.service +After=gitea-postgres.service network.target + +[Container] +ContainerName=gitea-server +Network=gitea.network +Image=docker.io/gitea/gitea:latest +EnvironmentFile=/opt/gitea/.env +Environment=DATABASE_TYPE=postgres +Environment=DATABASE_HOST=gitea-postgres +Environment=DATABASE_NAME=gitea +Environment=DATABASE_USER=gitea +Environment=DATABASE_PASSWD=${GITEA_DB_PASSWORD} +Environment=DATABASE_PORT=5432 +Environment=USER_UID=1000 +Environment=USER_GID=1000 +PublishPort=0.0.0.0:3000:3000 +PublishPort=2222:22 +Volume=/opt/gitea/data/gitea-gitea:/data +Volume=/etc/timezone:/etc/timezone:ro +Volume=/etc/localtime:/etc/localtime:ro + +[Service] +EnvironmentFile=/opt/gitea/.env +TimeoutStartSec=900 +Restart=always + +[Install] +WantedBy=default.target diff --git a/dankclaw/gitea/quadlet/gitea.network b/dankclaw/gitea/quadlet/gitea.network new file mode 100644 index 0000000..eb2383a --- /dev/null +++ b/dankclaw/gitea/quadlet/gitea.network @@ -0,0 +1,10 @@ +# Quadlet network: private bridge for the Gitea stack (rootless). +# +# Install path on dankclaw: ~/.config/containers/systemd/gitea.network +# Unit name: gitea-network.service (podman network name: systemd-gitea) +# +# DNS is enabled by default for quadlet networks. This matters: the +# automatic "podman" default network has dns_enabled=false, so container +# names (gitea-postgres, gitea-server, gitea-caddy) would not resolve. + +[Network] diff --git a/docs/Dankclaw/Dankclaw Stack.md b/docs/Dankclaw/Dankclaw Stack.md index 0555d51..b4ea6b9 100644 --- a/docs/Dankclaw/Dankclaw Stack.md +++ b/docs/Dankclaw/Dankclaw Stack.md @@ -23,7 +23,7 @@ graph LR | ComfyUI | comfyui.dankclaw.local | 8188 | [[ComfyUI]] | | OpenWebUI | chat.dankclaw.local | 3000 | [[OpenWebUI]] | | Hermes | hermes.dankclaw.local | 8090 | [[Hermes]] | -| Gitea | gitea.dankclaw.local | 3001 | [[Gitea]] | +| Gitea | gitea.dankclaw.local | 3000 | [[Gitea]] | | Grafana | grafana.dankclaw.local | 3002 | [[Grafana]] | | Prometheus | prometheus.dankclaw.local | 9090 | [[Prometheus]] | | Homepage | dankclaw.local | 80/443 | — | diff --git a/docs/Dankclaw/Gitea.md b/docs/Dankclaw/Gitea.md index 99c1d8b..fb398fd 100644 --- a/docs/Dankclaw/Gitea.md +++ b/docs/Dankclaw/Gitea.md @@ -1,63 +1,87 @@ # Gitea -Gitea is a lightweight self-hosted Git service with issues, pull requests, and a web UI. It already runs on Dankclaw as a podman-compose stack at `/opt/gitea/` and is being migrated into this repository. +Gitea is a self-hosted Git service with issues, pull requests, and a web UI. On Dankclaw it runs as three Quadlet-managed containers in the rootless podman session of user `dank`. -**Host:** Dankclaw — **Hostname:** `gitea.dankclaw.local` — **Port:** 3001 — **Docs:** https://docs.gitea.com +**Host:** Dankclaw — **LAN access:** `http://dankclaw:3000` (or `http://192.168.0.143:3000`) — **Public domain:** `gitea.franksdank.cloud` (DNS pending) — **Docs:** https://docs.gitea.com -## Facts (from live inventory + knowledge base) +## Containers -| Item | Value | -|------|-------| -| Image | `docker.io/gitea/gitea:latest` | -| DB image | `docker.io/library/postgres:16-alpine` | -| Gitea port | 3000 → 3001 (host, proxied) | -| SSH port | 2222 → 22 (host) | -| DB | postgres on the podman network (internal 5432) | -| Current location | `/opt/gitea/podman-compose.yml` on Dankclaw | +| Container | Unit | Image | Purpose | +|-----------|------|-------|---------| +| `gitea-postgres` | `gitea-postgres.service` | `docker.io/library/postgres:16-alpine` | Database | +| `gitea-server` | `gitea-server.service` | `docker.io/gitea/gitea:latest` | Web UI, API, git (HTTP :3000, SSH :2222) | +| `gitea-caddy` | `gitea-caddy.service` | `docker.io/library/caddy:latest` | Reverse proxy for the public domain | -## Migration Plan +The containers share the private bridge network `systemd-gitea`. The quadlet network file `gitea.network` enables DNS, so the containers resolve each other by name. The automatic `podman` default network has DNS disabled. Do not run the Gitea containers on it. -1. Copy the live `/opt/gitea/podman-compose.yml` into `dankclaw/compose.yaml` (redacted: secrets moved to `.env`). -2. Keep the bind-mount data at `/opt/gitea/data/` so repos survive. -3. Commit the compose, push, and pull on Dankclaw. -4. Replace the live stack by bringing up the repo-managed compose. +## Management -## Env (from the live compose, redacted) +Run all commands as `dank` on Dankclaw. -```yaml -environment: - DATABASE_TYPE: postgres - DATABASE_HOST: postgres - DATABASE_NAME: gitea - DATABASE_USER: gitea - DATABASE_PASSWD: "${GITEA_DB_PASSWORD}" # from .env, never committed - USER_UID: "1000" - USER_GID: "1000" +| Action | Command | +|--------|---------| +| Start all | `systemctl --user start gitea-postgres gitea-server gitea-caddy` | +| Stop all | `systemctl --user stop gitea-caddy gitea-server gitea-postgres` | +| Restart Gitea only | `systemctl --user restart gitea-server` | +| Status | `systemctl --user status gitea-postgres gitea-server gitea-caddy` | +| Container logs | `podman logs -f gitea-server` | +| Unit logs | `journalctl --user -u gitea-server -f` | + +The units start in dependency order: + +```mermaid +graph LR + N[gitea-network.service] --> P[gitea-postgres.service] + P --> S[gitea-server.service] + S --> C[gitea-caddy.service] ``` -## Volumes +## Boot persistence -| Container path | Host path (live) | -|----------------|------------------| -| `/data` | `/opt/gitea/data/gitea-gitea` | -| `/var/lib/postgresql/data` | `/opt/gitea/data/gitea-postgres` | -| `/root/.local/share/caddy` | `/opt/gitea/data/caddy/data` | +User-level linger is enabled for `dank` (`loginctl enable-linger dank`). The units start at host boot without a login session. Each unit carries `[Install] WantedBy=default.target`. The podman generator applies it at boot; no symlink is needed. -## Healthcheck +The containers use the quadlet fresh-start model. A stop removes the container (`podman run --rm --replace`). The next start recreates it. The bind-mounted data survives. -Gitea serves `/api/v1/version` and the web UI on `/`. +## Secrets -## Backup +The database password lives in `/opt/gitea/.env` on the host (mode 0600). The quadlet files reference it with `EnvironmentFile=`. The password never enters this repository. Placeholders live in [`dankclaw/gitea/.env.example`](gitea/.env.example). -Back up `/opt/gitea/data/` (tar) or `pg_dump` the gitea database. See the knowledge base page [[Gitea Data Persistence]]. +Do not change the password in place. Change the PostgreSQL role first, then update `/opt/gitea/.env` and the `[database] PASSWD` line in the app.ini (see Gotchas), then restart the stack. + +## Configuration files + +| File | Location | Repo copy | +|------|----------|-----------| +| Quadlet units | `~/.config/containers/systemd/gitea-{postgres,server,caddy}.container`, `gitea.network` | [`dankclaw/gitea/quadlet/`](gitea/quadlet/) | +| Caddyfile | `/opt/gitea/data/caddy/Caddyfile` | [`dankclaw/gitea/Caddyfile`](gitea/Caddyfile) | +| Secrets | `/opt/gitea/.env` (0600) | [`dankclaw/gitea/.env.example`](gitea/.env.example) | + +After editing a repo copy, copy it to the target, then run `systemctl --user daemon-reload` and restart the affected units. + +## Data volumes + +| Container path | Host path | Contents | +|----------------|-----------|----------| +| `/data` | `/opt/gitea/data/gitea-gitea` | Git repositories, app.ini, attachments | +| `/var/lib/postgresql/data` | `/opt/gitea/data/gitea-postgres` | PostgreSQL data | +| `/etc/caddy/Caddyfile`, `/root/.local/share/caddy` | `/opt/gitea/data/caddy/...` | Caddy config and TLS state | + +Back up `/opt/gitea/data/` (tar) or `pg_dump` the database. See the knowledge base page [[Gitea Data Persistence]]. ## Gotchas -- The live compose contains a real DB password — never commit it (test-enforced). -- Gitea and postgres both run as UID 1000; keep the bind mounts owned by `dank`. -- DNS `gitea.franksdank.cloud` → 71.227.227.172 is pending; the LAN hostname works now. +- **app.ini wins over env vars.** Gitea writes `/data/gitea/conf/app.ini` (host: `/opt/gitea/data/gitea-gitea/gitea/conf/app.ini`) on first start. The `[database] HOST` and `PASSWD` lines persist and override the `DATABASE_HOST`/`DATABASE_PASSWD` env vars. When you rename the database container, edit the `HOST` line. +- **The files in the data directory are owned by host UID 100999**, not `dank` (rootless ID mapping: container uid 1000 → host 100999). Edit them inside the container with `podman exec --user 1000 gitea-server ...`. +- **The host auto-reboots.** `unattended-upgrades` reboots Dankclaw when a package needs it. The quadlet units make Gitea survive that. See the 2026-08-16 incident below. +- **Port 3000 vs 3001.** The live host port is 3000. The full-stack [`dankclaw/compose.yaml`](../compose.yaml) maps 3001. Align the two before deploying the full stack. +- **Legacy compose retired.** `/opt/gitea/podman-compose.yml` is retired to `/opt/gitea/.deprecated/`. Do not run it; its containers would collide with the quadlet units on ports 3000, 2222, 80, and 443. + +## Incident: 2026-08-16 — down after unattended reboot + +`unattended-upgrades` rebooted Dankclaw at 15:01 on 2026-08-16. The Gitea containers ran through podman-compose with `restart: unless-stopped` and no systemd units. Rootless podman does not auto-start containers at boot without units. Gitea stayed down until a manual start at 16:02. The fix: migrate the stack to quadlet units with linger (this page). After the fix, verify on the next reboot. ## See Also - [[Dankclaw Stack]] -- Knowledge base: [[Gitea]] (tools), [[Gitea Data Persistence]], [[Gitea Architecture]] \ No newline at end of file +- [[Postgres]] +- Knowledge base: [[Gitea]] (tools), [[Gitea Management]], [[Gitea Data Persistence]], [[Docker and Podman]] diff --git a/docs/Dankclaw/Postgres.md b/docs/Dankclaw/Postgres.md index 972876c..51a5bdd 100644 --- a/docs/Dankclaw/Postgres.md +++ b/docs/Dankclaw/Postgres.md @@ -29,17 +29,19 @@ services: ## Gitea Connection -Gitea reaches the database over the podman network: +Gitea reaches the database over the `systemd-gitea` network (quadlet-managed live, compose-managed after the full-stack deploy): ```yaml environment: DATABASE_TYPE: postgres - DATABASE_HOST: postgres + DATABASE_HOST: gitea-postgres DATABASE_NAME: gitea DATABASE_USER: gitea DATABASE_PASSWD: "${GITEA_DB_PASSWORD}" ``` +The live `app.ini` on the host overrides env vars. When you rename the database container, update its `[database] HOST` line. See [[Gitea]] Gotchas. + ## Migration The live Gitea stack on Dankclaw already runs this image with the data at `/opt/gitea/data/gitea-postgres`. The migration keeps the bind mount in place; see [[Gitea]].