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

7.7 KiB
Raw Blame History

name description
vps-deploy 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

# 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:

# 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)

# 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)

ssh root@195.246.231.210 "cd /opt/krisenvorrat && docker compose restart"

Docker-Konfiguration auf dem VPS

Dockerfile (/opt/krisenvorrat/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)

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

ssh root@195.246.231.210 "docker ps"

Logs anzeigen

# 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

# 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

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

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.