2026-05-28 18:58:38 +02:00
|
|
|
# publish_update.ps1 - Upload Installer + Manifest nach Hetzner
|
|
|
|
|
# Laedt in release/ UND release/downloads/ hoch (beide Pfade).
|
2026-06-10 22:55:03 +02:00
|
|
|
# Aktualisiert zusaetzlich das Stable-Manifest fuer den Auto-Updater.
|
|
|
|
|
#
|
|
|
|
|
# ROOT CAUSE v1.3.0 (2026-05-31):
|
|
|
|
|
# Der oeffentliche Installer war korrekt, aber das Stable-Manifest unter
|
|
|
|
|
# /root/aza-app/release/downloads/updates/manifest.json zeigte noch die
|
|
|
|
|
# alte Version, weil dieses Skript das Manifest dort nicht mitaktualisiert
|
|
|
|
|
# hatte. Der Auto-Updater las das Stable-Manifest und bot kein Update an.
|
|
|
|
|
# Fix: [5/7] aktualisiert das Stable-Manifest zwingend bei jedem Release.
|
2026-04-16 13:32:32 +02:00
|
|
|
|
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
$projectRoot = $PSScriptRoot
|
|
|
|
|
$remoteHost = "root@178.104.51.177"
|
|
|
|
|
$remotePath = "/root/aza-app/release"
|
2026-05-28 18:58:38 +02:00
|
|
|
$remoteDownloadsPath = "/root/aza-app/release/downloads"
|
2026-06-10 22:55:03 +02:00
|
|
|
$remoteStableManifest = "/root/aza-app/release/downloads/updates/manifest.json"
|
|
|
|
|
$publicInstallerUrl = "https://api.aza-medwork.ch/downloads/aza_desktop_setup.exe"
|
|
|
|
|
$publicStableManifestUrl = "https://api.aza-medwork.ch/downloads/updates/manifest.json"
|
|
|
|
|
|
2026-04-16 13:32:32 +02:00
|
|
|
$installerPath = Join-Path $projectRoot "dist\installer\aza_desktop_setup.exe"
|
|
|
|
|
$manifestPath = Join-Path $projectRoot "release\version.json"
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host "==================================================="
|
|
|
|
|
Write-Host " AZA Release Upload + Verifikation"
|
|
|
|
|
Write-Host "==================================================="
|
|
|
|
|
Write-Host ""
|
|
|
|
|
|
|
|
|
|
# --- [1/7] Lokale Dateien + Version lesen ---
|
|
|
|
|
|
|
|
|
|
Write-Host "[1/7] Lokale Dateien pruefen + Version lesen..."
|
2026-04-16 13:32:32 +02:00
|
|
|
|
|
|
|
|
if (-not (Test-Path $installerPath)) {
|
|
|
|
|
Write-Error "ABBRUCH: $installerPath nicht gefunden."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if (-not (Test-Path $manifestPath)) {
|
|
|
|
|
Write-Error "ABBRUCH: $manifestPath nicht gefunden."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
2026-06-10 22:55:03 +02:00
|
|
|
if (-not (Test-Path (Join-Path $projectRoot "aza_version.py"))) {
|
|
|
|
|
Write-Error "ABBRUCH: aza_version.py nicht gefunden."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Version + Build aus aza_version.py und _build_info.py lesen
|
|
|
|
|
$versionContent = Get-Content (Join-Path $projectRoot "aza_version.py") -Raw
|
|
|
|
|
if ($versionContent -match 'APP_VERSION\s*=\s*"([^"]+)"') {
|
|
|
|
|
$appVersion = $matches[1].Trim()
|
|
|
|
|
} else {
|
|
|
|
|
Write-Error "ABBRUCH: APP_VERSION nicht in aza_version.py."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if ($versionContent -match 'APP_CHANNEL\s*=\s*"([^"]+)"') {
|
|
|
|
|
$appChannel = $matches[1].Trim()
|
|
|
|
|
} else {
|
|
|
|
|
$appChannel = "stable"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$buildStamp = ""
|
|
|
|
|
$buildInfoPath = Join-Path $projectRoot "_build_info.py"
|
|
|
|
|
if (Test-Path $buildInfoPath) {
|
|
|
|
|
$buildContent = Get-Content $buildInfoPath -Raw
|
|
|
|
|
if ($buildContent -match 'BUILD_TIMESTAMP\s*=\s*"([^"]+)"') {
|
|
|
|
|
$buildStamp = $matches[1].Trim()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (-not $buildStamp) {
|
|
|
|
|
$buildStamp = (Get-Item $installerPath).LastWriteTime.ToString("yyyyMMdd_HHmmss")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Lokaler Installer-Hash + Groesse (einmalig berechnen)
|
|
|
|
|
$localHash = (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLower()
|
|
|
|
|
$localSizeBytes = (Get-Item $installerPath).Length
|
|
|
|
|
$localSizeMB = [math]::Round($localSizeBytes / 1MB, 2)
|
|
|
|
|
|
|
|
|
|
Write-Host " Installer: $installerPath ($localSizeMB MB)"
|
|
|
|
|
Write-Host " Version: $appVersion"
|
|
|
|
|
Write-Host " Build: $buildStamp"
|
|
|
|
|
Write-Host " Channel: $appChannel"
|
|
|
|
|
Write-Host " SHA256: $localHash"
|
|
|
|
|
|
|
|
|
|
# --- [2/7] Installer hochladen ---
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "[2/7] Installer hochladen (release/ und release/downloads/)..."
|
2026-04-16 13:32:32 +02:00
|
|
|
|
|
|
|
|
scp "$installerPath" "${remoteHost}:${remotePath}/aza_desktop_setup.exe"
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Error "ABBRUCH: Installer-Upload nach release/ fehlgeschlagen."
|
2026-04-16 13:32:32 +02:00
|
|
|
exit 1
|
|
|
|
|
}
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host " release/ hochgeladen"
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
|
|
|
ssh $remoteHost "cp ${remotePath}/aza_desktop_setup.exe ${remoteDownloadsPath}/aza_desktop_setup.exe"
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Error "ABBRUCH: Kopieren nach release/downloads/ fehlgeschlagen."
|
|
|
|
|
exit 1
|
2026-05-28 18:58:38 +02:00
|
|
|
}
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host " release/downloads/ kopiert"
|
|
|
|
|
|
|
|
|
|
# --- [3/7] version.json hochladen ---
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "[3/7] release/version.json hochladen..."
|
2026-04-16 13:32:32 +02:00
|
|
|
|
|
|
|
|
scp "$manifestPath" "${remoteHost}:${remotePath}/version.json"
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Error "ABBRUCH: version.json-Upload fehlgeschlagen."
|
2026-04-16 13:32:32 +02:00
|
|
|
exit 1
|
|
|
|
|
}
|
2026-05-28 18:58:38 +02:00
|
|
|
Write-Host " version.json OK"
|
2026-04-16 13:32:32 +02:00
|
|
|
|
2026-06-10 22:55:03 +02:00
|
|
|
# --- [4/7] Server-Installer SHA256 pruefen ---
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
|
|
|
Write-Host ""
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host "[4/7] Server-SHA256-Abgleich (release/ vs. downloads/)..."
|
|
|
|
|
|
2026-05-28 18:58:38 +02:00
|
|
|
$hashOutput = ssh $remoteHost "sha256sum ${remotePath}/aza_desktop_setup.exe ${remoteDownloadsPath}/aza_desktop_setup.exe 2>/dev/null"
|
|
|
|
|
Write-Host $hashOutput
|
|
|
|
|
|
2026-06-10 22:55:03 +02:00
|
|
|
$hashes = ($hashOutput -split "`n") | Where-Object { $_ -match "^[0-9a-f]{64}" } | ForEach-Object { ($_ -split "\s+")[0] }
|
|
|
|
|
|
|
|
|
|
if ($hashes.Count -lt 2) {
|
|
|
|
|
Write-Error "ABBRUCH: SHA256 konnte nicht fuer beide Server-Pfade gelesen werden."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if ($hashes[0] -ne $hashes[1]) {
|
|
|
|
|
Write-Error "ABBRUCH: SHA256 release/ != release/downloads/ -- Installer-Pfade sind inkonsistent!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
if ($hashes[0].ToLower() -ne $localHash) {
|
|
|
|
|
Write-Error "ABBRUCH: Server-SHA256 ($($hashes[0])) != lokaler SHA256 ($localHash) -- Upload korrumpiert!"
|
|
|
|
|
exit 1
|
2026-05-28 18:58:38 +02:00
|
|
|
}
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host " SHA256 identisch: release/ = downloads/ = lokal OK"
|
|
|
|
|
|
|
|
|
|
# --- [5/7] Stable-Manifest aktualisieren ---
|
|
|
|
|
# PFLICHTSCHRITT -- verhindert Wiederholung des v1.3.0-Fehlers.
|
|
|
|
|
# Das Stable-Manifest liegt in einem anderen Pfad als version.json:
|
|
|
|
|
# /root/aza-app/release/downloads/updates/manifest.json
|
|
|
|
|
# Es wird bei jedem Release zwingend auf die neue Version gesetzt.
|
2026-05-28 18:58:38 +02:00
|
|
|
|
|
|
|
|
Write-Host ""
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host "[5/7] Stable-Manifest aktualisieren ($remoteStableManifest)..."
|
2026-04-16 13:32:32 +02:00
|
|
|
|
2026-06-10 22:55:03 +02:00
|
|
|
$stableManifestContent = @"
|
|
|
|
|
{
|
|
|
|
|
"product": "AZA Desktop",
|
|
|
|
|
"channel": "$appChannel",
|
|
|
|
|
"latest_version": "$appVersion",
|
|
|
|
|
"latest_build": "$buildStamp",
|
|
|
|
|
"min_supported_version": "1.2.0",
|
|
|
|
|
"mandatory": false,
|
|
|
|
|
"release_date": "$(Get-Date -Format 'yyyy-MM-dd')",
|
|
|
|
|
"notes_de": [
|
|
|
|
|
"AZA $appVersion"
|
|
|
|
|
],
|
|
|
|
|
"files": [
|
|
|
|
|
{
|
|
|
|
|
"name": "aza_desktop_setup.exe",
|
|
|
|
|
"url": "$publicInstallerUrl",
|
|
|
|
|
"sha256": "$localHash",
|
|
|
|
|
"size_bytes": $localSizeBytes
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"@
|
|
|
|
|
|
|
|
|
|
# Temporaere Datei lokal schreiben, dann hochladen
|
|
|
|
|
$tmpManifest = Join-Path $env:TEMP "aza_stable_manifest_tmp.json"
|
|
|
|
|
# UTF8 ohne BOM (Out-File -Encoding utf8 erzeugt auf Windows einen BOM)
|
|
|
|
|
[System.IO.File]::WriteAllText($tmpManifest, $stableManifestContent, [System.Text.UTF8Encoding]::new($false))
|
|
|
|
|
|
|
|
|
|
scp "$tmpManifest" "${remoteHost}:${remoteStableManifest}"
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
Remove-Item $tmpManifest -ErrorAction SilentlyContinue
|
|
|
|
|
Write-Error "ABBRUCH: Stable-Manifest-Upload fehlgeschlagen."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
Remove-Item $tmpManifest -ErrorAction SilentlyContinue
|
|
|
|
|
Write-Host " Stable-Manifest hochgeladen"
|
|
|
|
|
|
|
|
|
|
# Gegenpruefen: Manifest-SHA256 auf Server muss mit lokalem Hash uebereinstimmen
|
|
|
|
|
$manifestHashCheck = ssh $remoteHost "python3 -c `"import json; d=json.load(open('$remoteStableManifest')); print(d['files'][0]['sha256'])`""
|
|
|
|
|
$manifestHashCheck = ($manifestHashCheck -split "`n")[0].Trim().ToLower()
|
|
|
|
|
if ($manifestHashCheck -ne $localHash) {
|
|
|
|
|
Write-Error "ABBRUCH: Manifest SHA256 ($manifestHashCheck) != lokaler Installer SHA256 ($localHash)!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
Write-Host " Manifest-SHA256 stimmt mit Installer ueberein"
|
|
|
|
|
|
|
|
|
|
$manifestVersionCheck = ssh $remoteHost "python3 -c `"import json; d=json.load(open('$remoteStableManifest')); print(d['latest_version'])`""
|
|
|
|
|
$manifestVersionCheck = ($manifestVersionCheck -split "`n")[0].Trim()
|
|
|
|
|
if ($manifestVersionCheck -ne $appVersion) {
|
|
|
|
|
Write-Error "ABBRUCH: Manifest version ($manifestVersionCheck) != Release-Version ($appVersion)!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
Write-Host " Manifest-Version korrekt: $manifestVersionCheck"
|
|
|
|
|
|
|
|
|
|
# --- [6/7] Public-URL-Verifikation ---
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "[6/7] Public-URL-Verifikation..."
|
|
|
|
|
|
|
|
|
|
# Public Installer
|
|
|
|
|
$installerStatus = ssh $remoteHost "curl -sI $publicInstallerUrl | head -1"
|
|
|
|
|
$installerStatus = ($installerStatus -split "`n")[0].Trim()
|
|
|
|
|
Write-Host " Public Installer: $installerStatus"
|
|
|
|
|
if ($installerStatus -notmatch "200") {
|
|
|
|
|
Write-Error "ABBRUCH: Public Installer ist nicht erreichbar (Status: $installerStatus)!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Public Stable-Manifest
|
|
|
|
|
$stableManifestStatus = ssh $remoteHost "curl -sI $publicStableManifestUrl | head -1"
|
|
|
|
|
$stableManifestStatus = ($stableManifestStatus -split "`n")[0].Trim()
|
|
|
|
|
Write-Host " Public Manifest: $stableManifestStatus"
|
|
|
|
|
if ($stableManifestStatus -notmatch "200") {
|
|
|
|
|
Write-Error "ABBRUCH: Public Stable-Manifest ist nicht erreichbar (Status: $stableManifestStatus)!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Public Manifest zeigt neue Version
|
|
|
|
|
$publicVersion = ssh $remoteHost "curl -s $publicStableManifestUrl | python3 -c `"import json,sys; d=json.load(sys.stdin); print(d.get('latest_version',''))`"" 2>/dev/null
|
|
|
|
|
$publicVersion = ($publicVersion -split "`n")[0].Trim()
|
|
|
|
|
Write-Host " Public Manifest latest_version: $publicVersion"
|
|
|
|
|
if ($publicVersion -ne $appVersion) {
|
|
|
|
|
Write-Error "ABBRUCH: Public Manifest zeigt Version $publicVersion statt $appVersion!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- [7/7] Abschlussbericht ---
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "==================================================="
|
|
|
|
|
Write-Host " RELEASE-ABSCHLUSSBERICHT"
|
|
|
|
|
Write-Host "==================================================="
|
|
|
|
|
Write-Host " Version: $appVersion"
|
|
|
|
|
Write-Host " Build: $buildStamp"
|
|
|
|
|
Write-Host " Channel: $appChannel"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host " Lokaler Installer SHA256: $localHash"
|
|
|
|
|
$serverHash = $hashes[0].ToLower()
|
|
|
|
|
Write-Host " Server release/ SHA256: $serverHash"
|
|
|
|
|
$serverDlHash = $hashes[1].ToLower()
|
|
|
|
|
Write-Host " Server downloads/ SHA256: $serverDlHash"
|
|
|
|
|
Write-Host " Manifest SHA256: $manifestHashCheck"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
if ($localHash -eq $serverHash -and $localHash -eq $serverDlHash -and $localHash -eq $manifestHashCheck) {
|
|
|
|
|
Write-Host " SHA256 ALLE IDENTISCH" -ForegroundColor Green
|
|
|
|
|
} else {
|
|
|
|
|
Write-Error "ABBRUCH: SHA256-Divergenz im Abschlussbericht!"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host " Public Installer: $installerStatus"
|
|
|
|
|
Write-Host " Public Manifest: $stableManifestStatus"
|
|
|
|
|
Write-Host " Public Manifest Version: $publicVersion"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host " Auto-Updater sieht neue Version: JA" -ForegroundColor Green
|
2026-04-16 13:32:32 +02:00
|
|
|
Write-Host ""
|
2026-06-10 22:55:03 +02:00
|
|
|
Write-Host "==================================================="
|
|
|
|
|
Write-Host " UPLOAD FERTIG"
|
|
|
|
|
Write-Host "==================================================="
|
2026-04-16 13:32:32 +02:00
|
|
|
Write-Host ""
|