@@ -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.
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user