Reverse zone suggestions: yours and public, dismiss, per-view switch; child zones take their records

Suggestions split into networks that are yours (private IPv4 /24, ULA /48, an IPv6 /48 this server has an address in), which Create all creates, and public addresses (/32 or /128) to create only if they are yours or dismiss. A view can turn suggestions off, which also keeps it out of choosing a reverse zone's view (database version 13). A zone created inside another in the same view takes the parent's records under it; a delegation and its glue stay, a linked parent keeps them with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-12 22:01:18 +01:00
1 parent aa4b004a14
commit b24c730b97
21 files changed
+543 -107

No files matched your search

+5 -1
View File
@@ -37,7 +37,7 @@ func viewCmd() *cobra.Command {
}
var clients, dests []string
var recursiveOnly, comment, exposure string
var adopt, cascade, yes bool
var adopt, cascade, yes, reverseSuggest bool
viewBody := func(cmd *cobra.Command) map[string]any {
body := map[string]any{}
f := cmd.Flags()
@@ -63,6 +63,9 @@ func viewCmd() *cobra.Command {
if f.Changed("exposure") {
body["exposure"] = exposure
}
if f.Changed("reverse-suggestions") {
body["reverse_suggestions"] = reverseSuggest
}
return body
}
addViewFlags := func(c *cobra.Command) {
@@ -71,6 +74,7 @@ func viewCmd() *cobra.Command {
c.Flags().StringVar(&recursiveOnly, "recursive-only", "", "match-recursive-only: yes, no or default")
c.Flags().StringVar(&comment, "comment", "", "comment")
c.Flags().StringVar(&exposure, "exposure", "", "reachable from the internet: auto (worked out), public or private (docs/exposure.md)")
c.Flags().BoolVar(&reverseSuggest, "reverse-suggestions", true, "suggest reverse zones for the view's addresses (--reverse-suggestions=false: don't; docs/reverse.md)")
}
create := &cobra.Command{
Use: "create NAME",
+44 -13
View File
@@ -1081,6 +1081,7 @@ type reverseSuggestion struct {
View string `json:"view"`
Prefix string `json:"prefix"`
Zone string `json:"zone"`
Group string `json:"group"`
Why string `json:"why"`
Fill int `json:"fill"`
Samples []string `json:"samples"`
@@ -1088,20 +1089,44 @@ type reverseSuggestion struct {
func zoneSuggestReverseCmd() *cobra.Command {
var create bool
var dismiss, undismiss, view string
cmd := &cobra.Command{
Use: "suggest-reverse [--create]",
Use: "suggest-reverse [--create] [--dismiss NETWORK --view V]",
Short: "The reverse zones your private views lack for their records' addresses",
Long: `Lists the reverse zones the private views (or the server, without views)
lack for the addresses of their A and AAAA records: the /24 of a private
IPv4 network, a /32 for a public IPv4 address (the rest of its /24 isn't
yours), the /48 of IPv6 addresses. --create creates them all, filled with
PTR records, in one changeset (docs/reverse.md).`,
lack for the addresses of their A and AAAA records, in two groups:
yours the /24 of a private IPv4 network, the /48 of a ULA or of an IPv6
prefix this server has an address in
public a /32 (or /128) for any other public address: create it only if
the address is yours, not a web host's
--create creates the ones that are yours, filled with PTR records, in one
changeset; create a public one with 'jarl zone create NETWORK --view V
--fill'. --dismiss hides a suggestion for good (--undismiss shows it
again). A view can stop suggestions: jarl view update V
--reverse-suggestions=false (docs/reverse.md).`,
Example: ` jarl zone suggest-reverse
jarl zone suggest-reverse --create
jarl zone create 139.144.144.154/32 --view internal --fill
jarl zone suggest-reverse --dismiss 185.199.108.153/32 --view internal`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newClient()
if err != nil {
return err
}
if dismiss != "" || undismiss != "" {
path, prefix := "/reverse-zones/dismiss", dismiss
if undismiss != "" {
path, prefix = "/reverse-zones/undismiss", undismiss
}
resp, err := c.Mutate(ctx(), "POST", path, nil, map[string]any{"view": view, "prefix": prefix})
if err != nil {
return err
}
return report(resp)
}
var sugg []reverseSuggestion
if err := c.Get(ctx(), "/reverse-zones/suggestions", nil, &sugg); err != nil {
return err
@@ -1112,23 +1137,22 @@ PTR records, in one changeset (docs/reverse.md).`,
fmt.Fprintln(w, "No reverse zones missing.")
return
}
fmt.Fprintln(w, "VIEW\tNETWORK\tZONE\tPTRS\tFOR EXAMPLE")
fmt.Fprintln(w, "GROUP\tVIEW\tNETWORK\tZONE\tPTRS\tFOR EXAMPLE")
for _, s := range sugg {
v := s.View
if v == "" {
v = "-"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\n", v, s.Prefix, s.Zone, s.Fill, strings.Join(s.Samples, ", "))
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%s\n", s.Group, v, s.Prefix, s.Zone, s.Fill, strings.Join(s.Samples, ", "))
}
fmt.Fprintln(w, "\nCreate them, filled: jarl zone suggest-reverse --create (or one: jarl zone create NETWORK --view VIEW --fill)")
fmt.Fprintln(w, "\nCreate yours, filled: jarl zone suggest-reverse --create. A public one only if the address is yours:\njarl zone create NETWORK --view VIEW --fill (or hide it: --dismiss NETWORK --view VIEW).")
})
}
if len(sugg) == 0 {
fmt.Println("No reverse zones missing.")
return nil
}
var reqs []api.ChangeRequest
for _, s := range sugg {
if s.Group != "yours" {
continue
}
body := map[string]any{"name": s.Prefix, "fill": true}
if s.ViewID != 0 {
body["view_id"] = s.ViewID
@@ -1139,9 +1163,16 @@ PTR records, in one changeset (docs/reverse.md).`,
}
reqs = append(reqs, api.ChangeRequest{Op: "zone.create", Data: data})
}
if len(reqs) == 0 {
fmt.Println("No reverse zones of yours missing (public addresses are created one by one: see 'jarl zone suggest-reverse').")
return nil
}
return submitBatch(c, fmt.Sprintf("%d suggested reverse zones", len(reqs)), reqs)
},
}
cmd.Flags().BoolVar(&create, "create", false, "create them all, filled with PTR records")
cmd.Flags().BoolVar(&create, "create", false, "create the ones that are yours, filled with PTR records")
cmd.Flags().StringVar(&dismiss, "dismiss", "", "hide the suggestion for this network")
cmd.Flags().StringVar(&undismiss, "undismiss", "", "show a dismissed suggestion again")
cmd.Flags().StringVar(&view, "view", "", "with --dismiss/--undismiss: the suggestion's view")
return cmd
}