Encrypted DNS health, comparing a zone's views, --local everywhere

jarld checks the DoT and DoH listeners every 10 minutes as a client would (page, dashboard, jarl encrypted-dns health, jarl_encrypted_dns_up). The Compare views tab and jarl zone diff show what differs between a zone's views, with Use this version. --local works on unlinked zones. A changed certificates.dir is followed by encrypted DNS's tls statements. Sharing an already shared version replaces other views' overrides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-12 20:46:56 +01:00
1 parent 67ae0091b3
commit 13ffd8cad4
31 files changed
+1153 -18

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
package server
import (
"net/http"
"strings"
"jarl/internal/auth"
"jarl/internal/engine"
"jarl/internal/model"
)
// zoneCompare: GET /zones/{zone}/compare lines up a zone's records across
// the views that have it (docs/views.md#linked-zones). ?views=a,b picks the
// views (default: every view with a primary zone of that name), ?all=1
// includes the names and types that are the same everywhere.
func (s *Server) zoneCompare(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
st, err := s.state(r)
if err != nil {
return err
}
z, err := s.zoneByPath(r, st, p)
if err != nil {
// A name in several views is what there is to compare.
ref := r.PathValue("zone")
if ref == "root" {
ref = "."
}
name, perr := engine.ParseZoneName(ref)
if zs := st.ZonesNamed(name); perr != nil || len(zs) < 2 {
return err
} else {
z = zs[0]
}
}
var want []string
for _, v := range strings.Split(r.URL.Query().Get("views"), ",") {
if v = strings.TrimSpace(v); v != "" {
want = append(want, v)
}
}
var zones []*model.Zone
for _, o := range st.ZonesNamed(z.Name) {
vn := st.ViewName(o)
if o.Type != model.ZonePrimary || !p.CanSeeZone(o.Name, vn) || !p.Can(auth.Action{Resource: auth.ResRecords, Zone: o.Name, View: vn}) {
continue
}
if len(want) > 0 && !containsFold(want, vn) {
continue
}
zones = append(zones, o)
}
st.SortByView(zones)
if len(zones) < 2 {
return badRequest("zone %s is in only one view (that you may read): there is nothing to compare", engine.Display(z.Name))
}
rows := engine.CompareZones(st, zones)
all := queryBool(r, "all")
out := struct {
Zone string `json:"zone"`
Views []string `json:"views"`
Linked bool `json:"linked"` // all of them in one link
Same int `json:"same"` // names and types the same everywhere
Rows []engine.CompareRow `json:"rows"`
}{Zone: engine.Display(z.Name), Rows: []engine.CompareRow{}, Linked: true}
for _, o := range zones {
out.Views = append(out.Views, st.ViewName(o))
out.Linked = out.Linked && o.LinkGroup != 0 && o.LinkGroup == zones[0].LinkGroup
}
for _, row := range rows {
if row.Same {
out.Same++
if !all {
continue
}
}
out.Rows = append(out.Rows, row)
}
writeJSON(w, http.StatusOK, out)
return nil
}
func containsFold(list []string, s string) bool {
for _, x := range list {
if strings.EqualFold(x, s) {
return true
}
}
return false
}