Repository navigation
Conversation
Removed outdated sections on phased deployment and roadmap from REFERENCE.md.
- Catalog merge attributed to the gateway (cvmfs_receiver), not prepub
- Lease API marked as upstream cvmfs_gateway endpoints; PUT /leases/{token} unsupported (405); removed bogus 2-min TTL
- Path-scoped leases (partial namespace), not full-catalog locks
- §10 GC: bits only pins + cleans temp; cvmfs_server gc reclaims namespace
- §4.1 table: entry point/identity = publisher node (not S0); added Privilege row; removed worker-parallelism row
- §8.3 sequence: warm quorum before the catalog flip (three-phase order)
- Removed hard-link section, Bloom references, push-as-current-option
- secret -> hidden (dropped Google-Docs analogy); generic example names
- Distribution hierarchy O(10) S1 -> O(10) squid -> O(1000) workers
…; clarify lease renewal (405) constraint - catalog table: add hash, mode, mtimens, uid, gid, xattr (full 16 columns) - document all six tables (catalog, chunks, nested_catalogs, bind_mountpoints, statistics, properties); correct statistics to the 24 self_*/subtree_* counters; note schema 2.5 / revision 7 - flags: add FlagFileSpecial=16, FlagFileExternal=128, bind-mountpoint/hidden bits, compression bits 11-13; xattr signalled by non-NULL BLOB - REST API request tables: add preload_exe, preload_paths (both modes) - job-status response table: add the 14 missing fields (timing, n_new_objects, new_root_hash, distribution_*, failed_at_state, tar/tag metadata) - §8.3 sequence: warm quorum before the catalog flip (three-phase order) - §9.3: lease renewal PUT returns 405 on stock gateway (no renewal); lease bounded by max_lease_time; --lease-retry-max must exceed it
…efault off)" This reverts commit 45d582e.
Adapt the gateway client from the body-flag graft API (cvmfs PR #4268, "direct_graft":true in the commit body) to the dedicated graft endpoint (cvmfs PR #4296, POST /api/v1/leases/<token>/graft). PR #4268 was never merged; PR #4296 is the shape that landed, and it selects the DirectGraft fast path by request type rather than a wire flag. The commit body is identical for both endpoints, so DirectGraft now only changes the target URL: Commit and CommitFinalizeOnly route to .../graft when set, and the now-unused "direct_graft" field is dropped from the body. The URL/body construction that was duplicated across both methods moves into gatewayCommitURL/gatewayCommitBody helpers. The --gateway-direct-graft flag and the DirectGraft field are unchanged; only the underlying wire mechanism moved. REFERENCE.md documents the new endpoint and notes it requires the graft support from cvmfs PR #4296 (the standard lease/payload/commit endpoints still need no gateway changes). Assisted-by: Claude (claude-opus-4-8)
A build job can reserve its target CVMFS namespace BEFORE the (expensive) build so a taken namespace fails the job immediately instead of after hours of work. A CVMFS gateway lease can be taken on a path that does not yet exist, so this is a pure check: acquire the lease in a single attempt (TryAcquireOnce — no path_busy retry) and release it right away. prepub re-acquires the lease normally at publish time, so nothing is held across the long build. Responses: 204 reservable, 409 namespace taken (path_busy), 400 bad body, 502 gateway error. Gateway-only: in single-host (local) mode there is no shared lease to conflict on, so it returns 204. Authenticated like the jobs routes. Verified: go build ./cmd/prepub/... and go vet ./internal/api/... clean.
A package/version publishes once. With DirectGraft the receiver rejects a re-publish of an existing subtree with a generic "merge_error" (TryGraftNestedCatalog: "invalid attempt to graft nested catalog into existing directory"). Two improvements, both prepub-side: * Add cvmfscatalog.PathExists: walks nested catalogs from the current root to decide whether a publish path already exists in the repository. * /reserve now rejects an already-published path up front (409) so a duplicate fails fast before it wastes a build; the lease probe alone cannot catch this (a lease on an existing path is granted). * On a DirectGraft commit merge_error, confirm existence and surface a clear terminal 'already published' error instead of the cryptic reason. Different package subdirs still commit concurrently and both succeed (DirectGraft grafts onto the current gateway root, serialised per-repo by the gateway); only a true duplicate fails, and it is never retried.
Under rapid sequential commits the receiver grafts against the base manifest it fetches from stratum0, which lags the gateway's committed state — so the next package can graft onto a base missing the parent dir the previous commit just created, yielding a spurious merge_error (observed with all-already_installed runs where commits fire ~100ms apart). Add waitForManifestPropagation: while the per-repo commit lock is held, block until stratum0's published root advances past the base we committed against, then release so the next commit sees a current base. Applied after both the content commit and the ensureParentDirs (mkdir-p) commit. It is a barrier, not a retry: bounded (60s), and on timeout it logs loudly and proceeds (the commit already succeeded) rather than hanging. Replaces the single post-commit manifest fetch (which also recorded j.NewRootHash).
The serialize-until-published barrier only runs after a *successful* commit, so it cannot prevent the cold-start burst on a fresh arch tree: many jobs concurrently commit content against a base whose parent dirs (created by ensureParentDirs under a *separate* lock acquisition) are not yet committed or propagated, all fail merge_error, and the publish loop exits on the first failure (no retry). Acquire the per-repo commit lock BEFORE ensureParentDirs (Phase 2.65) and hold it through the pre-commit lease + SubmitPayload (2.7) and the content commit + barrier (Phase 4), so each package's parent-dir creation and content graft form one serialised, fully-propagated unit. ensureParentDirs no longer takes the lock itself. Root-level publishes still acquire at the pre-Phase-3 position via a commitLockHeld guard. Lock acquisition is factored into acquireCommitLock.
…Phase 1) Projects []cvmfscatalog.Entry into the canonical ingestsql SQLite descriptor (schema_revision 4: dirs/files/links/deletions/properties, copied verbatim from cvmfs swissknife_ingestsql.cc). Content is referenced by unsuffixed hex hash (single blob or ordered chunk list); files carry no xattr and dirs no ACL (bits has none); nested from Entry.IsNestedRoot; compressed from CompAlgo. Enforces ingestsql's fixed-chunk rule (ceil(size/24MiB) hashes) so content- defined chunking reaching this path is rejected rather than silently miscommitted. First step toward replacing the Go catalog builder (pkg/cvmfscatalog) with descriptor + ingestsql. go build/vet/test pass.
…Phase 2)
Introduces the coarse, publish-at-end-of-build data path:
* internal/buildset: per-package jobs Record their catalog entries + bits
fingerprint into a build-scoped accumulator; Load reads them back; Assemble
merges members into one repo-relative []Entry (prefixing package-relative
paths and marking each package root as a nested catalog), applying the
bits-hash dedup/conflict rule (same path+same fingerprint = idempotent;
differing = excluded Conflict / partial success); Finalize writes one
ingestsql descriptor (pkg/cvmfsdescriptor) and publishes the whole set in a
single commit via cvmfs_swissknife ingestsql.
* job.Job.BuildID groups a build's package jobs; submitJob accepts build_id
(JSON + multipart). Empty preserves legacy per-package commit.
Unit-tested (record/load round-trip, expand+nest, dedup, conflict). go build/
vet/test pass across the module. Orchestrator accumulate-branch, the
StateAccumulated FSM state, and the POST /builds/{id}/finalize endpoint are the
remaining live-wiring step.
…R-0007 Phase 2)
Live-wiring of the buildset accumulator:
* StateAccumulated FSM state (terminal): a coarse-publish package job whose
objects are uploaded/pre-warmed and whose catalog entries are recorded,
awaiting the build finalize. Registered in the spool state dirs + lookups;
reachable from StateUploading/StateDistributing.
* Orchestrator.Run: when j.BuildID is set (gateway mode), record the package's
entries into the build accumulator and finish in StateAccumulated instead of
acquiring a lease and committing. Empty BuildID keeps the legacy path.
* POST /api/v1/builds/{id}/finalize (authenticated): assemble the build's
accumulated members into one descriptor and publish them in a single commit
via buildset.Finalize (ingestsql). Ingestsql runtime params (swissknife,
-C config prefix, lease, env) come in the request body; repo + packages come
from the members. Reports published/conflicts (validate-then-commit).
* buildset.Member gains Repo (a build targets one repo).
go build + vet clean; job/spool/buildset/api/descriptor tests pass. (Pre-existing
unrelated failure in cmd/prepub main_test.go applyFileConfig arg count is not
touched by this change.)
Pre-existing drift: commit 4792930 (chunk min/avg/max overridable via config.yaml) added three *int64 params to applyFileConfig but did not update the test helper, so cmd/prepub failed to compile under 'go test'. Add the chunk fields to applyTestVars and pass them. Test-only; no behaviour change.
…est (ADR-0007) Testbed validation of a 2-package coarse build panicked in ingestsql (catalog_mgr_rw.cc: 'catalog for directory ... cannot be found'): ingestsql does not reliably auto-create the intermediate ancestor directories of a *branching* multi-package tree — it needs them in the descriptor. Assemble now synthesises a directory entry for every ancestor path between the build's common lease root and each entry (package roots keep their nested marking; ancestors above the lease root are left out — they are the graft attach point). Verified on the testbed: the same 2-package build then published in one commit (rev 35->36) with both package roots as nested catalogs. Unit test TestAssembleFillsIntermediateDirs covers it.
…R-0007 Phase 2) E2E on the testbed: the pipeline emits a package's root entry as FullPath='.' (and paths may carry a leading './'); expand now normalises both to the package base (previously '.' produced 'base/.'). Add cmd/prepub-finalize: a host-side tool that Loads a build's accumulated members and runs buildset.Finalize (one ingestsql commit). This is the deployment-correct finalize mechanism — the containerized prepub cannot run ingestsql (no swissknife, no store mount); it commits via the gateway, while ingestsql + the store live on the release-manager host.
…ADR-0007)
Per review, the coarse-publish finalize is now a normal prepub job so the console
reuses its existing submit+poll machinery instead of a bespoke endpoint call:
* job.Job.Finalize marks a payload-less job that publishes all of BuildID's
accumulated packages in one ingestsql commit; submitJob accepts finalize=true
(no tar) with a build_id (multipart).
* Orchestrator.Run short-circuits finalize jobs to Orchestrator.FinalizeBuild
(extracted, reused by the /builds/{id}/finalize endpoint too); FSM allows
incoming→committing for them.
* Ingest settings (swissknife path, -C config prefix, env) move to server/
orchestrator config (IngestSwissknife/IngestConfigPrefix/IngestEnv) so the
finalize job uses the prepub's own config rather than per-request params.
go build/vet and all 30 test packages pass.
… (ADR-0007) Wire the coarse-publish finalize ingest settings from the CLI to the Orchestrator (IngestSwissknife/IngestConfigPrefix/IngestEnv). Empty --ingest-config-prefix leaves finalize disabled (finalize jobs return a clear error). splitCSV helper parses --ingest-env into KEY=VAL entries.
…ingest ADR-0006 (superseded): gateway returns authoritative root on acquire. ADR-0007 (decided/implemented): coarse publish via descriptor + ingestsql — Variant A, publish-at-end-of-build, buildset accumulator + finalize, the ingestsql spooler-follows-upstream fix, Phase 0/2 testbed validation, and the multi-host S3 deployment analysis.
…R-0007) Document the coarse-publish API (build_id package jobs + finalize job), the deployment requirements (patched cvmfs_swissknife + LD_LIBRARY_PATH, the ingestsql gateway-client config prefix, the --ingest-* prepub flags, and the CAS=shared-store object-flow requirement), and the bits-console PREPUB_COARSE opt-in.
BuildSubtree rewrote a bare-file tar's entries but never created a directory entry for the nested-catalog root, so its mount-point in the parent catalog was born nameless (empty Name) and the directory appeared empty in the client (e.g. Modules/modulefiles/<pkg>). Synthesize the named root entry when absent, mirroring the coarse-publish buildset.expand() behaviour. Per-package path only; the coarse/ingestsql path already handled this.
ingestsql auto-detects the lease as the shallowest descriptor path and grafts it into its parent, which must already exist in the repo. Using the full common prefix breaks when it is deep and its parent is absent (a build whose paths are all under Packages/* with no modulefiles => lease .../Packages, parent missing => ingestsql aborts). Synthesize ancestors up to the first path component so the lease's parent is always the repo root. No change for normal builds whose common prefix is already a single component (e.g. x86_64-el10).
Coarse publish via ingestsql assumes fixed 24 MiB chunk boundaries and the descriptor emitter enforces chunk-count == ceil(size/24MiB). With coarse now the default publish mode, default the chunker to min==avg==max==24 MiB (fixed cuts) so it works out of the box. Per-package-only deployments that want finer dedup can set content-defined sizes via flags or config.yaml.
…efault The pre-commit pull announce (S1 pre-warming) was emitted whenever the broker was configured. With no S1 receivers there is nothing to warm and a warm gate must never block a commit, so gate the announce behind --prewarm (default off). Enable it once authoritative receivers exist. Post-commit pull (manifests + published broadcast) is unaffected, so receivers still converge normally.
Add an allowlist of authorized CVMFS group-root paths (--allowed-publish-prefix / allowed_publish_prefixes in config.yaml). reserve and submit now reject any target that resolves outside every configured root (403), so a build cannot reserve or publish into another group's namespace even with a valid prepub token. path.Clean collapses traversal (..) before the check. Empty allowlist leaves the check disabled (single-namespace deployments unchanged). Finalize is exempt: it carries no path and only commits packages already checked at submit. For a group whose user area is a sibling of releases/ (…/<group>/user vs …/<group>/releases), list the group ROOT so both are covered.
…e review
Adversarial review of cvmfs-prepub (the pipeline/CAS path was already clean;
findings are in local single-host mode + the API/provenance edges):
* extractTar link-then-write (HIGH): a malicious tar could plant a symlink
entry (x -> /etc/…) then a regular-file/hard-link entry at x or x/f — the
lexical prefix check passed while the real open() followed the symlink,
writing attacker content outside the CVMFS repo (as the publish user,
often root), or reading an arbitrary host file INTO the repo via a
hard-link source. Now every write target and hard-link source has its
parent chain Lstat-verified symlink-free (rejectSymlinkComponents, run
before any MkdirAll walks it) and the final element is refused if it is an
existing symlink. The CAS/pipeline path never touches the filesystem, so
it was never exposed.
* sanitizeID('..')=='..' (MED): buildDir(spool,'..') resolved to the spool
ROOT, so an authenticated publisher submitting build_id='..' could write
member records into — and, on finalize, os.RemoveAll — the whole spool.
Empty/'.'/'..'/all-dots now map to '_invalid_'.
* OIDC (MED): ValidateOIDCToken now pins the signature algorithm family
(never HMAC/none — alg-confusion) and, when PREPUB_OIDC_AUDIENCE is set,
binds the token to our audience. CI OIDC issuers are global, so without an
audience check any workflow anywhere obtained Verified=true (confused
deputy). Audience empty = not enforced (backward compatible).
* reserveHandler (LOW-MED): body capped at 1 MiB (io.LimitReader) like the
submit path, and req.Repo runs through broker.ValidateRepo — it previously
reached the Stratum0 URL builder unvalidated.
* Stratum0/gateway HTTP (LOW): pkg/cvmfscatalog now uses a 60s-timeout
client instead of http.DefaultClient (no timeout) — a dribbling Stratum0
could otherwise hang an orchestrator goroutine indefinitely (JobTimeout
defaults to disabled).
New tests: link-then-write and hard-link-through-symlink-source against
extractTar; build_id traversal against sanitizeID.
NOTE: go build/test could NOT be run in this environment (no working Go
toolchain in the sandbox; the build host is currently unreachable). Verified
gofmt-clean on every file and manually type-checked all touched symbols
(incl. golang-jwt/v5 WithAudience/WithValidMethods/ParserOption). go build
./... and go test ./... MUST be run on the build host before cutting the
release.
The 87-package O2 finalize put its workdir (descriptor.db + ingestsql -t
scratch) in the container /tmp via os.MkdirTemp("") — a whole-build
assembly can exceed a small tmpfs, and the resulting mkstemp ENOSPC inside
ingestsql surfaced as an unexplained SIGSEGV. Create the workdir under the
spool root instead (the sized, persistent volume; dot-prefixed, and the
spool only scans its state subdirectories so it is ignored).
Also log the tail of the captured ingestsql output when the background
finalize job fails: the guard/crash reason is only in that stderr, and the
client-facing job error is sanitized for internal failures — previously the
output was dropped entirely on this path (the empty job .output observed in
production). go test ./... green (30 packages).
Accumulated content objects are unreferenced by any published catalog until the build's finalize commit, so storage lost between accumulation and finalize (volume wipe on redeploy, GC) previously published a fully browsable tree whose every file read fails with EIO — seen in production when the O2 build's finalize was retried after a testbed rebuild. preflightObjects() samples up to two referenced objects per member (chunked files probe hex(chunk0)+"P", plain files hex(hash); dirs/symlinks/ deletions/empty files skipped), capped at 200 CAS.Exists probes with an even stride so every region of the build is represented. Any miss aborts the finalize with an error naming the first missing object and its path and advising a re-publish. Nil CAS (non-gateway deployments) skips the check. Unit tests cover pass, missing-object failure (named path + advice), skipped entry kinds, and the nil-CAS path. go test ./... green (30 pkgs).
PREPUB_MAX_CONCURRENT_JOBS / PREPUB_MIN_CONCURRENT_JOBS join the other .env-driven knobs, so the job-concurrency ceiling — the parallelism term in the throughput-bound window — can be swept without a redeploy. Both already appear in the startup tuning banner.
CI OIDC issuers are global, so an unset audience lets any workflow obtain Verified=true. provenance.New now refuses to start (Config.checkOIDCAudience) when issuers are configured but PREPUB_OIDC_AUDIENCE is empty; cmd/prepub exits non-zero. Covered by config_test.go; verified with go build/vet/test.
Remove the receiver-side fallback that derived its broker key from PREPUB_HMAC_SECRET, and drop the vestigial receiver HMACSecret requirement (no verification consumer) - a receiver now holds only its own PREPUB_NODE_KEY, so a compromise can't derive other nodes' keys or mint publisher tokens. Add `prepub node-key <node>` to provision a receiver's key on the publisher.
Clearer name for the per-node broker enrollment key read by cvmfs-prepub receivers (Stratum-1 nodes). No behaviour change.
Replace FIFO bucket eviction with O(1) LRU (container/list): every request refreshes its IP to most-recently-used, so a spoofed-IP flood evicts other one-shot entries instead of active legitimate clients. Clamp maxIPs>=1. Add ratelimit_test (LRU + token budget; -race and negative-control verified).
Resolve a symlinked spool (SELinux 226/NAMESPACE), make the CAS dir optional in the unit, read cas/spool paths from config.yaml, exact group checks.
Decide 'mounted' from the mount table, not from /cvmfs/<repo> existing; ask the stratum0 which parents are published and ingest the missing ones.
The directory-chain ingest crashed swissknife and at -b / its changes were dropped by the receiver merge. Needs CVMFS_GW_MKDIR_PARENTS on the gateway.
Backoff 1-30 min within retry_window (24h); conflicts and bad payloads fail at once. Payload deleted on any final state; attempts and last_error shown per job.
The old 'local publish mode' message also fired for the ingest backend and misled operators.
bits no longer exports NO_FIELDS; compare the empty field-set digest.
…aged-ingest # Conflicts: # REFERENCE.md
ADR, MEASUREMENTS and review ids pointed at documents that are not part of the repository; the design plans move out of the tree.
Drops warm quorum, /s1/catchup, /s1/{txn}/lease, GC pins and prepubctl.
The post-commit root-catalog pull now works and is hash-verified; one
--receiver-stratum0-url serves both paths; YAML false is honoured.
Checked against the code; removed features and duplicated sections dropped, undocumented endpoints and flags added. Outdated diagrams removed.
Persisted revocation, stricter discovery and provenance checks, input validation, recovery and shutdown fixes, CVMFS-compatible catalog flags and hashes, role-aware install.sh; docs updated; Apache-2.0 headers throughout.
install.sh --prewarm/--no-prewarm sets prewarm in config.yaml. Published-bytes counter for throughput in MB/s.
replace=true on the job; replace_on_conflict on the node only allows it. Replaced before the commit when the published hash is readable and differs; a failed commit never deletes. Health reports replace_allowed.
Records the wait for the per-repo commit lock. Keeps downloaded catalogs by hash (read-only, size-capped, LRU) in CacheDirectory or <spool>/catalog-cache.
Each ingest logs its output lines with when they arrived, so a slow publish can be split into transaction, swissknife and close. Records gain the pre-commit check and ancestors times.
install.sh --s3-conf-from writes cas.server_conf from the repository's S3 config (keys refreshed on update, tuning kept); prepub passes it to cvmfs_server ingest as --s3-config.
…tore SELinux labels connect-gw runs once when the gateway key is in place (mountless unless --mounted); the service user comes from CVMFS_USER when not given; the S3 source defaults to the file the copy names; restorecon on the installed paths.
…t writes under prepub refuses to start when the S3 config names another alias than the store's.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ingest and staged publish paths, reliable spool, hardening
Merges
fix/release-hardeningintomaster: 119 commits, 241 files, +30k/−12k lines, about half of them tests. It carries the staged-ingest work that bits.cern.ch production runs on cvmfs-bits-01, plus the clean-up and hardening done for the release.masterhas no commits that are not on this branch, so the merge is conflict-free (it can fast-forward).What changes
Publish paths. A job chooses how its package reaches the repository (
publish_path); one prepub serves several paths at once.cvmfs_server ingeston a mountless publisher, through the gateway. Missing parent directories are created through the gateway; a per-job--direct-s3sends data objects straight to S3, and the publisher reports the objects it uploaded over an inherited pipe. Cancelling a publish kills the wholecvmfs_serverprocess group.POST /api/v1/leases/<token>/graft(cvmfs PR #4296), replacing the"direct_graft"body flag that never landed upstream. This includes Valentin Volkl's change and gateway CI from lease: target dedicated gateway graft endpoint for DirectGraft #1, which this PR supersedes.Reliable spool. An accepted job is published eventually unless it fails for a permanent reason.
retry_window(default 24 h). Conflicts, invalid archives and operator aborts fail at once.max_tar_size_gib(default 10) or when the spool would drop belowspool_min_free_gib;/healthadvertises the limit. The same limit caps a single file in the tar on the streaming path; the non-streaming paths keep a 1 GiB per-file cap.Security.
S1_NODE_KEY, formerlyPREPUB_NODE_KEY). Revocation is persisted in<spool>/revoked-nodes.jsonand can be lifted withcvmfs-prepub revoke --undo(/api/v1/control/unrevoke).provenance-record.json).Distribution. Receivers pull from the publisher: one
--receiver-stratum0-url(the publisher base URL) serves both the object pull and the post-commit root-catalog pull, which now works and is hash-verified. Probes retry before a receiver is skipped.Catalogs. The writer passes
cvmfs_swissknife check(nested catalog sizes, link counts, split roots) and uses CVMFS's hash-algorithm IDs, suffixes and symlink/chunk flags.Observability. A measurement record per publish (
/api/v1/measurements),/api/v1/publishedfor "is this path published, by which build hash", Prometheus gauges for spool states, retries, spool disk, host load and memory, a startup tuning banner, andcvmfs-prepub --version.Operations.
install.shgainsupdate(keeps configuration) anduninstall, both acting on the roles installed on the host, plus--userand--spool-dir. INSTALL.md covers new nodes and moving prepub off the gateway node.Removed (never wired into a working path): warm quorum,
/s1/catchup,/s1/{txn}/lease, GC pins andprepubctl.Docs and licence. README, INSTALL and REFERENCE rewritten and checked against the code; Apache-2.0 headers throughout.
Before deploying
bitsalready does).PREPUB_NODE_KEYtoS1_NODE_KEYand give each receiver its own broker key.--data-addr,--data-host,--disk-headroom,--session-ttl,--tls-cert,--tls-keyand--warm-quorumfrom unit files; the receiver no longer starts with them. Set--receiver-stratum0-urlto the publisher base URL.gateway.direct_graft: false.--prewarm),--job-timeoutoff (retries and stall detection replace it),max_tar_size_gib10 — raise it where large packages need it./api/v1/measurementsneeds no authentication.Testing
go build ./...,go vet ./...andgo test ./...pass ate1b54ea, including the httpsig interop test against the currentbitsclient.