Slim down infra/: keep only project-specific deployment docs
All checks were successful
Deploy to NAS / deploy (push) Successful in 34s

General infra setup (NAS, act_runner, Gitea Actions reference)
moved to separate infra repo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-04-07 18:49:17 +02:00
parent ad65102fdc
commit 156917ece1
3 changed files with 40 additions and 437 deletions

View File

@@ -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 <user> -d <db>`
**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 <user>;
```
---
## 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/<projekt>/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/<org>/<repo>.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`

View File

@@ -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/<projekt>/
├── <app>-Container :<port> — 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=<sicheres-passwort>" | 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 <dbname>;
CREATE USER <dbuser> WITH PASSWORD '<passwort>';
GRANT ALL PRIVILEGES ON DATABASE <dbname> TO <dbuser>;
GRANT ALL ON SCHEMA public TO <dbuser>; -- wichtig für PostgreSQL 15+
CREATE DATABASE pamietnik;
CREATE USER pamietnik WITH PASSWORD '<passwort>';
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=<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/<projekt>/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/<projekt>/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/<projekt>` |
| `DB_USER` | `<dbuser>` |
| `DB_NAME` | `<dbname>` |
| `APP_PORT` | `<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/<org>/<repo>.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/<projekt>/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 <dbname> \
> /volume2/docker/shared/backup_$(date +%Y%m%d)_<dbname>.sql
# Alle Container stoppen
sudo docker compose -f /volume2/docker/shared/docker-compose.yml down
sudo docker compose -f /volume2/docker/<projekt>/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 |

View File

@@ -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 | `<passwort>` |
> **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 |