feat(configgen): Phase 2 Config-Generator + nginx → HAProxy-only Pivot
Architektur-Pivot: nginx fällt komplett weg. HAProxy 2.8+ übernimmt
TLS-Termination, L7-Routing per Host-Header und LB. ACME-Webroot
und Management-UI werden von edgeguard-api ausgeliefert (Phase 3
implementiert die zugehörigen Handler); HAProxy proxied
/.well-known/acme-challenge/* und Management-FQDN-Traffic an
127.0.0.1:9443. Eine Distro-Abhängigkeit weniger, ein Renderer
weniger, sauberere Trennung.
Renderer (alle mit Embed-Templates + Tests):
* internal/configgen/ — atomic write + systemctl reload helpers
* internal/haproxy/ — :80 + :443, ACME-ACL, Host-Header-Routing,
Stats-Frontend, api_backend Fallback
* internal/firewall/ — default-deny input, stateful baseline,
SSH-Rate-Limit, :80/:443 accept,
Cluster-Peer-Set für mTLS :8443,
Custom-Rules aus PG
* internal/{squid,wireguard,unbound}/ — Stubs (ErrNotImplemented)
Orchestrator + CLI:
* internal/services/configorch/ — fester Reihenfolge-Run, Stubs
sind soft-skip statt fatal
* cmd/edgeguard-ctl render-config [--no-reload] [--only=svc1,svc2]
Packaging:
* postinst: /etc/edgeguard/nginx raus, /var/lib/edgeguard/acme rein,
self-signed _default.pem via openssl req (damit HAProxy startet
bevor certbot etwas issuet hat)
* control: Depends nginx raus, openssl rein
* edgeguard-ui: dependency auf nginx weg, "Served by edgeguard-api
gin StaticFS"
Live-Smoke: render-config gegen lokale PG schreibt /etc/edgeguard/
haproxy/haproxy.cfg + nftables.d/ruleset.nft korrekt; CRUD-Test aus
Phase 2 läuft weiter unverändert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
internal/services/configorch/configorch.go
Normal file
74
internal/services/configorch/configorch.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Package configorch fans Render() out across every per-service
|
||||
// renderer in a stable order (haproxy → nftables → squid →
|
||||
// wireguard → unbound). The orchestrator stops on the first hard
|
||||
// error; ErrNotImplemented stubs are logged but do NOT abort the
|
||||
// run (squid/wireguard/unbound are stubs in Phase 2).
|
||||
package configorch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Name string
|
||||
Skipped bool
|
||||
Err error
|
||||
DurationS float64 // populated by callers if they care; orchestrator leaves it 0
|
||||
}
|
||||
|
||||
func (r Result) Status() string {
|
||||
switch {
|
||||
case r.Err == nil:
|
||||
return "ok"
|
||||
case errors.Is(r.Err, configgen.ErrNotImplemented):
|
||||
return "skipped (stub)"
|
||||
default:
|
||||
return "error: " + r.Err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Run invokes Render on every generator in `gens`, returning a per-
|
||||
// generator Result slice. Stops on the first hard error so a broken
|
||||
// HAProxy config doesn't bleed into a partial nftables rewrite.
|
||||
//
|
||||
// only: optional whitelist of generator names — empty slice means
|
||||
// "all". Useful for `render-config --only=haproxy` debugging.
|
||||
func Run(ctx context.Context, gens []configgen.Generator, only []string) ([]Result, error) {
|
||||
whitelist := map[string]bool{}
|
||||
for _, n := range only {
|
||||
whitelist[n] = true
|
||||
}
|
||||
out := make([]Result, 0, len(gens))
|
||||
for _, g := range gens {
|
||||
if len(whitelist) > 0 && !whitelist[g.Name()] {
|
||||
out = append(out, Result{Name: g.Name(), Skipped: true})
|
||||
continue
|
||||
}
|
||||
err := g.Render(ctx)
|
||||
out = append(out, Result{Name: g.Name(), Err: err})
|
||||
if err != nil && !errors.Is(err, configgen.ErrNotImplemented) {
|
||||
// hard failure — surface it but return what's done so far
|
||||
return out, fmt.Errorf("%s: %w", g.Name(), err)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Summarise turns the result slice into a human-readable multiline
|
||||
// string. Used by `edgeguard-ctl render-config` to print to stdout.
|
||||
func Summarise(results []Result) string {
|
||||
var b strings.Builder
|
||||
for _, r := range results {
|
||||
if r.Skipped {
|
||||
fmt.Fprintf(&b, " %-10s skipped (filtered)\n", r.Name)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, " %-10s %s\n", r.Name, r.Status())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
@@ -28,7 +28,7 @@ FROM routing_rules
|
||||
`
|
||||
|
||||
// List returns rules ordered by domain_id then priority desc — the
|
||||
// shape the config-renderer wants when building haproxy/nginx vhosts.
|
||||
// shape the config-renderer wants when building HAProxy backends.
|
||||
func (r *Repo) List(ctx context.Context) ([]models.RoutingRule, error) {
|
||||
rows, err := r.Pool.Query(ctx, baseSelect+
|
||||
" ORDER BY domain_id ASC, priority DESC, id ASC")
|
||||
|
||||
Reference in New Issue
Block a user