---
name: vps-deploy
description: "Server auf den VPS (1984 Hosting, Island) deployen, Docker-Container verwalten, Logs prüfen, SSH-Verbindung, Troubleshooting. Trigger-Phrasen: 'deploy', 'deployen', 'VPS', 'Server deployen', 'Server starten', 'Server stoppen', 'Server logs', 'Container', 'Docker VPS', 'production', 'Produktion', '1984', 'Island-Server'."
---

# Skill: VPS Deploy

Deployt den Bollwerk Ktor-Server auf den 1984 Hosting VPS in Island.

---

## VPS-Infrastruktur

| Eigenschaft     | Wert                                      |
| --------------- | ----------------------------------------- |
| Anbieter        | 1984 Hosting (1984.is), Reykjavik, Island |
| VPS-Name        | vpshoxc2sc                                |
| IP              | `195.246.231.210`                         |
| DNS PTR         | vps-195-246-231-210.1984.is               |
| OS              | Debian 12 (Bookworm)                      |
| RAM             | 2048 MB (Upgrade Mai 2026)                |
| CPU             | 1                                         |
| Disk            | 50 GB SSD (Upgrade Mai 2026)              |
| Transfer        | 1 TB/Monat                                |
| Docker          | Docker CE 29.x + docker-compose-plugin    |
| App-Verzeichnis | `/opt/bollwerk/`                          |

---

## SSH-Zugang

```powershell
# Voraussetzung: SSH-Agent muss laufen und Key geladen sein
ssh root@195.246.231.210
```

### SSH-Agent starten (einmalig pro Windows-Session)

Der SSH-Key ist passphrase-geschützt. Der Agent muss laufen, damit Batch-Befehle funktionieren:

```powershell
# 1. Agent-Dienst starten (braucht ggf. Admin-Rechte beim ersten Mal)
Start-Service ssh-agent

# 2. Key laden (fragt einmalig nach der Passphrase)
ssh-add C:\Users\JensR\.ssh\id_ed25519

# Prüfen
ssh-add -l
```

**Wichtig:** Ohne geladenen SSH-Agent schlagen alle `ssh`/`scp`-Befehle mit `Permission denied (publickey)` fehl. Immer zuerst `ssh-add -l` prüfen.

### SSH-Key-Details

| Eigenschaft | Wert                                                    |
| ----------- | ------------------------------------------------------- |
| Typ         | Ed25519 (256 Bit)                                       |
| Fingerprint | `SHA256:J/qjVt9r8CqnoshZFQWutau+3KG7JxDzRLHPyX41+gA`    |
| Private Key | `C:\Users\JensR\.ssh\id_ed25519` (passphrase-geschützt) |
| Public Key  | `C:\Users\JensR\.ssh\id_ed25519.pub`                    |
| Kommentar   | `bollwerk-vps`                                          |

---

## Deployment-Workflow

### Vollständiges Deployment (JAR neu bauen + hochladen)

```powershell
# 1. Fat-JAR lokal bauen
cd x:\bollwerk
.\gradlew.bat :server:buildFatJar

# 2. JAR auf VPS kopieren
scp x:\bollwerk\server\build\libs\server.jar root@195.246.231.210:/opt/bollwerk/server.jar

# 3. Container neu bauen und starten
ssh root@195.246.231.210 "cd /opt/bollwerk && docker compose up -d --build"
```

### Nur Container neustarten (ohne neues JAR)

```powershell
ssh root@195.246.231.210 "cd /opt/bollwerk && docker compose restart"
```

---

## Docker-Konfiguration auf dem VPS

### Dockerfile (`/opt/bollwerk/Dockerfile`)

```dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY server.jar server.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx768m", "-jar", "server.jar"]
```

**Hinweis:** `-Xmx768m` begrenzt den JVM-Heap (VPS hat 2 GB RAM seit Upgrade Mai 2026).

### docker-compose.yml (`/opt/bollwerk/docker-compose.yml`)

```yaml
services:
  bollwerk:
    build: .
    container_name: bollwerk-server
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - BOLLWERK_JWT_SECRET=<secret>
    volumes:
      - ./data:/app/data
```

**Hinweis:** Das JWT-Secret NICHT in Skill-Dateien oder Git speichern. Es liegt nur in der `docker-compose.yml` auf dem VPS.

---

## Authentifizierung

Der Server nutzt JWT-basierte Authentifizierung (kein API-Key mehr).

### Admin-Zugang

