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,80 @@
|
||||
---
|
||||
id: claim-free-licence
|
||||
title: Claim a Free licence
|
||||
sidebar_label: Claim a Free licence
|
||||
---
|
||||
|
||||
A self-hosted install runs unlicensed until you give it a licence. Free is a
|
||||
real tier in both deployments, and you can claim one for your install from the
|
||||
HQ portal.
|
||||
|
||||
## What a licence is
|
||||
|
||||
A signed file. It carries the instance UUID it belongs to, the tier, the server
|
||||
allowance, feature toggles and an expiry. The control plane verifies the
|
||||
signature locally — checking a licence never contacts HQ, and a running instance
|
||||
does not need HQ to be reachable.
|
||||
|
||||
Signing happens in exactly one place, in HQ. The control plane can only verify.
|
||||
|
||||
## 1. Find your instance UUID
|
||||
|
||||
In the control plane, go to **Settings → Licence**. The instance UUID is shown
|
||||
there. It is the identity your licence binds to.
|
||||
|
||||
## 2. Link the install to your HQ account
|
||||
|
||||
1. Sign in at [Vantage HQ](https://vantage-hq.hostxtra.co.uk). If you have no
|
||||
account, see [Accounts and signup](../hq/accounts-and-signup.md).
|
||||
2. Choose **Link an instance**.
|
||||
3. Paste the instance UUID and give it a name you will recognise.
|
||||
|
||||
Linking claims the UUID for your account. A UUID already linked elsewhere is
|
||||
refused with a conflict rather than silently moved.
|
||||
|
||||
## 3. Claim Free
|
||||
|
||||
With the instance linked, choose **Claim Free** on it. HQ issues a Free licence
|
||||
bound to that UUID and hands it back.
|
||||
|
||||
:::info One Free per account, per deployment
|
||||
The limit is enforced per account **and** deployment, so a Free cloud instance
|
||||
does not stop you claiming Free on a self-hosted install. Both the friendly
|
||||
pre-check and the issuer apply the same rule — deliberately, because a
|
||||
pre-check stricter than the issuer would refuse something that would actually
|
||||
have worked.
|
||||
:::
|
||||
|
||||
## 4. Install the licence
|
||||
|
||||
Download the licence from HQ and paste it in the control plane at
|
||||
**Settings → Licence**.
|
||||
|
||||
The instance validates the signature, checks the UUID matches its own, and
|
||||
starts reporting the tier, allowance and expiry.
|
||||
|
||||
:::warning Cloud instances cannot paste a licence
|
||||
On a cloud instance `POST /license` answers `409 cloud_managed`, and the UI
|
||||
hides the form entirely. A cloud licence is written directly by HQ. This is not
|
||||
a restriction the injection path has to work around — it writes to the database,
|
||||
not through the endpoint.
|
||||
:::
|
||||
|
||||
## Renewing
|
||||
|
||||
Free licences are renewable from HQ within a renewal window near expiry;
|
||||
outside that window the renew call refuses. See [Free tier](../hq/free-tier.md).
|
||||
|
||||
Pasting a licence keeps working while the current one is expired — that endpoint
|
||||
is exempt from the licence check, because it is the way out of degraded mode.
|
||||
|
||||
## Moving the install to new hardware
|
||||
|
||||
Rebuilding produces a new instance UUID, and a licence binds to a UUID. Use
|
||||
**Relink** in HQ to move the licence across. The number of relinks per term is
|
||||
capped; the portal shows how many you have left.
|
||||
|
||||
## Next
|
||||
|
||||
- [Licensing and entitlements](../hq/licensing-and-entitlements.md)
|
||||
- [Buying a paid self-hosted licence](../hq/self-hosted-instances.md)
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
id: cloud-vs-self-hosted
|
||||
title: Cloud or self-hosted
|
||||
sidebar_label: Cloud or self-hosted
|
||||
---
|
||||
|
||||
Vantage runs in two deployments. They are the same software; what differs is
|
||||
who operates it and how licensing, users and data lifecycle work.
|
||||
|
||||
## At a glance
|
||||
|
||||
| | Cloud | Self-hosted |
|
||||
| --- | --- | --- |
|
||||
| Who runs it | We do | You do |
|
||||
| Where you sign in | `<your-slug>.vantage.hostxtra.co.uk` | Your own hostname |
|
||||
| Database and backups | Ours | Yours |
|
||||
| Licence | Written for you when you buy or create the instance | Pasted in, or claimed from HQ |
|
||||
| Team members | Granted from HQ; the instance holds a projection | Created in the instance itself |
|
||||
| Free tier | Yes, one per account | Yes, one per account |
|
||||
| Expired Free instance | Eventually deleted, after warning | Never deleted |
|
||||
|
||||
## Cloud
|
||||
|
||||
You create an instance from the HQ portal and it exists a few seconds later,
|
||||
already licensed. People you grant access to get a real user inside that
|
||||
instance — see [People and roles](../hq/people-and-roles.md) — but HQ owns their
|
||||
password, role and existence.
|
||||
|
||||
:::info The instance does not phone home
|
||||
A grant writes a user row into the control plane once. After that the instance
|
||||
authenticates that person entirely on its own. HQ being down does not stop
|
||||
anyone signing in to a running instance.
|
||||
:::
|
||||
|
||||
Cloud instances on the Free tier are reaped after their licence expires, with
|
||||
warning emails first. See [Free tier](../hq/free-tier.md).
|
||||
|
||||
## Self-hosted
|
||||
|
||||
You run the Docker Compose stack on your own infrastructure. Nothing about the
|
||||
control plane requires an internet connection to HQ at runtime — a licence is a
|
||||
signed file, verified locally.
|
||||
|
||||
Two ways to get one:
|
||||
|
||||
1. **Free** — link the install to an HQ account and claim it
|
||||
([Claim a Free licence](./claim-free-licence.md)).
|
||||
2. **Paid** — buy from HQ, which creates a placeholder, then paste the install's
|
||||
real instance UUID to bind and issue
|
||||
([Self-hosted instances](../hq/self-hosted-instances.md)).
|
||||
|
||||
Self-hosted users are local (or OIDC). There is no projection from HQ, and the
|
||||
three member endpoints in HQ refuse to touch a self-hosted instance at all.
|
||||
|
||||
:::warning Self-hosted instances are never deleted by us
|
||||
The reaper that removes expired Free cloud instances is disabled by default and
|
||||
must stay that way on a self-hosted install. See `FREE_INSTANCE_REAP_AFTER` in
|
||||
[Environment variables](../reference/environment-variables.md).
|
||||
:::
|
||||
|
||||
## Which should you pick
|
||||
|
||||
Pick cloud if you want the thing running now and do not want to own a MongoDB.
|
||||
Pick self-hosted if your policy requires the control plane inside your own
|
||||
network, or the servers you manage cannot reach the public internet.
|
||||
|
||||
Moving between them is a migration, not a switch — instances are bound to a
|
||||
deployment at creation, and a licence binds to an instance UUID.
|
||||
|
||||
## Next
|
||||
|
||||
- [Self-hosted install](./self-hosted-install.md)
|
||||
- [Accounts and signup](../hq/accounts-and-signup.md) if you are going cloud
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
id: first-login
|
||||
title: First login
|
||||
sidebar_label: First login
|
||||
---
|
||||
|
||||
A fresh install has no users and no organisation. The first visit creates both.
|
||||
|
||||
## 1. Bootstrap
|
||||
|
||||
Open the control plane in a browser. Because no user exists, you land on
|
||||
`/setup`.
|
||||
|
||||
Fill in:
|
||||
|
||||
| Field | Notes |
|
||||
| --- | --- |
|
||||
| Organisation name | Display name. Shown throughout the UI |
|
||||
| Slug | Lowercase, used in the hostname on cloud. Some names are reserved |
|
||||
| Your name | |
|
||||
| Email | Becomes your sign-in identity |
|
||||
| Password | Stored bcrypt-hashed |
|
||||
|
||||
Submitting creates the organisation and its **owner** — you.
|
||||
|
||||
:::warning Bootstrap works exactly once
|
||||
The endpoint is open only while the database has no users. As soon as the first
|
||||
one exists, `/setup` redirects to the login page and the bootstrap endpoint
|
||||
refuses. There is no second chance to create the first owner, so record the
|
||||
credentials before you close the tab.
|
||||
:::
|
||||
|
||||
## 2. Sign in
|
||||
|
||||
You are taken to `/login`. Sign in with the email and password you just set.
|
||||
|
||||
Sessions are an opaque 32-byte token in the `km_session` cookie, with the body
|
||||
held in Redis for 24 hours. Restarting Redis signs everyone out and loses
|
||||
nothing else.
|
||||
|
||||
## 3. Look around
|
||||
|
||||
You land on the fleet dashboard, which is empty. The sidebar is the whole
|
||||
product:
|
||||
|
||||
| Section | What it does |
|
||||
| --- | --- |
|
||||
| Servers | The fleet — enrol, inspect, console, update |
|
||||
| Keys | SSH public keys and their assignments |
|
||||
| Workflows | Compose and run scripted work |
|
||||
| Steps | The reusable step library |
|
||||
| Monitors | HTTP, TCP, ICMP and TLS checks |
|
||||
| Secrets | The encrypted vault |
|
||||
| Audit | Every mutating action |
|
||||
| Settings | Members, SSO, alerts, retention, licence |
|
||||
|
||||
## 4. Add the rest of your team
|
||||
|
||||
Go to **Settings → Access**. Add members with a role:
|
||||
|
||||
| Role | Can |
|
||||
| --- | --- |
|
||||
| `owner` | Everything, including billing-adjacent settings |
|
||||
| `admin` | Everything except owner-only settings |
|
||||
| `member` | Day-to-day work — servers, keys, workflows, monitors |
|
||||
|
||||
Settings and organisation management require `owner` or `admin`.
|
||||
|
||||
If you would rather not manage passwords, configure OIDC instead — see
|
||||
[Settings](../vantage/settings.md#single-sign-on-oidc). OIDC is configured per
|
||||
organisation, and the client secret is stored encrypted.
|
||||
|
||||
## Next
|
||||
|
||||
[Add your first server](./first-server.md).
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
id: first-server
|
||||
title: Add your first server
|
||||
sidebar_label: Add your first server
|
||||
---
|
||||
|
||||
Enrolling a machine means running one command on it. The control plane issues a
|
||||
short-lived token, the install script fetches the agent and writes a config, and
|
||||
the machine registers itself.
|
||||
|
||||
## 1. Create the enrolment
|
||||
|
||||
In the UI, go to **Servers → Add server**. That calls `POST /api/servers/new`,
|
||||
which generates a server ID and a pre-registration token and hands back a ready
|
||||
one-liner.
|
||||
|
||||
:::warning The token is single-use and lives one hour
|
||||
It is the only credential in the flow, and it is spent the moment the agent
|
||||
calls `Register`. If you paste it somewhere and come back tomorrow, create a new
|
||||
enrolment instead — nothing is lost by doing so.
|
||||
:::
|
||||
|
||||
## 2. Run the one-liner
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
curl -fsSL "https://vantage.example.com/install?server_id=<id>&token=<token>" | bash
|
||||
```
|
||||
|
||||
Run it as root. The script:
|
||||
|
||||
1. Detects architecture — `x86_64` and `aarch64` only; anything else exits.
|
||||
2. Asks the Gitea API for the newest `agent/v*` release.
|
||||
3. Downloads the binary and `checksums.txt`, and **verifies the SHA-256**,
|
||||
aborting on a mismatch.
|
||||
4. Installs to `/usr/local/bin/vantage-agent`, mode `0755`.
|
||||
5. Writes `/etc/vantage/config.yaml` (directory `0700`, file `0600`) containing
|
||||
the server ID, the pre-registration token and the gRPC host.
|
||||
6. Writes `/etc/systemd/system/vantage-agent.service` with `Restart=always`, and
|
||||
runs `systemctl enable --now vantage-agent`.
|
||||
|
||||
### Windows
|
||||
|
||||
```powershell
|
||||
irm "https://vantage.example.com/install.ps1?server_id=<id>&token=<token>" | iex
|
||||
```
|
||||
|
||||
Run from an elevated PowerShell. The agent is registered as a service through
|
||||
NSSM, with the config at `%ProgramData%\vantage\config.yaml`. There is also an
|
||||
MSI built by CI if you would rather deploy that.
|
||||
|
||||
:::info Windows agents are second-class on purpose
|
||||
They register, heartbeat, run workflow steps and report inventory. They do
|
||||
**not** manage `authorized_keys` — the key subsystem is Linux-only, and a
|
||||
Windows agent stops after the heartbeat portion of the poll.
|
||||
:::
|
||||
|
||||
## 3. Watch it come up
|
||||
|
||||
The server appears immediately as `pending`. Within one poll interval — 30
|
||||
seconds — it flips to `active`.
|
||||
|
||||
On the machine:
|
||||
|
||||
```bash
|
||||
systemctl status vantage-agent
|
||||
journalctl -u vantage-agent -f
|
||||
```
|
||||
|
||||
What happens on that first run:
|
||||
|
||||
```
|
||||
1. Load /etc/vantage/config.yaml
|
||||
2. pre_reg_token present → Register() → save agent_token, clear pre_reg_token
|
||||
3. Reconnect with the permanent token
|
||||
4. Start: command stream · hourly update check · inventory · monitors
|
||||
5. Enter the SyncKeys poll loop
|
||||
```
|
||||
|
||||
After registration the config no longer contains the pre-registration token; it
|
||||
contains a permanent agent token instead. The control plane stores only the
|
||||
SHA-256 of that token, never the token itself.
|
||||
|
||||
## 4. Confirm it works
|
||||
|
||||
Open the server's detail page. Within a minute or two you should see:
|
||||
|
||||
- Status `active`, with a recent last-seen timestamp.
|
||||
- Inventory — CPU, memory, swap, partitions, kernel. Metrics refresh every 30
|
||||
seconds; the full static snapshot every 15 minutes.
|
||||
- Pending OS updates, checked hourly.
|
||||
|
||||
## If it does not appear
|
||||
|
||||
| Symptom | Cause |
|
||||
| --- | --- |
|
||||
| Script exits at "Unsupported architecture" | Not amd64 or arm64 |
|
||||
| "Checksum mismatch!" | Interrupted download, or a proxy rewriting the body. Re-run |
|
||||
| "Could not determine latest agent version" | The host cannot reach `gitea.hostxtra.co.uk`, or no `agent/v*` release exists |
|
||||
| Service runs, server stays `pending` | The machine cannot reach `GRPC_HOST`. Test it from that machine |
|
||||
| Registers once then goes `offline` | Reachable for `Register` but not for the poll — usually a firewall that permits the initial connection but drops the long-lived one |
|
||||
|
||||
A server is marked `offline` when its last-seen time passes the threshold; that
|
||||
sweep runs every two minutes, so allow for it before concluding anything.
|
||||
|
||||
## Next
|
||||
|
||||
- [Assign an SSH key](../vantage/ssh-keys.md)
|
||||
- [Run a workflow](../vantage/workflows.md)
|
||||
- [Claim a Free licence](./claim-free-licence.md)
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
id: self-hosted-install
|
||||
title: Install Vantage (self-hosted)
|
||||
sidebar_label: Self-hosted install
|
||||
---
|
||||
|
||||
This installs the control plane on a host you own. Budget about fifteen minutes.
|
||||
|
||||
## Before you start
|
||||
|
||||
You need:
|
||||
|
||||
- A Linux host with **Docker** and the **Compose plugin**.
|
||||
- A DNS name pointing at it. You will use it for both the web UI and, with a
|
||||
port, for agents.
|
||||
- A reverse proxy terminating TLS in front of the web UI. gRPC on `:9090` is
|
||||
reached directly by agents.
|
||||
- Two ports reachable from every machine you intend to manage: the web port for
|
||||
people, and **9090** for agents.
|
||||
- Outbound access from the control plane, and from every managed machine, to
|
||||
`gitea.hostxtra.co.uk`, which serves the agent releases.
|
||||
|
||||
The stack itself brings MongoDB, Redis and guacd with it. You do not need to
|
||||
provide a database.
|
||||
|
||||
## 1. Get the compose file
|
||||
|
||||
Put `deploy/docker-compose.yml` from the repository in a working directory, for
|
||||
example `/opt/vantage`.
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/vantage/data && cd /opt/vantage
|
||||
# copy docker-compose.yml here
|
||||
```
|
||||
|
||||
The `server` service bind-mounts `./data`, which is where workflow run logs are
|
||||
written. Create it before first boot so it is not owned by root-in-container in
|
||||
a way you did not intend.
|
||||
|
||||
## 2. Write the environment file
|
||||
|
||||
Create `/opt/vantage/.env`:
|
||||
|
||||
```bash
|
||||
# The host:port agents dial. NOT the web URL — this port speaks gRPC.
|
||||
GRPC_HOST=vantage.example.com:9090
|
||||
|
||||
|
||||
# 32 bytes as 64 hex characters. Generate with the command below.
|
||||
KEY_ENCRYPTION_KEY=
|
||||
|
||||
# Optional: where workflow run logs are written inside the container.
|
||||
VANTAGE_WORKFLOW_LOG_DIR=/data/workflow-logs
|
||||
```
|
||||
|
||||
Generate the encryption key:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
:::danger Keep the encryption key
|
||||
`KEY_ENCRYPTION_KEY` encrypts SSH private keys, vault secrets, OIDC client
|
||||
secrets and console credentials with AES-256-GCM. Lose it and every one of those
|
||||
becomes unreadable — there is no recovery path. Back it up somewhere other than
|
||||
the server it protects, and never rotate it without a planned re-encryption.
|
||||
:::
|
||||
|
||||
:::warning `GRPC_HOST` has no default
|
||||
The server refuses to boot without it. There is deliberately no fallback to the
|
||||
web host: that would hand every agent a port that does not speak gRPC, and the
|
||||
failure would only surface later, on each agent, as a connection error.
|
||||
:::
|
||||
|
||||
## 3. Start the stack
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Five services come up: `mongo`, `redis`, `guacd`, `server` and `web`.
|
||||
|
||||
Check the server got through boot:
|
||||
|
||||
```bash
|
||||
docker compose logs -f server
|
||||
```
|
||||
|
||||
Boot runs database migrations, builds indexes and seeds the default workflow
|
||||
step library. Index builders for auth and settings are **fatal on failure** —
|
||||
they enforce tenant isolation, so the server would rather not start than start
|
||||
without them.
|
||||
|
||||
## 4. Put a proxy in front
|
||||
|
||||
Point your reverse proxy at `web` on port `3000` and terminate TLS there. The
|
||||
web app calls the REST API through a Next rewrite, so you do not need to expose
|
||||
`8080` publicly.
|
||||
|
||||
Do **not** proxy `9090`. Agents connect to it directly over TLS.
|
||||
|
||||
## 5. First sign-in
|
||||
|
||||
Open your hostname in a browser. With no users in the database, you are sent to
|
||||
`/setup`.
|
||||
|
||||
Continue with [First login](./first-login.md).
|
||||
|
||||
## Verifying the install
|
||||
|
||||
| Check | Expected |
|
||||
| --- | --- |
|
||||
| `docker compose ps` | five services `running` |
|
||||
| `curl -s localhost:8080/auth/bootstrap-status` | JSON saying bootstrap is needed |
|
||||
| `nc -z your-host 9090` | open |
|
||||
| `docker compose logs server \| grep -i fatal` | nothing |
|
||||
|
||||
## Common install problems
|
||||
|
||||
**Server exits immediately.** Almost always a missing `GRPC_HOST`. The log line
|
||||
names it.
|
||||
|
||||
**Agents register but never go active.** They reached `:9090` for `Register` but
|
||||
cannot sustain the poll, or `GRPC_HOST` names a host they resolve differently.
|
||||
Check from the managed machine, not from the control plane host.
|
||||
|
||||
**Secrets pages error.** `KEY_ENCRYPTION_KEY` is empty or not 64 hex characters.
|
||||
|
||||
More in [Troubleshooting](../reference/troubleshooting.md).
|
||||
|
||||
## What this install does not include
|
||||
|
||||
The marketing site, the public form service, the HQ portal and this
|
||||
documentation site are separate services in `deploy/docker-compose.site.yml`.
|
||||
A self-hosted install deliberately runs none of them, and in particular never
|
||||
holds the licence signing key.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: what-is-vantage
|
||||
title: What is Vantage
|
||||
sidebar_label: What is Vantage
|
||||
---
|
||||
|
||||
Vantage manages a fleet of servers from one place. It began as SSH key
|
||||
management and grew outwards: key assignment, scripted workflow execution,
|
||||
service monitoring, a secrets vault, a browser-based console and OS update
|
||||
management.
|
||||
|
||||
## The pieces
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
W["Web UI<br/>servers · keys · workflows · monitors<br/>secrets · audit · console · settings"]
|
||||
S["Server<br/>REST :8080 · gRPC :9090<br/>MongoDB · Redis · guacd"]
|
||||
A["Agent<br/>one per managed server<br/>Linux and Windows"]
|
||||
W -->|REST, cookie session| S
|
||||
S -->|gRPC over TLS| A
|
||||
A -.->|outbound only| S
|
||||
```
|
||||
|
||||
**The server** holds all state and does all decision-making. It exposes a REST
|
||||
API on `:8080` for the web UI and a gRPC API on `:9090` for agents. MongoDB
|
||||
stores everything durable; Redis stores sessions and nothing else.
|
||||
|
||||
**The agent** is a single Go binary running as root on each managed server. It
|
||||
polls the control plane every 30 seconds for desired key state, and holds a
|
||||
bidirectional command stream so the server can push work — run a workflow step,
|
||||
generate a key, apply updates — without waiting for the next poll.
|
||||
|
||||
**The web UI** is the operator interface. Everything it does goes through the
|
||||
REST API, which is the actual security boundary; the UI only ever makes things
|
||||
convenient.
|
||||
|
||||
## How agents connect
|
||||
|
||||
The agent dials **out** to the control plane. There is no inbound listener on a
|
||||
managed server, no port to open and no NAT traversal to arrange. If the machine
|
||||
can reach your Vantage host on the gRPC port, it can be managed.
|
||||
|
||||
That direction is why `GRPC_HOST` exists as an explicit setting: the agent has
|
||||
to be told a `host:port` it can reach, and there is no safe default the server
|
||||
could guess on its behalf.
|
||||
|
||||
## Two request patterns
|
||||
|
||||
| Pattern | Used for | Why |
|
||||
| --- | --- | --- |
|
||||
| Poll (`SyncKeys`, every 30s) | desired SSH key state | Key changes are not urgent, and polling survives a dropped connection with no reconnection logic |
|
||||
| Push (`CommandStream`) | workflow steps, key generation, updates, agent self-update | Clicking Run should not wait up to 30 seconds |
|
||||
|
||||
## Multi-tenancy
|
||||
|
||||
Every document in the database carries an instance ID, and every query is scoped
|
||||
by it. One deployment can therefore host many independent tenants. On a
|
||||
self-hosted install that mechanism is still there — you simply have one tenant.
|
||||
|
||||
## Next
|
||||
|
||||
- [Cloud or self-hosted](./cloud-vs-self-hosted.md) — which one you want
|
||||
- [Self-hosted install](./self-hosted-install.md) — stand it up
|
||||
Reference in New Issue
Block a user