source: Klonkt/deploy/DEPLOY.md@ 9b36f45

main
Last change on this file since 9b36f45 was b5bae24, checked in by Robin Genis <roboburr@…>, 3 months ago

reconcile: commit uncommitted live /srv state (playback fix + prefetch, SF favicon, smooth scroll, scripts)

The live working tree /srv/prutfolio was ahead of the bare repo with direct
server edits that had never been committed back. The deploy hook does
checkout -f main, so the next deploy would have reverted the 3 modified
tracked files to f33db01 and lost this work:

  • audio-player.js: robust playback — retry+backoff on network hiccups (no longer skipping immediately) + next-track prefetch (downloads the next track while the current one plays -> ended->next swaps instantly, covering the autoplay lapse). Fix for the sometimes-next-doesn't-play bug.
  • server.js: favicon mark p -> SF (SoundFabrics rebrand). shell.ejs: audio-player.js cache buster ?v=5 -> ?v=6 + favicon ?v=sf.
  • Plus previously untracked project files committed: lenis.min.js + smooth-scroll.js (not yet wired), scripts/ (v9 import/migration), deploy/ docs (DEPLOY.md/backup.sh/nginx/verify.ps1), .well-known/assetlinks.json. audio-player.js.bak.20260614 deliberately NOT committed.

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 5.9 KB
Line 
1# PrutCMS v10 — Deployment Guide
2
3End-to-end production install on a fresh Ubuntu 22.04 / Debian 12 VPS
4(TransIP, Hetzner, etc.). Assumes a non-root user with sudo.
5
6---
7
8## 0. Prerequisites
9
10- VPS with Ubuntu 22.04 LTS or Debian 12.
11- DNS A/AAAA records for your domain pointing at the server's IP. Wait for
12 propagation (`dig +short YOUR-DOMAIN` should return the right IP) before
13 running certbot.
14- SSH access as a non-root user (e.g. `robin`).
15
16---
17
18## 1. System packages
19
20```bash
21sudo apt update && sudo apt upgrade -y
22sudo apt install -y curl ca-certificates git nginx ufw sqlite3
23```
24
25Install Node.js 20 LTS via NodeSource (don't use the OS-default — it's old):
26
27```bash
28curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
29sudo apt install -y nodejs
30node --version # should print v20.x.x
31```
32
33Install PM2 globally:
34
35```bash
36sudo npm install -g pm2
37```
38
39---
40
41## 2. Firewall
42
43```bash
44sudo ufw allow OpenSSH
45sudo ufw allow 'Nginx Full' # opens 80 + 443
46sudo ufw enable
47```
48
49---
50
51## 3. App user + project clone
52
53```bash
54# As root or via sudo, create a deploy user if you don't already have one
55# (skip if you're already on a non-root user)
56
57# As your user:
58mkdir -p ~/prutcms
59cd ~/prutcms
60# Clone or rsync your code here. e.g. via git:
61# git clone <your-repo> .
62
63npm ci --omit=dev
64```
65
66`ensureLocalHtmx()` in `server.js` will copy the bundled HTMX from
67`node_modules/htmx.org/dist/htmx.min.js` to `src/assets/js/htmx.min.js` on
68first boot. You don't have to do that step manually.
69
70---
71
72## 4. Environment variables
73
74Create `.env` in the project root:
75
76```ini
77NODE_ENV=production
78PORT=3000
79
80# 32+ random hex chars — required, the app refuses to boot without it.
81# Generate: openssl rand -hex 32
82SESSION_SECRET=<paste-strong-random-string>
83
84# Optional: pin the audio HMAC secret instead of letting the app generate one
85# in storage/.audio-secret. Generate the same way as SESSION_SECRET.
86# AUDIO_SECRET=<paste-different-random-string>
87
88# Optional: override storage paths
89# DATABASE_PATH=/home/robin/prutcms/storage/database.sqlite
90# AUDIO_PATH=/home/robin/prutcms/storage/audio
91# AVATAR_PATH=/home/robin/prutcms/storage/media/avatars
92# COVER_PATH=/home/robin/prutcms/storage/media/audio-covers
93# MEDIA_PATH=/home/robin/prutcms/storage/media
94```
95
96`chmod 600 .env` — keep it readable only by your user.
97
98---
99
100## 5. First boot
101
102```bash
103npm run migrate # creates storage/database.sqlite + tables
104```
105
106Then start with PM2:
107
108```bash
109pm2 start ecosystem.config.cjs --env production
110pm2 save
111pm2 startup # follow the printed command to make PM2 survive reboots
112```
113
114Check it's up:
115
116```bash
117curl -I http://127.0.0.1:3000/ # expect 200 / 302
118pm2 logs prutcms # live logs; Ctrl-C to detach
119```
120
121Register your first user (becomes god) by visiting
122`http://YOUR-SERVER-IP:3000/auth/register` BEFORE you point nginx at it
123(or just wait until SSL is up — registration works the same).
124
125---
126
127## 6. nginx + SSL
128
129```bash
130sudo cp deploy/nginx.conf.example /etc/nginx/sites-available/prutcms
131sudo $EDITOR /etc/nginx/sites-available/prutcms # replace <YOUR-DOMAIN>
132sudo ln -s /etc/nginx/sites-available/prutcms /etc/nginx/sites-enabled/
133sudo rm -f /etc/nginx/sites-enabled/default
134sudo nginx -t
135sudo systemctl reload nginx
136```
137
138Install certbot and provision certs:
139
140```bash
141sudo apt install -y certbot python3-certbot-nginx
142sudo mkdir -p /var/www/letsencrypt
143sudo certbot --nginx -d YOUR-DOMAIN -d www.YOUR-DOMAIN
144```
145
146Certbot edits the nginx config in place to wire in the certificate paths.
147Renewals run automatically via the certbot systemd timer; verify with:
148
149```bash
150sudo systemctl status certbot.timer
151```
152
153Now visit `https://YOUR-DOMAIN/`. You should see your site over HTTPS, with
154HSTS active and Prutter WebSocket working (browser dev-tools → Network →
155filter "WS" → see the `wss://YOUR-DOMAIN/ws/prutter` connection).
156
157---
158
159## 7. Backups
160
161```bash
162chmod +x deploy/backup.sh
163mkdir -p ~/backups/prutcms ~/prutcms/logs
164
165# Test it once
166./deploy/backup.sh
167
168# Schedule nightly at 03:00
169( crontab -l 2>/dev/null ; \
170 echo "0 3 * * * /home/$USER/prutcms/deploy/backup.sh >> /home/$USER/prutcms/logs/backup.log 2>&1" \
171) | crontab -
172
173crontab -l # verify
174```
175
176Restore is a tar -xzf into a clean directory + `npm ci` + start.
177
178---
179
180## 8. Updating
181
182```bash
183cd ~/prutcms
184git pull
185npm ci --omit=dev
186pm2 reload prutcms # zero-downtime within fork mode
187```
188
189The DB schema migrates automatically on boot (`ensureColumn` adds new columns
190idempotently). For destructive changes you'd need to write an explicit
191migration — not yet needed.
192
193---
194
195## 9. Multi-tenant setup
196
197After your first user/site is created (auto on first registration), use
198`/admin/sites` to create more sites. Each site gets its own URL prefix
199(`/sites/<slug>/`) and its own PWA scope, so installing the PWA from one
200site won't navigate into another.
201
202---
203
204## 10. Troubleshooting
205
206| Symptom | Check |
207|---|---|
208| `❌ FATAL: SESSION_SECRET is required` | `.env` missing or unreadable. `pm2 stop prutcms && pm2 start ecosystem.config.cjs --env production`. |
209| `❌ FATAL: SESSION_SECRET too weak for production` | Make it 32+ chars: `openssl rand -hex 32`. |
210| WS disconnects every minute | Check nginx `proxy_read_timeout` is ≥ 90s in `/ws/` block. |
211| Audio plays but seek stutters | nginx must have `proxy_buffering off` on `/audio/stream/`. |
212| 502 from nginx | `pm2 list` — is the app up? `pm2 logs prutcms --lines 100`. |
213| HTMX 404s on `/assets/js/htmx.min.js` | `ls node_modules/htmx.org/dist/htmx.min.js` — if missing, `npm install htmx.org`. The boot-copy step needs the package. |
214
215---
216
217## 11. What's NOT included
218
219- Email sending (password reset prints the URL to the console / page in
220 non-production). Hook up an SMTP or transactional service when needed.
221- Cluster mode / horizontal scaling. Single fork only — see comments in
222 `ecosystem.config.cjs` for what would have to change first.
223- Off-site backup replication. The local rotation keeps 14 days; copy the
224 tar.gz files off-server with rsync/restic/whatever you prefer.
Note: See TracBrowser for help on using the repository browser.