Backend:
- Go-Toolchain 1.26.4/1.26.6 -> 1.27.1
- alle 9 veralteten direkten Module auf latest (gin 1.10->1.12,
pgx 5.9->5.11, go-oidc 3.18->3.21, minio 7.1->7.3, goose 3.27->3.28,
haproxy-go 0.0.8->0.1.1, sftp, x/crypto 0.57, x/oauth2 0.37)
- quic-go 0.59.0 -> 0.59.1: GO-2026-5676 (HTTP/3 QPACK Trailer Memory
Exhaustion) kam mit dem Modul-Update rein und wurde vom govulncheck-
Gate gefangen.
Toolchain-Falle im Makefile (der eigentliche Knackpunkt): golangci-lint
und govulncheck verweigern die Arbeit, sobald go.mod eine neuere Go-Version
zielt als die, mit der sie selbst gebaut wurden — govulncheck meldete dann
"package requires newer Go version" fuer JEDES Paket, statt zu scannen. Ein
gruenes Ergebnis waere hier also nicht "keine Lücken", sondern "gar nicht
geprüft" gewesen. Beide Targets pinnen jetzt GOTOOLCHAIN auf die go.mod-
Version und installieren neu, sobald die Build-Go-Version der vorhandenen
Binary abweicht. Der bisherige `command -v`-Check hat eine veraltete Binary
nie erneuert.
Frontend: antd 6.3->6.6.3, React 19.2->19.3, axios 1.16->1.20,
tanstack-query 5.100->5.102, react-router 7.15->7.18, zustand, icons,
i18next 25->26, react-i18next 16->17, eslint 9->10.
TypeScript bewusst auf 6.0.3 statt 7.0.2: typescript-eslint unterstuetzt
auch in der neuesten Release (8.70.0) nur `typescript <6.1.0`. Mit TS 7
baut das UI zwar, aber ESLint bricht komplett ab ("typescript-eslint does
not support TS 7.0") — das Frontend-Linting waere damit still weg. 6.0.3
ist die neueste Version innerhalb der unterstuetzten Range.
Co-Authored-By: Claude Opus 5 <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: Service-Health-Grid hat 'nftables.service' per systemctl
abgefragt. Distro-Unit ist disabled (wir laden via 'nft -f' aus
dem Renderer) → Dashboard zeigte FW als 'inactive', obwohl Pakete
sehr wohl gefiltert werden.
Fix: Special-case in /system/services für unit='nftables'. Status
= existiert 'table inet edgeguard' im Kernel-Ruleset (sudo nft list
tables). 'kernel-loaded' wenn ja, 'no-table' wenn nein.
Plus: sudoers im postinst erweitert um 'nft list tables' + 'nft list
table inet edgeguard'.
Version 1.0.44.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: Renderer hat record.Name 1:1 ins local-data übernommen.
Bei Apex-Records (Operator gibt '@' oder leer ein um die Zone selbst
zu adressieren) kam '@.' raus statt der Zone-FQDN — unbound parsed
das als FQDN '@', was funktional tot ist.
Fix: resolveFQDN(recName, zoneName):
'@' / leer → zone + '.'
endet mit . → as-is
endet mit zone-suffix → name + '.'
sonst → name + '.' + zone + '.'
Renderer baut recordView{DNSRecord, FQDN} pro record.
Test: zone proxy.resdom.loc + record name='@' value='10.10.20.1'
$ dig @10.10.20.1 +short proxy.resdom.loc
10.10.20.1
Auch wenn der Operator 'proxy.resdom.loc' als Name eingibt
(absoluter FQDN), 'mailcow' (relativ), oder 'mailcow.proxy.resdom.loc.'
(absolut mit Punkt) — alle drei expandieren korrekt.
Version 1.0.42.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: Wenn der Operator eine NTP/DNS/Squid/WG-Mutation gemacht hat,
wurde nur der Service neu konfiguriert + reloadet — die Auto-FW-
Rules (udp/123, udp/tcp 53, tcp 3128, udp/<wg-port>) blieben aber
auf dem Stand des letzten firewall-renders. Operator musste manuell
'edgeguard-ctl render-config --only=nftables' fahren.
Fix: withFW-Wrapper in main.go der nach jedem Service-Reloader auch
den firewall-Renderer aufruft. Service-Reload-Errors propagieren
weiterhin (Aktion gilt als gescheitert), FW-Render-Errors werden
nur geloggt (DB-Row ist commited, FW kann nachgezogen werden).
Wirkt für: WG, Squid, DNS, NTP. (HAProxy nicht — Domains/Backends
generieren keine Auto-FW-Rules.)
Version 1.0.41.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renderer berechnet inbound-accept-Rules aus dem laufenden
Service-State — Operator legt keine FW-Rule mehr für DNS/Squid/WG-
Listen-Sockets manuell an.
internal/firewall:
* View.AutoRules + AutoFWRule struct (proto, port, optional dst-IP,
comment).
* loadAutoRules quert cross-service:
- DNS: dns_settings.listen_addresses ohne 127.x/::1 → udp+tcp 53
pro IP (mit ip daddr X-match).
- Squid: count(active forward_proxy_acls) > 0 → tcp 3128 (any IP,
squid bindet 0.0.0.0).
- WireGuard: server-mode + listen_port → udp <port> pro Iface.
* nft-Template emittiert eigene "Service-Auto-Rules"-Section vor
Operator-Rules. Comment im nft-Output zeigt source-service.
* LoadAutoRules exportiert für Handler-Endpoint.
Handler:
* GET /api/v1/firewall/auto-rules — gibt die berechnete Liste
zurück damit die UI sie anzeigen kann.
* FirewallHandler.Pool field + ctor-arg dazugekommen.
UI:
* SystemRulesCard fetcht /firewall/auto-rules + merged sie unter
die statischen Anti-Lockout-Rows. 30s-Polling. Operator sieht
jetzt im /firewall/Rules-Tab oben warum z.B. udp/53 offen ist
(auto: DNS auf 10.10.20.1).
Cleanup: alte manuelle DNS+WG-Rules per SQL gelöscht — Auto-Rules
übernehmen.
Version 1.0.38.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: Unbound bindet Listen-Sockets nur beim startup. Bei einer
Mutation von dns_settings.listen_addresses (z.B. neue LAN-IP für
Resolver-Zugriff) hat 'systemctl reload' die Config zwar gelesen,
aber nicht neu gebound — neue IPs blieben tot.
Fix: Renderer ruft RestartService statt ReloadService. ~200ms
Resolver-Downtime beim Save, dafür konsistentes Verhalten für jede
Settings/Zone/Record-Mutation.
Plus configgen.RestartService Helper neu (analog ReloadService),
sudoers im postinst um systemctl restart unbound.service erweitert.
NOTE für DNS-LAN-Zugang: zwei Operator-FW-Rules nötig (DNS-UDP +
DNS-TCP from any to any) wenn der Resolver auf LAN-IPs lauscht.
Aktuell manuell anzulegen — ein Auto-Rule-Generator (analog
NAT-auto-forward) wäre die nächste Iteration.
Version 1.0.36.
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>
Mehrere zusammenhängende Fehler beim Import der NAT-Rules von der
alten EdgeGuard-Box gefunden + behoben:
1. nft-Template: NAT-Rules landeten als Comment (gleicher
Whitespace-Trimmer-Bug wie bei den Operator-Rules vor zwei
commits). Fix: Body auf eigener Zeile via {{""}}-Padding.
2. nft-Syntax-Reihenfolge: emittierte 'tcp ip daddr X dport Y' →
parser-Fehler. Korrekt ist L3-match (ip saddr/daddr) zuerst,
dann L4 (tcp/udp dport). Reihenfolge in der dnat-Zeile
getauscht.
3. eth0 als Iface-Row hinzugefügt (Type ethernet, role wan) damit
der zone→iface-Lookup für 'wan' tatsächlich auf das Linux-Iface
trifft. Vorher war nur 'WAN'-bridge in der DB, das im Kernel
nicht existiert → iifname-match griff nicht.
4. forward-chain: ct status dnat accept (DNAT-Pakete dürfen
forwarden) + Auto-Forward pro SNAT/masquerade-Rule für die
Origin-Pakete (return geht via established,related).
5. postrouting_nat: ct status dnat masquerade als Hairpin-Catch-All
— sonst antwortet das DNAT-Ziel via seinem default-GW (oft
nicht zur EdgeGuard-Box) → SYN_SENT + UNREPLIED. Trade-off:
Backend sieht Box-IP statt client-IP.
6. Sysctl-Profil /etc/sysctl.d/99-edgeguard.conf bei jedem Install:
- Forwarding (ip_forward + ipv6 forwarding) — Voraussetzung für
ALLES NAT/DNAT/Masquerade.
- Conntrack-Buckets + max=524288 (Edge-Box trackt viele
parallele Sessions).
- HAProxy-Tuning (somaxconn 64k, rmem/wmem 16M, keepalive,
tcp_tw_reuse, ip_local_port_range).
- BBR + fq als modernes Congestion-Control + Queueing.
- Anti-DoS: tcp_syncookies, log_martians, kptr_restrict.
Verified end-to-end:
$ nc -v 89.163.205.100 2030
SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.16
Version 1.0.25.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KVS / management-center brauchen länger als 30s für TOTP-Validierung
(externe Session-Storage). Erhöht beide auf 60s — sicher noch unter
keepalive-grenzen, deckt aber legacy-Auth-flows ab.
Plus: VERSION bump 1.0.20 (asg{1,2}-cleanup direkt am DB-State,
keine code-Änderung dafür).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: backends.scheme war im Datenmodell + UI vorhanden, aber der
HAProxy-Renderer hat das Feld komplett ignoriert. Jeder Backend
wurde als plain HTTP angesprochen — wenn das Upstream (nginx etc.)
HTTPS erwartet, kam '400 The plain HTTP request was sent to HTTPS
port' zurück, was im Browser als 404/Fehler erschien.
Fix im Template: server-Zeile bekommt 'ssl verify none alpn h2,http/1.1'
wenn .Scheme == "https". 'verify none' weil interne Backends meist
self-signed; ALPN deckt H1 + H2 via Aushandlung ab (also funktioniert
sowohl proxy_protocol=https als auch =h2 aus dem alten EdgeGuard).
Version 1.0.19.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei show-stopper beim Cutover .101 → .6 entdeckt + behoben:
1. nft-Template-Bug: {{- if ...}}-Whitespace-Trimmer nach der
'# rule N' Kommentarzeile schluckte den Newline → die ganze
Operator-Rule landete als Teil des # Kommentars. nft akzeptierte
die Datei (legaler Comment) und der Operator sah keine Wirkung.
Fix: Body auf eigener Zeile via {{""}}-Padding, Trimmer raus.
2. wg-Renderer schrieb /etc/edgeguard/wireguard/<iface>.conf, aber
wg-quick@<iface>.service liest /etc/wireguard/<iface>.conf
(Distro-Default). Die zwei Files driftet auseinander — beim
Restart sah wg-quick die alte AllowedIPs. Fix: Renderer legt
einen Symlink /etc/wireguard/<iface>.conf → /etc/edgeguard/...
beim Render an (idempotent, ersetzt vorhandene Real-Files).
Beide Fixes waren voraussetzung für den .101 → .6 Cutover, der
jetzt sauber läuft: VIP .100 lebt auf .6, Unify Home dial't durch
zu wg7 (handshake), 10.0.10.x via wg7-Tunnel reachable.
Version 1.0.18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: edgeguard-scheduler war 60s-sleep-Stub. LE-Certs liefen nach
90 Tagen ab und mussten manuell re-issued werden.
Jetzt:
* internal/services/certrenewer — Pipeline (find expiring → ACME-Issue
→ certstore.WriteCombined → Repo.Upsert → haproxy reload). Kapselt
was der /tls-certs/issue-Handler macht, nur DB-driven für N Certs.
* edgeguard-scheduler nutzt acme.Service + tlscerts.Repo + certrenewer.
Tick alle 6h, Threshold 30 Tage Restlaufzeit. Sofort-Run bei
Startup damit eine frisch eingespielte Box auch ohne 6h-Wartezeit
prüft.
* Issuer == "letsencrypt" als Filter — manuell hochgeladene PEMs
bleiben unangetastet (Operator owns lifecycle).
* Errors landen in tls_certs.last_error, retry beim nächsten Tick
(transiente ACME-Failures self-heal).
* systemd-Unit edgeguard-scheduler.service: ReadWritePaths um
/etc/edgeguard erweitert (für Cert-PEM-Writes), NoNewPrivileges
auf false (sudo systemctl reload haproxy braucht setuid). Spiegelt
edgeguard-api-Unit.
Version 1.0.16.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: SSL-Issue-Form bot nur die operator-managed /domains an.
Wenn der Operator ein Cert für die Management-FQDN (utm-1.netcell-it.de
aus setup.json) wollte, war diese nicht in der Auswahl — er hätte
sie erst als Domain-Row anlegen müssen.
Jetzt: AutoComplete (statt Select) mit drei Quellen kombiniert:
* Management-FQDN aus /setup/status — als erste Option mit Hint
* Alle Cluster-Node-FQDNs aus /cluster/nodes
* Operator-/domains
Plus: jede beliebige FQDN ist eintippbar (DNS muss zeigen).
(combobox-mode in AntD ist deprecated — AutoComplete ist die
empfohlene Variante für free-text-with-suggestions.)
Version 1.0.15.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher: render-config --no-reload schrieb nur die Files; haproxy
wurde explizit per systemctl restart unten neu gefahren, aber
nft-Set blieb beim Kernel-Stand vom letzten Boot. Bug sichtbar bei
1.0.13: Anti-Lockout-Eintrag für 3443 war im Template, aber der
Kernel hatte die Regel nicht — Port von außen blockiert.
Fix: zwei render-Calls — haproxy mit --no-reload (wie bisher),
nftables ohne, damit `sudo nft -f` direkt nach dem Schreiben
ausgeführt wird.
Version 1.0.14.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* HAProxy neues Frontend mgmt_https :3443 → api_backend (Mgmt-UI).
Selbe TLS-Cert-Strecke wie :443 (gleicher /etc/edgeguard/tls/-Pool).
* :443 verliert default_backend → unbekannte Hosts kriegen 503,
nicht mehr versehentlich die Admin-UI. Plus default-Route auf
primary_backend pro Domain (catch-all-Routing dort, wo gewollt).
* Anti-Lockout in nft-Template um tcp dport 3443 erweitert
(zusätzlich zu 22 + 443).
* SystemRulesCard zeigt 3443 als 3. Anti-Lockout-Eintrag.
Erreichbarkeit:
* Public Backends: https://<domain>:443 (mit eigenem Cert oder LE)
* Admin-UI: https://<host>:3443 (jeder Hostname, default_backend)
* SSH: :22 (rate-limited 10/min)
Version 1.0.13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pages auf PageHeader/StatusDot/ActionButtons-Pattern migriert:
* Dashboard — Komplett-Rewrite. KPI-Tiles (Domains, Backends, Iface,
FW-Rules, NAT, WG), Detail-Cards (WireGuard live status, Firewall
zone overview, SSL expiring soon, Cluster nodes, Routing summary,
System info). Polled queries pro Card.
* Domains, Backends, RoutingRules, Networks, IPAddresses, SSL,
Cluster, Settings, Firewall (index) — alle inline Action-Buttons
→ ActionButtons; alle Yes/No-Renders → StatusDot; Add-Button in
DataTable.extraActions; PageHeader oben.
WireGuard
---------
* Neuer /wireguard/status-Endpoint parsed `wg show all dump`,
liefert {iface, peer_pubkey, endpoint, last_handshake_unix, rx, tx}.
Sudoers im postinst um `wg show` erweitert.
* Server-Drawer Peer-Liste zeigt jetzt Live-Status (Online/Offline-
Dot, "vor Xs", Traffic-Counter) per 10s-Polling. Importierte
"Unify Home" peer kann jetzt im UI verifiziert werden.
* Importer-Bug fixed: nextName ("# Unify Home" comment) wurde beim
Sektionswechsel zu früh geresettet — jetzt nur nach echtem
flushPeer.
Routing-Rules
-------------
* Aus Sidebar entfernt. URL bleibt funktional, aber für 90% der
Setups reicht domains.primary_backend_id (das HAProxy ohnehin
als default_backend rendert). Path-basiertes Routing ist ein
Advanced-Feature und kommt später als Domain-Modal-Tab zurück.
* nav.routing-Sidebar-Eintrag + BranchesOutlined-Import entfernt.
Misc
----
* "Firewall (v2)" → "Firewall" im Nav (DE).
* Dashboard-i18n Block in DE+EN.
* Version 1.0.11 → 1.0.12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symmetrisch zur Firewall: Domains-, Backends- und RoutingRules-Handler
bekommen einen Reloader-Hook injiziert, der nach jeder Mutation
haproxy.cfg neu rendert + sudo systemctl reload haproxy fährt. Errors
werden nur geloggt, nicht failed (Row ist committed; manuelle
Re-Render via edgeguard-ctl render-config bleibt möglich).
Vorher: nur Firewall-Regeln waren auto-applied — Domain/Backend-
Änderungen sind in der DB gelandet, aber das laufende haproxy hat
sie nicht gesehen bis zum nächsten render-config oder API-Restart.
Version 1.0.8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symmetrisch zur Backend-Auswahl im Domain-Modal: das Backend-Modal
hat jetzt einen Multi-Select für Domains. Auswahl wird beim Speichern
gegen den aktuellen Stand diff't und in N parallele PUTs an
/domains/:id übersetzt — Add setzt primary_backend_id auf die ID,
Remove auf null.
Domain bleibt die Quelle der Wahrheit (kein Schema-Change). Die
Backend-Seite ist nur eine alternative Edit-Affordance.
Version 1.0.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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>
* 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>
DataTable (components/DataTable.tsx) gibt jeder CRUD-Tabelle drei
Baseline-Features auf einmal:
* Search-Input (Volltext über alle string-Felder, case-insensitive)
* Pagination 25/Seite mit showSizeChanger
* Auto-sorter pro Spalte mit dataIndex (string→localeCompare,
number→subtract, boolean→bool→Number) — Spalten mit eigenem
sorter behalten den.
Sweep aller 13 CRUD-Pages auf <DataTable>: Domains, Backends,
Routing-Rules, Networks, IP-Addresses, SSL, Cluster, sechs Firewall-
Tabs. Kleine Sub-Tabellen (System-Discovered IP-Card) bleiben
auf <Table> — read-only ohne CRUD braucht keine Pagination.
i18n: common.search, common.totalRows.
Version-Bump auf 1.0.0 (User-Direktive: ohne -dev): VERSION-Datei,
Go-Literale in cmd/edgeguard-{api,ctl,scheduler}/main.go,
package.json, Sidebar-Konstante. Live deployed auf 89.163.205.6 als
edgeguard 1.0.0 (api + ui + meta).
Memory: project_versioning.md hält die Patch-Bump-Konvention fest
(Gitea Package Registry 409't bei Doppel-Upload — bei jedem Release
zuerst die VERSION inkrementieren).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scaffold und Core-Infrastruktur 1:1 nach enconf-Pattern (netcell-
webpanel/management-ui), reduziert auf EdgeGuard-Scope (kein reseller/
customer-Roles, keine codemirror/extensions). Stack: React 19 + AntD 6
+ TS strict + Vite + TanStack-Query + zustand + react-i18next.
Layout: AppLayout (Sider+Header+Content), Sidebar (Dashboard/Domains),
Header (User-Dropdown + Logout). i18n mit de/en common.json.
Pages: Login (POST /auth/login), Setup-Wizard (POST /setup/complete),
Dashboard (Health-Polling + Statistics), Domains (volles CRUD via
TanStack-Query gegen /domains-API). UpdateBanner-Komponente
(/system/package-versions, alle 5 min poll, /system/upgrade trigger)
ist von Tag 1 wie vom User gefordert eingebaut.
API-Wiring: cmd/edgeguard-api/main.go mountUI() — gin StaticFS für
/usr/share/edgeguard/ui/ (overridebar via EDGEGUARD_UI_DIR), echte
Files werden direkt geserved, alle nicht-API-Pfade fallen via
NoRoute auf index.html für React-Router-SPA. Wenn dist/ fehlt:
HTML-Placeholder mit Build-Hinweis.
Verifiziert: bun install + npx tsc -b strict (0 errors) + bun run
build (12 chunks). End-to-end gegen /tmp/eg-api: / serviert echte
React-index.html, /domains SPA-Fallback, /api/v1/* JSON, /assets/*
direkt, /api/v1/nonexistent korrekt 404.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>