Update infra docs with complete working setup
All checks were successful
Deploy to NAS / deploy (push) Successful in 44s

- README.md: rewrite with accurate setup steps including all lessons learned
- CLAUDE.md: update with working CI/CD patterns and known pitfalls
- gitea-actions.md: new file documenting Gitea Actions usage, expressions,
  act_runner config, and troubleshooting for future projects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-04-07 18:46:55 +02:00
parent e2419411fa
commit ad65102fdc
3 changed files with 382 additions and 226 deletions

View File

@@ -1,155 +1,124 @@
# CLAUDE.md — Pamietnik Infrastruktur # CLAUDE.md — NAS Infrastruktur
Diese Datei ist Kontext für eine dedizierte Infra-Session. Hier ist alles beschrieben, was zum Aufbau und Betrieb der Infrastruktur auf der Synology NAS notwendig ist. Kontext für Claude Code Sessions zum Thema Deployment und Infrastruktur auf der Synology NAS.
--- ---
## Umgebung ## Umgebung
- **NAS:** Synology DiskStation, DSM 7.x - **NAS:** Synology DiskStation, DSM 7.x, IP: `192.168.1.4`
- **Docker-Datenpfad:** `/volume2/docker/` (alle Container-Daten hier ablegen) - **SSH:** `ssh jacek@192.168.1.4` — Docker-Befehle erfordern `sudo`
- **Docker-Socket:** `/var/run/docker.sock` - **Docker-Datenpfad:** `/volume2/docker/` — alle persistenten Daten hier
- **Gitea:** läuft als Docker-Container auf der NAS, erreichbar unter `http://localhost:3000` - **Docker-Binary auf NAS:** `/usr/local/bin/docker`
- **SSH-Zugriff:** `ssh jacek@<NAS-IP>`, sudo-Befehle erforderlich für Docker - **Gitea:** `http://192.168.1.4:3000` (Docker-Container auf NAS)
--- ---
## Architektur ## Laufende Dienste
``` | Dienst | Container | Port | Pfad |
Synology NAS |--------|-----------|------|------|
├── Gitea (Docker) :3000 — Git-Server + CI/CD | PostgreSQL | `shared-postgres-1` | 5433 | `/volume2/docker/shared/` |
├── act_runner (Docker) — Gitea Actions Runner | act_runner | `gitea-runner` | — | `/volume2/docker/gitea-runner/` |
| Pamietnik | `pamietnik-api-1` | 9050 | `/volume2/docker/pamietnik/` |
├── /volume2/docker/shared/
│ └── postgres:16-alpine :5433 — Geteilte DB für alle Projekte ---
└── /volume2/docker/pamietnik/ ## Datenbankzugriff
└── api (Go + SPA) :9050 — Pamietnik App
**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
``` ```
**Verbindung App → Datenbank:** über `host-gateway:5433` (Docker-interner Alias für den NAS-Host) **Wichtig PostgreSQL 15+:** Nach `GRANT ALL PRIVILEGES ON DATABASE` zusätzlich:
```sql
--- GRANT ALL ON SCHEMA public TO <user>;
## Verzeichnisse auf der NAS
| Pfad | Inhalt |
|------|--------|
| `/volume2/docker/shared/pgdata` | PostgreSQL-Daten (persistent) |
| `/volume2/docker/shared/.env` | Secrets: `POSTGRES_PASSWORD` |
| `/volume2/docker/shared/docker-compose.yml` | Shared Stack (Postgres + pgAdmin) |
| `/volume2/docker/pamietnik/uploads` | Hochgeladene Bilder (persistent) |
| `/volume2/docker/pamietnik/.env` | `DB_PASSWORD`, `APP_PORT` |
| `/volume2/docker/pamietnik/docker-compose.yml` | Wird via CI/CD aus dem Repo kopiert |
| `/volume2/docker/gitea-runner/` | act_runner Konfiguration & Daten |
---
## Shared Stack (`/volume2/docker/shared/docker-compose.yml`)
```yaml
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
ports:
- "5433:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
volumes:
- /volume2/docker/shared/pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 5s
timeout: 5s
retries: 5
```
---
## Pamietnik Stack (`docker-compose.yml` im Repo-Root)
```yaml
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "${APP_PORT:-9050}:8080"
extra_hosts:
- "host-gateway:host-gateway"
environment:
DATABASE_URL: postgres://${DB_USER:-pamietnik}:${DB_PASSWORD:?DB_PASSWORD is required}@host-gateway:5433/${DB_NAME:-pamietnik}?sslmode=disable
LISTEN_ADDR: :8080
UPLOAD_DIR: /uploads
volumes:
- /volume2/docker/pamietnik/uploads:/uploads
restart: unless-stopped
``` ```
--- ---
## CI/CD: Gitea Actions ## CI/CD: Gitea Actions
**Workflow:** `.gitea/workflows/deploy.yml` — wird bei Push auf `main` ausgeführt. **Workflow-Datei:** `.gitea/workflows/deploy.yml`
**Trigger:** Push auf `main`
**Runner:** `gitea/act_runner` Container auf der NAS mit `--network host` und Docker-Socket-Mount. **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
**Gitea Secrets** (Repository → Einstellungen → Actions → Secrets): **Job-Container:** `docker:latest` mit `-v /volume2/docker:/volume2/docker`
- `DB_PASSWORD` — Passwort des `pamietnik` DB-Users - Docker CLI ist im Image enthalten
- `DEPLOY_DIR``/volume2/docker/pamietnik` - Socket wird automatisch vom Runner propagiert (nicht nochmal in `options` mounten → Duplicate-Fehler)
- `wget` statt `curl` verwenden (`curl` nicht im Image)
**Gitea Variables** (Repository → Einstellungen → Actions → Variables): **Gitea Konfiguration:**
- `DB_USER``pamietnik` - Nicht-sensitive Werte (Pfade, Ports, DB-Namen) → **Variables** (`vars.NAME`)
- `DB_NAME``pamietnik` - Passwörter, Tokens → **Secrets** (`secrets.NAME`)
- `APP_PORT``9050` - Variables mit Leerzeichen am Ende → Parsing-Fehler in Expressions
--- ---
## Dienste & URLs ## Deployment-Muster für neue Projekte
| Dienst | URL | ```yaml
|--------|-----| # docker-compose.yml im Repo
| Pamietnik | `http://<NAS-IP>:9050` | services:
| Gitea | `http://<NAS-IP>:3000` | api:
| PostgreSQL | `<NAS-IP>:5433` | 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
## Wichtige Befehle jobs:
deploy:
```bash runs-on: self-hosted
# Shared Stack starten container:
cd /volume2/docker/shared && sudo docker compose up -d image: docker:latest
options: -v /volume2/docker:/volume2/docker
# Logs steps:
sudo docker compose -f /volume2/docker/shared/docker-compose.yml logs -f - name: Pull code
sudo docker compose -f /volume2/docker/pamietnik/docker-compose.yml logs -f api run: |
if [ -d "${{ vars.DEPLOY_DIR }}/.git" ]; then
# Datenbank-User anlegen git -C ${{ vars.DEPLOY_DIR }} pull
sudo docker exec -it shared-postgres-1 psql -U postgres else
git clone http://192.168.1.4:3000/<org>/<repo>.git ${{ vars.DEPLOY_DIR }}
# Backup fi
sudo docker exec shared-postgres-1 pg_dump -U postgres pamietnik \ - name: Write .env
> /volume2/docker/shared/backup_$(date +%Y%m%d).sql 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
``` ```
--- ---
## Synology-spezifische Hinweise ## Bekannte Fallstricke
- Docker-Befehle erfordern `sudo` - **`Secure: true` auf Session-Cookies** schlägt fehl bei HTTP → `Secure: false` setzen
- Ports unter 1024 können Container nicht binden → `PGADMIN_LISTEN_PORT: 8080` nötig - **Ports unter 1024** können Container auf Synology nicht binden → internen Port > 1024 wählen
- pgAdmin-Verzeichnis braucht UID 5050: `sudo chown -R 5050:5050 /volume2/docker/shared/pgadmin` - **`localhost`** im Job-Container zeigt auf den Container selbst, nicht auf die NAS
- Docker-Socket ist unter `/var/run/docker.sock` erreichbar - **Volume-Mounts** in Workflow `container.options` werden doppelt gemountet wenn identisch mit Runner-Mount
- Container Manager UI unterstützt keinen Datei-Mount für den Docker-Socket → `docker run` via SSH nutzen - **`/volume2/docker`** muss in `valid_volumes` der `config.yaml` stehen sonst wird Mount ignoriert
--- ---
## Vollständige Setup-Anleitung ## Vollständige Setup-Anleitung
Siehe `infra/README.md` im Repo. Siehe `infra/README.md`

View File

@@ -1,85 +1,48 @@
# Infrastruktur & Deployment # Infrastruktur & Deployment
## Übersicht ## Architektur
``` ```
Synology NAS Synology NAS (192.168.1.4)
├── /volume2/docker/shared/ ← Geteilte Infrastruktur (PostgreSQL + pgAdmin) ├── Gitea :3000 — Git + CI/CD
│ ├── docker-compose.yml ├── act_runner — Gitea Actions Runner
│ ├── .env ├── /volume2/docker/shared/
│ ├── pgdata/ ← PostgreSQL-Daten (persistent) │ ├── postgres:16-alpine :5433 — Geteilte DB (alle Projekte)
│ └── pgadmin/ ← pgAdmin-Daten (persistent) │ └── pgdata/ — Persistente Daten
└── /volume2/docker/<projekt>/
└── /volume2/docker/pamietnik/ ← Pamietnik-Deployment ├── <app>-Container :<port> — Anwendung
├── docker-compose.yml ← Kopie aus dem Repo (via CI/CD) ├── uploads/ — Persistente Uploads
── .env ── .env — Projekt-Secrets
└── uploads/ ← Hochgeladene Bilder (persistent)
``` ```
**Datenbankverbindung aus Containern:** `host-gateway:5433`
--- ---
## 1. Geteilte Infrastruktur einrichten (einmalig) ## 1. Shared PostgreSQL einrichten (einmalig)
### Verzeichnisse anlegen
```bash ```bash
sudo mkdir -p /volume2/docker/shared/pgdata 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
``` ```
### docker-compose.yml kopieren Datenbank anlegen:
```bash
sudo cp infra/docker-compose.yml /volume2/docker/shared/docker-compose.yml
```
### .env anlegen
Datei `/volume2/docker/shared/.env`:
```env
POSTGRES_PASSWORD=<sicheres-passwort>
```
### Starten
```bash
cd /volume2/docker/shared
sudo docker compose up -d
```
### Datenbank & User anlegen
```bash ```bash
sudo docker exec -it shared-postgres-1 psql -U postgres sudo docker exec -it shared-postgres-1 psql -U postgres
``` ```
```sql ```sql
CREATE DATABASE pamietnik; CREATE DATABASE <dbname>;
CREATE USER pamietnik WITH PASSWORD 'deinPasswort'; CREATE USER <dbuser> WITH PASSWORD '<passwort>';
GRANT ALL PRIVILEGES ON DATABASE pamietnik TO pamietnik; GRANT ALL PRIVILEGES ON DATABASE <dbname> TO <dbuser>;
GRANT ALL ON SCHEMA public TO <dbuser>; -- wichtig für PostgreSQL 15+
\q \q
``` ```
--- ---
## 2. Pamietnik-Deployment einrichten (einmalig) ## 2. act_runner einrichten (einmalig)
```bash
sudo mkdir -p /volume2/docker/pamietnik/uploads
```
Datei `/volume2/docker/pamietnik/.env`:
```env
DB_PASSWORD=<passwort-von-oben>
APP_PORT=9050
```
---
## 3. Gitea CI/CD einrichten (einmalig)
### act_runner starten
Token holen: **Gitea → Site-Administration → Actions → Runner → Runner erstellen** Token holen: **Gitea → Site-Administration → Actions → Runner → Runner erstellen**
@@ -90,76 +53,144 @@ sudo docker run -d \
--network host \ --network host \
-v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/docker.sock:/var/run/docker.sock \
-v /volume2/docker/gitea-runner:/data \ -v /volume2/docker/gitea-runner:/data \
-e GITEA_INSTANCE_URL=http://localhost:3000 \ -e GITEA_INSTANCE_URL=http://192.168.1.4:3000 \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token-aus-gitea> \ -e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
-e GITEA_RUNNER_NAME=nas-runner \ -e GITEA_RUNNER_NAME=nas-runner \
-e GITEA_RUNNER_LABELS=self-hosted,linux,amd64 \ -e GITEA_RUNNER_LABELS=self-hosted,linux,amd64 \
gitea/act_runner:latest gitea/act_runner:latest
``` ```
### Gitea Secrets & Variables setzen > **Wichtig:** `GITEA_INSTANCE_URL` muss die NAS-IP sein, nicht `localhost` —
> Job-Container können `localhost` nicht auflösen.
**Repository → Einstellungen → Actions → Secrets:** `/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 | | Secret | Wert |
|--------|------| |--------|------|
| `DB_PASSWORD` | Passwort des `pamietnik` DB-Users | | `DB_PASSWORD` | Datenbankpasswort |
| `DEPLOY_DIR` | `/volume2/docker/pamietnik` |
**Repository → Einstellungen → Actions → Variables:**
**Variables** (Repository → Einstellungen → Actions → Variables):
| Variable | Wert | | Variable | Wert |
|----------|------| |----------|------|
| `DB_USER` | `pamietnik` | | `DEPLOY_DIR` | `/volume2/docker/<projekt>` |
| `DB_NAME` | `pamietnik` | | `DB_USER` | `<dbuser>` |
| `APP_PORT` | `9050` | | `DB_NAME` | `<dbname>` |
| `APP_PORT` | `<port>` |
--- ### 3.4 Gitea Actions Workflow (`.gitea/workflows/deploy.yml`)
## 4. Dienste & URLs
| Dienst | URL |
|--------|-----|
| Pamietnik App | `http://<NAS-IP>:9050` |
| PostgreSQL | `psql -h <NAS-IP> -p 5433 -U pamietnik -d pamietnik` |
---
## 5. Neues Projekt hinzufügen
```bash
sudo docker exec -it shared-postgres-1 psql -U postgres
```
```sql
CREATE DATABASE neuprojekt;
CREATE USER neuprojekt WITH PASSWORD 'passwort';
GRANT ALL PRIVILEGES ON DATABASE neuprojekt TO neuprojekt;
\q
```
In `docker-compose.yml` des neuen Projekts:
```yaml ```yaml
extra_hosts: name: Deploy to NAS
- "host-gateway:host-gateway"
environment: on:
DATABASE_URL: postgres://neuprojekt:passwort@host-gateway:5433/neuprojekt push:
branches: [main]
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
``` ```
--- ---
## 6. Wartung ## 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
```bash ```bash
# Logs # Logs
sudo docker compose -f /volume2/docker/shared/docker-compose.yml logs -f sudo docker compose -f /volume2/docker/shared/docker-compose.yml logs -f postgres
sudo docker compose -f /volume2/docker/pamietnik/docker-compose.yml logs -f api sudo docker compose -f /volume2/docker/<projekt>/docker-compose.yml logs -f
# Backup # Backup
sudo docker exec shared-postgres-1 pg_dump -U postgres pamietnik \ sudo docker exec shared-postgres-1 pg_dump -U postgres <dbname> \
> /volume2/docker/shared/backup_$(date +%Y%m%d).sql > /volume2/docker/shared/backup_$(date +%Y%m%d)_<dbname>.sql
# Stoppen # Alle Container stoppen
sudo docker compose -f /volume2/docker/shared/docker-compose.yml down sudo docker compose -f /volume2/docker/shared/docker-compose.yml down
sudo docker compose -f /volume2/docker/pamietnik/docker-compose.yml down sudo docker compose -f /volume2/docker/<projekt>/docker-compose.yml down
``` ```
---
## 6. Dienste & Ports
| Dienst | Port | Beschreibung |
|--------|------|--------------|
| Gitea | 3000 | Git-Server + CI/CD |
| PostgreSQL | 5433 | Geteilte DB (intern: 5432) |
| Pamietnik | 9050 | Tagebuch-App |

156
infra/gitea-actions.md Normal file
View File

@@ -0,0 +1,156 @@
# 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 |