# Deploying Two Keys How to run the site under pm2 on an app server and publish it at `https://two-keys.crosscode.co.za` through frp. ``` browser ──https──▶ central server (frps, TLS) ──frp tunnel──▶ app server (frpc) ──▶ 127.0.0.1:3010 (pm2: two-keys) ``` ## One port, one domain Two Keys is a **single Node process**. The same port serves the web pages, the `/api` routes and the websocket (`/socket.io`), and the browser connects back to whatever address it loaded the page from. So only `two-keys.crosscode.co.za` is needed. `two-keys-server.crosscode.co.za` is **not required**. There is no separate backend port to expose, and the client has no setting for a different server address, so pointing a second domain at it would change nothing. The examples below use these values. Change them to suit: | Value | Used here | |-------|-----------| | Install folder | `/var/www/two-keys` | | Local port | `3010` (set in `ecosystem.config.cjs`) | | pm2 app name | `two-keys` | | Public domain | `two-keys.crosscode.co.za` | ## 1. Prerequisites (app server) - Node.js 20 or newer (`node -v`) - git - pm2: `sudo npm install -g pm2` - frpc, already connected to your central frps ## 2. Create the folder and give your user ownership Run these as your normal login user, not as root. `$USER` is the user you are logged in as. ```bash sudo mkdir -p /var/www/two-keys sudo chown -R "$USER":"$USER" /var/www/two-keys ``` Everything after this runs **without** `sudo`. If you ever run `git`, `npm` or `pm2` in this folder with `sudo`, files end up owned by root and later builds fail with `EACCES`. Fix it by running the `chown` line again. The app writes its room snapshots to `server/data/rooms.json`, so the pm2 user must own the folder. ## 3. Get the code and build it ```bash git clone https://c-git.crosscode.co.za/joshua/two-keys.git /var/www/two-keys cd /var/www/two-keys git checkout v1.0.0 # or stay on main for the latest npm ci # do NOT add --omit=dev: the build needs the dev dependencies npm run validate-content # must print "All 8 games are valid." npm run build ``` ## 4. Start it with pm2 The port is set in `ecosystem.config.cjs` (`PORT: 3010`). Edit that file first if 3010 is taken. ```bash cd /var/www/two-keys pm2 start ecosystem.config.cjs pm2 save ``` Make pm2 come back after a reboot (once per server): ```bash pm2 startup # pm2 prints one "sudo env PATH=... pm2 startup ..." line. Copy and run that line, then: pm2 save ``` Check it locally: ```bash pm2 status two-keys curl http://127.0.0.1:3010/api/health pm2 logs two-keys --lines 20 # expect: "Two Keys server on http://localhost:3010 (8 games, ...)" ``` ## 5. Publish it through frp ### DNS Point `two-keys.crosscode.co.za` at the **central server** (the one running frps), the same way your other frp-hosted sites are set up. ### frpc on the app server Add one HTTP proxy to the frpc config, next to your existing ones. `frpc.toml` (frp 0.52 and newer): ```toml [[proxies]] name = "two-keys" type = "http" localIP = "127.0.0.1" localPort = 3010 customDomains = ["two-keys.crosscode.co.za"] ``` `frpc.ini` (older frp): ```ini [two-keys] type = http local_ip = 127.0.0.1 local_port = 3010 custom_domains = two-keys.crosscode.co.za ``` Then restart frpc, for example: ```bash sudo systemctl restart frpc ``` ### Central server Nothing Two Keys-specific is needed in frps itself: it routes by host name on its `vhostHTTPPort`, and websockets pass through an frp `http` proxy without extra settings. What does need attention is whatever terminates HTTPS in front of frps: - **Certificate:** add `two-keys.crosscode.co.za` to it, the same way as your other sites. - **Websocket upgrade:** if nginx (or similar) sits in front of frps, its server block for this domain must forward upgrades, or the game falls back to slow polling: ```nginx server { server_name two-keys.crosscode.co.za; # listen 443 ssl; plus your certificate lines location / { proxy_pass http://127.0.0.1:8080; # your frps vhostHTTPPort proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 3600s; # keep long games connected } } ``` The `Host` header must be passed through unchanged: frps uses it to pick the tunnel. ## 6. Check it from outside ```bash curl https://two-keys.crosscode.co.za/api/health ``` Then open `https://two-keys.crosscode.co.za` on two phones, start a game on one, and join with the room code on the other. If both lobbies update when a role is picked, the websocket is working end to end. ## Updating to a new version ```bash cd /var/www/two-keys git pull # or: git fetch --tags && git checkout v1.1.0 npm ci npm run validate-content npm run build pm2 restart two-keys ``` Games in progress survive the restart: rooms are saved to `server/data/rooms.json` on shutdown and restored on start. Players reconnect by themselves within a few seconds. ## Troubleshooting | Symptom | Likely cause | |---------|--------------| | `EACCES` during `npm ci`, build, or at start-up | Folder not owned by your user. Re-run the `chown` in step 2. | | `EADDRINUSE` in `pm2 logs` | Port 3010 is taken. Change `PORT` in `ecosystem.config.cjs` and `localPort` in frpc, then `pm2 restart two-keys --update-env` and restart frpc. | | Local `curl` works, domain gives frp's "page not found" | `customDomains` doesn't match the host name exactly, frpc wasn't restarted, or the front proxy rewrites `Host`. | | Site loads but games feel laggy, or `/socket.io` requests with `transport=websocket` fail in the browser's network tab | Websocket upgrade headers missing on the central server's HTTPS proxy. The game still works over polling. | | Site loads but no games listed | `npm run build` or `validate-content` failed. Check `pm2 logs two-keys`. | Do not run more than one instance (no pm2 cluster mode, no second copy behind a load balancer): room state is held in memory by the one process.