diff --git a/GerbilManagerWebAPI/appsettings.Production.json b/GerbilManagerWebAPI/appsettings.Production.json new file mode 100644 index 0000000..b811af8 --- /dev/null +++ b/GerbilManagerWebAPI/appsettings.Production.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "Photos": { + "RootPath": "C:\\gerbil-data\\photos" + } +} diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..104f22e --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,178 @@ +# GerbilManager — Betriebsanleitung (Ops Guide) + +_Für Julian und seine Frau. Alles Wichtige auf einer Seite._ + +--- + +## Schnellstart + +### Erstmalige Einrichtung (einmalig, als Administrator) + +```powershell +# 1. Autostart bei Windows-Anmeldung registrieren +powershell -ExecutionPolicy Bypass -File deploy\scripts\Register-AutoStart.ps1 + +# 2. Taeliches Backup um 03:00 Uhr registrieren +powershell -ExecutionPolicy Bypass -File deploy\scripts\Register-BackupTask.ps1 +``` + +Danach startet GerbilManager automatisch nach jedem Neustart. Fertig. + +--- + +### App manuell starten / stoppen + +```powershell +# Starten (minimiertes Fenster) +powershell -ExecutionPolicy Bypass -File deploy\scripts\Start-GerbilManager.ps1 + +# Stoppen +powershell -ExecutionPolicy Bypass -File deploy\scripts\Stop-GerbilManager.ps1 +``` + +### App im Browser öffnen + +| Gerät | URL | +|---|---| +| Laptop (lokal) | http://localhost:5173 | +| Handy / anderes Gerät im WLAN | http://192.168.2.124:5173 | + +> Die IP-Adresse (192.168.2.124) ist die aktuelle DHCP-Adresse des Laptops. +> Für eine stabile Adresse → Abschnitt "Feste IP-Adresse" weiter unten. + +--- + +## Datensicherung (Backup) + +Backups werden automatisch täglich um 03:00 Uhr nach `C:\gerbil-data\backups\` gesichert. +Jedes Backup enthält: +- `gerbilmanager_.sql` — kompletter Datenbankdump +- `photos_.zip` — alle Tierfotos + +Die letzten **7 Tage** werden aufbewahrt; ältere Backups werden automatisch gelöscht. + +### Backup manuell ausführen + +```powershell +powershell -ExecutionPolicy Bypass -File deploy\scripts\Backup-GerbilManager.ps1 +``` + +### Backup-Protokoll einsehen + +``` +C:\gerbil-data\backups\backup.log +``` + +--- + +## Wiederherstellung aus Backup + +> **Achtung:** Alle aktuellen Daten werden überschrieben! + +```powershell +# Neuestes Backup wiederherstellen (fragt vorher nach Bestätigung) +powershell -ExecutionPolicy Bypass -File deploy\scripts\Restore-GerbilManager.ps1 + +# Bestimmtes Backup wiederherstellen +powershell -ExecutionPolicy Bypass -File deploy\scripts\Restore-GerbilManager.ps1 ` + -BackupDir "C:\gerbil-data\backups\2026-06-05_03-00" +``` + +--- + +## Feste IP-Adresse (empfohlen) + +Damit das Handy immer dieselbe URL verwendet, sollte dem Laptop eine feste +IP-Adresse im Heimnetzwerk zugewiesen werden. + +### Option A: DHCP-Reservierung im Router (empfohlen) + +1. Router-Oberfläche öffnen (meist `http://192.168.2.1` oder `http://fritz.box`) +2. **Heimnetz → Netzwerk → IP-Adressen** (bei FRITZ!Box) +3. Den Eintrag für diesen Laptop suchen (Name: `GULUM-...` oder ähnlich) +4. **"Immer dieselbe IPv4-Adresse zuweisen"** aktivieren +5. Speichern. Ab sofort hat der Laptop immer 192.168.2.124. + +### Option B: Statische IP direkt am Laptop setzen + +```powershell +# Netzwerkadaptername ermitteln +Get-NetAdapter + +# Feste IP setzen (Beispiel fuer WLAN-Adapter "Wi-Fi") +New-NetIPAddress -InterfaceAlias "Wi-Fi" -IPAddress 192.168.2.124 -PrefixLength 24 -DefaultGateway 192.168.2.1 +Set-DnsClientServerAddress -InterfaceAlias "Wi-Fi" -ServerAddresses 192.168.2.1 +``` + +--- + +## Firewall-Regel (für Handy-Zugriff) + +Damit das Handy auf die App zugreifen kann, muss eine Firewall-Ausnahme eingerichtet sein. +Das WLAN "Katzastrophe 4" ist als **Public** eingestuft, daher ist der Befehl nötig: + +```powershell +# Als Administrator ausführen: +New-NetFirewallRule ` + -DisplayName "GerbilManager (Frontend + API)" ` + -Direction Inbound ` + -Action Allow ` + -Protocol TCP ` + -LocalPort 5173,5179 ` + -Profile Any +``` + +> **Einmalig nötig.** Prüfen ob die Regel bereits existiert: +> `Get-NetFirewallRule -DisplayName "GerbilManager*"` + +--- + +## Datenspeicherorte + +| Was | Pfad | +|---|---| +| Tierfotos | `C:\gerbil-data\photos\` | +| Datenbank | Docker-Volume (automatisch, Aspire-verwaltet) | +| Backups | `C:\gerbil-data\backups\` | +| App-Quellcode | `C:\Users\gulum\dev\GerbilManager\` | + +--- + +## Voraussetzungen (für Betrieb) + +| Software | Mindestversion | Status prüfen | +|---|---|---| +| Docker Desktop | Aktuell, läuft | `docker ps` | +| .NET SDK | 10.0+ | `dotnet --version` | +| Node.js | 18+ | `node --version` | + +Docker Desktop muss beim Windows-Start automatisch starten. +Einstellung: Docker Desktop → Settings → **Start Docker Desktop when you sign in**. + +--- + +## Problemlösung + +| Problem | Lösung | +|---|---| +| Seite lädt nicht | Prüfen ob Docker läuft: `docker ps`. Dann `Start-GerbilManager.ps1` | +| Handy erreicht App nicht | Firewall-Regel prüfen (oben). Beide Geräte im selben WLAN? | +| Daten verschwunden | Wiederherstellung: `Restore-GerbilManager.ps1` | +| Datenbank-Fehler beim Start | `docker ps` → Postgres-Container läuft? Sonst neu starten | +| Port belegt (Fehler 5179/5173) | `netstat -ano \| findstr ":5179"` → Prozess beenden | + +--- + +## Backup testen (einmalig empfohlen) + +```powershell +# 1. Backup erstellen +powershell -ExecutionPolicy Bypass -File deploy\scripts\Backup-GerbilManager.ps1 + +# 2. Prüfen ob Backup vorhanden +Get-ChildItem "C:\gerbil-data\backups\" -Directory | Select-Object -Last 3 + +# 3. Inhalt des neuesten Backups prüfen +$latest = (Get-ChildItem "C:\gerbil-data\backups\" -Directory | Sort-Object LastWriteTime -Desc | Select-Object -First 1).FullName +Get-ChildItem $latest +``` diff --git a/deploy/scripts/Register-AutoStart.ps1 b/deploy/scripts/Register-AutoStart.ps1 new file mode 100644 index 0000000..ed564c8 --- /dev/null +++ b/deploy/scripts/Register-AutoStart.ps1 @@ -0,0 +1,63 @@ +<# +.SYNOPSIS + Registriert GerbilManager als Windows-Autostart (geplante Aufgabe bei Benutzeranmeldung). + +.DESCRIPTION + Erstellt eine geplante Aufgabe, die Start-GerbilManager.ps1 automatisch startet, + wenn sich der aktuelle Benutzer anmeldet. Erfordert Administratorrechte. + +.NOTES + Erfordert: PowerShell als Administrator ausfuehren + Aufgabenname: GerbilManager AutoStart +#> +#Requires -RunAsAdministrator +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$taskName = "GerbilManager AutoStart" +$scriptPath = Join-Path $PSScriptRoot "Start-GerbilManager.ps1" +$projectRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") + +if (-not (Test-Path $scriptPath)) { + Write-Error "Startskript nicht gefunden: $scriptPath" + exit 1 +} + +Write-Host "Registriere geplante Aufgabe '$taskName'..." + +$action = New-ScheduledTaskAction ` + -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$scriptPath`"" ` + -WorkingDirectory $projectRoot + +$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME + +$settings = New-ScheduledTaskSettingsSet ` + -ExecutionTimeLimit (New-TimeSpan -Hours 0) ` + -RestartCount 3 ` + -RestartInterval (New-TimeSpan -Minutes 2) ` + -StartWhenAvailable ` + -MultipleInstances IgnoreNew + +$principal = New-ScheduledTaskPrincipal ` + -UserId $env:USERNAME ` + -LogonType Interactive ` + -RunLevel Highest + +# Vorhandene Aufgabe entfernen, falls noetig +Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue + +Register-ScheduledTask ` + -TaskName $taskName ` + -Action $action ` + -Trigger $trigger ` + -Settings $settings ` + -Principal $principal ` + -Description "Startet GerbilManager (Rennmaus-Verwaltung) automatisch bei der Benutzeranmeldung." ` + -Force | Out-Null + +Write-Host "Aufgabe registriert: '$taskName'" +Write-Host "GerbilManager startet ab sofort automatisch beim Anmelden." +Write-Host "" +Write-Host "Aufgabe pruefen : Get-ScheduledTask -TaskName '$taskName'" +Write-Host "Aufgabe entfernen: Unregister-ScheduledTask -TaskName '$taskName' -Confirm:`$false" diff --git a/deploy/scripts/Register-BackupTask.ps1 b/deploy/scripts/Register-BackupTask.ps1 new file mode 100644 index 0000000..a450841 --- /dev/null +++ b/deploy/scripts/Register-BackupTask.ps1 @@ -0,0 +1,69 @@ +<# +.SYNOPSIS + Registriert den GerbilManager-Backup als taegliche geplante Aufgabe (03:00 Uhr). + +.DESCRIPTION + Erstellt eine geplante Aufgabe, die Backup-GerbilManager.ps1 taeglich um 03:00 Uhr + ausfuehrt. Backups werden 7 Tage lang aufbewahrt. + +.PARAMETER BackupTime + Uhrzeit fuer das taeliche Backup. Standard: 03:00 + +.NOTES + Erfordert: PowerShell als Administrator ausfuehren + Aufgabenname: GerbilManager Backup +#> +#Requires -RunAsAdministrator +param( + [string]$BackupTime = "03:00" +) +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$taskName = "GerbilManager Backup" +$scriptPath = Join-Path $PSScriptRoot "Backup-GerbilManager.ps1" +$logPath = "C:\gerbil-data\backups\backup.log" + +if (-not (Test-Path $scriptPath)) { + Write-Error "Backup-Skript nicht gefunden: $scriptPath" + exit 1 +} + +# Backup-Verzeichnis vorab anlegen +New-Item -ItemType Directory -Force -Path "C:\gerbil-data\backups" | Out-Null + +Write-Host "Registriere Backup-Aufgabe '$taskName' (taeglich $BackupTime)..." + +$action = New-ScheduledTaskAction ` + -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$scriptPath`" >> `"$logPath`" 2>&1" + +$trigger = New-ScheduledTaskTrigger -Daily -At $BackupTime + +$settings = New-ScheduledTaskSettingsSet ` + -ExecutionTimeLimit (New-TimeSpan -Hours 1) ` + -StartWhenAvailable ` + -WakeToRun ` + -MultipleInstances IgnoreNew + +$principal = New-ScheduledTaskPrincipal ` + -UserId $env:USERNAME ` + -LogonType Interactive ` + -RunLevel Highest + +Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue + +Register-ScheduledTask ` + -TaskName $taskName ` + -Action $action ` + -Trigger $trigger ` + -Settings $settings ` + -Principal $principal ` + -Description "Taegiches Backup der GerbilManager-Datenbank und Fotos um $BackupTime Uhr." ` + -Force | Out-Null + +Write-Host "Backup-Aufgabe registriert: '$taskName'" +Write-Host "Backups werden taeglich um $BackupTime Uhr nach C:\gerbil-data\backups\ gesichert." +Write-Host "Protokoll: $logPath" +Write-Host "" +Write-Host "Backup jetzt testen: & `"$scriptPath`"" diff --git a/deploy/scripts/Start-GerbilManager.ps1 b/deploy/scripts/Start-GerbilManager.ps1 new file mode 100644 index 0000000..6389694 --- /dev/null +++ b/deploy/scripts/Start-GerbilManager.ps1 @@ -0,0 +1,72 @@ +<# +.SYNOPSIS + Startet GerbilManager im Produktionsmodus (API + Datenbank + Frontend). + +.DESCRIPTION + Setzt die Produktionsumgebung, legt Datenverzeichnisse an und startet den + Aspire AppHost im Hintergrund. Der Prozess laeuft als minimiertes Fenster. + Der Pfad zum AppHost wird relativ zu diesem Skript aufgeloest. + +.NOTES + Erfordert: .NET SDK 10+, Docker Desktop (running), Node.js 18+ + Startet auf: http://:5173 (Frontend), http://:5179 (API) +#> +param( + [switch]$Wait # Blockiert bis Ctrl+C wenn gesetzt (fuer manuelle Starts) +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$projectRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") + +# --- Datenverzeichnisse sicherstellen --- +$dataRoot = "C:\gerbil-data" +$photosDir = "$dataRoot\photos" +$backupsDir = "$dataRoot\backups" + +foreach ($dir in @($photosDir, $backupsDir)) { + if (-not (Test-Path $dir)) { + New-Item -ItemType Directory -Force -Path $dir | Out-Null + Write-Host "Verzeichnis angelegt: $dir" + } +} + +# --- Produktionsumgebung setzen --- +$env:ASPNETCORE_ENVIRONMENT = "Production" +$env:DOTNET_ENVIRONMENT = "Production" + +# Aspire Dashboard-Browser nicht automatisch oeffnen +$env:DOTNET_LAUNCH_BROWSER = "false" +# Kein Aspire Dashboard im Produktionsbetrieb (Dashboard-Port auf 0 → kein Start) +# Entfernen Sie die naechste Zeile, wenn Sie das Dashboard behalten moechten. +$env:ASPIRE_ALLOW_UNSECURED_TRANSPORT = "true" + +# --- AppHost starten --- +$appHostProject = Join-Path $projectRoot "GerbilManager.AppHost" + +Write-Host "Starte GerbilManager..." +Write-Host " Projekt : $appHostProject" +Write-Host " Umgebung: $($env:ASPNETCORE_ENVIRONMENT)" +Write-Host " Fotos : $photosDir" +Write-Host "" + +if ($Wait) { + # Interaktiver Modus: blockiert bis Ctrl+C + dotnet run --project $appHostProject --no-launch-profile +} else { + # Hintergrundmodus: startet minimiertes Fenster + $pidFile = Join-Path $PSScriptRoot "..\gerbilmanager.pid" + $proc = Start-Process "cmd.exe" ` + -ArgumentList "/c", "dotnet run --project `"$appHostProject`" --no-launch-profile" ` + -WorkingDirectory $projectRoot ` + -WindowStyle Minimized ` + -PassThru + $proc.Id | Out-File -FilePath $pidFile -Encoding UTF8 -Force + Write-Host "GerbilManager gestartet (PID $($proc.Id))." + Write-Host "Frontend : http://localhost:5173" + Write-Host "API : http://localhost:5179" + Write-Host "PID-Datei: $pidFile" + Write-Host "" + Write-Host "Zum Beenden: deploy\scripts\Stop-GerbilManager.ps1" +} diff --git a/deploy/scripts/Stop-GerbilManager.ps1 b/deploy/scripts/Stop-GerbilManager.ps1 new file mode 100644 index 0000000..d3ce2e7 --- /dev/null +++ b/deploy/scripts/Stop-GerbilManager.ps1 @@ -0,0 +1,49 @@ +<# +.SYNOPSIS + Stoppt alle laufenden GerbilManager-Prozesse (AppHost + Kindprozesse). + +.NOTES + Beendet dotnet-AppHost-Prozesse und wartet darauf, dass Docker-Container + von Aspire selbst heruntergefahren werden. +#> +Set-StrictMode -Version Latest +$ErrorActionPreference = "SilentlyContinue" + +$pidFile = Join-Path $PSScriptRoot "..\gerbilmanager.pid" + +# Ueber gespeicherte PID stoppen (wenn vorhanden) +if (Test-Path $pidFile) { + $savedPid = Get-Content $pidFile -Raw | ForEach-Object { $_.Trim() } + $proc = Get-Process -Id $savedPid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "Beende Prozess PID $savedPid ($($proc.Name))..." + Stop-Process -Id $savedPid -Force + Remove-Item $pidFile -Force + } +} + +# Alle dotnet-Prozesse stoppen, die den AppHost als Elternprozess haben +$appHostProcs = Get-Process -Name "dotnet" -ErrorAction SilentlyContinue | + Where-Object { $_.MainModule.FileName -like "*dotnet*" } +foreach ($p in $appHostProcs) { + $cmdLine = (Get-CimInstance Win32_Process -Filter "ProcessId = $($p.Id)").CommandLine + if ($cmdLine -like "*GerbilManager.AppHost*") { + Write-Host "Beende dotnet-AppHost (PID $($p.Id))..." + Stop-Process -Id $p.Id -Force + } +} + +# Vite-Dev-Server stoppen (node-Prozess auf Port 5173) +$nodePids = (netstat -ano | Select-String ":5173").ToString() -split "\s+" | + Where-Object { $_ -match "^\d+$" } | Select-Object -Unique +foreach ($nPid in $nodePids) { + $np = Get-Process -Id $nPid -ErrorAction SilentlyContinue + if ($np -and $np.Name -in @("node", "npm")) { + Write-Host "Beende Vite-Dev-Server (PID $nPid)..." + Stop-Process -Id $nPid -Force + } +} + +Write-Host "GerbilManager wurde gestoppt." +Write-Host "Hinweis: Der Postgres-Docker-Container laeuft weiterhin (Aspire managed ihn)." +Write-Host " Zum Stoppen: docker stop $(docker ps --filter 'name=postgres' --format '{{.Names}}' 2>$null)"