diff --git a/deploy/model-server/.env.example b/deploy/model-server/.env.example index 4f3a2714..a786bfa8 100644 --- a/deploy/model-server/.env.example +++ b/deploy/model-server/.env.example @@ -1,4 +1,5 @@ -# The public name of this machine. Point a DNS A record at it before `up`. +# Only for the bundled Caddy (`docker compose --profile caddy up -d`). With your own +# nginx, the name goes in its server block instead (README.md). MODEL_SERVER_DOMAIN=models.example.com # Shared with the gateway (AGENTFOX_MODEL_SERVER_TOKEN there too). diff --git a/deploy/model-server/README.md b/deploy/model-server/README.md index c954b91c..d38d4a47 100644 --- a/deploy/model-server/README.md +++ b/deploy/model-server/README.md @@ -38,15 +38,57 @@ Then, on the instance: # (Amazon Linux 2023: sudo dnf install -y docker && sudo systemctl enable --now docker) sudo usermod -aG docker $USER && newgrp docker # run docker without sudo git clone https://github.com/architsharm/agentfox && cd agentfox/deploy/model-server -cp .env.example .env # set MODEL_SERVER_DOMAIN and AGENTFOX_MODEL_SERVER_TOKEN +cp .env.example .env # set AGENTFOX_MODEL_SERVER_TOKEN docker compose pull && docker compose up -d docker compose logs -f models # ready once each model logs "warm in …" -curl -H "Authorization: Bearer $TOKEN" https://$MODEL_SERVER_DOMAIN/health +curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8090/health ``` Generate the token on the machine and paste it only into `.env` and the gateway's settings. Not into chat, tickets or shell history you share. +The server listens on `127.0.0.1:8090` only. Give it HTTPS and a name in one of two +ways. + +**Behind the machine's nginx** (it already serves other sites on 80/443). Pick a +name, point a DNS `A` record at the machine, then add a server block: + +```nginx +# /etc/nginx/sites-available/agentfox-models +upstream agentfox_models { + server 127.0.0.1:8090; + keepalive 16; # the gateway reuses connections; so should nginx +} + +server { + listen 80; + server_name models.example.com; + + location / { + proxy_pass http://agentfox_models; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_read_timeout 30s; + client_max_body_size 1m; + } +} +``` + +```bash +sudo ln -s /etc/nginx/sites-available/agentfox-models /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx +sudo certbot --nginx -d models.example.com # adds the 443 block and renews it +curl -H "Authorization: Bearer $TOKEN" https://models.example.com/health +``` + +**On a machine with nothing on 80/443**, use the bundled Caddy instead. Set +`MODEL_SERVER_DOMAIN` in `.env`, then run `docker compose --profile caddy up -d`. + +The name can be a subdomain of any domain you control. Only the gateway ever calls +it. A domain whose site is hosted elsewhere (on Vercel, say) still works: add an `A` +record for the subdomain in that domain's DNS, pointing at this machine. + `/health` lists each detector, whether it can run, and the device it runs on. Then point the gateway at it. On Vercel, these are project environment variables: diff --git a/deploy/model-server/docker-compose.yml b/deploy/model-server/docker-compose.yml index 3eb93991..f5a0dd6d 100644 --- a/deploy/model-server/docker-compose.yml +++ b/deploy/model-server/docker-compose.yml @@ -1,8 +1,14 @@ -# The model server on one machine (an EC2 instance, say), behind automatic HTTPS. +# The model server on one machine (an EC2 instance, say). # -# cp .env.example .env # set the domain and the token +# cp .env.example .env # set the token # docker compose up -d # +# It listens on 127.0.0.1:8090 only. Put HTTPS in front of it with: +# * the reverse proxy the machine already runs (nginx, say; README.md has the +# server block), or +# * on a machine with nothing on ports 80/443, the bundled Caddy, which gets a +# certificate on its own: docker compose --profile caddy up -d +# # Updating is a pull, and only when a model-side change ships: the gateway keeps # deploying from main as often as it likes without touching this machine. # @@ -23,8 +29,9 @@ services: AGENTFOX_MODEL_DEVICE: ${AGENTFOX_MODEL_DEVICE:-auto} # Weights are read from the image only, never fetched while serving. HF_HUB_OFFLINE: "1" - # Not published to the host: only Caddy reaches it. - expose: ["8090"] + # Loopback only: the host's reverse proxy (or the bundled Caddy) reaches it; the + # internet does not. + ports: ["127.0.0.1:8090:8090"] healthcheck: test: ["CMD", "python", "-c", "import os,urllib.request as u; u.urlopen(u.Request('http://127.0.0.1:8090/health', headers={'Authorization': 'Bearer ' + os.environ['AGENTFOX_MODEL_SERVER_TOKEN']}), timeout=5)"] interval: 30s @@ -34,12 +41,14 @@ services: start_period: 120s caddy: - # TLS from Let's Encrypt for MODEL_SERVER_DOMAIN, renewed automatically. The - # gateway sends the text it screens here, so it travels encrypted. + # Only with `--profile caddy`, for a machine with no reverse proxy of its own: it + # takes ports 80 and 443. TLS from Let's Encrypt for MODEL_SERVER_DOMAIN, renewed + # automatically. The gateway sends the text it screens here, so it travels encrypted. + profiles: ["caddy"] image: caddy:2.8 restart: unless-stopped ports: ["80:80", "443:443"] - command: ["caddy", "reverse-proxy", "--from", "${MODEL_SERVER_DOMAIN:?set MODEL_SERVER_DOMAIN in .env}", "--to", "models:8090"] + command: ["caddy", "reverse-proxy", "--from", "${MODEL_SERVER_DOMAIN:-set-MODEL_SERVER_DOMAIN-in-.env}", "--to", "models:8090"] volumes: - caddy-data:/data depends_on: [models]