Removed the demo business as its easier to just provision one manually.
This commit is contained in:
6 files changed
+32
-84
No files matched your search
+11
-11
@@ -2,10 +2,10 @@
|
|||||||
# stacks/infra/compose.yaml for the one-time setup this depends on
|
# stacks/infra/compose.yaml for the one-time setup this depends on
|
||||||
# (creating this business's database on the shared MySQL server) and
|
# (creating this business's database on the shared MySQL server) and
|
||||||
# stacks/<slug>/compose.yaml for how this file is used. Copy this to
|
# stacks/<slug>/compose.yaml for how this file is used. Copy this to
|
||||||
# `stacks/<slug>/.env` (e.g. `stacks/demo-business/.env` — kept out of
|
# `stacks/<slug>/.env` (kept out of git, see .gitignore) per business and
|
||||||
# git, see .gitignore) per business and fill in real values. Nothing here
|
# fill in real values. Nothing here is a real credential. Normally the
|
||||||
# is a real credential. Normally the management UI (manager/) generates
|
# management UI (manager/) generates this file for you — this template is
|
||||||
# this file for you — this template is for the by-hand setup path.
|
# for the by-hand setup path.
|
||||||
#
|
#
|
||||||
# The app starts and runs a complete first-install flow with ONLY the
|
# The app starts and runs a complete first-install flow with ONLY the
|
||||||
# "Required for basic startup" section below filled in — every other
|
# "Required for basic startup" section below filled in — every other
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# Manager-only bookkeeping (shown on the dashboard/edit screen, editable
|
# Manager-only bookkeeping (shown on the dashboard/edit screen, editable
|
||||||
# there) — the app itself never reads this; its own business name lives
|
# there) — the app itself never reads this; its own business name lives
|
||||||
# in the database, set via /install.
|
# 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
|
# The public domain this business is served on — must match the .caddy
|
||||||
# site file's own domain (see stacks/infra/sites/.gitkeep) and that
|
# 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
|
# domain's real DNS (pointed at the VPS) before Caddy can issue a
|
||||||
# certificate for it.
|
# certificate for it.
|
||||||
DOMAIN="demo-business.example.com"
|
DOMAIN="example-business.example.com"
|
||||||
|
|
||||||
# The app reaches MySQL via the shared infra's service hostname
|
# The app reaches MySQL via the shared infra's service hostname
|
||||||
# `next-chair-mysql`, never `localhost` — see stacks/infra/compose.yaml.
|
# `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.
|
# tells the app what to connect as.
|
||||||
DATABASE_HOST="next-chair-mysql"
|
DATABASE_HOST="next-chair-mysql"
|
||||||
DATABASE_PORT="3306"
|
DATABASE_PORT="3306"
|
||||||
DATABASE_NAME="demo_business"
|
DATABASE_NAME="example_business"
|
||||||
DATABASE_USER="demo_business"
|
DATABASE_USER="example_business"
|
||||||
DATABASE_PASSWORD="replace-me"
|
DATABASE_PASSWORD="replace-me"
|
||||||
# Generate with: npx auth secret
|
# Generate with: npx auth secret
|
||||||
AUTH_SECRET="replace-me-with-a-32-byte-random-string"
|
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.js needs to trust the host it's served from when not on localhost:3000.
|
||||||
AUTH_TRUST_HOST="true"
|
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
|
# One-time setup token the installation wizard (/install) requires before
|
||||||
# it will create the first business/administrator — generate with:
|
# it will create the first business/administrator — generate with:
|
||||||
# openssl rand -hex 32
|
# openssl rand -hex 32
|
||||||
@@ -51,7 +51,7 @@ CRON_SECRET="replace-me"
|
|||||||
# Platform-level account with every Owner permission for THIS business.
|
# Platform-level account with every Owner permission for THIS business.
|
||||||
# Not run automatically by this compose file (that's a Docker-entrypoint-
|
# Not run automatically by this compose file (that's a Docker-entrypoint-
|
||||||
# only behaviour, see docker/entrypoint.sh) — run it manually, once:
|
# 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
|
# Leave SUPER_ADMIN_EMAIL blank to skip entirely and use the /install
|
||||||
# wizard's own administrator account instead.
|
# wizard's own administrator account instead.
|
||||||
SUPER_ADMIN_EMAIL=""
|
SUPER_ADMIN_EMAIL=""
|
||||||
@@ -68,7 +68,7 @@ AUTH_GOOGLE_SECRET=""
|
|||||||
# OAuth client from AUTH_GOOGLE_ID/SECRET above (different scopes).
|
# OAuth client from AUTH_GOOGLE_ID/SECRET above (different scopes).
|
||||||
GOOGLE_CALENDAR_CLIENT_ID=""
|
GOOGLE_CALENDAR_CLIENT_ID=""
|
||||||
GOOGLE_CALENDAR_CLIENT_SECRET=""
|
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
|
# Encrypts stored refresh tokens at rest — generate with: openssl rand -hex 32
|
||||||
CALENDAR_TOKEN_ENCRYPTION_KEY=""
|
CALENDAR_TOKEN_ENCRYPTION_KEY=""
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -6,9 +6,9 @@
|
|||||||
#
|
#
|
||||||
# Only one instance of this ever runs (unlike the business image, which
|
# Only one instance of this ever runs (unlike the business image, which
|
||||||
# is built once and reused across every business — see
|
# is built once and reused across every business — see
|
||||||
# stacks/demo-business/compose.yaml's own note) so a per-deploy `build:`
|
# src/lib/compose-template.ts's own note) so a per-deploy `build:` in
|
||||||
# in stacks/manager/compose.yaml, rather than a separately tagged image,
|
# stacks/manager/compose.yaml, rather than a separately tagged image, is
|
||||||
# is the simpler choice here.
|
# the simpler choice here.
|
||||||
|
|
||||||
FROM node:22-alpine AS deps
|
FROM node:22-alpine AS deps
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Generates the exact same shape as stacks/demo-business/compose.yaml
|
* The canonical definition of a business stack's compose.yaml shape —
|
||||||
* and stacks/marios/compose.yaml (the two hand-written example stacks) —
|
* there's no separate hand-written example kept in the repo to stay in
|
||||||
* keep these in sync if that shape changes.
|
* 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 {
|
export function generateComposeYaml(slug: string): string {
|
||||||
const containerName = `next-chair-${slug}`;
|
const containerName = `next-chair-${slug}`;
|
||||||
const volumeName = `next-chair-media-${slug}`;
|
const volumeName = `next-chair-media-${slug}`;
|
||||||
return `name: ${containerName}
|
return `name: ${containerName}
|
||||||
|
|
||||||
# Generated by the management UI — see stacks/demo-business/compose.yaml
|
# Generated by the management UI.
|
||||||
# for the hand-written original this mirrors.
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
${containerName}:
|
${containerName}:
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -6,11 +6,13 @@ name: next-chair-infra
|
|||||||
# one shared schema — see step 1 below).
|
# one shared schema — see step 1 below).
|
||||||
#
|
#
|
||||||
# Lives in stacks/infra/, one directory per stack (each with its own
|
# Lives in stacks/infra/, one directory per stack (each with its own
|
||||||
# compose.yaml) — see stacks/demo-business/, stacks/marios/ for the same
|
# compose.yaml) — see stacks/manager/ for the management UI's own stack.
|
||||||
# shape per business, and stacks/manager/ for the management UI's own
|
# Per-business stacks (stacks/<slug>/) are provisioned by the management
|
||||||
# stack. Deliberately separate from docker-compose.local.yaml at the
|
# UI itself (see manager/src/lib/compose-template.ts for the shape it
|
||||||
# repo root, which is the single-business local/Docker-Desktop setup and
|
# generates) rather than kept as hand-written examples in this repo.
|
||||||
# stays untouched by any of this.
|
# 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
|
# Stack management/provisioning is handled by NextChair Manager (see
|
||||||
# manager/ at the repo root) — there is no separate stack-management UI
|
# 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
|
# initdb.d scripts only ever run once against an empty data
|
||||||
# directory — so this one step is manual, run once per business:
|
# directory — so this one step is manual, run once per business:
|
||||||
# docker exec -it next-chair-mysql mysql -uroot -p
|
# docker exec -it next-chair-mysql mysql -uroot -p
|
||||||
# CREATE DATABASE demo_business CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
# CREATE DATABASE example_business CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||||
# CREATE USER 'demo_business'@'%' IDENTIFIED BY 'choose-a-strong-password';
|
# CREATE USER 'example_business'@'%' IDENTIFIED BY 'choose-a-strong-password';
|
||||||
# GRANT ALL PRIVILEGES ON demo_business.* TO 'demo_business'@'%';
|
# GRANT ALL PRIVILEGES ON example_business.* TO 'example_business'@'%';
|
||||||
# FLUSH PRIVILEGES;
|
# FLUSH PRIVILEGES;
|
||||||
# 2. Copy .env.business.example to stacks/<slug>/.env, fill it in with
|
# 2. Copy .env.business.example to stacks/<slug>/.env, fill it in with
|
||||||
# the credentials just created and that business's domain.
|
# the credentials just created and that business's domain.
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ Keeps this otherwise-empty directory tracked in git.
|
|||||||
|
|
||||||
Each business gets its own <slug>.caddy file here, e.g.:
|
Each business gets its own <slug>.caddy file here, e.g.:
|
||||||
|
|
||||||
demo-business.example.com {
|
example-business.example.com {
|
||||||
reverse_proxy next-chair-demo-business:3000
|
reverse_proxy next-chair-example-business:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
Generated by the management UI (manager/) when a business is added, or
|
Generated by the management UI (manager/) when a business is added, or
|
||||||
|
|||||||
Reference in new issue
Block a user