diff --git a/.env.business.example b/.env.business.example index b9c065b..b0c8e1d 100644 --- a/.env.business.example +++ b/.env.business.example @@ -2,10 +2,10 @@ # stacks/infra/compose.yaml for the one-time setup this depends on # (creating this business's database on the shared MySQL server) and # stacks//compose.yaml for how this file is used. Copy this to -# `stacks//.env` (e.g. `stacks/demo-business/.env` — kept out of -# git, see .gitignore) per business and fill in real values. Nothing here -# is a real credential. Normally the management UI (manager/) generates -# this file for you — this template is for the by-hand setup path. +# `stacks//.env` (kept out of git, see .gitignore) per business and +# fill in real values. Nothing here is a real credential. Normally the +# management UI (manager/) generates this file for you — this template is +# for the by-hand setup path. # # The app starts and runs a complete first-install flow with ONLY the # "Required for basic startup" section below filled in — every other @@ -16,12 +16,12 @@ # Manager-only bookkeeping (shown on the dashboard/edit screen, editable # there) — the app itself never reads this; its own business name lives # in the database, set via /install. -BUSINESS_NAME="Demo Business" +BUSINESS_NAME="Example Business" # The public domain this business is served on — must match the .caddy # site file's own domain (see stacks/infra/sites/.gitkeep) and that # domain's real DNS (pointed at the VPS) before Caddy can issue a # certificate for it. -DOMAIN="demo-business.example.com" +DOMAIN="example-business.example.com" # The app reaches MySQL via the shared infra's service hostname # `next-chair-mysql`, never `localhost` — see stacks/infra/compose.yaml. @@ -31,14 +31,14 @@ DOMAIN="demo-business.example.com" # tells the app what to connect as. DATABASE_HOST="next-chair-mysql" DATABASE_PORT="3306" -DATABASE_NAME="demo_business" -DATABASE_USER="demo_business" +DATABASE_NAME="example_business" +DATABASE_USER="example_business" DATABASE_PASSWORD="replace-me" # Generate with: npx auth secret AUTH_SECRET="replace-me-with-a-32-byte-random-string" # Auth.js needs to trust the host it's served from when not on localhost:3000. AUTH_TRUST_HOST="true" -NEXT_PUBLIC_APP_URL="https://demo-business.example.com" +NEXT_PUBLIC_APP_URL="https://example-business.example.com" # One-time setup token the installation wizard (/install) requires before # it will create the first business/administrator — generate with: # openssl rand -hex 32 @@ -51,7 +51,7 @@ CRON_SECRET="replace-me" # Platform-level account with every Owner permission for THIS business. # Not run automatically by this compose file (that's a Docker-entrypoint- # only behaviour, see docker/entrypoint.sh) — run it manually, once: -# docker exec -it next-chair-demo-business npx tsx scripts/ensure-super-admin.ts +# docker exec -it next-chair-example-business npx tsx scripts/ensure-super-admin.ts # Leave SUPER_ADMIN_EMAIL blank to skip entirely and use the /install # wizard's own administrator account instead. SUPER_ADMIN_EMAIL="" @@ -68,7 +68,7 @@ AUTH_GOOGLE_SECRET="" # OAuth client from AUTH_GOOGLE_ID/SECRET above (different scopes). GOOGLE_CALENDAR_CLIENT_ID="" GOOGLE_CALENDAR_CLIENT_SECRET="" -GOOGLE_CALENDAR_REDIRECT_URI="https://demo-business.example.com/api/staff/calendar/google/callback" +GOOGLE_CALENDAR_REDIRECT_URI="https://example-business.example.com/api/staff/calendar/google/callback" # Encrypts stored refresh tokens at rest — generate with: openssl rand -hex 32 CALENDAR_TOKEN_ENCRYPTION_KEY="" diff --git a/manager/Dockerfile b/manager/Dockerfile index d5fe08e..9fccc86 100644 --- a/manager/Dockerfile +++ b/manager/Dockerfile @@ -6,9 +6,9 @@ # # Only one instance of this ever runs (unlike the business image, which # is built once and reused across every business — see -# stacks/demo-business/compose.yaml's own note) so a per-deploy `build:` -# in stacks/manager/compose.yaml, rather than a separately tagged image, -# is the simpler choice here. +# src/lib/compose-template.ts's own note) so a per-deploy `build:` in +# stacks/manager/compose.yaml, rather than a separately tagged image, is +# the simpler choice here. FROM node:22-alpine AS deps WORKDIR /app diff --git a/manager/src/lib/compose-template.ts b/manager/src/lib/compose-template.ts index f4de81e..10f5c44 100644 --- a/manager/src/lib/compose-template.ts +++ b/manager/src/lib/compose-template.ts @@ -1,15 +1,16 @@ /** - * Generates the exact same shape as stacks/demo-business/compose.yaml - * and stacks/marios/compose.yaml (the two hand-written example stacks) — - * keep these in sync if that shape changes. + * The canonical definition of a business stack's compose.yaml shape — + * there's no separate hand-written example kept in the repo to stay in + * sync with; this generator is the single source of truth. See + * stacks/infra/compose.yaml's own header for the by-hand equivalent + * steps (creating the database, .env, Caddy site) this replaces. */ export function generateComposeYaml(slug: string): string { const containerName = `next-chair-${slug}`; const volumeName = `next-chair-media-${slug}`; return `name: ${containerName} -# Generated by the management UI — see stacks/demo-business/compose.yaml -# for the hand-written original this mirrors. +# Generated by the management UI. services: ${containerName}: diff --git a/stacks/demo-business/compose.yaml b/stacks/demo-business/compose.yaml deleted file mode 100644 index 2dcf580..0000000 --- a/stacks/demo-business/compose.yaml +++ /dev/null @@ -1,55 +0,0 @@ -name: next-chair-demo-business - -# One business's app container on the shared multi-business VPS setup — -# see stacks/infra/compose.yaml for the shared MySQL/Caddy this attaches -# to, and that file's header for the exact one-time setup steps -# (create this business's database, copy .env.business.example, add a -# Caddy site file) needed before this file can start. -# -# docker compose -f stacks/demo-business/compose.yaml --env-file stacks/demo-business/.env up -d -# -# `stacks/marios/compose.yaml` is the identical shape for another -# business — this is also the shape the management UI (manager/) -# generates for every new business it provisions. -# -# Uses the shared prebuilt image (see the repo root's README/Dockerfile — -# `docker build -t nextchair-app:latest .` once per code update) rather -# than building from source per business, so adding or updating a -# business is just starting/recreating a container, not a rebuild. - -services: - next-chair-demo-business: - image: nextchair-app:latest - container_name: next-chair-demo-business - restart: unless-stopped - env_file: - - .env - volumes: - - next-chair-media-demo-business:/app/storage/media - healthcheck: - # No curl/wget in the node:22-alpine base image — a plain Node - # request avoids adding a package just for this. See - # src/app/api/health/route.ts: healthy as soon as the database is - # reachable, even before installation completes. - test: - [ - "CMD", - "node", - "-e", - "require('http').get('http://localhost:3000/api/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))", - ] - interval: 10s - timeout: 5s - retries: 10 - start_period: 20s - networks: - - next-chair-edge - -volumes: - next-chair-media-demo-business: - name: next-chair-media-demo-business - -networks: - next-chair-edge: - name: next-chair-edge - external: true diff --git a/stacks/infra/compose.yaml b/stacks/infra/compose.yaml index c7a4fec..2283c72 100644 --- a/stacks/infra/compose.yaml +++ b/stacks/infra/compose.yaml @@ -6,11 +6,13 @@ name: next-chair-infra # one shared schema — see step 1 below). # # Lives in stacks/infra/, one directory per stack (each with its own -# compose.yaml) — see stacks/demo-business/, stacks/marios/ for the same -# shape per business, and stacks/manager/ for the management UI's own -# stack. Deliberately separate from docker-compose.local.yaml at the -# repo root, which is the single-business local/Docker-Desktop setup and -# stays untouched by any of this. +# compose.yaml) — see stacks/manager/ for the management UI's own stack. +# Per-business stacks (stacks//) are provisioned by the management +# UI itself (see manager/src/lib/compose-template.ts for the shape it +# generates) rather than kept as hand-written examples in this repo. +# Deliberately separate from docker-compose.local.yaml at the repo root, +# which is the single-business local/Docker-Desktop setup and stays +# untouched by any of this. # # Stack management/provisioning is handled by NextChair Manager (see # manager/ at the repo root) — there is no separate stack-management UI @@ -31,9 +33,9 @@ name: next-chair-infra # initdb.d scripts only ever run once against an empty data # directory — so this one step is manual, run once per business: # docker exec -it next-chair-mysql mysql -uroot -p -# CREATE DATABASE demo_business CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; -# CREATE USER 'demo_business'@'%' IDENTIFIED BY 'choose-a-strong-password'; -# GRANT ALL PRIVILEGES ON demo_business.* TO 'demo_business'@'%'; +# CREATE DATABASE example_business CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +# CREATE USER 'example_business'@'%' IDENTIFIED BY 'choose-a-strong-password'; +# GRANT ALL PRIVILEGES ON example_business.* TO 'example_business'@'%'; # FLUSH PRIVILEGES; # 2. Copy .env.business.example to stacks//.env, fill it in with # the credentials just created and that business's domain. diff --git a/stacks/infra/sites/.gitkeep b/stacks/infra/sites/.gitkeep index ca1feb7..484a396 100644 --- a/stacks/infra/sites/.gitkeep +++ b/stacks/infra/sites/.gitkeep @@ -2,8 +2,8 @@ Keeps this otherwise-empty directory tracked in git. Each business gets its own .caddy file here, e.g.: - demo-business.example.com { - reverse_proxy next-chair-demo-business:3000 + example-business.example.com { + reverse_proxy next-chair-example-business:3000 } Generated by the management UI (manager/) when a business is added, or