Commit Graph

16 Commits

Author SHA1 Message Date
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
ea7c356455 feat(cluster): Phase 3 Foundation — node.conf + ha_nodes-Drift + UI
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>
2026-05-13 08:27:00 +02:00
Debian
b031725dfe feat(routes): Static-Routes-Management + Live-View (Networks-Tab)
Migration 0019: static_routes (id, destination, gateway, dev, metric,
table_name, active, comment).

internal/services/staticroutes/:
  - CRUD-Repo
  - Generator schreibt /etc/edgeguard/routes.conf (pipe-format) und
    triggert `sudo systemctl restart edgeguard-routes.service`
  - LiveAll() ruft `ip -j route show table all` und parsed JSON

internal/handlers/routes.go:
  GET /api/v1/routes           — managed (DB)
  POST/PUT/DELETE              — CRUD (re-render + apply on mutate)
  GET /api/v1/routes/live      — kernel-state via ip(8)

postinst:
  - /usr/sbin/edgeguard-apply-routes (root-owned shell-script). Liest
    routes.conf, flusht `proto 250` (= edgeguard), setzt neue Routen
    mit proto 250. Andere Quellen (kernel/dhcp/manuell) bleiben
    unangetastet.
  - /etc/systemd/system/edgeguard-routes.service (Type=oneshot,
    After=network-online.target). Beim Boot automatisch via
    multi-user.target.
  - /etc/iproute2/rt_protos.d/edgeguard.conf — Symbol "edgeguard" =
    250 damit `ip route show proto edgeguard` funktioniert.
    (Debian 13 hat kein /etc/iproute2 default → .d-Pattern statt
    rt_protos-Anhängen.)
  - sudoers: edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl
    restart edgeguard-routes.service

UI: Networks-Page jetzt mit Tabs (Interfaces + Routen). Routes-Tab
hat zwei Cards:
  - Live-Routen (read-only, 30s refresh, `proto edgeguard` farblich
    hervorgehoben)
  - Verwaltete Routen (CRUD-Tabelle, Add/Edit-Modal mit destination/
    gateway/dev/metric/table/active/comment)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:50:26 +02:00
Debian
571f51ba9a feat(backup): pg_dump + state-tarball + daily auto + UI
Production-Box braucht Backups — bisher keine. Jetzt komplette
Pipeline:

