feat(setup): Cluster-Node-Modus im Setup-Wizard + Install-Script-Port-Fix

Setup-Wizard zeigt jetzt beim ersten Aufruf eine Modusauswahl:
- "Neuinstallation" → bisheriger Flow (Admin-Account + FQDN + ACME)
- "Cluster-Knoten beitreten" → nur FQDN + ACME, kein Admin-Account;
  nach dem Submit werden die cluster-join-Befehle direkt angezeigt

Backend: NodeRequest + Store.CompleteAsNode() + POST /setup/complete-node
State: is_cluster_node Flag; login via PG-Replikation vom Primary

Install-Script: Setup-URL zeigt jetzt korrekt :3443/setup statt /setup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-29 16:07:19 +02:00
parent a0b3bccbc6
commit 7e5a7a83c0
10 changed files with 782 additions and 359 deletions

View File

@@ -63,52 +63,87 @@
"emptyDesc": "Zonen gruppieren Interfaces (lan, wan, dmz). Vordefinierte Zonen sind bereits da; eigene anlegen wenn du z. B. eine separate dmz-Zone oder eine wg-Zone fürs VPN brauchst."
},
"ao": {
"name": "Name", "kind": "Typ", "value": "Wert", "description": "Beschreibung",
"add": "Adress-Objekt hinzufügen", "edit": "Adress-Objekt bearbeiten",
"name": "Name",
"kind": "Typ",
"value": "Wert",
"description": "Beschreibung",
"add": "Adress-Objekt hinzufügen",
"edit": "Adress-Objekt bearbeiten",
"deleteConfirm": "Adress-Objekt {{name}} wirklich löschen?",
"emptyTitle": "Noch keine Adress-Objekte.",
"emptyDesc": "Wiederverwendbare benannte IPs/Netze/Ranges für Firewall- und NAT-Regeln (z. B. office-net = 10.0.0.0/24, mailbox-1 = 10.0.1.42)."
},
"ag": {
"name": "Name", "members": "Mitglieder", "description": "Beschreibung",
"add": "Adress-Gruppe hinzufügen", "edit": "Adress-Gruppe bearbeiten",
"name": "Name",
"members": "Mitglieder",
"description": "Beschreibung",
"add": "Adress-Gruppe hinzufügen",
"edit": "Adress-Gruppe bearbeiten",
"selectMembers": "Adress-Objekte wählen",
"deleteConfirm": "Adress-Gruppe {{name}} wirklich löschen?",
"emptyTitle": "Noch keine Adress-Gruppen.",
"emptyDesc": "Bündele mehrere Adress-Objekte zu einer Gruppe (z. B. office-locations = [hq-net, branch-1-net, branch-2-net]) — Regeln müssen dann nur eine Gruppe referenzieren."
},
"svc": {
"name": "Name", "proto": "Protokoll", "ports": "Ports",
"portStart": "Port (Start)", "portEnd": "Port (Ende)",
"description": "Beschreibung", "builtinHint": "Vordefiniert — nicht editierbar",
"add": "Service hinzufügen", "edit": "Service bearbeiten",
"name": "Name",
"proto": "Protokoll",
"ports": "Ports",
"portStart": "Port (Start)",
"portEnd": "Port (Ende)",
"description": "Beschreibung",
"builtinHint": "Vordefiniert — nicht editierbar",
"add": "Service hinzufügen",
"edit": "Service bearbeiten",
"deleteConfirm": "Service {{name}} wirklich löschen?",
"emptyTitle": "Noch keine eigenen Services.",
"emptyDesc": "Vordefinierte (HTTP, HTTPS, SSH, …) sind bereits da. Eigene Services für app-spezifische Ports (z. B. mailcow-imaps tcp/993) erleichtern Regeln deutlich."
},
"sg": {
"name": "Name", "members": "Mitglieder", "description": "Beschreibung",
"add": "Service-Gruppe hinzufügen", "edit": "Service-Gruppe bearbeiten",
"name": "Name",
"members": "Mitglieder",
"description": "Beschreibung",
"add": "Service-Gruppe hinzufügen",
"edit": "Service-Gruppe bearbeiten",
"selectMembers": "Services wählen",
"deleteConfirm": "Service-Gruppe {{name}} wirklich löschen?",
"emptyTitle": "Noch keine Service-Gruppen.",
"emptyDesc": "Bündele mehrere Services zu einer Gruppe (z. B. web-stack = [HTTP, HTTPS, HTTP/3]) — eine Regel mit der Gruppe ersetzt drei Regeln pro Service."
},
"rule": {
"name": "Name", "priority": "Priority", "enabled": "Aktiv", "log": "Logging",
"action": "Aktion", "src": "Quelle", "dst": "Ziel", "service": "Service",
"srcZone": "Quell-Zone", "dstZone": "Ziel-Zone",
"srcKind": "Quell-Typ", "dstKind": "Ziel-Typ",
"object": "Adress-Objekt", "group": "Adress-Gruppe",
"serviceKind": "Service-Typ", "serviceGroup": "Service-Gruppe",
"name": "Name",
"priority": "Priority",
"enabled": "Aktiv",
"log": "Logging",
"action": "Aktion",
"src": "Quelle",
"dst": "Ziel",
"service": "Service",
"srcZone": "Quell-Zone",
"dstZone": "Ziel-Zone",
"srcKind": "Quell-Typ",
"dstKind": "Ziel-Typ",
"object": "Adress-Objekt",
"group": "Adress-Gruppe",
"serviceKind": "Service-Typ",
"serviceGroup": "Service-Gruppe",
"comment": "Kommentar",
"hits": "Hits",
"add": "Regel hinzufügen", "edit": "Regel bearbeiten",
"add": "Regel hinzufügen",
"edit": "Regel bearbeiten",
"deleteConfirm": "Diese Regel wirklich löschen?",
"namePlaceholder": "z.B. SSH von Office erlauben",
"cidr": "CIDR",
"actions": { "accept": "ACCEPT", "drop": "DROP", "reject": "REJECT" },
"kinds": { "any": "Beliebig", "object": "Objekt", "group": "Gruppe", "cidr": "CIDR" },
"actions": {
"accept": "ACCEPT",
"drop": "DROP",
"reject": "REJECT"
},
"kinds": {
"any": "Beliebig",
"object": "Objekt",
"group": "Gruppe",
"cidr": "CIDR"
},
"duplicate": "Regel duplizieren",
"duplicated": "Regel dupliziert (deaktiviert, Priorität +1).",
"cidrInvalid": "Gültiges CIDR eingeben (z.B. 10.0.0.0/24)",
@@ -139,16 +174,27 @@
"noResultsHint": "Filter zurücksetzen um alle Regeln zu sehen."
},
"nat": {
"name": "Name", "priority": "Priority", "kind": "Typ", "enabled": "Aktiv",
"match": "Match", "target": "Ziel",
"inZone": "Eingangs-Zone", "outZone": "Ausgangs-Zone", "proto": "Protokoll",
"matchSrcCidr": "Source-CIDR (Match)", "matchDstCidr": "Dest-CIDR (Match)",
"name": "Name",
"priority": "Priority",
"kind": "Typ",
"enabled": "Aktiv",
"match": "Match",
"target": "Ziel",
"inZone": "Eingangs-Zone",
"outZone": "Ausgangs-Zone",
"proto": "Protokoll",
"matchSrcCidr": "Source-CIDR (Match)",
"matchDstCidr": "Dest-CIDR (Match)",
"matchDstCidrHint": "leer = jede dest-IP (z.B. öffentliche IP der Box)",
"dportStart": "Port (Start)", "dportEnd": "Port (Ende)",
"dportStart": "Port (Start)",
"dportEnd": "Port (Ende)",
"portRangeError": "End-Port muss ≥ Start-Port sein",
"targetAddr": "Ziel-Adresse", "targetPortStart": "Ziel-Port (Start)", "targetPortEnd": "Ziel-Port (Ende)",
"targetAddr": "Ziel-Adresse",
"targetPortStart": "Ziel-Port (Start)",
"targetPortEnd": "Ziel-Port (Ende)",
"comment": "Kommentar",
"add": "NAT-Regel hinzufügen", "edit": "NAT-Regel bearbeiten",
"add": "NAT-Regel hinzufügen",
"edit": "NAT-Regel bearbeiten",
"deleteConfirm": "Diese NAT-Regel wirklich löschen?",
"namePlaceholder": "z.B. HTTP zu Web-Backend weiterleiten",
"emptyTitle": "Noch keine NAT-Regeln.",
@@ -156,7 +202,10 @@
},
"sys": {
"title": "System-Regeln (immer aktiv)",
"chain": "Chain", "match": "Match", "action": "Aktion", "note": "Hinweis",
"chain": "Chain",
"match": "Match",
"action": "Aktion",
"note": "Hinweis",
"policy": "Default-Policy",
"policyValue": "Eingang DROP — alles muss explizit erlaubt werden.",
"order": "Auswertung",
@@ -266,11 +315,116 @@
"licenseKeyHint": "Wenn vorhanden: 30-Tage-Trial wird übersprungen, Features unmittelbar freigeschaltet. Kann auch später unter License nachgereicht werden.",
"submit": "Setup abschließen",
"successTitle": "Setup abgeschlossen",
"successHint": "Du wirst zur Anmeldung weitergeleitet."
"successHint": "Du wirst zur Anmeldung weitergeleitet.",
"back": "Zurück",
"modeSelect": "Wie soll dieser Server eingerichtet werden?",
"modeStandalone": "Neuinstallation",
"modeStandaloneDesc": "Eigenständiger EdgeGuard oder erster Cluster-Knoten. Erstellt einen Admin-Account auf diesem Server.",
"modeNode": "Vorhandenem Cluster beitreten",
"modeNodeDesc": "Diesen Server als zweiten Cluster-Knoten hinzufügen. Admin-Zugangsdaten werden automatisch vom Primary repliziert.",
"nodeTitle": "Cluster-Knoten einrichten",
"nodeIntro": "Trage den FQDN dieses Knotens ein. Es wird kein Admin-Account erstellt — die Zugangsdaten kommen nach der Datenbankreplikation vom Primary.",
"nodePreflightTitle": "Vor dem Fortfahren",
"nodePreflightDesc": "Stelle sicher, dass EdgeGuard auf dem Primary bereits läuft und du dort einen Join-Token erstellen kannst (Cluster-Seite). Du benötigst ihn im nächsten Schritt.",
"nodeSubmit": "Als Cluster-Knoten einrichten",
"nodeSuccessTitle": "Knoten konfiguriert",
"nodeJoinTitle": "Nächster Schritt: Cluster beitreten",
"nodeJoinDesc": "Dieser Server ({{fqdn}}) ist konfiguriert. Führe jetzt den Join-Befehl aus, um ihn mit dem Primary zu verbinden.",
"nodeStep1Title": "Join-Token auf dem Primary generieren",
"nodeStep1Desc": "Öffne die Cluster-Seite auf deinem Primary-EdgeGuard → Knoten hinzufügen → Join-Token generieren. Token kopieren.",
"nodeStep2Title": "Join-Befehl auf diesem Server ausführen",
"nodeStep2Desc": "Ersetze <primary-fqdn> mit dem FQDN deines Primarys und <token> mit dem Token aus Schritt 1:",
"nodeStep3Title": "API neu starten",
"nodeStep3Desc": "Nach erfolgreichem Join-Befehl API neu starten, damit die replizierte Konfiguration übernommen wird:",
"nodeLoginNote": "Der Login erfolgt mit den Admin-Zugangsdaten des Primarys. Das funktioniert sobald die Datenbankreplikation aufgebaut wurde (der Join-Befehl richtet das ein)."
},
"dashboard": {
"title": "Dashboard",
"welcomeHint": "Übersicht aller laufenden EdgeGuard-Komponenten."
"welcomeHint": "EdgeGuard-Übersicht — Health, Counts, Live-Status der wichtigsten Dienste.",
"kpi": {
"domains": "Domains",
"backends": "Backends",
"ifaces": "Interfaces",
"fwRules": "FW-Regeln",
"natRules": "NAT-Regeln",
"wg": "WG-Verbindungen"
},
"wgCard": {
"title": "WireGuard",
"empty": "Noch kein WG-Tunnel angelegt.",
"peersOnline": "{{online}} / {{total}} Peers verbunden"
},
"firewallCard": {
"title": "Firewall",
"zones": "Zonen",
"activeRules": "{{rules}} aktive Regeln · {{nat}} NAT"
},
"sslCard": {
"title": "SSL-Zertifikate",
"total": "Verwaltete Zertifikate",
"certExpired": "{{count}} Zertifikat(e) abgelaufen — sofort erneuern",
"expiringSoon": "{{count}} läuft bald ab (< 30 Tage)",
"allFresh": "Alle Zertifikate haben > 30 Tage Restlaufzeit."
},
"clusterCard": {
"title": "Cluster",
"nodes": "Knoten",
"modeSingle": "Single-Node",
"modeCluster": "Cluster",
"drift": "Config-Drift erkannt",
"health": {
"ok": "OK",
"degraded": "degraded",
"split-brain": "split-brain"
}
},
"routingCard": {
"title": "Routing",
"domains": "Domains",
"backends": "Backends",
"attached": "{{count}}/{{total}} Domains haben einen Primary-Backend"
},
"systemCard": {
"title": "System",
"version": "Version",
"api": "API",
"ifaces": "Interfaces",
"wg": "WireGuard"
},
"alertsCard": {
"title": "Aktuelle Alerts",
"viewAll": "Alle anzeigen"
},
"downBackendsAlert": "{{count}} Backend(s) komplett ausgefallen — kein Server UP",
"maintenanceAlert": "{{count}} Domain(s) im Wartungs-Modus",
"onboardingTitle": "Willkommen bei EdgeGuard",
"onboardingIntro": "Frische Box — hier die nächsten Schritte um Customer-Traffic zu routen:",
"onboardingStep1": "Backend-Pool anlegen (App-Server hinter HAProxy)",
"onboardingStep2": "Domain hinzufügen (FQDN, primary Backend zuweisen)",
"onboardingStep3": "TLS-Zertifikat ausstellen (Let's Encrypt HTTP-01)",
"servicesCard": {
"title": "Service-Status (live, 10s)"
},
"activityCard": {
"title": "Letzte Aktivität (Audit-Log)",
"empty": "Noch keine Aktivität — Mutationen werden hier protokolliert."
},
"haproxyCard": {
"title": "HAProxy-Backends (live)",
"empty": "Keine Backend-Stats erreichbar (HAProxy down oder admin.sock-permission).",
"frontends": "Listener"
},
"resCard": {
"load": "Load",
"memory": "Memory",
"disk": "Disk /",
"free": "frei",
"conntrack": "Conntrack",
"uptime": "Uptime"
},
"licenseOk": "Lizenz OK",
"licenseTrial": "Trial",
"licenseTrialDays": "Trial · {{days}}d"
},
"domains": {
"title": "Domains",
@@ -680,7 +834,10 @@
"wg": {
"title": "WireGuard",
"intro": "VPN-Tunnel über WireGuard. Server-Modus = wir lauschen für Peers; Client-Modus = wir verbinden zu einem festen Upstream. Privater Schlüssel liegt verschlüsselt in der DB.",
"tabs": { "servers": "Server-Tunnel", "clients": "Client-Tunnel" },
"tabs": {
"servers": "Server-Tunnel",
"clients": "Client-Tunnel"
},
"serverIntro": "Server-Tunnel hosten ein Peer-Roster — z.B. Mitarbeiter-Geräte oder Niederlassungen. Pro Peer bekommt der Operator eine .conf zum Download (oder QR-Code für Mobile).",
"clientIntro": "Client-Tunnel verbinden EdgeGuard zu einem fremden WireGuard-Server (z.B. HQ-Datacenter). Allowed-IPs steuert, welcher Traffic durch den Tunnel geroutet wird.",
"iface": {
@@ -767,98 +924,14 @@
"failed": "Import fehlgeschlagen"
}
},
"dashboard": {
"title": "Dashboard",
"welcomeHint": "EdgeGuard-Übersicht — Health, Counts, Live-Status der wichtigsten Dienste.",
"kpi": {
"domains": "Domains",
"backends": "Backends",
"ifaces": "Interfaces",
"fwRules": "FW-Regeln",
"natRules": "NAT-Regeln",
"wg": "WG-Verbindungen"
},
"wgCard": {
"title": "WireGuard",
"empty": "Noch kein WG-Tunnel angelegt.",
"peersOnline": "{{online}} / {{total}} Peers verbunden"
},
"firewallCard": {
"title": "Firewall",
"zones": "Zonen",
"activeRules": "{{rules}} aktive Regeln · {{nat}} NAT"
},
"sslCard": {
"title": "SSL-Zertifikate",
"total": "Verwaltete Zertifikate",
"certExpired": "{{count}} Zertifikat(e) abgelaufen — sofort erneuern",
"expiringSoon": "{{count}} läuft bald ab (< 30 Tage)",
"allFresh": "Alle Zertifikate haben > 30 Tage Restlaufzeit."
},
"clusterCard": {
"title": "Cluster",
"nodes": "Knoten",
"modeSingle": "Single-Node",
"modeCluster": "Cluster",
"drift": "Config-Drift erkannt",
"health": {
"ok": "OK",
"degraded": "degraded",
"split-brain": "split-brain"
}
},
"routingCard": {
"title": "Routing",
"domains": "Domains",
"backends": "Backends",
"attached": "{{count}}/{{total}} Domains haben einen Primary-Backend"
},
"systemCard": {
"title": "System",
"version": "Version",
"api": "API",
"ifaces": "Interfaces",
"wg": "WireGuard"
},
"alertsCard": {
"title": "Aktuelle Alerts",
"viewAll": "Alle anzeigen"
},
"downBackendsAlert": "{{count}} Backend(s) komplett ausgefallen — kein Server UP",
"maintenanceAlert": "{{count}} Domain(s) im Wartungs-Modus",
"onboardingTitle": "Willkommen bei EdgeGuard",
"onboardingIntro": "Frische Box — hier die nächsten Schritte um Customer-Traffic zu routen:",
"onboardingStep1": "Backend-Pool anlegen (App-Server hinter HAProxy)",
"onboardingStep2": "Domain hinzufügen (FQDN, primary Backend zuweisen)",
"onboardingStep3": "TLS-Zertifikat ausstellen (Let's Encrypt HTTP-01)",
"servicesCard": {
"title": "Service-Status (live, 10s)"
},
"activityCard": {
"title": "Letzte Aktivität (Audit-Log)",
"empty": "Noch keine Aktivität — Mutationen werden hier protokolliert."
},
"haproxyCard": {
"title": "HAProxy-Backends (live)",
"empty": "Keine Backend-Stats erreichbar (HAProxy down oder admin.sock-permission).",
"frontends": "Listener"
},
"resCard": {
"load": "Load",
"memory": "Memory",
"disk": "Disk /",
"free": "frei",
"conntrack": "Conntrack",
"uptime": "Uptime"
},
"licenseOk": "Lizenz OK",
"licenseTrial": "Trial",
"licenseTrialDays": "Trial · {{days}}d"
},
"ntp": {
"title": "Zeitserver (Chrony)",
"intro": "Chrony als Time-Sync-Daemon (NTP). Quellen oben, Listen-/Serve-Konfig im Settings-Tab. Wenn 'serve_clients' aktiv und LAN-IPs gebound sind, wird die Box selbst zum NTP-Server für das LAN.",
"tabs": { "pools": "Quellen", "settings": "Settings", "peers": "Peer-Status" },
"tabs": {
"pools": "Quellen",
"settings": "Settings",
"peers": "Peer-Status"
},
"sourcesCard": {
"title": "Live Peer-Status (chronyc sources)",
"name": "Name / IP",
@@ -934,7 +1007,11 @@
"dns": {
"title": "DNS (Unbound)",
"intro": "Unbound-Resolver auf :53. Lokale Zonen (authoritativ aus DNS-Records) und Forward-Zonen (per stub-zone weiter zu fremden Resolvern). Default-Forwarder für alles andere.",
"tabs": { "zones": "Zonen", "settings": "Resolver-Settings", "stats": "Resolver-Stats" },
"tabs": {
"zones": "Zonen",
"settings": "Resolver-Settings",
"stats": "Resolver-Stats"
},
"statsCard": {
"title": "Resolver-Statistiken (unbound-control stats_noreset)",
"totalQueries": "Abfragen gesamt",
@@ -1043,22 +1120,22 @@
},
"actions": {
"allow": "allow — Zugriff erlauben",
"deny": "deny — Zugriff blockieren"
"deny": "deny — Zugriff blockieren"
},
"aclTypes": {
"src": "src — Quell-IP/CIDR",
"dst": "dst — Ziel-IP/CIDR",
"dstdomain": "dstdomain — Ziel-Domain (exact)",
"srcdomain": "srcdomain — Quell-Domain (rDNS)",
"port": "port — Ziel-Port",
"proto": "proto — http/https/ftp/...",
"method": "method — GET/POST/CONNECT/...",
"time": "time — Wochentag/Zeit",
"url_regex": "url_regex — kompletter URL-Match",
"src": "src — Quell-IP/CIDR",
"dst": "dst — Ziel-IP/CIDR",
"dstdomain": "dstdomain — Ziel-Domain (exact)",
"srcdomain": "srcdomain — Quell-Domain (rDNS)",
"port": "port — Ziel-Port",
"proto": "proto — http/https/ftp/...",
"method": "method — GET/POST/CONNECT/...",
"time": "time — Wochentag/Zeit",
"url_regex": "url_regex — kompletter URL-Match",
"urlpath_regex": "urlpath_regex — Path-Teil",
"dstdom_regex": "dstdom_regex — Domain-Regex",
"srcdom_regex": "srcdom_regex — Quell-Domain-Regex",
"browser": "browser — User-Agent-Regex"
"dstdom_regex": "dstdom_regex — Domain-Regex",
"srcdom_regex": "srcdom_regex — Quell-Domain-Regex",
"browser": "browser — User-Agent-Regex"
}
},
"common": {
@@ -1173,7 +1250,10 @@
"intro": "Notification-Channels für kritische Events. Webhook (Slack/Discord/Teams/Generic-HTTP) oder Email (SMTP). Triggers: cert.expiring (<14 d), cert.renew_failed, backup.failed, license.invalid.",
"scopeTitle": "Was triggert Alarme?",
"scopeDesc": "cert.expiring — TLS-Zertifikat <14 Tage Restzeit (dedupe 12h). cert.renew_failed — ACME-Renewer hat Fails. backup.failed — Scheduled Backup konnte nicht erstellt werden. license.invalid — License-Server liefert valid=false. backend.down — alle Server eines Backend-Pools sind DOWN (2-Min-Check, dedupe 12h). disk.full — Root-Filesystem ≥80% Warnung, ≥90% Critical (stündlich, dedupe 12h). mem.high — RAM-Auslastung ≥85% Warnung, ≥95% Critical (5-Min-Check, dedupe 12h). conntrack.high — Conntrack-Tabelle ≥80% Warnung, ≥90% Critical; bei 100% werden alle neuen Verbindungen lautlos verworfen (2-Min-Check, dedupe 12h). ntp.unsync — chrony hat keine synchronisierte Zeitquelle; Drift führt zu TLS- und JWT-Fehlern (10-Min-Check, dedupe 12h). wg.tunnel.down — WireGuard-Client-Tunnel hat seit >5 Min keinen Handshake; Remote-Traffic wird lautlos verworfen (5-Min-Check, dedupe 12h).",
"tabs": { "channels": "Channels", "events": "History" },
"tabs": {
"channels": "Channels",
"events": "History"
},
"add": "Channel hinzufügen",
"addTitle": "Notification-Channel anlegen",
"editTitle": "Channel bearbeiten",
@@ -1264,11 +1344,21 @@
"run": "Ausführen",
"runFromBoxTitle": "Tools laufen auf der EdgeGuard-Box",
"runFromBoxDesc": "Diese Aufrufe nutzen die Outbound-Connectivity der EdgeGuard — nicht deinen lokalen Rechner. Wenn ping/curl auf der Box scheitert, ist das Box-Network-Layer das Problem (Firewall-Rules, Default-Route, DNS).",
"ping": { "intro": "ICMP echo-request × 4, 2s Timeout pro Paket. Misst Loss + RTT." },
"trace": { "intro": "Hop-by-Hop-Pfad mit UDP-Probes (max 20 Hops, kein reverse-DNS)." },
"dig": { "intro": "DNS-Lookup über unbound (Box-Resolver). Zeigt Antwort + AUTHORITY/ADDITIONAL Section." },
"curl": { "intro": "HTTPS-Probe mit -IsSv: TLS-Handshake-Details + Header. Folgt KEINE Redirects." },
"tcp": { "intro": "Pure TCP-Connect ohne I/O. Ideal um zu prüfen ob ein Backend-Port erreichbar ist." },
"ping": {
"intro": "ICMP echo-request × 4, 2s Timeout pro Paket. Misst Loss + RTT."
},
"trace": {
"intro": "Hop-by-Hop-Pfad mit UDP-Probes (max 20 Hops, kein reverse-DNS)."
},
"dig": {
"intro": "DNS-Lookup über unbound (Box-Resolver). Zeigt Antwort + AUTHORITY/ADDITIONAL Section."
},
"curl": {
"intro": "HTTPS-Probe mit -IsSv: TLS-Handshake-Details + Header. Folgt KEINE Redirects."
},
"tcp": {
"intro": "Pure TCP-Connect ohne I/O. Ideal um zu prüfen ob ein Backend-Port erreichbar ist."
},
"securityTitle": "Sicherheits-Hinweis",
"securityDesc": "Endpoints sind hinter Admin-Auth. Targets werden gegen eine strikte Allow-Liste validiert (keine Shell-Metazeichen). curl ist auf http(s) beschränkt — kein file:// / smb:// / data://.",
"noOutput": "(kein Output)"
@@ -1278,7 +1368,10 @@
"intro": "Sicherungen der PostgreSQL-Datenbank + /var/lib/edgeguard (Setup, License, JWT, ACME-Account). Täglicher Auto-Job + manueller Trigger.",
"scopeTitle": "Was wird gesichert?",
"scopeDesc": "DB-Dump (pg_dump --clean), setup.json, license_key, license.cache, .jwt_fingerprint, acme-account/. Konfig-Dateien (haproxy.cfg, nft, …) sind aus der DB regenerierbar und werden NICHT mitgesichert.",
"tabs": { "history": "Sicherungen", "remotes": "Off-Site-Ziele" },
"tabs": {
"history": "Sicherungen",
"remotes": "Off-Site-Ziele"
},
"statLastSuccess": "Letzte erfolgreiche Sicherung",
"statTotal": "Erfolgreiche Sicherungen",
"statSize": "Gesamtgröße",