Compare commits
10
Commits
d69a36ed7b
...
f7eee4fc3e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7eee4fc3e | ||
|
|
cca6453b86 | ||
|
|
aa581b5d1e | ||
|
|
f41f2aaae7 | ||
|
|
76057eb346 | ||
|
|
f564dd0479 | ||
|
|
54671a3652 | ||
|
|
8359791b6e | ||
|
|
036875345a | ||
|
|
c6cd96cbd1 |
@@ -0,0 +1,45 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# DOCS_URL, DOCS_BASE_URL and APP_URL are repo variables, and editing one
|
||||
# pushes no commit — so nothing would rebuild on its own. This is the
|
||||
# escape hatch for that, and DOCS_BASE_URL in particular is baked into
|
||||
# every asset path in the build.
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-docker
|
||||
container: docker:dind
|
||||
steps:
|
||||
- name: Setup
|
||||
run: apk add --update nodejs npm git
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to registry
|
||||
run: |
|
||||
echo "${{ secrets.RELEASE_TOKEN }}" | \
|
||||
docker login ${{ vars.DOCKER_HOST }} \
|
||||
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
|
||||
# One image, one directory — the whole repository is the docs site,
|
||||
# so there is nothing to detect a change in. It rebuilds on every
|
||||
# push, which is what a docs site wants anyway.
|
||||
#
|
||||
# No build args are passed, deliberately: DOCS_URL, DOCS_BASE_URL,
|
||||
# APP_URL and HQ_URL all have correct defaults in the Dockerfile,
|
||||
# and passing an unset repo variable would blank one rather than
|
||||
# leave it alone. DOCS_BASE_URL in particular must match the proxy
|
||||
# location that routes here and the directory the runtime stage
|
||||
# serves from; when those three disagree the HTML loads and every
|
||||
# asset 404s. Change it in the Dockerfile, not here.
|
||||
- name: Build and push image
|
||||
run: |
|
||||
IMAGE="${{ vars.DOCKER_HOST }}/vantage/vantage-docs:latest"
|
||||
docker build -t "$IMAGE" .
|
||||
docker push "$IMAGE"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
node_modules
|
||||
build
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
.env
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Vantage documentation (`vantage-docs`)
|
||||
|
||||
The user-facing documentation, served at **vantage.hostxtra.co.uk/docs**.
|
||||
Docusaurus 3 in docs-only mode (`routeBasePath: "/"`, no blog), one version
|
||||
tracking `main`, search indexed at build time by
|
||||
`@easyops-cn/docusaurus-search-local` so nothing external is keyed or called.
|
||||
|
||||
Extracted from the `vantage` monorepo with its history, where it was
|
||||
`docsite/`. The whole repository is the site — there is no subdirectory.
|
||||
|
||||
It documents the **product**, not the codebase: each repository's own CLAUDE.md
|
||||
remains the contributor's map, and the two are allowed to differ in altitude but
|
||||
not in fact. Five sections — Getting started, Vantage, Vantage HQ, Reference,
|
||||
Operations — with `sidebars.ts` authored by hand so ordering is a decision
|
||||
rather than a filename accident.
|
||||
|
||||
## A path, not a subdomain
|
||||
|
||||
`*.vantage.hostxtra.co.uk` is the per-tenant instance namespace, and the control
|
||||
plane's `APP_ROOT_LABEL` resolves an instance from the label before `vantage` —
|
||||
so a `docs.` label would be read as a tenant slug. Hence `/docs` on the
|
||||
marketing host instead.
|
||||
|
||||
**Three things must agree, and when they do not the HTML loads while every
|
||||
asset 404s:**
|
||||
|
||||
1. `DOCS_BASE_URL` (default `/docs/` in the Dockerfile)
|
||||
2. the proxy location that routes here — which must also sort **above** the
|
||||
catch-all forwarding to `site:3003`, or Next answers the 404 first
|
||||
3. the directory the runtime stage serves from,
|
||||
`/usr/share/nginx/html/docs`
|
||||
|
||||
The proxy forwards the **full** path; it does not strip `/docs`. That is the
|
||||
whole reason the third item exists.
|
||||
|
||||
Unlike the three Next apps this builds to static files, so its runtime stage is
|
||||
`nginx:alpine-slim` rather than Node, and it listens on **80**, not 3000.
|
||||
|
||||
## Design tokens
|
||||
|
||||
`src/css/custom.css` holds `vantage-site`'s `web/app/globals.css` token blocks
|
||||
**copied verbatim** — same names, same values. This is the one place the tokens
|
||||
are not consumed through Tailwind: everything below the token block maps
|
||||
Docusaurus's `--ifm-*` variables onto them. Docusaurus already stamps
|
||||
`data-theme` on `<html>`, which is the selector the dark block keys on, so the
|
||||
built-in toggle needed no wiring.
|
||||
|
||||
The rule holds all the same: **no rule in that file outside the token blocks may
|
||||
carry a hex.** The one concession is `static/img/favicon.svg`, which must, for
|
||||
the same reason the email layout must — a browser tab cannot read a token.
|
||||
|
||||
**Nothing enforces the match across the four front ends, and they are now in
|
||||
four repositories.** A token change here follows one in `vantage-site`; there is
|
||||
no way to make them one commit.
|
||||
|
||||
## Deployment
|
||||
|
||||
A fragment, composed on top of the control plane's own file:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f vantage/deploy/docker/docker-compose.yml \
|
||||
-f vantage-site/deploy/docker-compose.yml \
|
||||
-f vantage-docs/deploy/docker-compose.yml \
|
||||
-f vantage-admin/deploy/docker-compose.yml \
|
||||
up -d
|
||||
```
|
||||
|
||||
Not part of a self-hosted install.
|
||||
|
||||
## CI/CD
|
||||
|
||||
`.gitea/workflows/deploy.yml`, on every push to `main` plus `workflow_dispatch`.
|
||||
One image, `vantage/vantage-docs:latest`, from the repository root — the whole
|
||||
repository is the site, so there is nothing to detect a change in and it
|
||||
rebuilds on every push.
|
||||
|
||||
**No build args are passed, deliberately.** `DOCS_URL`, `DOCS_BASE_URL`,
|
||||
`APP_URL` and `HQ_URL` all have correct defaults in the Dockerfile, and passing
|
||||
an unset repo variable would blank one rather than leave it alone — which for
|
||||
`DOCS_BASE_URL` means every asset path in the build. Change them in the
|
||||
Dockerfile. The monorepo's workflow passed none either; this preserves that.
|
||||
|
||||
| Name | Type | Value |
|
||||
| --------------- | ------ | ----------------------------------------------------------- |
|
||||
| `RELEASE_TOKEN` | Secret | Gitea API token, `write:package`. No module fetch here, so it needs no org read access |
|
||||
| `REGISTRY_USER` | Secret | Gitea username. Must own `RELEASE_TOKEN` |
|
||||
| `DOCKER_HOST` | Variable | registry host used for image tags |
|
||||
|
||||
Despite the name the workflow **does not deploy**; it only builds and pushes.
|
||||
+1
-1
@@ -25,7 +25,7 @@ RUN npm run build
|
||||
|
||||
# Runtime stage
|
||||
#
|
||||
# Docusaurus emits a fully static site, so unlike web/, site/ and adminsite/
|
||||
# Docusaurus emits a fully static site, so unlike web/ and site/
|
||||
# there is no Node server at runtime. alpine-slim is roughly a quarter the size
|
||||
# of caddy:alpine, and nothing here needs automatic TLS — the host proxy
|
||||
# terminates it.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# The user documentation, served at vantage.hostxtra.co.uk/docs.
|
||||
#
|
||||
# A fragment, composed on top of the vantage repository's own file — see the
|
||||
# vantage-site fragment for the full command.
|
||||
#
|
||||
# A path on the marketing host, not a subdomain: *.vantage.hostxtra.co.uk is
|
||||
# the per-tenant instance namespace, and the control plane's APP_ROOT_LABEL
|
||||
# would read a `docs.` label as a tenant slug.
|
||||
#
|
||||
# The proxy location that routes here must sort ABOVE the catch-all forwarding
|
||||
# to site:3003, or Next answers the 404. It forwards the FULL path — it does
|
||||
# not strip /docs — so the proxy location, DOCS_BASE_URL and the directory the
|
||||
# image serves from (/usr/share/nginx/html/docs) must all agree. When they do
|
||||
# not, the HTML loads and every asset 404s.
|
||||
#
|
||||
# Unlike the Next apps this is a static build behind nginx, so it listens on 80
|
||||
# rather than 3000.
|
||||
services:
|
||||
docsite:
|
||||
image: gitea.hostxtra.co.uk/vantage/vantage-docs:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3005:80
|
||||
networks: {}
|
||||
@@ -96,8 +96,16 @@ rather than run in a half-prepared state.
|
||||
|
||||
## 4. Put a proxy in front
|
||||
|
||||
Point your reverse proxy at `web` on port `3000` and terminate TLS there. The
|
||||
web app reaches the API internally, so there is no need to publish port `8080`.
|
||||
Terminate TLS at your reverse proxy and route **one hostname to two backends**:
|
||||
|
||||
| Path | Backend |
|
||||
| -------------------------------------------------------------------------------- | ------------- |
|
||||
| `/api`, `/auth`, `/public`, `/install`, `/install.ps1`, `/update`, `/update.ps1` | `server:8080` |
|
||||
| everything else | `web:3000` |
|
||||
|
||||
Both rules are required. The web app forwards nothing to the API, so a proxy
|
||||
that sends the whole hostname to `web:3000` serves the interface and answers
|
||||
`404` to every request it makes — starting with the login form.
|
||||
|
||||
Agents connect to port `9090`. Vantage does not terminate TLS itself, so put
|
||||
that port behind your proxy too, with a certificate valid for the name in
|
||||
|
||||
@@ -28,13 +28,14 @@ entitlement.
|
||||
|
||||
## Features
|
||||
|
||||
Three features are enabled per instance rather than bundled into a tier:
|
||||
Four features are enabled per instance rather than bundled into a tier:
|
||||
|
||||
| Feature | What it enables |
|
||||
| ---------------------- | ------------------------------------------------------------------------------- |
|
||||
| Browser console | The [browser console](../vantage/browser-console.md) |
|
||||
| Single sign-on | [Sign-in through your identity provider](../vantage/settings.md#single-sign-on) |
|
||||
| Vulnerability scanning | [Package vulnerability scanning](../vantage/vulnerabilities.md) |
|
||||
| Status pages | [Public status pages](../vantage/status-pages.md) |
|
||||
|
||||
No tier includes them by default; you enable them on the instances that need
|
||||
them.
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
---
|
||||
id: backup-and-restore
|
||||
title: Backup and restore
|
||||
sidebar_label: Backup and restore
|
||||
---
|
||||
|
||||
`vantagectl` is a separate command-line tool that backs up and restores the
|
||||
MongoDB database behind a Vantage control plane. It talks to MongoDB directly,
|
||||
never to the Vantage API, so it works against a control plane that is down,
|
||||
half-migrated, or gone — exactly the situation a backup tool has to survive.
|
||||
|
||||
For the store-level overview — what holds what, and why the database alone is
|
||||
not a backup — see [Backups](./backups.md). This page covers the tool.
|
||||
|
||||
:::danger The key comes first
|
||||
Vantage encrypts SSH private keys, key passphrases, vault secrets, SSO client
|
||||
secrets and console credentials with `KEY_ENCRYPTION_KEY`. **It is not in your
|
||||
backup, and it is not recoverable.** A database restored without it is
|
||||
permanently unreadable — not degraded, not partially readable, unreadable.
|
||||
|
||||
Store it wherever you store the credentials you could not rebuild: a password
|
||||
manager, a secrets vault outside this control plane, a piece of paper in a
|
||||
safe. Anywhere but next to the archive.
|
||||
:::
|
||||
|
||||
## What a backup holds
|
||||
|
||||
Every collection in the database, the index definitions each one needs to be
|
||||
useful again, and a SHA-256 **fingerprint** of `KEY_ENCRYPTION_KEY` — never the
|
||||
key itself. The fingerprint is what lets a later `restore` or `verify` tell you
|
||||
that the key you are holding is the wrong one, before it writes a database
|
||||
nobody can read.
|
||||
|
||||
## What it does not hold
|
||||
|
||||
- **Redis sessions.** Everyone signs in again after a restore, which is already
|
||||
true whenever Redis itself restarts.
|
||||
- **The vulnerability database.** It is re-pulled automatically on next boot.
|
||||
- **Agent state on managed servers.** Nothing needs re-enrolling: agents
|
||||
reconnect on their own, because `servers.agent_token_hash` — the thing an
|
||||
agent authenticates with — is itself in the backup.
|
||||
|
||||
:::note Pin the version
|
||||
The image is published on each `vantagectl/v*` release and tagged with that
|
||||
version; `:latest` also moves. Pin a version in anything scheduled. A restore
|
||||
is easier to reason about when you can say which build produced the archive and
|
||||
which one read it back.
|
||||
:::
|
||||
|
||||
## Taking a backup
|
||||
|
||||
The loose binary:
|
||||
|
||||
```bash
|
||||
export MONGO_URI=mongodb://localhost:27017
|
||||
export MONGO_DB=vantage
|
||||
export KEY_ENCRYPTION_KEY=<your 64-char hex key>
|
||||
vantagectl backup --out /backups
|
||||
```
|
||||
|
||||
The container:
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e MONGO_URI=mongodb://mongo:27017 \
|
||||
-e MONGO_DB=vantage \
|
||||
-e KEY_ENCRYPTION_KEY=<your 64-char hex key> \
|
||||
-v /backups:/backups \
|
||||
gitea.hostxtra.co.uk/mrhid6/vantage/vantagectl:0.1.0 backup --out /backups
|
||||
```
|
||||
|
||||
Kubernetes, as a scheduled `CronJob` the Helm chart can render for you:
|
||||
|
||||
```yaml
|
||||
backup:
|
||||
enabled: true
|
||||
schedule: "0 2 * * *"
|
||||
image: "gitea.hostxtra.co.uk/mrhid6/vantage/vantagectl:0.1.0"
|
||||
pvcName: "vantage-backups"
|
||||
```
|
||||
|
||||
`backup.enabled` defaults to `false`, and the chart refuses to render if it is
|
||||
turned on without both `backup.image` and `backup.pvcName` — a backup needs a
|
||||
known image and somewhere durable to land, and guessing at either is worse than
|
||||
refusing to start. `backup.exclude` names collections to leave out (recorded in
|
||||
the archive's manifest, so an archive never claims to be complete when it is
|
||||
not), and `backup.successfulJobsHistoryLimit` / `backup.failedJobsHistoryLimit`
|
||||
/ `backup.resources` behave exactly as they do on any other `CronJob`.
|
||||
|
||||
`backup` refuses to run without `KEY_ENCRYPTION_KEY` set in the environment,
|
||||
unless you pass `--allow-no-key` — for a deployment that genuinely stores no
|
||||
encrypted data. Everywhere else, treat the refusal as the tool doing its job.
|
||||
|
||||
## Where to put the archive
|
||||
|
||||
`--out -` streams the tarball to stdout instead of writing a file, and every
|
||||
line of progress output goes to stderr — so piping the archive into something
|
||||
else is always safe, nothing progress-related lands in the stream.
|
||||
|
||||
Into `restic`:
|
||||
|
||||
```bash
|
||||
vantagectl backup --out - | restic backup --stdin --stdin-filename vantage.tar.gz
|
||||
```
|
||||
|
||||
Into S3:
|
||||
|
||||
```bash
|
||||
vantagectl backup --out - | aws s3 cp - s3://my-backups/vantage-$(date +%F).tar.gz
|
||||
```
|
||||
|
||||
An archive is as sensitive as a raw database dump — it carries every SSH key
|
||||
assignment, every secret group, every session-adjacent setting, in a form the
|
||||
right `KEY_ENCRYPTION_KEY` can decrypt. Whatever you pipe it into should
|
||||
encrypt it at rest; `vantagectl` itself does not.
|
||||
|
||||
## Checking a backup is real
|
||||
|
||||
```bash
|
||||
vantagectl verify /backups/vantage-backup-vantage-20260907T020000Z.tar.gz \
|
||||
--mongo-uri mongodb://localhost:27017 --db vantage
|
||||
```
|
||||
|
||||
Each line of output answers a different question:
|
||||
|
||||
- **`Archive`** — every member's checksum still matches; the tarball has not
|
||||
been truncated or corrupted.
|
||||
- **`Archive key`** / **`Your key`** — the fingerprint stored in the archive
|
||||
next to the fingerprint of the `KEY_ENCRYPTION_KEY` in your environment.
|
||||
- **`Key match`** — whether those two fingerprints agree.
|
||||
- **`Live probe`** — given `--mongo-uri`, `verify` goes one step further and
|
||||
decrypts a real ciphertext value from that database with the key you hold.
|
||||
A fingerprint match proves two archives agree about a key; only the probe
|
||||
proves the key in your hand actually reads the data.
|
||||
|
||||
`verify` exits non-zero the moment anything above is wrong, which is what makes
|
||||
it worth putting on a schedule — a backup job that "succeeded" last night is
|
||||
not the same claim as a backup that will actually restore.
|
||||
|
||||
## Looking inside an archive
|
||||
|
||||
`inspect` prints an archive's manifest and touches no database at all — no
|
||||
`--mongo-uri`, no key. It is what to run against an archive of unknown origin,
|
||||
before deciding whether it is the one you want:
|
||||
|
||||
```bash
|
||||
vantagectl inspect /backups/vantage-backup-vantage-20260907T020000Z.tar.gz
|
||||
```
|
||||
|
||||
It reports when the archive was taken and on which host, the Vantage and
|
||||
MongoDB versions behind it, the database it came from, the key fingerprint (or
|
||||
that it carries none), every collection with its document count and size, and
|
||||
anything `--exclude` left out. Opening the archive verifies every member's
|
||||
checksum on the way, so a corrupt archive fails here too.
|
||||
|
||||
Reach for `verify` instead when the question is whether the key you hold opens
|
||||
it; reach for `inspect` when the question is what it is.
|
||||
|
||||
## Restoring
|
||||
|
||||
`restore` expects the target database to be empty. Pointed at one that already
|
||||
holds data, it refuses outright: there are no merge semantics, because merging
|
||||
two control planes reconciles nothing and upserting old data over new would
|
||||
resurrect revoked keys and deleted users.
|
||||
|
||||
```bash
|
||||
vantagectl restore /backups/vantage-backup-vantage-20260907T020000Z.tar.gz \
|
||||
--mongo-uri mongodb://localhost:27017 --db vantage_restore
|
||||
```
|
||||
|
||||
To overwrite a database that is not empty, add `--force`, which drops each
|
||||
collection named in the archive before loading it. `--force` always needs a
|
||||
second assurance, in one of two forms:
|
||||
|
||||
- `--confirm-db NAME`, naming the target exactly. A mismatch is refused. This
|
||||
works everywhere — on a terminal and in a Kubernetes Job, a CI step or a cron
|
||||
entry alike — and is the form to script.
|
||||
- Nothing, on a terminal: `--force` alone prompts you to type the target
|
||||
database's name back, a deliberate pause before something destructive.
|
||||
|
||||
Without a terminal and without `--confirm-db`, `--force` is refused: there is
|
||||
nobody there to prompt. Naming the database in the command itself means a
|
||||
copy-pasted invocation carries its intended target with it and cannot destroy a
|
||||
different one by accident.
|
||||
|
||||
`--force` drops only the collections the archive carries. Anything else already
|
||||
in the target is left alone and named in a warning, so an archive taken with
|
||||
`--exclude workflow_log_lines` restored over a live database tells you the old
|
||||
log lines are still there, joined to freshly restored runs. Dropping them
|
||||
instead would delete data you never asked to delete.
|
||||
|
||||
`restore` also refuses when the archive's key fingerprint does not match the
|
||||
`KEY_ENCRYPTION_KEY` in your environment — see "When the key is wrong" below.
|
||||
|
||||
## The restore drill
|
||||
|
||||
An untested backup is a hypothesis, not a backup. Rehearse the whole path,
|
||||
monthly:
|
||||
|
||||
1. Restore last night's archive into a scratch database:
|
||||
```bash
|
||||
vantagectl restore /backups/vantage-backup-vantage-<date>.tar.gz \
|
||||
--mongo-uri mongodb://localhost:27017 --db vantage_drill
|
||||
```
|
||||
2. Run `verify` against the result to confirm the data that landed is actually
|
||||
readable with your current key:
|
||||
```bash
|
||||
vantagectl verify /backups/vantage-backup-vantage-<date>.tar.gz \
|
||||
--mongo-uri mongodb://localhost:27017 --db vantage_drill
|
||||
```
|
||||
3. Drop the scratch database. It served its purpose.
|
||||
|
||||
The failure this catches is not "the archive is corrupt" — `verify` alone
|
||||
catches that. It is "the archive is fine but nobody can actually stand a
|
||||
control plane back up from it," which only a real restore proves.
|
||||
|
||||
## When the key is wrong
|
||||
|
||||
If `restore` finds the archive's key fingerprint does not match the
|
||||
`KEY_ENCRYPTION_KEY` you are running with, it stops. Passing
|
||||
`--ignore-key-mismatch` proceeds anyway, but says plainly which collections
|
||||
will come back with ciphertext nobody can read:
|
||||
|
||||
- `keys` — SSH private keys and passphrases
|
||||
- `secrets` — the vault
|
||||
- `auth_providers` — OIDC/SSO client secrets
|
||||
- `console_sessions` — RDP/VNC credentials
|
||||
|
||||
There is no way to recover that ciphertext afterwards. If you have reached
|
||||
this point, the right key was lost along with the chance to read those rows —
|
||||
the fix is to re-enter each of them by hand (re-upload SSH keys, re-save vault
|
||||
secrets, reconfigure SSO), not to keep searching for a way to decrypt what is
|
||||
already in the database.
|
||||
@@ -24,7 +24,23 @@ values is permanently unreadable.
|
||||
Store the key somewhere other than the server it protects.
|
||||
:::
|
||||
|
||||
## Backing up MongoDB
|
||||
:::info Use `vantagectl`
|
||||
[**Backup and restore**](./backup-and-restore.md) is the supported way to take
|
||||
and restore a backup. It writes an archive that carries a fingerprint of
|
||||
`KEY_ENCRYPTION_KEY` — never the key — so a restore taken with the wrong key
|
||||
**refuses** rather than silently producing a database whose secrets nobody can
|
||||
read. It also checksums every archive member before writing anything, and
|
||||
refuses to restore into a database that already holds data. A plain
|
||||
`mongodump` does none of that: it records nothing about which key the data was
|
||||
encrypted under, so a restore from one succeeds even when the key is wrong and
|
||||
the failure only shows up later, as unreadable secrets.
|
||||
|
||||
The rest of this page, past the table above, describes the `mongodump` /
|
||||
`mongorestore` fallback for an operator who does not have `vantagectl`
|
||||
available. Prefer the linked page.
|
||||
:::
|
||||
|
||||
## Backing up MongoDB (fallback, without `vantagectl`)
|
||||
|
||||
With the bundled Mongo container:
|
||||
|
||||
@@ -33,6 +49,13 @@ docker compose exec -T mongo mongodump --archive --gzip --db vantage \
|
||||
> /backups/vantage-$(date +%F).archive.gz
|
||||
```
|
||||
|
||||
:::warning
|
||||
This archive records nothing about which `KEY_ENCRYPTION_KEY` it was taken
|
||||
under. Restoring it with the wrong key produces a database that looks intact
|
||||
and is not — every secret in it is silently unreadable until something tries
|
||||
to decrypt one.
|
||||
:::
|
||||
|
||||
Restoring:
|
||||
|
||||
```bash
|
||||
@@ -69,12 +92,13 @@ What it does **not** do is reconcile the world. After a restore:
|
||||
|
||||
| What | When |
|
||||
| ----------------- | ----------------------------------------------------- |
|
||||
| MongoDB dump | Nightly, retained per your policy |
|
||||
| Backup | Nightly, retained per your policy |
|
||||
| Environment file | On change, held in a password manager or secret store |
|
||||
| Restore rehearsal | Occasionally, into a throwaway host |
|
||||
|
||||
Rehearse a restore now and again. It is the step most often skipped, and the one
|
||||
that finds the problems.
|
||||
that finds the problems. See [Backup and restore](./backup-and-restore.md) for
|
||||
the drill, and for `verify`, which checks a backup is real without a restore.
|
||||
|
||||
## Cloud instances
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ it is absent.
|
||||
| `VANTAGE_LICENSE` | no | | A licence supplied at startup, so an automated install does not have to paste one in |
|
||||
| `VANTAGE_TRIVY_DB_REF` | no | `ghcr.io/aquasecurity/trivy-db:2` | Where the vulnerability database is pulled from. Point it at a mirror for an air-gapped install |
|
||||
| `VANTAGE_VULNDB_DISABLED` | no | | `true` switches [vulnerability scanning](../vantage/vulnerabilities.md) off entirely. Findings already stored are still served, and still shown as stale |
|
||||
| `TRUSTED_PROXIES` | no | `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` | Comma-separated CIDRs or addresses of proxies allowed to set `X-Forwarded-For`. The shipped Docker Compose and Helm chart default to the private RFC1918 ranges, which covers Nginx Proxy Manager on the Docker bridge network and Traefik on a Kubernetes pod CIDR. An operator whose proxy sits on a public address must set this themselves, or every visitor behind it shares one address for rate-limiting purposes. Unset entirely (outside those shipped defaults) trusts none, so the client address is the direct peer |
|
||||
| `TRUSTED_PROXIES` | no | `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` | Comma-separated CIDRs or addresses of proxies allowed to set `X-Forwarded-For`. The shipped Docker Compose and Helm chart default to the private RFC1918 ranges, which covers Nginx Proxy Manager on the Docker bridge network and Traefik on a Kubernetes pod CIDR. An operator whose proxy sits on a public address must set this themselves, or every visitor behind it shares one address for rate-limiting purposes. Unset entirely (outside those shipped defaults) trusts none, so the client address is the direct peer. **On a LAN-only install, narrow this to your proxy's address.** The RFC1918 default trusts every private range, so a client on 192.168.0.0/16 reaching the server directly is itself a "trusted proxy" and can put whatever it likes in `X-Forwarded-For` — and, on the public status route, in `X-Forwarded-Host`. Behind a proxy on a public address, or with no proxy at all, that is not reachable; on a flat LAN it is |
|
||||
|
||||
:::danger `KEY_ENCRYPTION_KEY` has no recovery path
|
||||
It encrypts SSH private keys, vault secrets, OIDC client secrets and console
|
||||
|
||||
@@ -9,7 +9,7 @@ sidebar_label: Ports and networking
|
||||
| Port | Service | Who connects | Expose publicly |
|
||||
| ------- | ----------- | -------------------------------- | --------------- |
|
||||
| `3000` | web | Browsers, via your reverse proxy | Yes, behind TLS |
|
||||
| `8080` | server API | The web app | No, firewall it |
|
||||
| `8080` | server API | Your reverse proxy | Not directly — proxied |
|
||||
| `9090` | server gRPC | Agents | **Yes** |
|
||||
| `4822` | guacd | The server | No, firewall it |
|
||||
| `27017` | MongoDB | The server | No |
|
||||
@@ -20,8 +20,8 @@ sidebar_label: Ports and networking
|
||||
```mermaid
|
||||
flowchart LR
|
||||
B["Browser"] -->|HTTPS| P["Reverse proxy"]
|
||||
P --> W["web :3000"]
|
||||
W --> S["server :8080"]
|
||||
P -->|"everything else"| W["web :3000"]
|
||||
P -->|"/api /auth /public /install* /update*"| S["server :8080"]
|
||||
A["Agent on a managed server"] -->|"gRPC/TLS :9090, outbound"| S
|
||||
S --> G["guacd :4822"]
|
||||
G -->|"relayed over the :9090 stream"| A
|
||||
@@ -77,8 +77,20 @@ On a private network you can skip TLS instead, by setting `tls: false` in each
|
||||
|
||||
## Reverse proxy notes
|
||||
|
||||
- Point the proxy at `web:3000`. The web app reaches the API internally, so
|
||||
`8080` does not need publishing.
|
||||
- **The proxy routes two backends on one hostname**, and both are required:
|
||||
|
||||
| Path | Backend |
|
||||
| ------------------------------------------------------------- | ------------- |
|
||||
| `/api`, `/auth`, `/public`, `/install`, `/install.ps1`, `/update`, `/update.ps1` | `server:8080` |
|
||||
| everything else | `web:3000` |
|
||||
|
||||
The web app forwards nothing to the API. Sending the whole hostname to
|
||||
`web:3000` loads the interface and every request it makes answers `404` —
|
||||
including the login form.
|
||||
|
||||
- Both backends must be the **same** hostname and certificate. The browser
|
||||
calls `/api` relative to the page it is on, and the session cookie is
|
||||
host-only.
|
||||
- The console uses a **WebSocket** at `/api/console/tunnel`. A proxy that does
|
||||
not forward upgrade headers breaks the console and nothing else.
|
||||
- Workflow log streaming is a long-lived response. A short proxy read timeout
|
||||
|
||||
@@ -20,6 +20,13 @@ or is not 64 hex characters.
|
||||
|
||||
## Nobody can sign in
|
||||
|
||||
**Every request 404s and the interface loads fine.** Your reverse proxy sends
|
||||
the whole hostname to `web:3000`. `/api`, `/auth`, `/public`, `/install*` and
|
||||
`/update*` belong to `server:8080` and the web app forwards nothing — see
|
||||
[Ports and networking](./ports-and-networking.md#reverse-proxy-notes). The
|
||||
tell is `curl -si https://<your-host>/auth/bootstrap-status` returning HTML
|
||||
with `x-powered-by: Next.js` instead of JSON.
|
||||
|
||||
**`/setup` appears when users already exist.** The server is pointed at a
|
||||
different database than you think. Check the database name in `MONGO_URI`,
|
||||
which is taken from the end of the URI.
|
||||
@@ -169,6 +176,11 @@ is `<your-instance>.vantage.<yourdomain>/status/<page-id>`, the same
|
||||
per-instance subdomain everything else in Vantage uses. A wrong or missing
|
||||
subdomain resolves to no instance at all, which is also a 404.
|
||||
|
||||
Third possibility: `/public` is not routed to the server. Check with
|
||||
`curl -si https://<your-instance>.vantage.<yourdomain>/public/status/<page-id>`
|
||||
— JSON is correct, HTML carrying `x-powered-by: Next.js` means the proxy sent
|
||||
that prefix to the web app.
|
||||
|
||||
**Loads, but shows an explanation instead of components.** This is not a
|
||||
fault — it is the page working as designed. It means either the licence has
|
||||
lapsed (a self-hosted instance past its grace period, or a cloud instance
|
||||
|
||||
@@ -22,9 +22,19 @@ of lowercase letters, digits and `-`, starting and ending with a letter or
|
||||
digit. It becomes part of the public URL:
|
||||
|
||||
```
|
||||
https://<your-instance>.vantage.<yourdomain>/status/<page-id>
|
||||
https://<your-vantage-address>/status/<page-id>
|
||||
```
|
||||
|
||||
On **Vantage Cloud** that address is your instance's own subdomain, so the page
|
||||
is at `https://<your-instance>.vantage.hostxtra.co.uk/status/<page-id>`.
|
||||
|
||||
On a **self-hosted** install it is whatever address you reach Vantage on —
|
||||
`https://vantage.acme.com/status/<page-id>`, or an IP and port on a LAN
|
||||
install. A self-hosted install serves exactly one Vantage instance, so no
|
||||
subdomain is needed to say which one you mean. The **Copy** control next to the
|
||||
page address in the editor gives you the exact URL for your install, which is
|
||||
the one to hand out.
|
||||
|
||||
**The page id cannot be changed after creation.** Once you have shared the
|
||||
link, changing the id would break it, so pick something you would still be
|
||||
happy with in a year — `platform`, `api`, a customer's own name for a
|
||||
@@ -38,6 +48,11 @@ so you can build out the components and copy before announcing it. Toggle
|
||||
**Published** when it is ready. Un-publishing later takes it back to *not
|
||||
found* rather than deleting anything.
|
||||
|
||||
**Delete page**, in the editor header, is the only way to correct a page id you
|
||||
regret — the id is fixed once created. It takes the page, its sections and its
|
||||
authored incidents with it; monitors and their history are untouched. If you
|
||||
only want the page off the internet, un-publish it instead.
|
||||
|
||||
## Sections and components
|
||||
|
||||
A page is organised into **sections** — arbitrary groupings such as "API" or
|
||||
@@ -55,12 +70,15 @@ more and claiming otherwise would be a false claim of health.
|
||||
|
||||
## What a visitor sees
|
||||
|
||||
- Component name, current state (up / down / under maintenance / unknown) and
|
||||
a 90-day uptime percentage.
|
||||
- Component name, current state (up / down / under maintenance / pending /
|
||||
unknown) and a 90-day uptime percentage. **Pending** is a monitor that has
|
||||
been added but has not produced a result yet; **unknown** is one nothing is
|
||||
checking any more.
|
||||
- A 90-day history bar per component.
|
||||
- Any active incidents, upcoming maintenance, and a rolling history of both.
|
||||
- An optional banner across the top of the page (info / warning / critical),
|
||||
for anything you want said regardless of component state.
|
||||
- An optional banner across the top of the page, for anything you want said
|
||||
regardless of component state. It is one notice with one appearance — there
|
||||
are no severity levels to choose between.
|
||||
|
||||
A visitor never sees a target URL, host or port, the check's expected status
|
||||
or keyword, latency, a certificate expiry date, failure text, or which
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ const sidebars: SidebarsConfig = {
|
||||
{
|
||||
type: "category",
|
||||
label: "Operations",
|
||||
items: ["operations/upgrading", "operations/backups", "operations/agent-updates"],
|
||||
items: ["operations/upgrading", "operations/backups", "operations/backup-and-restore", "operations/agent-updates"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user