Files
dnswatcher/TODO.md
T
clawbot cb89e4f8cf
check / check (push) Canceled after 0s
watcher: a port check that finds 443 closed removes its certificate entries (closes #265)
The TLS check runs only on an open port 443, and the certificate
cleanup removed an entry only when its name stopped resolving to the
address, so an entry stayed ok for as long as its port was closed.
The cleanup after the port checks now also removes the entries of an
address whose port 443 was found closed. Nothing is notified for the
removal; the Port Change already says the port closed. When the port
opens again, the next TLS check saves the certificate as new, with no
change or recovery notification. The README says both.

Judgement call: the entry goes even while none of the name's
nameservers answer, since the port check reached the address itself.

Model: opus-5-5
2026-10-13 21:27:34 +02:00

13 KiB

Workflow

  • branch (from next)
  • do the work in Next Step
  • move Next Step to the top of Completed Steps
  • move the top item of Future Steps into Next Step
  • commit (TODO.md changes in the same commit as the work)
  • push
  • open a PR against next

Status

pre-1.0. No git tags. Work lands on next by PR. Open work for 1.0 is tracked on the 1.0 milestone: https://git.eeqj.de/sneak/dnswatcher/milestone/7

Next Step

trial run of the finished image: #149

Completed Steps

  • 2026-10-13: a port check that finds port 443 closed removes that address's certificate entries, so none is left shown as ok (closes #265).
  • 2026-10-13: a state file holding {}, or leaving out a section, loads with that section empty; the first check no longer crashes on it (closes #267).
  • 2026-10-13: a stop before the first scan completes sends no startup notification; a scan that ran to the end still sends it (closes #271).
  • 2026-10-13: a nameserver leaving a domain's delegation sends only NS Change; one leaving a hostname's zone sends NS Change, not NS Failure (closes #262).
  • 2026-10-13: DNSWATCHER_METRICS_USERNAME set without DNSWATCHER_METRICS_PASSWORD stops startup (closes #268).
  • 2026-10-13: a save syncs the state file to disk before renaming it into place, so a crash cannot leave it empty and stop the next start (closes #264).
  • 2026-10-13: a PORT that cannot be bound stops startup with an error naming the address, instead of running without the HTTP server (closes #266).
  • 2026-10-13: a bad PORT, DNSWATCHER_TLS_EXPIRY_WARNING or true/false setting stops startup instead of being read as 0 or false (closes #269).
  • 2026-10-13: a check saving the state while the dashboard or /api/v1/status is read cannot crash the process (closes #261).
  • 2026-10-06: canonical files re-fetched from sneak/prompts at dd4027b; lint and test are phases of the Dockerfile, every scripted build uncached (closes #257).
  • 2026-10-05: a name listed in DNSWATCHER_SKIP_RECORD_NOTIFICATIONS gets no Record Change or Inconsistency notification; a name listed there that is not in DNSWATCHER_TARGETS stops startup (closes #255).
  • 2026-10-02: a nameserver whose query for one record type failed while the others answered with no records is ok, not nodata (closes #253).
  • 2026-10-02: a domain that does not exist is shown so, with no nameservers; no name gets a parent's nameservers when its own did not answer (closes #222).
  • 2026-10-02: the refused-query test sends one query to four operators' public resolvers in turn until one replies, not eight to one operator (closes #251).
  • 2026-10-02: a name removed from DNSWATCHER_TARGETS leaves the state, and so the dashboard and API, at startup, before the first check (closes #223).
  • 2026-10-02: a record type whose query to a nameserver fails keeps its previous records and alerts nothing; the other types are still saved (closes #231).
  • 2026-10-02: a Port Change notification lists the port's domains on a Domains: line and its hostnames on a Hostnames: line (closes #248).
  • 2026-10-02: the dashboard's Ports table and /api/v1/status port entries list a port's domains apart from its hostnames (closes #245).
  • 2026-10-02: nameservers a referral names without addresses are looked up, three deep at most; pool.ntp.org's nameservers resolve (closes #221).
  • 2026-10-02: an apex domain is not counted or listed as a hostname; its records show under Domains, and notifications about them say Domain: (closes #224).
  • 2026-10-02: the dashboard lists each nameserver's record types in one fixed order, the README's, then any other type, not a random one (closes #226).
  • 2026-10-02: the dashboard and /api/v1/status show why a nameserver query or a certificate check failed, which only the state file showed (closes #225).
  • 2026-10-02: a name's CNAME is stored once per nameserver, not once per record type asked for; a state file with repeats loads each value once (closes #220).
  • 2026-10-02: a DNS lookup that shutdown cuts short logs no error; one that fails otherwise, or runs out of time, still does (closes #229).
  • 2026-10-02: Record Change and Inconsistency notifications list only the record types that differ, each with its values as plain text (closes #219).
  • 2026-10-02: the startup notification no longer says every notification endpoint works; it says it is a test sent to each of them (closes #230).
  • 2026-10-02: a Mattermost webhook that answers an HTTP error is logged as mattermost notification failed, not as a Slack failure (closes #227).
  • 2026-10-02: durations in the log are written as text such as 2m0s, not as a bare count of nanoseconds (closes #228).
  • 2026-10-02: a watched name whose nameservers answer with a CNAME and no address gets port and TLS checks at the end of its CNAME chain (closes #203).
  • 2026-10-02: a resolver test that reads one record type from a nameserver's answer asks again when that type is missing from it (closes #218).
  • 2026-10-02: a plain docker build . of a clone stamps its tag or short commit, not dev: the build context now carries .git (closes #210).
  • 2026-10-02: a query a server refuses is not resent asking for recursion, and every root server refusing is reported as DNS interception (closes #206).
  • 2026-10-02: a push to a branch cancels that branch's older CI run, and the checkout leaves no token in .git/config (closes #216).
  • 2026-10-02: watcher tests send far fewer queries and a live attempt may take 18s; nameserver addresses are asked only for A, AAAA, CNAME (closes #214).
  • 2026-10-02: the resolver tries root servers, and every other server list it walks, in a random order each time, not always from the top (closes #138).
  • 2026-10-02: a name listed more than once in DNSWATCHER_TARGETS, in any letter case or with a trailing dot, is watched once (closes #207).
  • 2026-10-01: README checked against the code and corrected: metrics, CORS, notification retries, CNAMEs, state file fields, Design tree (closes #108).
  • 2026-10-01: a certificate within the expiry warning period is warned about on every TLS check, where some checks used to skip it at random (closes #204).
  • 2026-10-01: a domain's NS set is its delegation from the parent zone's servers, not whichever of its own servers answered first (closes #200).
  • 2026-10-01: README has Getting Started, Rationale and TODO sections, and its Architecture section is now Design, in the order policy sets (closes #173).
  • 2026-10-01: a zone's server that answers SERVFAIL or a referral leading no closer is passed over for the next, as one that times out is (closes #197).
  • 2026-10-01: when none of a configured name's nameservers answered, the port state saved for its addresses is kept, not removed (closes #193).
  • 2026-10-01: ResolveIPAddresses returns an error, not no addresses, when no nameserver of the name's zone answered (closes #190).
  • 2026-10-01: make fmt and make fmt-check cover Markdown with prettier, run in Docker at the version pinned by yarn.lock (closes #119).
  • 2026-10-01: make fmt-check fails on a file goimports would change; both format scripts run goimports at its pinned commit, not from PATH (#119).
  • 2026-10-01: a hostname is queried at the servers of the zone it is in, found by following delegations for the name, not its last two labels (closes #189).
  • 2026-10-01: each nameserver's addresses are saved with its domain, and a change while it stays in the delegation is notified (closes #105).
  • 2026-10-01: the watcher saves state when it stops, and shutdown waits for that save, so it no longer relies on the state's own stop hook (closes #114).
  • 2026-10-01: DNSWATCHER_SENTRY_DSN reports panics in HTTP handlers to Sentry, and a DSN Sentry cannot parse stops startup (closes #107).
  • 2026-10-01: a port or TLS check that shutdown cuts short saves nothing and sends no notification, as a cut-short DNS lookup already did (closes #185).
  • 2026-10-01: the client address from X-Forwarded-For is the last entry that is not a trusted proxy, not the first, which the client sets (closes #181).
  • 2026-10-01: a nameserver that does not answer is saved as error with the reason, and NS failure and NS recovery are notified (closes #104).
  • 2026-10-01: a DNSWATCHER_DNS_INTERVAL or DNSWATCHER_TLS_INTERVAL that is not a positive duration stops startup; empty means the default (closes #177).
  • 2026-10-01: /metrics allows each client address 30 requests a minute, counted before Basic Auth, and answers 429 beyond that (closes #101).
  • 2026-10-01: the image built by make docker reports the git describe version, not dev, and the startup log now shows it (closes #109).
  • 2026-10-01: two notify shutdown tests always release the delivery they hold, so a drain that returns early fails them instead of hanging (closes #176).
  • 2026-10-01: script/install-precommit asks git for the repository's git directory, so make hooks also works where .git is a file (closes #129).
  • 2026-10-01: TODO.md brought up to date: open issues listed by URL, every Completed Steps entry cut to at most two lines (closes #146).
  • 2026-10-01: wildcard CORS now applies only to the public routes, not to /metrics, and allows only the methods they serve (closes #100).
  • 2026-10-01: internal/state and internal/watcher no longer export test-only constructors: two moved to export_test.go, one is deleted (closes #111).
  • 2026-10-01: notify shutdown tests use one timing constant per meaning, name the bound they check, and require the drain's debug line (closes #116).
  • 2026-09-29: the entrypoint chowns the data directory to dnswatcher and runs dnswatcher as that user, so a host bind mount needs no chown (closes #166).
  • 2026-09-29: the live-DNS test package is renamed internal/livednstest; make lint fails when program code imports it (closes #164).
  • 2026-09-29: .golangci.yml re-fetched from sneak/prompts, with gomodguard_v2 and the org depguard test-support rule (closes #123).
  • 2026-09-29: watcher and resolver tests that look something up in DNS use the real resolver against live DNS servers (closes #159).
  • 2026-09-28: the inconsistency alert is sent once, when two nameservers start to disagree; every pair of nameservers is compared (closes #158).
  • 2026-09-28: DNS names in record values (CNAME, MX, SRV and NS targets) are lower-cased, so letter case alone is not a change (closes #157).
  • 2026-09-28: lint and tests run on every build: script/cibuild and script/docker pass --no-cache-filter=lint,builder (closes #115).
  • 2026-09-28: the server timeout test drives Run and checks the timeouts on the http.Server it serves (closes #120).
  • 2026-09-28: upaas deploy readiness: the image runs as user dnswatcher with a HEALTHCHECK; README "Running under upaas" (closes #147).
  • 2026-09-21: added behavioural tests for internal/globals, internal/healthcheck, and internal/logger (closes #110).
  • 2026-09-21: go mod tidy dropped the redundant golang.org/x/sync // indirect line so script/bootstrap leaves a clean tree (#132)
  • 2026-08-10: comment-only corrections to script/bootstrap, script/cibuild and Dockerfile.lint; no behaviour changed.
  • 2026-08-10: MIT LICENSE added at the repository root; the README's first line and License section name the licence.
  • 2026-08-10: policy scaffold present: REPO_POLICIES.md, .editorconfig, .dockerignore, CI workflow, make fmt-check, make docker, make hooks.
  • 2026-08-10: Go's test cache disabled in script/test (-count=1), so every run queries live DNS; a failed run is rerun with -v.
  • 2026-08-10: live-DNS tests made robust rather than gated (#93): a limit on concurrent lookups, retries, and a quorum across nameservers.
  • 2026-08-10: all linting moved into Docker: script/lint builds Dockerfile.lint, and the root Dockerfile has its own lint stage.
  • 2026-08-09: in-flight notification deliveries are drained at shutdown, bounded by the shutdown deadline (#106).
  • 2026-08-09: http.Server sets all four socket timeouts; WriteTimeout stays above the 60s handler timeout (#99).
  • 2026-08-09: SecurityHeaders() middleware sets HSTS, CSP and the other security headers REPO_POLICIES.md requires on every response.
  • 2026-08-07: golangci-lint bumped to v2.12.2 and .golangci.yml set to the org config; fixed the resulting goconst, dupl and lll findings.
  • 2026-07-07 Adopted scripts-to-rule-them-all: script/ entrypoints, Makefile shims, README Entrypoints section
  • 2026-02-20: iterative DNS resolver implemented
  • 2026-02-20: CI actions and go install refs pinned to commit SHAs; Gitea Actions workflow added
  • 2026-02-20: watcher monitoring orchestrator merged to main (#8)
  • 2026-02-20: DOMAINS/HOSTNAMES unified into single TARGETS config (#11)
  • 2026-02-19: TCP port connectivity checker, made concurrent with port validation; gosec G704 SSRF findings fixed without suppression
  • 2026-02-19: TLS certificate inspector with no-peer-certificates error path and IP SANs
  • 2026-02-19: gosec SSRF and formatting fixes on main
  • 2026-02-19: initial scaffold with per-nameserver DNS monitoring model

Future Steps

  • 1.0 readiness: run it with a real config and read the logs: #66
  • review toward 1.0: #144