bollwerk/.github/skills/vps-deploy/SKILL.md
Jens Reinemann 56ac9b1425 feat: Messaging-System mit Offline-First und WebSocket-Push (#58)
## Server
- Messages-Tabelle (id, sender_id, receiver_id, body, sent_at, delivered_at)
- MessageRepository: save/getUndelivered/getConversation/markDelivered (JOIN statt N+1)
- POST /api/messages, GET /api/messages/{userId}: Nachrichten senden/abrufen
- GET /api/users: User-Liste fuer authentifizierte User (ohne eigenen Account)
- WebSocketManager: notifyNewMessage() + isOnline()
- WebSocketRoutes: unzugestellte Nachrichten bei Reconnect pushen
- LoginResponse: userId + username ergaenzt
- Server-Dependency: kotlinx.serialization fuer shared

## App
- MessageEntity + MessageDao (Room, Migration 3->4)
- KrisenvorratDatabase v4, Migrations.MIGRATION_3_4
- MessageRepositoryImpl: Offline-First (isPending), drain bei WebSocket-Connect
- WebSocketEvent.NewMessage -> MessageDto aus shared
- WebSocketClientImpl: new_message-Event parsen
- AUTH_USER_ID in SettingsKeys, SyncServiceImpl speichert userId bei Login
- UserListScreen + UserListViewModel: User-Liste anzeigen
- ChatScreen + ChatViewModel: WhatsApp-Style Chat (links/rechts, Zeitstempel)
- Navigation: Screen.UserList, Screen.Chat, MESSAGES in Bottom-Nav
- RepositoryModule: MessageRepository gebunden

## Tests
- 234 Tests, 0 Fehler
2026-05-16 23:35:25 +02:00

218 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 Krisenvorrat 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 | 1024 MB |
| CPU | 1 |
| Disk | 25 GB SSD |
| Transfer | 1 TB/Monat |
| Docker | Docker CE 29.x + docker-compose-plugin |
| App-Verzeichnis | `/opt/krisenvorrat/` |
---
## 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 | `krisenvorrat-vps` |
---
## Deployment-Workflow
### Vollständiges Deployment (JAR neu bauen + hochladen)
```powershell
# 1. Fat-JAR lokal bauen
cd x:\krisenvorrat
.\gradlew.bat :server:buildFatJar
# 2. JAR auf VPS kopieren
scp x:\krisenvorrat\server\build\libs\server.jar root@195.246.231.210:/opt/krisenvorrat/server.jar
# 3. Container neu bauen und starten
ssh root@195.246.231.210 "cd /opt/krisenvorrat && docker compose up -d --build"
```
### Nur Container neustarten (ohne neues JAR)
```powershell
ssh root@195.246.231.210 "cd /opt/krisenvorrat && docker compose restart"
```
---
## Docker-Konfiguration auf dem VPS
### Dockerfile (`/opt/krisenvorrat/Dockerfile`)
```dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY server.jar server.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx384m", "-jar", "server.jar"]
```
**Hinweis:** `-Xmx384m` begrenzt den JVM-Heap, weil der VPS nur 1 GB RAM hat.
### docker-compose.yml (`/opt/krisenvorrat/docker-compose.yml`)
```yaml
services:
krisenvorrat:
build: .
container_name: krisenvorrat-server
restart: unless-stopped
ports:
- '8080:8080'
environment:
- KRISENVORRAT_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:** `http://195.246.231.210:8080/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 `KRISENVORRAT_ADMIN_PASSWORD` ENV wird ein zufälliges Passwort generiert und in die Logs geschrieben.
### Environment-Variablen
| Variable | Pflicht | Beschreibung |
| ------------------------------- | ------- | --------------------------------------------------- |
| `KRISENVORRAT_JWT_SECRET` | ja | Secret für JWT-Token-Signierung (mind. 32 Zeichen) |
| `KRISENVORRAT_ADMIN_PASSWORD` | nein | Admin-Passwort beim ersten Start (sonst auto-gen.) |
---
## Server-Endpunkte
| Endpunkt | Auth | Beschreibung |
| ------------------------------ | ----- | ------------------------------------- |
| `GET /api/health` | nein | Health-Check → "OK" |
| `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 | Alle User auflisten |
| `POST /api/admin/users` | Admin | Neuen User anlegen |
| `PUT /api/admin/users/{id}` | Admin | Passwort ändern |
| `DELETE /api/admin/users/{id}` | Admin | 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 krisenvorrat-server --tail 50"
# Live-Logs (Ctrl+C zum Beenden)
ssh root@195.246.231.210 "docker logs krisenvorrat-server -f"
```
### Health-Check
```powershell
# Vom lokalen PC
Invoke-WebRequest -Uri "http://195.246.231.210:8080/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/krisenvorrat && docker compose stop"
ssh root@195.246.231.210 "cd /opt/krisenvorrat && docker compose start"
ssh root@195.246.231.210 "cd /opt/krisenvorrat && 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/krisenvorrat/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/krisenvorrat/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.