fix(publish): robustes Error-Handling in publish-apk.ps1
- Pre-Checks vor jeder Dateiänderung (Token, Regex-Validierung, SSH-Agent) - Rollback: build.gradle.kts wird bei Build- oder Upload-Fehler zurückgesetzt - SCP/SSH mit ConnectTimeout (kein ewiges Hängen bei VPS-Ausfall) - API-Call mit Retry (2 Versuche, 3s Pause) + Recovery-Hinweis - Verify + Git: non-fatal (nur Warnung, kein Abbruch) - versionCode-Validierung: neuer Code muss > aktueller sein - Set-StrictMode -Version Latest
This commit is contained in:
parent
f3eab7b10d
commit
23e0a47967
1 changed files with 223 additions and 93 deletions
312
.github/skills/publish/publish-apk.ps1
vendored
312
.github/skills/publish/publish-apk.ps1
vendored
|
|
@ -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"
|
||||
$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
|
||||
# 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 = <int>'"
|
||||
exit 1
|
||||
}
|
||||
if (-not $nameMatch.Success) {
|
||||
Write-Fail "versionName nicht in $BuildGradle gefunden. Regex-Muster: versionName = `"<string>`""
|
||||
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)"
|
||||
[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 <TOKEN>'; '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-Error "Version-Update fehlgeschlagen: $_"
|
||||
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
|
||||
} else {
|
||||
Write-Host "`n[4/4] Verifizieren..." -ForegroundColor Yellow
|
||||
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)"
|
||||
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 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)"
|
||||
if (-not $SkipPush) {
|
||||
git push
|
||||
Write-Host "[OK] Gepusht" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[--] Push uebersprungen (-SkipPush)" -ForegroundColor DarkGray
|
||||
} catch {
|
||||
Write-Host "[??] git commit fehlgeschlagen: $_" -ForegroundColor DarkYellow
|
||||
Write-Host " Manuell: git add $BuildGradle ; git commit -m 'chore: release v$newName ($newCode)'" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
Write-Host "`n=== Publish abgeschlossen ===" -ForegroundColor Cyan
|
||||
Write-Host "Homepage: $ServerUrl/" -ForegroundColor DarkGray
|
||||
Write-Host "API: $ServerUrl/api/version" -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue