|
@@ -4,37 +4,63 @@ The app lives at **davidwindham.com/radio** — a long-running Node process that
|
|
|
Apache proxies to, same shape as `/chess` and `/ask`. It is *not* served from the
|
|
Apache proxies to, same shape as `/chess` and `/ask`. It is *not* served from the
|
|
|
web docroot.
|
|
web docroot.
|
|
|
|
|
|
|
|
-## The one thing that's different from chess
|
|
|
|
|
|
|
+Code is synced to the server by **FTP**, and the process is managed over SSH
|
|
|
|
|
+(pm2 or systemd), same as the other node apps.
|
|
|
|
|
|
|
|
-**This app has a build step.** Express serves `app/`, which is compiled from
|
|
|
|
|
-`src/` by esbuild and is **gitignored** — it is not in the repo. So production
|
|
|
|
|
-must build it, and building needs the devDependencies (esbuild, bootstrap,
|
|
|
|
|
-handlebars). That means a full `npm install`, not `npm ci --omit=dev`.
|
|
|
|
|
|
|
+## Build locally — this is the part that bites
|
|
|
|
|
|
|
|
-Every deploy is therefore: **pull → install → build → restart.**
|
|
|
|
|
|
|
+Express serves `app/`, which is compiled from `src/` by esbuild and is
|
|
|
|
|
+**gitignored**. FTP uploads files from your disk, so:
|
|
|
|
|
+
|
|
|
|
|
+> **Run `npm run build` locally before every upload, and make sure `app/` is
|
|
|
|
|
+> included in what you FTP up.**
|
|
|
|
|
+
|
|
|
|
|
+If you forget, the server keeps serving the *previous* `app/` (or none), and your
|
|
|
|
|
+changes silently don't appear — no error, just stale files. `src/` and the build
|
|
|
|
|
+tooling (esbuild, bootstrap, handlebars) never need to go to the server at all;
|
|
|
|
|
+they only exist to produce `app/` on your machine.
|
|
|
|
|
+
|
|
|
|
|
+## What the server actually needs
|
|
|
|
|
+
|
|
|
|
|
+Only four things, at runtime:
|
|
|
|
|
+
|
|
|
|
|
+| On the server | Where it comes from |
|
|
|
|
|
+| --- | --- |
|
|
|
|
|
+| `app/` | built locally, uploaded by FTP |
|
|
|
|
|
+| `app.js` | uploaded by FTP |
|
|
|
|
|
+| `package.json` (+ `package-lock.json`) | uploaded by FTP |
|
|
|
|
|
+| `node_modules/` (prod only — 5 packages) | `npm ci --omit=dev` on the server |
|
|
|
|
|
+
|
|
|
|
|
+Not needed on the server: `src/`, `build.mjs`, `esbuild`, `bootstrap`,
|
|
|
|
|
+`handlebars`. Those are build-time only.
|
|
|
|
|
|
|
|
## First-time setup
|
|
## First-time setup
|
|
|
|
|
|
|
|
-1. **Check out the repo** on the server (e.g. `/var/www/radio`).
|
|
|
|
|
|
|
+1. **Build locally.**
|
|
|
|
|
+ ```sh
|
|
|
|
|
+ npm run build # produces app/
|
|
|
|
|
+ ```
|
|
|
|
|
|
|
|
- The default branch tracks `master`. If you deployed the `modernize` branch,
|
|
|
|
|
- check that out instead.
|
|
|
|
|
|
|
+2. **FTP up** the project folder, including `app/`. At minimum:
|
|
|
|
|
+ `app/`, `app.js`, `package.json`, `package-lock.json`,
|
|
|
|
|
+ `ecosystem.config.cjs`, `deploy/`. Skip `node_modules/`, `.env`, `src/`.
|
|
|
|
|
|
|
|
-2. **Build.**
|
|
|
|
|
|
|
+3. **Install runtime deps on the server** (SSH, once — and again only when
|
|
|
|
|
+ `package.json` changes):
|
|
|
```sh
|
|
```sh
|
|
|
- npm install # full — the build needs the devDeps
|
|
|
|
|
- npm run build # src/ -> app/
|
|
|
|
|
|
|
+ cd /path/to/radio
|
|
|
|
|
+ npm ci --omit=dev # installs ONLY the 5 runtime deps, no build tooling
|
|
|
```
|
|
```
|
|
|
|
|
+ *(No shell npm? Then also build `node_modules` locally with
|
|
|
|
|
+ `npm ci --omit=dev` and FTP it up. Slower — thousands of files — but works.)*
|
|
|
|
|
|
|
|
-3. **Configure secrets.** All operational config (PORT, BASE_PATH, TRUST_PROXY,
|
|
|
|
|
- stream URLs) is set by the process manager below. The only secret is the
|
|
|
|
|
- Last.fm API key. Put it in a gitignored `.env` in the repo root:
|
|
|
|
|
|
|
+4. **Set the secret** — a gitignored `.env` in the app root on the server:
|
|
|
```sh
|
|
```sh
|
|
|
echo 'LASTFM_API_KEY=your-key-here' > .env
|
|
echo 'LASTFM_API_KEY=your-key-here' > .env
|
|
|
```
|
|
```
|
|
|
- Unset is fine too — the sidebar lists just stay empty and the app still runs.
|
|
|
|
|
|
|
+ Unset is fine — the sidebar lists just stay empty and the app still runs.
|
|
|
|
|
|
|
|
-4. **Start it — pick one process manager:**
|
|
|
|
|
|
|
+5. **Start it — pick one process manager:**
|
|
|
|
|
|
|
|
**pm2** (matches chess-io):
|
|
**pm2** (matches chess-io):
|
|
|
```sh
|
|
```sh
|
|
@@ -52,7 +78,7 @@ Every deploy is therefore: **pull → install → build → restart.**
|
|
|
sudo systemctl enable --now radio
|
|
sudo systemctl enable --now radio
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-5. **Add the Apache proxy.** Paste the block from
|
|
|
|
|
|
|
+6. **Add the Apache proxy.** Paste the block from
|
|
|
`deploy/apache-radio.conf.example` into the **existing** davidwindham.com
|
|
`deploy/apache-radio.conf.example` into the **existing** davidwindham.com
|
|
|
vhost (both `:80` and `:443`) — not a new vhost. Check the Apache version
|
|
vhost (both `:80` and `:443`) — not a new vhost. Check the Apache version
|
|
|
first (`apachectl -v`); the file has Option A for 2.4.47+ and Option B for
|
|
first (`apachectl -v`); the file has Option A for 2.4.47+ and Option B for
|
|
@@ -61,29 +87,41 @@ Every deploy is therefore: **pull → install → build → restart.**
|
|
|
apachectl configtest && sudo systemctl reload apache2 # or: apachectl -k graceful
|
|
apachectl configtest && sudo systemctl reload apache2 # or: apachectl -k graceful
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-6. **Verify:**
|
|
|
|
|
|
|
+7. **Verify:**
|
|
|
```sh
|
|
```sh
|
|
|
- curl -s https://davidwindham.com/radio/health # {"ok":true,...}
|
|
|
|
|
- curl -s https://davidwindham.com/radio/api/status # {"online":...}
|
|
|
|
|
|
|
+ curl -s https://davidwindham.com/radio/health # {"ok":true,...}
|
|
|
|
|
+ curl -s https://davidwindham.com/radio/api/status # {"online":...}
|
|
|
```
|
|
```
|
|
|
- Then load `https://davidwindham.com/radio/` in a browser and confirm the
|
|
|
|
|
- chat connects (network tab: a `101 Switching Protocols` on
|
|
|
|
|
- `/radio/socket.io/…` means the websocket upgraded; HTTP 200 long-polls mean
|
|
|
|
|
- the Apache upgrade rule isn't matching).
|
|
|
|
|
|
|
+ Then load `https://davidwindham.com/radio/` and confirm the chat connects
|
|
|
|
|
+ (network tab: a `101 Switching Protocols` on `/radio/socket.io/…` means the
|
|
|
|
|
+ websocket upgraded; HTTP-200 long-polls mean the Apache upgrade rule isn't
|
|
|
|
|
+ matching).
|
|
|
|
|
|
|
|
## Redeploying new code
|
|
## Redeploying new code
|
|
|
|
|
|
|
|
```sh
|
|
```sh
|
|
|
-git pull && npm install && npm run build && pm2 restart radio
|
|
|
|
|
-# (or: sudo systemctl restart radio)
|
|
|
|
|
-```
|
|
|
|
|
|
|
+# locally
|
|
|
|
|
+npm run build
|
|
|
|
|
|
|
|
-Optional, to slim `node_modules` after building — the devDeps are only needed
|
|
|
|
|
-at build time, since their output is baked into `app/`:
|
|
|
|
|
-```sh
|
|
|
|
|
-npm prune --omit=dev # run AFTER npm run build, never before
|
|
|
|
|
|
|
+# FTP: upload the changed files — and app/ whenever you rebuilt
|
|
|
|
|
+
|
|
|
|
|
+# on the server (SSH)
|
|
|
|
|
+pm2 restart radio # or: sudo systemctl restart radio
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+Run `npm ci --omit=dev` on the server again **only** if `package.json` changed.
|
|
|
|
|
+The node process serves the new `app/` the moment it restarts — but it must
|
|
|
|
|
+restart; it won't pick up files live.
|
|
|
|
|
+
|
|
|
|
|
+**FTP uploads but never deletes.** Files removed from the project (this cleanup
|
|
|
|
|
+dropped `gulpfile.js`, the whole `webrtc-*` bundle, and vendored libs) will
|
|
|
|
|
+linger on the server after a plain upload — harmless clutter, except inside
|
|
|
|
|
+`app/`, where a stale bundle could actually be served. `npm run build` wipes
|
|
|
|
|
+`app/` locally each time, so the safe move is to **delete the server's `app/`
|
|
|
|
|
+before uploading the fresh one** (or use an FTP "mirror / delete orphans" mode
|
|
|
|
|
+scoped to `app/`). For a first deploy there's nothing there yet, so it doesn't
|
|
|
|
|
+matter.
|
|
|
|
|
+
|
|
|
## Prerequisites on the server
|
|
## Prerequisites on the server
|
|
|
|
|
|
|
|
- **Node 20+** (22.9+ if you use the `--env-file-if-exists` line in
|
|
- **Node 20+** (22.9+ if you use the `--env-file-if-exists` line in
|