Attempt 2 of a management ui.
This commit is contained in:
54 files changed
+8804
-267
No files matched your search
@@ -0,0 +1,89 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user