feat: documentation site
Docusaurus 3 docs-only site at docsite/, served statically by nginx under /docs on the marketing host. Covers getting started (self-hosted install through first server and first licence), the control plane, Vantage HQ, a reference section and operations. Wired into docker-compose.site.yml as docsite (3005:80) and into the image build workflow, rebuilding on its own directory only. Never added to the self-hosted compose file.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
---
|
||||
id: agent-updates
|
||||
title: Agent updates
|
||||
sidebar_label: Agent updates
|
||||
---
|
||||
|
||||
Agents are versioned and released independently of the control plane, and update
|
||||
themselves on command.
|
||||
|
||||
## Checking the current version
|
||||
|
||||
Each server's detail page shows the version it reported at its last sync.
|
||||
`GET /api/agent/latest-version` reports the newest release available.
|
||||
|
||||
## Updating from the UI
|
||||
|
||||
**Servers → *a server* → Update agent** pushes `UpdateAgentCmd` with a target
|
||||
version. The agent then:
|
||||
|
||||
1. Downloads the binary for its platform from the release.
|
||||
2. Verifies the SHA-256 against `checksums.txt`.
|
||||
3. Stops itself, replaces the binary in place, and starts again.
|
||||
|
||||
`Restart=always` on the systemd unit is what makes the last step work.
|
||||
|
||||
The server briefly goes `offline` and comes back within a poll interval or two.
|
||||
|
||||
## Updating from the machine
|
||||
|
||||
There is a dynamic update script, the counterpart to the install one:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://vantage.example.com/update | bash
|
||||
```
|
||||
|
||||
```powershell
|
||||
irm https://vantage.example.com/update.ps1 | iex
|
||||
```
|
||||
|
||||
It does the same download, checksum and replace, then restarts the service. Use
|
||||
this when the control plane cannot push — for example, when the machine is
|
||||
reachable but its command stream is not.
|
||||
|
||||
## Rolling out across a fleet
|
||||
|
||||
There is no built-in bulk update. Two reasonable approaches:
|
||||
|
||||
- Update from each server's page, a few at a time.
|
||||
- Build a [workflow](../vantage/workflows.md) whose step runs the update script,
|
||||
and target it at the machines you want. That gives you ordering, failure
|
||||
handling and a log.
|
||||
|
||||
:::tip Update a canary first
|
||||
An agent that fails to start after replacing itself needs hands on that machine.
|
||||
Do one, confirm it returns to `active`, then do the rest.
|
||||
:::
|
||||
|
||||
## Version compatibility
|
||||
|
||||
The gRPC API is versioned to tolerate an agent older than the control plane. The
|
||||
reverse — an agent newer than the control plane — is not a case anyone tests.
|
||||
Upgrade the control plane first.
|
||||
|
||||
Agents report their version on every `SyncKeys`, so a fleet running mixed
|
||||
versions is visible in the server list rather than something you have to go
|
||||
looking for.
|
||||
|
||||
## If an update fails
|
||||
|
||||
| Symptom | Cause |
|
||||
| --- | --- |
|
||||
| "Checksum mismatch" | Interrupted download, or a proxy rewriting the body. Retry |
|
||||
| Downloads nothing | The machine cannot reach `gitea.hostxtra.co.uk` |
|
||||
| Service will not start afterwards | Wrong architecture binary, or the file was replaced while a different service manager held it. Reinstall with the install one-liner |
|
||||
|
||||
Reinstalling is always safe: the config file is left alone, so the agent comes
|
||||
back with the same identity and token.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
id: backups
|
||||
title: Backups
|
||||
sidebar_label: Backups
|
||||
---
|
||||
|
||||
Two things matter: **MongoDB** and **`KEY_ENCRYPTION_KEY`**. A backup missing
|
||||
either one restores to something unusable.
|
||||
|
||||
## What holds what
|
||||
|
||||
| Store | Contents | Back up |
|
||||
| --- | --- | --- |
|
||||
| MongoDB | Everything durable — servers, keys, assignments, workflows, runs, monitors, incidents, secrets, settings, audit | **Yes** |
|
||||
| Redis | Sessions only | No. Losing it signs everyone out and nothing else |
|
||||
| `./data` bind mount | Workflow run logs | Optional |
|
||||
| `KEY_ENCRYPTION_KEY` | Not stored anywhere by the app | **Yes, separately** |
|
||||
|
||||
:::danger The database alone is not a backup
|
||||
Private keys, vault secrets, OIDC client secrets and console credentials are
|
||||
encrypted with `KEY_ENCRYPTION_KEY`, which lives in your environment file and
|
||||
nowhere in the database. Restore the database without it and every one of those
|
||||
values is permanently unreadable.
|
||||
|
||||
Store the key somewhere other than the server it protects.
|
||||
:::
|
||||
|
||||
## Backing up MongoDB
|
||||
|
||||
With the bundled Mongo container:
|
||||
|
||||
```bash
|
||||
docker compose exec -T mongo mongodump --archive --gzip --db vantage \
|
||||
> /backups/vantage-$(date +%F).archive.gz
|
||||
```
|
||||
|
||||
Restoring:
|
||||
|
||||
```bash
|
||||
docker compose exec -T mongo mongorestore --archive --gzip --drop \
|
||||
< /backups/vantage-2026-07-28.archive.gz
|
||||
```
|
||||
|
||||
`--drop` replaces existing collections. Stop the `server` container first, so
|
||||
nothing writes during the restore.
|
||||
|
||||
## Backing up the environment file
|
||||
|
||||
```bash
|
||||
cp /opt/vantage/.env /secure-location/vantage.env
|
||||
```
|
||||
|
||||
Treat it as a credential in its own right — it holds the encryption key.
|
||||
|
||||
## Run logs
|
||||
|
||||
Workflow run logs live in the `./data` bind mount, not in the database. They are
|
||||
swept on the retention schedule anyway, so most people do not back them up. If
|
||||
you keep them for compliance, set retention to `0` (forever) and include the
|
||||
directory.
|
||||
|
||||
## What a restore gives you
|
||||
|
||||
Everything: fleet, keys, assignments, workflows and their history, monitors and
|
||||
incidents, secrets, settings and the audit log.
|
||||
|
||||
What it does **not** do is reconcile the world. After a restore:
|
||||
|
||||
- Agents reconnect with their existing tokens, since the token hashes are in the
|
||||
database.
|
||||
- If the restore is older than an enrolment, that server's token hash is missing
|
||||
and the agent will fail to authenticate — re-enrol it.
|
||||
- The next agent poll rewrites `authorized_keys` to match the restored desired
|
||||
state, which may remove keys added since the backup.
|
||||
|
||||
## A workable schedule
|
||||
|
||||
| What | When |
|
||||
| --- | --- |
|
||||
| MongoDB dump | Nightly, retained per your policy |
|
||||
| Environment file | On change, held in a password manager or secret store |
|
||||
| Restore rehearsal | Occasionally, into a throwaway host |
|
||||
|
||||
The rehearsal is the part that gets skipped and the part that finds the
|
||||
problems.
|
||||
|
||||
## Cloud instances
|
||||
|
||||
We back these up. You do not need to.
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
id: ci-cd
|
||||
title: CI/CD
|
||||
sidebar_label: CI/CD
|
||||
---
|
||||
|
||||
Two Gitea Actions workflows: one releases agents, one builds images.
|
||||
|
||||
## Agent releases
|
||||
|
||||
Triggered by an `agent/v*` tag.
|
||||
|
||||
```bash
|
||||
git tag agent/v1.0.0 && git push origin agent/v1.0.0
|
||||
```
|
||||
|
||||
Builds `linux/amd64`, `linux/arm64` and `windows/amd64`, writes `checksums.txt`
|
||||
and creates a Gitea release. A second job on Windows packages the WiX MSI.
|
||||
|
||||
The install and update scripts read the newest `agent/v*` release from the Gitea
|
||||
API, so tagging is what makes a new agent available to every install.
|
||||
|
||||
## Image builds
|
||||
|
||||
Triggered on every push to `main`. Builds and pushes seven images: `server`,
|
||||
`web`, `site`, `sitesvc`, `admin`, `adminsite` and `docsite`.
|
||||
|
||||
:::warning Despite the name, this workflow does not deploy
|
||||
There is no SSH step. Rolling images out is a manual step on the host:
|
||||
|
||||
```bash
|
||||
cd /opt/vantage && \
|
||||
docker compose -f docker-compose.yml -f docker-compose.site.yml pull && \
|
||||
docker compose -f docker-compose.yml -f docker-compose.site.yml up -d --remove-orphans
|
||||
```
|
||||
:::
|
||||
|
||||
### Each image rebuilds only when its own inputs change
|
||||
|
||||
A `git diff` against the previous head decides. That is why the checkout uses
|
||||
`fetch-depth: 0` — a shallow clone has one commit and nothing to diff against.
|
||||
|
||||
| Image | Rebuilds when |
|
||||
| --- | --- |
|
||||
| `server` | `server/`, `shared/`, `proto/`, `go.work` |
|
||||
| `admin` | `admin/`, `shared/`, `go.work` |
|
||||
| `sitesvc` | `sitesvc/`, `shared/`, `go.work` |
|
||||
| `web` · `site` · `adminsite` · `docsite` | their own directory only |
|
||||
|
||||
`shared/` fans out to all three Go images because each of their Dockerfiles
|
||||
copies it from a root context. **If a fourth service ever imports `shared/`, it
|
||||
must be added to that list or it will ship stale.**
|
||||
|
||||
Everything rebuilds when there is no trustworthy base commit to diff against: a
|
||||
manual `workflow_dispatch`, a new branch, or a force-push whose old head is
|
||||
gone. Changing the workflow file itself also rebuilds everything, since a build
|
||||
argument is baked into each image.
|
||||
|
||||
### The gap: repository variables
|
||||
|
||||
:::danger Editing a repository variable pushes no commit, so nothing rebuilds
|
||||
Values like `API_URL`, `ADMIN_API_URL`, `HQ_URL`, `ADMIN_ENV`, `PADDLE_ENV`,
|
||||
`PADDLE_CLIENT_TOKEN`, `DOCS_URL` and `DOCS_BASE_URL` are baked into images at
|
||||
build time. After editing one, run the workflow manually — that is what
|
||||
`workflow_dispatch` is for.
|
||||
|
||||
The symptom is a frontend that keeps using the old value with no error anywhere,
|
||||
which is a long afternoon if you do not know about this.
|
||||
:::
|
||||
|
||||
The same applies to base images: a service nobody touches stops being rebuilt on
|
||||
newer base layers. A periodic manual run covers it.
|
||||
|
||||
## Secrets and variables
|
||||
|
||||
| Name | Type | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `RELEASE_TOKEN` | Secret | Gitea API token, `write:release` |
|
||||
| `REGISTRY_USER` / `REGISTRY_PASSWORD` | Secret | Registry push credentials |
|
||||
| `PADDLE_API_KEY` | Secret | Read by admin at runtime |
|
||||
| `PADDLE_WEBHOOK_SECRET` | Secret | Webhook signature verification |
|
||||
| `GITEA_HOST` / `DOCKER_HOST` | Variable | Hosts used in tags and URLs |
|
||||
| `API_URL` | Variable | Baked into `web` |
|
||||
| `HQ_URL` | Variable | Baked into `web`; empty on self-hosted |
|
||||
| `SITE_API_URL` / `SITE_CONTACT_EMAIL` | Variable | Baked into `site` |
|
||||
| `ADMIN_API_URL` | Variable | Baked into `adminsite` **and** `site` |
|
||||
| `ADMIN_ENV` | Variable | Environment badge in the portal |
|
||||
| `PADDLE_ENV` | Variable | Baked into `adminsite`, read by `admin`. Must match on both sides |
|
||||
| `PADDLE_CLIENT_TOKEN` | Variable | Browser Paddle token for checkout |
|
||||
| `DOCS_URL` / `DOCS_BASE_URL` | Variable | Baked into `docsite` |
|
||||
|
||||
Anything marked "browser-reachable" must be an origin a browser can actually
|
||||
resolve — not an internal service name. Get it wrong and every request fails at
|
||||
runtime with a not-connected panel rather than at build time.
|
||||
|
||||
## The documentation site
|
||||
|
||||
`docsite/` builds to static files and is served by nginx under `/docs` on the
|
||||
marketing host, routed by its own proxy location.
|
||||
|
||||
`DOCS_BASE_URL` has to agree with three things at once: that proxy location, the
|
||||
directory the runtime image serves from, and the value baked into the build. When
|
||||
they disagree the page loads and every stylesheet and script 404s.
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: upgrading
|
||||
title: Upgrading
|
||||
sidebar_label: Upgrading
|
||||
---
|
||||
|
||||
Upgrading the control plane is a pull and a recreate. Agents are versioned and
|
||||
upgraded separately — see [Agent updates](./agent-updates.md).
|
||||
|
||||
## Upgrade
|
||||
|
||||
```bash
|
||||
cd /opt/vantage
|
||||
docker compose pull
|
||||
docker compose up -d --remove-orphans
|
||||
```
|
||||
|
||||
`--remove-orphans` clears containers for services that no longer exist in the
|
||||
Compose file, which is what leaves a stale container running after a service is
|
||||
renamed or removed.
|
||||
|
||||
## What happens on boot
|
||||
|
||||
1. **Migrations** run, recording markers so each runs once.
|
||||
2. **Indexes** are ensured. Auth and settings index builders are fatal on
|
||||
failure; secret and workflow ones only warn.
|
||||
3. **Default steps** are reseeded from the image, overwriting the `default`
|
||||
library — which is why those steps are read-only.
|
||||
|
||||
Watch it:
|
||||
|
||||
```bash
|
||||
docker compose logs -f server
|
||||
```
|
||||
|
||||
## Before you upgrade
|
||||
|
||||
- **Back up MongoDB.** See [Backups](./backups.md). Migrations are one-way.
|
||||
- **Read the release notes** for anything about migrations or environment
|
||||
variables.
|
||||
- **Check your `.env`** still supplies everything required. A newly required
|
||||
variable stops the boot rather than defaulting to something unsafe.
|
||||
|
||||
## Downgrading
|
||||
|
||||
There is no automatic downgrade. Migrations do not roll back, so returning to an
|
||||
older image means restoring the database backup taken before the upgrade. This
|
||||
is the reason the backup is not optional.
|
||||
|
||||
## Zero-downtime
|
||||
|
||||
The stack is not designed for it. `docker compose up -d` recreates the server
|
||||
container, which is a short interruption:
|
||||
|
||||
- Agents reconnect on their own — they retry, and the poll loop is idempotent.
|
||||
- Workflow runs in progress lose their command stream. Steps already dispatched
|
||||
finish on the agent, but their results have nowhere to go. **Do not upgrade
|
||||
during a run.**
|
||||
- Sessions survive, because they live in Redis rather than in the server.
|
||||
|
||||
## Upgrading the hosted deployment
|
||||
|
||||
Both Compose files, together:
|
||||
|
||||
```bash
|
||||
cd /opt/vantage
|
||||
docker compose -f docker-compose.yml -f docker-compose.site.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.site.yml up -d --remove-orphans
|
||||
```
|
||||
|
||||
CI builds and pushes images but does **not** deploy them; rolling out is this
|
||||
manual step. See [CI/CD](./ci-cd.md).
|
||||
|
||||
## After upgrading
|
||||
|
||||
- Confirm every service is `running`.
|
||||
- Confirm servers return to `active` within a couple of poll intervals.
|
||||
- Open a page that touches encryption — a secret group — to confirm
|
||||
`KEY_ENCRYPTION_KEY` came through.
|
||||
Reference in New Issue
Block a user