|
| 1 | +# MMGIS local deployment model |
| 2 | + |
| 3 | +How MMGIS runs on a developer machine — both the simple single-instance case and the multi-instance pattern this skill automates. |
| 4 | + |
| 5 | +## What a deployment is |
| 6 | + |
| 7 | +A deployment is one MMGIS instance: a server process on a `PORT`, a database named `DB_NAME`, an `.env`, and its own `node_modules`, in one directory. The only backing service required is **one Postgres+PostGIS container**. STAC / TiTiler / tipg / veloserver are optional (`--profile stac`, `--profile veloserver`) and off by default — not needed for a dashboard or for tests. |
| 8 | + |
| 9 | +## The three run modes (from package.json) |
| 10 | + |
| 11 | +- **`npm start`** — has a `prestart` hook that runs `docker-compose -f docker-compose.db.yml up -d --wait` to bring up a DB container, then `node scripts/init-db.js && node scripts/server.js`. The vanilla single-machine path. |
| 12 | +- **`npm run start:no-docker`** — `init-db.js` + `server.js` with **no** `prestart` hook (npm only fires `prestart` before `start`). Assumes a DB is already reachable. **This is what coexisting deployments use**, because the `prestart` container always binds host port 5432 and would collide when a shared container already holds it. |
| 13 | +- **`npm run start:test`** — `NODE_ENV=test PORT=8888 node scripts/server.js`. What Playwright auto-launches for e2e. |
| 14 | + |
| 15 | +## Database |
| 16 | + |
| 17 | +- **Auto-init.** `init-db.js` creates the `$DB_NAME` database, the `postgis` + `btree_gist` extensions, the `session` table, and spatial indexes. Idempotent ("already exists → nothing to do"). |
| 18 | +- **Auto-schema.** `server.js` runs Sequelize `.sync()`, which creates all tables on boot. **No manual migrations.** |
| 19 | +- **Cross-branch schema.** `.sync()` reconciles *additive* changes automatically. Dropping/renaming columns across branches is the only case needing manual care. |
| 20 | + |
| 21 | +## Ports |
| 22 | + |
| 23 | +- In `NODE_ENV=development`: the API + WebSocket run on `PORT`; the **dashboard UI is served by webpack-dev-server on `PORT+1`**. `/` on `PORT` redirects to `PORT+1`. So dev uses **two** adjacent ports. |
| 24 | +- In test/production: everything is on `PORT` (no `+1`). |
| 25 | +- The API healthcheck is `GET /api/utils/healthcheck` on `PORT` (returns "Alive and Well!"). It comes up before webpack finishes its first compile, so the API can be healthy while the dashboard is still compiling for a few seconds. |
| 26 | + |
| 27 | +## Coexisting many deployments |
| 28 | + |
| 29 | +They differ only in `PORT` and `DB_NAME`, against one shared container: |
| 30 | + |
| 31 | +- **One shared Postgres container.** `docker-compose.db.yml` hardcodes host `5432:5432`, so only one can run. Start it once from the main checkout (`npm run db:start`); every deployment points at `localhost:5432` with a distinct `DB_NAME`. Coexisting deployments launch with `start:no-docker`. |
| 32 | +- **Ports stepped by 10** (dev burns `PORT` and `PORT+1`), starting at 8888 and skipping any already in an `.env` or listening. |
| 33 | +- **`.env` and `node_modules` are gitignored** — never present in a fresh worktree; each deployment provisions its own (`npm install --force`; `--force` is required by this dependency tree, not `--legacy-peer-deps`). |
| 34 | + |
| 35 | +## Config: how a deployment gets a working dashboard |
| 36 | + |
| 37 | +A fresh database has no admin and no missions, so the landing page would be empty. Two ways to populate it: |
| 38 | + |
| 39 | +- **Golden clone (default).** A frozen baseline database `mmgis_golden` holds a baseline admin (`admin`, permission `111`) and a mission. Each new deployment's database is created as `CREATE DATABASE <db> TEMPLATE mmgis_golden`, so it boots already populated and then evolves independently. Postgres requires the template to have no active connections — that's why `mmgis_golden` is frozen (nothing connects to it) and why you must never clone from the live `mmgis` database. |
| 40 | +- **First-run signup.** Without a golden, the `/first_signup` endpoint makes the first account created a full admin (permission `111`); you then build missions in the Configure page. |
| 41 | + |
| 42 | +Where the golden itself comes from — it lives only in the local Docker volume, so each machine builds its own: |
| 43 | + |
| 44 | +- **Seed (fresh machine).** `seed-golden.sh` builds it from the committed `seed/baseline-mission.json`: it boots a temporary server against a scratch database (Sequelize creates the schema), seeds an admin + the baseline mission through MMGIS's own APIs (`first_signup` → `login` → `/api/configure/add`), then renames the scratch DB to `mmgis_golden`. The basemap token is injected at seed time from `MAPBOX_TOKEN` (env or the main checkout's `.env`) — **tokens are never committed to git**; the seed file holds a `{{MAPBOX_TOKEN}}` placeholder. |
| 45 | +- **Snapshot (existing machine).** `refresh-golden.sh` re-baselines from a live database via `pg_dump` (default source: `mmgis`). |
| 46 | + |
| 47 | +Note the golden does **not** survive `docker-compose down -v` or deleting the `mmgis_mmgis-local-db` volume — that wipes every database. Re-seed or re-snapshot afterward. |
| 48 | + |
| 49 | +`FORCE_CONFIG_PATH=<file.json>` forces a single read-only mission from a file and **bypasses the Configure system** — a special case, not how normal deployments are configured. |
| 50 | + |
| 51 | +## Tests |
| 52 | + |
| 53 | +- **Unit (`npm run test:unit`)** — pure JS, no server, no database. Run anywhere in parallel, no collisions. |
| 54 | +- **E2E (`npm run test:e2e`)** — Playwright auto-starts the server via `start:test`, which hardcodes `PORT=8888`, and `reuseExistingServer` is on locally. If another server is already on 8888, Playwright attaches to it and tests the wrong code. Run e2e only when 8888 is free (the `test.sh` helper enforces this). Parallel e2e across deployments would need app changes and is out of scope. |
| 55 | + |
| 56 | +## Gotchas |
| 57 | + |
| 58 | +- **CRLF in `.env`.** Some `.env` files use Windows line endings; a naive shell read yields values with a trailing `\r` (e.g. role `mmgis\r` → "role does not exist"). Always strip CR when reading `.env` in shell. |
| 59 | +- **Stray DB containers.** Running plain `npm start` in a worktree spins up that worktree's *own* DB container (e.g. `mmgis-<name>-db-1`). These can linger (often in "Created" state) and confuse "which container is the shared one." The shared one is whichever publishes host port 5432. |
0 commit comments