initial commit
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/owen/vibedns/internal/auditlog"
|
||||
"github.com/owen/vibedns/internal/config"
|
||||
)
|
||||
|
||||
// RestartRequired lists the settings that only take effect after a restart,
|
||||
// because they control a bound socket.
|
||||
var RestartRequired = map[string]string{
|
||||
config.KeyDNSUDPListen: "DNS UDP listen address",
|
||||
config.KeyDNSTCPListen: "DNS TCP listen address",
|
||||
config.KeyHTTPListen: "Management HTTP listen address",
|
||||
}
|
||||
|
||||
// SettingsGroup names a page of the settings interface.
|
||||
type SettingsGroup string
|
||||
|
||||
// Settings pages.
|
||||
const (
|
||||
GroupDNS SettingsGroup = "dns"
|
||||
GroupResolver SettingsGroup = "resolver"
|
||||
GroupCache SettingsGroup = "cache"
|
||||
GroupLogging SettingsGroup = "logging"
|
||||
GroupHTTP SettingsGroup = "http"
|
||||
GroupBackup SettingsGroup = "backup"
|
||||
GroupRateLimit SettingsGroup = "ratelimit"
|
||||
)
|
||||
|
||||
// SaveSettings validates and persists a complete settings object.
|
||||
//
|
||||
// Validation runs against the merged result rather than the submitted fields,
|
||||
// so a change that would leave the server in an unusable state — recursion on
|
||||
// with no upstreams, or an empty ACL — is rejected before it is stored.
|
||||
func (a *App) SaveSettings(ctx context.Context, actor auditlog.Actor, group SettingsGroup, next config.Settings) error {
|
||||
next.Normalise()
|
||||
if err := next.Validate(); err != nil {
|
||||
return Invalid("%s", err.Error())
|
||||
}
|
||||
|
||||
current := a.Settings()
|
||||
changed := diffSettings(current.ToMap(), next.ToMap())
|
||||
if len(changed) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only the keys belonging to this group are written, so two administrators
|
||||
// editing different pages cannot overwrite each other's work.
|
||||
toWrite := map[string]string{}
|
||||
full := next.ToMap()
|
||||
for _, k := range changed {
|
||||
toWrite[k] = full[k]
|
||||
}
|
||||
|
||||
if err := a.DB.SetSettings(ctx, toWrite); err != nil {
|
||||
return Internal(err, "The settings could not be saved.")
|
||||
}
|
||||
|
||||
a.Audit.Record(ctx, actor, "settings.update", auditlog.ObjectSettings, string(group), string(group),
|
||||
auditlog.Changes("keys", strings.Join(changed, " ")))
|
||||
|
||||
if err := a.Runtime.Reload(ctx); err != nil {
|
||||
return Internal(err, "The settings were saved but could not be applied. Restart the server.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// diffSettings returns the keys whose values differ.
|
||||
func diffSettings(before, after map[string]string) []string {
|
||||
var changed []string
|
||||
for k, v := range after {
|
||||
if before[k] != v {
|
||||
changed = append(changed, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(changed)
|
||||
return changed
|
||||
}
|
||||
|
||||
// PendingRestart reports which changed settings need a restart to take effect.
|
||||
func (a *App) PendingRestart(ctx context.Context) []string {
|
||||
current := a.Settings()
|
||||
udp, tcp := a.DNS.ListenAddrs()
|
||||
|
||||
var pending []string
|
||||
if current.DNS.UDPListen != udp {
|
||||
pending = append(pending, fmt.Sprintf("DNS UDP address (listening on %s, configured as %s)",
|
||||
udp, current.DNS.UDPListen))
|
||||
}
|
||||
if current.DNS.TCPListen != tcp {
|
||||
pending = append(pending, fmt.Sprintf("DNS TCP address (listening on %s, configured as %s)",
|
||||
tcp, current.DNS.TCPListen))
|
||||
}
|
||||
return pending
|
||||
}
|
||||
|
||||
// TestUpstream probes one upstream resolver on demand.
|
||||
func (a *App) TestUpstream(ctx context.Context, addr, qname string) (string, error) {
|
||||
s := a.Settings()
|
||||
if strings.TrimSpace(qname) == "" {
|
||||
qname = "example.com"
|
||||
}
|
||||
upstreams := config.SplitLines(addr)
|
||||
if len(upstreams) == 0 {
|
||||
return "", Invalid("Enter an upstream resolver address.")
|
||||
}
|
||||
target := upstreams[0]
|
||||
if !strings.Contains(target, ":") {
|
||||
target += ":53"
|
||||
}
|
||||
|
||||
rtt, rcode, err := resolverCheck(ctx, target, qname, s)
|
||||
if err != nil {
|
||||
return "", Invalid("%s", err.Error())
|
||||
}
|
||||
return fmt.Sprintf("%s answered %s in %.0f ms", target, rcode, float64(rtt.Microseconds())/1000), nil
|
||||
}
|
||||
Reference in New Issue
Block a user