This commit is contained in:
2026-05-16 20:33:36 +02:00
parent 96c1029d91
commit 968bf7d102
212 changed files with 954195 additions and 658 deletions

View File

@@ -0,0 +1,77 @@
# AZA Reverse Proxy + Static Files
#
# Production: DOMAIN env must point to this machine (DNS A-Record).
# Caddy handles automatic HTTPS via Let's Encrypt.
#
# Local testing: http://localhost:8080
{
email {$ACME_EMAIL:admin@aza-medwork.ch}
}
{$AZA_DOMAIN:localhost} {
encode gzip zstd
# Static web assets (landing, download, styles)
handle /web/* {
root * /app
file_server
}
# Release artifacts (version.json, installer)
handle /release/* {
root * /app
file_server
}
# Download shortcut: /download/filename -> /release/filename
handle /download/* {
root * /app/release
uri strip_prefix /download
file_server
}
# All other requests -> backend API
handle {
reverse_proxy {$BACKEND_UPSTREAM:backend:8000}
}
}
# Empfang-Subdomain: empfang.aza-medwork.ch
# Root "/" wird transparent auf /empfang/ umgeschrieben (kein sichtbarer Redirect)
{$AZA_EMPFANG_DOMAIN:empfang.aza-medwork.ch} {
encode gzip zstd
handle / {
rewrite * /empfang/
reverse_proxy {$BACKEND_UPSTREAM:backend:8000}
}
handle {
reverse_proxy {$BACKEND_UPSTREAM:backend:8000}
}
}
:8080 {
encode gzip zstd
handle /web/* {
root * /app
file_server
}
handle /release/* {
root * /app
file_server
}
handle /download/* {
root * /app/release
uri strip_prefix /download
file_server
}
handle {
reverse_proxy {$BACKEND_UPSTREAM_LOCAL:localhost:8000}
}
}
}

View File

@@ -0,0 +1,10 @@
Vor dem AZA Update-System Phase 1 Patch.
Wiederherstellen (PowerShell, Arbeitsverzeichnis: AzA march 2026):
$folder = 'backup_aza_update_phase1_20260515_232326'
Copy-Item -LiteralPath "$folder\desktop_update_check.py" -Destination .\desktop_update_check.py -Force
Copy-Item -LiteralPath "$folder\basis14.py" -Destination .\basis14.py -Force
Copy-Item -LiteralPath "$folder\build_release_manifest.ps1" -Destination .\build_release_manifest.ps1 -Force
Copy-Item -LiteralPath "$folder\version.json" -Destination .\release\version.json -Force
Copy-Item -LiteralPath "$folder\Caddyfile" -Destination .\deploy\Caddyfile -Force

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
$projectRoot = $PSScriptRoot
$azaVersionPy = Join-Path $projectRoot "aza_version.py"
$manifestPath = Join-Path $projectRoot "release\version.json"
$releaseBaseUrl = $env:AZA_RELEASE_BASE_URL
if (-not (Test-Path $azaVersionPy)) {
Write-Error "aza_version.py nicht gefunden: $azaVersionPy"
exit 1
}
Write-Host "Erzeuge release/version.json aus aza_version.py..."
$versionContent = Get-Content $azaVersionPy -Raw
if ($versionContent -match 'APP_VERSION\s*=\s*"([^"]+)"') {
$appVersion = $matches[1].Trim()
} else {
Write-Error "APP_VERSION nicht in aza_version.py gefunden."
exit 1
}
if ($versionContent -match 'APP_CHANNEL\s*=\s*"([^"]+)"') {
$appChannel = $matches[1].Trim()
} else {
Write-Error "APP_CHANNEL nicht in aza_version.py gefunden."
exit 1
}
$manifest = @{}
if (Test-Path $manifestPath) {
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
}
$releaseDate = if ($manifest.release_date) { $manifest.release_date } else { (Get-Date).ToString("yyyy-MM-dd") }
$minVersion = if ($manifest.minimum_supported_version) { $manifest.minimum_supported_version } else { $appVersion }
$downloadUrl = if ($releaseBaseUrl) {
"$releaseBaseUrl/aza_desktop_setup.exe"
} elseif ($manifest.download_url) {
$manifest.download_url
} else {
"https://api.aza-medwork.ch/download/aza_desktop_setup.exe"
}
$installerType = if ($manifest.installer_type) { $manifest.installer_type } else { "inno-setup" }
$releaseNotes = if ($manifest.release_notes) { $manifest.release_notes } else { @("Desktop-Build aktualisiert") }
$newManifest = [ordered]@{
version = $appVersion
channel = $appChannel
release_date = $releaseDate
minimum_supported_version = $minVersion
download_url = $downloadUrl
installer_type = $installerType
release_notes = $releaseNotes
}
$newManifest | ConvertTo-Json -Depth 5 | Set-Content $manifestPath -Encoding UTF8
if (-not $appVersion) {
Write-Error "release/version.json konnte nicht erzeugt werden."
exit 1
}
Write-Host "release/version.json aktualisiert."
Write-Host "Version: $appVersion"
if ($releaseBaseUrl) {
Write-Host "Download-URL Base: $releaseBaseUrl"
}

View File

@@ -0,0 +1,126 @@
import os
import sys
import tempfile
import webbrowser
import requests
from aza_version import APP_CHANNEL, APP_VERSION
UPDATE_MANIFEST_URL = "https://api.aza-medwork.ch/download/version.json"
_MIN_INSTALLER_BYTES = 1_000_000
def _is_newer_version(latest: str, current: str) -> bool:
def parse(v: str):
parts = []
for p in str(v).strip().split("."):
try:
parts.append(int(p))
except Exception:
parts.append(0)
while len(parts) < 3:
parts.append(0)
return tuple(parts[:3])
return parse(latest) > parse(current)
def check_for_updates():
try:
r = requests.get(UPDATE_MANIFEST_URL, timeout=4)
if r.status_code != 200:
return None
data = r.json()
latest = str(data.get("version") or "").strip()
channel = str(data.get("channel") or "stable").strip()
download_url = str(data.get("download_url") or "").strip()
if channel != APP_CHANNEL:
return None
if latest and _is_newer_version(latest, APP_VERSION):
return {
"update_available": True,
"latest_version": latest,
"download_url": download_url,
}
return None
except Exception:
return None
def _download_installer(download_url: str) -> str | None:
"""Downloads installer to TEMP. Returns path on success, None on failure."""
try:
target = os.path.join(tempfile.gettempdir(), "aza_desktop_setup_latest.exe")
with requests.get(download_url, stream=True, timeout=60) as r:
if r.status_code != 200:
return None
with open(target, "wb") as f:
for chunk in r.iter_content(chunk_size=512 * 1024):
if chunk:
f.write(chunk)
if os.path.isfile(target) and os.path.getsize(target) >= _MIN_INSTALLER_BYTES:
return target
return None
except Exception:
return None
def prompt_update_if_available():
"""Prueft auf Update, zeigt Dialog, laedt Installer herunter und startet ihn."""
info = check_for_updates()
if not info or not info.get("update_available"):
return
download_url = info.get("download_url", "")
latest_version = info.get("latest_version", "")
try:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
root.withdraw()
answer = messagebox.askyesno(
"Update verfuegbar",
f"AZA {latest_version} ist verfuegbar.\n\n"
f"Ihre Version: {APP_VERSION}\n\n"
"Moechten Sie das Update jetzt installieren?",
parent=root,
)
if not answer:
root.destroy()
return
root.destroy()
installer_path = _download_installer(download_url)
if installer_path:
os.startfile(installer_path)
sys.exit(0)
_root2 = tk.Tk()
_root2.withdraw()
retry = messagebox.askyesno(
"Download fehlgeschlagen",
"Das Update konnte nicht heruntergeladen werden.\n\n"
"Moechten Sie die Download-Seite im Browser oeffnen?",
parent=_root2,
)
if retry and download_url:
webbrowser.open(download_url)
_root2.destroy()
except Exception:
pass
if __name__ == "__main__":
result = check_for_updates()
print(result)

View File

@@ -0,0 +1,18 @@
{
"version": "1.2.0",
"channel": "stable",
"release_date": "2026-03-14",
"minimum_supported_version": "1.0.0",
"download_url": "https://api.aza-medwork.ch/download/aza_desktop_setup.exe",
"installer_type": "inno-setup",
"release_notes": [
"Erste stabile Verkaufsversion",
"6 Module: KI-Assistent, Krankengeschichte, Audio-Notizen, Uebersetzer, Aerzte-Netzwerk, Praxis-Intern",
"Automatischer lokaler Backend-Start",
"Integrierter Systemstatus mit 12 Pruefpunkten",
"Professioneller Launcher mit Modulauswahl",
"Projekt-Notizen mit Bild-Einfuegen und Diktat",
"OpenAI-Key Setup-Helfer",
"Windows-Firewall wird automatisch konfiguriert"
]
}