Add deployment guide and pm2 process file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
|||||||
|
# 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 "Can't reach the server" toasts, or games feel laggy | Websocket upgrade headers missing on the central server's HTTPS proxy. |
|
||||||
|
| 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.
|
||||||
@@ -24,3 +24,6 @@ Co-op mystery games for couples. Two players, different clues, every lock needs
|
|||||||
## Adding a new game later
|
## Adding a new game later
|
||||||
Copy a YAML in `content/games/`, change the `id` to match the filename, write your chapters,
|
Copy a YAML in `content/games/`, change the `id` to match the filename, write your chapters,
|
||||||
and run `npm run validate-content`. No code changes needed for new story games.
|
and run `npm run validate-content`. No code changes needed for new story games.
|
||||||
|
|
||||||
|
## Deploying
|
||||||
|
See [DEPLOYMENT.md](DEPLOYMENT.md) for running it under pm2 and publishing it through frp.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// pm2 process file. See DEPLOYMENT.md.
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'two-keys',
|
||||||
|
script: 'server/dist/index.js',
|
||||||
|
cwd: __dirname,
|
||||||
|
// One instance only: rooms live in this process's memory.
|
||||||
|
instances: 1,
|
||||||
|
exec_mode: 'fork',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3010,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user