1- # Deploying JSS + plugins to a public host (pm2 + nginx )
1+ # Deploying JSS + plugins to a public host (pm2 + haproxy )
22
33The runbook for putting this repo on a box like ` melvin.me ` . The code needs
44** no build step** — deploying is ` git pull ` + ` pm2 restart ` . The care is all
55in three things: which plugins you expose, keeping the public Host consistent,
66and persisting the data root.
77
8+ ## Where this lands on melvin.me (as-recon'd 2026-07-12)
9+
10+ The box already runs, under pm2, a ** separate JSS pod per site** — ` melvin.me `
11+ (`jss start --port 5420 --root /home/ubuntu/www/melvin.me --single-user …
12+ --idp-issuer https://melvin.me/ --git --activitypub --nostr --webrtc --tunnel
13+ --pay` ) and ` losos.org` , both off the shared ` /home/ubuntu/jss` install, which
14+ is ** JSS 0.0.204** . haproxy on :80/:443 routes by hostname to each backend.
15+
16+ Two consequences:
17+
18+ 1 . ** The plugins need JSS ≥ 0.0.219** (serverInfo/reservePath/mountApp/
19+ api.plugins live there; the forge won't run on 0.0.204). Do ** not** upgrade
20+ the shared install to get them — that would move melvin.me * and* losos.org
21+ 15 patch versions at once. Instead this repo's ` npm ci ` brings its ** own**
22+ pinned 0.0.219 in its own ` node_modules ` , and runs as a ** new, isolated pm2
23+ app** (` forge ` , port 5480) with zero effect on the live 0.0.204 pods.
24+ 2 . ** Mount it as a subdomain** — ` forge.melvin.me ` → haproxy backend
25+ ` 127.0.0.1:5480 ` . A path split (` melvin.me/forge ` ) collides with the
26+ existing pod's own ` /api ` , ` /oauth ` , ` /.well-known/* ` . See
27+ ` haproxy-forge.cfg ` .
28+
29+ The box is otherwise ready: Ubuntu 24.04, node 22, ** git 2.43** (≥2.38 so forge
30+ merges work), ` git-http-backend ` present, pm2 6.
31+
32+ ## Prerequisites (check on the box first)
33+
834## Prerequisites (check on the box first)
935
1036``` bash
@@ -23,13 +49,13 @@ pm2 -v # process manager
2349## 1. Get the code
2450
2551``` bash
26- sudo mkdir -p /var/lib/jss && sudo chown ubuntu:ubuntu /var/lib/jss # PERSISTENT data root
52+ mkdir -p /home/ubuntu/forge-data # PERSISTENT data root (own to ubuntu)
2753cd /home/ubuntu
2854git clone https://github.com/JavaScriptSolidServer/plugins.git
29- cd plugins && git checkout gh-pages && npm ci
55+ cd plugins && git checkout gh-pages && npm ci # brings its OWN jss 0.0.219
3056```
3157
32- The data root (` /var/lib/jss ` ) holds pods, forge repos, issue indexes, and the
58+ The data root (` /home/ubuntu/forge-data ` ) holds pods, forge repos, issue indexes, and the
3359** Blocktrails trail private keys** . Keep it OUTSIDE the checkout (so ` git pull `
3460never touches it) and back it up. Losing the trail keys orphans your anchors.
3561
@@ -38,30 +64,36 @@ never touches it) and back it up. Losing the trail keys orphans your anchors.
3864JSS mints WebIDs against ` idpIssuer ` (` https://melvin.me ` ) and checks pod
3965ownership against the ** request Host** . Behind a TLS proxy this means two rules:
4066
41- 1 . ** nginx passes ` Host ` through unchanged** — ` proxy_set_header Host $host; `
42- (in the supplied conf). A rewritten Host = 403 on users' own writes.
67+ 1 . ** haproxy passes ` Host ` through unchanged** — in ` mode http ` it does by
68+ default; just don't add ` http-request set-header Host … ` . A rewritten Host
69+ = 403 on users' own writes.
43702 . ** The server's own loopback presents the public Host.** ` serve.prod.js `
4471 sets every plugin's ` loopbackUrl ` to ` PUBLIC_URL ` , so add a hosts entry so
4572 that self-call resolves locally instead of hair-pinning out to the internet:
4673
4774 ``` bash
48- echo " 127.0.0.1 melvin.me" | sudo tee -a /etc/hosts
75+ echo " 127.0.0.1 forge. melvin.me" | sudo tee -a /etc/hosts
4976 ```
5077
51- Now a plugin's loopback to ` https://melvin.me/... ` hits local nginx →
52- JSS, presenting ` Host: melvin.me ` , and pod-owning writes pass WAC.
78+ Now a plugin's loopback to ` https://forge.melvin.me/... ` hits local haproxy
79+ → the forge instance, presenting ` Host: forge.melvin.me ` , and pod-owning
80+ writes pass WAC.
81+
82+ ## 3. haproxy
5383
54- ## 3. nginx
84+ Add the backend + ACL from ` deploy/haproxy-forge.cfg ` to your existing
85+ ` /etc/haproxy/haproxy.cfg ` (subdomain ` forge.melvin.me ` → ` 127.0.0.1:5480 ` ),
86+ then:
5587
5688``` bash
57- sudo cp deploy/nginx-melvin.me.conf /etc/nginx/sites-available/melvin.me
58- sudo ln -sf /etc/nginx/sites-available/melvin.me /etc/nginx/sites-enabled/
59- # ensure this line exists in http{} (nginx.conf) for WebSocket upgrades:
60- # map $http_upgrade $connection_upgrade { default upgrade; '' close; }
61- sudo nginx -t && sudo systemctl reload nginx
89+ sudo haproxy -c -f /etc/haproxy/haproxy.cfg # validate
90+ sudo systemctl reload haproxy
6291```
6392
64- TLS cert via certbot if you don't have one: ` sudo certbot --nginx -d melvin.me ` .
93+ Needs a cert covering ` forge.melvin.me ` (a ` *.melvin.me ` wildcard, or add one
94+ to the crt-list) and a DNS record ` forge.melvin.me → this box ` .
95+ (` nginx-melvin.me.conf ` is kept in this dir as an alternative if you ever move
96+ off haproxy.)
6597
6698## 4. pm2
6799
@@ -76,14 +108,14 @@ pm2 logs jss # watch it come up
76108## 5. Verify
77109
78110``` bash
79- curl -s https://melvin.me/dashboard/ -o /dev/null -w ' %{http_code}\n' # 200
80- curl -s https://melvin.me/forge/ -o /dev/null -w ' %{http_code}\n' # 200
111+ curl -s https://forge. melvin.me/dashboard/ -o /dev/null -w ' %{http_code}\n' # 200
112+ curl -s https://forge. melvin.me/forge/ -o /dev/null -w ' %{http_code}\n' # 200
81113# register a pod, then confirm a pod-owning write round-trips (the Host check):
82- # open https://melvin.me/forge/ , sign in, open an issue — 200, not 403.
114+ # open https://forge. melvin.me/forge/ , sign in, open an issue — 200, not 403.
83115```
84116
85- Point Phanpy (or any Mastodon client) at ` https://melvin.me ` to exercise the
86- fediverse surface.
117+ Point Phanpy (or any Mastodon client) at ` https://forge. melvin.me ` to exercise
118+ the fediverse surface. (Your existing ` melvin.me ` pod is untouched.)
87119
88120## Updating
89121
0 commit comments