250 Commits

Author SHA1 Message Date
Debian
73619c17f8 fix(system): render-configs rendert jetzt ALLE Dienste, nicht nur HAProxy
- 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>
2026-05-24 22:29:00 +02:00
Debian
8d7a43bc8c feat(wireguard): selektiver .conf-Import — Auswahl einzelner Interfaces per Modal
- 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>
2026-05-24 22:09:37 +02:00
Debian
ac068bc9dd feat(dns+ntp): DNS-Cache-Flush + NTP-Force-Sync — operative Aktionen (1.1.94)
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>
2026-05-24 16:52:20 +02:00
Debian
0856c2fc10 feat(rbac): RBAC-Audit abschliessen — Viewer-Sperre für alle verbleibenden Seiten (1.1.93)
Domains, Backends (inkl. ServerPanel), Backups/History und Alerts:
isViewer-Check + disabled Switches, Tooltip-wrapped Add-Buttons,
bedingte Delete/Restore-Render (Viewer → disabled Tooltip statt Popconfirm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:39:24 +02:00
Debian
d3a3b93c1b feat(rbac): Viewer-Rolle auf alle verbleibenden CRUD-Seiten durchsetzen — v1.1.92
DNS, NTP, ForwardProxy, RoutingRules, Networks (Interfaces + Routes),
IPAddresses, WireGuard (Servers + Clients + Peers), Backups/RemoteTargets, SSL:
Add-Buttons, quickToggle-Switches und Settings-Save-Buttons für Viewer deaktiviert.
ActionButtons-Komponente schützt Edit/Delete bereits zentral; hier wurden nur
die verbleibenden Mutationspunkte (Add, Switch, Submit) nachgezogen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:56:24 +02:00
Debian
d4267e2003 feat(rbac): Viewer-Rolle auf alle Firewall-CRUD-Seiten durchsetzen — v1.1.91
AddressObjects, AddressGroups, Services, ServiceGroups, Rules, NATRules, Zones:
Add/Edit-Buttons disabled + Tooltip, Delete bedingt gerendert (viewer vs. Popconfirm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:20:54 +02:00
Debian
8b0e8a8ab4 fix(rbac): Viewer-Rolle in Settings + Cluster — operative Buttons sperren
Settings: HAProxy-Reload, Render-Configs, Backup-Now, Service-Restart,
E-Mail-Save, Maintenance-Toggle, Backup-/Audit-Retention, Auto-Update-
und IPv6-Toggle disabled für Viewer (Passwort-Änderung bleibt aktiv).
Cluster: Join-Token-Generation, Peer-Removal, mTLS-Cert-Renew disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:14:51 +02:00
Debian
2d027b3044 fix(rbac): Viewer-Rolle in Detail-Pages + ActionButtons — Edit ebenfalls sperren
- 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>
2026-05-24 13:10:29 +02:00
Debian
9708e4441b fix(api): WireGuard Private-Key-Verlust bei Peer-Update + Domain-Header-Ownership
wireguard.go: UpdatePeer zerstörte den gespeicherten encPriv wenn der Client
denselben public_key im Update-Body mitschickte (encPriv = nil auf Zeile 532).
Fix: encPriv wird nur geleert wenn der public_key sich tatsächlich ÄNDERT.
Andernfalls (gleicher Public Key = Benutzer ändert z.B. nur Allowed-IPs)
bleibt das verschlüsselte Private Key erhalten und QR/conf-Download
bleiben verfügbar.

domains.go + domainheaders.go: UpdateHeader() ignorierte die domainID aus der
URL. Fix: Update-SQL prüft jetzt WHERE id=$4 AND domain_id=$5 — falsch
adressierte Requests kriegen 404 statt blind zu updaten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:59:13 +02:00
Debian
1aaca2c056 fix(i18n): WireGuard QR/.conf Buttons + Backend 'Upgrade gestartet' → Englisch
- Servers.tsx: 'QR' und '.conf' Button-Labels in t() überführt (wg.peer.qr/conf)
- system.go: hardkodierter deutscher API-Response-String 'Upgrade gestartet'
  → 'upgrade started' (API-Responses sollen immer Englisch sein)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:55:36 +02:00
Debian
49e3e39ea6 fix(i18n): RemoteTargets S3/SFTP-Labels + NTP-Interface-Labels vollständig lokalisieren
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>
2026-05-24 12:52:13 +02:00
Debian
bb14d2ac6c fix(i18n): hardkodierte Strings in Cluster/Users/Backups/Alerts bereinigen
- Cluster: statusTag() bekommt t-Parameter; Status-Labels (online/offline/
  joining/leaving/unknown) → cluster.status.* Keys
- Users: 'You'-Tag → t('users.you') / de: 'Ich'
- Backups: 'OK'-Status-Tag → t('backups.okTag')
- Alerts: Kanal-Kind-Labels + alle SMTP-Formular-Labels in t() überführt
- en/de: cluster.status.*, users.you, backups.okTag, alerts.smtp.*,
  alerts.kindWebhook/kindEmail ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:47:28 +02:00
Debian
6e52dc0620 fix(ui): fehlende onError-Handler + i18n in Backends/Domains/Dashboard
- Backends: create + del Mutations bekommen onError; 'Backup'-Tag → t()
- Domains: create + del Mutations bekommen onError
- Dashboard: LicenseChip-Texte ('License OK', 'Trial') in t() überführt;
  useTranslation() direkt in der Komponente verwendet
- en/de: dashboard.licenseOk/licenseTrial/licenseTrialDays ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:42:37 +02:00
Debian
ca1ee64829 fix(firewall): fehlende onError-Handler + i18n in Rules + NATRules
- 6 Mutations (create/update/del je Rules + NATRules) bekommen onError-Handler
- Deutsche Placeholder-Texte → t() überführt
- Action-Labels (accept/drop/reject) und Kind-Labels (any/object/group/cidr)
  in t() überführt statt hardkodiert anzeigen
- CIDR-Label-Hardkodierung in Rules-Formular behoben
- en/de: fw.rule.actions.*, fw.rule.kinds.*, fw.rule.namePlaceholder,
  fw.nat.namePlaceholder ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:40:08 +02:00
Debian
92eece936d fix(haproxy+unbound): http_to_https per Domain + leere Forward-Zones überspringen
haproxy: http_to_https=false Domains bekommen jetzt eigene use_backend-Regeln
im public_http-Frontend statt dem globalen HTTPS-Redirect. Das Feld war bisher
in DB + UI vorhanden aber vom Config-Generator komplett ignoriert.

unbound: Forward-Zones ohne forward-addr (ForwardTo=nil oder leer) werden jetzt
übersprungen. Unbound lehnt solche Blöcke beim Start ab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:36:53 +02:00
Debian
519522b8ee fix(i18n): hardkodierte Strings in DNS/RoutingRules/Settings/Backends bereinigen
- DNS Settings: Loopback/allIPv4/allIPv6 Interface-Labels in t() überführt
- RoutingRules: UP/DOWN-Tags + fehlende onError-Handler in create/update
- Settings: t-Variable-Shadowing in top_tables-Map behoben (t → tbl)
- Backends: 'Live'-Spaltenheader → t('backends.server.live')
- common.up/down + dns.settings.allIPv4/allIPv6/loopback in en+de ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:30:43 +02:00
Debian
e04bddd574 feat(wireguard): QR-Modal nach Peer-Erstellung automatisch öffnen
Wenn beim Anlegen eines neuen Peers ein Schlüsselpaar server-seitig
generiert wurde (has_private_key=true), öffnet sich das QR-Modal
direkt nach dem Speichern — kein manuelles Klicken nötig.

Außerdem: version-Fallback-Strings in allen drei Binaries auf 1.1.80 synct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:25:31 +02:00
Debian
e37e472960 fix(i18n): License-Status-Tags + letzte hardkodierte Strings entfernt
- statusTag() akzeptiert jetzt t als Parameter — Aktiv/Trial/
  Abgelaufen/Ungültig gehen durch license.tag* Keys
- en+de common.json: license.tagActive/tagTrial/tagExpired/tagInvalid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:20:03 +02:00
Debian
f33c1bfe4f fix(i18n): ForwardProxy ACL-Labels + Diagnostics-Output vollständig übersetzt
- ForwardProxy: ACL_TYPE_KEYS-Array + t('fwd.aclTypes.*') statt
  hartkodierter gemischter DE/EN-Labels; action-Select und
  value-Placeholder ebenso via t()
- Diagnostics: '(kein Output)' → t('diag.noOutput') — war einzige
  sichtbare deutsche Konstante außerhalb t()
- en/de common.json: fwd.aclTypes.*, fwd.actions.*, fwd.valuePlaceholder,
  diag.noOutput hinzugefügt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:16:08 +02:00
Debian
c9caf35596 feat(dashboard): HAProxy Listener-Stats + i18n-Relativzeit
- 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>
2026-05-24 12:09:53 +02:00
Debian
e18037375f fix(i18n): relative-time strings vollständig lokalisiert
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>
2026-05-24 12:02:27 +02:00
Debian
b06bb76c20 chore: bump version 1.1.75
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:47:37 +02:00
Debian
ba907f5403 chore: bump version 1.1.74
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:10:07 +02:00
Debian
ee192a1c9c chore: bump version 1.1.73
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 10:53:45 +02:00
Debian
99982ef9d1 feat(ui): Quick-Toggles, Config-Preview, Dashboard-Alerts, Domain-Detail-Health
Quick-Toggle-Switches (kein Modal nötig) für: Backends, Backend-Server,
DNS-Zonen, DNS-Records, Domains (active), Firewall-Rules, NAT-Rules,
Forward-Proxy ACLs, Routing-Rules.

Dashboard: Alert-Banner für komplett ausgefallene Backends (HAProxy-Stats)
und Domains im Maintenance-Mode.

Domain-Detail: HAProxy-Live-Health-Badge (15s Polling), TLS-Cert
ausstellen/erneuern direkt aus dem Detail, Routing-Rules-Panel inline.

Config-Preview (Settings): alle 4 Generatoren (haproxy, nftables, squid,
unbound) rendern via RenderToString ohne Disk-Write — GET /system/config-preview.

ActionButtons: Viewer-Rolle blendet Delete aus (RBAC-Ergänzung).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 08:55:22 +02:00
Debian
386972366b feat(rbac): Viewer-Rolle durchsetzen — alle Mutations nur für Admins
- middleware.go: RequireAdminForMutations() blockiert POST/PUT/DELETE
  für Nicht-Admins (GET/HEAD passieren immer durch)
- main.go: Middleware in den authed-Gruppe eingehängt — wirkt für
  alle ~30 Resource-Handler gleichzeitig
- api/client.ts: 403 → AntD-Notification "Nur Lesezugriff" statt
  stiller Fehler
- Header: "Nur lesen"-Badge + Tooltip wenn role=viewer
- i18n de+en: viewerBadge + viewerHint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 16:57:17 +02:00
Debian
4e01b4569c feat(ipv6): IPv6-Support für HAProxy + Settings-Toggle
- setup.State.IPv6Enabled + Store.SetIPv6Enabled()
- GET/POST /system/ipv6 im SystemHandler; HAProxy-Reload on save
- HAProxy-Template: bind [::]:80, [::]:443, quic6@:443, [::]:3443
  werden nur emittiert wenn IPv6Enabled=true
- haproxy.View.IPv6Enabled aus SetupStore befüllt
- Settings-UI: neues IPv6-Card (zwischen Auto-Update und Passwort)
- i18n de+en ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 16:48:26 +02:00
Debian
a490576420 feat(users): Multi-User-Management — DB-backed Login-Accounts + UI
- 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>
2026-05-23 10:22:27 +02:00
Debian
8293783fe6 feat(backends+domains): HTTP-Protokoll-Flags — force_http1 + disable_h3
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>
2026-05-22 07:18:48 +02:00
Debian
6445e162a6 fix(wg+fw): Peer-Sync-Bug via sudo-Symlink + Site-to-Site-Masquerade
- WireGuard-Peer-Änderungen landeten nicht im laufenden Interface:
  /etc/wireguard/ ist root:root 700, os.Readlink schlug fehl →
  ensureWGQuickSymlink fiel immer in den Error-Pfad. Fix: Symlink
  via sudo /bin/ln -sf (sudoers-Entry in postinst ergänzt).

- Site-to-Site-Masquerade: Roadwarrior-Clients (z. B. 192.168.99.3)
  konnten LANs hinter anderen Peers nicht erreichen, weil das remote
  Gateway die VPN-Client-IP nicht als Tunnel-Route kannte. Fix: auto
  masquerade in nftables postrouting_nat pro WireGuard-Server-Interface
  (oifname "wg7" ip saddr 192.168.99.0/24 masquerade).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:31:54 +02:00
Debian
bc5d81d966 feat(wg): Push-Routes (Client-Routes) für WireGuard-Server-Interfaces
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>
2026-05-21 13:09:51 +02:00
Debian
1f0d05019e fix(wg+fw): Startup-nftables-Render + /32-Hinweis bei Peer-AllowedIPs
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>
2026-05-21 12:34:12 +02:00
Debian
a0403b8d00 fix(firewall): WireGuard peer-to-peer forward-Chain-Auto-Rule
Die forward-chain hatte policy=drop ohne Ausnahme für WireGuard-
Peer-to-Peer-Traffic. Pakete von Peer A nach Peer B (beide am
selben wg-Interface) müssen durch die Box forwarded werden — das
war bisher stiller Drop.

Lösung: loadView liest alle aktiven server-mode WG-Interfaces und
trägt sie in View.WGServerIfaces ein. Das Template emittiert pro
Interface iifname "<wg>" accept in der forward-chain. Return-Pakete
gehen bereits via ct state established durch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:34:48 +02:00
Debian
711e9446e6 feat(domains): Domain-Detailseite + Response-Headers-Panel
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>
2026-05-21 09:04:41 +02:00
Debian
f4d178cb38 feat(firewall): nftables hit-counter pro Regel + Live-Hits-Spalte in der UI
- 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>
2026-05-21 08:42:31 +02:00
Debian
9a05e8bca8 feat(haproxy): req_tot + req_rate in Stats-API; Backend-Detail + Dashboard nutzen sie
GET /haproxy/stats liefert jetzt req_tot (kumulierte Requests seit
HAProxy-Start) und req_rate (Requests/s im letzten Messfenster).
Backend-Detail zeigt in der Live-Spalte 'N sess · X/s' wenn Traffic
fließt; Dashboard-HAProxy-Card zeigt req/s ebenfalls an.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:03:54 +02:00
Debian
95583e9657 fix(ui): Domain-Sync + HAProxy-Live-Stats in Backend-Detail
- Domain-Attachments werden beim Speichern korrekt sync't
  (syncDomainAttachments war in Detail.tsx vergessen worden)
- Server-Tabelle in der Detail-Seite zeigt jetzt eine 'Live'-Spalte
  mit HAProxy-Status (UP/DOWN), aktive Sessions und Bytes per Hover-
  Tooltip; Daten kommen aus dem gemeinsamen ['haproxy','stats']-Query

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 21:40:55 +02:00
Debian
7fab17c231 feat(ui): Backend-Edit als dedizierte Seite statt Modal
/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>
2026-05-20 21:17:47 +02:00
Debian
bd6e67f059 feat(networks): Live-Traffic-Zähler in System-Interfaces-Card
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>
2026-05-20 14:09:37 +02:00
Debian
e4b66cfcac feat(ui): traffic tooltip (sess/bytes) auf Backend-Health-Chips
Backends- und Routing-Rules-Seite zeigen jetzt beim Hover auf den
UP/DOWN-Chip die aggregierten HAProxy-Stats des jeweiligen Backends:
aktive Sessions + kumulierte Bytes rein/raus seit letztem HAProxy-Start.
Daten kommen aus dem bereits vorhandenen /haproxy/stats-Endpunkt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 07:21:16 +02:00
Debian
20f3a4fe26 feat(ui): HAProxy-Health-Chip in Routing-Rules-Tabelle
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 16:22:15 +02:00
Debian
35b7308ce2 feat: umfangreiches UI+API-Polish (v1.1.36–1.1.42)
Backend:
- Audit-Log: Search-Endpoint mit ILIKE-Filter (actor/action/subject/date)
- NTP: /ntp/status via chronyc tracking (Stratum, Offset, Quelle)
- System: /service-restart mit Allowlist (haproxy/squid/unbound/chrony/scheduler)
- Domain-Response-Headers + Rate-Limit (Migration 0024)
- Join-Tokens (Migration 0025), Cluster-mTLS, Aggregator-Fan-Out
- apt-Service für Update-Banner (apt-get update + Versionsprüfung)
- Backup-Retry mit exponential backoff (retry_apt 3×)
- publish.sh fail-fast + cleanup-old.sh (max 10 Versionen)

Frontend:
- Audit-Log-Page (/audit) mit Filter + Pagination
- ErrorBoundary an React-Root + Vite build-target festgenagelt (iOS 15+)
- Storage-Schema-Stamp: auto-wipe bei Versions-Mismatch (blank-page-Fix)
- EmptyState-Komponente überall ausgerollt
- SSL: Aggregate-Karte (total/expiring/expired/errors)
- Backups: Aggregate-Karte (letzter Backup/Größe/Fehlschläge 24h) + Backup-Now
- NTP: Sync-Status-Karte (chronyc tracking live)
- Domains: Backend-UP/DOWN-Chip aus HAProxy-Stats
- Backends: HAProxy-Status-Spalte (UP/DEGRADED/DOWN)
- Settings: Service-Neustart-Karte (haproxy/squid/unbound/chrony/scheduler)
- Settings: Upgrade-Status-Card, Wartungsmodus, Auto-Update, Retention
- Dashboard: Recent-Alerts, Cluster-Health, License-Chip, Onboarding-Hint
- System-Regeln im Firewall als eigener Tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 16:18:41 +02:00
Debian
3178e25e78 feat(haproxy): X-Forwarded-Proto + X-Real-IP an alle Backends weiterleiten
User-Frage: „Werden via haproxy die echten IPs durchgereicht?". Antwort:
X-Forwarded-For ja (option forwardfor), aber Apps wie WordPress/Mailcow
brauchen zusätzlich X-Forwarded-Proto=https um Redirect-Loops zu
vermeiden, und X-Real-IP ist die bequeme single-value-Variante die viele
Tools out-of-the-box lesen (ohne die XFF-Chain parsen zu müssen).

Beide Frontends (public_https + mgmt_https) emittieren jetzt:
  http-request set-header X-Forwarded-Proto https
  http-request set-header X-Real-IP %[src]

Was Backends sehen:
  X-Forwarded-For:  <client-ip>             (defaults: option forwardfor)
  X-Forwarded-Proto: https                  (NEW)
  X-Real-IP:        <client-ip>             (NEW, single value)

PROXY-Protocol-Toggle pro Backend kommt nicht in diesem Release — der
Operator hat „nur Header-Variante" gewählt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:28:41 +02:00
Debian
a2d08eaa47 fix(wireguard): off-by-one im wg-show-Parser — UI zeigte verbundene
Peers als „offline"

Bug-Report: Unify-Home-Tunnel ist aktiv (handshake live, 2.8 GB rx),
UI zeigte „Letzter Handshake: nie / Traffic: — / Offline".

Root cause: handlers/wireguard.go Status() parsed `wg show all dump`
mit `PeerPublicKey: fields[2]` — fields[2] ist aber der pre-shared
key (PSK), NICHT der peer_pubkey. Format mit "all"-prefix ist:

  iface lines (5 cols):  iface priv_key pubkey listen_port fwmark
  peer  lines (9 cols):  iface peer_pubkey psk endpoint allowed_ips
                         last_hs rx tx keepalive

Damit matched die UI gegen den PSK → DB-Peer-Row (gespeichert per
pubkey) traf nie zu, Status blieb „nie/—" auch bei aktivem Tunnel.

Fix: fields[1] statt fields[2]. Endpoint/AllowedIPs/Handshake/RX/TX
waren bereits korrekt verschoben.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 19:09:38 +02:00
Debian
c79bfe84ec feat(auth): Self-Service-Admin-Password-Reset via CLI-Token
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>
2026-05-13 19:04:25 +02:00
Debian
27ac7b53fc feat(backup): Off-Site-Upload nach S3 + SFTP
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>
2026-05-13 18:49:02 +02:00
Debian
81a8217493 feat(alerts): Health-Alarme via Webhook + Email-SMTP
Sidebar → System → Alarme.

Migration 0021: alert_channels (kind=webhook|email, target, settings,
active) + alert_events (kind, severity=info/warning/error/critical,
subject, message, sent_to JSONB).

internal/services/alerts/:
  - Fire(kind, severity, subject, message) — broadcastet an alle
    aktiven Channels + persistiert Event mit per-Channel-Result
    (ok/error) in sent_to.
  - Webhook-Sender: POST JSON {kind, severity, subject, message,
    content, text, fired_at}. Slack/Discord/Teams akzeptieren das
    out-of-the-box ohne Adapter (content + text-Felder gleichzeitig).
  - Email-Sender: net/smtp + STARTTLS optional. Settings (smtp_host,
    smtp_port, username/password, from, use_tls) liegen in
    channel.settings JSONB.

internal/handlers/alerts.go: CRUD + POST /alerts/test + GET
/alerts/events (history).

Scheduler-Trigger:
  - cert.expiring  — TLS-Cert <14 Tage Restzeit (12h-dedupe pro cert)
                     severity warning, <3 Tage → error
  - cert.renew_failed       — Renewer-Cycle hat fails
  - cert.renewer.run_failed — Renewer-Cycle abgebrochen
  - backup.failed  — Scheduled Backup error
  - license.invalid — License-Server liefert valid=false

In-process Dedupe (12h TTL, map[key]time.Time) verhindert dass
identische Alerts in Schleifen feuern.

UI (pages/Alerts): Tabs Channels (CRUD-Tabelle, Add-Modal mit
conditional-Email-Fields) + History (200 letzte Events mit
severity-Tag + per-Channel-Delivery-Status). Header-Button
„Test-Alert" feuert einen Test-Event in alle aktiven Channels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:57:05 +02:00
Debian
4a34629023 feat(audit): Live-Stream im Dashboard via WebSocket
Recent-Activity-Karte zeigt neue audit_log-Events jetzt sofort statt
in 15s-Polls.

internal/services/audit/audit.go:
  - Repo bekommt Subscribe()-Methode mit fan-out-channel (Buffer 32,
    non-blocking-send — langsame Clients droppen Events statt die
    Pipeline zu blockieren).
  - Log() macht jetzt INSERT … RETURNING id, created_at und broadcastet
    den fertigen Entry an alle Subscribers. Broadcast nur nach
    erfolgreichem INSERT — failed inserts erscheinen nicht.

internal/handlers/audit.go:
  - Neuer GET /api/v1/audit/live (WebSocket): sendet beim Connect die
    letzten 50 Einträge (oldest→newest), danach Live-Stream aus
    Subscribe-Channel. 30s-Ping gegen HAProxy-Tunnel-Timeout.
  - Recent (Poll-Endpoint) bleibt für Fallbacks erhalten.

UI Dashboard:
  - useAuditLive(keep=15)-Hook ersetzt das 15s-useQuery-Poll.
  - WebSocket auf wss://<host>/api/v1/audit/live; Auto-Reconnect alle
    2s nach Drop.
  - dedupe per id (Snapshot + erste live-Events können sich kurz
    überschneiden während des Subscribe-Race).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:39:04 +02:00
Debian
5bdea1bced feat(diagnostics): UI-Tools — ping/traceroute/dig/curl/tcp
Operator-Diagnose ohne SSH. Sidebar → System → Diagnose.

internal/services/diagnostics/:
  - Ping        — ping -c4 -W2 (12s timeout)
  - Traceroute  — traceroute -n -w2 -q1 -m20 (60s timeout)
  - Dig         — dig +timeout=3 +tries=2 <TYPE> <NAME> (Types A/AAAA/
                  CNAME/MX/TXT/NS/SOA/PTR/SRV/CAA whitelisted)
  - Curl        — curl -IsSv --max-time 10 (nur http(s)://, kein
                  file:// / smb:// / data://)
  - TCPProbe    — nc -zv -w5 (8s timeout)

Sicherheit: validTarget() prüft jeden Input gegen Allow-List
[a-zA-Z0-9.:/_-]; verhindert Shell-Metachar-Injection. exec.Command
mit nackten Argument-Slices (kein /bin/sh, kein Glob-Expansion).

internal/handlers/diagnostics.go: POST /api/v1/diagnostics/<tool>
hinter requireAuth.

UI (pages/Diagnostics): 5 Tool-Cards, jede mit eigenem Input + Run-
Button + monospace-Output-Pane (dunkel, scrollbar, max-height 320px).
Pro Tool ein Status-Tag (OK / exit N) + Dauer-ms. info-Alert oben
erklärt dass Tools auf der Box laufen, nicht im Browser; security-
Alert unten erklärt die Restrictions.

control: iputils-ping, traceroute, dnsutils, curl, netcat-openbsd
als Depends. Auf Test-Box bereits da (waren Distro-defaults).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 15:30:07 +02:00
Debian
e07b484a48 feat(cluster): Config-Hash-Compute für Drift-Detection
Setzt die Foundation aus 1.0.70 fort — bisher war ha_nodes.config_hash
noch NULL und das UI konnte keinen Drift erkennen.

internal/cluster/confighash.go:
  - ComputeConfigHash() berechnet SHA-256 (truncated auf 16 hex chars)
    über alle replizierbaren Tabellen. Pattern 1:1 aus mail-gateway/
    internal/handlers/cluster_status.go (driftHashSpec).
  - Pro Tabelle: md5((to_jsonb(t) - id - updated_at - created_at -
    excludes)::text) per row, dann string_agg ORDER BY rh.
  - Singleton-Tabellen (dns_settings, ntp_settings, mail_config-Stil)
    hashen direkt ohne agg.
  - 23 Tabellen: domains, backends, backend_servers, routing_rules,
    network_interfaces, ip_addresses, tls_certs (mit ExtraExclude
    last_renewed_at + last_error damit cert-renewal keinen drift
    erzeugt), firewall_zones+address_objects+address_groups+services+
    service_groups+rules+nat_rules, wireguard_interfaces+peers,
    forward_proxy_acls, dns_zones+records+settings, ntp_pools+settings,
    static_routes.
  - RefreshLocalHash() schreibt den Hash in die eigene ha_nodes-Row.

Scheduler:
  - 5-min-Tick ruft RefreshLocalHash. Pro-Mutation-Refresh wäre zu
    teuer (jede UI-Action triggert sonst 23 jsonb-Queries).
  - Initial-Refresh beim Scheduler-Boot damit /cluster/status nicht
    5 min auf den ersten Wert wartet.

handlers/cluster.go:
  - Status() ruft RefreshLocalHash mit 2s-Timeout on-demand. Damit
    sieht das UI auch zwischen den Scheduler-Ticks immer frische
    Werte; bei Timeout fallback auf den DB-Wert (eventuell stale).

Verifiziert auf 1.0.71: ha_nodes-Row hat config_hash=728834dce5ca4e48,
scheduler-log "config-hash refresh enabled tick=5m0s".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:33:42 +02:00