90 lines
3.7 KiB
Markdown
90 lines
3.7 KiB
Markdown
# NextChair Manager
|
|
|
|
Standalone platform-operator tool for provisioning and managing NextChair
|
|
business deployments on the shared multi-business VPS setup (see
|
|
`../stacks/`). Not part of the main app — a genuinely separate Next.js
|
|
app, own `package.json`, own build, own container.
|
|
|
|
## Why it's a separate app
|
|
|
|
An earlier attempt at this exact feature was built as routes inside the
|
|
main app (`src/app/manager/...`). It was reverted after repeated,
|
|
never-fully-resolved bugs: the main app's `src/proxy.ts`, root layout,
|
|
root page, and global `SessionProvider` all statically import Prisma /
|
|
Auth.js / business branding — code a Manager (which has no business
|
|
database of its own, by definition) kept transitively pulling in, causing
|
|
crashes in "Manager mode."
|
|
|
|
This app avoids that class of bug structurally rather than by careful
|
|
avoidance: it never imports anything from the main app's `src/` at all.
|
|
A few small pieces (bcrypt convention, the signed-cookie primitive) were
|
|
copied and adapted, not imported — see `src/lib/auth.ts` and
|
|
`src/lib/session.ts`.
|
|
|
|
## What it does
|
|
|
|
- Lists provisioned businesses and their running/stopped state.
|
|
- **Add Business**: collects a name/slug/domain, then generates secrets,
|
|
creates that business's database + user on the shared MySQL server,
|
|
writes its `stacks/<slug>/compose.yaml` + `.env` + Caddy site file,
|
|
reloads Caddy, starts the container, and runs its migrations. See
|
|
`src/lib/provisioning.ts` for the exact sequence.
|
|
- Per business: start / stop / restart / update (recreate the container
|
|
against the current shared image tag — not a source rebuild) / view
|
|
recent logs.
|
|
- Does **not** duplicate the `/install` wizard — after provisioning, the
|
|
operator is linked to `https://<domain>/install` to finish that
|
|
business's own setup.
|
|
|
|
## How auth works
|
|
|
|
A single dedicated platform-operator credential (`MANAGER_EMAIL` /
|
|
`MANAGER_PASSWORD_HASH`, bcrypt cost 12) — not tied to any one business's
|
|
own Super Administrator, since this tool spans every business's separate
|
|
database and has no "primary" one of its own. A signed (HMAC, not
|
|
encrypted) cookie holds the session; see `src/lib/session.ts`.
|
|
|
|
## The Docker-socket trust boundary
|
|
|
|
This container has the host's Docker socket mounted (`docker.sock`) so it
|
|
can run `docker compose`/`docker exec` against the host's dockerd — see
|
|
`src/lib/docker.ts`. Root inside this container plus that socket
|
|
effectively means root on the host. Consequences:
|
|
|
|
- It must stay behind the login above at all times — never expose it
|
|
without that gate, and don't reuse `MANAGER_PASSWORD_HASH` anywhere
|
|
else.
|
|
- `STACKS_DIR` must be bind-mounted at the *identical absolute path* on
|
|
both host and container (see `.env.example`) — a `docker compose -f
|
|
<path>` command run from inside this container is executed by the
|
|
*host's* dockerd, so the path only means the same file on both sides if
|
|
the mount doesn't change it. This is the same constraint Dockge itself
|
|
has.
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
cp manager/.env.example stacks/manager/.env # fill in real values
|
|
docker compose -f stacks/manager/compose.yaml --env-file stacks/manager/.env up -d --build
|
|
```
|
|
|
|
Requires `stacks/infra/compose.yaml` already running (shared MySQL,
|
|
Caddy, the `next-chair-edge` network). Add a Caddy site for this app the
|
|
same way as any business (see `stacks/infra/sites/.gitkeep`), pointed at
|
|
`next-chair-manager:3000`.
|
|
|
|
## Local development
|
|
|
|
```bash
|
|
cd manager
|
|
npm install
|
|
cp .env.example .env.local
|
|
npm run dev
|
|
```
|
|
|
|
Requires network access to a real MySQL server and `docker`/`docker
|
|
compose` on the machine you run it from for the provisioning/stack
|
|
actions to actually work — the dashboard's read-only parts (listing
|
|
stacks) will also just show nothing if `STACKS_DIR` doesn't point at a
|
|
real directory yet.
|