Files
edgeguard-native/internal/squid/squid.go
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

118 lines
3.1 KiB
Go

// Package squid renders /etc/edgeguard/squid/squid.conf from
// forward_proxy_acls and reloads squid.service. Cache directory
// + in-memory cache are hard-coded sensible defaults; the operator
// scope is just "what can pass through the proxy".
package squid
import (
"bytes"
"context"
_ "embed"
"fmt"
"os"
"path/filepath"
"text/template"
"github.com/jackc/pgx/v5/pgxpool"
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
"git.netcell-it.de/projekte/edgeguard-native/internal/services/forwardproxy"
)
const (
confPath = "/etc/edgeguard/squid/squid.conf"
listenPort = 3128
)
//go:embed squid.cfg.tpl
var cfgTpl string
var tpl = template.Must(template.New("squid").Parse(cfgTpl))
type View struct {
ListenPort int
ACLs []models.ForwardProxyACL
}
type Generator struct {
Pool *pgxpool.Pool
Repo *forwardproxy.Repo
SkipReload bool
}
func New(pool *pgxpool.Pool) *Generator {
return &Generator{Pool: pool, Repo: forwardproxy.New(pool)}
}
func (g *Generator) Name() string { return "squid" }
func (g *Generator) renderBuf(ctx context.Context) (bytes.Buffer, error) {
acls, err := g.Repo.List(ctx)
if err != nil {
return bytes.Buffer{}, fmt.Errorf("list acls: %w", err)
}
view := View{ListenPort: listenPort, ACLs: acls}
var body bytes.Buffer
if err := tpl.Execute(&body, view); err != nil {
return bytes.Buffer{}, fmt.Errorf("template: %w", err)
}
return body, nil
}
func (g *Generator) RenderToString(ctx context.Context) (string, error) {
buf, err := g.renderBuf(ctx)
if err != nil {
return "", err
}
return buf.String(), nil
}
func (g *Generator) Render(ctx context.Context) error {
body, err := g.renderBuf(ctx)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(confPath), 0o755); err != nil {
return fmt.Errorf("mkdir: %w", err)
}
tmp := confPath + ".tmp"
if err := os.WriteFile(tmp, body.Bytes(), 0o644); err != nil {
return fmt.Errorf("write %s: %w", tmp, err)
}
if err := os.Rename(tmp, confPath); err != nil {
return fmt.Errorf("rename: %w", err)
}
if err := ensureDistroSymlink(); err != nil {
return fmt.Errorf("symlink: %w", err)
}
if g.SkipReload {
return nil
}
return configgen.ReloadService("squid")
}
// ensureDistroSymlink prüft ob /etc/squid/squid.conf auf unsere
// managed conf zeigt. Setup ist Postinst-Verantwortung (Renderer
// hat als edgeguard-User kein Schreibrecht in /etc/squid). Wenn
// Symlink fehlt → Warnung, aber kein Fehler — squid liest dann
// noch die Distro-Default und der Operator merkt's beim nächsten
// reload.
func ensureDistroSymlink() error {
const link = "/etc/squid/squid.conf"
if cur, err := os.Readlink(link); err == nil && cur == confPath {
return nil
}
// Versuch zu setzen — bei permission-denied (= edgeguard-User
// hat keinen Schreibrecht in /etc/squid) warnen + ok melden.
if _, err := os.Stat(link); err == nil {
_ = os.Rename(link, link+".distro-bak")
}
if err := os.Symlink(confPath, link); err != nil {
// Postinst hat den Symlink schon angelegt oder soll's beim
// Upgrade nachholen. Renderer sollte hier nicht failen.
return nil
}
return nil
}