Documentation · Installation

The full server setup.

The quick start gets it running on a laptop. This is what changes when it needs to survive a reboot and face the internet.


Requirements

What the server needs.

Node

A current LTS release. The agent build and the WebSocket process both run on it.

PostgreSQL

One database. Keep it on localhost, or behind a private network.

A reverse proxy

Apache, Nginx or Caddy — anything that can forward a WebSocket upgrade. This is the step people get wrong.

A process manager

PM2 is what the repository ships configuration for, running the web app and the WebSocket process as two apps.


Environment

Two required, the rest optional.

Required

DATABASE_URL

PostgreSQL connection string.

JWT_SECRET

Session signing secret. Unique per deployment — a session minted by one deployment must not be valid on another.

Optional

NEXT_PUBLIC_APP_URL

Public URL of the web app.

NEXT_PUBLIC_WS_URL

Public WebSocket URL, usually the app URL with a /ws prefix.

PORT

Web app port. Defaults to 50051.

WS_PORT

WebSocket server port. Defaults to 50052.

WS_HOST

Bind address for the WebSocket server. Leave it on 127.0.0.1 unless it runs on a different host — and firewall it if you change it.

WS_INTERNAL_TOKEN

Token for the app-to-WebSocket relay. Derived from JWT_SECRET when unset, identically on both sides.

SMTP_HOST / PORT / USER / PASS

Outbound mail for PIN resets and share invitations.

FROM_EMAIL / FROM_NAME

Sender identity on those emails.

VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT

Web push. Push is disabled rather than broken when unset — but use the same pair in development and production, or existing subscriptions stop working.


Reverse proxy

Two upstreams, one of them a WebSocket.

The web app and the WebSocket server are separate processes on separate ports. Route / to the app and /ws/ to the WebSocket server, and make sure the Upgrade and Connection headers survive the hop. Terminals connecting and then doing nothing is almost always this.

nginx
location / { proxy_pass http://127.0.0.1:50051; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } location /ws/ { proxy_pass http://127.0.0.1:50052; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; }
apache
RewriteEngine On RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule ^/ws/(.*) ws://127.0.0.1:50052/ws/$1 [P,L] ProxyPass /ws/ http://127.0.0.1:50052/ws/ ProxyPassReverse /ws/ http://127.0.0.1:50052/ws/ ProxyPass / http://127.0.0.1:50051/ ProxyPassReverse / http://127.0.0.1:50051/

Terminate TLS at the proxy. Sessions and terminal traffic are not safe over plain HTTP.


Running it

Build, then start both processes.

$ npm run build
$ npm run pm2:start
Starts the web app and the WebSocket process, then saves the PM2 process list

On a server that shares its database with another environment, use the migration scripts rather than db:push — the latter diffs the schema and will drop what it does not recognise.


Security checklist

Before you point a real machine at it.

  • JWT_SECRET is strong and unique to this deployment.
  • TLS terminates in front of the app, and HTTP redirects to HTTPS.
  • The WebSocket server is on 127.0.0.1, or firewalled if it is not.
  • PostgreSQL is not listening on a public interface.
  • Every account has two-factor enrolled — the API enforces it, but check nobody is stuck half-enrolled.
  • You know what is currently tunnelled. Anything tunnelled is reachable by anyone with the URL.