# 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: `` 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//…`). 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