diff --git a/docsite/docs/getting-started/claim-free-licence.md b/docsite/docs/getting-started/claim-free-licence.md index 0c43a05..58ac43c 100644 --- a/docsite/docs/getting-started/claim-free-licence.md +++ b/docsite/docs/getting-started/claim-free-licence.md @@ -12,7 +12,7 @@ HQ portal. A signed file. It carries the instance UUID it belongs to, the tier, the server allowance, feature toggles and an expiry. The control plane verifies the -signature locally — checking a licence never contacts HQ, and a running instance +signature locally checking a licence never contacts HQ, and a running instance does not need HQ to be reachable. Signing happens in exactly one place, in HQ. The control plane can only verify. @@ -40,7 +40,7 @@ bound to that UUID and hands it back. :::info One Free per account, per deployment The limit is enforced per account **and** deployment, so a Free cloud instance does not stop you claiming Free on a self-hosted install. Both the friendly -pre-check and the issuer apply the same rule — deliberately, because a +pre-check and the issuer apply the same rule deliberately, because a pre-check stricter than the issuer would refuse something that would actually have worked. ::: @@ -56,7 +56,7 @@ starts reporting the tier, allowance and expiry. :::warning Cloud instances cannot paste a licence On a cloud instance `POST /license` answers `409 cloud_managed`, and the UI hides the form entirely. A cloud licence is written directly by HQ. This is not -a restriction the injection path has to work around — it writes to the database, +a restriction the injection path has to work around it writes to the database, not through the endpoint. ::: @@ -65,7 +65,7 @@ not through the endpoint. Free licences are renewable from HQ within a renewal window near expiry; outside that window the renew call refuses. See [Free tier](../hq/free-tier.md). -Pasting a licence keeps working while the current one is expired — that endpoint +Pasting a licence keeps working while the current one is expired that endpoint is exempt from the licence check, because it is the way out of degraded mode. ## Moving the install to new hardware diff --git a/docsite/docs/getting-started/cloud-vs-self-hosted.md b/docsite/docs/getting-started/cloud-vs-self-hosted.md index 1becc09..71b89c6 100644 --- a/docsite/docs/getting-started/cloud-vs-self-hosted.md +++ b/docsite/docs/getting-started/cloud-vs-self-hosted.md @@ -9,21 +9,21 @@ who operates it and how licensing, users and data lifecycle work. ## At a glance -| | Cloud | Self-hosted | -| --- | --- | --- | -| Who runs it | We do | You do | -| Where you sign in | `.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 | Written for you when you buy or create the instance | Pasted in, or claimed from HQ | +| Team members | Granted from HQ; the instance holds a projection | Created in the instance itself | +| Free tier | Yes, one per account | Yes, one per account | +| Expired Free instance | Eventually deleted, after warning | Never deleted | ## Cloud You create an instance from the HQ portal and it exists a few seconds later, already licensed. People you grant access to get a real user inside that -instance — see [People and roles](../hq/people-and-roles.md) — but HQ owns their +instance see [People and roles](../hq/people-and-roles.md) but HQ owns their password, role and existence. :::info The instance does not phone home @@ -38,33 +38,27 @@ 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 +control plane requires an internet connection to HQ at runtime a licence is a signed file, verified locally. Two ways to get one: -1. **Free** — link the install to an HQ account and claim it +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 +2. **Paid** buy from HQ, which creates a placeholder, then paste the install's real instance UUID to bind and issue ([Self-hosted instances](../hq/self-hosted-instances.md)). Self-hosted users are local (or OIDC). There is no projection from HQ, and the three member endpoints in HQ refuse to touch a self-hosted instance at all. -:::warning Self-hosted instances are never deleted by us -The reaper that removes expired Free cloud instances is disabled by default and -must stay that way on a self-hosted install. See `FREE_INSTANCE_REAP_AFTER` in -[Environment variables](../reference/environment-variables.md). -::: - ## Which should you pick Pick cloud if you want the thing running now and do not want to own a MongoDB. Pick self-hosted if your policy requires the control plane inside your own network, or the servers you manage cannot reach the public internet. -Moving between them is a migration, not a switch — instances are bound to a +Moving between them is a migration, not a switch instances are bound to a deployment at creation, and a licence binds to an instance UUID. ## Next diff --git a/docsite/docs/getting-started/first-login.md b/docsite/docs/getting-started/first-login.md index b44b8b2..5295131 100644 --- a/docsite/docs/getting-started/first-login.md +++ b/docsite/docs/getting-started/first-login.md @@ -13,15 +13,15 @@ Open the control plane in a browser. Because no user exists, you land on Fill in: -| Field | Notes | -| --- | --- | -| Organisation name | Display name. Shown throughout the UI | -| Slug | Lowercase, used in the hostname on cloud. Some names are reserved | -| Your name | | -| Email | Becomes your sign-in identity | -| Password | Stored bcrypt-hashed | +| Field | Notes | +| ----------------- | ----------------------------------------------------------------- | +| Organisation name | Display name. Shown throughout the UI | +| Slug | Lowercase, used in the hostname on cloud. Some names are reserved | +| Your name | | +| Email | Becomes your sign-in identity | +| Password | Stored bcrypt-hashed | -Submitting creates the organisation and its **owner** — you. +Submitting creates the organisation and its **owner** you. :::warning Bootstrap works exactly once The endpoint is open only while the database has no users. As soon as the first @@ -43,30 +43,30 @@ nothing else. You land on the fleet dashboard, which is empty. The sidebar is the whole product: -| Section | What it does | -| --- | --- | -| Servers | The fleet — enrol, inspect, console, update | -| Keys | SSH public keys and their assignments | -| Workflows | Compose and run scripted work | -| Steps | The reusable step library | -| Monitors | HTTP, TCP, ICMP and TLS checks | -| Secrets | The encrypted vault | -| Audit | Every mutating action | -| Settings | Members, SSO, alerts, retention, licence | +| Section | What it does | +| --------- | ----------------------------------------- | +| Servers | The fleet enrol, inspect, console, update | +| Keys | SSH public keys and their assignments | +| Workflows | Compose and run scripted work | +| Steps | The reusable step library | +| Monitors | HTTP, TCP, ICMP and TLS checks | +| Secrets | The encrypted vault | +| Audit | Every mutating action | +| Settings | Members, SSO, alerts, retention, licence | ## 4. Add the rest of your team Go to **Settings → Access**. Add members with a role: -| Role | Can | -| --- | --- | -| `owner` | Everything, including billing-adjacent settings | -| `admin` | Everything except owner-only settings | -| `member` | Day-to-day work — servers, keys, workflows, monitors | +| Role | Can | +| -------- | -------------------------------------------------- | +| `owner` | Everything, including billing-adjacent settings | +| `admin` | Everything except owner-only settings | +| `member` | Day-to-day work servers, keys, workflows, monitors | Settings and organisation management require `owner` or `admin`. -If you would rather not manage passwords, configure OIDC instead — see +If you would rather not manage passwords, configure OIDC instead see [Settings](../vantage/settings.md#single-sign-on-oidc). OIDC is configured per organisation, and the client secret is stored encrypted. diff --git a/docsite/docs/getting-started/first-server.md b/docsite/docs/getting-started/first-server.md index 98c32e6..19c5a79 100644 --- a/docsite/docs/getting-started/first-server.md +++ b/docsite/docs/getting-started/first-server.md @@ -17,7 +17,7 @@ one-liner. :::warning The token is single-use and lives one hour It is the only credential in the flow, and it is spent the moment the agent calls `Register`. If you paste it somewhere and come back tomorrow, create a new -enrolment instead — nothing is lost by doing so. +enrolment instead nothing is lost by doing so. ::: ## 2. Run the one-liner @@ -30,7 +30,7 @@ curl -fsSL "https://vantage.example.com/install?server_id=&token=" | Run it as root. The script: -1. Detects architecture — `x86_64` and `aarch64` only; anything else exits. +1. Detects architecture `x86_64` and `aarch64` only; anything else exits. 2. Asks the Gitea API for the newest `agent/v*` release. 3. Downloads the binary and `checksums.txt`, and **verifies the SHA-256**, aborting on a mismatch. @@ -52,14 +52,14 @@ MSI built by CI if you would rather deploy that. :::info Windows agents are second-class on purpose They register, heartbeat, run workflow steps and report inventory. They do -**not** manage `authorized_keys` — the key subsystem is Linux-only, and a +**not** manage `authorized_keys` the key subsystem is Linux-only, and a Windows agent stops after the heartbeat portion of the poll. ::: ## 3. Watch it come up -The server appears immediately as `pending`. Within one poll interval — 30 -seconds — it flips to `active`. +The server appears immediately as `pending`. Within one poll interval 30 +seconds it flips to `active`. On the machine: @@ -72,10 +72,10 @@ What happens on that first run: ``` 1. Load /etc/vantage/config.yaml -2. pre_reg_token present → Register() → save agent_token, clear pre_reg_token +2. pre_reg_token present → register → save agent_token, clear pre_reg_token 3. Reconnect with the permanent token 4. Start: command stream · hourly update check · inventory · monitors -5. Enter the SyncKeys poll loop +5. Enter the key poll loop ``` After registration the config no longer contains the pre-registration token; it @@ -87,19 +87,19 @@ SHA-256 of that token, never the token itself. Open the server's detail page. Within a minute or two you should see: - Status `active`, with a recent last-seen timestamp. -- Inventory — CPU, memory, swap, partitions, kernel. Metrics refresh every 30 +- Inventory CPU, memory, swap, partitions, kernel. Metrics refresh every 30 seconds; the full static snapshot every 15 minutes. - Pending OS updates, checked hourly. ## If it does not appear -| Symptom | Cause | -| --- | --- | -| Script exits at "Unsupported architecture" | Not amd64 or arm64 | -| "Checksum mismatch!" | Interrupted download, or a proxy rewriting the body. Re-run | -| "Could not determine latest agent version" | The host cannot reach `gitea.hostxtra.co.uk`, or no `agent/v*` release exists | -| Service runs, server stays `pending` | The machine cannot reach `GRPC_HOST`. Test it from that machine | -| Registers once then goes `offline` | Reachable for `Register` but not for the poll — usually a firewall that permits the initial connection but drops the long-lived one | +| Symptom | Cause | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | +| Script exits at "Unsupported architecture" | Not amd64 or arm64 | +| "Checksum mismatch!" | Interrupted download, or a proxy rewriting the body. Re-run | +| "Could not determine latest agent version" | The host cannot reach `gitea.hostxtra.co.uk`, or no `agent/v*` release exists | +| Service runs, server stays `pending` | The machine cannot reach `GRPC_HOST`. Test it from that machine | +| Registers once then goes `offline` | Reachable for `Register` but not for the poll usually a firewall that permits the initial connection but drops the long-lived one | A server is marked `offline` when its last-seen time passes the threshold; that sweep runs every two minutes, so allow for it before concluding anything. diff --git a/docsite/docs/getting-started/self-hosted-install.md b/docsite/docs/getting-started/self-hosted-install.md index a7d1ab4..75179cf 100644 --- a/docsite/docs/getting-started/self-hosted-install.md +++ b/docsite/docs/getting-started/self-hosted-install.md @@ -42,7 +42,7 @@ a way you did not intend. Create `/opt/vantage/.env`: ```bash -# The host:port agents dial. NOT the web URL — this port speaks gRPC. +# The host:port agents dial. NOT the web URL this port speaks gRPC. GRPC_HOST=vantage.example.com:9090 @@ -62,7 +62,7 @@ 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 +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. ::: @@ -109,12 +109,12 @@ Continue with [First login](./first-login.md). ## Verifying the install -| Check | Expected | -| --- | --- | -| `docker compose ps` | five services `running` | +| Check | Expected | +| ---------------------------------------------- | ------------------------------- | +| `docker compose ps` | five services `running` | | `curl -s localhost:8080/auth/bootstrap-status` | JSON saying bootstrap is needed | -| `nc -z your-host 9090` | open | -| `docker compose logs server \| grep -i fatal` | nothing | +| `nc -z your-host 9090` | open | +| `docker compose logs server \| grep -i fatal` | nothing | ## Common install problems @@ -131,7 +131,6 @@ More in [Troubleshooting](../reference/troubleshooting.md). ## What this install does not include -The marketing site, the public form service, the HQ portal and this -documentation site are separate services in `deploy/docker-compose.site.yml`. -A self-hosted install deliberately runs none of them, and in particular never -holds the licence signing key. +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. diff --git a/docsite/docs/getting-started/what-is-vantage.md b/docsite/docs/getting-started/what-is-vantage.md index 84c58c4..f7e69a5 100644 --- a/docsite/docs/getting-started/what-is-vantage.md +++ b/docsite/docs/getting-started/what-is-vantage.md @@ -27,8 +27,8 @@ stores everything durable; Redis stores sessions and nothing else. **The agent** is a single Go binary running as root on each managed server. It polls the control plane every 30 seconds for desired key state, and holds a -bidirectional command stream so the server can push work — run a workflow step, -generate a key, apply updates — without waiting for the next poll. +bidirectional command stream so the server can push work run a workflow step, +generate a key, apply updates without waiting for the next poll. **The web UI** is the operator interface. Everything it does goes through the REST API, which is the actual security boundary; the UI only ever makes things @@ -46,18 +46,18 @@ could guess on its behalf. ## Two request patterns -| Pattern | Used for | Why | -| --- | --- | --- | -| Poll (`SyncKeys`, every 30s) | desired SSH key state | Key changes are not urgent, and polling survives a dropped connection with no reconnection logic | -| Push (`CommandStream`) | workflow steps, key generation, updates, agent self-update | Clicking Run should not wait up to 30 seconds | +| 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. +self-hosted install that mechanism is still there you simply have one tenant. ## Next -- [Cloud or self-hosted](./cloud-vs-self-hosted.md) — which one you want -- [Self-hosted install](./self-hosted-install.md) — stand it up +- [Cloud or self-hosted](./cloud-vs-self-hosted.md) which one you want +- [Self-hosted install](./self-hosted-install.md) stand it up diff --git a/docsite/docs/hq/accounts-and-signup.md b/docsite/docs/hq/accounts-and-signup.md index 46e8af0..be2932b 100644 --- a/docsite/docs/hq/accounts-and-signup.md +++ b/docsite/docs/hq/accounts-and-signup.md @@ -12,17 +12,17 @@ behind your instances: your team, your instances, their licences and billing. One account holds many people and many instances. Everyone in it has an account role: -| Role | Can | -| --- | --- | -| `owner` | Everything, including billing | -| `admin` | Invite people, create instances, grant instance access | -| `member` | Read what the account holds | +| Role | Can | +| -------- | ------------------------------------------------------ | +| `owner` | Everything, including billing | +| `admin` | Invite people, create instances, grant instance access | +| `member` | Read what the account holds | 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 +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. ## Signing up @@ -37,7 +37,7 @@ you later create or link one. :::info Verify before you can sign in An unverified account gets a distinct "check your email" message rather than a -generic authentication failure — the address is already known to be yours, so +generic authentication failure the address is already known to be yours, so there is nothing to protect by being vague. ::: @@ -45,7 +45,7 @@ Verification links are valid for **24 hours**. The token is 32 random bytes and only its SHA-256 hash is stored, so a leaked database yields no working links. If the verification email cannot be sent, the signup is rolled back rather than -left stranded — retry rather than assuming a half-created account is in the way. +left stranded retry rather than assuming a half-created account is in the way. ## Signing in @@ -54,11 +54,11 @@ signing in to HQ does not sign you in to an instance, and vice versa. ## 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 add colleagues | [People and roles](./people-and-roles.md) | +| 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 add colleagues | [People and roles](./people-and-roles.md) | | To understand tiers and limits | [Licensing and entitlements](./licensing-and-entitlements.md) | ## The portal layout diff --git a/docsite/docs/hq/billing.md b/docsite/docs/hq/billing.md index dfd0af1..f6d5e04 100644 --- a/docsite/docs/hq/billing.md +++ b/docsite/docs/hq/billing.md @@ -52,8 +52,8 @@ flowchart LR ``` The webhook is the **only** issuing path for paid plans. It is signature -verified, processed exactly once, and resolved from the subscription's *current* -line items — so a webhook that arrives out of order still produces the right +verified, processed exactly once, and resolved from the subscription's _current_ +line items so a webhook that arrives out of order still produces the right answer rather than replaying a stale state. A licence is signed from **granted** only. A checkout you abandon changes @@ -65,7 +65,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 reaped. ## Renewals diff --git a/docsite/docs/hq/cloud-instances.md b/docsite/docs/hq/cloud-instances.md index 23d20ec..2a62712 100644 --- a/docsite/docs/hq/cloud-instances.md +++ b/docsite/docs/hq/cloud-instances.md @@ -15,7 +15,7 @@ A cloud instance is a Vantage control plane we run for you, reachable at 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). +shared see [People and roles](./people-and-roles.md). ### Slugs @@ -24,7 +24,7 @@ reserved. Pick something 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 -a paid plan — or free up the first. +a paid plan or free up the first. ::: ## Using it @@ -41,9 +41,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, expiry. +- **Members** who has access and with what instance role. +- **Actions** grant access, change configuration, renew. ## Members @@ -53,14 +53,14 @@ 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**. 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. +casually changed ask support if you need it. ## What happens if the licence lapses @@ -73,6 +73,6 @@ runs to its grace-padded expiry and then lapses. ## Deleting -Ask support. Deletion is performed by the control plane, not by HQ — the control +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. diff --git a/docsite/docs/hq/free-tier.md b/docsite/docs/hq/free-tier.md index ff3a202..d88ec02 100644 --- a/docsite/docs/hq/free-tier.md +++ b/docsite/docs/hq/free-tier.md @@ -4,18 +4,18 @@ 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 real tier in both deployments not a trial that turns into nothing. ## What you get -| | Free | -| --- | --- | -| Servers | 3 | -| Monitors | 3 | -| Secret groups | 1 | -| Notification channels | 1 | -| Audit retention | 30 days | -| Support | Community | +| | Free | +| --------------------- | --------- | +| Servers | 3 | +| Monitors | 3 | +| Secret groups | 1 | +| Notification channels | 1 | +| Audit retention | 30 days | +| Support | Community | Browser console and single sign-on are not included; they are per-instance features on a paid plan. @@ -23,7 +23,7 @@ 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. ## Free is outside Paddle @@ -49,8 +49,7 @@ cloud instance can lapse and eventually be deleted without anyone noticing. ## What happens when it lapses **Self-hosted:** the instance goes into degraded mode after the grace period and -stays that way. Nothing is deleted, ever — the reaper is disabled by default on -a self-hosted install and must stay that way. +stays that way. Nothing is deleted, ever. **Cloud:** the instance goes into degraded mode, and after a further period the instance **and all its data are deleted**. Warning emails are sent first, naming @@ -69,7 +68,7 @@ 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 diff --git a/docsite/docs/hq/licensing-and-entitlements.md b/docsite/docs/hq/licensing-and-entitlements.md index 47b290f..df334e3 100644 --- a/docsite/docs/hq/licensing-and-entitlements.md +++ b/docsite/docs/hq/licensing-and-entitlements.md @@ -10,30 +10,30 @@ 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 | -| --- | --- | --- | --- | -| Servers (base) | 3 | 3 | 10 | -| Monitors | 3 | unlimited | unlimited | -| Secret groups | 1 | unlimited | unlimited | -| Notification channels | 1 | unlimited | unlimited | -| Audit retention | 30 days | 365 days | unlimited | -| Support | Community | Email, 24×5 | Email and phone, 24×7 | +| | Free | Professional | Enterprise | +| --------------------- | --------- | ------------ | --------------------- | +| Servers (base) | 3 | 3 | 10 | +| Monitors | 3 | unlimited | unlimited | +| Secret groups | 1 | unlimited | unlimited | +| Notification channels | 1 | unlimited | unlimited | +| Audit retention | 30 days | 365 days | unlimited | +| Support | Community | Email, 24×5 | Email and phone, 24×7 | 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: -| Feature | What it enables | -| --- | --- | -| `console` | The [browser console](../vantage/browser-console.md) | -| `oidc` | Per-instance [single sign-on](../vantage/settings.md#single-sign-on-oidc) | +| Feature | What it enables | +| --------- | ------------------------------------------------------------------------- | +| `console` | The [browser console](../vantage/browser-console.md) | +| `oidc` | Per-instance [single sign-on](../vantage/settings.md#single-sign-on-oidc) | No tier includes them by default; you enable them on the instances that need them. @@ -42,9 +42,9 @@ them. Each instance has one entitlement row holding two configurations: -| | Meaning | -| --- | --- | -| **Desired** | What you last asked for | +| | Meaning | +| ----------- | ---------------------------- | +| **Desired** | What you last asked for | | **Granted** | What a payment has confirmed | Checkout is built from **desired**. A licence is only ever signed from @@ -80,7 +80,7 @@ signing happens only in HQ. 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. -The way out is a current licence — renew or purchase, then paste it (self-hosted) +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/docsite/docs/hq/people-and-roles.md b/docsite/docs/hq/people-and-roles.md index 17617bc..8bb0ead 100644 --- a/docsite/docs/hq/people-and-roles.md +++ b/docsite/docs/hq/people-and-roles.md @@ -11,11 +11,11 @@ each **instance**. **People** lists everyone in the account. -| Role | Can | -| --- | --- | -| `owner` | Everything, including billing | -| `admin` | Invite, create instances, grant instance access | -| `member` | Read | +| Role | Can | +| -------- | ----------------------------------------------- | +| `owner` | Everything, including billing | +| `admin` | Invite, create instances, grant instance access | +| `member` | Read | Owners and admins invite; billing is owner-only. @@ -53,7 +53,7 @@ flowchart LR ``` The instance authenticates that user exactly as it authenticates anyone else, -with **no runtime dependency on HQ**. Revoking deletes the row — the control +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. ### Granting diff --git a/docsite/docs/hq/self-hosted-instances.md b/docsite/docs/hq/self-hosted-instances.md index 2e4c65a..3838284 100644 --- a/docsite/docs/hq/self-hosted-instances.md +++ b/docsite/docs/hq/self-hosted-instances.md @@ -52,7 +52,7 @@ 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. 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 +left. This is not meant to obstruct disaster recovery if you have exhausted them for a real reason, ask support. ## Installing the licence @@ -60,13 +60,13 @@ them for a real reason, ask support. Paste it at **Settings → Licence** in your install. The instance verifies the signature and checks that the UUID matches its own. -Pasting works even while the current licence is expired — that endpoint is +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. ## 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 does not fetch licences. When a licence is reissued renewal, +configuration change, relink download the new one from 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/docsite/docs/index.md b/docsite/docs/index.md index fc2dc33..8486009 100644 --- a/docsite/docs/index.md +++ b/docsite/docs/index.md @@ -17,17 +17,17 @@ firewall holes. ## Where to start -| If you want to | Read | -| --- | --- | -| Understand what the pieces are | [What is Vantage](./getting-started/what-is-vantage.md) | -| Run it on your own hardware | [Self-hosted install](./getting-started/self-hosted-install.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) | +| If you want to | Read | +| --------------------------------------- | --------------------------------------------------------------- | +| Understand what the pieces are | [What is Vantage](./getting-started/what-is-vantage.md) | +| Run it on your own hardware | [Self-hosted install](./getting-started/self-hosted-install.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/docsite/docs/operations/agent-updates.md b/docsite/docs/operations/agent-updates.md index a857396..a3b92eb 100644 --- a/docsite/docs/operations/agent-updates.md +++ b/docsite/docs/operations/agent-updates.md @@ -14,7 +14,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 +**Servers → _a server_ → Update agent** pushes `UpdateAgentCmd` with a target version. The agent then: 1. Downloads the binary for its platform from the release. @@ -38,7 +38,7 @@ 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 +this when the control plane cannot push for example, when the machine is reachable but its command stream is not. ## Rolling out across a fleet @@ -57,20 +57,20 @@ Do one, confirm it returns to `active`, then do the rest. ## Version compatibility -The gRPC API is versioned to tolerate an agent older than the control plane. The -reverse — an agent newer than the control plane — is not a case anyone tests. +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. -Agents report their version on every `SyncKeys`, so a fleet running mixed +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 looking for. ## If an update fails -| Symptom | Cause | -| --- | --- | -| "Checksum mismatch" | Interrupted download, or a proxy rewriting the body. Retry | -| Downloads nothing | The machine cannot reach `gitea.hostxtra.co.uk` | +| Symptom | Cause | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| "Checksum mismatch" | Interrupted download, or a proxy rewriting the body. Retry | +| Downloads nothing | The machine cannot reach `gitea.hostxtra.co.uk` | | Service will not start afterwards | Wrong architecture binary, or the file was replaced while a different service manager held it. Reinstall with the install one-liner | Reinstalling is always safe: the config file is left alone, so the agent comes diff --git a/docsite/docs/operations/backups.md b/docsite/docs/operations/backups.md index 5ecd44c..8eff3e5 100644 --- a/docsite/docs/operations/backups.md +++ b/docsite/docs/operations/backups.md @@ -9,12 +9,12 @@ either one restores to something unusable. ## What holds what -| Store | Contents | Back up | -| --- | --- | --- | -| MongoDB | Everything durable — servers, keys, assignments, workflows, runs, monitors, incidents, secrets, settings, audit | **Yes** | -| Redis | Sessions only | No. Losing it signs everyone out and nothing else | -| `./data` bind mount | Workflow run logs | Optional | -| `KEY_ENCRYPTION_KEY` | Not stored anywhere by the app | **Yes, separately** | +| Store | Contents | Back up | +| -------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| MongoDB | Everything durable servers, keys, assignments, workflows, runs, monitors, incidents, secrets, settings, audit | **Yes** | +| Redis | Sessions only | No. Losing it signs everyone out and nothing else | +| `./data` bind mount | Workflow run logs | Optional | +| `KEY_ENCRYPTION_KEY` | Not stored anywhere by the app | **Yes, separately** | :::danger The database alone is not a backup Private keys, vault secrets, OIDC client secrets and console credentials are @@ -50,7 +50,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 it holds the encryption key. ## Run logs @@ -69,17 +69,17 @@ 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. ## A workable schedule -| What | When | -| --- | --- | -| MongoDB dump | Nightly, retained per your policy | -| Environment file | On change, held in a password manager or secret store | -| Restore rehearsal | Occasionally, into a throwaway host | +| What | When | +| ----------------- | ----------------------------------------------------- | +| MongoDB dump | Nightly, retained per your policy | +| Environment file | On change, held in a password manager or secret store | +| Restore rehearsal | Occasionally, into a throwaway host | The rehearsal is the part that gets skipped and the part that finds the problems. diff --git a/docsite/docs/operations/ci-cd.md b/docsite/docs/operations/ci-cd.md deleted file mode 100644 index 21f7bbf..0000000 --- a/docsite/docs/operations/ci-cd.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -id: ci-cd -title: CI/CD -sidebar_label: CI/CD ---- - -Two Gitea Actions workflows: one releases agents, one builds images. - -## Agent releases - -Triggered by an `agent/v*` tag. - -```bash -git tag agent/v1.0.0 && git push origin agent/v1.0.0 -``` - -Builds `linux/amd64`, `linux/arm64` and `windows/amd64`, writes `checksums.txt` -and creates a Gitea release. A second job on Windows packages the WiX MSI. - -The install and update scripts read the newest `agent/v*` release from the Gitea -API, so tagging is what makes a new agent available to every install. - -## Image builds - -Triggered on every push to `main`. Builds and pushes seven images: `server`, -`web`, `site`, `sitesvc`, `admin`, `adminsite` and `docsite`. - -:::warning Despite the name, this workflow does not deploy -There is no SSH step. Rolling images out is a manual step on the host: - -```bash -cd /opt/vantage && \ - docker compose -f docker-compose.yml -f docker-compose.site.yml pull && \ - docker compose -f docker-compose.yml -f docker-compose.site.yml up -d --remove-orphans -``` -::: - -### Each image rebuilds only when its own inputs change - -A `git diff` against the previous head decides. That is why the checkout uses -`fetch-depth: 0` — a shallow clone has one commit and nothing to diff against. - -| Image | Rebuilds when | -| --- | --- | -| `server` | `server/`, `shared/`, `proto/`, `go.work` | -| `admin` | `admin/`, `shared/`, `go.work` | -| `sitesvc` | `sitesvc/`, `shared/`, `go.work` | -| `web` · `site` · `adminsite` · `docsite` | their own directory only | - -`shared/` fans out to all three Go images because each of their Dockerfiles -copies it from a root context. **If a fourth service ever imports `shared/`, it -must be added to that list or it will ship stale.** - -Everything rebuilds when there is no trustworthy base commit to diff against: a -manual `workflow_dispatch`, a new branch, or a force-push whose old head is -gone. Changing the workflow file itself also rebuilds everything, since a build -argument is baked into each image. - -### The gap: repository variables - -:::danger Editing a repository variable pushes no commit, so nothing rebuilds -Values like `ADMIN_API_URL`, `HQ_URL`, `ADMIN_ENV`, `PADDLE_ENV`, -`PADDLE_CLIENT_TOKEN`, `DOCS_URL` and `DOCS_BASE_URL` are baked into images at -build time. After editing one, run the workflow manually — that is what -`workflow_dispatch` is for. - -The symptom is a frontend that keeps using the old value with no error anywhere, -which is a long afternoon if you do not know about this. -::: - -The same applies to base images: a service nobody touches stops being rebuilt on -newer base layers. A periodic manual run covers it. - -## Secrets and variables - -| Name | Type | Purpose | -| --- | --- | --- | -| `RELEASE_TOKEN` | Secret | Gitea API token, `write:release` | -| `REGISTRY_USER` / `REGISTRY_PASSWORD` | Secret | Registry push credentials | -| `PADDLE_API_KEY` | Secret | Read by admin at runtime | -| `PADDLE_WEBHOOK_SECRET` | Secret | Webhook signature verification | -| `GITEA_HOST` / `DOCKER_HOST` | Variable | Hosts used in tags and URLs | -| `HQ_URL` | Variable | Baked into `web`; empty on self-hosted | -| `SITE_API_URL` / `SITE_CONTACT_EMAIL` | Variable | Baked into `site` | -| `ADMIN_API_URL` | Variable | Baked into `adminsite` **and** `site` | -| `ADMIN_ENV` | Variable | Environment badge in the portal | -| `PADDLE_ENV` | Variable | Baked into `adminsite`, read by `admin`. Must match on both sides | -| `PADDLE_CLIENT_TOKEN` | Variable | Browser Paddle token for checkout | -| `DOCS_URL` / `DOCS_BASE_URL` | Variable | Baked into `docsite` | - -Anything marked "browser-reachable" must be an origin a browser can actually -resolve — not an internal service name. Get it wrong and every request fails at -runtime with a not-connected panel rather than at build time. - -## The documentation site - -`docsite/` builds to static files and is served by nginx under `/docs` on the -marketing host, routed by its own proxy location. - -`DOCS_BASE_URL` has to agree with three things at once: that proxy location, the -directory the runtime image serves from, and the value baked into the build. When -they disagree the page loads and every stylesheet and script 404s. diff --git a/docsite/docs/operations/upgrading.md b/docsite/docs/operations/upgrading.md index 3369d94..9b61977 100644 --- a/docsite/docs/operations/upgrading.md +++ b/docsite/docs/operations/upgrading.md @@ -5,7 +5,12 @@ 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 +is nothing here for you to do. +::: ## Upgrade @@ -25,7 +30,7 @@ renamed or removed. 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. + library which is why those steps are read-only. Watch it: @@ -52,28 +57,15 @@ 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 they retry, and the poll loop is idempotent. - Workflow runs in progress lose their command stream. Steps already dispatched finish on the agent, but their results have nowhere to go. **Do not upgrade during a run.** - Sessions survive, because they live in Redis rather than in the server. -## Upgrading the hosted deployment - -Both Compose files, together: - -```bash -cd /opt/vantage -docker compose -f docker-compose.yml -f docker-compose.site.yml pull -docker compose -f docker-compose.yml -f docker-compose.site.yml up -d --remove-orphans -``` - -CI builds and pushes images but does **not** deploy them; rolling out is this -manual step. See [CI/CD](./ci-cd.md). - ## After upgrading - Confirm every service is `running`. - Confirm servers return to `active` within a couple of poll intervals. -- Open a page that touches encryption — a secret group — to confirm +- Open a page that touches encryption a secret group to confirm `KEY_ENCRYPTION_KEY` came through. diff --git a/docsite/docs/reference/agent-config.md b/docsite/docs/reference/agent-config.md index 7dc8d92..3e26bba 100644 --- a/docsite/docs/reference/agent-config.md +++ b/docsite/docs/reference/agent-config.md @@ -8,10 +8,10 @@ The agent reads no environment variables. Everything is in one YAML file. ## Location -| Platform | Path | -| --- | --- | -| Linux | `/etc/vantage/config.yaml` | -| Windows | `%ProgramData%\vantage\config.yaml` | +| Platform | Path | +| -------- | ----------------------------------- | +| Linux | `/etc/vantage/config.yaml` | +| Windows | `%ProgramData%\vantage\config.yaml` | Directory `0700`, file `0600`. The install script sets both. @@ -26,17 +26,17 @@ poll_interval: 30s tls: true ``` -| Field | Meaning | -| --- | --- | -| `server_url` | `host:port` of the gRPC endpoint. Comes from the server's `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 | -| `poll_interval` | How often `SyncKeys` runs. Default `30s` | -| `tls` | Whether to use TLS. Leave `true` | +| Field | Meaning | +| --------------- | --------------------------------------------------------------------- | +| `server_url` | `host:port` of the gRPC endpoint. Comes from the server's `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 | +| `poll_interval` | How often the agent polls for key state. Default `30s` | +| `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 +`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. That is why it is `0600` and the directory is `0700`. ::: @@ -45,21 +45,21 @@ it is `0600` and the directory is `0700`. ``` 1. Load the config -2. pre_reg_token present → Register() → save agent_token, +2. pre_reg_token present → register → save agent_token, clear pre_reg_token, reconnect -3. Start goroutines: command stream · hourly update check · - inventory · monitors -4. Enter the SyncKeys poll loop +3. Start: command stream · hourly update check · inventory · monitors +4. Enter the key poll loop ``` ## The poll loop ``` -1. SyncKeys(server_id, agent_token, agent_version) -2. Non-Linux hosts stop here — Windows agents register and heartbeat only +1. Ask the control plane for the desired key state, reporting the + agent version +2. Non-Linux hosts stop here Windows agents register and heartbeat only 3. Diff the desired keys against /root/.ssh/authorized_keys; unchanged → write nothing -4. Changed → write a temp file, os.Rename() over the real one, chmod 0600 +4. Changed → write a temp file, rename it over the real one, chmod 0600 ``` ## Service management @@ -107,5 +107,5 @@ rm -rf /etc/vantage systemctl daemon-reload ``` -Keys already written to `authorized_keys` remain on disk — the agent is no +Keys already written to `authorized_keys` remain on disk the agent is no longer running to remove them. Revoke first if that matters. diff --git a/docsite/docs/reference/environment-variables.md b/docsite/docs/reference/environment-variables.md index 9ac32a2..fe95e62 100644 --- a/docsite/docs/reference/environment-variables.md +++ b/docsite/docs/reference/environment-variables.md @@ -9,20 +9,16 @@ it is absent. ## Server -| Name | Required | Default | Notes | -| --- | --- | --- | --- | -| `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 only | -| `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 | -| `GUACD_ADDR` | no | `guacd:4822` | The [browser console](../vantage/browser-console.md) daemon | -| `APP_ROOT_LABEL` | no | `vantage` | The app root label for the host and session organisation guard | -| `VANTAGE_WORKFLOW_LOG_DIR` | no | — | Where workflow run logs are written | -| `VANTAGE_DEFAULT_STEPS_DIR` | no | baked into the image | Where the seeded step library is read from | -| `VANTAGE_DEPLOYMENT` | no | self-hosted | Set to `cloud` on a cloud instance. Governs whether a licence may be pasted | -| `VANTAGE_LICENSE` | no | — | A licence blob, used **only** when the instance has no stored one | -| `FREE_INSTANCE_REAP_AFTER` | no | empty | How long past a Free licence's expiry before the instance and all its data are deleted | +| Name | Required | Default | Notes | +| -------------------------- | --------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `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 only | +| `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 | +| `GUACD_ADDR` | no | `guacd:4822` | The [browser console](../vantage/browser-console.md) daemon | +| `APP_ROOT_LABEL` | no | `vantage` | The app root label for the host and session organisation guard | +| `VANTAGE_WORKFLOW_LOG_DIR` | no | | Where workflow run logs are written | :::danger `KEY_ENCRYPTION_KEY` has no recovery path It encrypts SSH private keys, vault secrets, OIDC client secrets and console @@ -30,11 +26,6 @@ credentials. Lose it and all of them are unreadable. Back it up separately from the database it protects. ::: -:::warning `FREE_INSTANCE_REAP_AFTER` empty means disabled, and empty is the default -That is the correct value for a self-hosted install, which must never reap. It -is set only on the hosted deployment. -::: - :::info A wrong `APP_ROOT_LABEL` fails quietly It does not error. It simply stops matching, and the host/session guard stops protecting anything. @@ -50,57 +41,3 @@ remap with Docker's port publishing instead. The agent reads no environment variables. Everything is in its [config file](./agent-config.md). - -## Hosted-only services - -These run only on the hosted deployment, from -`deploy/docker-compose.site.yml`. A self-hosted install runs none of them. - -### sitesvc — the public contact form - -| Name | Required | Notes | -| --- | --- | --- | -| `MONGO_URI` | yes | Must point at the control plane's database. Refuses to start against a database that has not run the instances migration. The database name is read from the URI path; a URI without one is refused rather than defaulted | -| `SMTP_HOST`, `SMTP_FROM` | yes | Without them the contact form answers `503` rather than silently dropping messages | -| `SMTP_TO` | no | Defaults to `support@hostxtra.co.uk` | -| `SMTP_PORT` | no | Defaults to `587`; `465` uses implicit TLS | -| `SMTP_USERNAME`, `SMTP_PASSWORD` | no | Auth is skipped when the username is empty | -| `SITE_ORIGIN` | yes in practice | Comma-separated allowed origins. Unset refuses every cross-origin browser request | -| `TRUST_PROXY` | no | Only `true` behind a proxy that overwrites `X-Forwarded-For`, or clients spoof past the rate limiter | - -### admin — the licensing authority - -| Name | Required | Notes | -| --- | --- | --- | -| `ADMIN_MONGO_URI` | yes | Admin's own database | -| `CONTROL_MONGO_URI` | yes | The control plane's database, for licence injection and user projection | -| `LICENSE_SIGNING_KEY` | yes | **The only service that ever holds this.** Never add it to the server, and never add admin to the self-hosted Compose file | -| `REDIS_ADDR`, `REDIS_USERNAME`, `REDIS_PASSWORD` | yes | Admin uses an external Redis; the base Compose file hardcodes `redis:6379` for the server, so these reach admin only | -| `ADMIN_ORIGIN` | yes | Comma-separated browser origins that call admin. See the warning below | -| `PADDLE_API_KEY` | yes | Boot-required | -| `PADDLE_WEBHOOK_SECRET` | yes | Boot-required. An unverified webhook endpoint is one anyone can issue licences through | -| `PADDLE_ENV` | yes | `sandbox` or `production`. Selects which catalogue price IDs are served, and must match the value baked into the portal build | -| `SMTP_*` | yes in practice | Account, licence and billing email | -| `PUBLIC_URL`, `APP_LOGIN_URL` | yes in practice | Used in links inside emails | -| `FREE_INSTANCE_REAP_AFTER` | yes | Must match the control plane's value. Admin only uses it to name the date in warning emails; the control plane performs the delete | - -:::warning A missing `ADMIN_ORIGIN` entry produces no error anywhere -The CORS layer simply omits the allow-origin header and still answers the -preflight with `204`. The browser blocks the request and **admin logs nothing at -all**. The symptom is a preflight failure on an endpoint that works perfectly -under `curl`. -::: - -## Build-time variables - -These are baked into frontend images at build time, not read at runtime. -Changing one requires rebuilding that image — and because editing a CI variable -pushes no commit, nothing rebuilds on its own. See [CI/CD](../operations/ci-cd.md). - -| Name | Baked into | -| --- | --- | -| `HQ_URL` | `web` | -| `SITE_API_URL`, `SITE_CONTACT_EMAIL` | `site` | -| `ADMIN_API_URL` | `adminsite` **and** `site` | -| `ADMIN_ENV`, `PADDLE_CLIENT_TOKEN`, `PADDLE_ENV` | `adminsite` | -| `DOCS_URL`, `DOCS_BASE_URL` | `docsite` | diff --git a/docsite/docs/reference/ports-and-networking.md b/docsite/docs/reference/ports-and-networking.md index 4580d59..983db52 100644 --- a/docsite/docs/reference/ports-and-networking.md +++ b/docsite/docs/reference/ports-and-networking.md @@ -6,26 +6,14 @@ sidebar_label: Ports and networking ## Control plane ports -| Port | Service | Who connects | Expose publicly | -| --- | --- | --- | --- | -| `3000` | web | Browsers, via your reverse proxy | Yes, behind TLS | -| `8080` | server REST | The web app | No | -| `9090` | server gRPC | Agents | **Yes** | -| `4822` | guacd | The server | No | -| `27017` | MongoDB | The server | No | -| `6379` | Redis | The server | No | - -## Hosted-only ports - -Only on the hosted deployment, from `deploy/docker-compose.site.yml`. - -| Port | Service | -| --- | --- | -| `3003` | marketing site | -| `3004` | HQ portal | -| `3005` | this documentation site | -| `8082` | sitesvc | -| `8083` | admin | +| Port | Service | Who connects | Expose publicly | +| ------- | ----------- | -------------------------------- | --------------- | +| `3000` | web | Browsers, via your reverse proxy | Yes, behind TLS | +| `8080` | server REST | The web app | No | +| `9090` | server gRPC | Agents | **Yes** | +| `4822` | guacd | The server | No | +| `27017` | MongoDB | The server | No | +| `6379` | Redis | The server | No | ## Direction of travel @@ -46,8 +34,8 @@ NAT is not an obstacle. The only requirement is that the machine can reach `GRPC_HOST`. **The console does not use the agent.** guacd connects directly to the target on -the protocol port. A machine reachable only by its agent — behind NAT, on a -private subnet — cannot be consoled, even though every other feature works. +the protocol port. A machine reachable only by its agent behind NAT, on a +private subnet cannot be consoled, even though every other feature works. ## What to open @@ -75,7 +63,7 @@ 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 +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. ## Reverse proxy notes @@ -91,7 +79,7 @@ register and then fail to hold the command stream. 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. +the config by hand the install script's only job is to do those two things. Licence verification is entirely local, so a licensed install works with no outbound access to HQ at all. diff --git a/docsite/docs/reference/rest-api.md b/docsite/docs/reference/rest-api.md index 32078c0..213ebbe 100644 --- a/docsite/docs/reference/rest-api.md +++ b/docsite/docs/reference/rest-api.md @@ -12,7 +12,7 @@ has no privileges it does not. Most endpoints take a session: an opaque 32-byte token in the `km_session` cookie, with the body in Redis for 24 hours. -One endpoint takes a bearer token instead — the External Secrets Operator read +One endpoint takes a bearer token instead the External Secrets Operator read path. ## Unauthenticated @@ -119,14 +119,14 @@ GET,PUT /org/oidc (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` | +| 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. +works while the current one is expired that is the way out of degraded mode. ## Multi-tenancy @@ -143,7 +143,7 @@ session and does not need that distinction. ## Admin API -The HQ service has its own API, its own database and its own session cookie -(`admin_session`) on port `8083`. It is documented in the -[Vantage HQ](../hq/accounts-and-signup.md) section rather than here; the two -services share no session and no authentication. +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. diff --git a/docsite/docs/reference/troubleshooting.md b/docsite/docs/reference/troubleshooting.md index fa190ac..acee941 100644 --- a/docsite/docs/reference/troubleshooting.md +++ b/docsite/docs/reference/troubleshooting.md @@ -8,7 +8,7 @@ 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 @@ -47,11 +47,11 @@ Work through it in this order: 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. -| Symptom | Cause | -| --- | --- | +| 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 | -| 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 | +| 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 | Remember the offline sweep runs every two minutes, so status is never instantaneous. @@ -67,7 +67,7 @@ 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. @@ -81,13 +81,13 @@ instantaneous. ## The console will not connect -| Symptom | Cause | -| --- | --- | -| 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 at "connecting" | The **control plane** cannot reach the target on the protocol port. The agent's reachability is irrelevant here | -| Fails only in production | The reverse proxy is not forwarding WebSocket upgrade headers | +| Symptom | Cause | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------- | +| 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 at "connecting" | The **control plane** cannot reach the target on the protocol port. The agent's reachability is irrelevant here | +| Fails only in production | The reverse proxy is not forwarding WebSocket upgrade headers | ## Monitors report down when the service is up @@ -98,7 +98,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 @@ -107,23 +107,19 @@ needs `host`, `port`, `from` and `to`, and Telegram needs both `token` and ## Licence problems -| 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 | -| Cannot enrol another server | The server allowance is reached. Raise it in HQ or remove one | +| 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 | +| Cannot enrol another server | The server allowance is reached. Raise it in HQ or remove one | ## HQ portal problems -**A request fails in the browser but works under `curl`.** The browser origin is -missing from `ADMIN_ORIGIN`. This produces no log line at all in admin — the -preflight is answered `204` without the allow-origin header, and the browser -blocks the real request. - -**A price or plan looks wrong after an edit.** Repository variables are baked -into images at build time and editing one pushes no commit, so nothing rebuilds. -Trigger the build manually. See [CI/CD](../operations/ci-cd.md). +The portal is a hosted service, so problems with it are ours to fix rather than +yours to configure. If a page fails to load, an action reports an error, or a +plan or price looks wrong after a change, contact support with your instance +UUID and roughly when it happened. ## Gathering information before asking for help @@ -133,5 +129,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 UUID from **Settings → Licence** it is the reference support works from. diff --git a/docsite/docs/vantage/audit-log.md b/docsite/docs/vantage/audit-log.md index 1b392ed..e4e753f 100644 --- a/docsite/docs/vantage/audit-log.md +++ b/docsite/docs/vantage/audit-log.md @@ -8,13 +8,13 @@ Every mutating API path writes an audit event. The log is at **Audit**. ## What an event carries -| Field | Meaning | -| --- | --- | +| Field | Meaning | +| ------ | -------------------------------------------------------- | | Action | A dotted name, e.g. `server.created`, `settings.updated` | -| Actor | Who did it | -| Target | The object acted on | -| Detail | A short human-readable note | -| Time | When | +| Actor | Who did it | +| Target | The object acted on | +| Detail | A short human-readable note | +| Time | When | ## What is recorded @@ -36,7 +36,7 @@ lookup. ## Retention -Audit events are not swept by the workflow log retention setting — that setting +Audit events are not swept by the workflow log retention setting that setting governs run logs only. Audit history stays until the instance does. :::warning It is a log, not a control diff --git a/docsite/docs/vantage/browser-console.md b/docsite/docs/vantage/browser-console.md index 33ed587..03b4ce4 100644 --- a/docsite/docs/vantage/browser-console.md +++ b/docsite/docs/vantage/browser-console.md @@ -7,7 +7,7 @@ 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. -Protocol handling is Apache Guacamole's — the control plane proxies a WebSocket +Protocol handling is Apache Guacamole's the control plane proxies a WebSocket to a **guacd** daemon and manages credentials around it. ## Requirements @@ -27,8 +27,8 @@ From a server's page, choose **Console**. Then: 1. The UI calls `POST /api/console/connect`, which mints a **one-time** session token. 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. +3. The server marks the token consumed atomically, so a second use cannot + race and proxies the connection to guacd. ## Credentials @@ -52,19 +52,19 @@ is worth one connection at most, and only until it is used. ## Session behaviour Closing the tab ends the session. There is no reconnect and no session -persistence — reopening mints a new token and a new connection. +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 +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. ## 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 at connecting | The control plane cannot reach the target on the protocol port | +| 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 at connecting | The control plane cannot reach the target on the protocol port | diff --git a/docsite/docs/vantage/monitors.md b/docsite/docs/vantage/monitors.md index e92f719..4b784ec 100644 --- a/docsite/docs/vantage/monitors.md +++ b/docsite/docs/vantage/monitors.md @@ -9,12 +9,12 @@ from, and a notification path when they stop being satisfied. ## Types -| Type | Checks | Options | -| --- | --- | --- | -| `http` | An HTTP(S) URL | method, expected status, keyword that must appear in the body, allow insecure TLS | -| `tcp` | A host and port accept a connection | — | -| `icmp` | A host answers ping | — | -| `tls` | A certificate is valid and not expiring | warn N days before expiry | +| Type | Checks | Options | +| ------ | --------------------------------------- | --------------------------------------------------------------------------------- | +| `http` | An HTTP(S) URL | method, expected status, keyword that must appear in the body, allow insecure TLS | +| `tcp` | A host and port accept a connection | | +| `icmp` | A host answers ping | | +| `tls` | A certificate is valid and not expiring | warn N days before expiry | An `http` monitor with a keyword is usually the one you want for an application: a 200 that returns an error page still fails the keyword. @@ -23,12 +23,12 @@ a 200 that returns an error page still fails the keyword. Every monitor has a **runner**: -| Runner | Meaning | -| --- | --- | -| `server` | The control plane's scheduler performs the check | +| Runner | Meaning | +| ----------- | -------------------------------------------------------------- | +| `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, +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 target network: a database on a private subnet, a service bound to localhost, a device on a management VLAN. @@ -41,8 +41,8 @@ different questions, and outages usually live in the gap. ## Interval, retries and state -- **Interval** — how often to check. -- **Retries** — how many consecutive failures are tolerated before the state +- **Interval** how often to check. +- **Retries** how many consecutive failures are tolerated before the state flips. A monitor sits in `pending` until its first result. Failures accumulate; once @@ -65,7 +65,7 @@ hours does not send a message per interval. The monitor detail page shows: -- **Uptime**, from hourly rollup records — checks performed, how many were up, +- **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. - **Incidents**, each with a start, a resolution and the cause recorded at the @@ -74,5 +74,5 @@ The monitor detail page shows: ## 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 +Prefer disabling for anything seasonal the uptime record is usually the part you wanted. diff --git a/docsite/docs/vantage/notification-channels.md b/docsite/docs/vantage/notification-channels.md index ad331e2..ffa3654 100644 --- a/docsite/docs/vantage/notification-channels.md +++ b/docsite/docs/vantage/notification-channels.md @@ -15,9 +15,9 @@ Manage them at **Settings → Notifications**. Posts JSON to a URL you choose. -| Setting | | -| --- | --- | -| `url` | Required | +| Setting | | +| ------- | -------- | +| `url` | Required | ```json { @@ -35,31 +35,31 @@ after 10 seconds. ### Discord -| Setting | | -| --- | --- | -| `url` | Discord webhook URL | +| Setting | | +| ------- | ------------------- | +| `url` | Discord webhook URL | Posts the alert as message content. ### Slack -| Setting | | -| --- | --- | -| `url` | Slack incoming webhook URL | +| Setting | | +| ------- | -------------------------- | +| `url` | Slack incoming webhook URL | ### Telegram -| Setting | | -| --- | --- | -| `token` | Bot token | +| Setting | | +| --------- | ----------- | +| `token` | Bot token | | `chat_id` | Target chat | ### SMTP -| Setting | | -| --- | --- | -| `host`, `port` | Required | -| `from`, `to` | Required | +| Setting | | +| ---------------------- | ---------------------------------------------------- | +| `host`, `port` | Required | +| `from`, `to` | Required | | `username`, `password` | Optional; auth is skipped when the username is empty | Port `465` uses implicit TLS; anything else uses STARTTLS. @@ -83,7 +83,7 @@ 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 +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. diff --git a/docsite/docs/vantage/secrets.md b/docsite/docs/vantage/secrets.md index 2353aeb..de1ccb3 100644 --- a/docsite/docs/vantage/secrets.md +++ b/docsite/docs/vantage/secrets.md @@ -10,7 +10,7 @@ External Secrets Operator. ## Groups and values -A **group** is a named bundle — `prod-db`, `registry`, `acme-api`. Inside it are +A **group** is a named bundle `prod-db`, `registry`, `acme-api`. Inside it are key/value pairs. Group by consumer, not by type. A group is the unit a workflow step references @@ -22,7 +22,7 @@ a group holding everything is over-sharing to every step that needs any of it. **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 +separate action on a separate endpoint, and it writes an audit event so looking at a secret is a recorded act. Deleting a single key and deleting the whole group are separate operations. @@ -42,7 +42,7 @@ library entry. :::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 +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. ::: diff --git a/docsite/docs/vantage/servers.md b/docsite/docs/vantage/servers.md index e541c02..7507bbb 100644 --- a/docsite/docs/vantage/servers.md +++ b/docsite/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, and everything else in Vantage keys, workflows, monitors, +consoles targets these records. ## Enrolling a server @@ -16,14 +16,14 @@ a one-liner to run as root on the target machine. ## Lifecycle -| Status | Meaning | -| --- | --- | +| Status | Meaning | +| --------- | --------------------------------------------------- | | `pending` | Enrolment created; the agent has not registered yet | -| `active` | The agent registered and is syncing | -| `offline` | Last-seen passed the threshold | +| `active` | The agent registered and is syncing | +| `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 +takes a little while to be marked as such. That delay is intentional a single missed poll is not an outage. ## The server detail page @@ -37,9 +37,9 @@ Which SSH keys are assigned to this machine, and their state. See Agents report: -| Data | Refreshed | -| --- | --- | -| CPU, memory, swap, load | every 30 seconds | +| Data | Refreshed | +| ---------------------------------------- | ---------------- | +| CPU, memory, swap, load | every 30 seconds | | Partitions, kernel, full static snapshot | every 15 minutes | The two carry separate timestamps, so a stale static snapshot beside fresh @@ -50,15 +50,15 @@ 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 +- **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 +- **Update agent** pushes `UpdateAgentCmd` with a target version; the agent downloads the release, verifies it and replaces itself. 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 +build it as a [workflow](./workflows.md) instead that is what workflows exist for. ::: @@ -69,7 +69,7 @@ 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 +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. @@ -94,5 +94,5 @@ no longer running to remove them. Revoke and let the agent apply the change 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 +to read a token back out of the control plane if one is lost, re-enrol the machine. diff --git a/docsite/docs/vantage/settings.md b/docsite/docs/vantage/settings.md index 05fe2ab..5a95163 100644 --- a/docsite/docs/vantage/settings.md +++ b/docsite/docs/vantage/settings.md @@ -11,7 +11,7 @@ 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 +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. ::: @@ -22,10 +22,10 @@ distinguish from Settings. The old path still redirects. Add, remove and re-role the people who can sign in. -| Role | Can | -| --- | --- | -| `owner` | Everything | -| `admin` | Everything except owner-only settings | +| Role | Can | +| -------- | ---------------------------------------------------- | +| `owner` | Everything | +| `admin` | Everything except owner-only settings | | `member` | Servers, keys, workflows, monitors, secrets, console | Local members authenticate with email and a bcrypt-hashed password. @@ -37,7 +37,7 @@ read-only rows with a link 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 +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). ::: @@ -46,10 +46,10 @@ password hash. Manage them from [People and roles](../hq/people-and-roles.md). Configured per organisation: -| Field | | -| --- | --- | -| Issuer | Your provider's issuer URL | -| Client ID | | +| Field | | +| ------------- | ---------------------------- | +| Issuer | Your provider's issuer URL | +| Client ID | | | Client secret | Stored AES-256-GCM encrypted | Sign-in then goes `/auth/oidc/start` → your provider → `/auth/oidc/callback`. @@ -60,7 +60,7 @@ misconfigured or unreachable, a local account is the way back in. ## Monitoring - **Alert defaults** for monitors. -- **Notification channels** — their own page. See +- **Notification channels** their own page. See [Notification channels](./notification-channels.md). ## Integrations @@ -69,11 +69,11 @@ misconfigured or unreachable, a local account is the way back in. How long run logs are kept. -| Value | Meaning | -| --- | --- | -| unset | 30 days | +| Value | Meaning | +| -------- | -------------- | +| unset | 30 days | | a number | that many days | -| `0` | forever | +| `0` | forever | ### ESO read token @@ -87,7 +87,7 @@ once, stored as a SHA-256 hash, rotatable. See features and expiry. On **self-hosted**, paste a licence here. This works even while the current -licence is expired — that is the way out of degraded mode. +licence is expired that is the way out of degraded 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 @@ -108,6 +108,6 @@ 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 +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. ::: diff --git a/docsite/docs/vantage/ssh-keys.md b/docsite/docs/vantage/ssh-keys.md index 0584b1e..ccf8705 100644 --- a/docsite/docs/vantage/ssh-keys.md +++ b/docsite/docs/vantage/ssh-keys.md @@ -28,7 +28,7 @@ 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 +`has_passphrase` never the material. Retrieving a stored private key is its own endpoint and its own audit event. :::tip Why store a private key at all @@ -38,7 +38,7 @@ 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 +Assign a key to one or more servers. Within one poll interval 30 seconds the agent picks up the change. ## Revoking @@ -58,7 +58,7 @@ until it ends. Kill sessions on the machine if that matters. Each poll: -1. `SyncKeys` returns the desired set of public keys for that server. +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. @@ -77,5 +77,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 +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. diff --git a/docsite/docs/vantage/workflows.md b/docsite/docs/vantage/workflows.md index ed0c205..a7a3a01 100644 --- a/docsite/docs/vantage/workflows.md +++ b/docsite/docs/vantage/workflows.md @@ -13,14 +13,14 @@ back live. 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 | +| 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 | ### Passing values between steps @@ -59,7 +59,7 @@ 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. +restart refusing is the honest answer. 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 @@ -67,7 +67,7 @@ shared library permanently, a file has to be committed to the repository and the server image rebuilt. ::: -The UI mirrors this — the step modal opens read-only and Delete is hidden — but +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 @@ -80,11 +80,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` | 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 | ### Per-step overrides @@ -94,9 +94,8 @@ scoped to that workflow. ## Running -**Run** snapshots the resolved steps into the run record and dispatches -`RunStepCmd` to each target's agent over the command stream — no waiting for the -next poll. +**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. :::info Runs freeze their steps The snapshot is why editing a step tomorrow never rewrites what happened today. @@ -119,11 +118,11 @@ nothing further is dispatched. Run logs are swept on a schedule set by `workflow_log_retention_days` in Settings: -| Value | Meaning | -| --- | --- | -| unset | 30 days | +| Value | Meaning | +| -------- | -------------- | +| unset | 30 days | | a number | that many days | -| `0` | keep forever | +| `0` | keep forever | ## Import and export diff --git a/docsite/sidebars.ts b/docsite/sidebars.ts index 6413e36..587d126 100644 --- a/docsite/sidebars.ts +++ b/docsite/sidebars.ts @@ -52,7 +52,6 @@ const sidebars: SidebarsConfig = { items: [ "reference/environment-variables", "reference/rest-api", - "reference/grpc-api", "reference/agent-config", "reference/ports-and-networking", "reference/troubleshooting", diff --git a/web/next.config.ts b/web/next.config.ts index 23cbd01..8c42919 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -4,19 +4,10 @@ import type { NextConfig } from "next"; // process, never in the browser, so this never needed the NEXT_PUBLIC_ prefix // that pins a value into the image at build time. NEXT_PUBLIC_API_URL is still // honoured so an existing deployment passing it keeps working. -const apiUrl = - process.env.API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8080"; +const apiUrl = process.env.API_URL ?? process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8080"; const nextConfig: NextConfig = { output: "standalone", - async redirects() { - return [ - // Members and SSO moved onto /settings. Permanent, because the old - // page is gone rather than temporarily unavailable — but it costs - // nothing to keep an old bookmark or a linked support reply working. - { source: "/settings/instance", destination: "/settings", permanent: true }, - ]; - }, async rewrites() { return [ {