diff --git a/Dockerfile b/Dockerfile index 2618499..52ff427 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.11-slim WORKDIR /app -RUN pip install fastapi jinja2 uvicorn requests python-dateutil python-multipart +RUN pip install fastapi jinja2 uvicorn requests python-dateutil COPY main.py . COPY reference_versions.json ./reference_versions.json diff --git a/gen_ref_vers.py b/gen_ref_vers.py deleted file mode 100644 index 88b59b2..0000000 --- a/gen_ref_vers.py +++ /dev/null @@ -1,44 +0,0 @@ -import requests -import re -import json - -# Logischer Name (wie im UI) → Launchpad-Quellpaketname -PACKAGES = { - "python3": "python3-defaults", - "nginx": "nginx", - "openssh-server": "openssh" -} - -# Ubuntu-Release (muss auf launchpad.net vorhanden sein) -UBUNTU_RELEASE = "noble" - -def get_version(pkg_source, release=UBUNTU_RELEASE): - try: - url = f"https://launchpad.net/ubuntu/{release}/+source/{pkg_source}" - r = requests.get(url, timeout=10) - if r.status_code != 200: - print(f"⚠️ Fehler {r.status_code} bei {url}") - return None - - # Suche nach: Current version:
VERSION
- match = re.search(r'Current version:\s*
([^<]+)
', r.text) - return match.group(1).strip() if match else None - except Exception as e: - print(f"❌ Fehler bei {pkg_source}: {e}") - return None - -def generate(): - result = {} - for display_name, source_name in PACKAGES.items(): - version = get_version(source_name) - if version: - result[display_name] = version - else: - print(f"❌ Keine Version gefunden für {display_name}") - return result - -if __name__ == "__main__": - versions = generate() - with open("reference_versions.json", "w") as f: - json.dump(versions, f, indent=2) - print("✅ Referenz gespeichert in reference_versions.json") diff --git a/main.py b/main.py index aa88466..7c0f32d 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,9 @@ -from fastapi import FastAPI, Request, Form -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi import FastAPI +from fastapi.responses import HTMLResponse from jinja2 import Template import requests import json from dateutil import parser, tz -from pathlib import Path app = FastAPI() @@ -12,6 +11,104 @@ API_URL = "http://192.168.18.4:8000/results/updatecheck/" READ_API_KEY = "read_key_1" REFERENCE_FILE = "reference_versions.json" +# ================= Templates ================== + +MAIN_TEMPLATE = Template(""" + + + + + UpdateLog Übersicht + + + + +

UpdateLog Übersicht

+ {% for server, content in data.items() %} +
+ + 🔍 Verlauf anzeigen + +
+ {% endfor %} + + +""") + +HISTORY_TEMPLATE = Template(""" + + + + + Verlauf: {{ hostname }} + + + +

Update-Verlauf für {{ hostname }}

+ ⬅ Zurück zur Übersicht + {% for entry in history %} +

{{ entry.timestamp }}

+ + + {% for pkg, ver in entry.packages.items() %} + + + + + {% endfor %} +
PaketInstalliert
{{ pkg }}{{ ver }}
+ {% endfor %} + + +""") + +# ================= Routes ================== + +@app.get("/", response_class=HTMLResponse) +def index(): + data = fetch_latest_per_host() + return MAIN_TEMPLATE.render(data=data) + +@app.get("/host/{hostname}", response_class=HTMLResponse) +def host_history(hostname: str): + history = fetch_history_for_host(hostname) + return HISTORY_TEMPLATE.render(hostname=hostname, history=history) + +# ================= Datenfunktionen ================== + def fetch_raw_lines(): try: response = requests.get(API_URL, headers={"Authorization": f"Bearer {READ_API_KEY}"}, timeout=5) @@ -25,46 +122,52 @@ def fetch_reference(): try: with open(REFERENCE_FILE) as f: return json.load(f) - except Exception: + except Exception as e: + print(f"❌ Fehler beim Laden der Referenzversionen: {e}") return {} def fetch_latest_per_host(): lines = fetch_raw_lines() reference = fetch_reference() - latest = {} + latest_by_host = {} for line in lines: parts = line.strip().split(",", 3) if len(parts) < 4: continue timestamp, hostname, _, packages = parts - if hostname not in latest or timestamp > latest[hostname]["timestamp"]: - latest[hostname] = { + if hostname not in latest_by_host or timestamp > latest_by_host[hostname]["timestamp"]: + latest_by_host[hostname] = { "timestamp": timestamp, "raw": packages } result = {} - for host, entry in latest.items(): + for hostname, entry in latest_by_host.items(): + # Zeitstempel umrechnen try: - dt = parser.isoparse(entry["timestamp"]).astimezone(tz.gettz("Europe/Berlin")) - display_time = dt.strftime("%Y-%m-%d %H:%M:%S") - except: + dt_utc = parser.isoparse(entry["timestamp"]) + dt_local = dt_utc.astimezone(tz.gettz("Europe/Berlin")) + display_time = dt_local.strftime("%Y-%m-%d %H:%M:%S") + except Exception: display_time = entry["timestamp"] - pkgs = {} - for p in entry["raw"].strip(";").split(";"): - if "=" in p: - k, v = p.split("=", 1) - pkgs[k] = { - "current": v, - "expected": reference.get(k) + package_str = entry["raw"] + result[hostname] = { + "timestamp": display_time, + "packages": {} + } + for item in package_str.strip(";").split(";"): + if "=" not in item: + continue + pkg, version = item.split("=", 1) + expected = reference.get(pkg) + if expected: + result[hostname]["packages"][pkg] = { + "current": version, + "expected": expected } - result[host] = { - "timestamp": display_time, - "packages": pkgs - } return result def fetch_history_for_host(hostname): @@ -72,143 +175,20 @@ def fetch_history_for_host(hostname): history = [] for line in lines: parts = line.strip().split(",", 3) - if len(parts) < 4 or parts[1] != hostname: + if len(parts) < 4: continue - timestamp, _, _, raw = parts - pkgs = {} - for p in raw.strip(";").split(";"): - if "=" in p: - k, v = p.split("=", 1) - pkgs[k] = v - history.append({"timestamp": timestamp, "packages": pkgs}) + timestamp, host, _, packages = parts + if host != hostname: + continue + parsed = {} + for item in packages.strip(";").split(";"): + if "=" not in item: + continue + pkg, version = item.split("=", 1) + parsed[pkg] = version + history.append({ + "timestamp": timestamp, + "packages": parsed + }) history.sort(key=lambda x: x["timestamp"], reverse=True) return history - -@app.get("/", response_class=HTMLResponse) -def index(): - data = fetch_latest_per_host() - return MAIN_TEMPLATE.render(data=data) - -@app.get("/host/{hostname}", response_class=HTMLResponse) -def host_view(hostname: str): - return HOST_TEMPLATE.render(hostname=hostname, history=fetch_history_for_host(hostname)) - -@app.get("/edit", response_class=HTMLResponse) -def edit_view(): - try: - data = Path(REFERENCE_FILE).read_text() - except: - data = "{}" - return EDIT_TEMPLATE.render(content=data) - -@app.post("/edit", response_class=RedirectResponse) -def edit_post(content: str = Form(...)): - try: - json.loads(content) - Path(REFERENCE_FILE).write_text(content) - except Exception as e: - print(f"Fehler beim Schreiben der Referenzdatei: {e}") - return RedirectResponse(url="/edit", status_code=303) - -# ========== Templates ========== - -MAIN_TEMPLATE = Template(""" - - - - - Update Übersicht - - - - -

Update Übersicht

- - ⚙️ Referenz bearbeiten - {% for host, d in data.items() %} -
- - 🔍 Verlauf - -
- {% endfor %} - - -""") - -HOST_TEMPLATE = Template(""" - - -{{ hostname }} - - -

Verlauf: {{ hostname }}

- ⬅ Zurück - {% for h in history %} -

{{ h.timestamp }}

- - - {% for p, v in h.packages.items() %} - - {% endfor %} -
PaketVersion
{{ p }}{{ v }}
- {% endfor %} - - -""") - -EDIT_TEMPLATE = Template(""" - - -Referenz bearbeiten - - -

Referenzdatei bearbeiten

-⬅ Zurück -
-
- -
- - -""") diff --git a/reference_versions.json b/reference_versions.json index 7854f28..987f8ef 100644 --- a/reference_versions.json +++ b/reference_versions.json @@ -1,5 +1,4 @@ { "python3": "3.12.3-0ubuntu2", - "nginx": "1.24.0-2ubuntu7.4", - "openssh-server": "1:9.6p1-3ubuntu13.12" -} \ No newline at end of file + "openssh-server": "1:9.6p1-3ubuntu13.11" +} diff --git a/run_container.sh b/run_container.sh index 4d69bc0..ca61608 100755 --- a/run_container.sh +++ b/run_container.sh @@ -13,5 +13,10 @@ docker build -t $IMAGE_NAME . docker run -d \ --name $CONTAINER_NAME \ -p $PORT:8080 \ - -v "$(realpath ./reference_versions.json)":/app/reference_versions.json \ + --read-only \ + --tmpfs /tmp \ + --cap-drop ALL \ + --security-opt no-new-privileges \ $IMAGE_NAME + +echo "✅ Update-UI läuft unter http://localhost:$PORT" diff --git a/send-update-log-win.ps1 b/send-update-log-win.ps1 deleted file mode 100644 index 7ef3731..0000000 --- a/send-update-log-win.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -# Konfiguration -$apiUrl = "http://192.168.18.4:8000/api/updatecheck/" -$apiToken = "9d207bf0-10f5-4d8f-a479-22ff5aeff8d1" - -# Hostname und Zeit -$hostname = $env:COMPUTERNAME -$timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:sszzz") - -# OS- und Kernel-Info -$os = (Get-CimInstance Win32_OperatingSystem).Caption -$kernel = (Get-CimInstance Win32_OperatingSystem).Version - -# Installierte Pakete mit DisplayName -$raw = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | - Where-Object { $_.DisplayName -and ($_.DisplayName -match 'Python|OpenSSH|nginx') } | - ForEach-Object { "$($_.DisplayName)=unknown" } - -# Fallback -if (-not $raw) { - $raw = @("none=none") -} - -# Paketzeichenfolge zusammenbauen -$packageString = "os=$os;kernel=$kernel;" + ($raw -join ";") - -# Body zusammenbauen -$body = "$hostname,$timestamp,$packageString" - -# Header -$headers = @{ - "Authorization" = "Bearer $apiToken" -} - -# Abschicken -try { - Invoke-RestMethod -Uri $apiUrl -Method Post -Headers $headers -Body $body - Write-Host "✅ Update-Log gesendet von $hostname" -} -catch { - Write-Warning "❌ Fehler beim Senden: $_" -} diff --git a/send-update.log.sh b/send-update.log.sh deleted file mode 100644 index 687bd88..0000000 --- a/send-update.log.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -API_URL="http://192.168.18.4:8000/api/updatecheck/" -API_TOKEN="9d207bf0-10f5-4d8f-a479-22ff5aeff8d1" - -HOSTNAME=$(hostname) -TIMESTAMP=$(date -Iseconds) -OS=$(lsb_release -ds 2>/dev/null || echo "Unknown") -KERNEL=$(uname -r) - -# Pakete -PACKAGES="python3|openssh-server|nginx" -PACKAGE_VERSIONS=$(dpkg-query -W -f='${binary:Package}=${Version}\n' 2>/dev/null | grep -E "$PACKAGES" | tr '\n' ';') - -# Zusammenbauen -BODY="$HOSTNAME,$TIMESTAMP,os=$OS;kernel=$KERNEL;$PACKAGE_VERSIONS" - -curl -s -X POST "$API_URL" \ - -H "Authorization: Bearer $API_TOKEN" \ - -d "$BODY"