From 156917ece135055b4b19ba2175355b62b3aa6e5d Mon Sep 17 00:00:00 2001 From: "Christoph K." Date: Tue, 7 Apr 2026 18:49:17 +0200 Subject: [PATCH] Slim down infra/: keep only project-specific deployment docs General infra setup (NAS, act_runner, Gitea Actions reference) moved to separate infra repo. Co-Authored-By: Claude Sonnet 4.6 --- infra/CLAUDE.md | 124 -------------------------- infra/README.md | 197 +++++++++-------------------------------- infra/gitea-actions.md | 156 -------------------------------- 3 files changed, 40 insertions(+), 437 deletions(-) delete mode 100644 infra/CLAUDE.md delete mode 100644 infra/gitea-actions.md diff --git a/infra/CLAUDE.md b/infra/CLAUDE.md deleted file mode 100644 index 78b2aa0..0000000 --- a/infra/CLAUDE.md +++ /dev/null @@ -1,124 +0,0 @@ -# CLAUDE.md — NAS Infrastruktur - -Kontext für Claude Code Sessions zum Thema Deployment und Infrastruktur auf der Synology NAS. - ---- - -## Umgebung - -- **NAS:** Synology DiskStation, DSM 7.x, IP: `192.168.1.4` -- **SSH:** `ssh jacek@192.168.1.4` — Docker-Befehle erfordern `sudo` -- **Docker-Datenpfad:** `/volume2/docker/` — alle persistenten Daten hier -- **Docker-Binary auf NAS:** `/usr/local/bin/docker` -- **Gitea:** `http://192.168.1.4:3000` (Docker-Container auf NAS) - ---- - -## Laufende Dienste - -| Dienst | Container | Port | Pfad | -|--------|-----------|------|------| -| PostgreSQL | `shared-postgres-1` | 5433 | `/volume2/docker/shared/` | -| act_runner | `gitea-runner` | — | `/volume2/docker/gitea-runner/` | -| Pamietnik | `pamietnik-api-1` | 9050 | `/volume2/docker/pamietnik/` | - ---- - -## Datenbankzugriff - -**Aus Containern:** `host-gateway:5433` -(`host-gateway` ist Docker's eingebauter Alias für den Host — in `extra_hosts` und `docker-compose.yml` deklarieren) - -**Remote (Heimnetz):** `psql -h 192.168.1.4 -p 5433 -U -d ` - -**Direkt auf NAS:** -```bash -sudo docker exec -it shared-postgres-1 psql -U postgres -``` - -**Wichtig PostgreSQL 15+:** Nach `GRANT ALL PRIVILEGES ON DATABASE` zusätzlich: -```sql -GRANT ALL ON SCHEMA public TO ; -``` - ---- - -## CI/CD: Gitea Actions - -**Workflow-Datei:** `.gitea/workflows/deploy.yml` -**Trigger:** Push auf `main` - -**Runner-Setup:** -- Container: `gitea/act_runner:latest` mit `--network host` -- `GITEA_INSTANCE_URL` muss NAS-IP sein (`192.168.1.4:3000`), **nicht** `localhost` -- `config.yaml` braucht `valid_volumes: [/volume2/docker]` sonst werden Mounts ignoriert - -**Job-Container:** `docker:latest` mit `-v /volume2/docker:/volume2/docker` -- Docker CLI ist im Image enthalten -- Socket wird automatisch vom Runner propagiert (nicht nochmal in `options` mounten → Duplicate-Fehler) -- `wget` statt `curl` verwenden (`curl` nicht im Image) - -**Gitea Konfiguration:** -- Nicht-sensitive Werte (Pfade, Ports, DB-Namen) → **Variables** (`vars.NAME`) -- Passwörter, Tokens → **Secrets** (`secrets.NAME`) -- Variables mit Leerzeichen am Ende → Parsing-Fehler in Expressions - ---- - -## Deployment-Muster für neue Projekte - -```yaml -# docker-compose.yml im Repo -services: - api: - build: . - ports: - - "${APP_PORT:-8080}:8080" - extra_hosts: - - "host-gateway:host-gateway" - environment: - DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@host-gateway:5433/${DB_NAME} - volumes: - - /volume2/docker//uploads:/uploads - restart: unless-stopped -``` - -```yaml -# .gitea/workflows/deploy.yml -jobs: - deploy: - runs-on: self-hosted - container: - image: docker:latest - options: -v /volume2/docker:/volume2/docker - steps: - - name: Pull code - run: | - if [ -d "${{ vars.DEPLOY_DIR }}/.git" ]; then - git -C ${{ vars.DEPLOY_DIR }} pull - else - git clone http://192.168.1.4:3000//.git ${{ vars.DEPLOY_DIR }} - fi - - name: Write .env - run: printf 'DB_PASSWORD=%s\n' '${{ secrets.DB_PASSWORD }}' > ${{ vars.DEPLOY_DIR }}/.env - - name: Build & Deploy - run: docker compose -f ${{ vars.DEPLOY_DIR }}/docker-compose.yml up --build -d - - name: Health check - run: sleep 15 && wget -qO- http://192.168.1.4:${{ vars.APP_PORT }}/healthz || exit 1 -``` - ---- - -## Bekannte Fallstricke - -- **`Secure: true` auf Session-Cookies** schlägt fehl bei HTTP → `Secure: false` setzen -- **Ports unter 1024** können Container auf Synology nicht binden → internen Port > 1024 wählen -- **`localhost`** im Job-Container zeigt auf den Container selbst, nicht auf die NAS -- **Volume-Mounts** in Workflow `container.options` werden doppelt gemountet wenn identisch mit Runner-Mount -- **`/volume2/docker`** muss in `valid_volumes` der `config.yaml` stehen sonst wird Mount ignoriert - ---- - -## Vollständige Setup-Anleitung - -Siehe `infra/README.md` diff --git a/infra/README.md b/infra/README.md index 9298f65..a281434 100644 --- a/infra/README.md +++ b/infra/README.md @@ -1,196 +1,79 @@ -# Infrastruktur & Deployment +# Deployment -## Architektur +## Voraussetzungen -``` -Synology NAS (192.168.1.4) -├── Gitea :3000 — Git + CI/CD -├── act_runner — Gitea Actions Runner -├── /volume2/docker/shared/ -│ ├── postgres:16-alpine :5433 — Geteilte DB (alle Projekte) -│ └── pgdata/ — Persistente Daten -└── /volume2/docker// - ├── -Container : — Anwendung - ├── uploads/ — Persistente Uploads - └── .env — Projekt-Secrets -``` - -**Datenbankverbindung aus Containern:** `host-gateway:5433` +- Synology NAS mit laufendem shared PostgreSQL Stack (`/volume2/docker/shared/`) +- act_runner registriert und online +- Siehe Infra-Repo für Setup-Details --- -## 1. Shared PostgreSQL einrichten (einmalig) +## Datenbank einrichten (einmalig) -```bash -sudo mkdir -p /volume2/docker/shared/pgdata -sudo cp infra/docker-compose.yml /volume2/docker/shared/ -echo "POSTGRES_PASSWORD=" | sudo tee /volume2/docker/shared/.env -cd /volume2/docker/shared && sudo docker compose up -d -``` - -Datenbank anlegen: ```bash sudo docker exec -it shared-postgres-1 psql -U postgres ``` + ```sql -CREATE DATABASE ; -CREATE USER WITH PASSWORD ''; -GRANT ALL PRIVILEGES ON DATABASE TO ; -GRANT ALL ON SCHEMA public TO ; -- wichtig für PostgreSQL 15+ +CREATE DATABASE pamietnik; +CREATE USER pamietnik WITH PASSWORD ''; +GRANT ALL PRIVILEGES ON DATABASE pamietnik TO pamietnik; +GRANT ALL ON SCHEMA public TO pamietnik; \q ``` --- -## 2. act_runner einrichten (einmalig) +## Gitea Secrets & Variables -Token holen: **Gitea → Site-Administration → Actions → Runner → Runner erstellen** +**Repository → Einstellungen → Actions → Secrets:** -```bash -sudo docker run -d \ - --name gitea-runner \ - --restart unless-stopped \ - --network host \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v /volume2/docker/gitea-runner:/data \ - -e GITEA_INSTANCE_URL=http://192.168.1.4:3000 \ - -e GITEA_RUNNER_REGISTRATION_TOKEN= \ - -e GITEA_RUNNER_NAME=nas-runner \ - -e GITEA_RUNNER_LABELS=self-hosted,linux,amd64 \ - gitea/act_runner:latest -``` - -> **Wichtig:** `GITEA_INSTANCE_URL` muss die NAS-IP sein, nicht `localhost` — -> Job-Container können `localhost` nicht auflösen. - -`/volume2/docker/gitea-runner/config.yaml` anlegen: -```yaml -runner: - name: "nas-runner" - -container: - valid_volumes: - - /volume2/docker -``` - -Runner neu starten: `sudo docker restart gitea-runner` - ---- - -## 3. Neues Projekt deployen - -### 3.1 Verzeichnis anlegen -```bash -sudo mkdir -p /volume2/docker//uploads -``` - -### 3.2 docker-compose.yml (im Repo) -```yaml -services: - api: - build: - context: . - dockerfile: Dockerfile - ports: - - "${APP_PORT:-8080}:8080" - extra_hosts: - - "host-gateway:host-gateway" - environment: - DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@host-gateway:5433/${DB_NAME} - volumes: - - /volume2/docker//uploads:/uploads - restart: unless-stopped -``` - -### 3.3 Gitea Secrets & Variables - -**Secrets** (Repository → Einstellungen → Actions → Secrets): | Secret | Wert | |--------|------| -| `DB_PASSWORD` | Datenbankpasswort | +| `DB_PASSWORD` | Passwort des `pamietnik` DB-Users | + +**Repository → Einstellungen → Actions → Variables:** -**Variables** (Repository → Einstellungen → Actions → Variables): | Variable | Wert | |----------|------| -| `DEPLOY_DIR` | `/volume2/docker/` | -| `DB_USER` | `` | -| `DB_NAME` | `` | -| `APP_PORT` | `` | +| `DEPLOY_DIR` | `/volume2/docker/pamietnik` | +| `DB_USER` | `pamietnik` | +| `DB_NAME` | `pamietnik` | +| `APP_PORT` | `9050` | -### 3.4 Gitea Actions Workflow (`.gitea/workflows/deploy.yml`) -```yaml -name: Deploy to NAS +--- -on: - push: - branches: [main] +## Deploy -jobs: - deploy: - runs-on: self-hosted - container: - image: docker:latest - options: -v /volume2/docker:/volume2/docker +Push auf `main` triggert automatisch den Workflow (`.gitea/workflows/deploy.yml`): - steps: - - name: Pull code - run: | - if [ -d "${{ vars.DEPLOY_DIR }}/.git" ]; then - git -C ${{ vars.DEPLOY_DIR }} pull - else - git clone http://192.168.1.4:3000//.git ${{ vars.DEPLOY_DIR }} - fi +1. Code nach `/volume2/docker/pamietnik/` klonen/pullen +2. `.env` mit DB-Credentials schreiben +3. `docker compose up --build -d` +4. Health check auf `/healthz` - - name: Write .env - run: printf 'DB_PASSWORD=%s\n' '${{ secrets.DB_PASSWORD }}' > ${{ vars.DEPLOY_DIR }}/.env +App erreichbar unter: `http://192.168.1.4:9050` - - name: Build & Deploy - run: docker compose -f ${{ vars.DEPLOY_DIR }}/docker-compose.yml up --build -d +--- - - name: Health check - run: | - sleep 15 - wget -qO- http://192.168.1.4:${{ vars.APP_PORT }}/healthz || exit 1 +## Ersten User anlegen + +```bash +sudo docker exec -it pamietnik-api-1 /createuser ``` --- -## 4. Synology-spezifische Hinweise - -| Problem | Lösung | -|---------|--------| -| `permission denied` bei Docker-Befehlen | Immer `sudo docker ...` verwenden | -| Ports < 1024 nicht bindbar | Internen Port > 1024 wählen, z.B. `LISTEN_PORT: 8080` | -| Docker-Socket im Container Manager UI nicht mountbar | `docker run` via SSH ausführen | -| `act_runner` kann `localhost:3000` nicht erreichen | NAS-IP statt `localhost` in `GITEA_INSTANCE_URL` | -| Volume-Mount wird ignoriert | `valid_volumes` in `config.yaml` eintragen | -| Session-Cookies funktionieren nicht | `Secure: false` setzen bei HTTP-Deployment | -| `GRANT ALL PRIVILEGES` reicht nicht | Zusätzlich `GRANT ALL ON SCHEMA public` ausführen (PG 15+) | - ---- - -## 5. Wartung +## Logs & Wartung ```bash # Logs -sudo docker compose -f /volume2/docker/shared/docker-compose.yml logs -f postgres -sudo docker compose -f /volume2/docker//docker-compose.yml logs -f +sudo docker compose -f /volume2/docker/pamietnik/docker-compose.yml logs -f + +# Neustart +sudo docker compose -f /volume2/docker/pamietnik/docker-compose.yml restart # Backup -sudo docker exec shared-postgres-1 pg_dump -U postgres \ - > /volume2/docker/shared/backup_$(date +%Y%m%d)_.sql - -# Alle Container stoppen -sudo docker compose -f /volume2/docker/shared/docker-compose.yml down -sudo docker compose -f /volume2/docker//docker-compose.yml down +sudo docker exec shared-postgres-1 pg_dump -U postgres pamietnik \ + > /volume2/docker/shared/backup_$(date +%Y%m%d)_pamietnik.sql ``` - ---- - -## 6. Dienste & Ports - -| Dienst | Port | Beschreibung | -|--------|------|--------------| -| Gitea | 3000 | Git-Server + CI/CD | -| PostgreSQL | 5433 | Geteilte DB (intern: 5432) | -| Pamietnik | 9050 | Tagebuch-App | diff --git a/infra/gitea-actions.md b/infra/gitea-actions.md deleted file mode 100644 index 4ee1bd5..0000000 --- a/infra/gitea-actions.md +++ /dev/null @@ -1,156 +0,0 @@ -# Gitea Actions — Verwendung & Referenz - -## Grundprinzip - -Gitea Actions ist kompatibel mit GitHub Actions Syntax. Workflow-Dateien liegen in `.gitea/workflows/*.yml` im Repository. Bei Push auf `main` wird der Workflow automatisch ausgeführt. - -``` -Push → Gitea → act_runner auf NAS → Job-Container (docker:latest) → Deploy -``` - ---- - -## Workflow-Struktur - -```yaml -name: Deploy to NAS # Anzeigename in Gitea UI - -on: - push: - branches: [main] # Trigger: Push auf main - -jobs: - deploy: # Job-Name - runs-on: self-hosted # Welcher Runner-Typ - container: - image: docker:latest # Job läuft in diesem Container - options: -v /volume2/docker:/volume2/docker # Volume-Mounts - - steps: - - name: Schritt 1 - run: echo "Shell-Befehl" - - - name: Schritt 2 - run: | - echo "Mehrzeiliger" - echo "Shell-Block" -``` - ---- - -## Expressions - -Werte aus Gitea zur Laufzeit einsetzen: - -| Expression | Quelle | Beispiel | -|------------|--------|---------| -| `${{ secrets.NAME }}` | Secrets (maskiert in Logs) | `${{ secrets.DB_PASSWORD }}` | -| `${{ vars.NAME }}` | Variables (sichtbar in Logs) | `${{ vars.DEPLOY_DIR }}` | -| `${{ github.repository }}` | Repo-Metadaten | `christoph/pamietnik` | -| `${{ github.sha }}` | Aktueller Commit-Hash | `abc1234...` | - -**Secrets vs Variables:** -- **Secrets** → Passwörter, Tokens, API-Keys (`secrets.NAME`) -- **Variables** → Pfade, Ports, Namen, alles Nicht-Sensible (`vars.NAME`) - -Setzen unter: **Repository → Einstellungen → Actions → Secrets / Variables** - ---- - -## act_runner auf der NAS - -Der Runner empfängt Jobs von Gitea und führt sie aus. - -### Laufender Container -```bash -sudo docker ps | grep gitea-runner -sudo docker logs gitea-runner -f # Live-Logs -``` - -### Konfiguration -`/volume2/docker/gitea-runner/config.yaml`: -```yaml -runner: - name: "nas-runner" - -container: - valid_volumes: - - /volume2/docker # Erlaubt Volume-Mounts in Job-Containern -``` - -### Wichtige Einschränkungen auf Synology -- `GITEA_INSTANCE_URL` muss NAS-IP sein (`192.168.1.4:3000`), **nicht** `localhost` - → Job-Container sehen `localhost` als sich selbst, nicht als NAS -- Docker-Socket wird automatisch in Job-Container propagiert - → **nicht** nochmals in `container.options` mounten (→ Duplicate-Fehler) -- `valid_volumes` in `config.yaml` pflegen, sonst werden Mounts ignoriert - ---- - -## Job-Container: docker:latest - -Für Deployments wird `docker:latest` als Job-Container verwendet: - -```yaml -container: - image: docker:latest - options: -v /volume2/docker:/volume2/docker -``` - -**Was ist enthalten:** -- Docker CLI (`docker`, `docker compose`) -- `wget`, `sh`, `git` -- **Nicht enthalten:** `curl`, `bash` (nur `sh`) - -**Volume-Mount `-v /volume2/docker:/volume2/docker`:** -Macht den NAS-Dateipfad im Container verfügbar — notwendig damit git clone, .env schreiben und docker compose auf die richtigen Dateien zugreifen. - ---- - -## Secrets & Variables anlegen - -**Gitea UI:** -1. Repository → Einstellungen → Actions -2. "Secrets" für Passwörter/Tokens → `secrets.NAME` -3. "Variables" für Konfiguration → `vars.NAME` - -**Typische Variablen pro Projekt:** - -| Name | Typ | Beispielwert | -|------|-----|-------------| -| `DEPLOY_DIR` | Variable | `/volume2/docker/pamietnik` | -| `APP_PORT` | Variable | `9050` | -| `DB_USER` | Variable | `pamietnik` | -| `DB_NAME` | Variable | `pamietnik` | -| `DB_PASSWORD` | Secret | `` | - -> **Achtung:** Keine Leerzeichen am Ende von Variable-Werten — führt zu Parsing-Fehlern in Expressions. - ---- - -## Workflow-Status überwachen - -**Gitea UI:** Repository → Actions → laufenden/letzten Job anklicken - -**Runner-Logs live:** -```bash -sudo docker logs gitea-runner -f -``` - -**Job neu triggern (leerer Commit):** -```bash -git commit --allow-empty -m "ci: retrigger deploy" && git push -``` - ---- - -## Fehlersuche - -| Fehler | Ursache | Lösung | -|--------|---------|--------| -| `connection refused :3000` | `localhost` statt NAS-IP | `GITEA_INSTANCE_URL=http://192.168.1.4:3000` | -| `Duplicate mount point` | Socket doppelt gemountet | Nur einmal in `container.options` oder nur Runner-Mount | -| `not a valid volume, will be ignored` | `valid_volumes` fehlt | In `config.yaml` eintragen | -| `curl: not found` | nicht in docker:latest | `wget -qO-` verwenden | -| `cannot find node in PATH` | `actions/checkout@v4` braucht Node | Stattdessen `git clone/pull` direkt verwenden | -| Variable leer (`/docker-compose.yml`) | Als Secret statt Variable gesetzt | Unter Variables (nicht Secrets) anlegen |