324 lines
16 KiB
PowerShell
324 lines
16 KiB
PowerShell
<#
|
||
.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 im Format x.y.z (z.B. "1.7.14").
|
||
.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 }
|
||
|
||
function Get-MajorMinor {
|
||
param([string]$VersionName)
|
||
$m = [regex]::Match($VersionName, '^(\d+)\.(\d+)(?:\.(\d+))?$')
|
||
if (-not $m.Success) { return $null }
|
||
return @($m.Groups[1].Value, $m.Groups[2].Value)
|
||
}
|
||
|
||
function Get-ThreePartVersion {
|
||
param([string]$VersionName)
|
||
$m = [regex]::Match($VersionName, '^(\d+)\.(\d+)\.(\d+)$')
|
||
if (-not $m.Success) { return $null }
|
||
return @([int]$m.Groups[1].Value, [int]$m.Groups[2].Value, [int]$m.Groups[3].Value)
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# 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 = <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 }
|
||
|
||
if ($VersionName) {
|
||
$parts = Get-ThreePartVersion $VersionName
|
||
if (-not $parts) {
|
||
Write-Fail "-VersionName muss strikt im Format x.y.z angegeben werden (z.B. 1.7.14)."
|
||
exit 1
|
||
}
|
||
if ($parts[2] -ne $newCode) {
|
||
Write-Fail "Patch-Segment der versionName ($($parts[2])) muss dem versionCode ($newCode) entsprechen."
|
||
exit 1
|
||
}
|
||
$newName = $VersionName
|
||
} else {
|
||
$majorMinor = Get-MajorMinor $currentName
|
||
if (-not $majorMinor) {
|
||
Write-Fail "Aktuelle versionName '$currentName' hat kein gueltiges Schema. Erlaubt: x.y oder x.y.z"
|
||
exit 1
|
||
}
|
||
$newName = "$($majorMinor[0]).$($majorMinor[1]).$newCode"
|
||
}
|
||
|
||
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 ===" -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"
|
||
$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 <TOKEN>'; 'Content-Type'='application/json' } ``" -ForegroundColor DarkGray
|
||
Write-Host " -Body '$apiBody'" -ForegroundColor DarkGray
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($apiSuccess) {
|
||
Write-OK "Version gesetzt: $newName"
|
||
} 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"
|
||
} catch {
|
||
Write-Host "[??] git commit fehlgeschlagen: $_" -ForegroundColor DarkYellow
|
||
Write-Host " Manuell: git add $BuildGradle ; git commit -m 'chore: release v$newName'" -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"
|
||
Write-Host " Code : $newCode"
|
||
Write-Host " Homepage: $ServerUrl/"
|
||
Write-Host " API : $ServerUrl/api/version"
|