00e16bff9e
CI / Assistant Split Regression (push) Failing after 8m25s
Release Image / Build And Push Images (push) Failing after 8m53s
CI / Unit Tests (push) Failing after 10m23s
Docker Deploy Test / Fresh-Linux Docker Deploy (push) Failing after 9m31s
CI / Typecheck (push) Failing after 10m57s
CI / Architecture Guardrails (push) Failing after 11m7s
CI / Lint (push) Successful in 32m7s
CI / Build (push) Has been skipped
CI / E2E Tests (push) Has been skipped
Prevents slow crash-recovery fsync on QNAP HDD-backed storage after container stop/replace. Without the grace period postgres is killed mid-write, and the next startup blocks Gitea for 5-10 minutes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
231 lines
9.3 KiB
Markdown
231 lines
9.3 KiB
Markdown
# Gitea + Act Runner — Single-File Compose (QNAP Container Station)
|
|
|
|
Eine einzige `docker-compose.yml` zum Direkt-Einfügen in Container Station. Persistente Daten liegen unter `/share/Container/gitea/` (stabiler Pfad, überlebt Stack-Recreate). Runner-Config wird beim Start inline generiert.
|
|
|
|
## Vorbereitung auf der QNAP (einmalig)
|
|
|
|
1. **Shared Folder `Container` existieren lassen** — falls nicht vorhanden, in File Station → New Shared Folder → Name `Container`.
|
|
|
|
2. **Per SSH die Daten-Verzeichnisse anlegen** mit den korrekten Ownerships für die Container-UIDs:
|
|
|
|
```bash
|
|
sudo mkdir -p /share/Container/gitea/gitea-data \
|
|
/share/Container/gitea/postgres-data \
|
|
/share/Container/gitea/act-runner-data
|
|
|
|
# Postgres-Container läuft als UID 70
|
|
sudo chown -R 70:70 /share/Container/gitea/postgres-data
|
|
|
|
# Gitea läuft intern als git user (UID 1000)
|
|
sudo chown -R 1000:1000 /share/Container/gitea/gitea-data /share/Container/gitea/act-runner-data
|
|
```
|
|
|
|
3. **Registrierungs-Token-Ablauf (wie vorher):** Erst Gitea + DB deployen (act_runner-Block auskommentiert oder mit leerem Token). Dann im Web-UI Runner-Token erzeugen → als Env-Var im Stack hinterlegen → act_runner deployen.
|
|
|
|
## docker-compose.yml
|
|
|
|
```yaml
|
|
version: "3"
|
|
|
|
services:
|
|
gitea:
|
|
image: gitea/gitea:latest
|
|
container_name: gitea
|
|
environment:
|
|
- GITEA__database__DB_TYPE=postgres
|
|
- GITEA__database__HOST=db:5432
|
|
- GITEA__database__NAME=gitea
|
|
- GITEA__database__USER=gitea
|
|
- GITEA__database__PASSWD=UGi2VZA7SgYGov
|
|
- GITEA__server__DOMAIN=gitea.hartmut-noerenberg.com
|
|
- GITEA__server__SSH_DOMAIN=gitea.hartmut.noerenberg.com
|
|
- GITEA__server__ROOT_URL=https://gitea.hartmut-noerenberg.com/
|
|
- GITEA__server__SSH_PORT=2222
|
|
- GITEA__server__HTTP_PORT=3000
|
|
# Gitea Actions aktivieren
|
|
- GITEA__actions__ENABLED=true
|
|
- GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com
|
|
- GITEA__actions__LOG_COMPRESSION=zstd
|
|
restart: unless-stopped
|
|
networks:
|
|
- gitea
|
|
- nginxproxy_nginxintern
|
|
volumes:
|
|
- /share/Container/gitea/gitea-data:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
ports:
|
|
- "3000:3000"
|
|
- "2222:22"
|
|
depends_on:
|
|
- db
|
|
|
|
db:
|
|
image: postgres:16-alpine
|
|
container_name: gitea-db
|
|
restart: unless-stopped
|
|
# Geben wir Postgres Zeit für sauberen Shutdown beim Stop/Replace.
|
|
# Ohne diesen Grace muss beim nächsten Start Crash-Recovery laufen
|
|
# (fsync über alle Files) — auf HDD-backed QNAP-Storage dauert das
|
|
# schnell 5-10 Minuten und blockt Gitea beim Start.
|
|
stop_grace_period: 60s
|
|
environment:
|
|
- POSTGRES_USER=gitea
|
|
- POSTGRES_PASSWORD=UGi2VZA7SgYGov
|
|
- POSTGRES_DB=gitea
|
|
networks:
|
|
- gitea
|
|
volumes:
|
|
- /share/Container/gitea/postgres-data:/var/lib/postgresql/data
|
|
|
|
act_runner:
|
|
image: gitea/act_runner:latest
|
|
container_name: gitea-act-runner
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- gitea
|
|
environment:
|
|
- GITEA_INSTANCE_URL=http://gitea:3000
|
|
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}
|
|
- GITEA_RUNNER_NAME=qnap-runner-1
|
|
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://node:20-bookworm
|
|
- CONFIG_FILE=/config.yaml
|
|
networks:
|
|
- gitea
|
|
volumes:
|
|
- /share/Container/gitea/act-runner-data:/data
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
cat > /config.yaml <<'EOF'
|
|
log:
|
|
level: info
|
|
runner:
|
|
file: /data/.runner
|
|
capacity: 4
|
|
timeout: 3h
|
|
insecure: false
|
|
fetch_timeout: 5s
|
|
fetch_interval: 2s
|
|
cache:
|
|
enabled: true
|
|
dir: /data/cache
|
|
container:
|
|
network: gitea_gitea
|
|
privileged: false
|
|
options: ""
|
|
workdir_parent: /workspace
|
|
valid_volumes:
|
|
- /var/run/docker.sock
|
|
host:
|
|
workdir_parent: /data/workflows
|
|
EOF
|
|
if [ ! -f /data/.runner ]; then
|
|
act_runner register --no-interactive \
|
|
--instance "$$GITEA_INSTANCE_URL" \
|
|
--token "$$GITEA_RUNNER_REGISTRATION_TOKEN" \
|
|
--name "$$GITEA_RUNNER_NAME" \
|
|
--labels "$$GITEA_RUNNER_LABELS" \
|
|
--config /config.yaml
|
|
fi
|
|
exec act_runner daemon --config /config.yaml
|
|
|
|
networks:
|
|
gitea:
|
|
external: false
|
|
nginxproxy_nginxintern:
|
|
external: true
|
|
```
|
|
|
|
## Deploy-Ablauf in Container Station
|
|
|
|
**Phase 1: Gitea + DB (ohne Runner)**
|
|
|
|
1. Container Station → **Applications → Create**
|
|
2. Application Name: `gitea`
|
|
3. Obige YAML einfügen, **aber den gesamten `act_runner`-Service-Block temporär auskommentieren** (mit `#` vor jeder Zeile, oder einfach löschen und später wieder einfügen)
|
|
4. Create + Start
|
|
5. Browser: `https://gitea.hartmut-noerenberg.com` → Admin-User anlegen, Repos/Orgs einrichten
|
|
|
|
**Phase 2: Runner hinzufügen**
|
|
|
|
6. In Gitea als Admin: **Site Administration → Actions → Runners → Create new Runner** → Token kopieren
|
|
7. In Container Station: Stack `gitea` → **Edit** → `act_runner`-Block wieder einfügen → unter **Environment Variables** hinzufügen:
|
|
- Key: `GITEA_RUNNER_REGISTRATION_TOKEN`
|
|
- Value: `<Token aus Schritt 6>`
|
|
8. Stack neu deployen
|
|
9. Logs prüfen:
|
|
```bash
|
|
docker logs -f gitea-act-runner
|
|
# Erwartet: "Runner registered successfully" + "Listening for tasks"
|
|
```
|
|
10. In Gitea: **Site Administration → Actions → Runners** → `qnap-runner-1` mit Status `Idle`
|
|
|
|
## Warum absolute Pfade
|
|
|
|
Relative Pfade (`./gitea-data`) werden von Container Station relativ zum internen Application-Directory aufgelöst (`/share/CACHEDEV1_DATA/Container/container-station-data/application/<stack>/…`). Beim Ersetzen oder Neuanlegen eines Stacks kann Container Station dieses Directory neu erzeugen oder löschen — das führt zum Datenverlust wie beim letzten Versuch.
|
|
|
|
Absolute Pfade unter `/share/Container/gitea/` sind **außerhalb** der Container-Station-Verwaltung. Stack kann beliebig gelöscht, umbenannt, migriert werden — die Daten bleiben, weil Container Station sie nicht als "seine" Volumes betrachtet.
|
|
|
|
## Repo-Secrets für CI/CD
|
|
|
|
Im capakraken-Repo → **Settings → Actions → Secrets** eintragen:
|
|
|
|
| Secret | Zweck |
|
|
| ----------------------- | -------------------------------------- |
|
|
| `STAGING_SSH_KEY` | Private SSH-Key für Deploy |
|
|
| `STAGING_SSH_HOST` | Staging-Hostname |
|
|
| `STAGING_SSH_PORT` | SSH-Port (meist `22`) |
|
|
| `STAGING_SSH_USER` | Deploy-User |
|
|
| `STAGING_DEPLOY_PATH` | Deploy-Verzeichnis auf Staging-Host |
|
|
| `STAGING_APP_HOST_PORT` | App-Port auf dem Host |
|
|
| `STAGING_GHCR_USERNAME` | Registry-User |
|
|
| `STAGING_GHCR_TOKEN` | Registry-Token mit Package-Write-Scope |
|
|
| `PROD_*` | Analog für Produktion |
|
|
|
|
## Backup-Empfehlung (nach diesem Vorfall umso wichtiger)
|
|
|
|
Tägliches Backup per Cron oder QNAP-Snapshot auf `/share/Container/gitea/`:
|
|
|
|
```bash
|
|
# Beispiel — in QNAP Cron oder Systemd-Timer
|
|
sudo tar -czf /share/Backups/gitea-$(date +%Y%m%d).tar.gz /share/Container/gitea/
|
|
# Retention: letzte 14 Tage behalten
|
|
find /share/Backups/ -name 'gitea-*.tar.gz' -mtime +14 -delete
|
|
```
|
|
|
|
Zusätzlich: QNAP **Storage & Snapshots** → Volume-Snapshots für `/share/Container/` aktivieren.
|
|
|
|
## Sicherheits-Notiz
|
|
|
|
`/var/run/docker.sock` ist gemountet, damit `release-image.yml` Images bauen kann. Das gibt jedem Workflow-Job vollen Zugriff auf den QNAP-Docker-Daemon — akzeptabel für Single-Tenant mit eigenen Repos. Für untrusted Repos stattdessen docker-in-docker Sidecar (auf Anfrage).
|
|
|
|
## Troubleshooting
|
|
|
|
**Runner registriert sich nicht:**
|
|
|
|
- Token abgelaufen → neuen in Gitea-UI erzeugen → Env-Var aktualisieren → `act_runner`-Container neu starten
|
|
- `GITEA_INSTANCE_URL` muss im internen Docker-Netz erreichbar sein (`http://gitea:3000`), nicht über Nginx-Proxy
|
|
- Fehler `open /data/.runner: no such file or directory` → der custom `entrypoint` überschreibt das Standard-Auto-Register-Skript des Images. Lösung: expliziter `act_runner register`-Aufruf vor `daemon` (siehe oben im Entrypoint-Block)
|
|
- Fehler `instance address is empty` trotz gesetzter Env-Vars → Docker Compose interpoliert `$VAR` im YAML **bevor** der Container startet. Im Entrypoint-Skript müssen Variablen als `$$VAR` geschrieben werden, damit ein literales `$` an den Container geht und von der Shell zur Laufzeit aufgelöst wird
|
|
|
|
**Postgres startet nicht, "permission denied":**
|
|
|
|
- `postgres-data` gehört nicht UID 70 → `sudo chown -R 70:70 /share/Container/gitea/postgres-data`
|
|
|
|
**Gitea startet nicht, "cannot create /data/...":**
|
|
|
|
- `gitea-data` gehört nicht UID 1000 → `sudo chown -R 1000:1000 /share/Container/gitea/gitea-data`
|
|
|
|
**Jobs scheitern bei Docker-Operationen:**
|
|
|
|
- Socket-Mount prüfen
|
|
- `container.network` in der inline-generierten Runner-Config muss zum echten Docker-Netzwerknamen passen (`docker network ls`)
|
|
|
|
**`uses: actions/checkout@v4` schlägt fehl:**
|
|
|
|
- `GITEA__actions__DEFAULT_ACTIONS_URL=https://github.com` gesetzt?
|
|
- Gitea-Container braucht Outbound-Internetzugang zu github.com
|