diff --git a/.env.docker b/.env.docker new file mode 100644 index 0000000..b323980 --- /dev/null +++ b/.env.docker @@ -0,0 +1,13 @@ +# Dummy environment for Docker build stage +# These values are only needed so Next.js can compile during docker build +# Runtime values are injected via docker-compose environment +DATABASE_URL=postgresql://lageplan:lageplan_secret@db:5432/lageplan +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=build-time-secret-not-used-at-runtime +MINIO_ENDPOINT=minio +MINIO_PORT=9000 +MINIO_ACCESS_KEY=minioadmin +MINIO_SECRET_KEY=minioadmin123 +MINIO_BUCKET=lageplan-icons +MINIO_USE_SSL=false +MINIO_PUBLIC_URL=http://localhost:9000 diff --git a/.env.example b/.env.example index fd900e9..c9ef67e 100644 --- a/.env.example +++ b/.env.example @@ -27,3 +27,8 @@ MINIO_PUBLIC_URL=http://localhost:9002 # Web App WEB_PORT=3000 NODE_ENV=development + +# --- CI/CD / Registry (nur für Portainer Deployment) --- +# Gitea Registry Login für Watchtower (automatische Image-Updates) +GITEA_REGISTRY_USER=adminpepe +GITEA_REGISTRY_PASS=dein_gitea_token_oder_passwort diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index cf93dc3..62da8ce 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,7 +1,48 @@ -# Deployment wird von Portainer erledigt (nicht Gitea Actions). -# Portainer Stack zeigt auf dieses Repo und baut bei Push automatisch neu. -# Siehe docker-compose.portainer.yml für Details. -# -# Dieses File ist leer damit keine pending Jobs mehr entstehen. -name: disabled -on: workflow_dispatch +name: Build and Push Docker Image + +on: + push: + branches: + - main + - master + workflow_dispatch: + +env: + REGISTRY: git.purepixel.ch + IMAGE: git.purepixel.ch/adminpepe/lageplan + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest + type=sha,prefix=,suffix=,format=short + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 8bc0b92..5d1d61d 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,3 @@ Reglement_*/ # Stack env (contains secrets) stack.env -.env.docker diff --git a/deploy/README.md b/deploy/README.md index d803e71..214eb96 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,102 +1,168 @@ -# Lageplan — Portainer Deployment +# Lageplan — CI/CD & Portainer Deployment -## Architektur +## Übersicht ``` -Browser → :3000 (Web App) → intern: db:5432, minio:9000 +┌─────────────┐ Push ┌─────────────────┐ Build + Push ┌─────────────────┐ +│ Dein PC │ ────────────► │ Gitea (git) │ ──────────────────► │ Gitea Registry │ +│ (VS Code) │ │ git.purepixel │ │ (Docker Image) │ +└─────────────┘ └─────────────────┘ └────────┬────────┘ + │ + │ Pull + ▼ + ┌─────────────────┐ + │ Portainer │ + │ (Stack Deploy) │ + └─────────────────┘ ``` -Nur **ein Port (3000)** muss exponiert werden. DB und MinIO laufen rein intern im Docker-Netzwerk. Icons werden über die Web-App gestreamt — kein direkter MinIO-Zugriff nötig. +1. **Push auf `main`** → Gitea Actions baut Docker Image +2. **Image wird gepusht** → Gitea Container Registry (`git.purepixel.ch`) +3. **Watchtower (in Portainer)** → prüft alle 60s auf neue Images und startet Container neu --- -## Dateien +## Voraussetzungen -| Datei | Beschreibung | -|-------|-------------| -| `lageplan-web-v1.0.0.tar` | Docker Image (~92 MB) | -| `portainer-stack.yml` | Stack YAML für Portainer | -| `.env.example` | Environment Variables Vorlage | +- Gitea läuft mit Container Registry aktiviert +- Gitea Actions Runner ist registriert (`deploy/docker-compose.runner.yml`) +- Portainer Stack ist deployed mit korrekten Environment-Variablen --- -## Schritt 1: Image auf Server laden +## Schritt 1: Gitea Container Registry aktivieren -```bash -# Kopieren -scp lageplan-web-v1.0.0.tar user@server:/tmp/ +In Gitea: +1. **Admin-Konsole** → **Konfiguration** → **Pakete** +2. **Container Registry** auf `Aktiviert` setzen +3. Speichern -# Auf dem Server laden -docker load -i /tmp/lageplan-web-v1.0.0.tar +Oder direkt in der `app.ini`: +```ini +[packages] +ENABLED = true -# Prüfen -docker images | grep lageplan +[package.container_registry] +ENABLED = true ``` --- -## Schritt 2: Stack in Portainer erstellen +## Schritt 2: Gitea Actions Runner registrieren + +1. In Gitea: **Admin** → **Actions** → **Runners** → **Neuen Runner erstellen** +2. Token kopieren +3. In Portainer: Stack `gitea-runner` deployen mit [`deploy/docker-compose.runner.yml`](docker-compose.runner.yml) +4. Environment Variable `RUNNER_TOKEN` = das kopierte Token + +--- + +## Schritt 3: Gitea Access Token erstellen + +Das CI/CD Workflow braucht einen Token um Images in die Registry zu pushen: + +1. Gitea → **Einstellungen** → **Anwendungen** → **Token erstellen** +2. Name: `registry-push` +3. Berechtigungen: `package:write` (mindestens) +4. Token kopieren und als **Repository Secret** hinterlegen: + - Repo → **Einstellungen** → **Secrets** → **Neues Secret** + - Name: `GITEA_TOKEN` + - Wert: das kopierte Token + +--- + +## Schritt 4: Portainer Stack deployen 1. **Portainer** → `Stacks` → `+ Add stack` 2. **Name**: `lageplan` -3. **Web editor**: Inhalt von `portainer-stack.yml` einfügen -4. **Environment variables** setzen: +3. **Build method**: `Repository` +4. **Git-URL**: `https://git.purepixel.ch/adminpepe/Lageplan.git` +5. **Compose path**: `docker-compose.portainer.yml` +6. **GitOps updates**: ✅ Aktivieren +7. **Mechanism**: `Webhook` +8. **Webhook URL kopieren** (für später) -| Variable | Wert | -|----------|------| -| `POSTGRES_USER` | `lageplan` | -| `POSTGRES_PASSWORD` | *(sicheres Passwort)* | -| `POSTGRES_DB` | `lageplan` | -| `MINIO_ROOT_USER` | `minioadmin` | -| `MINIO_ROOT_PASSWORD` | *(sicheres Passwort)* | -| `MINIO_BUCKET` | `lageplan-icons` | -| `WEB_PORT` | `3000` | -| `NEXTAUTH_URL` | `http://SERVER_IP:3000` | -| `NEXTAUTH_SECRET` | *(langer zufälliger String)* | +### Environment Variables setzen: -> **Tipp**: Secret generieren: `openssl rand -base64 32` +| Variable | Wert | Beschreibung | +|----------|------|-------------| +| `POSTGRES_USER` | `lageplan` | DB User | +| `POSTGRES_PASSWORD` | *(sicheres Passwort)* | DB Passwort | +| `POSTGRES_DB` | `lageplan` | DB Name | +| `NEXTAUTH_URL` | `https://lageplan.ch` | Deine Domain | +| `NEXTAUTH_SECRET` | *(openssl rand -base64 32)* | Auth Secret | +| `MINIO_ROOT_USER` | `minioadmin` | MinIO User | +| `MINIO_ROOT_PASSWORD` | *(sicheres Passwort)* | MinIO Passwort | +| `MINIO_BUCKET` | `lageplan-icons` | Bucket Name | +| `MINIO_PUBLIC_URL` | `https://s3.deinedomain.ch` | MinIO externe URL | +| `GITEA_REGISTRY_USER` | `adminpepe` | Gitea User für Watchtower | +| `GITEA_REGISTRY_PASS` | *(Token oder Passwort)* | Gitea Passwort/Token | -5. **Deploy the stack** +9. **Deploy the stack** --- -## Schritt 3: Datenbank initialisieren (einmalig) +## Schritt 5: Webhook in Gitea eintragen -In Portainer: Container `web` → Console → `/bin/sh`: +Damit Portainer bei jedem Push automatisch neu deployed: -```bash -npx prisma db push -npx prisma db seed -``` - -Oder per SSH: - -```bash -docker exec -it lageplan-web-1 npx prisma db push -docker exec -it lageplan-web-1 npx prisma db seed -``` +1. Gitea Repo → **Einstellungen** → **Webhooks** → **Neuer Webhook** → `Gitea` +2. **Ziel-URL**: Die kopierte Portainer Webhook URL +3. **HTTP-Methode**: `POST` +4. **Trigger**: Nur `Push events` (oder auch `Branch filter: main`) +5. **Webhook aktivieren** → Hinzufügen --- -## Schritt 4: Zugriff +## Schritt 6: Erstes Deployment testen -- **Web App**: `http://SERVER_IP:3000` -- **Login**: `admin@lageplan.local` / `admin123` +1. Lokal einen Push auf `main` machen: + ```bash + git add . + git commit -m "Test CI/CD" + git push origin main + ``` +2. In Gitea: **Actions** Tab → Build-Job sollte laufen +3. Wenn grün → Image wurde in Registry gepusht +4. Watchtower (in Portainer) holt neues Image innerhalb von 60s +5. App ist unter `NEXTAUTH_URL` erreichbar --- -## Update +## Troubleshooting +### Gitea Actions startet nicht +- Prüfen ob Runner registriert ist: Gitea → Admin → Actions → Runners +- Runner muss `Idle` oder `Active` zeigen + +### Image Push schlägt fehl (401 Unauthorized) +- `GITEA_TOKEN` Secret im Repo korrekt hinterlegt? +- Token hat Berechtigung `package:write`? +- Registry in Gitea aktiviert? + +### Watchtower zieht kein neues Image +- `GITEA_REGISTRY_USER` und `GITEA_REGISTRY_PASS` in Portainer gesetzt? +- Image-Name in `docker-compose.portainer.yml` korrekt? +- Watchtower Logs prüfen: Portainer → Container `watchtower` → Logs + +### App startet nicht / DB-Fehler +- Environment Variables in Portainer korrekt? +- `DATABASE_URL` wird automatisch gebaut, nur `POSTGRES_*` muss gesetzt werden +- Bei erstem Start: Prisma Migrations/Seed im Web-Container ausführen: + ```bash + docker exec -it lageplan-web-1 npx prisma db push + docker exec -it lageplan-web-1 npx prisma db seed + ``` + +--- + +## Manuelles Update (falls nötig) + +Wenn Watchtower mal nicht greift: ```bash -# Lokal: neues Image bauen + exportieren -docker compose build web -docker tag lageplan-web:latest lageplan-web:v1.1.0 -docker save lageplan-web:v1.1.0 -o deploy/lageplan-web-v1.1.0.tar - -# Server: laden -docker load -i lageplan-web-v1.1.0.tar - -# Portainer: Stack → Editor → Image-Tag ändern → Update the stack +# Auf dem Portainer-Host +docker pull git.purepixel.ch/adminpepe/lageplan:latest +docker compose -f docker-compose.portainer.yml up -d web ``` --- diff --git a/deploy/portainer.env b/deploy/portainer.env index b6d5dfa..27873cc 100644 --- a/deploy/portainer.env +++ b/deploy/portainer.env @@ -7,3 +7,8 @@ MINIO_BUCKET=lageplan-icons WEB_PORT=3000 NEXTAUTH_URL=http://SERVER_IP:3000 NEXTAUTH_SECRET=HIER_LANGEN_ZUFAELLIGEN_STRING_GENERIEREN +MINIO_PUBLIC_URL=http://SERVER_IP:9000 + +# Gitea Registry Auth für Watchtower (automatische Image-Updates) +GITEA_REGISTRY_USER=adminpepe +GITEA_REGISTRY_PASS=HIER_GITEA_TOKEN_ODER_PASSWORT_SETZEN diff --git a/docker-compose.gitea.yml b/docker-compose.gitea.yml index bc8ccfa..9e60268 100644 --- a/docker-compose.gitea.yml +++ b/docker-compose.gitea.yml @@ -1,5 +1,5 @@ ############################################## -# Gitea — Lightweight Git Server +# Gitea — Lightweight Git Server + Container Registry # # Verwendung in Portainer: # 1. Stacks → Add Stack → "Gitea" @@ -12,6 +12,10 @@ # 3. Repository "lageplan" erstellen # 4. Vom PC aus: git init → git remote add origin → git push # +# Container Registry aktivieren: +# 1. Gitea Admin → Konfiguration → Pakete → Container Registry aktivieren +# 2. Oder app.ini: [packages] ENABLED = true +# # Daten werden in gitea_data persistiert. ############################################## @@ -27,6 +31,9 @@ services: - GITEA__server__ROOT_URL=https://git.purepixel.ch - GITEA__server__HTTP_PORT=3000 - GITEA__server__LFS_START_SERVER=true + # Container Registry aktivieren + - GITEA__packages__ENABLED=true + - GITEA__package__container_registry__ENABLED=true volumes: - gitea_data:/data - /etc/timezone:/etc/timezone:ro @@ -43,4 +50,3 @@ volumes: networks: lageplan_lageplan-net: external: true - diff --git a/docker-compose.portainer.yml b/docker-compose.portainer.yml index 3d0b94d..6235998 100644 --- a/docker-compose.portainer.yml +++ b/docker-compose.portainer.yml @@ -10,7 +10,8 @@ # 6. Environment-Variablen setzen (siehe unten) # 7. Deploy # -# Danach: Push auf main → Portainer baut automatisch neu +# Danach: Push auf main → Gitea Actions baut Image → +# Portainer Webhook/Watchtower holt neues Image # # Benötigte Environment-Variablen: # POSTGRES_USER (default: lageplan) @@ -21,6 +22,8 @@ # MINIO_ROOT_USER (default: minioadmin) # MINIO_ROOT_PASSWORD (ÄNDERN!) # MINIO_PUBLIC_URL (z.B. https://s3.example.com) +# GITEA_REGISTRY_USER (für Watchtower Registry-Auth) +# GITEA_REGISTRY_PASS (für Watchtower Registry-Auth) ############################################## services: @@ -81,10 +84,8 @@ services: - lageplan # ─── Lageplan Web App ────────────────────── + # Image kommt aus Gitea Container Registry (gebaut via Gitea Actions) web: - build: - context: . - dockerfile: Dockerfile image: git.purepixel.ch/adminpepe/lageplan:latest restart: unless-stopped environment: @@ -118,6 +119,9 @@ services: WATCHTOWER_POLL_INTERVAL: 60 WATCHTOWER_CLEANUP: "true" WATCHTOWER_LABEL_ENABLE: "false" + # Gitea Registry Auth + REPO_USER: ${GITEA_REGISTRY_USER} + REPO_PASS: ${GITEA_REGISTRY_PASS} networks: - lageplan