diff --git a/.github/skills/publish/publish-apk.ps1 b/.github/skills/publish/publish-apk.ps1 index 93011e3..b4b1288 100644 --- a/.github/skills/publish/publish-apk.ps1 +++ b/.github/skills/publish/publish-apk.ps1 @@ -9,6 +9,11 @@ 4. Server-Version per API aktualisieren (kein Container-Neustart nötig) 5. Verifizieren (überspringbar mit -SkipVerify) 6. git commit + push – überspringbar mit -SkipPush + + Rollback-Strategie: + - Fehler vor/während Upload → build.gradle.kts wird automatisch zurückgesetzt + - Fehler nach Upload (API) → APK liegt schon oben; kein Rollback, Recovery-Hinweis + - Fehler bei Verify/Git → Deployment war erfolgreich; nur Warnung, kein Abbruch .PARAMETER VersionName Neue versionName (z.B. "1.8"). Wenn weggelassen, bleibt die aktuelle. .PARAMETER VersionCode @@ -35,128 +40,253 @@ param( [switch] $SkipPush ) +Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" -$VPS = "root@195.246.231.210" -$RemoteDir = "/opt/bollwerk" -$ServerUrl = "https://bollwerk.online" + +$VPS = "root@195.246.231.210" +$RemoteDir = "/opt/bollwerk" +$ServerUrl = "https://bollwerk.online" $BuildGradle = "app/build.gradle.kts" -# --- Admin-Token laden --- +# ───────────────────────────────────────────────────────────────────────────── +# Hilfsfunktionen +# ───────────────────────────────────────────────────────────────────────────── + +function Rollback-Gradle { + param([string]$OriginalContent) + Write-Host "[ROLLBACK] build.gradle.kts wird zurueckgesetzt..." -ForegroundColor DarkYellow + try { + [System.IO.File]::WriteAllText((Resolve-Path $BuildGradle).Path, $OriginalContent) + Write-Host "[ROLLBACK] build.gradle.kts zurueckgesetzt." -ForegroundColor DarkYellow + } catch { + Write-Warning "[ROLLBACK FEHLGESCHLAGEN] Bitte build.gradle.kts manuell korrigieren: $_" + } +} + +function Write-Step { + param([string]$Text) + Write-Host "`n$Text" -ForegroundColor Yellow +} + +function Write-OK { param([string]$Text); Write-Host "[OK] $Text" -ForegroundColor Green } +function Write-Skip { param([string]$Text); Write-Host "[--] $Text" -ForegroundColor DarkGray } +function Write-Fail { param([string]$Text); Write-Host "[!!] $Text" -ForegroundColor Red } + +# ───────────────────────────────────────────────────────────────────────────── +# Pre-Checks (nichts wird verändert) +# ───────────────────────────────────────────────────────────────────────────── + +# Admin-Token $AdminToken = $env:BOLLWERK_ADMIN_TOKEN if (-not $AdminToken) { - Write-Error "Umgebungsvariable BOLLWERK_ADMIN_TOKEN ist nicht gesetzt. Bitte setzen: `$env:BOLLWERK_ADMIN_TOKEN = 'dein-token'" + Write-Fail "BOLLWERK_ADMIN_TOKEN ist nicht gesetzt." + Write-Host " Bitte setzen: `$env:BOLLWERK_ADMIN_TOKEN = 'dein-token'" -ForegroundColor DarkGray exit 1 } -# --- Version aus build.gradle.kts lesen und bumpen --- -$gradleContent = Get-Content $BuildGradle -Raw -$currentCode = [int]([regex]::Match($gradleContent, 'versionCode\s*=\s*(\d+)').Groups[1].Value) -$currentName = [regex]::Match($gradleContent, 'versionName\s*=\s*"([^"]+)"').Groups[1].Value -$newCode = if ($VersionCode -gt 0) { $VersionCode } else { $currentCode + 1 } -$newName = if ($VersionName) { $VersionName } else { $currentName } +# build.gradle.kts lesen + Regex validieren +if (-not (Test-Path $BuildGradle)) { + Write-Fail "Datei nicht gefunden: $BuildGradle" + exit 1 +} +$originalContent = Get-Content $BuildGradle -Raw + +$codeMatch = [regex]::Match($originalContent, 'versionCode\s*=\s*(\d+)') +$nameMatch = [regex]::Match($originalContent, 'versionName\s*=\s*"([^"]+)"') +if (-not $codeMatch.Success) { + Write-Fail "versionCode nicht in $BuildGradle gefunden. Regex-Muster: 'versionCode = '" + exit 1 +} +if (-not $nameMatch.Success) { + Write-Fail "versionName nicht in $BuildGradle gefunden. Regex-Muster: versionName = `"`"" + exit 1 +} + +$currentCode = [int]$codeMatch.Groups[1].Value +$currentName = $nameMatch.Groups[1].Value +$newCode = if ($VersionCode -gt 0) { $VersionCode } else { $currentCode + 1 } +$newName = if ($VersionName) { $VersionName } else { $currentName } + +if ($newCode -le $currentCode) { + Write-Fail "Neuer versionCode ($newCode) muss groesser als aktueller ($currentCode) sein." + exit 1 +} + +# SSH-Agent (vor jeder Dateiänderung prüfen) +$null = ssh-add -l 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Fail "SSH-Agent hat keinen Key. Bitte ausfuehren: ssh-add C:\Users\JensR\.ssh\id_ed25519" + exit 1 +} +Write-OK "Pre-Checks bestanden (Token, Gradle-Parsing, SSH-Agent)" Write-Host "" Write-Host "=== Publish APK v$newName (build $newCode) ===" -ForegroundColor Cyan Write-Host " $currentName ($currentCode) → $newName ($newCode)" -ForegroundColor Gray -Write-Host "" -$updated = $gradleContent -replace "versionCode\s*=\s*$currentCode", "versionCode = $newCode" +# ───────────────────────────────────────────────────────────────────────────── +# Schritt 0: build.gradle.kts patchen +# ───────────────────────────────────────────────────────────────────────────── +$patchedContent = $originalContent -replace "versionCode\s*=\s*$currentCode\b", "versionCode = $newCode" if ($VersionName) { - $updated = $updated -replace 'versionName\s*=\s*"[^"]+"', "versionName = `"$newName`"" -} -[System.IO.File]::WriteAllText((Resolve-Path $BuildGradle).Path, $updated) -Write-Host "[OK] build.gradle.kts: versionCode=$newCode, versionName=$newName" -ForegroundColor Green - -# --- Build --- -if ($SkipBuild) { - Write-Host "[--] Build uebersprungen (-SkipBuild)" -ForegroundColor DarkGray -} else { - Write-Host "`n[1/4] APK bauen..." -ForegroundColor Yellow - & ./gradlew assembleDebug - if ($LASTEXITCODE -ne 0) { Write-Error "Build fehlgeschlagen."; exit 1 } - Write-Host "[OK] APK gebaut" -ForegroundColor Green -} - -if (-not (Test-Path $ApkPath)) { - Write-Error "APK nicht gefunden: $ApkPath - bitte zuerst './gradlew assembleDebug' ausfuehren." - exit 1 -} - -# SSH-Agent prüfen -$sshKeys = ssh-add -l 2>&1 -if ($LASTEXITCODE -ne 0) { - Write-Error "SSH-Agent hat keinen Key geladen. Bitte 'ssh-add' ausfuehren." - exit 1 -} -Write-Host "[OK] SSH-Agent aktiv" -ForegroundColor Green - -# --- Schritt 2: APK hochladen --- -Write-Host "`n[2/4] APK hochladen..." -ForegroundColor Yellow -ssh $VPS "mkdir -p $RemoteDir/data" -scp $ApkPath "${VPS}:${RemoteDir}/data/app-latest.apk" -if ($LASTEXITCODE -ne 0) { Write-Error "APK-Upload fehlgeschlagen."; exit 1 } -Write-Host "[OK] APK hochgeladen" -ForegroundColor Green - -# --- Schritt 3: Server über neue Version benachrichtigen --- -Write-Host "`n[3/4] Server-Version aktualisieren (API-Call)..." -ForegroundColor Yellow - -$body = @{ versionCode = $newCode; versionName = $newName } | ConvertTo-Json -Compress -$headers = @{ - "Authorization" = "Bearer $AdminToken" - "Content-Type" = "application/json" + $patchedContent = $patchedContent -replace 'versionName\s*=\s*"[^"]+"', "versionName = `"$newName`"" } try { - $response = Invoke-WebRequest -Uri "$ServerUrl/api/admin/version" ` - -Method POST ` - -Headers $headers ` - -Body $body ` - -UseBasicParsing - if ($response.StatusCode -ne 200) { - Write-Error "Version-Update fehlgeschlagen: HTTP $($response.StatusCode)" - exit 1 - } + [System.IO.File]::WriteAllText((Resolve-Path $BuildGradle).Path, $patchedContent) + Write-OK "build.gradle.kts aktualisiert (versionCode=$newCode, versionName=$newName)" } catch { - Write-Error "Version-Update fehlgeschlagen: $_" + Write-Fail "build.gradle.kts konnte nicht geschrieben werden: $_" exit 1 } -Write-Host "[OK] Version gesetzt: $newName (build $newCode)" -ForegroundColor Green -# --- Schritt 4: Verifizieren --- -if ($SkipVerify) { - Write-Host "`n[4/4] Verifizierung uebersprungen." -ForegroundColor DarkGray +# ───────────────────────────────────────────────────────────────────────────── +# Schritt 1: Build +# ───────────────────────────────────────────────────────────────────────────── +if ($SkipBuild) { + Write-Skip "Build uebersprungen (-SkipBuild)" } else { - Write-Host "`n[4/4] Verifizieren..." -ForegroundColor Yellow + Write-Step "[1/4] APK bauen..." + try { + & ./gradlew assembleDebug + if ($LASTEXITCODE -ne 0) { throw "Gradle exit code $LASTEXITCODE" } + Write-OK "APK gebaut" + } catch { + Write-Fail "Build fehlgeschlagen: $_" + Rollback-Gradle $originalContent + exit 1 + } +} + +if (-not (Test-Path $ApkPath)) { + Write-Fail "APK nicht gefunden nach Build: $ApkPath" + Rollback-Gradle $originalContent + exit 1 +} + +# ───────────────────────────────────────────────────────────────────────────── +# Schritt 2: APK hochladen (Rollback bei Fehler) +# ───────────────────────────────────────────────────────────────────────────── +Write-Step "[2/4] APK hochladen → VPS..." +try { + # Remote-Verzeichnis sicherstellen + $mkdirOutput = ssh -o ConnectTimeout=15 $VPS "mkdir -p $RemoteDir/data" 2>&1 + if ($LASTEXITCODE -ne 0) { throw "ssh mkdir fehlgeschlagen (Exit $LASTEXITCODE): $mkdirOutput" } + + # SCP mit Timeout-Hint (ConnectTimeout via SSH-Config oder ServerAliveInterval) + scp -o ConnectTimeout=30 $ApkPath "${VPS}:${RemoteDir}/data/app-latest.apk" + if ($LASTEXITCODE -ne 0) { throw "scp fehlgeschlagen (Exit $LASTEXITCODE)" } + + Write-OK "APK hochgeladen" +} catch { + Write-Fail "Upload fehlgeschlagen: $_" + Rollback-Gradle $originalContent + Write-Host "" + Write-Host " TIPP: Pruefen ob VPS erreichbar ist:" -ForegroundColor DarkGray + Write-Host " ssh $VPS 'echo OK'" -ForegroundColor DarkGray + exit 1 +} + +# ───────────────────────────────────────────────────────────────────────────── +# Schritt 3: Server-Version per API setzen +# Ab hier kein Rollback mehr (APK liegt schon oben – Recovery statt Rollback) +# ───────────────────────────────────────────────────────────────────────────── +Write-Step "[3/4] Server-Version aktualisieren..." +$apiBody = @{ versionCode = $newCode; versionName = $newName } | ConvertTo-Json -Compress +$apiHeaders = @{ "Authorization" = "Bearer $AdminToken"; "Content-Type" = "application/json" } +$apiSuccess = $false + +$maxRetries = 2 +for ($attempt = 1; $attempt -le $maxRetries; $attempt++) { + try { + $response = Invoke-WebRequest -Uri "$ServerUrl/api/admin/version" ` + -Method POST -Headers $apiHeaders -Body $apiBody ` + -UseBasicParsing -TimeoutSec 20 + if ($response.StatusCode -eq 200) { + $apiSuccess = $true + break + } + throw "HTTP $($response.StatusCode)" + } catch { + if ($attempt -lt $maxRetries) { + Write-Host " Versuch $attempt fehlgeschlagen ($_) – Retry in 3s..." -ForegroundColor DarkYellow + Start-Sleep -Seconds 3 + } else { + Write-Fail "API-Call nach $maxRetries Versuchen fehlgeschlagen: $_" + Write-Host "" + Write-Host " APK liegt bereits auf dem VPS. Recovery-Befehl:" -ForegroundColor DarkYellow + Write-Host " Invoke-WebRequest -Uri '$ServerUrl/api/admin/version' ``" -ForegroundColor DarkGray + Write-Host " -Method POST -UseBasicParsing ``" -ForegroundColor DarkGray + Write-Host " -Headers @{ 'Authorization'='Bearer '; 'Content-Type'='application/json' } ``" -ForegroundColor DarkGray + Write-Host " -Body '$apiBody'" -ForegroundColor DarkGray + } + } +} + +if ($apiSuccess) { + Write-OK "Version gesetzt: $newName (build $newCode)" +} else { + # Deployment war teilweise erfolgreich → trotzdem committen, aber warnen + Write-Host "" + Write-Host " WARNUNG: VPS-Version nicht aktualisiert. Build.gradle.kts wird trotzdem committet." -ForegroundColor DarkYellow +} + +# ───────────────────────────────────────────────────────────────────────────── +# Schritt 4: Verifizieren (non-fatal) +# ───────────────────────────────────────────────────────────────────────────── +if (-not $apiSuccess -or $SkipVerify) { + Write-Skip "Verifizierung uebersprungen" +} else { + Write-Step "[4/4] Verifizieren..." Start-Sleep -Seconds 2 - - $versionResponse = Invoke-WebRequest -Uri "$ServerUrl/api/version" -UseBasicParsing - $versionJson = $versionResponse.Content | ConvertFrom-Json - - if ($versionJson.versionCode -eq $newCode -and $versionJson.versionName -eq $newName) { - Write-Host "[OK] /api/version: versionCode=$($versionJson.versionCode), versionName=$($versionJson.versionName)" -ForegroundColor Green - } else { - Write-Warning "Version stimmt nicht ueberein! Erwartet: $newCode/$newName, Erhalten: $($versionJson.versionCode)/$($versionJson.versionName)" + try { + $versionJson = (Invoke-WebRequest -Uri "$ServerUrl/api/version" -UseBasicParsing -TimeoutSec 15).Content | ConvertFrom-Json + if ($versionJson.versionCode -eq $newCode -and $versionJson.versionName -eq $newName) { + Write-OK "/api/version: $($versionJson.versionName) ($($versionJson.versionCode))" + } else { + Write-Host "[??] /api/version meldet $($versionJson.versionName) ($($versionJson.versionCode)) – erwartet $newName ($newCode)" -ForegroundColor DarkYellow + } + } catch { + Write-Host "[??] /api/version nicht erreichbar: $_" -ForegroundColor DarkYellow } - $homepageResponse = Invoke-WebRequest -Uri "$ServerUrl/" -UseBasicParsing - if ($homepageResponse.StatusCode -eq 200) { - Write-Host "[OK] Homepage erreichbar (HTTP $($homepageResponse.StatusCode))" -ForegroundColor Green - } else { - Write-Warning "Homepage liefert HTTP $($homepageResponse.StatusCode)" + try { + $hp = Invoke-WebRequest -Uri "$ServerUrl/" -UseBasicParsing -TimeoutSec 10 + Write-OK "Homepage erreichbar (HTTP $($hp.StatusCode))" + } catch { + Write-Host "[??] Homepage nicht erreichbar: $_" -ForegroundColor DarkYellow } } -# --- Git Commit + Push --- -Write-Host "`n[Git] Committen..." -ForegroundColor Yellow -git add $BuildGradle -git commit -m "chore: release v$newName ($newCode)" +# ───────────────────────────────────────────────────────────────────────────── +# Git Commit + Push (non-fatal – Deployment war erfolgreich) +# ───────────────────────────────────────────────────────────────────────────── +Write-Step "[Git] Version-Bump committen..." +try { + git add $BuildGradle + git commit -m "chore: release v$newName ($newCode)" +} catch { + Write-Host "[??] git commit fehlgeschlagen: $_" -ForegroundColor DarkYellow + Write-Host " Manuell: git add $BuildGradle ; git commit -m 'chore: release v$newName ($newCode)'" -ForegroundColor DarkGray +} + if (-not $SkipPush) { - git push - Write-Host "[OK] Gepusht" -ForegroundColor Green + try { + git push + Write-OK "Gepusht" + } catch { + Write-Host "[??] git push fehlgeschlagen: $_" -ForegroundColor DarkYellow + Write-Host " Manuell: git push" -ForegroundColor DarkGray + } } else { - Write-Host "[--] Push uebersprungen (-SkipPush)" -ForegroundColor DarkGray + Write-Skip "Push uebersprungen (-SkipPush)" } -Write-Host "`n=== Publish abgeschlossen ===" -ForegroundColor Cyan -Write-Host "Homepage: $ServerUrl/" -ForegroundColor DarkGray -Write-Host "API: $ServerUrl/api/version" -ForegroundColor DarkGray +# ───────────────────────────────────────────────────────────────────────────── +# Zusammenfassung +# ───────────────────────────────────────────────────────────────────────────── +Write-Host "" +Write-Host "=== Publish abgeschlossen ===" -ForegroundColor Cyan +Write-Host " Version : $newName (build $newCode)" +Write-Host " Homepage: $ServerUrl/" +Write-Host " API : $ServerUrl/api/version"