Skip to content

Release to our nginx server

This page takes a checkout of this repository to a running release on our own server: the site, the docs and the embeddable elements served by nginx, and our own service behind it. It is for whoever does the release. You need this repository set up (make setup, make web-setup) and shell access to the server, which runs nginx and Python 3.12.

Where it is served

Everything that depends on where the release lives comes from one set of deploy settings. Copy deploy.env.example to deploy.env (ignored by git) and edit it, or set the same names in the environment, which wins:

Setting Default What it sets
GD_URL_PREFIX / the URL path the site is served under: /, or a sub-path such as /games/
GD_SERVER_NAME games.example.org the host name nginx answers for, and the host in the oEmbed files' URLs
GD_TLS_CERTIFICATE /etc/ssl/games.example.org/fullchain.pem the certificate chain on the server
GD_TLS_CERTIFICATE_KEY /etc/ssl/games.example.org/privkey.pem its private key on the server
GD_WEB_ROOT /srv/grounded-decision/current/web the directory holding dist/web on the server (a link switched per release; step 2)
GD_SERVICE_UPSTREAM 127.0.0.1:8700 our own service's address, HOST:PORT or unix:/path

make dist renders every nginx file from the templates in ops/nginx/ with these values, and refuses a value that isn't valid (a prefix that doesn't start and end with /, a relative path, an unknown setting). Nothing else needs them: the site and the docs link to each other and to their assets with relative URLs, and the browser reaches our service at ws/ under the site's own root, so the same dist/web works under any prefix. tests/test_deploy_settings.py serves a release with real nginx at / and under /games/.

1. Build everything locally

make check WEB=1   # every test, lint, type check and the strict docs build
make dist          # writes dist/web, dist/service and dist/nginx
make web-e2e-nginx # optional: the browser tests against dist/ through real nginx
Directory What it holds Where it goes on the server
dist/web/ the site, docs/, elements/, the Python engine files, replays, ambient a release directory, linked as GD_WEB_ROOT
dist/nginx/ the server block, the site's locations, the service proxy, generated headers /etc/nginx/grounded-decision/
dist/service/ our own service: wheels for an offline install, systemd unit, release.sh anywhere on the service machine, e.g. /tmp

dist/nginx/grounded-decision-headers*.conf are generated from the site's _headers files by tools/nginx_config.py, under the URL prefix. The end-to-end tests apply the same files, so nginx sends exactly the headers the tests checked. Don't edit anything in dist/nginx/; change the deploy settings, the templates or _headers, and rebuild.

2. Copy the static files and switch to them

With the default GD_WEB_ROOT (/srv/grounded-decision/current/web):

VERSION=$(date -u +%Y%m%dT%H%M%SZ)
rsync -a dist/web/ server:/srv/grounded-decision/releases/$VERSION/web/
ssh server "ln -sfn /srv/grounded-decision/releases/$VERSION /srv/grounded-decision/current.new \
  && mv -T /srv/grounded-decision/current.new /srv/grounded-decision/current"

The mv -T makes the switch atomic: a visitor sees either the old release or the new one, never a mix.

3. Install the nginx configuration (first time, or when it changed)

rsync -a dist/nginx/ server:/etc/nginx/grounded-decision/

Include it once from the http { } block of /etc/nginx/nginx.conf:

include grounded-decision/grounded-decision.conf;

and check and reload:

ssh server "sudo nginx -t && sudo nginx -s reload"

To serve the site inside an existing server { } block instead (say, under /games/ on a host that serves other things), include the pieces yourself: in http { }, map_hash_bucket_size 128; and map_hash_max_size 4096; first, then grounded-decision-http.conf and grounded-decision-headers-maps.conf; inside that server block, grounded-decision-site.conf.

4. Install or upgrade our own service

Copy dist/service/ to the service machine and run its release script. It installs the release offline into its own directory, switches to it, restarts the service and checks its health, and switches back by itself if the new release is unhealthy:

rsync -a dist/service/ server:/tmp/grounded-decision-service/
ssh server "sudo /tmp/grounded-decision-service/bin/release.sh upgrade"

The first install, the systemd unit, settings and logs are on Run our own service.

5. Check it

  • Open the site at https:// + GD_SERVER_NAME + GD_URL_PREFIX: the home page, a game's play page, docs/.
  • curl -I on that URL shows the Content-Security-Policy header.
  • On the server, curl http://127.0.0.1:8700/healthz answers with the service's version. Through nginx, healthz under the prefix answers only from the server itself.

Roll back

  • Static files: point current at the previous directory under /srv/grounded-decision/releases/ the same way as step 2.
  • Service: release.sh rollback.
  • nginx config: copy the previous dist/nginx/, then nginx -t && nginx -s reload.

What can go wrong

Symptom Cause Fix
make dist stops with a settings error a deploy setting isn't valid, or is misspelt fix deploy.env (or the environment) as the message says
Every page is 404 GD_URL_PREFIX doesn't match the URL in use set it to the path you serve from, make dist again, copy dist/nginx/
The live engine never starts; the console mentions WebAssembly nginx serves .wasm with the wrong type keep grounded-decision-site.conf included; it maps application/wasm
nginx -t fails on map_hash_bucket_size the directive is set twice, or after a map keep one copy, before the first map in the http block
Embedded elements on our other sites don't load that site's Content Security Policy add the entries listed on Embed the games and demos
api/ answers 502 the service isn't running systemctl status 'grounded-decision@*'; see Run our own service