diff --git a/docs/getting-started/self-hosted-install.md b/docs/getting-started/self-hosted-install.md index a6908ee..5ccf48a 100644 --- a/docs/getting-started/self-hosted-install.md +++ b/docs/getting-started/self-hosted-install.md @@ -13,8 +13,10 @@ You need: - A Linux host with **Docker** and the **Compose plugin**. - A DNS name pointing at that host. People use it for the web UI, and your agents use it too. -- A reverse proxy in front of Vantage that terminates TLS. It needs to handle - both the web UI and the agent port, `9090`, which speaks HTTP/2. +- A way to terminate TLS. The stack ships an nginx reverse proxy that routes + the web UI and API on plain HTTP; you can terminate TLS in that nginx (see + [step 4](#4-the-reverse-proxy)) or in a proxy you already run in front of it. + The agent port, `9090`, speaks HTTP/2. - Those two ports reachable: the web port from wherever your people are, and `9090` from every machine you intend to manage. - Outbound access from the control plane, and from each managed machine, to @@ -23,13 +25,17 @@ You need: The stack brings MongoDB, Redis and the console daemon with it, so there is no database to provide. -## 1. Get the Compose file +## 1. Get the Compose file and proxy config ```bash -mkdir -p /opt/vantage && cd /opt/vantage +mkdir -p /opt/vantage/nginx && cd /opt/vantage curl -fsSLO https://gitea.hostxtra.co.uk/vantage/vantage-app/raw/branch/main/deploy/docker/docker-compose.yml +curl -fsSL -o nginx/vantage.conf https://gitea.hostxtra.co.uk/vantage/vantage-app/raw/branch/main/deploy/docker/nginx/vantage.conf ``` +The Compose file mounts `./nginx/vantage.conf` into the `nginx` service, so it +must sit at that path next to `docker-compose.yml`. + ## 2. Write the environment file Create `/opt/vantage/.env`: @@ -44,6 +50,9 @@ KEY_ENCRYPTION_KEY= # The host serving agent downloads. GITEA_HOST=gitea.hostxtra.co.uk + +# Optional: host port for the bundled nginx proxy. Defaults to 80. +# NGINX_HTTP_PORT=80 ``` Generate the encryption key: @@ -82,7 +91,7 @@ docker compose up -d docker compose ps ``` -Five services start: `mongo`, `redis`, `guacd`, `server` and `web`. +Six services start: `mongo`, `redis`, `guacd`, `server`, `web` and `nginx`. Check the server got through startup: @@ -94,9 +103,9 @@ On first boot it prepares the database and loads the built-in workflow step library. If it stops during that, it will say why, and it is meant to stop rather than run in a half-prepared state. -## 4. Put a proxy in front +## 4. The reverse proxy -Terminate TLS at your reverse proxy and route **one hostname to two backends**: +The `nginx` service routes **one hostname to two backends**: | Path | Backend | | -------------------------------------------------------------------------------- | ------------- | @@ -105,7 +114,119 @@ Terminate TLS at your reverse proxy and route **one hostname to two backends**: Both rules are required. The web app forwards nothing to the API, so a proxy that sends the whole hostname to `web:3000` serves the interface and answers -`404` to every request it makes — starting with the login form. +`404` to every request it makes — starting with the login form. If you replace +the bundled nginx with your own proxy, it must route the same way. + +This is the shipped `nginx/vantage.conf`: + +```nginx +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream vantage_server { + server server:8080; + keepalive 16; +} + +upstream vantage_web { + server web:3000; + keepalive 16; +} + +server { + listen 80; + listen [::]:80; + server_name _; + + client_max_body_size 10m; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # REST API. The browser console is a WebSocket and workflow logs stream, + # so no buffering and a long read timeout. + location ^~ /api/ { + proxy_pass http://vantage_server; + proxy_buffering off; + proxy_read_timeout 1h; + proxy_send_timeout 1h; + } + + location ^~ /auth/ { + proxy_pass http://vantage_server; + } + + location ^~ /public/ { + proxy_pass http://vantage_server; + } + + # /install, /install.ps1, /update, /update.ps1 + location ~ ^/(install|update)(\.ps1)?$ { + proxy_pass http://vantage_server; + } + + location / { + proxy_pass http://vantage_web; + } +} +``` + +The `Upgrade`/`Connection` headers and `proxy_buffering off` are not optional: +without them the browser console cannot open its WebSocket and live workflow +logs arrive in bursts or not at all. + +### Terminating TLS in nginx + +The shipped config speaks plain HTTP, which is right when another proxy or load +balancer in front of it already terminates TLS. To terminate TLS in the bundled +nginx instead, mount your certificate and add a `443` server, redirecting `80` +to it. + +In `docker-compose.yml`, extend the `nginx` service: + +```yaml +nginx: + ports: + - 80:80 + - 443:443 + volumes: + - ./nginx/vantage.conf:/etc/nginx/conf.d/default.conf:ro + - ./certs:/etc/nginx/certs:ro +``` + +In `nginx/vantage.conf`, replace the `listen 80` lines of the existing server +with the TLS ones, and add a redirect server: + +```nginx +server { + listen 80; + listen [::]:80; + server_name vantage.example.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name vantage.example.com; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + + # ... the rest of the shipped server block, unchanged ... +} +``` + +### Agent traffic on 9090 Agents connect to port `9090`. Vantage does not terminate TLS itself, so put that port behind your proxy too, with a certificate valid for the name in @@ -113,6 +234,33 @@ that port behind your proxy too, with a certificate valid for the name in by default, and the symptom is agents that register once and then stop responding. +With the bundled nginx, remove `9090:9090` from the `server` service's `ports`, +add `9090:9090` to the `nginx` service, and add this server to +`nginx/vantage.conf`: + +```nginx +server { + listen 9090 ssl; + listen [::]:9090 ssl; + http2 on; + server_name vantage.example.com; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + + # Command streams are long-lived; do not time them out. + grpc_read_timeout 1h; + grpc_send_timeout 1h; + + location / { + grpc_pass grpc://server:9090; + } +} +``` + +`grpc://` is plain HTTP/2 (h2c) to the container, which is what the server +speaks. + For a private network where TLS is not required, you can instead set `tls: false` in each [agent's config](../reference/agent-config.md) and let agents reach the port directly. @@ -128,8 +276,9 @@ Continue with [First login](./first-login.md). | Check | Expected | | ---------------------------------------------- | ------------------------------- | -| `docker compose ps` | five services `running` | +| `docker compose ps` | six services `running` | | `curl -s localhost:8080/auth/bootstrap-status` | JSON saying bootstrap is needed | +| `curl -s localhost/auth/bootstrap-status` | the same JSON, through nginx | | `nc -z your-host 9090` | open | | `docker compose logs server` | no fatal errors |