docs: Updated docs
Server Deploy / deploy (push) Failing after 1m57s

This commit is contained in:
2026-07-28 16:53:42 +01:00
parent 0424547dd4
commit 487de34a50
34 changed files with 369 additions and 577 deletions
@@ -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
@@ -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 | `<your-slug>.vantage.hostxtra.co.uk` | Your own hostname |
| Database and backups | Ours | Yours |
| Licence | Written for you when you buy or create the instance | Pasted in, or claimed from HQ |
| Team members | Granted from HQ; the instance holds a projection | Created in the instance itself |
| Free tier | Yes, one per account | Yes, one per account |
| Expired Free instance | Eventually deleted, after warning | Never deleted |
| | Cloud | Self-hosted |
| --------------------- | --------------------------------------------------- | ------------------------------ |
| Who runs it | We do | You do |
| Where you sign in | `<your-slug>.vantage.hostxtra.co.uk` | Your own hostname |
| Database and backups | Ours | Yours |
| Licence | Written for you when you buy or create the instance | Pasted in, or claimed from HQ |
| Team members | Granted from HQ; the instance holds a projection | Created in the instance itself |
| Free tier | Yes, one per account | Yes, one per account |
| Expired Free instance | Eventually deleted, after warning | Never deleted |
## Cloud
You create an instance from the HQ portal and it exists a few seconds later,
already licensed. People you grant access to get a real user inside that
instance see [People and roles](../hq/people-and-roles.md) but HQ owns their
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
+24 -24
View File
@@ -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.
+15 -15
View File
@@ -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=<id>&token=<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.
@@ -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.
@@ -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
+14 -14
View File
@@ -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
+3 -3
View File
@@ -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
+8 -8
View File
@@ -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.
+12 -13
View File
@@ -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
+18 -18
View File
@@ -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
+6 -6
View File
@@ -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
+4 -4
View File
@@ -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,
+8 -8
View File
@@ -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.
+9 -9
View File
@@ -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
+13 -13
View File
@@ -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.
-102
View File
@@ -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.
+9 -17
View File
@@ -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.
+21 -21
View File
@@ -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.
+10 -73
View File
@@ -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` |
+12 -24
View File
@@ -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.
+11 -11
View File
@@ -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.
+25 -29
View File
@@ -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.
+7 -7
View File
@@ -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
+11 -11
View File
@@ -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 |
+14 -14
View File
@@ -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.
+17 -17
View File
@@ -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.
+3 -3
View File
@@ -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.
:::
+15 -15
View File
@@ -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.
+17 -17
View File
@@ -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.
:::
+4 -4
View File
@@ -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.
+21 -22
View File
@@ -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
-1
View File
@@ -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",
+1 -10
View File
@@ -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 [
{