feat(config-preview): chrony + wireguard in Config-Preview verfügbar

- chrony.RenderToString(): rendert /etc/chrony/conf.d/edgeguard.conf
  ohne Datei-Write oder Service-Reload
- wireguard.RenderToString(): kombiniert alle aktiven Interface-Configs;
  PrivateKey + PresharedKey werden als <redacted> ausgegeben (sicher für
  UI-Anzeige)
- main.go: beide in WithConfigPreviewers eingetragen
- Settings UI: chrony + wireguard im Generator-Dropdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-25 06:28:48 +02:00
parent 73619c17f8
commit cfb0e9ed01
7 changed files with 108 additions and 8 deletions

View File

@@ -49,6 +49,30 @@ func New(pool *pgxpool.Pool) *Generator {
func (g *Generator) Name() string { return "chrony" }
// RenderToString renders the chrony config to a string without writing
// to disk or reloading the service. Used by the config-preview endpoint.
func (g *Generator) RenderToString(ctx context.Context) (string, error) {
settings, err := g.Repo.GetSettings(ctx)
if err != nil {
return "", fmt.Errorf("settings: %w", err)
}
pools, err := g.Repo.ListPools(ctx)
if err != nil {
return "", fmt.Errorf("pools: %w", err)
}
view := View{
Settings: settings,
Pools: pools,
ListenAddresses: filterNonLoopback(splitCSV(settings.ListenAddresses)),
AllowACLs: splitCSV(settings.AllowACL),
}
var body bytes.Buffer
if err := tpl.Execute(&body, view); err != nil {
return "", fmt.Errorf("template: %w", err)
}
return body.String(), nil
}
func (g *Generator) Render(ctx context.Context) error {
settings, err := g.Repo.GetSettings(ctx)
if err != nil {

View File

@@ -44,6 +44,78 @@ func New(pool *pgxpool.Pool, box *secrets.Box) *Generator {
func (g *Generator) Name() string { return "wireguard" }
// RenderToString renders all active interface configs to a combined
// string for the config-preview endpoint. Private keys are redacted
// so the output is safe to display in the management UI.
func (g *Generator) RenderToString(ctx context.Context) (string, error) {
ifs, err := g.Ifaces.List(ctx)
if err != nil {
return "", fmt.Errorf("list ifaces: %w", err)
}
var combined strings.Builder
for _, ifc := range ifs {
if !ifc.Active {
continue
}
fmt.Fprintf(&combined, "# ── %s (%s) ────────────────────────────────\n", ifc.Name, ifc.Mode)
combined.WriteString("[Interface]\n")
fmt.Fprintf(&combined, "Address = %s\n", ifc.AddressCIDR)
combined.WriteString("PrivateKey = <redacted>\n")
if ifc.ListenPort != nil {
fmt.Fprintf(&combined, "ListenPort = %d\n", *ifc.ListenPort)
}
if ifc.MTU != nil {
fmt.Fprintf(&combined, "MTU = %d\n", *ifc.MTU)
}
combined.WriteString("\n")
switch ifc.Mode {
case "client":
if ifc.PeerPublicKey != nil && ifc.PeerEndpoint != nil {
combined.WriteString("[Peer]\n")
fmt.Fprintf(&combined, "PublicKey = %s\n", *ifc.PeerPublicKey)
fmt.Fprintf(&combined, "Endpoint = %s\n", *ifc.PeerEndpoint)
if ifc.AllowedIPs != nil && *ifc.AllowedIPs != "" {
fmt.Fprintf(&combined, "AllowedIPs = %s\n", *ifc.AllowedIPs)
} else {
combined.WriteString("AllowedIPs = 0.0.0.0/0,::/0\n")
}
if ifc.PersistentKeepalive != nil {
fmt.Fprintf(&combined, "PersistentKeepalive = %d\n", *ifc.PersistentKeepalive)
}
if len(ifc.PeerPSKEnc) > 0 {
combined.WriteString("PresharedKey = <redacted>\n")
}
}
case "server":
peers, err := g.Peers.ListForInterface(ctx, ifc.ID)
if err == nil {
sort.Slice(peers, func(i, j int) bool { return peers[i].Name < peers[j].Name })
for _, p := range peers {
if !p.Enabled {
continue
}
combined.WriteString("[Peer]\n")
fmt.Fprintf(&combined, "# %s\n", p.Name)
fmt.Fprintf(&combined, "PublicKey = %s\n", p.PublicKey)
fmt.Fprintf(&combined, "AllowedIPs = %s\n", p.AllowedIPs)
if p.Keepalive != nil {
fmt.Fprintf(&combined, "PersistentKeepalive = %d\n", *p.Keepalive)
}
if len(p.PSKEnc) > 0 {
combined.WriteString("PresharedKey = <redacted>\n")
}
combined.WriteString("\n")
}
}
}
combined.WriteString("\n")
}
if combined.Len() == 0 {
return "# No active WireGuard interfaces configured.\n", nil
}
return combined.String(), nil
}
func (g *Generator) Render(ctx context.Context) error {
if err := os.MkdirAll(ConfDir, 0o700); err != nil {
return fmt.Errorf("mkdir %s: %w", ConfDir, err)