diff --git a/Dockerfile b/Dockerfile
index 52ff427..2618499 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
+RUN pip install fastapi jinja2 uvicorn requests python-dateutil python-multipart
COPY main.py .
COPY reference_versions.json ./reference_versions.json
diff --git a/gen_ref_vers.py b/gen_ref_vers.py
new file mode 100644
index 0000000..88b59b2
--- /dev/null
+++ b/gen_ref_vers.py
@@ -0,0 +1,44 @@
+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 7c0f32d..aa88466 100644
--- a/main.py
+++ b/main.py
@@ -1,9 +1,10 @@
-from fastapi import FastAPI
-from fastapi.responses import HTMLResponse
+from fastapi import FastAPI, Request, Form
+from fastapi.responses import HTMLResponse, RedirectResponse
from jinja2 import Template
import requests
import json
from dateutil import parser, tz
+from pathlib import Path
app = FastAPI()
@@ -11,104 +12,6 @@ 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
-
-
- Paket | Installiert | Referenz | Status |
- {% for pkg, info in content.packages.items() %}
-
- {{ pkg }} |
- {{ info.current }} |
- {{ info.expected }} |
- {{ '✅' if info.current == info.expected else '❌' }} |
-
- {% endfor %}
-
-
-
- {% endfor %}
-
-
-""")
-
-HISTORY_TEMPLATE = Template("""
-
-
-
-
- Verlauf: {{ hostname }}
-
-
-
- Update-Verlauf für {{ hostname }}
- ⬅ Zurück zur Übersicht
- {% for entry in history %}
- {{ entry.timestamp }}
-
- Paket | Installiert |
- {% for pkg, ver in entry.packages.items() %}
-
- {{ pkg }} |
- {{ ver }} |
-
- {% endfor %}
-
- {% 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)
@@ -122,52 +25,46 @@ def fetch_reference():
try:
with open(REFERENCE_FILE) as f:
return json.load(f)
- except Exception as e:
- print(f"❌ Fehler beim Laden der Referenzversionen: {e}")
+ except Exception:
return {}
def fetch_latest_per_host():
lines = fetch_raw_lines()
reference = fetch_reference()
- latest_by_host = {}
+ latest = {}
for line in lines:
parts = line.strip().split(",", 3)
if len(parts) < 4:
continue
timestamp, hostname, _, packages = parts
- if hostname not in latest_by_host or timestamp > latest_by_host[hostname]["timestamp"]:
- latest_by_host[hostname] = {
+ if hostname not in latest or timestamp > latest[hostname]["timestamp"]:
+ latest[hostname] = {
"timestamp": timestamp,
"raw": packages
}
result = {}
- for hostname, entry in latest_by_host.items():
- # Zeitstempel umrechnen
+ for host, entry in latest.items():
try:
- 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:
+ dt = parser.isoparse(entry["timestamp"]).astimezone(tz.gettz("Europe/Berlin"))
+ display_time = dt.strftime("%Y-%m-%d %H:%M:%S")
+ except:
display_time = entry["timestamp"]
- 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
+ pkgs = {}
+ for p in entry["raw"].strip(";").split(";"):
+ if "=" in p:
+ k, v = p.split("=", 1)
+ pkgs[k] = {
+ "current": v,
+ "expected": reference.get(k)
}
+ result[host] = {
+ "timestamp": display_time,
+ "packages": pkgs
+ }
return result
def fetch_history_for_host(hostname):
@@ -175,20 +72,143 @@ def fetch_history_for_host(hostname):
history = []
for line in lines:
parts = line.strip().split(",", 3)
- if len(parts) < 4:
+ if len(parts) < 4 or parts[1] != hostname:
continue
- 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
- })
+ 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})
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
+
+
+ Paket | Installiert | Referenz | Status |
+ {% for p, info in d.packages.items() %}
+
+ {{ p }} |
+ {{ info.current }} |
+ {{ info.expected or '–' }} |
+
+ {% if not info.expected %}❓ fehlt
+ {% elif info.current != info.expected %}❌
+ {% else %}✅
+ {% endif %}
+ |
+
+ {% endfor %}
+
+
+
+ {% endfor %}
+
+
+""")
+
+HOST_TEMPLATE = Template("""
+
+
+{{ hostname }}
+
+
+ Verlauf: {{ hostname }}
+ ⬅ Zurück
+ {% for h in history %}
+ {{ h.timestamp }}
+
+ Paket | Version |
+ {% for p, v in h.packages.items() %}
+ {{ p }} | {{ v }} |
+ {% endfor %}
+
+ {% endfor %}
+
+
+""")
+
+EDIT_TEMPLATE = Template("""
+
+
+Referenz bearbeiten
+
+
+Referenzdatei bearbeiten
+⬅ Zurück
+
+
+
+""")
diff --git a/reference_versions.json b/reference_versions.json
index 987f8ef..7854f28 100644
--- a/reference_versions.json
+++ b/reference_versions.json
@@ -1,4 +1,5 @@
{
"python3": "3.12.3-0ubuntu2",
- "openssh-server": "1:9.6p1-3ubuntu13.11"
-}
+ "nginx": "1.24.0-2ubuntu7.4",
+ "openssh-server": "1:9.6p1-3ubuntu13.12"
+}
\ No newline at end of file
diff --git a/run_container.sh b/run_container.sh
index ca61608..4d69bc0 100755
--- a/run_container.sh
+++ b/run_container.sh
@@ -13,10 +13,5 @@ docker build -t $IMAGE_NAME .
docker run -d \
--name $CONTAINER_NAME \
-p $PORT:8080 \
- --read-only \
- --tmpfs /tmp \
- --cap-drop ALL \
- --security-opt no-new-privileges \
+ -v "$(realpath ./reference_versions.json)":/app/reference_versions.json \
$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
new file mode 100644
index 0000000..7ef3731
--- /dev/null
+++ b/send-update-log-win.ps1
@@ -0,0 +1,41 @@
+# 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
new file mode 100644
index 0000000..687bd88
--- /dev/null
+++ b/send-update.log.sh
@@ -0,0 +1,20 @@
+#!/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"