Backend (internal/services/backup/):
  - Output: /var/backups/edgeguard/eg-YYYYMMDD-HHMMSS.tar.gz
  - Inhalt: dump.sql (pg_dump --clean --if-exists --no-owner --no-acl),
    files/setup.json, files/license_key, files/license.cache,
    files/.jwt_fingerprint, files/node.conf, files/acme-account/* +
    manifest.json (Version, kind, hostname, sizes)
  - sha256 während-write via TeeWriter, Size + sha in backups-DB-Row
  - Failure-Path: row mit status=failed + error, kein orphan-tarball
  - Prune(keepN=14) löscht erfolgreiche Backups älter als die letzten N

Migration 0018: backups(id, file, size, sha256, db/files bytes, kind,
status, error, host, started/finished).

Scheduler (cmd/edgeguard-scheduler):
  - 24h-Tick → backup.Run(KindScheduled) + Prune. Beim Boot wird ein
    initialer Backup NICHT sofort gezogen (kein nervöses Spam),
    sondern erst beim nächsten 24h-Slot.

REST (internal/handlers/backup.go):
  GET    /api/v1/backups              — list (newest first)
  POST   /api/v1/backups              — trigger manual (sync, audit'ed)
  GET    /api/v1/backups/:id          — single
  GET    /api/v1/backups/:id/download — sendfile tar.gz
  DELETE /api/v1/backups/:id          — entferne file + row

UI (management-ui/src/pages/Backups):
  - Liste mit Time, File+sha (first 16), Kind-Tag, Status, Size (mit
    DB + Files Aufschlüsselung), Dauer
  - „Backup jetzt erstellen" Button, Refresh, Download, Delete
  - Auto-Refresh 30s
  - Sidebar-Eintrag „Backups" unter System

postinst:
  - /var/backups/edgeguard 0750 edgeguard:edgeguard (enthält sensitive
    pg_dump + license_key → NICHT world-readable)
  - sudoers-Whitelist `sudo -u postgres /usr/bin/pg_dump --clean
    --if-exists --no-owner --no-acl edgeguard` — exakte Form

Verifiziert auf der Box: backups-Tabelle existiert, scheduler logged
„backup enabled tick=24h dir=/var/backups/edgeguard keep_n=14",
pg_dump-via-sudoers liefert 2808 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:08:18 +02:00
Debian
26f321de9d feat(backends): WebSocket-Toggle pro Backend
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>
2026-05-11 21:51:09 +02:00
Debian
8aac24b566 feat(backends): Pool-Modell — Backend = Pool, N Server pro Backend
Migration 0016: backend_servers (id, backend_id, name, address, port,
weight, backup, active) + backends.lb_algorithm. Daten-Migration kopiert
bestehende backends.address/port als ersten Server, dann DROP COLUMN.

HAProxy-Renderer: rendert pro Backend einen Block mit `balance <algo>`
+ N `server`-Zeilen (weight, backup-Flag, optional check inter 5s).
LB-Algorithmen: roundrobin / leastconn / source.

REST: /backends/:id/servers (GET/POST), /backend-servers/:id (PUT/DELETE).
Re-rendert HAProxy nach jeder Server-Mutation.

UI: address/port aus Backend-Form raus, lb_algorithm-Select rein. Server
verwaltet ein expandable Sub-Panel pro Backend-Row (Tabelle + Add/Edit/
Delete-Modal). Domain-Attachment-Multi-Select bleibt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:55:47 +02:00
Debian
e4d83d226e feat: NTP-Server (Chrony) — vollständig
Stub raus, vollständige Implementierung analog Unbound/Squid:

* Migration 0015: ntp_settings (single-row mit listen_addresses,
  allow_acl, serve_clients, makestep, rtcsync) + ntp_pools (kind
  pool|server, address, iburst/prefer, minpoll/maxpoll). Default
  4 deutsche pool.ntp.org-Server seeded.
* Models DNSSettings/NTPPool, services/ntp Repo, handlers/ntp.go
  REST /api/v1/ntp/{settings,pools} mit Auto-Restart nach Mutation.
* internal/chrony/chrony.cfg.tpl + chrony.go: Renderer schreibt
  /etc/chrony/conf.d/edgeguard.conf direkt (analog unbound — distro
  chrony.conf included conf.d automatisch). Listen-bind nur wenn
  serve_clients=true; sonst port 0 (= Client-only).
* main.go: ntpRepo + chronyReloader injiziert.
* render.go: chrony als sechste generator.
* postinst:
  - chrony als hard Depends im control file.
  - Conf-Datei /etc/chrony/conf.d/edgeguard.conf wird als
    edgeguard:edgeguard 0644 angelegt.
  - Sudoers für systemctl reload + restart chrony.
* Auto-FW-Rule-Generator: udp/123 wenn serve_clients=true und
  listen_addresses non-loopback enthält.
* Frontend /ntp: PageHeader + Quellen-Tab + Settings-Tab. Listen-
  Addresses als Multi-Select aus Kernel-IPs (analog DNS).
* Sidebar-Eintrag unter Network.
* i18n DE/EN für ntp.* Block.

chrony.service hat kein 'reload' — Renderer ruft RestartService auf.

Verified: 4 default-pool-server connected (chronyc sources zeigt
sie nach erstem render).

Version 1.0.40.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 06:58:54 +02:00
Debian
e537d70e04 feat: Unbound DNS-Resolver — vollständig (Renderer + Handler + UI)
Stub raus, vollständig implementiert:

* Migration 0014: dns_settings (single-row) + dns_zones.forward_to.
  Default-Settings sind sinnvoll für die typische LAN-Resolver-Rolle
  (1.1.1.1 + 9.9.9.9 upstream, localnet allow, DNSSEC + qname-min on).
* internal/services/dns: CRUD-Repo für zones, records, settings.
* internal/handlers/dns.go: REST /api/v1/dns/zones, /records, /settings
  mit Auto-Reload nach jeder Mutation.
* internal/unbound/unbound.cfg.tpl + unbound.go: Renderer schreibt
  /etc/unbound/unbound.conf.d/edgeguard.conf direkt (kein Symlink-
  Dance, weil AppArmor unbound nur /etc/unbound erlaubt). Local-zones
  authoritativ aus dns_records; forward-zones per stub-zone; default-
  forwarders catchen alles sonst.
* main.go: dnsRepo + unbound-Reloader injiziert.
* render.go: unbound.New() bekommt Pool.
* postinst:
  - Conf-Datei /etc/unbound/unbound.conf.d/edgeguard.conf wird als
    edgeguard:edgeguard 0644 angelegt damit Renderer schreiben kann.
  - /etc/edgeguard + Service-Subdirs auf 0755 (Squid + Unbound laufen
    NICHT als edgeguard, brauchen Read-Traversal).
  - Sudoers: systemctl reload unbound.service whitelisted.
* Template: chroot:"" (Conf liegt außerhalb /var/lib/unbound default-
  chroot), DNSSEC-Trust-Anchor NICHT setzen (Distro hat schon
  root-auto-trust-anchor-file.conf — sonst doppelter Anchor → start
  failure).
* Frontend /dns: PageHeader + zwei Tabs (Zones + Resolver-Settings).
  Zones-Tab mit Drawer für Records (CRUD pro Zone, A/AAAA/CNAME/TXT/
  MX/SRV/NS/PTR/CAA). Sidebar-Eintrag unter Network.
* i18n DE/EN für dns.* Block.

Verified end-to-end: render → unbound restart → dig @127.0.0.1
example.com → 104.20.23.154 / 172.66.147.243.

Version 1.0.34 (mehrere Iterationen wegen AppArmor + chroot + perms).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 06:24:51 +02:00
Debian
85904d0c36 feat: WireGuard (server + client + peers + QR) + shared UI components
WireGuard
---------
* Migration 0013: wireguard_interfaces (server|client mode, key envelope-
  encrypted) + wireguard_peers (per-server roster). Drop old empty
  0005-Schema (Option-A peer_type, kein Iface-FK), neuer Aufbau mit
  zwei Tabellen + FK.
* internal/services/secrets: Box mit AES-256-GCM, Master-Key in
  /var/lib/edgeguard/.master_key (lazy-create, 0600). Sealed/Open
  für PrivateKey + PSK.
* internal/services/wireguard: KeyGen (Curve25519 mit clamping),
  PublicFromPrivate (für Import), InterfacesRepo, PeersRepo, Importer
  (parst /etc/wireguard/*.conf, server vs. client heuristisch nach
  ListenPort + Peer-Anzahl).
* internal/wireguard: Renderer schreibt /etc/edgeguard/wireguard/<iface>.conf
  (0600), restartet wg-quick@<iface> via sudo (sudoers im postinst
  erweitert). Idempotent — re-render nur wenn content geändert.
* internal/handlers/wireguard.go: REST CRUD für interfaces+peers,
  /generate-keypair, /peers/:id/config (text/plain wg-quick conf),
  /peers/:id/qr (PNG via go-qrcode). Auto-reload nach Mutation.
* edgeguard-ctl wg-import [--path /etc/wireguard]: liest existierende
  conf-Files in die DB. Idempotent (überspringt vorhandene Iface-Namen).

Shared UI components (proxy-lb-waf design pattern)
--------------------------------------------------
* PageHeader: icon + title + subtitle + extras row, einheitlich oben
  auf jeder Page.
* ActionButtons: Edit + Delete combo mit Popconfirm + Tooltip.
* StatusDot: AntD Badge pattern statt "Yes/No" — schneller scanbar
  in dichten Tabellen.
* DataTable: pageSizeOptions [20,50,100,200] + extraActions-Alias +
  optional renderMobileCard für Card-Liste auf < md Breakpoint.
* enterprise.css: .page-header* + .datatable-toolbar Klassen.

Frontend WireGuard
------------------
* /vpn/wireguard mit zwei Tabs (Server / Client) im neuen Pattern.
* Server-Tab: Modal mit Generate-Keypair-Toggle, Peer-Roster im
  Drawer per Server. Pro Peer: QR-Code-Modal + .conf-Download.
* Client-Tab: Upstream-Card im Modal, full-tunnel-Default
  (0.0.0.0/0,::/0), Keepalive 25.
* i18n DE/EN für wg.* Block + common.* Erweiterung.

Misc
----
* Sidebar: WireGuard unter Security-Sektion.
* Nav-i18n: "Firewall (v2)" → "Firewall".
* Version 1.0.8 → 1.0.11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:51:25 +02:00
Debian
51ea1fc802 feat: Zonen als first-class Entity + Domain↔Backend-Verknüpfung sichtbar
* Migration 0012: firewall_zones (id, name UNIQUE, description, builtin),
  Seed wan/lan/dmz/mgmt/cluster als builtin. CHECK-Constraints auf
  network_interfaces.role + firewall_rules.{src,dst}_zone +
  firewall_nat_rules.{in,out}_zone gedroppt — Validation lebt jetzt
  app-side (Handler prüft Existenz in firewall_zones).
* Backend: firewall.ZonesRepo (CRUD + Exists + References-Lookup),
  /api/v1/firewall/zones, builtin geschützt (Name nicht änderbar,
  Delete blockiert), Rename eines Custom-Zone aktuell ohne Cascade
  (Handler-Sorge bei Rules/NAT/Networks).
* Handler-Validation in CreateRule/UpdateRule/CreateNAT/UpdateNAT +
  NetworksHandler: Zone-Existence-Check pro Mutation, 400 bei Tippfehler.
* Frontend: Firewall-Tab "Zonen" (CRUD mit builtin-Schutz). Networks-
  Form lädt Rollen aus /firewall/zones (statt hardcoded Liste); Rules-
  und NAT-Forms ziehen die Zone-Auswahl ebenfalls aus der API.
* Domain-Form bekommt Primary-Backend-Picker (Field war im Modell,
  fehlte im UI). Backends-Tabelle zeigt umgekehrt welche Domains
  darauf zeigen — bidirektionale Sicht ohne Schemaänderung.
* HAProxy-Renderer: safeID-FuncMap escaped Server-Namen mit Whitespace
  ("Control Master 1" → "Control_Master_1"). Vorher ist haproxy beim
  Reload an Spaces im Backend-Namen kaputt gegangen.
* Version 1.0.3 → 1.0.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:05:27 +02:00
Debian
aa14b6b2be feat: Networks-Members für bridge/bond + System-Rules-Card + Theme-Revert
* Migration 0011: members JSONB für network_interfaces. Bridge/bond
  brauchen ≥1 Member (NOT VALID-Constraint, schont bestehende Rows).
  vlan/wireguard/ethernet ignorieren das Feld.
* Backend-Validation pro Typ: vlan→parent+vlan_id, bridge/bond→members,
  ethernet/wireguard→keins. Repo serialisiert via JSONB.
* Form Networks: Members-Multi-Select für bridge/bond, Composition-
  Spalte zeigt vlan-tag bzw. Member-Liste.
* Firewall-Rules-Tab zeigt jetzt SystemRulesCard ganz oben — Anti-
  Lockout (SSH/443), stateful baseline, default-deny-Erklärung.
* Theme-Tokens 1:1 mail-gateway: fontSize 13, controlHeight 34
  (vorher zu dichtes 12/28). Density kommt vom DataTable size="small".
* Makefile publish-amd64 lädt jetzt auch edgeguard-ui_*_all.deb und
  edgeguard_*_all.deb hoch (vorher nur api).
* Version 1.0.0 → 1.0.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:19:07 +02:00
Debian
e517783c42 feat(fw): Migration 0010 — Firewall-v2-Schema (Fortigate-Style)
Phase-1 firewall_rules (chain/match_expr raw nft) → Fortigate-Niveau:
* firewall_address_objects (host/network/range/fqdn)
* firewall_address_groups + members junction
* firewall_services (proto+port range, builtin-Flag)
* firewall_service_groups + members junction
* firewall_rules komplett umgebaut: src_zone+addr/group/cidr,
  dst_zone+addr/group/cidr, service_object_id ODER service_group_id,
  action accept|drop|reject, log-Flag, priority+enabled
* firewall_nat_rules (kind=dnat|snat|masquerade) als separate Tabelle

Zonen kommen aus network_interfaces.role (wan|lan|dmz|mgmt|cluster
+ pseudo-Zone 'any').

Builtin-Inserts: 18 Standard-Services (HTTP/HTTPS/SSH/DNS/SMTP-Familie/
DBs/RDP/WireGuard/Ping) plus 5 Service-Groups (Web, Mail-Submit,
Mail-Receive, DNS, Ping).

Renderer (internal/firewall/firewall.go) lässt firewall_rules-Query
für jetzt aus — Template fällt auf baseline + cluster-peer-set
zurück. Volle Render-Logik mit den neuen Joins kommt mit Task #44.

Models + Repos + Handlers + Frontend folgen in den nächsten Commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:52:01 +02:00
Debian
ca03e69637 feat: Network/IP-Verwaltung + Mailguard-Design-Übernahme
Backend:
* Migration 0009_networks: network_interfaces (ethernet|vlan|bond|
  bridge|wireguard, role wan|lan|dmz|mgmt|cluster, parent + vlan_id
  für VLANs) + ip_addresses (interface_id FK, address+prefix, is_vip
  + vip_priority für Cluster-Failover-VIPs).
* Repos services/networkifs + services/ipaddresses + Models +
  Handler /api/v1/network-interfaces (CRUD + /:id/ip-addresses)
  und /api/v1/ip-addresses (CRUD).
* /api/v1/system/interfaces refactored auf Go-natives net.Interfaces()
  statt `ip -j addr show` shell-out — die systemd-Sandbox blockt
  AF_NETLINK auch für Go's runtime, deswegen edgeguard-api.service
  RestrictAddressFamilies um AF_NETLINK ergänzt. Output-Shape
  bleibt identisch (ifindex, ifname, flags[], mtu, link_type,
  address, addr_info[]) — Frontend muss nicht angepasst werden.

Frontend:
* Networks-Page (/networks): "System-discovered Interfaces"
  read-only Tags-Card oben, deklarierte Interfaces unten als
  Tabelle mit Modal-CRUD; Type-Switch zeigt parent+vlan_id-Felder
  bei type=vlan; Role-Tags farbig (wan blau, lan grün, dmz orange,
  mgmt purple, cluster magenta).
* IPAddresses-Page (/ip-addresses): Tabelle pro Interface, VIP-
  Toggle blendet vip_priority-Eingabe ein. Goldenes VIP-Tag in der
  Liste.
* Sidebar erweitert um Networks + IP-Adressen + section-grouping.

Design 1:1 von mail-gateway/management-ui/ übernommen:
* enterprise.css verbatim (Inter-Font via Google CDN statt local
  woff2), Sidebar 240px dunkler Gradient #0B1426→#101D33→#0D1829,
  branding-accent #1677ff für Active-State, abgerundete Cards mit
  shadow-Token, Header weiß mit subtilem backdrop-filter.
* AntD-Theme-Tokens: colorPrimary #0EA5E9, fontSize 13, fontFamily
  'Inter', controlHeight 34, borderRadius 6.
* Layout-Komponenten neu strukturiert: AppLayout/Sidebar/Header
  matchen mailguard-Klassen-Naming (.app-layout, .main-content,
  .sidebar-section, .sidebar-menu-item.active, .header-left, …).
* Sidebar mit 4 Sektionen (Übersicht / Routing / Netzwerk / System)
  + Logo-Header + Versions-Footer.

Live-deployed auf 89.163.205.6: Networks-Endpoint listet eth0
(89.163.205.6/24, MAC bc:24:11:64:29:e8) + lo, frontend zeigt sie
als System-Tags in der Networks-Page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:08:44 +02:00
Debian
b307a7b1f7 feat(db): Phase 1 — DB-Schema, goose-Migrations, GORM-Models
Initialer Schema-Set (8 Migrationen, 13 Tabellen) für EdgeGuard v1:
users + audit_log + system_settings, ha_nodes, backends/domains/
routing_rules/tls_certs, forward_proxy_acls, wireguard_peers,
firewall_rules, dns_zones/dns_records, licenses. Migrations liegen
in internal/database/migrations/ (analog mail-gateway) und werden
per //go:embed ins Binary gepackt — keine separate SQL-Dateien im
.deb. ValidateMigrations + Test schützen vor Duplicate-Versionen
(mail-gateway 2026-05-08-Vorfall). GORM-Models für alle Tabellen,
sensible Felder (password_hash, private_key_enc) sind json:"-".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 23:44:44 +02:00
Felix Netzel
0ceab4c814 chore: initial skeleton
- docs/architecture.md: native rewrite plan (5 services + control plane,
  Active-Active cluster like nmg, Floating-IP for HTTP ingress)
- cmd/edgeguard-{api,scheduler,ctl}: minimal Gin + CLI stubs
- packaging/debian/edgeguard-{api,ui,meta}: control + maintainer scripts
- deploy/systemd/edgeguard-api.service + edgeguard-scheduler.service
  with hardening defaults
- Makefile: build / cross-compile (amd64+arm64) / deb / publish targets
- scripts/install.sh + scripts/apt-repo/build-package.sh stubs
2026-05-08 18:45:41 +02:00