Updates golangci-lint to v2.12.2 and sets `.golangci.yml` to the org-standard v2-schema config already deployed across the org's repos. The config change is owner-authorized (see #96 (comment) and #96 (comment)); the same file is being landed as canonical via prompts PR #24 (sneak/prompts#24).
## Changes
- **Commit-pinned installs**: golangci-lint pinned to commit `c0d3ddc9cf3faa61a4e378e879ece580256d76e5` (v2.12.2, released 2026-05-06) in `Dockerfile` and `script/bootstrap`.
- **`.golangci.yml` set to the org-standard v2 config** (sha256 `021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`), byte-identical to the file used across the org's other repos. Settings live under `linters.settings`, so the `lll`/`funlen`/`cyclop`/`dupl` thresholds are actually applied (under the old hybrid file, v2 silently ignored the top-level `linters-settings` block).
- **Lint fixes** required by the now-active thresholds:
- `goconst`: shared constants for repeated status/priority/DNS-fixture strings in `internal/watcher/watcher.go` and the notify, state, and watcher tests
- `dupl`: consolidated duplicated ntfy/slack HTTP-error tests and SendNotification endpoint-error tests behind shared helpers in `internal/notify/delivery_test.go`
- `lll`: wrapped long test table entries and comments in `internal/config/classify_test.go`, `internal/notify/history_test.go`, `internal/state/state_test.go`, `internal/watcher/watcher_test.go`; shortened one inline nolint justification in `internal/notify/retry.go`
- **`TODO.md`**: Completed Steps entry updated in the same commit.
- Rebased onto current `main` (`f79cd98`); the branch is one clean commit.
## Notes
- v2.12 deprecates the `gomodguard` linter in favor of `gomodguard_v2`. The org-standard config does not disable the deprecated linter, so golangci-lint may emit an informational deprecation warning; this is accepted by the owner and does not affect the exit status (this exact config+code combination was CI-green at `dea7e44`).
## Verification
- `make check` exits 0 (fmt-check, tests, lint)
- `make lint`: 0 issues; no deprecation warning surfaced in the runs performed
- sha256 of `.golangci.yml` at HEAD verified equal to `021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb`
Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #96
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
When set to a truthy value, sends a startup status notification to all configured notification channels after the first full scan completes on application startup. The notification is clearly an all-ok/success message showing the number of monitored domains, hostnames, ports, and certificates.
Changes:
- Added `SendTestNotification` config field reading `DNSWATCHER_SEND_TEST_NOTIFICATION`
- Added `maybeSendTestNotification()` in watcher, called after initial `RunOnce` in `Run`
- Added 3 watcher tests (enabled via Run, enabled via RunOnce alone, disabled)
- Added config tests for the new field
- Updated README: env var table, example .env, Docker example
Closes#84
Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: #85
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
## Summary
DNS checks now always complete before port or TLS checks begin, ensuring those checks use freshly resolved IP addresses instead of potentially stale ones from a previous cycle.
## Problem
Port and TLS checks read IP addresses from state that was populated during the most recent DNS check. If DNS changes between cycles, port/TLS checks may target stale IPs. In particular, when the TLS ticker fired (every 12h), it ran `runTLSChecks` without refreshing DNS first — meaning TLS checks could use IPs that were up to 12 hours old.
## Changes
- **Extract `runDNSChecks()`** from the former `runDNSAndPortChecks()` so DNS resolution can be invoked independently as a prerequisite for any check type.
- **TLS ticker now runs DNS first**: When the TLS ticker fires, DNS checks run before TLS checks, ensuring fresh IPs.
- **`RunOnce` uses explicit 3-phase ordering**: DNS → ports → TLS. Port checks must complete before TLS because TLS checks only target IPs where port 443 is open.
- **New test `TestDNSRunsBeforePortAndTLSChecks`**: Verifies that when DNS IPs change between cycles, port and TLS checks pick up the new IPs.
- **README updated**: Monitoring lifecycle section now documents the DNS-first ordering guarantee.
## Check ordering
| Trigger | Phase 1 | Phase 2 | Phase 3 |
|---------|---------|---------|----------|
| Startup (`RunOnce`) | DNS | Ports | TLS |
| DNS ticker | DNS | Ports | — |
| TLS ticker | DNS | — | TLS |
closes #58
Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: #64
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
checkTLSExpiry fired every monitoring cycle with no deduplication,
causing notification spam for expiring certificates. Added an
in-memory map tracking the last notification time per domain/IP
pair, suppressing re-notification within the TLS check interval.
Added TestTLSExpiryWarningDedup to verify deduplication works.
collectIPs only reads HostnameState, but checkDomain only stored
DomainState (nameservers). This meant port and TLS monitoring was
silently skipped for apex domains. Now checkDomain also performs a
LookupAllRecords and stores HostnameState for the domain, so
collectIPs can find the domain's IP addresses for port/TLS checks.
Added TestDomainPortAndTLSChecks to verify the fix.
- CheckPorts now runs all port checks concurrently using errgroup
- Added port number validation (1-65535) with ErrInvalidPort sentinel error
- Updated PortChecker interface to use *PortResult return type
- Added tests for invalid port numbers (0, negative, >65535)
- All checks pass (make check clean)