New files:
manager/src/lib/env-file.ts — a shared, surgical .env reader/updater. It only ever touches the specific keys you tell it to (never regenerates the file), so anything hand-edited into a business's .env since provisioning is preserved exactly.
manager/src/app/dashboard/[slug]/edit/ — the edit page, its form component, and its server action.
Modified:
provisioning.ts gained updateBusiness() alongside the existing provisionBusiness(), reusing the same Caddy-site-writing and container-recreation logic (refactored into a shared writeCaddySiteAndReload helper).
stacks.ts/compose-template.ts — extended to track a BUSINESS_NAME field (previously collected on the Add Business form but silently discarded — that gap is now fixed too, so newly created businesses get their name persisted from the start).
The detail page and dashboard list now show the business name, and the detail page has an Edit button.
Per the requirements:
Slug is read-only in the edit form (disabled input, never accepted by the server action — it's bound in from the route, not form data).
Domain uniqueness is validated against every other business (and correctly self-excludes the business being edited).
A domain change rewrites the Caddy site file, reloads Caddy, and force-recreates the container; a name-only change does neither.
No secrets (DATABASE_PASSWORD, AUTH_SECRET, etc.) are ever read into the edit UI — only name/domain/databaseName/status are exposed.
Clear success banners ("Changes saved" vs. "...the container was restarted...") and inline error display, matching the existing Add Business pattern.
NextChair
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.
# 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).
git clone <your-repo-url> /opt/nextchair
cd /opt/nextchair
# 2. Shared Docker network every stack attaches to.
docker network create next-chair-edge
# 3. Shared infrastructure: MySQL, Caddy (reverse proxy + automatic TLS),
# Dockge (stack management UI, http://<vps-ip>: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
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).
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).
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)
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://<your-manager-domain> — 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://<business-domain>/install.
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/<slug>/
directory) to recreate it against the new image.