Connect over polling first so the game still works when a proxy drops the websocket upgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
194 lines
6.2 KiB
Markdown
194 lines
6.2 KiB
Markdown
# 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.
|