Adds GET /ntp/sources endpoint (runs chronyc sources, parses tabular
output) and a new "Peer status" tab in the NTP page showing all
configured peers with mode, state badge, stratum, poll interval,
reach register, last-rx and offset/error sample.
v1.1.101
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /system/health gibt zusätzlich hostname, os (PRETTY_NAME aus
/etc/os-release) und kernel (/proc/version, ohne Build-Details) zurück
- Settings-Seite: SystemHealth-Interface erweitert, System-Info-Card
zeigt Hostname, Betriebssystem und Kernel-Version an (conditional,
nur wenn vorhanden)
- Nützlich beim Verwalten mehrerer Boxen — ohne SSH sofort sehen
welches System gerade geöffnet ist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SystemHandler.ExtraReloaders: neues Feld für nftables, wireguard,
squid, unbound, chrony Reloader
- WithAllReloaders(): Wire-Methode; main.go bindet alle Reloader nach
Initialisierung ein
- RenderConfigs: läuft alle Reloader durch, sammelt Fehler, antwortet
mit {ok, rendered[], errors{}} — partiell OK wird als warning gemeldet
- UI: Settings-Button-Label "Re-render all configs"; partial-error
Toast zeigt welche Dienste fehlschlugen
- Timeout: 30s statt 10s (alle Dienste brauchen mehr Zeit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /wireguard/importable listet alle .conf-Dateien in /etc/wireguard/
mit already_in-Flag (bereits in DB)
- POST /wireguard/import nimmt optionale Names-Liste; ohne Namen → alles
- edgeguard-ctl wg-import [iface…] importiert nur die genannten Interfaces
- UI: Checkbox-Modal mit "Alle neuen auswählen" + Einzelauswahl;
bereits importierte Interfaces disabled + grüner Tag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: POST /dns/flush-cache (unbound-control flush_zone .)
POST /ntp/force-sync (chronyc makestep)
Beide werden im Audit-Log festgehalten.
UI: Schaltflächen in DNS-Settings und NTP-Settings neben Save,
mit Tooltip-Beschreibung + i18n (de+en).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ActionButtons: Edit-Button wird für Viewer wie Delete gesperrt (Tooltip zeigt Reason)
- Domains/Detail: isViewer-Flag an alle Sub-Panels weitergegeben; Save-, TLS-Cert-,
Routing-Rules- und Headers-Buttons für Viewer disabled
- Backends/Detail: Save-Button + ServerPanel Add-Button für Viewer disabled
- i18n: domains.backendUp/backendDown Keys (waren noch hardkodiert)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RemoteTargets: alle 20+ hardkodierten Form-Labels und deutschen Hilfetexte
(S3: Endpoint/Region/Bucket/AccessKey/SecretKey/PathPrefix/HTTPS;
SFTP: Host/Port/Username/Password/PrivateKey/RemoteDir/HostKeyFp)
sind jetzt in t()-Aufrufen mit remotes.s3.*/remotes.sftp.*-Keys.
NTP: Listen-Address-Dropdown-Labels (alle IPv4/IPv6-Interfaces, Loopback)
verwenden jetzt dns.settings.allIPv4/allIPv6/loopback (identisch zu DNS-Settings).
NTP-Pool-Adresse-Placeholder → t('ntp.pool.addressPlaceholder').
en/de: alle neuen Keys ergänzt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- haproxy_stats.go: FRONTEND-Rows aus show-stat CSV auslesen und als
frontends[] zurückgeben (Name, Sessions, MaxSess, Bytes, ReqTot/Rate)
- Dashboard: Listener-Sektion über Backend-Liste; query jetzt unified
haproxyStats mit backends/frontends alias für Abwärtskompatibilität
- common.relTime.*-Keys (en+de) eingeführt; relativeTime/relativeFromIso
in Dashboard + relTime in WireGuard Servers/Clients auf t-Parameter
umgestellt statt hardcodierter Strings
- CertExpiry in Cluster-Seite nutzt jetzt certDaysRemaining/certExpiredDaysAgo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
relativeTime() in Dashboard, relTime() in WireGuard Clients+Servers und
CertExpiry() im Cluster-Page haben bisher hardcodiertes Deutsch
(„vor 5s", „abgelaufen vor X Tagen") gerendert, unabhängig von der
gewählten UI-Sprache.
Neu: gemeinsame i18n-Keys common.relTime.{Xs,Xm,Xh,Xd,never} in EN+DE,
sowie cluster.{certDaysRemaining,certExpiredDaysAgo}. Alle Helper-
Funktionen nehmen jetzt t() als Parameter und sind damit sprachunabhängig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- internal/services/users: Repo mit CRUD, bcrypt (cost 12), Upsert für
setup-store-Admin-Migration, RecordLogin
- internal/handlers/users: GET/POST/PUT /users, POST /users/:id/password,
DELETE /users/:id; Schutz gegen Selbst-Löschung
- auth.go Login: DB-Nutzer first, Fallback auf setup-store-Admin;
bei erfolgreichem Fallback wird der Admin per Upsert in die DB
migriert (kein manueller Eingriff nötig)
- management-ui: /users-Seite mit Tabelle, Anlegen-, Bearbeiten-,
Passwort-setzen- und Löschen-Modals; "You"-Badge für eigenen Account
- Sidebar + Route + i18n (de/en) ergänzt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: force_http1 (bool, default false) — zwingt HAProxy auf der
Backend-Verbindung zu HTTP/1.1 statt h2,http/1.1 zu verhandeln.
Nötig für Legacy-Apps die kein h2 sprechen.
Domain: disable_h3 (bool, default false) — unterdrückt Alt-Svc-
Response-Header für diese Domain. Browser erhalten keinen h3/QUIC-
Hinweis und bleiben auf h2/http1.1.
Migration 0027, Model+Service+HAProxy-Template+UI+i18n.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neues Feld 'client_routes' auf wireguard_interfaces: der Operator
trägt dort kommagetrennte Netzwerke ein (z. B. 10.0.10.0/24 für ein
LAN hinter der Box). Der Peer-Config-Download fügt diese automatisch
als zusätzliche AllowedIPs in den [Peer]-Block der Client-Config ein.
Bisher wurde nur das Server-Tunnel-Subnetz (ifc.address_cidr) als
AllowedIPs exportiert — Peers konnten so keine anderen Netze über
den Tunnel erreichen ohne die Config manuell anzupassen.
Migration: 0026_wg_client_routes.sql
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zwei unabhängige Fixes für das WireGuard peer-to-peer Problem:
1. Startup-nftables-Render: edgeguard-api rendert beim Start die
nftables-Konfiguration neu. Damit werden Template-Änderungen aus
einem Update (z.B. 1.1.51 WG-forward-Rule) sofort aktiv, ohne
dass der Operator manuell eine Firewall-Mutation triggern müsste.
2. UI-Hint: Peer-AllowedIPs-Feld erklärt explizit warum /32 nötig
ist und was /24 kaputtmacht (Server routet ganzen Subnet-Block zu
einem Peer → andere Peers nicht mehr erreichbar).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Statt aller 15+ Felder in einem Modal gibt es jetzt eine eigene Seite
unter /domains/:id. Der Create-Flow bleibt im Modal (Name, Backend, Active,
HTTP→HTTPS), ein Hinweistext führt nach dem Speichern auf die Detailseite
weiter.
Die Detailseite enthält links das vollständige Settings-Formular (HSTS,
Rate-Limit, Maintenance, Max-Body, Notes, WWW-Redirect) und rechts das
HeadersPanel zum Verwalten custom HTTP-Response-Header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ruleset.nft.tpl: operator-rules bekommen `counter <action> comment "egid:<id>"`
→ nft zählt Packets + Bytes per Regel ab dem letzten Ruleset-Apply
- handlers/firewall_counters.go: GET /firewall/counters parst
`sudo nft list table inet edgeguard` per Regex, liefert [{rule_id,packets,bytes}]
- handlers/firewall_counters_test.go: unit-tests für parseNFTCounters
- Firewall/Rules.tsx: neue "Hits"-Spalte (Packet-Count, Tooltip mit Bytes),
10s-Polling via TanStack Query ['fw','counters']
- i18n de+en: fw.rule.hits
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/backends/:id zeigt Pool-Einstellungen (links) und Server-Liste
(rechts) auf einer eigenen Seite. Der Edit-Button in der Liste
navigiert dorthin; Anlegen bleibt ein schlankes Modal ohne Server-
Panel. Außerdem: Backend-Detail lädt den Pool per GET /backends/:id
und speichert alle Felder direkt, keine Sync-Logik nötig.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GET /system/interfaces liefert jetzt rx_bytes/tx_bytes/rx_packets/
tx_packets/rx_drop/tx_drop aus /proc/net/dev. Die System-Interfaces-
Card in Netzwerk → Interfaces zeigt die Werte als Mini-Tabelle mit
10s-Refetch; Hover auf den Bytes-Werten zeigt Paketzähler + Drops.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Operator hat Admin-Passwort vergessen aber SSH-Zugang zur Box →
schneller Reset ohne SMTP/Email-Setup.
Flow:
1. `sudo edgeguard-ctl reset-password` auf der Box → 32-hex-Token
+ ISO-Expiry werden nach /var/lib/edgeguard/.reset-token (mode
0600 edgeguard:edgeguard) geschrieben, Token kommt auf stdout.
TTL: 30 min.
2. Login-Seite hat „Passwort vergessen?"-Link → /reset-password.
3. Reset-Page: Token + neues Passwort (min. 12). POST /auth/reset-
password validiert Token (constant-time compare), prüft Expiry,
löscht das File (single-use), hash't das Passwort + speichert
in setup.json.
internal/services/setup/:
- SetAdminPassword() — bcrypt-hash + save, fehler wenn setup nicht
completed
- GenerateResetToken() / ConsumeResetToken() — File-basiert,
Format: "<token>|<RFC3339-expiry>"
internal/handlers/auth.go: POST /api/v1/auth/reset-password.
cmd/edgeguard-ctl/main.go: `reset-password` command.
UI: /reset-password Page mit Info-Alert für CLI-Snippet
(„sudo edgeguard-ctl reset-password" im dunklen Code-Block); Login-
Seite bekommt den „Passwort vergessen?"-Link.
Verifiziert auf 1.0.76: CLI druckt Token + schreibt File mit 0600
edgeguard:edgeguard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schutz gegen Box-Total-Loss — lokale Backups in /var/backups/edgeguard
helfen nicht, wenn die Disk stirbt oder die Box brennt. Nach jedem
erfolgreichen lokalen Backup wird die tar.gz an alle aktiven
Off-Site-Ziele hochgeladen.
Migration 0022: backup_remotes (kind=s3|sftp, target_url, settings
JSONB, active, last_upload_at, last_error) + backups.remote_uploads
JSONB (per-Target-Result).
internal/services/backup/remote/:
- UploadAll() — pro aktivem Target ein Upload, Failures non-fatal
- S3 via minio-go/v7 — funktioniert mit AWS, MinIO, Backblaze B2,
Cloudflare R2, Hetzner Object Storage (alle S3-API-kompatibel)
- SFTP via golang.org/x/crypto/ssh + pkg/sftp. Password + Private-
Key (OpenSSH, base64-encoded) als Auth. Optional host_key_
fingerprint-Pinning (SHA256:...); leer = TOFU (unsicher vs MitM,
OK für initial setup).
- Test() lädt eine 1KB-Probe + löscht sie wieder — Operator-UI hat
einen „Verbindung testen"-Button.
backup.Service.RemoteUploader-Interface: nach erfolgreichem
recordSuccess() läuft UploadAll, Results landen in backups.remote_
uploads JSONB. last_upload_at/last_error in backup_remotes pro Target
gepflegt. API + Scheduler injizieren beide den Adapter.
internal/handlers/backup_remotes.go: CRUD + POST /:id/test. Sensitive
Felder (secret_key, password, private_key) werden in GET-Responses
durch ***SET*** maskiert; UpdateChannel merged das zurück damit der
Operator bei Edit ohne Re-Eingabe speichern kann.
UI: Backups-Page jetzt mit Tabs "Sicherungen" + "Off-Site-Ziele".
Tab 2 hat CRUD-Tabelle mit kind-konditionalem Form (S3-Felder oder
SFTP-Felder), Test-Button pro Row, last_upload-Status mit FAIL-Tag
bei Errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-Vorbereitung für Multi-Node, ohne dass eine zweite Box nötig ist.
Single-Node-Mode bleibt der Default; alles existiert und wird sichtbar,
sobald ein 2. Knoten joined (Phase 3.2 später).
Migration 0020:
ha_nodes += version (edgeguard-api-Version)
config_hash (drift-Detection-Hash)
mgmt_ip (Management-IP, niemals VIP)
status (online|offline|joining|leaving|unknown)
internal/cluster/local_config.go:
/etc/edgeguard/node.conf — INI-style, node-lokale Identität:
NODE_ID, HOSTNAME, MGMT_IP, ROLE, PEER_HOSTS. NIEMALS zwischen
Cluster-Peers replizieren. LoadLocalConfig / SaveLocalConfig /
EnsureLocalConfig (auto-Generierung beim ersten Boot).
MgmtIP-Default = firstNonLoopbackIPv4(); Operator kann
überschreiben (mehrere Interfaces).
internal/cluster/store.go:
- HANode-Model um die 4 neuen Felder erweitert
- UpsertSelf nimmt jetzt mgmt_ip/version/config_hash/status, COALESCE
erhält werte wenn der Caller sie nicht setzt
- EnsureSelfRegistered-Signatur: + role + version-Argument
internal/handlers/cluster.go:
GET /api/v1/cluster/status — strukturierter Endpoint:
{local_id, local_node, peers[], mode, health, drift_found, updated_at}
GET /api/v1/cluster/nodes bleibt für Tools.
UI (pages/Cluster):
- Header zeigt Mode-Tag (Single-Node / Cluster) + Health-Tag (OK /
degraded / split-brain)
- Self-Card: Descriptions mit FQDN, Node-ID, Status, Role, Version,
MGMT-IP, API-URL, Config-Hash
- Peers-Tabelle nur wenn vorhanden, mit "drift"-Marker pro Row
- Drift-Alert-Banner wenn ein Peer einen anderen config_hash hat
- Single-Node-Mode Hinweis-Alert ("cluster-join kommt in 3.2")
postinst: leeres /etc/edgeguard/node.conf wird angelegt (chown
edgeguard); API auto-befüllt beim ersten boot.
main.go ruft EnsureLocalConfig + EnsureSelfRegistered mit version.
Verifiziert auf der Box (1.0.70):
- /etc/edgeguard/node.conf hat NODE_ID, HOSTNAME, MGMT_IP=89.163.205.6,
ROLE=primary
- ha_nodes-Row: status=online, version=1.0.70, mgmt_ip=89.163.205.6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI-Restruktur nach User-Feedback:
- Sidebar-Eintrag „Firewall-Live" entfernt — gehört thematisch unter
Firewall, kein Top-Level-Item. Standalone-Page /firewall-live raus.
- Neuer Firewall-Tab „Live-Log" zwischen NAT und Zonen.
- Default = AUS: zeigt Empty-State mit Start-Button. WebSocket
verbindet erst nach Klick. Stop-Button schließt explizit.
- Filter-Inputs (src/dst/rule_id) jetzt 300ms debounced — vorher
triggerte jeder Tastendruck einen WS-Reconnect.
Server-Pipeline „wirklich live" gepinnt:
- ulogd.conf NFLOG-Plugin bekommt qthreshold=1 + qtimeout=1. Default
des Kernels batched Pakete bis 1024 oder 1s; mit 1/1 fließt jedes
Paket sofort. Critical für die Wahrnehmung „live" statt „bursty".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neue Page management-ui/src/pages/FirewallLive — Live-Tail der NFLOG-
Events aus /api/v1/firewall/log/live (WebSocket). Features:
- Status-Indicator (Live/getrennt), Auto-Reconnect alle 2s nach Drop
- Filter-Bar (action/proto/src/dst/rule_id) — bei Änderung wird der
WS neu verbunden, Server schickt frischen Snapshot
- Pause-Toggle: während Pause werden Events gebuffert (max 1000),
beim Resume in die Tabelle gemerged
- CSV-Export der aktuellen Tabelle (timestamp/rule/action/proto/src/
dst/iface/size)
- Color-coded Action-Tags (ACCEPT=grün, DROP=rot, REJECT=orange)
- Ring-Buffer 1000 im UI damit die DOM-Last bei Hochlast bleibt
- Sidebar-Eintrag "Firewall-Log" unter Sicherheit (Eye-Icon)
- DE/EN i18n
haproxy: backend api_backend bekommt `timeout tunnel 1h` damit der
WebSocket-Stream nicht nach `timeout server 60s` ohne Events stirbt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
handler: edgeguard-User darf systemd-run nicht direkt aufrufen ("Inter-
active authentication required"). sudo -n + sudoers-Whitelist auf
exakt die Unit-Form für edgeguard-upgrade.service.
UI: UpdateBanner-Komponente neu — Pattern wie mail-gateway/enconf:
Banner mit Force-Check-Button + Popconfirm. Beim Apply zeigt full-
screen-Overlay mit animiertem Orbit (zwei Ringe + Dots), Versions-
sprung, vier Step-Indicators (Download/Install/Restart/Verify) und
Live-Timer. Poll auf /system/health detektiert Version-Flip ODER
"sah down dann up" und window.reload nach 1.5s. Sicherheits-Timeout
2 min schickt sonst auch reload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration 0017 fügt backends.websocket BOOL. Wenn aktiv emittiert der
HAProxy-Renderer `timeout tunnel 1h` IM Backend-Block; defaults-Section
hat den Global-Timeout dafür verloren. Backends ohne WS-Workload bleiben
bei strikten HTTP-Timeouts (Connection-Hygiene). Migrations-Heuristik
schaltet vm-pool/proxmox/console/vnc-Namen auto auf true damit Proxmox-
Konsole nach Deploy weiterhin durchläuft.
UI: Switch im Backend-Modal + WS-Tag in der Übersichtstabelle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Update-Banner war eine Inline-Alert mit "Apply now"-Button —
ein Click → Hintergrund-Apt-Run, kein Feedback ob's durch ist.
Jetzt:
* Banner zeigt nur noch Hint + "Details anzeigen"-Button.
* Modal listet alle Pakete mit Upgrade (installed → available),
prominenter Warnhinweis dass edgeguard-api+scheduler restartet
werden (~2-5s Unterbrechung) — HAProxy/nft/WG/Squid/Unbound/Chrony
bleiben durch.
* "Jetzt anwenden" startet den apt-Run, Modal schaltet auf Apply-
Mode (kein Cancel mehr, Progress-Alert mit aktueller→Ziel-Version).
* Polling von /system/health auf 2s erhöht während Apply. Sobald
health.version == target → success-toast + auto-reload der UI
(1.5s delay damit das neue UI-Bundle gecached werden kann).
Pattern entlehnt von enconf/mail-gateway. Plus i18n-Erweiterung
für update.modalTitle / modalIntro / modalWarn / applyingHint /
applyingDetail / success.
Version 1.0.45.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Free-Text-Input ('127.0.0.1, ::1, 10.10.20.3') — Operator
musste Werte tippen + auf Format aufpassen.
Jetzt: Multi-Select (mode='tags') das die IPs aus /system/interfaces
+ vier Spezial-Werte (0.0.0.0, ::, 127.0.0.1, ::1) anbietet. Optionen
zeigen IP + Iface-Name + Family ('10.0.20.26 — ens19 (IPv4)'). Tag-
Mode lässt zusätzlich freie Eingabe zu, falls eine geplante VIP noch
nicht im Kernel ist.
Convertierung Form↔Wire: UI Array ↔ DB Comma-CSV.
Version 1.0.35.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>