diff --git a/README.md b/README.md index 4b6723c..ad55404 100644 --- a/README.md +++ b/README.md @@ -4,48 +4,168 @@ Booking app as an alternative to Booksy ## VPS deployment (multi-business) -One-time setup on a fresh VPS with Docker and the Compose plugin already -installed. Run everything from the repo's checkout directory. +Everything below runs on a fresh VPS with Docker and the Compose plugin +already installed — no other prerequisite (no host Node.js/npm needed; +every command either uses Docker itself or plain shell tools). Every code +block is meant to be pasted as-is, in order. + +### 0. One-time: clone the repo and generate your secrets ```bash -# 1. Clone the repo at the path you intend to keep it at — STACKS_DIR -# below must be this exact absolute path, since containers shell -# `docker compose` back out to the HOST's dockerd via a mounted -# socket (see stacks/infra/compose.yaml and manager/README.md for why). +# STACKS_DIR later on must be this exact absolute path — every stack +# shells `docker compose` back out to the HOST's dockerd via a mounted +# socket, so the path only resolves to the same thing on both sides if +# it's cloned here and never moved. git clone /opt/nextchair cd /opt/nextchair +``` -# 2. Shared Docker network every stack attaches to. +Generate the secrets you'll paste into the `.env` files in the steps +below. Run these now and keep the output somewhere safe (a password +manager) — you'll need each value again in a moment. + +```bash +# MYSQL_ROOT_PASSWORD — goes into BOTH stacks/infra/.env and manager/.env +# (the same value in both places). +echo "MYSQL_ROOT_PASSWORD=$(openssl rand -hex 24)" + +# MANAGER_SESSION_SECRET — manager/.env only. +echo "MANAGER_SESSION_SECRET=$(openssl rand -hex 32)" + +# MANAGER_SETTINGS_ENCRYPTION_KEY — manager/.env only. Encrypts the +# SMTP/VAPID secrets you'll enter in the Manager's Settings page later. +# Losing/rotating this makes any already-saved secret there undecryptable, +# so store it as carefully as the other secrets on this page. +echo "MANAGER_SETTINGS_ENCRYPTION_KEY=$(openssl rand -hex 32)" +``` + +```bash +# MANAGER_PASSWORD_HASH — manager/.env only. Replace "your-password" with +# your chosen Manager login password first. Run via a throwaway container +# so this works with nothing but Docker installed on the host. +docker run --rm node:22-alpine sh -c \ + "mkdir -p /tmp/gen && cd /tmp/gen && npm install bcryptjs --no-audit --no-fund >/dev/null 2>&1 && \ + node -e \"console.log('MANAGER_PASSWORD_HASH=' + require('/tmp/gen/node_modules/bcryptjs').hashSync('your-password', 12))\"" +``` + +### 1. Shared infrastructure — network, MySQL, Caddy, Dockge + +```bash docker network create next-chair-edge -# 3. Shared infrastructure: MySQL, Caddy (reverse proxy + automatic TLS), -# Dockge (stack management UI, http://:5001). cp .env.infra.example stacks/infra/.env -# edit stacks/infra/.env — MYSQL_ROOT_PASSWORD, ACME_EMAIL, and set -# STACKS_DIR to this exact clone path, e.g. /opt/nextchair/stacks +``` + +Edit `stacks/infra/.env`: +- `MYSQL_ROOT_PASSWORD` — the first secret you generated above. +- `ACME_EMAIL` — a real address you check (Let's Encrypt renewal notices). +- `STACKS_DIR` — the absolute clone path, e.g. `/opt/nextchair/stacks`. + +```bash docker compose -f stacks/infra/compose.yaml --env-file stacks/infra/.env up -d +``` -# 4. Build the shared business image (re-run this after every code -# update — see stacks/blake-heritage/compose.yaml's own note). +### 2. Build the shared application image + +Every business (and no other step in this guide) runs this same image — +you only rebuild it here, once, and again whenever you ship a code +update (see "Updating" below). + +```bash docker build -t nextchair-app:latest . +``` -# 5. The management UI (manager/) — this is what you'll use to add -# businesses from here on, not the by-hand steps in stacks/infra/ -# compose.yaml's header (those exist only as a fallback/reference). +### 3. The management UI (Manager) + +This is what you'll use to add every business from here on. + +```bash cp manager/.env.example stacks/manager/.env -# edit stacks/manager/.env — MANAGER_EMAIL/PASSWORD_HASH, MANAGER_SESSION_SECRET, -# MYSQL_ROOT_PASSWORD (same value as step 3), and STACKS_DIR (same as step 3) +``` + +Edit `stacks/manager/.env`: +- `MANAGER_EMAIL` — the email you'll log in with. +- `MANAGER_PASSWORD_HASH` — the bcrypt hash generated in step 0. +- `MANAGER_SESSION_SECRET` and `MANAGER_SETTINGS_ENCRYPTION_KEY` — the two + other values generated in step 0. +- `MANAGER_PUBLIC_URL` — `https://` + the domain you'll point at this app + in the next step. +- `MYSQL_ROOT_PASSWORD` — same value as `stacks/infra/.env`. +- `STACKS_DIR` — same absolute path as step 1. + +```bash docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build ``` -Then, per business, point its domain's DNS at the VPS and use the -management UI (`https://` — add a Caddy site for it -the same way as any business, see `stacks/infra/sites/.gitkeep`) to add -it: name, slug, domain — it creates the database, generates secrets, -starts the container, and runs migrations for you. Finish setup by -visiting `https:///install`. +Point your chosen Manager domain's DNS (A/AAAA record) at the VPS, then +give it a Caddy site and reload: -To ship a code update to every business: `git pull`, rebuild the image -(step 4), then use each business's **Update** button in the management UI -(or `docker compose up -d --force-recreate` in its `stacks//` -directory) to recreate it against the new image. \ No newline at end of file +```bash +cat > stacks/infra/sites/manager.caddy << 'EOF' +manager.example.com { + reverse_proxy next-chair-manager:3000 +} +EOF +docker exec next-chair-caddy caddy reload --config /etc/caddy/Caddyfile +``` + +(Replace `manager.example.com` with your real domain — Caddy issues its +TLS certificate automatically on first request.) + +### 4. Configure platform-wide settings (once) + +Log into the Manager at `https://` and open +**Settings**. Both sections are optional to fill in now, but doing it +before adding your first business means that business never needs its +own SMTP/push setup: + +- **Email / SMTP** — your SMTP relay's host/port/username/password and a + from-address. Use **Send Test Email** to confirm it before saving for + real, then **Save Changes** to apply it to every business. +- **Push Notifications** — click **Generate VAPID Keys** once (a contact + email is enough to start). This is a one-time, platform-wide key pair — + see that section's own copy for what happens if you ever regenerate it. + +### 5. Add your first business + +Still in the Manager: **Add Business** → name, slug, domain. This creates +its database, generates its secrets, starts its container, and runs its +migrations for you — no manual per-business `.env` editing needed. + +Point that business's own domain's DNS at the VPS (the Manager doesn't do +this for you), then finish its setup by visiting +`https:///install`. + +Repeat this step for each additional business, whenever you want one. + +## Updating after a code change + +```bash +cd /opt/nextchair +git pull + +# Rebuild the one shared image every business uses. +docker build -t nextchair-app:latest . +``` + +Then, for **each** business: use its **Update** button in the Manager UI, +or run this directly: + +```bash +cd stacks/ +docker compose --env-file .env up -d --force-recreate +``` + +Either way this recreates that business's container against the freshly +built image — and its own startup automatically runs +`prisma migrate deploy`, so any new database migration is applied on the +same step. No separate migration command needed. + +If the code change touched the **Manager app itself** (anything under +`manager/`), update it the same way — this one does rebuild, since unlike +a business it doesn't share a prebuilt image: + +```bash +cd /opt/nextchair +docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build +```