Update getHelpPathFromRoute to point at the help pages that actually
exist: rename source-* links to provider-*, the home alias to domains,
and add coverage for login, signup, notifications, provider features,
domain history, import/export, subdomains, services, checks,
availability and whois routes.
Both documents are served no-cache but had no validator, so every
revalidation re-downloaded the full body and could never return 304.
Serve them via http.ServeContent with an ETag computed once at handler
creation, letting conditional requests answer 304. manifest.json is now
pre-rendered once instead of re-read and re-transformed per request.
The service worker controls all client-side caching, so it must never be
pinned to an old version. It was served with no explicit cache policy;
mark it no-cache so updates are always picked up.
/fonts, /img and /favicon.ico live at stable, non-content-hashed URLs, so
the 7-day immutable header meant a changed favicon, image or font would
not reach already-cached clients for up to a week.
Serve them with a revalidated policy (max-age=86400, must-revalidate) set
on the successful response instead, and drop the now-unused immutable
middleware. Only the content-hashed /_app tree keeps immutable.
The immutable middleware set Cache-Control before the handler ran, so a
missing content-hashed asset under /_app/immutable/ 404'd with the 7-day
immutable header still attached: a stale client could pin a negative
response for a week. The stale-bundle fallback also only covered .js,
leaving dropped .css (and other) chunks exposed.
Open the asset first and set the immutable directive only on a successful
serve, so a 404 never inherits it. Broaden the stale-asset path to mark
any missing /_app/immutable/ response no-cache, still returning the
self-healing reload module for .js.
Surface the remote (externalized) checker HTTP timeout through the
standard config plumbing instead of a hidden env var: add
Options.CheckerHTTPTimeout, register the -checker-http-timeout CLI flag
(default 120s), and wire it into the shared client via SetHTTPTimeout at
startup.
The SPA references content-hashed bundles under /_app/immutable/ embedded
in the binary. After a deploy the binary ships new hashes and drops the old
ones, so a browser reusing a cached HTML page (or a stale /_app bundle)
requests hashes that 404: the page renders but the app never starts, leaving
it visible but frozen.
- routes.go: serve the page HTML (and the rewritten manifest.json) with
Cache-Control no-cache so clients always revalidate and load a page that
points at bundles which actually exist; /_app/* stays immutable.
- routes.go: when a missing /_app/immutable/*.js is requested (a stale cached
client), return a self-healing reload module (200) instead of a dead 404 so
already-stuck clients recover on their own.
The reload module is issued synchronously and throws at the end of evaluation
so the failing import() rejects: SvelteKit's bootstrap never reaches
kit.start(...), avoiding a "kit.start is not a function" TypeError. We do not
touch the Cache Storage API: the service worker manages its own cache
lifecycle and async caches.* work would lose the race against kit.start.
When updating a service on a published zone, the backend creates a new
derivative zone with a new ID. The frontend was navigating back using the
old zone ID, causing the layout loader to re-fetch the pre-update zone and
overwrite the store, making the change appear lost. Unify add and update
paths to always refresh domains and navigate to the returned zone ID.
Add a "Send Invitation" action on the admin auth-users page that
creates an account with an already verified email and emails the
recipient a recovery link worded as an invitation to try happyDomain.
The emailVerification field is optional (string | undefined), so an
unverified email yields undefined, not null. The strict !== null check
wrongly treated it as verified, showing the Verified badge and the
Mark as Unverified button. Use loose != null to catch both cases.
When -disable-embedded-login is set but neither OIDC nor -external-auth is
configured, the embedded login form stays visible to avoid lockout, making
the flag a no-op. Emit a startup warning so operators understand why login
is still available.
Hash variable-length key components (checker names, targets, session
hashes) and compact the reverse-chrono segment so secondary index and
notification-state keys stay within the 64-char limit some KV backends
enforce. Add migrateFrom12 to rewrite the affected keys and key_size_test
to assert the bound for every key builder.
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.
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.