Files
MP-Server/macropad-relay/DEPLOY.md
T
shadowdao b5e5bed7dd security(relay): minor hardening follow-ups from PR review
- Uniform auth responses remove the session-enumeration oracle: unknown
  session and wrong password now return an identical 401 at the API layer and
  an identical WS handshake/close; desktop-status and the 503 "not connected"
  signal are only exposed after successful auth.
- Session-count cap re-checked after bcrypt.hash so concurrent creates can't
  overshoot maxSessions.
- Relay web client reconnect backoff resets only after a successful auth
  response (an accept-then-close server no longer defeats the backoff).
- idGenerator comment corrected to match the rejection-sampling; drop unused
  recordFailure return value.
- DEPLOY.md: document ALLOWED_ORIGINS for reverse-proxy deployments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:13:33 -07:00

3.7 KiB

MacroPad Relay Server - Deployment Guide

Cloud Node Container Deployment

For AnHonestHost cloud-node-container deployment:

1. Build Locally

cd /home/jknapp/code/macropad/macropad-relay
npm install
npm run build   # required: the build is no longer triggered automatically on install

Note: the postinstall build hook was removed. Always run npm run build explicitly (locally or in CI) after npm install to produce dist/.

2. Prepare Deployment Package

The build outputs to dist/ with public files copied. Upload:

# Upload built files to your node container app directory
rsync -avz --exclude 'node_modules' --exclude 'src' --exclude '.git' \
  dist/ package.json public/ \
  user@YOUR_SERVER:/path/to/app/

3. On Server

The cloud-node-container will automatically:

  • Install dependencies from package.json
  • Start the app using PM2
  • Configure the process from package.json settings

4. Create Data Directory

mkdir -p /path/to/app/data

Directory Structure on Server

app/
├── index.js          # Main entry (compiled)
├── config.js
├── server.js
├── services/
├── handlers/
├── utils/
├── public/
│   ├── login.html
│   └── app.html
├── data/
│   └── sessions.json  # Created automatically
└── package.json

Update After Code Changes

# On local machine:
cd /home/jknapp/code/macropad/macropad-relay
npm run build

rsync -avz --exclude 'node_modules' --exclude 'src' --exclude '.git' --exclude 'data' \
  dist/ package.json public/ \
  user@YOUR_SERVER:/path/to/app/

# On server - restart via your container's control panel or:
pm2 restart macropad-relay

Environment Variables

Set these in your container configuration:

  • PORT - Server port (default: 3000)
  • DATA_DIR - Data storage path (default: ./data)
  • NODE_ENV - production or development
  • LOG_LEVEL - info, debug, error
  • ALLOWED_ORIGINS - Comma-separated list of allowed browser origins for the WebSocket Origin check (e.g. https://macropad.example.com). Set this to your public origin(s) when running behind a reverse proxy (see note below).

Reverse proxy note (ALLOWED_ORIGINS): When ALLOWED_ORIGINS is unset, the WebSocket upgrade Origin check falls back to comparing the browser's Origin host against the raw Host header seen by the app. Behind a reverse proxy the internal Host (e.g. localhost:3000) often differs from the public origin the browser sends (e.g. https://macropad.example.com), so legitimate browser WebSocket upgrades get rejected. Set ALLOWED_ORIGINS to your public origin(s) to fix this. (Forwarding the public host via proxy_set_header Host $host;, as in the Nginx example below, also helps, but setting ALLOWED_ORIGINS explicitly is the reliable fix.)

Test It Works

# Test health endpoint
curl http://localhost:3000/health

# Should return (counts only - no session ids are exposed):
# {"status":"ok","desktops":0,"webClients":0,"uptime":1.23}

Nginx/Reverse Proxy (for HTTPS)

location / {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # WebSocket timeout (24 hours)
    proxy_read_timeout 86400;
}

Troubleshooting

Check logs:

pm2 logs macropad-relay

Check sessions:

cat /path/to/app/data/sessions.json

Port in use:

lsof -i :3000