Backup payloads carry the source SchemaVersion but Restore ignored it,
silently importing data shaped for a different schema. Compare against
the current store schema upfront and abort with a clear error;
versionless legacy dumps (Version == 0) are still accepted.
A backup payload whose service body lost its inner pointer (e.g. a PTR
without Record) caused Service.MarshalJSON to panic during restore,
aborting the whole zone write. Wrap GenComment/GetNbResources in a
recover so the surrounding zone still persists, and nil-guard
PTR.GenComment to avoid the dereference in the first place.
Users no longer need to know the dot-delimited token format; the provider
form now collects the public prefix and secret separately and joins them
when constructing the libdns provider.
Distinguish full-scan call sites from point lookups that share the same
operation+entity labels (e.g. GetAuthUserByEmail vs GetAuthUser), and
extend the duration histogram buckets to 30s so scan tails are visible
instead of collapsing into +Inf.
Bump checker-sdk-go to v1.10.0 and apply the whole-checker eligibility
verdict (Eligible/EligibilityReason from POST /definition) with fail-open
semantics: hide/skip a checker only on a definitive negative. The listing
flow drops ineligible checkers from the "what runs" view and annotates them
in the management view; the scheduler honours the verdict at launch time.
Introduce a DomainAvailabilityWatch entity (model, storage, usecase and
REST endpoints) letting a user track a domain they do not own and get
notified the moment it becomes available for registration. A dedicated
domain_availability checker reads WHOIS/RDAP via pkg/domaininfo and inverts
the status (OK while registered, Crit once free) so the existing dispatcher
fires exactly once on the transition. The scheduler enumerates watches and
enqueues the check, carrying the watch id in CheckTarget.DomainId; autofill
and notification payloads fall back to the watch store to resolve the name.
Watches are included in per-user backup/restore. The web UI adds an
availability watchlist page and navigation entry.
Make Domain.ProviderId optional so a user can monitor the registration
status (WHOIS/expiry, lock, propagation) of a domain hosted on a provider
happyDomain does not support. Add Domain.IsManaged() and guard the
provider-coupled paths (zone import, correction listing, tidy) so a
monitor-only domain degrades cleanly instead of dereferencing a missing
provider. The web add-domain flow gains a "monitor only" option and the
domain detail view hides zone editing for these domains.
Deletes from the retention janitor and tidy passes leave tombstones that
LevelDB only compacts opportunistically, slowing prefix scans for hours. A
background worker now runs a full-keyspace CompactRange on a configurable
interval (-leveldb-compaction-interval, default 24h, 0 disables). Its
lifecycle is bound to the store: Close stops the worker before closing the
DB so no compaction races the close.
Open LevelDB with a larger block cache, write buffer, open-files cache,
compaction table size and a Bloom filter instead of the small goleveldb
defaults, since the workload is dominated by point lookups. All values
are exposed as flags so operators can tune them per deployment.
Also fix the recovery path discarding the recovered DB handle, which
left the storage with a nil database after a successful RecoverFile.
A checker ID is embedded verbatim in several "|"-delimited storage keys
(chckrcfg|, chckpln-chkr|, chckexec-chkr|, ...). An ID containing "|"
would misalign key parsing and let records collide. Validate at the
registration boundary and refuse such IDs.
Route every backend write through a shared storage.Marshal helper that uses
a json.Encoder with SetEscapeHTML(false), so stored payloads no longer waste
encoder work escaping <, > and & that never reach a browser as raw HTML.
The fallback path in DeleteExecutionsByChecker and DeleteEvaluationsByChecker
scanned whole secondary-index prefixes per orphan, turning a delete loop into
O(orphans x index size). Rebuild the user and domain execution index keys
directly from the checker key's shared trailing segments instead, and leave the
planId-first plan index, unknowable from a missing primary, to the Tidy jobs.
Also remove the now-dead deleteCheckPlanSecondaryIndexesByPlanID.
Align the notifrec-user index with the notifch/notifpref pattern: the
index now holds an empty value and records are resolved through the
primary key, dropping the previous double-write of the full record.
The checker, user and domain execution indexes and both evaluation
indexes now embed a reverse-chronological time segment, so a forward
prefix scan returns the newest entries first and stops at the requested
limit instead of loading every match and sorting it in memory.
GetLatestEvaluation becomes a single-row read. migrateFrom11 (still
unpublished) rebuilds the affected indexes from the primary records.
Two concurrent writers targeting the same observation-ref primary key could
both observe the same old snapshot id, each stage a delete for it in their
batch, and each commit its own snap-index. The loser's snap-index would
outlive its primary write and the next cascade delete of THAT snapshot
would erase a primary that no longer belongs to it.
Serialize the Get and the batch commit per primary key through a 64-shard
mutex table on KVStorage, picked by FNV-1a hash of the primary key.
Migrate Update/Delete/Restore variants for executions, plans, evaluations,
channels, preferences, records, plus ReplaceDiscoveryEntries and the
cascading deletes to use the Batch primitive, so primary records and their
secondary indexes are committed atomically instead of leaving the tidy job
load-bearing.
Replace the non-standard HAPPYDOMAIN_SOCKET variable with
HAPPYDOMAIN_ADMIN_BIND, matching the config env var. Handle TCP bind
addresses (:8080, 0.0.0.0:8080, [::]:8080) in addition to Unix sockets.
Move the by-name sort into listScopedCheckers so every consumer gets a
stable ordering, and drop the redundant sort wrappers in CheckerListPage
and CheckResultsDashboard.
Add with_availables query param to checker status endpoint; restrict
default listing to checkers that auto-schedule or have an active plan.
Introduce IsAutoScheduled helper unifying scheduler eligibility logic.
The route at /domains/[dn]/checks listed checker configurations, not check
runs. Rename it to /checkers and use /checks for a new aggregated dashboard
that surfaces the latest result for every enabled checker across the domain
and its services in one place. Same split applied at service scope.
Expose GET /api/users/stats returning provider, domain and zone counts
per user. Zone count is derived from the ZoneHistory length of each
domain. Wire the new endpoint in the admin UI as a sortable table at
/users/stats.
Refactors the admin backup usecase to share per-user collection logic
via backupOneUser(), then adds BackupUser() which filters system-wide
collections (checker configs, plans, evaluations, executions, discovery)
down to the requesting user by UserId.
Wires the backup usecase into the public API dependency graph and adds
a download button in the /me settings page near the delete-account
section.
Replace the per-route editableGroups/readOnlyGroups callback pair in
CheckerConfigPage with a single groups prop that returns both lists, and
switch all call-sites to buildOptionGroupLayout. Add showCheckerInfo and
showExecutions flags so the admin checker page can reuse CheckerConfigPage
without schedule controls or execution links.
CheckerOptionsPanel now shows an "Overriding inherited" badge and a
"Reset to inherited" link for fields where a local value shadows an
inherited one. CheckerOptionsGroups shows the effective inherited value
next to the type hint in read-only groups.
Extend collectAllOptionDocs, splitPositionalOptions, and
collectAutoFillKeys to include serviceOpts alongside the existing
adminOpts/userOpts/domainOpts buckets.
Add buildOptionGroupLayout which derives editable/read-only group lists
from the checker's option documentation and a CheckerPageScope ("admin",
"domain", or "service"). This consolidates the per-route hand-curated
arrays into one place.
Improve splitPositionalOptions with an optional isCurrentScope predicate
so the correct positional is selected for the page's scope instead of
always taking the last stored entry.
Make CheckerScope.domainId optional so callers can target the global
admin scope (no domain). getScopedCheckStatus, getScopedCheckOptions,
and updateScopedCheckOptions now short-circuit to the admin /checkers/*
routes when domainId is absent. All narrower-scope callsites gain the
non-null assertion required by the type change.
Callers like MergeCheckerOptions and SetCheckerOption mutated the map
returned by getScopedOptions before persisting; the store may hand back
a shared reference so mutating it caused silent state corruption. Return
a copy instead.
Add filterOptionsForScope which drops auto-fill keys (system-provided at
runtime) and NoOverride keys declared at a broader scope, so saving at a
narrow scope cannot accidentally persist fields that belong elsewhere.
Add a `disableMetrics` store that gets set when metrics fail to load,
preventing users from switching to a broken metrics view. Also disable
the JSON view button when no observations data is present.
WHOIS data may report statuses as "client transfer prohibited" (spaced,
lowercase) while the configured required value is camelCase
"clientTransferProhibited". Strip spaces, dashes and underscores when
comparing so all common formats match.
Remove the invalid bind:rr chain between RecordLine and RecordText (RecordText only reads rr, never writes back), and use refreshDomains + invalidateAll instead of directly setting thisZone when the returned zone has a new ID, matching the pattern already used in the domain layout.