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 parent
1e9d921b95
commit
04112e88e6
11 files changed
+198
-45
No files matched your search
+1
-1
@@ -48,7 +48,7 @@ hermes.dankclaw.local {
|
|||||||
|
|
||||||
# --- Gitea (migrated) ---
|
# --- Gitea (migrated) ---
|
||||||
gitea.dankclaw.local {
|
gitea.dankclaw.local {
|
||||||
reverse_proxy gitea:3000
|
reverse_proxy gitea-server:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Grafana ---
|
# --- Grafana ---
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
DATABASE_TYPE: postgres
|
DATABASE_TYPE: postgres
|
||||||
DATABASE_HOST: postgres
|
DATABASE_HOST: gitea-postgres
|
||||||
DATABASE_NAME: gitea
|
DATABASE_NAME: gitea
|
||||||
DATABASE_USER: gitea
|
DATABASE_USER: gitea
|
||||||
DATABASE_PASSWD: "${GITEA_DB_PASSWORD}"
|
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]] |
|
| ComfyUI | comfyui.dankclaw.local | 8188 | [[ComfyUI]] |
|
||||||
| OpenWebUI | chat.dankclaw.local | 3000 | [[OpenWebUI]] |
|
| OpenWebUI | chat.dankclaw.local | 3000 | [[OpenWebUI]] |
|
||||||
| Hermes | hermes.dankclaw.local | 8090 | [[Hermes]] |
|
| Hermes | hermes.dankclaw.local | 8090 | [[Hermes]] |
|
||||||
| Gitea | gitea.dankclaw.local | 3001 | [[Gitea]] |
|
| Gitea | gitea.dankclaw.local | 3000 | [[Gitea]] |
|
||||||
| Grafana | grafana.dankclaw.local | 3002 | [[Grafana]] |
|
| Grafana | grafana.dankclaw.local | 3002 | [[Grafana]] |
|
||||||
| Prometheus | prometheus.dankclaw.local | 9090 | [[Prometheus]] |
|
| Prometheus | prometheus.dankclaw.local | 9090 | [[Prometheus]] |
|
||||||
| Homepage | dankclaw.local | 80/443 | — |
|
| Homepage | dankclaw.local | 80/443 | — |
|
||||||
|
|||||||
+64
-40
@@ -1,63 +1,87 @@
|
|||||||
# Gitea
|
# 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 |
|
| Container | Unit | Image | Purpose |
|
||||||
|------|-------|
|
|-----------|------|-------|---------|
|
||||||
| Image | `docker.io/gitea/gitea:latest` |
|
| `gitea-postgres` | `gitea-postgres.service` | `docker.io/library/postgres:16-alpine` | Database |
|
||||||
| DB image | `docker.io/library/postgres:16-alpine` |
|
| `gitea-server` | `gitea-server.service` | `docker.io/gitea/gitea:latest` | Web UI, API, git (HTTP :3000, SSH :2222) |
|
||||||
| Gitea port | 3000 → 3001 (host, proxied) |
|
| `gitea-caddy` | `gitea-caddy.service` | `docker.io/library/caddy:latest` | Reverse proxy for the public domain |
|
||||||
| SSH port | 2222 → 22 (host) |
|
|
||||||
| DB | postgres on the podman network (internal 5432) |
|
|
||||||
| Current location | `/opt/gitea/podman-compose.yml` on Dankclaw |
|
|
||||||
|
|
||||||
## 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`).
|
## Management
|
||||||
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.
|
|
||||||
|
|
||||||
## Env (from the live compose, redacted)
|
Run all commands as `dank` on Dankclaw.
|
||||||
|
|
||||||
```yaml
|
| Action | Command |
|
||||||
environment:
|
|--------|---------|
|
||||||
DATABASE_TYPE: postgres
|
| Start all | `systemctl --user start gitea-postgres gitea-server gitea-caddy` |
|
||||||
DATABASE_HOST: postgres
|
| Stop all | `systemctl --user stop gitea-caddy gitea-server gitea-postgres` |
|
||||||
DATABASE_NAME: gitea
|
| Restart Gitea only | `systemctl --user restart gitea-server` |
|
||||||
DATABASE_USER: gitea
|
| Status | `systemctl --user status gitea-postgres gitea-server gitea-caddy` |
|
||||||
DATABASE_PASSWD: "${GITEA_DB_PASSWORD}" # from .env, never committed
|
| Container logs | `podman logs -f gitea-server` |
|
||||||
USER_UID: "1000"
|
| Unit logs | `journalctl --user -u gitea-server -f` |
|
||||||
USER_GID: "1000"
|
|
||||||
|
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) |
|
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.
|
||||||
|----------------|------------------|
|
|
||||||
| `/data` | `/opt/gitea/data/gitea-gitea` |
|
|
||||||
| `/var/lib/postgresql/data` | `/opt/gitea/data/gitea-postgres` |
|
|
||||||
| `/root/.local/share/caddy` | `/opt/gitea/data/caddy/data` |
|
|
||||||
|
|
||||||
## 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
|
## Gotchas
|
||||||
|
|
||||||
- The live compose contains a real DB password — never commit it (test-enforced).
|
- **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.
|
||||||
- Gitea and postgres both run as UID 1000; keep the bind mounts owned by `dank`.
|
- **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 ...`.
|
||||||
- DNS `gitea.franksdank.cloud` → 71.227.227.172 is pending; the LAN hostname works now.
|
- **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
|
## See Also
|
||||||
|
|
||||||
- [[Dankclaw Stack]]
|
- [[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 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
|
```yaml
|
||||||
environment:
|
environment:
|
||||||
DATABASE_TYPE: postgres
|
DATABASE_TYPE: postgres
|
||||||
DATABASE_HOST: postgres
|
DATABASE_HOST: gitea-postgres
|
||||||
DATABASE_NAME: gitea
|
DATABASE_NAME: gitea
|
||||||
DATABASE_USER: gitea
|
DATABASE_USER: gitea
|
||||||
DATABASE_PASSWD: "${GITEA_DB_PASSWORD}"
|
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
|
## 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]].
|
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