diff --git a/docs/getting-started/claim-free-licence.md b/docs/getting-started/claim-free-licence.md index f6ac6ab..fcba8f2 100644 --- a/docs/getting-started/claim-free-licence.md +++ b/docs/getting-started/claim-free-licence.md @@ -4,64 +4,73 @@ 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. +A self-hosted install stays read-only until you give it a licence. You claim a +Free one from Vantage HQ, and it takes a couple of minutes. + +:::warning An unlicensed install is read-only +You can sign in and look around, but adding servers, keys, workflows and +everything else is refused until a licence is installed. Do this before +[adding your first server](./first-server.md). +::: ## What a licence is -A signed file. It carries the instance ID it belongs to, the tier, the server -allowance, feature toggles and an expiry. The control plane verifies the -signature locally. +A signed file that names your instance, its tier, how many servers you may +manage, which features are enabled and when it expires. Your install checks the +signature itself, so it never has to reach Vantage HQ to work. -A running instance does not need HQ to be reachable. - -:::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. +:::info One Free licence per account, per deployment +A Free cloud instance does not use up your Free self-hosted one. They are +separate. ::: ## 1. Find your instance ID -In the control plane, go to **Settings → Licence**. The instance ID is shown there. +Open the **Licence** page from the sidebar of your install. The instance ID is +shown at the top, and it is the value Vantage HQ asks for. -## 2. Create a free license +## 2. Create the instance in Vantage HQ 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. Click on the **Buy A Plan** button. -3. Click on **Self Hosted** then click on the **Free** plan, then finally Paste the instance ID and give it a name you will recognise. + account yet, see [Accounts and signup](../hq/accounts-and-signup.md). +2. On **Overview**, choose **License my own install**. Once you already have an + instance, the same page offers **Buy a plan** instead. +3. Choose **Self-hosted**, then the **Free** plan. +4. Paste your instance ID, give the instance a name you will recognise, and + click **Create licence**. -You will then see the new instance on the **Overview** page. +The new instance now appears on the **Overview** page. -## 3. Downloading the free license +## 3. Download the licence -With the instance created go to the **Overview** page and expand the new instance. - -Click on the **View Instance Settings** button. You can then click on the **Download License** or the **Copy to clipboard** button. +Expand the new instance on **Overview** and click **View Instance Settings**. +From there, use **Download licence** or **Copy to clipboard**. ## 4. Install the licence -Download the licence from HQ and paste it in the control plane at -**Settings → Licence**. +Back in your install, open the **Licence** page, paste the licence and save. +Your instance confirms the licence was issued to it, then shows your tier, +server allowance and expiry date. -The instance validates the signature, checks the ID matches its own, and -starts reporting the tier, allowance and expiry. - -:::info Cloud instances do **not** require installing the license as this is done automatically. +:::info Cloud instances need none of this +A cloud instance is licensed automatically when it is created. These steps are +for self-hosted installs only. ::: ## Renewing -Free licences are renewable from HQ within a renewal window near expiry; -outside that window you cannot renew early. See [Free tier](../hq/free-tier.md). +Free licences run for a year. The renew button appears in Vantage HQ seven days +before expiry and stays available after it, so a lapsed instance can still be +rescued. See [Free tier](../hq/free-tier.md). ## Moving the install to new hardware -Rebuilding produces a new instance ID, and a licence binds to a ID. 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. +A rebuilt install gets a new instance ID, and a licence only works for the ID it +was issued to. Use **Relink** in Vantage HQ to move the licence across. You get +three relinks per term, and the portal shows how many are left. ## Next +- [Add your first server](./first-server.md) - [Licensing and entitlements](../hq/licensing-and-entitlements.md) - [Buying a paid self-hosted licence](../hq/self-hosted-instances.md) diff --git a/docs/getting-started/cloud-vs-self-hosted.md b/docs/getting-started/cloud-vs-self-hosted.md index 71b89c6..b15d1ee 100644 --- a/docs/getting-started/cloud-vs-self-hosted.md +++ b/docs/getting-started/cloud-vs-self-hosted.md @@ -4,64 +4,62 @@ 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. +Vantage runs in two ways. It is the same software; what differs is who runs it, +and how licensing and user accounts work. ## At a glance -| | Cloud | Self-hosted | -| --------------------- | --------------------------------------------------- | ------------------------------ | -| Who runs it | We do | You do | -| Where you sign in | `.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 | Self-hosted | +| --------------------- | ------------------------------------------- | ------------------------------ | +| Who runs it | We do | You do | +| Where you sign in | `.vantage.hostxtra.co.uk` | Your own hostname | +| Database and backups | Ours | Yours | +| Licence | Installed for you | You paste it in | +| Team members | Granted from Vantage HQ | Created in the instance itself | +| Free tier | Yes, one per account | Yes, one per account | +| Expired Free instance | Eventually deleted, after warning emails | 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. +You create an instance from the Vantage HQ portal and it is ready seconds later, +already licensed. People you grant access to get a real account inside that +instance, but Vantage HQ owns their password and role. See +[People and roles](../hq/people-and-roles.md). -:::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. -::: +Your instance keeps working whether or not Vantage HQ is reachable. Signing in +and managing servers never depend on it. -Cloud instances on the Free tier are reaped after their licence expires, with -warning emails first. See [Free tier](../hq/free-tier.md). +Cloud instances on the Free tier are deleted some time 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. +You run Vantage with Docker Compose on your own infrastructure. It needs no +connection to us at runtime, because a licence is a signed file your install +checks for itself. 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)). +1. **Free.** Link the install to a Vantage HQ account and claim it. See + [Claim a Free licence](./claim-free-licence.md). +2. **Paid.** Buy from Vantage HQ, then paste your install's instance ID to have + the licence issued. See + [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. +People who sign in to a self-hosted install are created in the install itself, +either with a password or through single sign-on. Vantage HQ cannot add them for +you. ## Which should you pick -Pick cloud if you want the thing running now and do not want to own a MongoDB. +Pick cloud if you want it running today and would rather not run a database. Pick self-hosted if your policy requires the control plane inside your own -network, or the servers you manage cannot reach the public internet. +network, or the servers you manage cannot reach the 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. +Moving between the two means migrating your data, and a new licence, since a +licence is tied to one instance. ## Next - [Self-hosted install](./self-hosted-install.md) -- [Accounts and signup](../hq/accounts-and-signup.md) if you are going cloud +- [Accounts and signup](../hq/accounts-and-signup.md), if you are going cloud diff --git a/docs/getting-started/first-login.md b/docs/getting-started/first-login.md index 9580a31..cd7a275 100644 --- a/docs/getting-started/first-login.md +++ b/docs/getting-started/first-login.md @@ -6,67 +6,82 @@ sidebar_label: First login A fresh install has no users and no instance. The first visit creates both. -## 1. Bootstrap +## 1. Create the first account -Open the control plane in a browser. Because no user exists, you land on -`/setup`. +Open your Vantage address in a browser. Because no user exists yet, you land on +the setup page. Fill in: -| Field | Notes | -| ------------- | ------------------------------------- | -| Instance name | Display name. Shown throughout the UI | -| Email | Becomes your sign-in identity | -| Password | Stored bcrypt-hashed | +| Field | Notes | +| ---------------- | --------------------------------------------------------- | +| Instance name | Also used to derive your instance's own subdomain | +| Owner email | Becomes your sign-in identity | +| Password | At least 8 characters | +| Confirm password | Must match | -Submitting creates the instance and its **owner** you. +**Setup Instance** creates the instance and makes you its **owner**. -:::warning Bootstrap works exactly once -The endpoint is open only while the database has no users. As soon as the first -one exists, There is no second chance to create the first owner, so record the -credentials before you continue. +:::warning Your instance gets its own address +If you installed on a name like `vantage.example.com`, an instance called Acme +signs in at `acme.vantage.example.com`, and each instance keeps its own sign-in. +Make sure DNS and your reverse proxy cover that subdomain, or use a wildcard. ::: -## 2. Copy the Instance ID +:::warning Setup runs exactly once +It is only available while the database has no users. Once yours exists, the +page closes for good, so record the email and password before you continue. +::: -Once you have finished setup you will see the successfully created page. +## 2. Copy the instance ID -This will show the Instance ID. You will need this ID when creating a license in the HQ. +The confirmation page, headed **Instance created**, shows your instance ID and +your sign-in address. You need that ID to claim a licence in Vantage HQ, and it +is the reference support works from. You can find it again later on the +**Licence** page in the sidebar. ## 3. Sign in -Click the continue to sign in button on the successful setup page. - -You will be taken to `/login`. Sign in with the email and password you just set. +Click **Go to sign in**, then sign in with the email and password you just set. ## 4. Look around -You land on the servers dashboard, which is empty. The sidebar is the whole -product: +You land on the servers page, which is empty. The sidebar is the whole product: -| Section | What it does | -| --------- | ------------------------------------------ | -| Servers | The server 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 | +| Section | What it does | +| --------------- | ------------------------------------------------ | +| Servers | Your fleet: enrol, inspect, console, update | +| Monitors | HTTP, TCP, ping and certificate checks | +| Vulnerabilities | Known security issues in installed packages | +| Workloads | Containers and services running on your servers | +| SSH Keys | Public keys and which servers they are on | +| Secrets | The encrypted vault | +| Workflows | Compose and run scripted work | +| Steps | The reusable step library | +| Audit Log | A record of everything that changed | +| Licence | Your tier, allowance and expiry | +| Settings | People, sign-in, alerts and integrations | -## 5. Add the rest of your team +**Licence** and **Settings** are shown only to owners and admins. -Go to **Settings → Access**. Add members with a role: +## 5. Install your licence -| Role | Can | -| -------- | -------------------------------------------------- | -| `owner` | Everything, including billing-adjacent settings | -| `admin` | Everything except owner-only settings | -| `member` | Day-to-day work servers, keys, workflows, monitors | +Until a licence is installed, the instance is read-only: you can look, but you +cannot add servers or anything else. Continue with +[Claim a Free licence](./claim-free-licence.md). -Settings and organisation management require `owner` or `admin`. +## 6. Add the rest of your team -If you would rather not manage passwords, configure single sign-on instead: see [Settings](../vantage/settings.md#single-sign-on). +Go to **Settings → Access** and add people with a role: -You can add more than one identity provider; each gets its own button on the login page, and no buttons appear at all until at least one provider is configured. +| Role | Can | +| -------- | ---------------------------------------------------- | +| `owner` | Everything | +| `admin` | Everything except owner-only settings | +| `member` | Day-to-day work: servers, keys, workflows, monitors | + +Changing settings, and adding or removing people, needs `owner` or `admin`. + +If you would rather not manage passwords, you can use single sign-on instead, +which is available on paid plans. See +[Settings](../vantage/settings.md#single-sign-on). diff --git a/docs/getting-started/first-server.md b/docs/getting-started/first-server.md index 0e4ff81..6796c94 100644 --- a/docs/getting-started/first-server.md +++ b/docs/getting-started/first-server.md @@ -4,64 +4,67 @@ title: Add your first server sidebar_label: Add your first server --- -Enrolling a server 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. +Enrolling a server means running one command on it. Vantage issues a short-lived +token, the install script fetches the agent and writes a config file, and the +machine registers itself. + +:::info You need a licence first +An unlicensed install is read-only, so **Add server** will be refused until a +licence is in place. If you have not done that yet, start with +[Claim a Free licence](./claim-free-licence.md). +::: ## 1. Create the enrolment -In the UI, go to **Servers → Add server** Then click the **Generate Install Command** button. -This generates a server ID and a pre-registration token +Go to **Servers → Add server**, then click **Generate install command**. Vantage +creates a server record and an enrolment token for it. -:::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 registers. +:::warning The token is single-use and lasts one hour +It is the only credential in the flow, and it is spent the moment the agent +registers. If it expires, generate a new command rather than reusing the old one. ::: ## 2. Run the one-liner ### Linux -Run the generated install script as root. - -Here is an example of the install script: +Run the generated command as root. It looks like this: ```bash curl -fsSL "https://vantage.example.com/install?server_id=&token=" | bash ``` -What the script does: +The script: -1. Detects architecture `x86_64` and `aarch64` only; anything else exits. -2. Downloads the binary and `checksums.txt`, and **verifies the SHA-256**, aborting on a mismatch. -3. Installs to `/usr/local/bin/vantage-agent`, mode `0755`. -4. Writes the config file at `/etc/vantage/config.yaml` - 1. This contains the server ID, the pre-registration token and the gRPC host. -5. Writes the systemd service file `/etc/systemd/system/vantage-agent.service` and starts the agent. +1. Checks the architecture. Only `x86_64` and `aarch64` are supported. +2. Downloads the agent and verifies its SHA-256 checksum, stopping on a mismatch. +3. Installs the agent to `/usr/local/bin/vantage-agent`. +4. Writes `/etc/vantage/config.yaml` with the server ID, the enrolment token and + the address the agent connects to. +5. Installs and starts the `vantage-agent` systemd service. ### Windows +Run this from an elevated PowerShell prompt: + ```powershell irm "https://vantage.example.com/install.ps1?server_id=&token=" | iex ``` -Run from an elevated PowerShell. +It writes the config to `%ProgramData%\vantage\config.yaml`, installs the agent +as a Windows service and starts it. -What the script does: - -1. Creates the config at `%ProgramData%\vantage\config.yaml`. - 1. This contains the server ID, the pre-registration token and the gRPC host. -2. Downloads the agent MSI from Gitea. -3. Installs the MSI and creates the Windows service. -4. Starts the agent. - -:::info Windows agents do **not** manage `authorized_keys` as this is a Linux-only function. +:::info Windows servers do not get SSH key management +Windows agents register, report inventory and run workflow steps. Managing +`authorized_keys` is a Linux-only feature. ::: ## 3. Watch it come up -The server appears immediately as `pending`. Within one poll interval, 30 seconds it becomes `active`. +The server appears as `pending` straight away, and becomes `active` within about +30 seconds. -Check the systemd logs using the following commands: +On Linux you can watch the agent itself: ```bash systemctl status vantage-agent @@ -70,28 +73,30 @@ journalctl -u vantage-agent -f ## 4. Confirm it works -Open the server's detail page. Within a minute or two you should see: +Open the server's 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. +- Status `active`, with a recent last-seen time. +- Inventory: CPU, memory, swap, partitions and kernel. Metrics refresh every 30 + seconds, and the fuller snapshot every 15 minutes. +- Any pending OS updates, which the agent checks for 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 | +| Symptom | What to check | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| Script stops at "Unsupported architecture" | The machine is not 64-bit x86 or ARM | +| "Checksum mismatch!" | An interrupted download, or a proxy rewriting the response. Run it again | +| "Could not determine latest agent version" | The machine cannot reach the release host, or `GITEA_HOST` is not set on your control plane | +| Service runs, server stays `pending` | The machine cannot reach the agent port. Test it from that machine, not from the control plane | +| Registers, then goes `offline` | A firewall or proxy allows the first connection but drops the long-lived one | +| "Server limit reached" | Your licence allowance is full. Raise it in Vantage HQ, or remove a server you no longer manage | -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. +A server is marked `offline` once it has not been seen for a while, and that +check runs every couple of minutes, so give it a moment before concluding +anything. -## Next Steps +## Next steps - [Assign an SSH key](../vantage/ssh-keys.md) - [Run a workflow](../vantage/workflows.md) -- [Claim a Free licence](./claim-free-licence.md) +- [Watch something with a monitor](../vantage/monitors.md) diff --git a/docs/getting-started/self-hosted-install.md b/docs/getting-started/self-hosted-install.md index 625da0f..b9f413f 100644 --- a/docs/getting-started/self-hosted-install.md +++ b/docs/getting-started/self-hosted-install.md @@ -4,52 +4,46 @@ title: Install Vantage (self-hosted) sidebar_label: Self-hosted install --- -This installs the control plane on a host you own. Budget about fifteen minutes. +This puts 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. +- A DNS name pointing at that host. People use it for the web UI, and your + agents use it too. +- A reverse proxy in front of Vantage that terminates TLS. It needs to handle + both the web UI and the agent port, `9090`, which speaks HTTP/2. +- Those two ports reachable: the web port from wherever your people are, and + `9090` from every machine you intend to manage. +- Outbound access from the control plane, and from each managed machine, to + `gitea.hostxtra.co.uk`, which serves the agent downloads. -The stack itself brings MongoDB, Redis and guacd with it. You do not need to -provide a database. +The stack brings MongoDB, Redis and the console daemon with it, so there is no +database to provide. -## 1. Get the compose file - -Put `deploy/docker-compose.yml` from the repository in a working directory, for -example `/opt/vantage`. +## 1. Get the Compose file ```bash -mkdir -p /opt/vantage/data && cd /opt/vantage -# copy docker-compose.yml here +mkdir -p /opt/vantage && cd /opt/vantage +curl -fsSLO https://gitea.hostxtra.co.uk/mrhid6/vantage/raw/branch/main/deploy/docker/docker-compose.yml ``` -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. +# The host:port your agents connect to. This is 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. +# 32 bytes as 64 hex characters. Generate it with the command below. KEY_ENCRYPTION_KEY= -# Optional: where workflow run logs are written inside the container. +# The host serving agent downloads. +GITEA_HOST=gitea.hostxtra.co.uk ``` Generate the encryption key: @@ -58,17 +52,27 @@ Generate the encryption key: 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. +Then make sure the `server` service passes `GITEA_HOST` through, by adding this +line to its `environment:` block in `docker-compose.yml`: + +```yaml +GITEA_HOST: ${GITEA_HOST} +``` + +Without it, the install command you hand to a new server cannot work out which +agent to download. + +:::danger Keep the encryption key safe +`KEY_ENCRYPTION_KEY` encrypts SSH private keys, vault secrets, single sign-on +client secrets and console credentials. If you lose it, all of those become +unreadable and there is no way to recover them. Back it up somewhere other than +the server it protects, and do not change it once the install is in use. ::: :::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. +The server will not start without it. There is deliberately no fallback to your +web address, because that port does not speak the protocol agents use, and the +mistake would only show up later as every agent failing to connect. ::: ## 3. Start the stack @@ -78,31 +82,37 @@ docker compose up -d docker compose ps ``` -Five services come up: `mongo`, `redis`, `guacd`, `server` and `web`. +Five services start: `mongo`, `redis`, `guacd`, `server` and `web`. -Check the server got through boot: +Check the server got through startup: ```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. +On first boot it prepares the database and loads the built-in workflow step +library. If it stops during that, it will say why, and it is meant to stop +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 calls the REST API through a Next rewrite, so you do not need to expose -`8080` publicly. +web app reaches the API internally, so there is no need to publish port `8080`. -Do **not** proxy `9090`. Agents connect to it directly over TLS. +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 +`GRPC_HOST`. The proxy must speak HTTP/2 through to Vantage. Many do not do so +by default, and the symptom is agents that register once and then stop +responding. + +For a private network where TLS is not required, you can instead set +`tls: false` in each [agent's config](../reference/agent-config.md) and let +agents reach the port directly. ## 5. First sign-in -Open your hostname in a browser. With no users in the database, you are sent to -`/setup`. +Open your hostname in a browser. With no users in the database yet, you are sent +to the setup page. Continue with [First login](./first-login.md). @@ -113,23 +123,24 @@ Continue with [First login](./first-login.md). | `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 | +| `docker compose logs server` | no fatal errors | ## Common install problems -**Server exits immediately.** Almost always a missing `GRPC_HOST`. The log line +**The server exits immediately.** Almost always a missing `GRPC_HOST`. The log 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. +**Agents register but never go active.** They reached port `9090` once but +cannot hold the connection, or your proxy is not passing HTTP/2 through. Test +from the managed machine, not from the control plane host. -**Secrets pages error.** `KEY_ENCRYPTION_KEY` is empty or not 64 hex characters. +**Secrets pages show an error.** `KEY_ENCRYPTION_KEY` is empty or is not 64 hex +characters. More in [Troubleshooting](../reference/troubleshooting.md). -## What this install does not include +## What is not included -The website, the HQ portal and this documentation site are hosted by us and are -not part of a self-hosted install. It deliberately runs none of them, and in -particular never holds the licence signing key. +The marketing site, the Vantage HQ portal and this documentation site are hosted +by us. A self-hosted install runs none of them, and it never holds the key that +signs licences. diff --git a/docs/getting-started/what-is-vantage.md b/docs/getting-started/what-is-vantage.md index f7e69a5..0004de5 100644 --- a/docs/getting-started/what-is-vantage.md +++ b/docs/getting-started/what-is-vantage.md @@ -5,59 +5,52 @@ 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. +management and grew outwards: key assignment, scripted workflows, service +monitoring, a secrets vault, a browser console and OS update management. ## The pieces ```mermaid flowchart TD W["Web UI
servers · keys · workflows · monitors
secrets · audit · console · settings"] - S["Server
REST :8080 · gRPC :9090
MongoDB · Redis · guacd"] + S["Vantage server
the control plane"] A["Agent
one per managed server
Linux and Windows"] - W -->|REST, cookie session| S - S -->|gRPC over TLS| A - A -.->|outbound only| S + W -->|you sign in here| S + S -->|sends work| A + A -.->|connects outbound| 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 server** is the control plane. It holds all your data and makes all the +decisions. -**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 agent** is a single small program running on each managed server. It asks +the control plane what it should be doing, and holds an open connection so +Vantage can send it work without waiting. -**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. +**The web UI** is what you use. Everything it can do goes through the same API +that enforces your permissions, so nothing is possible in the UI that would not +be permitted elsewhere. ## 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. +The agent always connects **outbound**. There is no listener on a managed +server, no port to open and no NAT to work around. If the machine can reach your +Vantage address, 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. +That is why you tell Vantage its own agent address (`GRPC_HOST`) when you install +it: the agent has to be given an address it can reach, and Vantage cannot guess +one for you. -## Two request patterns +## Keeping things current -| Pattern | Used for | Why | -| ----------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| Poll, every 30s | desired SSH key state | Key changes are not urgent, and polling survives a dropped connection with no reconnection logic | -| Push, over the command stream | 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. +| What | How it works | +| ------------------------- | ------------------------------------------------------------------------- | +| SSH keys on a server | The agent checks every 30 seconds and only writes when something changed | +| Workflow steps, updates | Sent to the agent straight away, so clicking Run does not wait for a check | +| Inventory | Reported every 30 seconds, with a fuller snapshot every 15 minutes | +| Pending OS updates | Checked hourly | ## Next -- [Cloud or self-hosted](./cloud-vs-self-hosted.md) which one you want -- [Self-hosted install](./self-hosted-install.md) stand it up +- [Cloud or self-hosted](./cloud-vs-self-hosted.md), to pick which one you want +- [Self-hosted install](./self-hosted-install.md), to stand it up diff --git a/docs/hq/accounts-and-signup.md b/docs/hq/accounts-and-signup.md index e7e4be2..87cc28b 100644 --- a/docs/hq/accounts-and-signup.md +++ b/docs/hq/accounts-and-signup.md @@ -20,9 +20,9 @@ role: Reading is open to any signed-in member. Every mutation except changing your own password requires `owner` or `admin`. Billing is owner-only. -The three words are the same as the control plane's roles, on purpose but they -are separate things. Your account role governs the portal; your role _inside_ an -instance governs that instance. +These are the same three words your instances use, but they are separate things. +Your account role controls what you can do in the portal. Your role inside an +instance controls what you can do there. ## Signing up @@ -38,14 +38,19 @@ Verification links are valid for **24 hours**. ## Signing in -Use the Email and password used in the signup form to login to the HQ, signing in to HQ does not sign you in to an instance, and vice versa. +Sign in at [Vantage HQ](https://vantage-hq.hostxtra.co.uk) with the email and +password from the signup form. + +HQ and your Vantage instances have separate sessions: signing in to HQ does not +sign you in to an instance, and signing in to an instance does not sign you in +to HQ. ## What comes next | You want | Go to | | ------------------------------ | ------------------------------------------------------------- | | A Vantage instance we run | [Cloud instances](./cloud-instances.md) | -| To license an install you run | [Self-hosted instances](./self-hosted-instances.md) | +| To licence an install you run | [Self-hosted instances](./self-hosted-instances.md) | | To add colleagues | [People and roles](./people-and-roles.md) | | To understand tiers and limits | [Licensing and entitlements](./licensing-and-entitlements.md) | @@ -55,4 +60,4 @@ Three destinations: **Overview**, **People**, **Billing**. - Overview lists your instances. - People shows all the account members and their roles. -- Billing show the current subscriptions and subscription management. +- Billing shows your current subscriptions and lets you manage them. diff --git a/docs/hq/billing.md b/docs/hq/billing.md index f720583..5aa35fe 100644 --- a/docs/hq/billing.md +++ b/docs/hq/billing.md @@ -12,25 +12,29 @@ customer reference and nothing sensitive. The Billing page requires the **owner-only** account role. ::: -## Buying A Plan +## Buying a plan -Buying a plan license can be found in Vantage HQ by clicking on the **Buy a Plan** button on the **Overview** page. +In Vantage HQ, click **Buy a plan** on the **Overview** page. ### Cloud -On the **Buy A Plan** page you will need to select the **Deployment** to **Cloud** then chose your **Billing** cycle (Monthly or Annually). +1. Set **Deployment** to **Cloud**. +2. Choose a **Billing** cycle, monthly or annual. +3. Choose a **Plan** and configure its features and server allowance. +4. Enter an **Instance name** and click **Continue to payment**. -Then select your desired **Plan** and configure the features. - -Finally specify the **Instance Name** and click the **Continue to payment** button. +The instance is created and licensed as soon as payment confirms. ### Self-hosted -On the **Buy A Plan** page you will need to select the **Deployment** to **Self-Hosted** then chose your **Billing** cycle (Monthly or Annually). +1. Set **Deployment** to **Self-hosted**. +2. Choose a **Billing** cycle, monthly or annual. +3. Choose a **Plan** and configure its features and server allowance. +4. Enter an **Instance name** and click **Continue to payment**. -Then select your desired **Plan** and configure the features. - -Finally specify the **Instance Name** and click the **Continue to payment** button. +Payment creates a placeholder instance with no licence yet. You then paste your +install's instance ID to have the licence issued. See +[Self-hosted instances](./self-hosted-instances.md). ## Cancelling and failed payments @@ -38,7 +42,7 @@ Cancelling, or a payment going past due, takes **no immediate licence action**. Your licence runs to its grace-padded expiry and then lapses normally. There is no mid-term cut-off. -For a cloud Free instance, lapsing eventually leads to deletion see +For a cloud Free instance, lapsing eventually leads to deletion. See [Free tier](./free-tier.md). Paid instances are not deleted. ## Renewals @@ -47,4 +51,4 @@ At renewal the subscription bills again and the licence is reissued for the new term. It is also the only moment a scheduled **reduction** takes effect. - Self-hosted customers: download and paste the reissued licence. -- Cloud customers: the license is automatically linked to the instance. +- Cloud customers: nothing to do. The licence is written to the instance for you. diff --git a/docs/hq/cloud-instances.md b/docs/hq/cloud-instances.md index 2a62712..89a69d8 100644 --- a/docs/hq/cloud-instances.md +++ b/docs/hq/cloud-instances.md @@ -9,18 +9,19 @@ A cloud instance is a Vantage control plane we run for you, reachable at ## Creating one -1. **Overview → New instance**. -2. Choose a name and a slug. -3. Create. +1. On **Overview**, choose **Create a cloud instance**. Once you already have an + instance, the same page offers **Buy a plan** instead. +2. Choose **Cloud**, then the plan you want. +3. Give the instance a name and confirm. The instance is provisioned with you as its owner, and a Free licence is issued -immediately. The owner user inside it gets your HQ password hash **copied**, not -shared see [People and roles](./people-and-roles.md). +immediately. Your Vantage HQ password gets you into it, though the two are kept +in step rather than shared. See [People and roles](./people-and-roles.md). ### Slugs -The slug becomes your hostname label, so it is lowercase, and some names are -reserved. Pick something you can say on a phone call. +Your instance's address comes from the name you choose, lowercased, with some +names reserved. Pick something short that you can say on a phone call. :::warning One Free instance per account, per deployment Creating a second Free cloud instance is refused. If you want another, it needs @@ -41,9 +42,9 @@ affect anyone signing in or any agent syncing. Each instance on Overview is one record. Closed, it is a row. Open, it shows: -- **Licence contents** tier, server allowance, features, expiry. -- **Members** who has access and with what instance role. -- **Actions** grant access, change configuration, renew. +- **Licence contents**: tier, server allowance, features and expiry. +- **Members**: who has access, and with what role. +- **Actions**: grant access, change configuration, renew. ## Members @@ -53,14 +54,15 @@ Granting access writes a real user into the instance. Covered fully in ## Changing what it can do Server allowance and per-instance features (browser console, single sign-on) are -part of the instance's **entitlement**. Changing it goes through billing see +part of the instance's **entitlement**, the configuration your licence is cut +from. Changing it goes through billing. See [Licensing and entitlements](./licensing-and-entitlements.md) and [Billing](./billing.md). ## Renaming -The display name is free to change. The slug is the hostname and is not -casually changed ask support if you need it. +The display name is free to change. The slug is part of your hostname, so ask +support if you need that changed. ## What happens if the licence lapses @@ -73,6 +75,4 @@ runs to its grace-padded expiry and then lapses. ## Deleting -Ask support. Deletion is performed by the control plane, not by HQ the control -plane is the only service that knows which collections carry the instance ID, -and duplicating that list into HQ would be a list that drifts. +Ask support. diff --git a/docs/hq/free-tier.md b/docs/hq/free-tier.md index 8d50bae..8b0963b 100644 --- a/docs/hq/free-tier.md +++ b/docs/hq/free-tier.md @@ -4,7 +4,8 @@ title: Free tier sidebar_label: Free tier --- -Free is a real tier in both deployments not a trial that turns into nothing. +Free is a permanent tier. It is available whether we host Vantage for you or you +host it yourself. ## What you get @@ -23,16 +24,15 @@ features on a paid plan. ## One per account, per deployment The limit is enforced per account **and** deployment. A Free cloud instance does -not prevent a Free self-hosted one they are separate slots. +not prevent a Free self-hosted one; they are separate slots. ## Renewing -Free licences have a term and must be renewed from the portal. +Free licences run for a year and are renewed from the portal. - The renew button appears **7 days before expiry**. -- It stays available **after** expiry, right up until the instance is reaped — - so the same button rescues a lapsed instance rather than needing a second - mechanism. +- It stays available **after** expiry, right up until a lapsed cloud instance is + deleted, so the same button rescues one. - Renewing outside that window is refused, and the message names the date it opens. @@ -55,19 +55,13 @@ There is no restore. If a cloud Free instance is approaching that date and you want to keep it, renew it, or move it to a paid plan. ::: -Deletion is carried out by the control plane rather than by HQ. HQ sends the -warnings because it knows the billing address; the control plane performs the -delete because it is the only service that knows which collections carry the -instance ID. - ## Moving off Free Change the instance's configuration to a paid tier and check out. Your data -stays where it is a tier change reissues a licence, it does not rebuild +stays where it is: a tier change reissues a licence, it does not rebuild anything. ## Relinks -Free instances get the same relink allowance as paid ones: three per term. That -cap exists to put a human in front of a fourth attempt, not to obstruct a -genuine rebuild. +Free instances get the same allowance as paid ones: three relinks per term. If +you genuinely need more, ask support. diff --git a/docs/hq/licensing-and-entitlements.md b/docs/hq/licensing-and-entitlements.md index 05dbc0a..19162d8 100644 --- a/docs/hq/licensing-and-entitlements.md +++ b/docs/hq/licensing-and-entitlements.md @@ -10,7 +10,7 @@ A **licence** is a signed statement of what one instance may do. An ## Tiers Three tiers, in both deployments. The allowances are identical across cloud and -self-hosted what differs is the term on offer, not what you get. +self-hosted; what differs is the term on offer, not what you get. | | Free | Professional | Enterprise | | --------------------- | --------- | ------------ | --------------------- | @@ -23,17 +23,18 @@ self-hosted what differs is the term on offer, not what you get. The server count is **metered**: the base allowance comes with the tier, and you buy additional servers on top. That is why Professional shows a real number -rather than "unlimited" the number you actually have is the one in your +rather than "unlimited": the number you actually have is the one in your entitlement. ## Features -Two are per-instance toggles rather than tier bundles: +Three features are enabled per instance rather than bundled into a tier: | Feature | What it enables | | --------- | -------------------------------------------------------------------- | -| `console` | The [browser console](../vantage/browser-console.md) | -| `oidc` | Per-instance [single sign-on](../vantage/settings.md#single-sign-on) | +| 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) | No tier includes them by default; you enable them on the instances that need them. @@ -62,26 +63,35 @@ happens at renewal. ## What a licence carries -Instance UUID, deployment, tier, resolved limits, features, term and expiry — -all signed. +Your instance ID, whether it is cloud or self-hosted, the tier, your limits, +which features are enabled, and when it expires. All of it is signed. Two properties follow from that: -- **A licence is bound to one instance UUID.** Moving it takes a - [relink](./self-hosted-instances.md#relinking). -- **A licence is a snapshot.** Editing a plan later never rewrites an issued - licence, the same way editing a workflow step never rewrites a past run. +- **A licence works for one instance only.** Moving it to a rebuilt install + takes a [relink](./self-hosted-instances.md#relinking). +- **A licence is a snapshot.** Changing a plan later does not rewrite a licence + already issued. Verification is local. Your instance does not call HQ to check a licence, and signing happens only in HQ. -## Expiry and grace +## Expiry, grace and degraded mode -Expiry is padded with a grace period. Past that, the instance goes into degraded -mode: it keeps running and keeps your data, but stops letting you do everything. +Expiry is padded with a few days' grace. Past that, an instance goes into +**degraded mode**, which means: -The way out is a current licence renew or purchase, then paste it (self-hosted) -or let it be written for you (cloud). +- It keeps running, and all of your data stays exactly where it is. +- You can still sign in and read everything. +- Adding or changing anything is refused. +- Deleting things still works, so you can get back under a reduced allowance. +- Applying OS updates still works, because security patching is never blocked. + +A brand-new self-hosted install behaves the same way until you install its first +licence. + +The way out is a current licence: renew or purchase, then paste it +(self-hosted) or let it be written for you (cloud). ## Server limits in practice diff --git a/docs/hq/people-and-roles.md b/docs/hq/people-and-roles.md index 8bb0ead..d071be5 100644 --- a/docs/hq/people-and-roles.md +++ b/docs/hq/people-and-roles.md @@ -26,14 +26,11 @@ Owners and admins invite; billing is owner-only. 3. They receive a link and set their own password at `/accept-invite`. :::info Why you cannot set their password -An invitation creates a person with an **empty password hash**, which cannot -authenticate at all until they set one. If the inviter chose it, that password -would be a shared credential to every instance the person is later granted -access to. +An invited person cannot sign in at all until they set their own password. If +you chose it for them, it would be a shared password to every instance they are +later given access to. -The verification endpoint knows the difference: a token belonging to a -passwordless person reports that a password is needed and is left unspent, so -the link still works when they get to it. +Their invitation link stays valid until they use it to set that password. ::: ### Removing someone @@ -43,26 +40,26 @@ happens to their instance access. ## Instance access -Granting access to a **cloud** instance creates a real user inside that -instance's control plane, with `auth_source: "hq"`. +Granting access to a **cloud** instance creates a real account inside that +instance, marked as managed by Vantage HQ. ```mermaid flowchart LR - P["HQ account member"] -->|grant| U["Control-plane user
auth_source: hq"] - U --> I["The instance authenticates
this user like any other"] + P["Person in your Vantage HQ account"] -->|you grant access| U["Account inside the instance"] + U --> I["They sign in at the instance,
like anyone else"] ``` -The instance authenticates that user exactly as it authenticates anyone else, -with **no runtime dependency on HQ**. Revoking deletes the row the control -plane has no disabled state, and a row that exists is a row that can sign in. +They then sign in at the instance itself, and that keeps working whether or not +Vantage HQ is reachable. Revoking removes the account outright, so access ends +immediately. ### Granting On an instance record, **Members → Add**, choose an account member and an instance role (`owner`, `admin`, `member`). -One person holds at most one user per instance, so granting twice is refused -rather than quietly creating a second user. +One person gets one account per instance, so granting twice is refused rather +than quietly creating a second. ### Roles inside an instance @@ -71,28 +68,24 @@ instance `owner`, or the reverse. ### Revoking -Removes the user from the instance immediately. Any live session ends with the -session, since the user row backing it is gone. +Removes their access immediately, and ends any session they have open. :::warning Self-hosted instances cannot be granted from HQ -All three member endpoints refuse when the instance is self-hosted. Manage those -users in the instance itself, at **Settings → Access**. +Vantage HQ cannot add or remove people in a self-hosted install. Manage them in +the install itself, at **Settings → Access**. ::: ## Passwords -Your HQ password is the single source of truth for every user projected from it. -Changing it in the portal rehashes it and copies the hash to every instance you -have been granted. +One Vantage HQ password covers you and every cloud instance you have been given +access to. Change it in the portal and it changes everywhere, within about 15 +minutes at worst if an instance is briefly unreachable. -Propagation is best-effort and immediate; a background pass compares and repairs -every 15 minutes, so a temporarily unreachable instance catches up on its own. - -There is no local password-change endpoint for those users in the control plane, -so there is never a second writer for the hash. +Those people cannot change that password inside an instance, so there is only +ever one place it is set. :::warning HQ-managed users are read-only in the instance -Changing the role of, or deleting, an `hq`-sourced user inside the control plane -is refused with `409`. Do it from the portal. The UI shows those rows read-only -with a link back here, but the API is the boundary; the UI is the courtesy. +Changing the role of, or removing, someone managed by Vantage HQ has to be done +from the portal. Inside the instance those rows are read-only, with a link back +here. ::: diff --git a/docs/hq/self-hosted-instances.md b/docs/hq/self-hosted-instances.md index 3838284..b3a7d98 100644 --- a/docs/hq/self-hosted-instances.md +++ b/docs/hq/self-hosted-instances.md @@ -19,54 +19,57 @@ buying in order to build it. ```mermaid flowchart LR - A["Buy in HQ"] --> B["Placeholder instance
awaiting_link, no licence"] - B --> C["Install Vantage
get its instance UUID"] - C --> D["Paste the UUID
claim-link"] - D --> E["Licence issued
bound to that UUID"] + A["Buy in Vantage HQ"] --> B["Placeholder instance
no licence yet"] + B --> C["Install Vantage
find its instance ID"] + C --> D["Paste your install's ID"] + D --> E["Licence issued
for that instance"] ``` -1. **Overview → Buy self-hosted**, choose tier, term and configuration. -2. Complete checkout. HQ creates a **placeholder** instance in state - `awaiting_link` with no licence attached. +1. On **Overview**, choose **License my own install**, or **Buy a plan** if you + already have an instance. Pick **Self-hosted**, then your tier, billing cycle + and configuration. +2. Complete checkout. Vantage HQ creates a **placeholder** instance, waiting to + be linked, with no licence yet. 3. [Install Vantage](../getting-started/self-hosted-install.md) if you have not - already, and find its instance UUID at **Settings → Licence**. -4. Back in HQ, open the placeholder and paste the UUID. -5. The licence is issued, bound to that UUID. Download it and paste it into your - install. + already, and find its instance ID on the **Licence** page. +4. Back in Vantage HQ, open the placeholder and paste that ID. +5. The licence is issued. Download it and paste it into your install. -:::info Why there is a placeholder at all -A licence binds to an instance UUID, and at the moment of payment that UUID may -not exist yet. Issuing early would mean issuing to nothing; refusing to sell -until you had installed would be the wrong order. The placeholder holds the -purchase until there is something to bind to. +:::info Why there is a placeholder +A licence is issued to one instance, and at the moment you pay, that install may +not exist yet. The placeholder holds your purchase until it does. ::: -## Linking an existing install +## Linking an install you already have -If the install already exists, **Link an instance** takes the UUID directly. A -UUID already claimed by another account is refused with a conflict. +If the install exists before the purchase, the flow is the same: buy, then open +the placeholder from **Overview**, choose **Link install** and paste its instance +ID. An ID already claimed by another account is refused. + +For a Free licence there is no placeholder step. See +[Claim a Free licence](../getting-started/claim-free-licence.md). ## Relinking -Rebuilding the host produces a new instance UUID, and the old licence no longer -matches. **Relink** moves the licence to the new UUID and reissues. +Rebuilding the host gives you a new instance ID, which your old licence does not +match. **Relink** moves the licence across and reissues it. The number of relinks per term is capped, and the portal shows how many you have -left. This is not meant to obstruct disaster recovery if you have exhausted -them for a real reason, ask support. +left. If you have used them all for a genuine reason, ask support. ## Installing the licence -Paste it at **Settings → Licence** in your install. The instance verifies the -signature and checks that the UUID matches its own. +Paste it on the **Licence** page in your install. It confirms the licence was +issued to that instance before applying it. -Pasting works even while the current licence is expired that endpoint is -exempt from the licence check, because it is the route out of degraded mode. +Pasting works even while your current licence has expired, because that is how +you get out of degraded mode. ## Keeping it current -Your install does not fetch licences. When a licence is reissued renewal, -configuration change, relink download the new one from HQ and paste it in. +Your install never downloads a licence by itself. Whenever one is reissued, on +renewal, on a configuration change or after a relink, download it from Vantage +HQ and paste it in. :::warning Nothing reminds your install The control plane knows only what its licence says. Expiry emails come from HQ, diff --git a/docs/index.md b/docs/index.md index 8486009..a5e44d3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,9 +7,9 @@ slug: / # Vantage documentation -Vantage is a self-hosted, multi-tenant infrastructure control plane. It manages -SSH keys, runs scripted workflows, watches services, stores secrets, opens -browser consoles and applies OS updates across a fleet of servers. +Vantage manages a fleet of servers from one place: SSH keys, scripted workflows, +service monitoring, a secrets vault, browser consoles and OS updates. Run it +yourself, or let us run it for you. A central server drives a lightweight agent installed on each managed machine. The agent connects **outbound only**, so managed servers need no inbound @@ -20,14 +20,16 @@ firewall holes. | If you want to | Read | | --------------------------------------- | --------------------------------------------------------------- | | Understand what the pieces are | [What is Vantage](./getting-started/what-is-vantage.md) | +| Decide who should run it | [Cloud or self-hosted](./getting-started/cloud-vs-self-hosted.md) | | Run it on your own hardware | [Self-hosted install](./getting-started/self-hosted-install.md) | +| Licence a self-hosted install | [Claim a Free licence](./getting-started/claim-free-licence.md) | | Enrol your first machine | [Add your first server](./getting-started/first-server.md) | | Manage your account, licence or billing | [Vantage HQ](./hq/accounts-and-signup.md) | | Look something up | [Reference](./reference/environment-variables.md) | ## The two products -**Vantage** is the control plane the thing you sign in to in order to manage +**Vantage** is the control plane, the thing you sign in to in order to manage servers. It runs either on your own infrastructure or as a cloud instance we run for you. diff --git a/docs/operations/agent-updates.md b/docs/operations/agent-updates.md index 6a974c6..b73dd5c 100644 --- a/docs/operations/agent-updates.md +++ b/docs/operations/agent-updates.md @@ -13,8 +13,7 @@ Each server's detail page shows the version it reported at its last sync. ## Updating from the UI -**Servers → _a server_ → Update agent** pushes `UpdateAgentCmd` with a target -version. The agent then: +Open a server and choose **Update agent**. The agent then: 1. Downloads the binary for its platform from the release. 2. Verifies the SHA-256 against `checksums.txt`. @@ -35,8 +34,8 @@ 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. +this when Vantage cannot reach the agent to push the update, but you can still +reach the machine. ## Rolling out across a fleet @@ -54,9 +53,8 @@ Do one, confirm it returns to `active`, then do the rest. ## Version compatibility -The agent 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. +An agent older than your control plane is supported. An agent newer than it is +not, so upgrade the control plane first. Agents report their version on every poll, so a fleet running mixed versions is visible in the server list rather than something you have to go diff --git a/docs/operations/backups.md b/docs/operations/backups.md index f49fee0..481966a 100644 --- a/docs/operations/backups.md +++ b/docs/operations/backups.md @@ -11,9 +11,8 @@ either one restores to something unusable. | Store | Contents | Back up | | -------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -| MongoDB | Everything durable servers, keys, assignments, workflows, runs, monitors, incidents, secrets, settings, audit | **Yes** | +| MongoDB | Everything durable: servers, keys, assignments, workflows, runs and their logs, 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 @@ -50,7 +49,7 @@ nothing writes during the restore. cp /opt/vantage/.env /secure-location/vantage.env ``` -Treat it as a credential in its own right it holds the encryption key. +Treat it as a credential in its own right, since it holds the encryption key. ## What a restore gives you @@ -62,7 +61,7 @@ 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. + 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. @@ -74,8 +73,8 @@ What it does **not** do is reconcile the world. After a restore: | 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. +Rehearse a restore now and again. It is the step most often skipped, and the one +that finds the problems. ## Cloud instances diff --git a/docs/operations/upgrading.md b/docs/operations/upgrading.md index 9b61977..0b8d27f 100644 --- a/docs/operations/upgrading.md +++ b/docs/operations/upgrading.md @@ -5,7 +5,7 @@ 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). +upgraded separately. See [Agent updates](./agent-updates.md). :::info Cloud instances upgrade themselves This page is for self-hosted installs. If your instance is hosted by us, there @@ -26,11 +26,11 @@ 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. +1. The database is brought up to date. Each change runs once. +2. The built-in workflow steps are reinstalled, which is why those steps cannot + be edited. + +If Vantage cannot complete either safely, it stops rather than run half-prepared. Watch it: @@ -46,6 +46,16 @@ docker compose logs -f server - **Check your `.env`** still supplies everything required. A newly required variable stops the boot rather than defaulting to something unsafe. +## Single sign-on after an upgrade + +Each identity provider now has its own callback URL. If you configured single +sign-on on an older version it was carried over, but its callback URL changed, +and sign-in through it fails until you copy the new one from its card in +**Settings** and register it with your identity provider. The card shows a +reminder until you dismiss it. + +Password sign-in is unaffected, so you can always sign in locally to fix this. + ## Downgrading There is no automatic downgrade. Migrations do not roll back, so returning to an @@ -57,7 +67,7 @@ is the reason the backup is not optional. 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. +- Agents reconnect on their own. - 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.** @@ -67,5 +77,4 @@ container, which is a short interruption: - 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. +- Open a secret group, to confirm `KEY_ENCRYPTION_KEY` came through. diff --git a/docs/reference/agent-config.md b/docs/reference/agent-config.md index 26041e8..1dbf3b3 100644 --- a/docs/reference/agent-config.md +++ b/docs/reference/agent-config.md @@ -20,15 +20,15 @@ Directory `0700`, file `0600`. The install script sets both. ```yaml server_url: "vantage.yourdomain.com:9090" server_id: "" -pre_reg_token: "" # removed after the first successful Register() -agent_token: "" # written by the agent after Register() +pre_reg_token: "" # cleared once the agent has registered +agent_token: "" # written by the agent when it registers poll_interval: 30s tls: true ``` | Field | Meaning | | --------------- | --------------------------------------------------------------------- | -| `server_url` | `host:port` of the gRPC endpoint. Comes from the server's `GRPC_HOST` | +| `server_url` | The `host:port` the agent connects to. Comes from your `GRPC_HOST` | | `server_id` | The identity issued when the enrolment was created | | `pre_reg_token` | Single-use, one hour. Cleared once registration succeeds | | `agent_token` | The permanent credential, written by the agent itself | @@ -36,8 +36,8 @@ tls: true | `tls` | Whether to use TLS. Leave `true` | :::danger This file is the credential -`agent_token` is plaintext here and nowhere else the control plane holds only -its SHA-256. Anyone who can read this file can act as this agent. +`agent_token` exists in full only in this file. Anyone who can read it can act +as this agent. ::: ## Service management @@ -77,5 +77,5 @@ rm -rf /etc/vantage systemctl daemon-reload ``` -Keys already written to `authorized_keys` remain on disk the agent is no -longer running to remove them. Revoke first if that matters. +Keys already written to `authorized_keys` remain on disk, because the agent is +no longer running to remove them. Revoke first if that matters. diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md index be0e557..fee38df 100644 --- a/docs/reference/environment-variables.md +++ b/docs/reference/environment-variables.md @@ -13,16 +13,18 @@ it is absent. | -------------------------- | --------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `GRPC_HOST` | **yes** | | The `host:port` agents dial. Boot fails without it. There is deliberately no fallback to the web host: that would hand every agent a port that does not speak gRPC | | `MONGO_URI` | no | `mongodb://localhost:27017` | The database name is taken from the URI path, falling back to `vantage`. There is no separate `MONGO_DB` | -| `REDIS_ADDR` | no | `localhost:6379` | Sessions, and the bus that routes agent commands between server replicas. Every replica must point at the **same** Redis | +| `REDIS_ADDR` | no | `localhost:6379` | Where sessions are held. If you run more than one copy of Vantage, they must all point at the same Redis | | `REDIS_USERNAME` | no | | Redis 6+ ACL user. Leave empty against a legacy `requirepass` instance, which authenticates with the password alone | | `REDIS_PASSWORD` | no | | Leave empty for an unauthenticated Redis. Both of these exist so an install can use a managed Redis rather than the bundled one | | `KEY_ENCRYPTION_KEY` | yes in practice | | 64 hex characters (32 bytes) for AES-256-GCM. Required for private keys, vault secrets, OIDC client secrets and console credentials | -| `GITEA_HOST` | yes | `gitea.example.com` | Used to build the install scripts and agent download URLs. The default is a placeholder that will not resolve | +| `GITEA_HOST` | yes in practice | `gitea.example.com` | Host serving agent releases; used to build the install scripts and download URLs. The default is a placeholder that will not resolve, so set it to `gitea.hostxtra.co.uk` | | `GUACD_ADDR` | no | `guacd:4822` | The [browser console](../vantage/browser-console.md) daemon | | `PROXY_ADVERTISE_HOST` | no | `server` | The hostname **guacd** uses to reach the control plane's console relay. Wrong here and every console session fails at connect with guacd unable to resolve the relay | | `PROXY_LISTEN_HOST` | no | `0.0.0.0` | Interface the ephemeral relay listeners bind. Narrow it only if guacd shares a known interface | -| `POD_IP` | no | | Kubernetes only, set by the Helm chart from the downward API. Overrides `PROXY_ADVERTISE_HOST`, because a console relay belongs to one replica and a Service address names all of them | -| `APP_ROOT_LABEL` | no | `vantage` | The app root label for the host and session organisation guard | +| `APP_ROOT_LABEL` | no | `vantage` | The label Vantage expects in its own hostname, used to match a browser session to the right instance | +| `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 | :::danger `KEY_ENCRYPTION_KEY` has no recovery path It encrypts SSH private keys, vault secrets, OIDC client secrets and console @@ -38,8 +40,8 @@ protecting anything. ### Not configurable The HTTP port (`8080`) and the gRPC port (`9090`) are fixed in the server. The -`HTTP_PORT` and `GRPC_PORT` entries in the shipped Compose file are inert — -remap with Docker's port publishing instead. +`HTTP_PORT` and `GRPC_PORT` entries in the shipped Compose file have no effect. +Remap the ports with Docker instead. ## Agent diff --git a/docs/reference/ports-and-networking.md b/docs/reference/ports-and-networking.md index e747fe2..e1d9e49 100644 --- a/docs/reference/ports-and-networking.md +++ b/docs/reference/ports-and-networking.md @@ -9,9 +9,9 @@ sidebar_label: Ports and networking | Port | Service | Who connects | Expose publicly | | ------- | ----------- | -------------------------------- | --------------- | | `3000` | web | Browsers, via your reverse proxy | Yes, behind TLS | -| `8080` | server REST | The web app | No | +| `8080` | server API | The web app | No, firewall it | | `9090` | server gRPC | Agents | **Yes** | -| `4822` | guacd | The server | No | +| `4822` | guacd | The server | No, firewall it | | `27017` | MongoDB | The server | No | | `6379` | Redis | The server | No | @@ -35,11 +35,10 @@ NAT is not an obstacle. The only requirement is that the machine can reach `GRPC_HOST`. **The console rides the agent's connection too.** guacd never dials the target -directly; the server pushes a command down the agent's existing outbound gRPC -stream on `9090`, and the agent relays the protocol traffic from its own -loopback. No route from the control plane to the target's address is needed, -and no new inbound port opens on the target — the same connection that carries -key sync carries console traffic. This is what makes the console work for a +directly. Vantage sends the request down the connection the agent already holds +on port `9090`, and the agent connects to the service locally on that machine. No route from the control plane to the target's address is needed, +and no new inbound port opens on the target. The same connection that keeps keys +in sync carries console traffic, which is what makes the console work for a machine behind NAT on a private subnet, as long as its agent is online. ## What to open @@ -55,8 +54,8 @@ machine behind NAT on a private subnet, as long as its agent is online. - Anything a server-run [monitor](../vantage/monitors.md) checks. - SMTP, if you use an SMTP notification channel. -No route to the machines you intend to console is needed — that traffic rides -the agent's existing outbound `9090` connection instead. +No route to the machines you intend to console is needed. That traffic uses the +connection the agent already holds. ### Outbound from a managed machine @@ -68,14 +67,17 @@ the agent's existing outbound `9090` connection instead. Terminate TLS for the web UI at your reverse proxy. -gRPC on `9090` is reached directly by agents with `tls: true`, so that port needs -a valid certificate for the name in `GRPC_HOST`. If you proxy it, the proxy must -speak HTTP/2 end to end many do not by default, and the symptom is agents that -register and then fail to hold the command stream. +Vantage does not terminate TLS itself, so port `9090` needs the same treatment: +put it behind your proxy with a certificate valid for the name in `GRPC_HOST`. +The proxy has to pass HTTP/2 through to Vantage. Many do not do that by default, +and the symptom is agents that register once and then stop responding. + +On a private network you can skip TLS instead, by setting `tls: false` in each +[agent's config](./agent-config.md). ## Reverse proxy notes -- Point the proxy at `web:3000`. The web app reaches the REST API internally, so +- Point the proxy at `web:3000`. The web app reaches the API internally, so `8080` does not need publishing. - The console uses a **WebSocket** at `/api/console/tunnel`. A proxy that does not forward upgrade headers breaks the console and nothing else. @@ -85,8 +87,8 @@ register and then fail to hold the command stream. ## Air-gapped and restricted networks The control plane needs outbound access to fetch agent releases. Managed -machines need it too, unless you distribute the agent binary yourself and write -the config by hand the install script's only job is to do those two things. +machines need it too, unless you distribute the agent yourself and write its +config by hand, which is all the install script does. Licence verification is entirely local, so a licensed install works with no outbound access to HQ at all. diff --git a/docs/reference/rest-api.md b/docs/reference/rest-api.md index 107567c..843470f 100644 --- a/docs/reference/rest-api.md +++ b/docs/reference/rest-api.md @@ -1,154 +1,59 @@ --- id: rest-api -title: REST API -sidebar_label: REST API +title: Automating Vantage +sidebar_label: Automating Vantage --- -The control plane's HTTP API, on port `8080`. The web UI is a client of it and -has no privileges it does not. +Everything the web UI does, it does through Vantage's own API, so anything you +can do on screen you can also do from a script. -## Authentication +The routes mirror the product: `/api/servers`, `/api/keys`, `/api/workflows`, +`/api/monitors`, `/api/secrets`, `/api/audit`, and so on. -Most endpoints take a session: an opaque 32-byte token in the `km_session` -cookie, with the body in Redis for 24 hours. +## Where it is -One endpoint takes a bearer token instead the External Secrets Operator read -path. +On a self-hosted install the API is served on port `8080`, behind the same +reverse proxy as the web UI, under `/api` and `/auth`. On a cloud instance it is +your instance hostname. -## Unauthenticated +## Signing in -``` -GET /install /install.ps1 # dynamic agent install scripts -GET /update /update.ps1 -GET /auth/bootstrap-status -POST /auth/bootstrap /auth/login /auth/logout -GET /auth/me -GET /auth/providers # {local_enabled, providers:[{id,name,preset}]} — no issuer, client ID or secret -GET /auth/oidc/:providerId/start · /auth/oidc/:providerId/callback -GET /api/secrets/:group/values # bearer token (ESO) +Most calls use a session, exactly as the browser does: + +```bash +curl -c cookies.txt -X POST https://vantage.example.com/auth/login \ + -H 'Content-Type: application/json' \ + -d '{"email":"you@example.com","password":"..."}' + +curl -b cookies.txt https://vantage.example.com/api/servers ``` -`/install` and `/install.ps1` take `server_id` and `token` as query parameters -and return a shell script with the newest agent version substituted in. +Sessions last 24 hours. Your role applies exactly as it does in the UI: a +`member` calling an owner-only route is refused. -`POST /auth/bootstrap` works only while the database has no users. +## The one exception -## Session-authenticated, under `/api` +Kubernetes reads secret groups with a token instead of a session, so that it +does not need an account. See +[Secrets](../vantage/secrets.md#kubernetes-external-secrets-operator). -### Servers +## Things worth knowing -``` -GET,POST /servers -GET,POST /servers/new -GET,DELETE /servers/:id -POST /servers/:id/generate-key -POST /servers/:id/update-agent -POST /servers/:id/apply-updates -``` +- **Reads always work. Changes need a valid licence.** Without one, the instance + is read-only and any call that changes something is refused. Deleting things, + applying OS updates and installing a licence are always allowed, so you can + always get back under your allowance or out of read-only mode. +- **Some features are licensed.** The browser console, single sign-on and + vulnerability scanning are refused if your licence does not include them. +- **Some things cannot be changed here.** A cloud instance refuses a pasted + licence, and people managed by Vantage HQ cannot be re-roled or deleted inside + the instance. +- **Errors are JSON**, with an `error` field naming the reason. +- **Everything that changes something is audited**, whether it came from the UI + or from a script. See [Audit log](../vantage/audit-log.md). -### Keys +## Vantage HQ -``` -GET,POST /keys -GET,DELETE /keys/:id -GET /keys/:id/private-key -POST /keys/:id/assign -DELETE /keys/:id/assign/:serverId -``` - -### Workflows and steps - -``` -GET,POST /steps -PUT,DELETE /steps/:id -GET /steps/:id/export -POST /steps/import · /steps/seed-defaults · /steps/parse -GET /steps/usage -GET,POST /workflows -GET,PUT,DELETE /workflows/:id -POST /workflows/:id/run -GET /workflows/:id/runs -GET /runs/:runId -POST /runs/:runId/cancel -GET /runs/:runId/servers/:serverId/logs -GET /runs/:runId/servers/:serverId/logs/stream -``` - -`PUT` and `DELETE` on a step whose source is `default` answer `409`. See -[Workflows](../vantage/workflows.md#default-steps). - -### Monitors and channels - -``` -GET,POST /monitors -GET,PUT,DELETE /monitors/:id -GET /monitors/:id/incidents · /monitors/:id/uptime -GET,POST /channels -PUT,DELETE /channels/:id -POST /channels/:id/test -``` - -### Secrets - -``` -GET,POST /secrets -GET,PUT,DELETE /secrets/:group -POST /secrets/:group/reveal -DELETE /secrets/:group/:key -``` - -### Console - -``` -POST /console/connect -GET /console/tunnel # websocket -``` - -### Other - -``` -GET /audit -GET /agent/latest-version -GET,PUT /settings (owner|admin) -POST /settings/secrets-token (owner|admin) -GET /license -POST /license (self-hosted only) -GET,POST /org/users -PUT /org/users/:id/role -DELETE /org/users/:id -GET,POST /auth/providers (owner|admin) -PUT,DELETE /auth/providers/:id (owner|admin) -POST /auth/providers/:id/test · /auth/providers/:id/ack-notice (owner|admin) -GET /auth/presets (owner|admin) -``` - -## Notable refusals - -| Endpoint | Condition | Status | -| -------------------------------------------------- | ------------------------------ | ------------------- | -| `POST /license` | deployment is `cloud` | `409 cloud_managed` | -| `PUT,DELETE /steps/:id` | the step's source is `default` | `409` | -| `PUT /org/users/:id/role`, `DELETE /org/users/:id` | the user's auth source is `hq` | `409` | - -`POST /license` is exempt from the licence check, so pasting a valid licence -works while the current one is expired that is the way out of degraded mode. - -## Multi-tenancy - -Every request is scoped to the instance resolved from the session. On a -multi-tenant deployment, a request arriving at `.vantage.` also has -its host checked against the session's instance, and a mismatch is rejected. - -## Errors - -Errors are JSON with an `error` field. Customer-facing endpoints in the HQ API -answer `404` rather than `403` for another account's resource, because a `403` -confirms the resource exists; the control plane's own API is single-tenant per -session and does not need that distinction. - -## Admin API - -Vantage HQ is a separate hosted service with its own API and its own session. -Its behaviour is described in the [Vantage HQ](../hq/accounts-and-signup.md) -section rather than here; the two services share no session and no -authentication. +The portal is a separate service with its own sign-in, described in the +[Vantage HQ](../hq/accounts-and-signup.md) section. A Vantage session does not +work there, and an HQ session does not work in your instance. diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index c7a012b..548c50a 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -8,13 +8,12 @@ Symptoms, in the order people hit them. ## The server will not start -**Exits immediately on boot.** Almost always a missing `GRPC_HOST` the server +**Exits immediately on boot.** Almost always a missing `GRPC_HOST`. The server refuses to start rather than guess a value that would break every agent later. -**Fails during index creation.** The auth and settings index builders are fatal -on failure by design: those unique indexes are what enforce tenant isolation, -so starting without them is worse than not starting. Check the MongoDB user's -permissions and whether a conflicting index already exists. +**Fails while preparing the database.** Vantage stops rather than run without +the safeguards it sets up at startup. Check the MongoDB user's permissions and +whether an old, conflicting index is already there. **Starts, but every secret operation errors.** `KEY_ENCRYPTION_KEY` is missing or is not 64 hex characters. @@ -22,15 +21,15 @@ or is not 64 hex characters. ## Nobody can sign in **`/setup` appears when users already exist.** The server is pointed at a -different database than you think. Check the database name in `MONGO_URI` — -it comes from the URI path, not a separate variable. +different database than you think. Check the database name in `MONGO_URI`, +which is taken from the end of the URI. **Sessions do not stick.** Redis is unreachable, or the cookie is being dropped because the site is served over plain HTTP. -**"Wrong organisation" style rejections.** The host and session guard is -comparing the request host's label against the session's organisation. Check -`APP_ROOT_LABEL`. +**"Wrong organisation" style rejections.** Vantage compares the address you +browsed to against the instance your session belongs to. On a custom domain, +check `APP_ROOT_LABEL`. **OIDC redirects and then fails.** The callback URL registered with the provider must match exactly. Keep one local owner account so a broken provider is not a @@ -44,12 +43,12 @@ Work through it in this order: 2. What does it say? `journalctl -u vantage-agent -f`. 3. Can that machine reach the endpoint? Test `GRPC_HOST` **from the machine**, not from the control plane host. -4. Was the token already used or expired? It is single-use and lives one hour — - create a fresh enrolment rather than reusing the old command. +4. Was the token already used, or older than an hour? Generate a fresh install + command rather than reusing the old one. | Symptom | Cause | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| Registers, then goes `offline` within minutes | Something permits the short `Register` call but drops the long-lived stream. Usually a proxy or idle-timeout middlebox | +| Registers, then goes `offline` within minutes | The short registration call gets through but the long-lived connection is dropped, usually by a proxy or an idle timeout | | Stays `pending` forever | Registration never happened. Token spent, or the endpoint unreachable | | Flaps between `active` and `offline` | Intermittent path, or a poll interval longer than the offline threshold | @@ -67,15 +66,15 @@ instantaneous. ## A workflow run fails or hangs -- **Hangs at dispatch.** The target's command stream is not connected the +- **Hangs at dispatch.** The target's command stream is not connected; the server may be `offline`. - **Fails immediately with an interpreter error.** A bash step on a Windows target, or PowerShell on Linux. - **A value does not reach the next step.** Values pass through the file at - `$WORKFLOW_ENV`, one `KEY=value` per line. Declaring an output does not export - it. -- **A secret is empty.** The group is not in the step's `secret_refs`, or the - key name differs from the environment variable you are reading. + `$WORKFLOW_ENV`, one `KEY=value` per line. Listing an output does not pass it + on by itself. +- **A secret is empty.** The group is not attached to that step, or the key name + differs from the variable you are reading. - **Logs stop mid-run.** A reverse proxy read timeout cut the stream. The run itself continues; reload the page. @@ -85,8 +84,8 @@ instantaneous. | ----------------------------- | --------------------------------------------------------------------------------------------------------------- | | Connects, then closes at once | guacd unreachable. Check `GUACD_ADDR` and that the container is running | | SSH rejects the key | The stored key has no private half, or is not on the target | -| RDP fails on retry | Credentials are single-use and consumed at tunnel open enter them again | -| Hangs, then disconnects | The agent never claimed the relay, nothing is listening on the protocol port on the target's own loopback address, or guacd never dialled in time. Check the audit log for `console.proxy_failed` — its reason (`agent_timeout`, `dial_refused`, `guacd_timeout`, `rejected`) names which | +| RDP fails on retry | Credentials are single-use and consumed at tunnel open. Enter them again | +| Hangs, then disconnects | The agent could not reach the service on that machine, or setting up the session timed out. The audit log records which | | Fails only in production | The reverse proxy is not forwarding WebSocket upgrade headers | ## Monitors report down when the service is up @@ -98,7 +97,7 @@ instantaneous. ## Notifications are not arriving -Use the channel **Test** button it goes through the real delivery path, so a +Use the channel **Test** button. It goes through the real delivery path, so a test that arrives proves credentials, network path and destination. If the test fails: a webhook returning 300 or above counts as a failure, SMTP @@ -109,9 +108,9 @@ needs `host`, `port`, `from` and `to`, and Telegram needs both `token` and | Symptom | Cause | | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `409 cloud_managed` when pasting | It is a cloud instance. Licences are written by HQ; there is nothing to paste | -| Licence rejected as not matching | It is bound to a different instance UUID. Relink in HQ | -| Instance degraded despite a valid-looking licence | It has expired past its grace period. Pasting still works that endpoint stays available specifically so it can | +| "Managed by Vantage HQ" when pasting | It is a cloud instance, which is licensed for you. There is nothing to paste | +| Licence rejected as not matching | It was issued to a different instance ID. Relink it in Vantage HQ | +| Instance degraded despite a valid-looking licence | It expired more than a few days ago. Pasting a new one still works, which is how you recover | | Cannot enrol another server | The server allowance is reached. Raise it in HQ or remove one | ## HQ portal problems @@ -129,5 +128,5 @@ docker compose logs --tail=200 server journalctl -u vantage-agent --no-pager -n 200 # on the affected machine ``` -Include your instance UUID from **Settings → Licence** it is the reference +Include your instance ID from the **Licence** page, which is the reference support works from. diff --git a/docs/vantage/audit-log.md b/docs/vantage/audit-log.md index 46d9a74..97b3aa5 100644 --- a/docs/vantage/audit-log.md +++ b/docs/vantage/audit-log.md @@ -4,7 +4,8 @@ title: Audit log sidebar_label: Audit log --- -Every mutating API path writes an audit event. The log is at **Audit**. +Anything that changes something is recorded, whether it was done in the UI or by +a script. The log is at **Audit Log**. ## What an event carries @@ -23,21 +24,24 @@ keys and assignments, workflow and step changes, runs triggered, monitors and channels, secret groups and reveals, console sessions opened, settings and member changes, licence installs. -Reads are not recorded, with one deliberate exception: **revealing a secret** -writes an event, because reading that particular thing is an act rather than a -lookup. +Simply looking at something is not recorded, with one exception: **revealing a +secret** is. ## What is not recorded -- Sign-ins and sign-out. +- Sign-ins and sign-outs. - Anything inside a console session. - Step output. That lives in the run log, kept under the workflow retention setting rather than with the audit log. ## Retention -Audit events are not swept by the workflow log retention setting that setting -governs run logs only. Audit history stays until the instance does. +How long audit events are kept comes from your licence: 30 days on Free, a year +on Professional, and unlimited on Enterprise. See +[Licensing and entitlements](../hq/licensing-and-entitlements.md). + +This is separate from the workflow log retention setting, which covers run logs +only. :::warning It is a log, not a control The audit log tells you what happened. It does not restrict what can happen, and @@ -45,18 +49,11 @@ an admin can do anything an admin can do. Use roles for restriction and the log for accountability. ::: -## Getting events out +## Searching and exporting -`GET /api/audit` returns one page of events as JSON: +The page searches by actor, detail and event type, and filters by category, such +as `workflow`, `key` or `server`. The count shown is the number of matching +events, not the number on screen. -```json -{ "events": [ ... ], "total": 3214 } -``` - -It accepts `limit` (default 50, maximum 200), `skip`, `q` to search actor, -details and event type, and `category` to match the part of an event type -before the dot — `workflow`, `key`, `server`. `total` counts everything -matching the filter, not the page, so a short page is not the end of the log. - -There is no streaming or push export; if you need events in a SIEM, poll that -endpoint, walking `skip` until you have `total`. +The same events are available from the API if you want them in a log system of +your own. See [Automating Vantage](../reference/rest-api.md). diff --git a/docs/vantage/browser-console.md b/docs/vantage/browser-console.md index 796d59c..e132238 100644 --- a/docs/vantage/browser-console.md +++ b/docs/vantage/browser-console.md @@ -4,70 +4,59 @@ title: Browser console sidebar_label: Browser console --- -An SSH, RDP or VNC session in a browser tab, with no client software and no -inbound port on the target beyond the one the protocol already uses. +An SSH, RDP or VNC session in a browser tab, with no client software to install +and no new port to open on the target machine. -Protocol handling is Apache Guacamole's the control plane proxies a WebSocket -to a **guacd** daemon and manages credentials around it. +:::info Requires the console feature on your licence +The console is a per-instance feature you enable on a paid plan. Without it, the +Console button is unavailable. See +[Licensing and entitlements](../hq/licensing-and-entitlements.md). +::: -## Requirements +## What you need -- `guacd` running and reachable from the server. The bundled Compose stack - includes it; `GUACD_ADDR` defaults to `guacd:4822`. -- `KEY_ENCRYPTION_KEY` set, since every credential involved is stored encrypted. -- The target's **agent must be online**. Console traffic is relayed over the - agent's existing outbound connection, so the control plane never needs a route - to the server's address — but it does need the agent. -- No inbound port on the target, beyond what the protocol already listens on - locally. A service bound only to `127.0.0.1` works, because the agent dials - loopback on the target itself. +- The target server's **agent must be online**. Console traffic travels over the + connection the agent already holds, so an offline agent means no session. +- `KEY_ENCRYPTION_KEY` set on a self-hosted install, since every credential + involved is stored encrypted. +- The service you are connecting to listening on the machine itself. It does not + have to be reachable from anywhere else, because the agent connects to it + locally. ## Opening a session -From a server's page, choose **Console**. Then: - -1. The UI calls `POST /api/console/connect`, which mints a **one-time** session - token. If the target's agent is not connected, this fails immediately with - `409 agent_offline` rather than hanging. -2. The browser opens a WebSocket to `GET /api/console/tunnel` with that token. -3. The server marks the token consumed atomically, so a second use cannot - race and proxies the connection to guacd. +From a server's page, choose **Console**, pick the protocol and connect. Vantage +issues a one-time ticket for that session, and the connection is refused rather +than left hanging if the agent is not online. ## Credentials ### SSH -Authenticates with a private key stored in the [key library](./ssh-keys.md). The -key must have its private half uploaded; a public-only key cannot open a -session. +Uses a private key from your [key library](./ssh-keys.md). The key must have had +its private half uploaded; a public key alone cannot open a session. ### RDP and VNC -You supply credentials when connecting. They are encrypted, **single-use**, and -consumed when the tunnel opens. They are not retained for the next session. +You type the credentials when you connect. They are encrypted, used once and +discarded, so the next session asks again. -:::info Why single-use -A stored console credential is a standing grant to that machine for anyone who -can reach the endpoint. Consuming it at tunnel-open means a leaked session token -is worth one connection at most, and only until it is used. -::: +## During and after a session -## Session behaviour +Closing the tab ends the session. There is no reconnect: opening it again starts +a fresh session. -Closing the tab ends the session. There is no reconnect and no session -persistence reopening mints a new token and a new connection. - -## Auditing - -Opening a console is an audited action, with actor, server and time. What -happens _inside_ the session is not recorded: there is no session capture or -keystroke log. If you need that, it has to come from the target machine. +Opening a console is recorded in the [audit log](./audit-log.md), with who did +it, which server and when. What happens inside the session is not recorded. +There is no session replay or keystroke capture, so if you need that, it has to +come from the target machine itself. ## When it does not work -| Symptom | Cause | -| -------------------------------- | ------------------------------------------------------------------------------- | -| Connects then closes immediately | guacd unreachable check `GUACD_ADDR` and that the container is up | -| SSH refuses the key | The stored key has no private half, or is not in the target's `authorized_keys` | -| RDP fails on a fresh credential | Credentials are consumed on open; a retry needs them entered again | -| Hangs, then disconnects | The agent never claimed the relay, nothing is listening on the protocol port on the target's own loopback address, or guacd never dialled in time. Check the audit log for `console.proxy_failed` — its reason (`agent_timeout`, `dial_refused`, `guacd_timeout`, `rejected`) names which | +| Symptom | What to check | +| -------------------------------- | ------------------------------------------------------------------------------------------ | +| Connects, then closes at once | The console daemon is unreachable. On a self-hosted install, check that `guacd` is running | +| SSH refuses the key | The stored key has no private half, or is not assigned to that server | +| RDP fails when you retry | Credentials are used once. Enter them again | +| Hangs, then disconnects | The agent could not reach the service on the machine, or the session timed out setting up. The audit log records the reason | +| Works locally, fails in production | Your reverse proxy is not forwarding WebSocket connections | diff --git a/docs/vantage/monitors.md b/docs/vantage/monitors.md index 4b784ec..d49f743 100644 --- a/docs/vantage/monitors.md +++ b/docs/vantage/monitors.md @@ -28,22 +28,22 @@ Every monitor has a **runner**: | `server` | The control plane's scheduler performs the check | | a server ID | That server's agent performs it locally and reports the result | -Use `server` for anything reachable from the control plane public endpoints, -your own front door. Use an agent for anything only reachable from inside the +Use `server` for anything Vantage itself can reach, such as your public website +or API. Use an agent for anything only reachable from inside the target network: a database on a private subnet, a service bound to localhost, a device on a management VLAN. -:::tip Agent-run monitors measure what your users can't -A check run from the control plane tells you the service is reachable from -there. A check run on the machine tells you the process is alive. Those are -different questions, and outages usually live in the gap. +:::tip The two answer different questions +A check from Vantage tells you the service is reachable over the network. A +check on the machine tells you the process is running. Watch both where it +matters. ::: ## Interval, retries and state -- **Interval** how often to check. -- **Retries** how many consecutive failures are tolerated before the state - flips. +- **Interval** is how often to check. +- **Retries** is how many failures in a row are tolerated before the state + changes. A monitor sits in `pending` until its first result. Failures accumulate; once they exceed `retries`, the monitor goes `down`, an **incident** opens and the @@ -58,21 +58,20 @@ Attach one or more [notification channels](./notification-channels.md) to a monitor. Channels are shared, so one Slack destination can serve every monitor you have. -Notification state is tracked per monitor, so a service that is down for six -hours does not send a message per interval. +You get a message when a monitor goes down and another when it recovers, not one +per check while it stays down. ## Uptime and incidents The monitor detail page shows: -- **Uptime**, from hourly rollup records checks performed, how many were up, - and mean latency per hour. Rollups are what make the graph cheap to draw over - long windows. +- **Uptime**, summarised per hour: how many checks ran, how many passed and the + average response time. - **Incidents**, each with a start, a resolution and the cause recorded at the moment it opened. ## Disabling versus deleting Disabling stops the checks and keeps the history. Deleting removes the monitor. -Prefer disabling for anything seasonal the uptime record is usually the part -you wanted. +Disable anything seasonal rather than deleting it, since the uptime record is +usually the part worth keeping. diff --git a/docs/vantage/notification-channels.md b/docs/vantage/notification-channels.md index ffa3654..91535e2 100644 --- a/docs/vantage/notification-channels.md +++ b/docs/vantage/notification-channels.md @@ -4,8 +4,8 @@ title: Notification channels sidebar_label: Notification channels --- -A channel is a destination for alerts. [Monitors](./monitors.md) reference -channels by ID, so one destination serves as many monitors as you like. +A channel is a destination for alerts. One channel serves as many +[monitors](./monitors.md) as you like. Manage them at **Settings → Notifications**. @@ -64,9 +64,7 @@ Posts the alert as message content. Port `465` uses implicit TLS; anything else uses STARTTLS. -Alert emails are rendered by the same email system that sends licence and -account mail, so a monitor alert and an account email look like the same -product. +Alert emails look like the rest of the mail Vantage sends you. ## The message @@ -83,9 +81,9 @@ something that needs to branch on status. ## Testing Every channel has a **Test** button. It dispatches a fabricated down event for a -monitor called "Test monitor", through the real delivery path so a test that -arrives proves the credentials, the network path and the destination, not just -the configuration form. +monitor called "Test monitor" and sends it the same way a real alert goes out, so +a test that arrives proves the credentials, the network path and the destination +as well as the form. :::tip Test after every change Channel settings are only exercised when something breaks, which is the worst diff --git a/docs/vantage/secrets.md b/docs/vantage/secrets.md index de1ccb3..eb20081 100644 --- a/docs/vantage/secrets.md +++ b/docs/vantage/secrets.md @@ -10,54 +10,53 @@ External Secrets Operator. ## Groups and values -A **group** is a named bundle `prod-db`, `registry`, `acme-api`. Inside it are -key/value pairs. +A **group** is a named bundle, such as `prod-db`, `registry` or `acme-api`. +Inside it are key/value pairs. -Group by consumer, not by type. A group is the unit a workflow step references -and the unit ESO reads, so a group that matches one consumer is one reference; -a group holding everything is over-sharing to every step that needs any of it. +Group by who uses them rather than by what they are. A workflow step references +a whole group, so a group that matches one job stays tidy, while a group holding +everything hands all of it to every step that needs any of it. ## Managing them **Secrets → New group**, then add keys. -Values are write-then-hidden. The list shows keys, never values. **Reveal** is a -separate action on a separate endpoint, and it writes an audit event so -looking at a secret is a recorded act. +Once saved, a value is hidden. The list shows key names only. **Reveal** is a +separate action, and it is written to the audit log. Deleting a single key and deleting the whole group are separate operations. ## Using secrets in workflows -Add the group name to a step's `secret_refs`. At execution the group's pairs are -injected into the step's environment: +Add the group to a step's secret references. When the step runs, the group's +pairs are available to it as environment variables: ```bash -# secret_refs: ["registry"] +# with the "registry" group attached to this step echo "$REGISTRY_PASSWORD" | docker login registry.example.com -u "$REGISTRY_USER" --password-stdin ``` -A workflow can also override `secret_refs` per step, without changing the -library entry. +A workflow can change which groups a step uses without changing the step in the +library. :::warning A step can print its own secrets -Injection puts values in the environment. If your script echoes them, or runs -with `set -x`, they land in the run log which is stored on disk and readable -in the UI. Vantage does not scrub step output. +Values arrive as environment variables. If your script prints them, or runs with +`set -x`, they end up in the run log, which anyone who can see the run can read. +Vantage does not filter step output. ::: ## Kubernetes External Secrets Operator -`GET /api/secrets/:group/values` returns a group's pairs for ESO, authenticated -with a **bearer token** rather than a session. +Kubernetes can read a secret group directly, using a token rather than a +sign-in. -1. Generate the token at **Settings → Integrations**. It is shown once; only its - SHA-256 is stored. +1. Generate the token at **Settings → Integrations**. It is shown once, and + Vantage stores only a fingerprint of it. 2. Put it in a Kubernetes secret. -3. Point an ESO `SecretStore` at the endpoint with that bearer token. +3. Point an External Secrets Operator `SecretStore` at your Vantage address with + that token. -The token is rotatable: generating a new one replaces the stored hash and -invalidates the old one immediately. +Generating a new token replaces the old one immediately. :::danger This token reads every group It is instance-wide, not scoped to one group. Treat it as a credential to the diff --git a/docs/vantage/servers.md b/docs/vantage/servers.md index b8f386a..7c18e03 100644 --- a/docs/vantage/servers.md +++ b/docs/vantage/servers.md @@ -5,8 +5,8 @@ sidebar_label: Servers --- The fleet. Every managed machine runs an agent that connects outbound to the -control plane, and everything else in Vantage keys, workflows, monitors, -consoles targets these records. +control plane. Keys, workflows, monitors and consoles all point at these +records. ## Enrolling a server @@ -23,18 +23,17 @@ a one-liner to run as root on the target machine. | `offline` | Last-seen passed the threshold | The offline sweep runs every two minutes, so a machine that has just gone away -takes a little while to be marked as such. That delay is intentional a single -missed poll is not an outage. +takes a little while to be marked as such. ## Tags A tag is a `key:value` label you put on a server. Tags are how you say what a -machine **is** `env:prod`, `role:web`, `team:core-infra` so that you can find -it later, and so that a [workflow](./workflows.md) can target it without you +machine is, such as `env:prod`, `role:web` or `team:core-infra`, so that you can +find it later and so a [workflow](./workflows.md) can target it without you naming it by hand. -There is no tag library to manage first. A tag exists because a server carries -it, and it stops existing when the last server carrying it drops it. +There is no tag library to set up first. A tag exists as soon as a server +carries it, and disappears when the last server carrying it drops it. ### The rules @@ -45,25 +44,22 @@ it, and it stops existing when the last server carrying it drops it. | Value length | up to 64 characters | | Per server | up to 20 tags | -Neither half may be empty, and keys beginning `sys:` are reserved for tags -Vantage may derive from inventory later, so a tag you write today can never -collide with one invented for you tomorrow. +Neither half may be empty, and keys beginning `sys:` are reserved for Vantage's +own use. -Anything outside those rules is refused with a message naming the rule, rather -than quietly saved in a shape you did not intend. Uppercase is not folded to -lowercase for you `Env` is a mistake, not a synonym for `env`. +Anything outside those rules is refused, with a message naming the rule. +Uppercase is not corrected for you, so `Env` and `env` are different tags. ### Editing a server's tags -On the server detail page, **Edit** beside the tag chips. Saving replaces the -whole set: what you see in the editor is exactly what the server will have. -There is no per-tag merge, so if two people edit the same server at once, the -last save wins outright rather than producing a blend of the two. +On the server's page, click **Edit** beside the tags. Saving replaces the whole +set, so what you see in the editor is exactly what the server ends up with. If +two people edit the same server at once, the last save wins. ### Filtering the fleet The **Servers** list has a picker per tag key in use. Choosing values from more -than one key narrows the list a server must match **all** of them, not any. +than one key narrows the list, because a server must match **all** of them. Untagged servers appear only when no filter is set. :::tip A filtered fleet view is a link @@ -96,16 +92,13 @@ metrics is normal rather than a fault. Agents check for pending package updates hourly and report the count. From the server page you can: -- **Apply updates** pushes `ApplyUpdatesCmd` down the command stream. The - agent runs the platform's package manager and reports back. -- **Update agent** pushes `UpdateAgentCmd` with a target version; the agent - downloads the release, verifies it and replaces itself. See +- **Apply updates** runs the machine's own package manager and reports back. +- **Update agent** upgrades the Vantage agent on that machine. See [Agent updates](../operations/agent-updates.md). :::warning Applying updates is not scheduled or staged -It runs now, on that machine. If you need ordering, health gates or a canary, -build it as a [workflow](./workflows.md) instead that is what workflows exist -for. +It runs immediately, on that machine. If you need ordering, health checks or a +test machine first, build it as a [workflow](./workflows.md) instead. ::: ### Console @@ -114,10 +107,8 @@ Opens a browser SSH, RDP or VNC session. See [Browser console](./browser-console ## Windows servers -Windows agents register, heartbeat, run workflow steps and report inventory. -They do not manage `authorized_keys` the poll loop stops after the heartbeat -on any non-Linux host. This is a deliberate scope decision, not a gap being -worked on. +Windows agents register, run workflow steps and report inventory. They do not +manage `authorized_keys`. ## Removing a server @@ -138,7 +129,6 @@ no longer running to remove them. Revoke and let the agent apply the change ## Agent tokens -Each server has its own token. The control plane stores only its SHA-256; the -plaintext exists in the agent's `0600` config and nowhere else. There is no way -to read a token back out of the control plane if one is lost, re-enrol the -machine. +Each server has its own token, which exists in full only in the agent's config +file on that machine. Vantage stores a fingerprint of it and cannot show it to +you again. If a token is lost, enrol the machine again. diff --git a/docs/vantage/settings.md b/docs/vantage/settings.md index 54894b4..4bdd656 100644 --- a/docs/vantage/settings.md +++ b/docs/vantage/settings.md @@ -4,31 +4,14 @@ title: Settings sidebar_label: Settings --- -One page, three groups: **Access**, **Monitoring** and **Integrations**. Plus -the licence, which has its own page. +One page, three groups: **Access**, **Monitoring** and **Integrations**. Your +licence has its own page. -Settings require the `owner` or `admin` role. - -:::info Where instance settings went -Members and single sign-on used to live at `/settings/instance`. They are now -the Access group at the top of this page splitting "who can sign in" from "how -this instance behaves" produced two half-pages and a nav entry nobody could -distinguish from Settings. The old path still redirects. -::: - -:::danger Upgrading breaks existing single sign-on until you re-register the callback URL -Callback URLs are now per provider instead of one shared URL for the whole -instance. If you already had single sign-on configured, it was carried -forward automatically, but its callback URL changed and **sign-in through it -will fail until you copy the new callback URL from its settings card and -register it with your identity provider**. The migrated provider's card shows -a dismissable warning as a reminder. Password sign-in is not affected by this -change, so an administrator can always sign in locally to make the update. -::: +Settings need the `owner` or `admin` role. ## Access -### Members +### People Add, remove and re-role the people who can sign in. @@ -38,112 +21,103 @@ Add, remove and re-role the people who can sign in. | `admin` | Everything except owner-only settings | | `member` | Servers, keys, workflows, monitors, secrets, console | -Local members authenticate with email and a bcrypt-hashed password. +Local members sign in with an email address and a password. -#### Members managed by Vantage HQ +#### People managed by Vantage HQ -On a cloud instance, people granted access from the HQ portal appear here as -read-only rows with a link to the portal. +On a cloud instance, anyone granted access from the Vantage HQ portal appears +here as a read-only row with a link back to the portal. -:::warning HQ-managed users cannot be edited locally -Changing the role of, or deleting, an `hq`-sourced user is refused with `409`. -HQ owns their role, their password and whether they exist at all a local -change would be overwritten by the next sync and would leave two writers for one -password hash. Manage them from [People and roles](../hq/people-and-roles.md). +:::warning You cannot edit those people here +Their role, password and access are owned by Vantage HQ, so changing or removing +them has to be done there. See +[People and roles](../hq/people-and-roles.md). ::: ### Single sign-on -Add as many identity providers as you need: one instance can have several at -once, each with its own name, its own button on the login page and its own -callback URL. +:::info Requires the single sign-on feature on your licence +It is a per-instance feature you enable on a paid plan. +::: -Pick a provider from the list of presets: +Add as many identity providers as you need. Each has its own name, its own +button on the login page and its own callback URL. -| Preset | You provide | -| ---------------------- | ------------------------------------------- | -| Microsoft Entra ID | Directory (tenant) ID | -| Google Workspace | nothing further, the issuer is fixed | -| Okta | Your Okta org domain | -| GitHub | Client ID and client secret only | -| Other (OpenID Connect) | The issuer URL of your identity provider | +If you configured single sign-on on an older version, see +[Upgrading](../operations/upgrading.md#single-sign-on-after-an-upgrade). -Every provider also needs a **Client ID** and **Client secret**; the secret is -stored AES-256-GCM encrypted and never shown again after you save it. +Start from a preset: -:::info GitHub requires a verified primary email -Vantage signs a person in by their email address. GitHub is asked for the -account's addresses and only accepts one that is **both** the account's -primary address **and** marked verified: an address GitHub has not confirmed -is not proof anyone controls it. +| Preset | You provide | +| ---------------------- | ---------------------------------------- | +| Microsoft Entra ID | Directory (tenant) ID | +| Google Workspace | Nothing further | +| Okta | Your Okta org domain | +| GitHub | Client ID and client secret only | +| Other (OpenID Connect) | The issuer URL of your provider | + +Every provider also needs a **Client ID** and **Client secret**. The secret is +stored encrypted and is never shown again after you save it. + +:::info GitHub needs a verified primary email +Vantage identifies people by email address, and it only accepts a GitHub address +that is both the account's primary address and confirmed by GitHub. ::: #### Callback URL -Each provider gets its own callback URL, shown on its settings card with a -copy button. This is the address you register with the identity provider when -you set up the application on their side: each provider is registered -separately, even if you have several with the same identity provider. +Each provider's card shows its callback URL with a copy button. That is the +address you register with the identity provider when you set up the application +on their side. Register each provider separately, even where several use the +same identity provider. #### Turning off password sign-in -You can disable local (email and password) sign-in once at least one provider -is enabled. Vantage refuses to save a change that would leave nobody able to -sign in, whether that change comes from the local login toggle or from -disabling the last enabled provider. Keep at least one option open until every -person who needs access can reach the new one. +Once at least one provider is enabled you can turn off email and password +sign-in. Vantage refuses any change that would leave nobody able to sign in, +whether that is switching off passwords or disabling your last provider. Keep +one route open until everyone who needs access can use the new one. ## Monitoring -- **Alert defaults** for monitors. -- **Notification channels** their own page. See - [Notification channels](./notification-channels.md). - -## Integrations - -### Workflow log retention - -How long run logs are kept. +- **Offline threshold**, how long a server may go unheard from before it is + marked offline. The default is 5 minutes. +- **Offline alerts**, the [notification channels](./notification-channels.md) to + tell when that happens. +- **Notification channels** have [their own page](./notification-channels.md). +- **Workflow log retention**, how long run logs are kept. | Value | Meaning | | -------- | -------------- | | unset | 30 days | | a number | that many days | -| `0` | forever | +| `0` | keep forever | -### ESO read token +## Integrations -The bearer token External Secrets Operator uses to read secret groups. Shown -once, stored as a SHA-256 hash, rotatable. See +### External Secrets Operator token + +The token Kubernetes uses to read your secret groups. It is shown once, stored +only as a fingerprint, and can be replaced at any time. See [Secrets](./secrets.md#kubernetes-external-secrets-operator). ## Licence -`/settings/license` shows the deployment, tier, server allowance, enabled -features and expiry. +The **Licence** page, in the sidebar, shows your instance ID, whether you are +cloud or self-hosted, your tier, server allowance, enabled features and expiry +date. -On **self-hosted**, paste a licence here. This works even while the current -licence is expired that is the way out of degraded mode. +On a **self-hosted** install you paste your licence here. This works even while +your current licence has expired, which is how you get an instance out of +read-only mode. -On **cloud**, there is no paste form. The endpoint answers `409 cloud_managed`, -because a cloud licence is written by HQ directly. The page links to the portal -instead. +On a **cloud** instance there is nothing to paste. Licences are installed for +you, and the page links to the portal instead. See [Licensing and entitlements](../hq/licensing-and-entitlements.md). ## Sessions -Sessions are an opaque token in the `km_session` cookie, held in Redis with a -24-hour TTL. There is no per-session management UI; restarting Redis signs -everyone out and affects nothing else. - -## Host and organisation guard - -On a multi-tenant deployment, a request to `.vantage.` resolves the -organisation from the slug and rejects a session belonging to a different one. -The label it looks for comes from `APP_ROOT_LABEL`. - -:::warning A wrong `APP_ROOT_LABEL` disables the guard -It does not fail loudly it simply stops matching, and the host check stops -protecting anything. If you serve the UI on a custom domain, set it to match. -::: +Signing in gives you a session that lasts 24 hours. There is no session list to +manage. On a self-hosted install, restarting Redis signs everyone out and affects +nothing else. diff --git a/docs/vantage/ssh-keys.md b/docs/vantage/ssh-keys.md index ccf8705..db4aec8 100644 --- a/docs/vantage/ssh-keys.md +++ b/docs/vantage/ssh-keys.md @@ -22,14 +22,12 @@ fingerprint, and never needs the private half for this path. ### Generate one on a server -Vantage can have an agent generate a keypair on a managed machine -(`GenerateKeyCmd` over the command stream). The public half comes back to the -library. You may optionally upload the private half too, in which case it is +Vantage can have a managed machine generate a keypair for you. The public half +comes back to the library. You may optionally upload the private half too, in which case it is stored **AES-256-GCM encrypted** under `KEY_ENCRYPTION_KEY`. -The JSON representation of a key exposes only `has_private_key` and -`has_passphrase` never the material. Retrieving a stored private key is its -own endpoint and its own audit event. +Vantage never displays stored private key material in a list. Retrieving one is +a separate, deliberate action, and it is written to the audit log. :::tip Why store a private key at all The [browser console](./browser-console.md) needs one to open an SSH session. If @@ -38,13 +36,13 @@ you are not using the console, do not upload private halves. ## Assigning -Assign a key to one or more servers. Within one poll interval 30 seconds the -agent picks up the change. +Assign a key to one or more servers. The agent picks up the change within about +30 seconds. ## Revoking -Revocation is **soft**: the assignment gets a `revoked_at` timestamp rather than -being deleted, so the history of who had access to what, and when, survives. +Revoking marks the assignment revoked, with a timestamp, rather than erasing it, +so the record of who had access to what, and when, survives. The agent treats a revoked assignment as "not desired" and removes the line from `authorized_keys` on its next sync. @@ -60,13 +58,12 @@ Each poll: 1. The control plane returns the desired set of public keys for that server. 2. The agent reads `/root/.ssh/authorized_keys` and computes fingerprints. -3. **If the sets match, it writes nothing.** No disk churn on unchanged state, - which is most polls. -4. If they differ, it writes a temporary file, then `os.Rename()`s it over the - real one and sets mode `0600`. +3. **If they match, it writes nothing.** That is true of almost every check. +4. If they differ, it writes the new file alongside the old one and swaps it in + one step. -The rename is atomic, so a machine that dies mid-write keeps the old file -intact. There is no window in which `authorized_keys` is truncated or partial. +The swap cannot be interrupted halfway, so a machine that loses power mid-change +keeps its old, working file. :::danger Vantage owns the whole file The agent rewrites `authorized_keys` to match the desired set. Keys added by @@ -77,5 +74,5 @@ it in Vantage. ## Recovering from a lockout If you have removed every key from a machine and cannot get in, you still have -the console provided a private key is stored or out-of-band access from your -hosting provider. Vantage has no backdoor and does not keep a break-glass key. +the console, provided a private key is stored, or whatever out-of-band access +your hosting provider offers. Vantage has no backdoor and does not keep a break-glass key. diff --git a/docs/vantage/vulnerabilities.md b/docs/vantage/vulnerabilities.md index b3eb103..2d0f3df 100644 --- a/docs/vantage/vulnerabilities.md +++ b/docs/vantage/vulnerabilities.md @@ -9,8 +9,8 @@ against the security advisories published by that server's own distribution and raises a finding for anything not yet patched. Requires the **vulnerability scanning** feature on your licence. Without it, -agents collect nothing at all — there is no inventory stored and no findings -page to read. +nothing is collected and there is no findings page. See +[Licensing and entitlements](../hq/licensing-and-entitlements.md). ## What gets scanned @@ -20,10 +20,9 @@ report. Windows servers are not scanned. -Some distributions publish no machine-readable advisory feed. Those servers -show **unsupported** on their own page rather than appearing as having no -vulnerabilities — the two are very different answers, and only one of them is -good news. +Some distributions publish no security advisories Vantage can read. Those +servers are shown as **unsupported**, rather than as having no vulnerabilities. +Those are very different answers, and only one of them is good news. ## Why versions look "wrong" @@ -43,9 +42,9 @@ the accurate one for the package you are actually running. ## The board -`/vulnerabilities` groups findings by CVE. One row per CVE with the number of -affected servers, expandable to the individual servers — the same CVE across -forty machines is one decision, not forty. +The **Vulnerabilities** page groups findings by CVE, one row each, with the +number of servers affected. Expand a row to see them. The same CVE across forty +machines is usually one decision, not forty. Severity counts at the top filter the list when clicked. The state tabs switch between **open**, **accepted** and **fixed**. @@ -60,8 +59,8 @@ A finding with a known fixed version gets an **Apply updates** button, which runs the same OS update the server page offers. There is no separate patching mechanism. -Vantage never patches automatically. An unattended upgrade triggered by a third -party's data feed is a fleet-wide change nobody chose. +Vantage never patches automatically. Applying updates is always something you +ask for. ## Accepting a finding @@ -72,23 +71,21 @@ or one with no vendor fix published at all. a reason that is recorded in the audit log along with your name. On that date it reopens by itself. -The expiry is required. A dismissal with no end date is how a finding gets -forgotten, and it is exactly what an auditor will ask to see. +An expiry date is required, so nothing is dismissed permanently by accident. ## Alerts -Alert rules live with your notification channels, under -**Settings → Notification Channels**. A rule has a minimum severity, an optional -server tag filter, and one or more channels. +Alert rules live with your +[notification channels](./notification-channels.md). A rule has a minimum +severity, an optional server tag filter, and the channels to notify. -A rule sends **one digest per scan** summarising what newly opened — never one -message per finding. A database refresh can open several hundred findings at -once, and a message each would flood the channel. +A rule sends **one summary per scan** covering everything newly found, rather +than one message per finding. A single update to the security data can raise +hundreds at once. Findings that were already open do not re-alert. ## Fleet-wide package search -`GET /api/packages/search?name=openssl` answers which servers run a given -package and at what version, across the whole fleet. Useful during an incident -before a finding exists for it. +Search your whole fleet for a package by name to see which servers have it and +at what version. Useful during an incident, before there is a finding for it. diff --git a/docs/vantage/workflows.md b/docs/vantage/workflows.md index 8d4305b..00a214e 100644 --- a/docs/vantage/workflows.md +++ b/docs/vantage/workflows.md @@ -4,10 +4,9 @@ title: Workflows and steps sidebar_label: Workflows --- -A **step** is a reusable script with declared inputs, outputs and secret -references. A **workflow** composes steps in order and targets a set of servers. -Running one dispatches the steps to each target's agent and streams the output -back live. +A **step** is a reusable script with its own inputs, outputs and secrets. A +**workflow** puts steps in order and aims them at a set of servers. Running one +sends the steps to each server and streams the output back as it happens. ## Steps @@ -15,12 +14,12 @@ A step has: | Field | Meaning | | --------------------- | ----------------------------------------------- | -| `name`, `description` | Library identity | -| `interpreter` | `bash` or `powershell` | -| `script` | The body | -| `declared_inputs` | Named parameters with defaults and descriptions | -| `declared_outputs` | Names this step promises to export | -| `secret_refs` | Vault entries injected as environment variables | +| Name and description | How you recognise it in the library | +| Interpreter | `bash` or `powershell` | +| Script | What it runs | +| Inputs | Named parameters, with defaults | +| Outputs | Values this step passes on | +| Secrets | Vault groups made available to it | ### Passing values between steps @@ -34,42 +33,36 @@ echo "$HOSTNAME" echo "HOSTNAME=$HOSTNAME" >> $WORKFLOW_ENV ``` -That is the whole mechanism. `declared_outputs` documents what a step exports so -the designer can show it; the file is what actually carries the value. +That is the whole mechanism. Listing a step's outputs documents them for the +designer, but writing to that file is what actually passes a value on. ### Secrets -List a vault group in `secret_refs` and its key/value pairs are injected as -environment variables when the step runs. They are not written to the run log -unless your own script echoes them. See [Secrets](./secrets.md). +Add a vault group to a step and its pairs are available as environment variables +while it runs. They do not appear in the run log unless your own script prints +them. See [Secrets](./secrets.md). ### The workspace -Every run gets a per-run working directory on each target. Steps share it, so -one step can leave a file for the next. The agent removes it at the end of the -run (`CleanupWorkspaceCmd`). +Every run gets its own working directory on each server. Steps share it, so one +step can leave a file for the next. It is deleted when the run finishes. Do not use it for anything that must outlive the run. ## Default steps -A small library is seeded into every organisation at boot from the image, so a -new install is not staring at an empty page. +Vantage ships a small library of ready-made steps, so a new install is not +staring at an empty page. :::warning Default steps are read-only -Editing or deleting one is refused with `409`. Seeding rewrites them on every -boot, so an edit would silently revert and a delete would come back at the next -restart refusing is the honest answer. +They are reinstalled every time Vantage restarts, so any edit or deletion would +come back anyway. Vantage refuses the change rather than letting it quietly +revert. -To customise one, use the per-step **script override** in the workflow designer, -which belongs to that workflow and is not touched by seeding. To add to the -shared library permanently, a file has to be committed to the repository and the -server image rebuilt. +To adapt one, override its script inside the workflow that uses it. That change +belongs to the workflow and is left alone. ::: -The UI mirrors this the step modal opens read-only and Delete is hidden but -the API is the boundary; the UI is the courtesy. - ## Building a workflow 1. **Workflows → New**. @@ -81,11 +74,11 @@ the API is the boundary; the UI is the courtesy. ### Failure behaviour -| `on_failure` | Effect | -| ------------ | --------------------------------------------------------------- | -| `stop` | Abort this server's run. Other servers continue | -| `continue` | Record the failure, run the next step anyway | -| `retry` | Re-run the step up to `max_retries`, then treat it as a failure | +| On failure | Effect | +| ---------- | ------------------------------------------------------------- | +| Stop | Stop this server's run. Other servers carry on | +| Continue | Record the failure and run the next step anyway | +| Retry | Try again up to the limit you set, then count it as a failure | ### Per-step overrides @@ -97,27 +90,21 @@ scoped to that workflow. A workflow names servers two ways, and it can use both at once: -- **Target servers** an explicit list you pick from the fleet. -- **Target tags** a `key:value` selector matched against - [server tags](./servers.md#tags). More than one key ANDs: a server must carry - every pair to match. +- **Target servers**, a list you pick by hand. +- **Target tags**, matched against [server tags](./servers.md#tags). Give more + than one tag and a server must carry all of them to match. -A run goes to the **union** of the two, with duplicates removed. A server that is -both named explicitly and matched by the selector runs once, not twice. This is -what lets a workflow say "every production web server, plus this one box I am -watching" without maintaining a list. +A run goes to both sets combined. A server that is named by hand and also matched +by a tag runs once. That is how a workflow can say "every production web server, +plus this one machine I am watching" without you keeping a list up to date. The designer shows the resolved count as you edit, so you can see how many machines a change to the selector just added or removed before you save. :::warning An empty selector matches nothing -Clearing the tag selector does not mean "all servers". A workflow with no named -servers and no tags matches nothing and is refused at run time rather than -reported as a success over zero machines. - -The alternative reading, where an empty field means the whole fleet, turns a -cleared box into a fleet-wide run. That is not a mistake anyone should be able to -make by deleting text. +Clearing the tags does not mean "all servers". A workflow with no servers and no +tags matches nothing, and running it is refused rather than reported as a +success over zero machines. ::: Tags are read **at run time**, not when you save. Tag a new machine `env:prod` @@ -127,31 +114,29 @@ machine from every workflow that selected on it. ### Offline servers are still targeted -A server matched by tag is dispatched to even if its agent is offline, and that -step fails visibly on that machine. Vantage does not quietly shrink your target -list to the machines that happened to be reachable a patch run that skipped -three servers and reported success is worse than one that failed on three and -said so. +A server is still targeted when its agent is offline, and the run fails visibly +on that machine. Vantage does not quietly drop unreachable servers from a run, +because a patch run that skipped three servers and called itself a success is +harder to spot than one that failed. Re-run the workflow once they are back, or fix the agent first. ## Running -**Run** snapshots the resolved steps into the run record and dispatches each step -to the target's agent over the command stream no waiting for the next poll. +**Run** records the exact steps being run, then sends them to each server +straight away. :::info Runs freeze their steps -The snapshot is why editing a step tomorrow never rewrites what happened today. -A run shows the script that actually executed, not the current library version. +Editing a step tomorrow never changes what a past run shows. A run always +displays the script that actually ran. ::: Targets run **in parallel**; steps within one server run **in order**. ## Schedules -A workflow can carry a schedule, and Vantage will start it the same way a person -would — the same dispatch, the same snapshot, the same run page. A scheduled run -is an ordinary run with `schedule` recorded as who triggered it. +A workflow can run on a schedule. A scheduled run is an ordinary run, on the +same run page, with the schedule recorded as what started it. Open a workflow, choose **Edit**, and tick **Run on a schedule**. The expression is standard five-field cron: @@ -175,10 +160,10 @@ than the browser, so what you see is exactly what will fire. ### Timezones -A schedule stores an IANA timezone by name — `Europe/London`, not an offset. -That is what makes a 02:00 job stay at 02:00 across a daylight-saving change -instead of drifting an hour for half the year. An unknown zone is refused when -you save it, not at 2am. +A schedule stores a timezone by name, such as `Europe/London`, rather than an +offset. That keeps a 02:00 job at 02:00 across daylight-saving changes instead +of drifting by an hour for half the year. A timezone Vantage does not recognise +is refused when you save it. ### Overlaps are skipped, not queued @@ -189,28 +174,27 @@ should fall behind visibly rather than pile up. ### Missed occurrences -If the control plane was not running when an occurrence was due, it still fires -when the control plane comes back — but only within **one hour** of the due -time. Anything older is recorded as missed and dropped. A job missed by ten -minutes during an upgrade should still run; one missed by two days should not -suddenly fire at lunchtime. +If Vantage was not running when a scheduled run was due, it still runs when +Vantage comes back, as long as that is within **one hour** of the due time. +Anything older is recorded as missed and skipped, so a job missed during a short +upgrade catches up, while one missed for two days does not suddenly start at +lunchtime. Either kind of skip is shown on the workflow's schedule panel, with the time it was due and why it did not run. ## Watching a run -Step stdout and stderr stream back as chunks, are appended to a log file on the -server, and the UI follows them live. Each step records status, attempts, exit -code and its exported environment. +Output streams back as it happens and the page follows it live. Each step +records its status, how many attempts it took, its exit code and any values it +passed on. **Cancel** stops a run in progress. Steps already running on an agent finish; nothing further is dispatched. ## Log retention -Run logs are swept on a schedule set by `workflow_log_retention_days` in -Settings: +How long run logs are kept is set under **Settings → Monitoring**: | Value | Meaning | | -------- | -------------- | @@ -220,16 +204,15 @@ Settings: ## Import and export -Steps export to a JSON file (`vantage.step/v1`) and import back, which is how -you move a step between instances or keep one in version control. There is also -a parse endpoint that turns a pasted script into a draft step by reading its -declared inputs and outputs. +A step exports to a file and imports back, which is how you move one between +instances or keep it in version control. You can also paste a script and have +Vantage turn it into a draft step for you. ## Practical notes - A step is a script. It runs as root, on the target, with no sandbox. Review what you import. -- Keep steps small and single-purpose; compose them in the workflow. That is - what makes the library reusable rather than a folder of near-duplicates. +- Keep steps small and single-purpose, and combine them in the workflow. A + library of small steps stays reusable. - PowerShell steps only make sense on Windows targets and bash steps on Linux ones. Nothing stops you targeting the wrong one; the step simply fails. diff --git a/docs/vantage/workloads.md b/docs/vantage/workloads.md index bef6975..962eaea 100644 --- a/docs/vantage/workloads.md +++ b/docs/vantage/workloads.md @@ -4,30 +4,27 @@ title: Workloads sidebar_label: Workloads --- -A **workload** is one Docker container or one systemd service. Each Linux -server reports what it runs, and you can start, stop and restart those -workloads — and read a snapshot of their logs — without opening a console. +A **workload** is one Docker container or one systemd service. Each Linux server +reports what it is running, and you can start, stop and restart those workloads, +and read their recent logs, without opening a console. Available on every instance. No licence feature is required. ## What gets reported -Linux servers only. Agents report every 60 seconds, and an unchanged list costs -a single small message rather than the whole thing again. +Linux servers only, reported every 60 seconds. -- **Containers** — every container, running or not, with its image, published +- **Containers**: every container, running or not, with its image, published ports, health, restart count and the compose stack it belongs to. -- **Services** — systemd units that are running or failed, plus units that are - enabled but currently stopped. The platform's own units (`systemd-*`, - `user@*`, `session-*`) are filtered out; a typical host has 300 of them and - they bury the ten you care about. +- **Services**: systemd units that are running, failed, or enabled but stopped. + The operating system's own units are hidden, since a typical host has hundreds + of them and they bury the ones you care about. Windows servers report no workloads at all. ## Docker not in use is not an error -Three different things look identical if you are careless, and only one of them -is a problem: +Three states look similar, and only one of them is a problem: | What you see | What it means | | ------------ | ------------- | @@ -37,54 +34,47 @@ is a problem: ## Stacks are grouped -Compose stacks appear first, grouped under the stack name, then loose -containers, then services. A stack is one thing even when it is six containers, -and a flat list turns one decision into six rows. +Compose stacks appear first, grouped under the stack name, then individual +containers, then services. -The stack name comes from Docker's own `com.docker.compose.project` label. No -compose file is read from disk — a file on disk may not be what is running. +The stack name comes from Docker itself, so it reflects what is actually +running. ## Controlling a workload Start, stop and restart are **owner or admin only**, and every action is written to the audit log naming you, the server and the target. -The agent refuses to act on itself. `vantage-agent.service` is shown with its -buttons disabled: a server that stops its own agent goes offline, and the only -way back is SSH or physical access — which is exactly what this page exists to -avoid needing. +The Vantage agent will not act on itself, and its buttons are disabled. A server +that stopped its own agent would go offline, and getting it back would need SSH +or physical access. -A stop that never finishes is not reported as success. Both `docker stop` and -`systemctl stop` run under a 90-second limit, and a timeout comes back as a -real error. +A stop that never finishes is not reported as a success. Vantage waits up to 90 +seconds and then reports the failure. ## Reading logs -Logs are **owner or admin only** and every read is audited. Unlike workflow -logs, a container's output cannot be masked: it is arbitrary, and a startup -banner or a stack trace may contain credentials nobody declared. +Reading logs is **owner or admin only**, and every read is audited. A +container's output cannot be filtered the way a workflow's can, and a startup +banner or stack trace may contain credentials nobody expected. -A log read returns a snapshot of at most **500 lines or 256KB**, whichever -limit is reached first, with the most recent output kept. When either limit -binds, the dialog says so — a truncated log must never be read as a complete -one. +You get the most recent output, up to **500 lines or 256KB**, whichever comes +first. If it was cut short, the dialog says so. -There is no live following. The [browser console](./browser-console.md) already -gives you a real terminal on the same server, where `docker logs -f` works -properly with its own scrollback. +There is no live tail here. For that, use the +[browser console](./browser-console.md), which gives you a real terminal on the +same server. ## Refreshing -Opening a server's Workloads panel asks its agent to report immediately, so -what is on screen is current rather than up to a minute old. That matters -because the panel has a Restart button on it: a stale row is not just a wrong -impression, it is a wrong action aimed at something that already died. +Opening a server's Workloads panel asks its agent to report straight away, so +what you see is current rather than up to a minute old. That matters when the +next thing you click is Restart. -If the agent is offline the refresh fails visibly rather than queueing. A -command whose target cannot be reached must say so. +If the agent is offline, the refresh reports a failure rather than waiting. ## Fleet view -**Workloads** in the sidebar searches the whole fleet by image, stack or state -— "which of these servers is still on the old image" — and links each result -back to its server. +**Workloads** in the sidebar searches your whole fleet by image, stack or state, +which is how you answer questions like "which servers are still on the old +image". Each result links back to its server. diff --git a/sidebars.ts b/sidebars.ts index 7d5756d..6ecaaff 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -14,8 +14,8 @@ const sidebars: SidebarsConfig = { "getting-started/cloud-vs-self-hosted", "getting-started/self-hosted-install", "getting-started/first-login", - "getting-started/first-server", "getting-started/claim-free-licence", + "getting-started/first-server", ], }, {