- **Admin-UI:** `https://bollwerk.online/admin/`
- **Admin-User:** `admin`
- **Admin-Passwort:** Der User muss das Passwort selbst eingeben. Es ist NICHT gespeichert – bei Bedarf den User fragen.
- Beim ersten Start ohne `BOLLWERK_ADMIN_PASSWORD` ENV wird ein zufälliges Passwort generiert und in die Logs geschrieben.

### Environment-Variablen

| Variable                  | Pflicht | Beschreibung                                       |
| ------------------------- | ------- | -------------------------------------------------- |
| `BOLLWERK_JWT_SECRET`     | ja      | Secret für JWT-Token-Signierung (mind. 32 Zeichen) |
| `BOLLWERK_ADMIN_TOKEN`    | ja      | Bearer-Token für POST /api/admin/version (mind. 32 Zeichen) |
| `BOLLWERK_ADMIN_PASSWORD` | nein    | Admin-Passwort beim ersten Start (sonst auto-gen.) |

---

## Server-Endpunkte

| Endpunkt                          | Auth         | Beschreibung                          |
| --------------------------------- | ------------ | ------------------------------------- |
| `GET /api/health`                 | nein         | Health-Check → "OK"                   |
| `GET /api/version`                | nein         | Version + APK-URL (JSON)              |
| `POST /api/admin/version`         | Admin-Token  | Version setzen (kein Neustart nötig)  |
| `POST /api/auth/login`            | nein         | Login → JWT (Access + Refresh Token)  |
| `POST /api/auth/refresh`          | nein         | Access-Token erneuern                 |
| `GET /api/inventory`              | JWT          | Inventar des Users abrufen            |
| `PUT /api/inventory`              | JWT          | Inventar des Users hochladen          |
| `PATCH /api/inventory/items/{id}` | JWT          | Einzelnen Artikel updaten             |
| `GET /api/admin/users`            | Admin (JWT)  | Alle User auflisten                   |
| `POST /api/admin/users`           | Admin (JWT)  | Neuen User anlegen                    |
| `PUT /api/admin/users/{id}`       | Admin (JWT)  | Passwort ändern                       |
| `DELETE /api/admin/users/{id}`    | Admin (JWT)  | User löschen                          |
| `WS /ws/sync`                     | JWT          | WebSocket für Push-Benachrichtigungen |

JWT wird als `Authorization: Bearer <accessToken>` Header mitgeschickt.

---

## Monitoring & Troubleshooting

### Container-Status prüfen

```powershell
ssh root@195.246.231.210 "docker ps"
```

### Logs anzeigen

```powershell
# Letzte 50 Zeilen
ssh root@195.246.231.210 "docker logs bollwerk-server --tail 50"

# Live-Logs (Ctrl+C zum Beenden)
ssh root@195.246.231.210 "docker logs bollwerk-server -f"
```

### Health-Check

```powershell
# Vom lokalen PC
Invoke-WebRequest -Uri "https://bollwerk.online/api/health" -UseBasicParsing

# Auf dem VPS direkt
ssh root@195.246.231.210 "curl -s http://localhost:8080/api/health"
```

### Container stoppen/starten

```powershell
ssh root@195.246.231.210 "cd /opt/bollwerk && docker compose stop"
ssh root@195.246.231.210 "cd /opt/bollwerk && docker compose start"
ssh root@195.246.231.210 "cd /opt/bollwerk && docker compose down"   # Container entfernen
```

### RAM-Nutzung prüfen

```powershell
ssh root@195.246.231.210 "free -h && echo '---' && docker stats --no-stream"
```

### Daten-Persistenz

Die SQLite-Datenbank wird unter `/opt/bollwerk/data/` auf dem Host gemountet und überlebt Container-Neustarts.

---

## Bekannte Einschränkungen

- **1 GB RAM:** JVM-Heap auf 384 MB begrenzt. Kein Spielraum für weitere Dienste.
- **Kein HTTPS:** Server läuft aktuell nur auf HTTP Port 8080. Für HTTPS → Caddy als Reverse Proxy einrichten.
- **Kein CI/CD:** Deployment ist manuell (JAR bauen → scp → docker compose up). Ggf. GitHub Actions Pipeline ergänzen.
- **Dockerfile lokal:** Das Dockerfile auf dem VPS (`/opt/bollwerk/Dockerfile`) ist ein schlankes Runtime-Only-Image. Das Multi-Stage-Dockerfile im Repo-Root ist für lokale Builds gedacht.
- **SSH-Escape-Problem:** Beim Schreiben von Dateien via SSH-Heredoc werden JSON-Quotes zerstört. Dateien immer lokal erstellen und per `scp` hochladen.
