fix: gitea boot persistence via quadlet (incident 2026-08-16)
unattended-upgrades rebooted dankclaw at 15:01; the legacy podman-compose gitea stack had no systemd units, so rootless podman did not auto-start the containers. Gitea was down ~1 hour. - add quadlet units (dankclaw/gitea/quadlet/): gitea-postgres, gitea-server, gitea-caddy + gitea.network (DNS enabled; the default rootless podman network has dns_enabled=false) - add gitea stack Caddyfile (upstream gitea-server:3000) and .env.example; live password stays in /opt/gitea/.env (0600) - fix latent name bugs in main stack config: DATABASE_HOST gitea-postgres, Caddyfile upstream gitea-server - docs: rewrite Gitea page for the quadlet stack (management, boot persistence, secrets, gotchas, incident log); fix port 3000 in stack table; Postgres connection notes - legacy compose retired on target to /opt/gitea/.deprecated/
This commit is contained in:
+1
-1
@@ -48,7 +48,7 @@ hermes.dankclaw.local {
|
||||
|
||||
# --- Gitea (migrated) ---
|
||||
gitea.dankclaw.local {
|
||||
reverse_proxy gitea:3000
|
||||
reverse_proxy gitea-server:3000
|
||||
}
|
||||
|
||||
# --- Grafana ---
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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 | — |
|
||||
|
||||
+64
-40
@@ -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]]
|
||||
- [[Postgres]]
|
||||
- Knowledge base: [[Gitea]] (tools), [[Gitea Management]], [[Gitea Data Persistence]], [[Docker and Podman]]
|
||||
|
||||
@@ -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]].
|
||||
|
||||
Reference in New Issue
Block a user