fix(dev-local): harden one-command startup

This commit is contained in:
jjbrunton committed 2026-08-30 17:25:30 +01:00
1 parent 3d53d001b7
commit 476e4c845f
2 files changed
+472 -57

No files matched your search

+64 -20
View File
@@ -16,6 +16,22 @@ described by their environment examples.
| MCP | Hono | 3002 |
| Expo Metro | Expo | 8081 |
If another local project owns one of the application ports, add a different,
unique value to the ignored root `.env.local` before running `up`:
```text
SAUCI_WEB_PORT=3100
SAUCI_ADMIN_PORT=3101
SAUCI_MCP_PORT=3102
SAUCI_METRO_PORT=8181
```
`SAUCI_POSTGRES_PORT` and `SAUCI_API_PORT` remain available for their existing
local overrides. Every configured port must be a unique integer from 1 through
65535. Before launching managed services, the launcher checks both IPv4 and IPv6
listeners and fails closed with the owning PID, command, and working directory;
it never stops another project's process.
PostgreSQL is bound to `127.0.0.1`, is healthchecked before application startup,
and stores data in a named Docker volume. The local connection string is:
@@ -26,28 +42,56 @@ postgresql://sauci:sauci_local_only@127.0.0.1:54320/sauci
Those credentials are for local development only. Deployed environments must
inject unique secrets; they must never consume `infra/backend/.env.example`.
## First run
## Start the stack
1. Use Node from `.nvmrc` and run `npm ci`.
2. Install Docker Desktop or Rancher Desktop and tmux.
3. Configure the hosted Supabase Auth public URL/key for the mobile application
and the hosted Auth issuer/JWKS settings for the API. Do not configure any
local command with production credentials. Authenticated mobile E2E requires
the designated hosted non-production Auth project and a disposable,
onboarding-complete user whose subject has a matching local profile. Pass
its credentials as `E2E_EMAIL` and `E2E_PASSWORD` only to the Maestro process;
do not store them in an Expo environment file.
To exercise MCP tools, also set a local `ADMIN_API_SERVICE_TOKEN`, its
fixture `ADMIN_API_SERVICE_USER_ID`, and a separate `SAUCI_MCP_API_KEY`.
`scripts/dev-local.sh` passes the service token to MCP as
`SAUCI_ADMIN_API_TOKEN`; MCP never receives a database or Supabase service
credential.
4. Run `scripts/dev-local.sh reset` to recreate only the dedicated local Docker
volume, apply API migrations, and create configured fixtures.
5. Run `scripts/dev-local.sh up`.
One command starts the local database, applies migrations and fixtures, and
launches the application services:
`up` is idempotent. `down` stops tmux application processes and leaves the
database available. `down --all` also stops PostgreSQL but retains its volume.
```bash
scripts/dev-local.sh up
```
Before the first use, install Docker Desktop or Rancher Desktop, tmux, and the
repository dependencies with `npm ci`. The launcher reads `.nvmrc` itself. When
the active Node version differs, it loads an installed Homebrew `nvm` and selects
the exact version automatically. If that version has not been installed yet, run
the command the launcher prints once, then rerun `up`.
Create the ignored root `.env.local` with the public Auth settings for the
designated non-production Supabase project (`itbzhrvlgvdmzbnhzhyx`). Set
`SUPABASE_AUTH_URL` (or `EXPO_PUBLIC_SUPABASE_URL`) and
`EXPO_PUBLIC_SUPABASE_ANON_KEY`; the launcher uses the URL for the API and both
values for Expo. It accepts only this designated hosted project or loopback Auth,
and explicitly rejects the production project and every other hosted project.
Environment variables exported in the shell take precedence over `.env.local`.
The launcher treats `.env.local` as data, never shell code: use unquoted
`KEY=VALUE` lines for only the documented launcher keys. Never place production
credentials or Auth issuer/JWKS, Auth service-role, RevenueCat, or other provider
secrets in `.env.local`; the launcher removes those inherited values from its
local API and worker processes. Each `up` replaces its managed tmux windows so
stale application processes cannot retain earlier configuration, while leaving
unrelated windows intact. Credentials are passed only to the local process that
needs them: MCP receives its local API token/key but never `DATABASE_URL`.
Before replacing a managed window, the launcher verifies and stops only its
current descendant processes. If those descendants do not exit promptly, it
leaves that window in place and fails rather than broadening process cleanup.
This includes a previously captured descendant that has left the pane lineage:
the launcher will not signal it again or replace the window until that captured
process identity is gone.
Authenticated mobile E2E requires a disposable, onboarding-complete user whose
subject has a matching local profile. Pass its credentials as `E2E_EMAIL` and
`E2E_PASSWORD` only to the Maestro process; do not store them in an Expo
environment file. To exercise MCP tools, also set a local
`ADMIN_API_SERVICE_TOKEN`, its fixture `ADMIN_API_SERVICE_USER_ID`, and a
separate `SAUCI_MCP_API_KEY`. `scripts/dev-local.sh` passes the service token to
MCP as `SAUCI_ADMIN_API_TOKEN`; MCP never receives a database or Supabase service
credential.
`up` is idempotent. `down` stops only the launcher's managed tmux application
windows and leaves unrelated windows and the database available. `down --all`
also stops PostgreSQL but retains its volume, and runs only after managed
application cleanup succeeds.
Use `status`, `logs <service>`, `restart <service>`, and `attach` for diagnosis.
`logs postgres` reads Compose logs; other names read their tmux panes.
The `worker` pane owns scheduled jobs, notifications, moderation classification,