diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 00000000..ef2a5618 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,48 @@ +# Muzix deploy + +`muzix.kcolbchain.com` is a Next.js standalone build behind Caddy on the +shared kcolbchain VPS. There is no GitHub Action and no webhook — +deploys are triggered by SSHing into the VPS and running this script. + +## Layout on the VPS + +| Path | What | +|------|------| +| `/opt/muzix/` | Git checkout of `kcolbchain/muzix` (the `main` branch is prod) | +| `/opt/muzix/web/.next/standalone/` | Next.js standalone build (`server.js`) — what systemd runs | +| `/opt/muzix/web/.next/standalone/public/` | **Hand-wired** — Next.js does not copy `public/` into standalone output | +| `/opt/muzix/web/.next/standalone/.next/static/` | **Hand-wired** — Next.js does not copy `.next/static` into standalone output. Note: this lives at `standalone/.next/static`, **not** `standalone/web/.next/static` — the source dir-layout is misleading. | +| `/etc/systemd/system/muzix-web.service` | systemd unit — `node /opt/muzix/web/.next/standalone/server.js` on `127.0.0.1:3700` | +| `/etc/caddy/conf.d/muzix.caddy` | Caddy reverse-proxy to `127.0.0.1:3700` | + +## Deploy + +```bash +ssh user@ '/opt/muzix/deploy/deploy.sh' +``` + +The script: + +1. `git fetch && git reset --hard origin/main` in `/opt/muzix` +2. `npm install` + `npm run build` in `/opt/muzix/web` +3. Copies `web/public/` → `web/.next/standalone/public/` +4. Copies `web/.next/static/` → `web/.next/standalone/.next/static/` +5. `systemctl restart muzix-web` +6. Verifies the service is active; on failure prints the last 30 journal lines and exits non-zero + +## Why two hand-wired copies? + +Next.js standalone output deliberately omits `public/` and +`.next/static/` to keep the standalone tarball minimal — the docs tell +you to copy them in for production. See +https://nextjs.org/docs/app/api-reference/config/next-config-js/output. + +The labs (`/mixdown.html`, `/labelton.html`) live in `web/public/`, so +forgetting the public-copy step is how they 404 in prod. + +## Adding new sub-routes / static assets + +- Static one-pagers under `web/public/` reach prod automatically once + this script runs. +- New Next.js routes under `web/app/` need a fresh `npm run build`, + which the script already does. diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 00000000..541fa4f7 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# ============================================================= +# Muzix — prod deploy +# Runs on the VPS that serves muzix.kcolbchain.com (port 3700, +# behind Caddy). Pulls main, rebuilds the Next.js standalone, +# wires in the assets standalone doesn't bundle automatically, +# and restarts the systemd service. +# +# Usage: ssh user@ '/opt/muzix/deploy/deploy.sh' +# ============================================================= + +set -euo pipefail + +REPO=/opt/muzix +SERVICE=muzix-web +SUBJECT=muzix.kcolbchain.com +STANDALONE="$REPO/web/.next/standalone" + +echo "==> Deploying ${SUBJECT} (origin/main → prod)..." + +cd "$REPO" +git fetch origin --quiet +git reset --hard origin/main +HEAD_SHA="$(git rev-parse --short HEAD)" +echo " Source at ${HEAD_SHA}: $(git log -1 --pretty='%s')" + +cd "$REPO/web" +echo "==> npm install..." +npm install --no-audit --no-fund --prefer-offline > /dev/null + +echo "==> next build..." +npm run build > /dev/null + +# Next.js standalone output does NOT copy these by design — the docs +# explicitly tell you to wire them in for production. See: +# https://nextjs.org/docs/app/api-reference/config/next-config-js/output +# Both destinations are RELATIVE TO THE STANDALONE ROOT — NOT under web/. +# server.js does process.chdir(__dirname) where __dirname is the standalone +# root, then looks for `./.next/static` and `./public`. Writing them under +# `standalone/web/.next/static` (a mirror of the source layout) is wrong: +# the homepage will render but every /_next/static/* request will 404, +# silently breaking all styling. +echo "==> wiring public/ and .next/static into standalone..." +rm -rf "$STANDALONE/public" +mkdir -p "$STANDALONE/public" +cp -r "$REPO/web/public/." "$STANDALONE/public/" + +rm -rf "$STANDALONE/.next/static" +cp -r "$REPO/web/.next/static" "$STANDALONE/.next/static" + +echo "==> systemctl restart ${SERVICE}..." +sudo systemctl restart "$SERVICE" +sleep 2 + +if systemctl is-active --quiet "$SERVICE"; then + echo "==> Deploy complete at ${HEAD_SHA}. ${SERVICE} is active." +else + echo "!! ${SERVICE} failed to start. Recent logs:" + journalctl -u "$SERVICE" -n 30 --no-pager + exit 1 +fi