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
+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.