Revert OPS-1 Windows-plan commits (dada3af, 6a37f9e): superseded by TrueNAS deployment target; re-landed on main against GIT-3 — content preserved on Dwight's branch-to-be
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Photos": {
|
||||
"RootPath": "C:\\gerbil-data\\photos"
|
||||
}
|
||||
}
|
||||
178
deploy/README.md
178
deploy/README.md
@@ -1,178 +0,0 @@
|
||||
# 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_<datum>.sql` — kompletter Datenbankdump
|
||||
- `photos_<datum>.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
|
||||
```
|
||||
@@ -1,126 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sichert die GerbilManager-Datenbank (pg_dump) und den Fotoordner.
|
||||
|
||||
.DESCRIPTION
|
||||
1. Findet den laufenden Aspire-Postgres-Docker-Container.
|
||||
2. Fuehrt pg_dump aus und speichert das SQL-Dump in C:\gerbil-data\backups\.
|
||||
3. Kopiert den Fotoordner als ZIP ins Backup-Verzeichnis.
|
||||
4. Rotiert alte Backups: behaelt die letzten $KeepDays Tage.
|
||||
|
||||
.PARAMETER BackupRoot
|
||||
Pfad zum Backup-Verzeichnis. Standard: C:\gerbil-data\backups
|
||||
|
||||
.PARAMETER PhotosRoot
|
||||
Pfad zum Fotoordner. Standard: C:\gerbil-data\photos
|
||||
|
||||
.PARAMETER KeepDays
|
||||
Anzahl der Tage, die Backups aufbewahrt werden. Standard: 7
|
||||
|
||||
.EXAMPLE
|
||||
.\Backup-GerbilManager.ps1
|
||||
.\Backup-GerbilManager.ps1 -KeepDays 14 -BackupRoot D:\Sicherungen
|
||||
#>
|
||||
param(
|
||||
[string]$BackupRoot = "C:\gerbil-data\backups",
|
||||
[string]$PhotosRoot = "C:\gerbil-data\photos",
|
||||
[int]$KeepDays = 7
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm"
|
||||
$backupDir = Join-Path $BackupRoot $timestamp
|
||||
$logFile = Join-Path $BackupRoot "backup.log"
|
||||
$dbDumpFile = Join-Path $backupDir "gerbilmanager_$timestamp.sql"
|
||||
$photoZip = Join-Path $backupDir "photos_$timestamp.zip"
|
||||
|
||||
function Log([string]$msg) {
|
||||
$line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $msg"
|
||||
Write-Host $line
|
||||
Add-Content -Path $logFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
# --- Voraussetzungen pruefen ---
|
||||
if (-not (Get-Command "docker" -ErrorAction SilentlyContinue)) {
|
||||
Log "FEHLER: Docker nicht gefunden. Backup abgebrochen."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Backup-Verzeichnis anlegen ---
|
||||
New-Item -ItemType Directory -Force -Path $backupDir | Out-Null
|
||||
Log "Backup-Verzeichnis: $backupDir"
|
||||
|
||||
# --- Postgres-Container finden ---
|
||||
Log "Suche Postgres-Container..."
|
||||
$containers = docker ps --format "{{.Names}}" 2>&1
|
||||
$pgContainer = $containers -split "`n" | Where-Object { $_ -match "postgres" } | Select-Object -First 1
|
||||
|
||||
if (-not $pgContainer) {
|
||||
Log "FEHLER: Kein laufender Postgres-Container gefunden. Ist GerbilManager gestartet?"
|
||||
exit 1
|
||||
}
|
||||
$pgContainer = $pgContainer.Trim()
|
||||
Log "Gefundener Container: $pgContainer"
|
||||
|
||||
# Aspire setzt scram-sha-256 Auth - Passwort aus Container-Env lesen
|
||||
$pgPassword = (docker inspect $pgContainer --format "{{range .Config.Env}}{{println .}}{{end}}" 2>&1) -split "`n" |
|
||||
Where-Object { $_ -match "^POSTGRES_PASSWORD=" } |
|
||||
ForEach-Object { $_ -replace "^POSTGRES_PASSWORD=", "" } |
|
||||
Select-Object -First 1
|
||||
if (-not $pgPassword) {
|
||||
Log "FEHLER: POSTGRES_PASSWORD nicht im Container gefunden."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Datenbank-Dump ---
|
||||
Log "Starte pg_dump fuer Datenbank 'gerbilmanager'..."
|
||||
try {
|
||||
docker exec -e "PGPASSWORD=$pgPassword" $pgContainer `
|
||||
pg_dump --clean --if-exists --format=plain --username=postgres gerbilmanager `
|
||||
| Out-File -FilePath $dbDumpFile -Encoding UTF8
|
||||
|
||||
$dumpSize = [math]::Round((Get-Item $dbDumpFile).Length / 1KB, 1)
|
||||
Log "Datenbank-Dump erstellt: $dbDumpFile ($dumpSize KB)"
|
||||
} catch {
|
||||
Log "FEHLER beim Datenbank-Dump: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Dump-Validierung: muss mindestens 'CREATE TABLE' enthalten
|
||||
$dumpContent = Get-Content $dbDumpFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($dumpContent -notlike "*CREATE TABLE*" -and $dumpContent -notlike "*PostgreSQL*") {
|
||||
Log "WARNUNG: Dump-Datei sieht ungueltig aus - pruefen Sie $dbDumpFile manuell."
|
||||
}
|
||||
|
||||
# --- Fotos sichern ---
|
||||
if (Test-Path $PhotosRoot) {
|
||||
$photoCount = (Get-ChildItem $PhotosRoot -File -ErrorAction SilentlyContinue).Count
|
||||
if ($photoCount -gt 0) {
|
||||
Log "Komprimiere $photoCount Fotos nach $photoZip..."
|
||||
try {
|
||||
Compress-Archive -Path "$PhotosRoot\*" -DestinationPath $photoZip -Force
|
||||
$zipSize = [math]::Round((Get-Item $photoZip).Length / 1MB, 1)
|
||||
Log "Foto-Archiv erstellt: $photoZip ($zipSize MB)"
|
||||
} catch {
|
||||
Log "WARNUNG: Foto-Backup fehlgeschlagen: $_"
|
||||
}
|
||||
} else {
|
||||
Log "Kein Fotoordner oder keine Fotos vorhanden - Foto-Backup uebersprungen."
|
||||
}
|
||||
} else {
|
||||
Log "Fotoordner nicht gefunden ($PhotosRoot) - Foto-Backup uebersprungen."
|
||||
}
|
||||
|
||||
# --- Rotation: alte Backups loeschen ---
|
||||
Log "Rotiere Backups (behalte letzte $KeepDays Tage)..."
|
||||
$cutoff = (Get-Date).AddDays(-$KeepDays)
|
||||
$oldBackups = Get-ChildItem -Path $BackupRoot -Directory |
|
||||
Where-Object { $_.LastWriteTime -lt $cutoff }
|
||||
foreach ($old in $oldBackups) {
|
||||
Log "Loesche altes Backup: $($old.FullName)"
|
||||
Remove-Item $old.FullName -Recurse -Force
|
||||
}
|
||||
$remaining = @(Get-ChildItem -Path $BackupRoot -Directory).Count
|
||||
Log "Backup abgeschlossen. Vorhandene Backups: $remaining"
|
||||
@@ -1,63 +0,0 @@
|
||||
<#
|
||||
.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"
|
||||
@@ -1,69 +0,0 @@
|
||||
<#
|
||||
.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`""
|
||||
@@ -1,152 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Stellt GerbilManager aus einem Backup wieder her.
|
||||
|
||||
.DESCRIPTION
|
||||
WARNUNG: Ueberschreibt alle aktuellen Datenbankdaten und Fotos!
|
||||
|
||||
1. Stoppt GerbilManager (falls laufend).
|
||||
2. Stellt die Datenbank aus dem pg_dump-SQL-File wieder her.
|
||||
3. Entpackt das Foto-Archiv.
|
||||
4. Startet GerbilManager neu.
|
||||
|
||||
.PARAMETER BackupDir
|
||||
Pfad zum Backup-Unterverzeichnis (z.B. C:\gerbil-data\backups\2026-06-06_03-00).
|
||||
Wenn nicht angegeben, wird das neueste verfuegbare Backup verwendet.
|
||||
|
||||
.PARAMETER PhotosRoot
|
||||
Pfad zum Fotoordner. Standard: C:\gerbil-data\photos
|
||||
|
||||
.PARAMETER NoRestart
|
||||
GerbilManager nach der Wiederherstellung NICHT neu starten.
|
||||
|
||||
.EXAMPLE
|
||||
# Neuestes Backup wiederherstellen
|
||||
.\Restore-GerbilManager.ps1
|
||||
|
||||
# Bestimmtes Backup wiederherstellen
|
||||
.\Restore-GerbilManager.ps1 -BackupDir "C:\gerbil-data\backups\2026-06-05_03-00"
|
||||
#>
|
||||
param(
|
||||
[string]$BackupDir = "",
|
||||
[string]$PhotosRoot = "C:\gerbil-data\photos",
|
||||
[switch]$NoRestart
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$backupRoot = "C:\gerbil-data\backups"
|
||||
|
||||
function Log([string]$msg) {
|
||||
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] $msg"
|
||||
}
|
||||
|
||||
# --- Backup-Verzeichnis bestimmen ---
|
||||
if ($BackupDir -eq "") {
|
||||
$latest = Get-ChildItem -Path $backupRoot -Directory |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
if (-not $latest) {
|
||||
Log "FEHLER: Kein Backup gefunden in $backupRoot"
|
||||
exit 1
|
||||
}
|
||||
$BackupDir = $latest.FullName
|
||||
}
|
||||
|
||||
if (-not (Test-Path $BackupDir)) {
|
||||
Log "FEHLER: Backup-Verzeichnis nicht gefunden: $BackupDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Dateien im Backup suchen ---
|
||||
$sqlFile = Get-ChildItem -Path $BackupDir -Filter "*.sql" | Select-Object -First 1
|
||||
$zipFile = Get-ChildItem -Path $BackupDir -Filter "*.zip" | Select-Object -First 1
|
||||
|
||||
if (-not $sqlFile) {
|
||||
Log "FEHLER: Kein SQL-Dump (.sql) in $BackupDir gefunden."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Log "Wiederherstellung aus: $BackupDir"
|
||||
Log " Datenbank: $($sqlFile.Name)"
|
||||
if ($zipFile) { Log " Fotos : $($zipFile.Name)" }
|
||||
|
||||
# --- Bestaetigung ---
|
||||
Write-Host ""
|
||||
Write-Host "WARNUNG: Alle aktuellen Datenbankdaten und Fotos werden UNWIDERRUFLICH ueberschrieben!" -ForegroundColor Red
|
||||
$confirm = Read-Host "Fortfahren? (ja/nein)"
|
||||
if ($confirm -ne "ja") {
|
||||
Log "Abgebrochen."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- GerbilManager stoppen (falls laufend) ---
|
||||
$stopScript = Join-Path $PSScriptRoot "Stop-GerbilManager.ps1"
|
||||
if (Test-Path $stopScript) {
|
||||
Log "Stoppe GerbilManager..."
|
||||
& $stopScript
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
|
||||
# --- Postgres-Container finden ---
|
||||
Log "Suche Postgres-Container..."
|
||||
$containers = docker ps --format "{{.Names}}" 2>&1
|
||||
$pgContainer = $containers -split "`n" | Where-Object { $_ -match "postgres" } | Select-Object -First 1
|
||||
if (-not $pgContainer) {
|
||||
# Container koennte gestoppt sein - starten wir ihn kurz
|
||||
Log "Postgres-Container laeuft nicht. Starte ihn zunaechst..."
|
||||
Log "Bitte starten Sie GerbilManager kurz einmal (Start-GerbilManager.ps1) und versuchen Sie es erneut."
|
||||
exit 1
|
||||
}
|
||||
$pgContainer = $pgContainer.Trim()
|
||||
Log "Container: $pgContainer"
|
||||
|
||||
# Aspire setzt scram-sha-256 Auth - Passwort aus Container-Env lesen
|
||||
$pgPassword = (docker inspect $pgContainer --format "{{range .Config.Env}}{{println .}}{{end}}" 2>&1) -split "`n" |
|
||||
Where-Object { $_ -match "^POSTGRES_PASSWORD=" } |
|
||||
ForEach-Object { $_ -replace "^POSTGRES_PASSWORD=", "" } |
|
||||
Select-Object -First 1
|
||||
if (-not $pgPassword) {
|
||||
Log "FEHLER: POSTGRES_PASSWORD nicht im Container gefunden."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Datenbankwiederherstellung ---
|
||||
Log "Stelle Datenbank wieder her aus $($sqlFile.Name)..."
|
||||
|
||||
# Verbindungen trennen
|
||||
docker exec -e "PGPASSWORD=$pgPassword" $pgContainer psql -U postgres -c `
|
||||
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'gerbilmanager' AND pid <> pg_backend_pid();" | Out-Null
|
||||
|
||||
# SQL-Dump einspielen (--clean im Dump macht DROP TABLE IF EXISTS vor CREATE)
|
||||
Get-Content $sqlFile.FullName -Raw | docker exec -i -e "PGPASSWORD=$pgPassword" $pgContainer psql -U postgres gerbilmanager
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Log "FEHLER: Datenbankwiederherstellung fehlgeschlagen (exit $LASTEXITCODE)."
|
||||
exit 1
|
||||
}
|
||||
Log "Datenbankwiederherstellung erfolgreich."
|
||||
|
||||
# --- Fotos wiederherstellen ---
|
||||
if ($zipFile) {
|
||||
Log "Stelle Fotos wieder her..."
|
||||
if (Test-Path $PhotosRoot) {
|
||||
Remove-Item "$PhotosRoot\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
New-Item -ItemType Directory -Force -Path $PhotosRoot | Out-Null
|
||||
}
|
||||
Expand-Archive -Path $zipFile.FullName -DestinationPath $PhotosRoot -Force
|
||||
$count = (Get-ChildItem $PhotosRoot -File).Count
|
||||
Log "Fotos wiederhergestellt: $count Dateien."
|
||||
}
|
||||
|
||||
# --- GerbilManager neu starten ---
|
||||
if (-not $NoRestart) {
|
||||
$startScript = Join-Path $PSScriptRoot "Start-GerbilManager.ps1"
|
||||
if (Test-Path $startScript) {
|
||||
Log "Starte GerbilManager neu..."
|
||||
& $startScript
|
||||
}
|
||||
}
|
||||
|
||||
Log "Wiederherstellung abgeschlossen."
|
||||
@@ -1,72 +0,0 @@
|
||||
<#
|
||||
.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://<LAN-IP>:5173 (Frontend), http://<LAN-IP>: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"
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<#
|
||||
.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)"
|
||||
Reference in New Issue
Block a user