vantagectl/ becomes the root of gitea.hostxtra.co.uk/vantage/vantage-ctl.
The command keeps the name vantagectl; only the repository and the image
path change, to vantage/vantage-ctl.
chart-release.yml's render checks are repointed at the new image. The
chart itself has no default backup.image and fails loudly without one, so
an existing cluster keeps working until someone changes the value.
go.work stays, now with a single use ./server entry: without it a go.work
further up the developer's filesystem is picked up instead.
agent/ becomes the root of gitea.hostxtra.co.uk/vantage/vantage-agent,
with installer/ alongside it, and agent-release.yml goes with them.
Releases now come from that repository, so the six places this server
generates or reads a release URL are repointed: both install scripts,
both update scripts, and the latest-version lookup in dispatch.go. The
agent/v* tag prefix is unchanged — those scripts grep for it.
Agents built before this move have the old mrhid6/vantage path compiled
into their self-update and will 404 on the push-button update. The
remedy is the /update one-liner, which this server generates and which
therefore has to ship first.
vantage.proto documents the hand-written types in shared/grpc/pb, and
nothing compiles it. Keeping it in a different repository from the Go
types it describes meant the one rule holding them together — add the
message to both in the same commit — could not be followed at all.
server's rebuild trigger loses proto/, which it only carried as
insurance against exactly that split.
site/ and sitesvc/ become web/ and server/ in vantage-site; docsite/
becomes the root of vantage-docs. Their images move with them, to
vantage/vantage-site/{web,server} and vantage/vantage-docs.
Nothing here imported any of them, and sitesvc turned out to read no
database at all, so both cuts are clean. docker-compose.site.yml is
deleted rather than emptied: every service it held now ships with the
repository that builds it, and deploy/docker/docker-compose.yml is once
again exactly a self-hosted install.
Corrects four comments that named sitesvc for work it no longer does.
admin/ and adminsite/ are extracted with their history to
gitea.hostxtra.co.uk/vantage/vantage-admin, where they are named server/
and web/ for what they are rather than for the services they run. Their
images move with them, to vantage/vantage-admin/{server,web}.
Nothing here imported them, so the cut is clean: the only coupling was
always at runtime, through admin writing into the control plane's
database. The parts of that contract this side enforces are unchanged and
still documented here — hq-sourced users, POST /license answering 409
cloud_managed, and FREE_INSTANCE_REAP_AFTER needing to match.
LICENSE_SIGNING_KEY now appears in no compose file in this repository.
Keeping it out used to be a rule someone had to remember; it is the
repository boundary now.
docker-compose.site.yml loses both services and gains a note on how the
host composes the three files together.
go list -m -f '{{.Dir}}' reports an empty Dir and exits 0 for a module that
is not in the cache, so on a cold runner swag was handed an empty --dir and
failed several steps later with 'dir: does not exist'. Download first, and
fail loudly if the path is still not there.
shared/ is extracted to gitea.hostxtra.co.uk/vantage/vantage-shared and
pinned at v0.1.0 by server, agent, admin, sitesvc and vantagectl. The
replace directives and the ./shared entry in go.work are gone.
Every Go build now needs a credential for the private module: CI writes a
netrc per job from REGISTRY_USER + RELEASE_TOKEN and sets GOPRIVATE, and
the four Go Dockerfiles take it as a BuildKit secret rather than a build
arg, which would survive in the builder layer's history. RELEASE_TOKEN
needs read access to the vantage org.
admin, sitesvc and vantagectl now build from their own directory; only
server still needs the repository root, for default_steps/. The rebuild
triggers in server-deploy.yml lose their shared/ patterns, since a
service now moves when its own go.mod pin does.
vantagectl is a released tool, not a running service. An operator restoring a
database should be able to name the version they ran, and ":latest, rebuilt
whenever main moved" cannot be named after the fact.
The image now builds in vantagectl-release.yml on a vantagectl/v* tag, tagged
with that version as well as latest, with VERSION passed through so the binary
inside reports the tag rather than "dev". server-deploy.yml no longer builds
it and is back to seven images.
The cost is that a shared/ fix reaches the image only at the next release
rather than the next push to main. That is the intended trade and is written
down in CLAUDE.md next to the trigger table.
Vantage encrypts SSH private keys, vault secrets, SSO client secrets and
console credentials with KEY_ENCRYPTION_KEY. That key is in no backup and is
not recoverable, so restoring a database without it produces a control plane
whose every secret is permanently unreadable.
vantagectl dumps and restores a whole database and stamps a SHA-256
fingerprint of the key into every archive, so a restore refuses rather than
producing that database. The key itself is never written.
- shared/cryptobox: AES-GCM extracted so the server and the CLI share one
implementation rather than a copy that can drift
- shared/backup: manifest, tar+gzip archive with per-member checksums
verified before the first write, dump, restore, verify
- vantagectl: its own module, so cobra stays out of the server, admin and
sitesvc module graphs
- container image, tagged release workflow, CI rebuild trigger
- optional Helm CronJob, off by default
- operator documentation, reconciled with the existing backups page
backups.md kept its store-level table and danger note but now points to
vantagectl as the supported path, with mongodump/mongorestore demoted to
an explicit fallback and a warning that a plain dump records no key
fingerprint. backup-and-restore.md links back for the store-level
overview.
The page leads with KEY_ENCRYPTION_KEY rather than mentioning it in a
note, because holding a good database dump and no key is the way this goes
wrong.
Off by default: a backup with nowhere durable to land is a false sense of
safety and the chart cannot know where that is. NOTES.txt says so when it
is off.
No restore manifest ships: a restore must never be something a helm
upgrade can trigger.
resolveGlobals can fail for two distinct reasons — no MongoDB URI, or no
resolvable database name — and verify.go was printing a hardcoded
no-URI note regardless of which one occurred, misleading an operator
whose URI was fine but whose database name could not be resolved.
The scratch runtime stage copies an explicit /tmp: restore extracts an
archive there before verifying it, and a scratch image has none.
shared/ now fans out to four Go images rather than three.
--force requires a typed database name on a terminal and --confirm-db
without one, so a copy-pasted restore command carries its intended target
and cannot destroy a different database.
Also silences cobra's own error print (root.go) so a failure is reported
once by main.go instead of twice, and pins the Changed()-based env
fallback in resolveGlobals with a test for an explicitly empty --db.
Progress output goes to stderr so --out - stays a clean pipe into restic,
age or aws s3 cp. Archive names carry no colon, because these get copied
onto Windows shares.
Its own module rather than a package under shared, so cobra and pflag stay
out of the module graphs of server, admin and sitesvc, which never use
them.
A fingerprint comparison proves two archives agree about a key. Only
opening real ciphertext from the target proves the key in hand reads the
data, which is the question an operator actually has.
Every refusal happens before the first write: format, checksums, key
policy, then target inspection. A unique index that will not build aborts,
because the unique indexes here are tenant-isolation properties rather
than optimisations.
Collections are enumerated live rather than from a list, so a collection
added later is backed up with no code change. Documents are written as the
raw BSON the driver returned, so Decimal128, ObjectId, DateTime and binary
subtypes survive byte for byte.
Open extracts and verifies every member against the manifest before the
reader is usable, so a corrupt archive is refused before a restore writes
its first document rather than halfway through.