<# .SYNOPSIS Vollständiger Publish-Workflow: Version bumpen, bauen, auf VPS deployen, committen. .DESCRIPTION Führt alle Schritte des Release-Workflows aus: 1. versionCode automatisch erhöhen (aus build.gradle.kts) 2. APK bauen (./gradlew assembleDebug) – überspringbar mit -SkipBuild 3. APK per SCP auf VPS hochladen 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 Expliziter versionCode. Wenn weggelassen, wird der aktuelle automatisch um 1 erhöht. .PARAMETER ApkPath Pfad zur APK-Datei. Default: app/build/outputs/apk/debug/app-debug.apk .PARAMETER SkipBuild Gradle-Build überspringen (wenn APK bereits gebaut). .PARAMETER SkipVerify Verifizierung nach dem Deploy überspringen. .PARAMETER SkipPush Git-Push überspringen (nur lokaler Commit). .EXAMPLE & ".github/skills/publish/publish-apk.ps1" & ".github/skills/publish/publish-apk.ps1" -VersionName "2.0" & ".github/skills/publish/publish-apk.ps1" -SkipBuild -SkipPush #> param( [string] $VersionName, [int] $VersionCode = 0, [string] $ApkPath = "app/build/outputs/apk/debug/app-debug.apk", [switch] $SkipBuild, [switch] $SkipVerify, [switch] $SkipPush ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $VPS = "root@195.246.231.210" $RemoteDir = "/opt/bollwerk" $ServerUrl = "https://bollwerk.online" $BuildGradle = "app/build.gradle.kts" # ───────────────────────────────────────────────────────────────────────────── # 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-Fail "BOLLWERK_ADMIN_TOKEN ist nicht gesetzt." Write-Host " Bitte setzen: `$env:BOLLWERK_ADMIN_TOKEN = 'dein-token'" -ForegroundColor DarkGray exit 1 } # 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 # ───────────────────────────────────────────────────────────────────────────── # Schritt 0: build.gradle.kts patchen # ───────────────────────────────────────────────────────────────────────────── $patchedContent = $originalContent -replace "versionCode\s*=\s*$currentCode\b", "versionCode = $newCode" if ($VersionName) { $patchedContent = $patchedContent -replace 'versionName\s*=\s*"[^"]+"', "versionName = `"$newName`"" } try { [System.IO.File]::WriteAllText((Resolve-Path $BuildGradle).Path, $patchedContent) Write-OK "build.gradle.kts aktualisiert (versionCode=$newCode, versionName=$newName)" } catch { Write-Fail "build.gradle.kts konnte nicht geschrieben werden: $_" exit 1 } # ───────────────────────────────────────────────────────────────────────────── # Schritt 1: Build # ───────────────────────────────────────────────────────────────────────────── if ($SkipBuild) { Write-Skip "Build uebersprungen (-SkipBuild)" } else { 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 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 } 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 (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) { try { git push Write-OK "Gepusht" } catch { Write-Host "[??] git push fehlgeschlagen: $_" -ForegroundColor DarkYellow Write-Host " Manuell: git push" -ForegroundColor DarkGray } } else { Write-Skip "Push uebersprungen (-SkipPush)" } # ───────────────────────────────────────────────────────────────────────────── # 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"