fix(unbound): restart statt reload + DNS Auto-FW-Rules dokumentiert

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>
This commit is contained in:
Debian
2026-05-11 06:32:59 +02:00
parent 979b3cfa66
commit 8357d84c7b
9 changed files with 27 additions and 7 deletions

View File

@@ -1 +1 @@
1.0.35 1.0.36

View File

@@ -43,7 +43,7 @@ import (
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard" wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
) )
var version = "1.0.35" var version = "1.0.36"
func main() { func main() {
addr := os.Getenv("EDGEGUARD_API_ADDR") addr := os.Getenv("EDGEGUARD_API_ADDR")

View File

@@ -9,7 +9,7 @@ import (
"os" "os"
) )
var version = "1.0.35" var version = "1.0.36"
const usage = `edgeguard-ctl — EdgeGuard CLI const usage = `edgeguard-ctl — EdgeGuard CLI

View File

@@ -21,7 +21,7 @@ import (
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
) )
var version = "1.0.35" var version = "1.0.36"
const ( const (
// renewTickInterval — how often we re-evaluate expiring certs. // renewTickInterval — how often we re-evaluate expiring certs.

View File

@@ -87,6 +87,18 @@ func ReloadService(name string) error {
return nil return nil
} }
// RestartService runs `sudo -n systemctl restart <name>.service`.
// Use over ReloadService when the daemon needs to re-read more than
// just rules — e.g. unbound rebinds listen-sockets only on startup,
// so a settings.listen_addresses change requires restart.
func RestartService(name string) error {
cmd := exec.Command("sudo", "-n", "/usr/bin/systemctl", "restart", name+".service")
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("sudo systemctl restart %s.service: %w (output: %s)", name, err, strings.TrimSpace(string(out)))
}
return nil
}
// EtcEdgeguard is the on-target config root. Templated path used by // EtcEdgeguard is the on-target config root. Templated path used by
// all renderers — never let renderers hard-code their own. // all renderers — never let renderers hard-code their own.
const EtcEdgeguard = "/etc/edgeguard" const EtcEdgeguard = "/etc/edgeguard"

View File

@@ -131,7 +131,13 @@ func (g *Generator) Render(ctx context.Context) error {
if g.SkipReload { if g.SkipReload {
return nil return nil
} }
return configgen.ReloadService("unbound") // Restart statt reload: unbound bindet Listen-Sockets nur beim
// Startup. Bei Settings-Änderungen (listen_addresses-Wechsel)
// greift ein bloßes 'systemctl reload' nicht — die neuen IPs
// werden erst nach echtem Restart gebound. Trade-off: ~200ms
// Downtime des Resolvers, dafür konsistentes Verhalten für jede
// Mutation.
return configgen.RestartService("unbound")
} }
func splitCSV(s string) []string { func splitCSV(s string) []string {

View File

@@ -1,7 +1,7 @@
{ {
"name": "edgeguard-management-ui", "name": "edgeguard-management-ui",
"private": true, "private": true,
"version": "1.0.35", "version": "1.0.36",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -73,7 +73,7 @@ const NAV: NavSection[] = [
}, },
] ]
const VERSION = '1.0.35' const VERSION = '1.0.36'
export default function Sidebar({ isOpen, onClose }: SidebarProps) { export default function Sidebar({ isOpen, onClose }: SidebarProps) {
const { t } = useTranslation() const { t } = useTranslation()

View File

@@ -58,6 +58,8 @@ edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload squid.service
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload squid.service edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload squid.service
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload unbound.service edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload unbound.service
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload unbound.service edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload unbound.service
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart unbound.service
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart unbound.service
SUDOERS SUDOERS
# ── Distro-Conf-Includes für die per-Service Renderer ───────── # ── Distro-Conf-Includes für die per-Service Renderer ─────────