docs: update README, AGENT, ARQUITECTURE for 555xx ports, Portainer deploy, absolute paths
This commit is contained in:
@@ -4,12 +4,13 @@ A compact field guide for anyone (human or AI) making changes to this stack. Ski
|
||||
|
||||
---
|
||||
|
||||
## 1. The portability contract (don't break it)
|
||||
## 1. The deployment model
|
||||
|
||||
1. **No named volumes.** All runtime state bind-mounts to `./data/`. `docker compose down -v` must never be required to migrate: copy the folder, `docker compose up -d`, done.
|
||||
2. **No absolute paths** in `docker-compose.yml` or any config.
|
||||
3. **Secrets live in `.env`**; the `.env` ships with dev defaults and is safe to commit until real deploy. Any non-local deployment must rotate every key (see §6).
|
||||
4. Anything that resets the DB in dev is `Remove-Item -Recurse -Force .\data` — not `docker volume rm`.
|
||||
1. **Portainer-native.** No `build:` steps. Every service pulls a pre-built image. The `web` service uses `nginx:1.27-alpine` with bind-mounted static files.
|
||||
2. **Absolute host paths.** All bind mounts point to `/mnt/user/appdata/mc-cars/...`. Runtime state lives under `/mnt/user/appdata/mc-cars/data/`.
|
||||
3. **No named volumes.** Wiping data means `rm -rf /mnt/user/appdata/mc-cars/data/db` — not `docker volume rm`.
|
||||
4. **Secrets live in `.env`**; the `.env` ships with dev defaults. Any non-local deployment must rotate every key (see §6).
|
||||
5. **External ports are in the `555xx` range** to avoid collisions on a busy host (`55521` Kong, `55530` Studio, `55532` Postgres, `55543` Kong TLS, `55580` web).
|
||||
|
||||
---
|
||||
|
||||
@@ -17,11 +18,11 @@ A compact field guide for anyone (human or AI) making changes to this stack. Ski
|
||||
|
||||
Migrations come in two flavours:
|
||||
|
||||
### First-boot only (runs iff `./data/db` is empty)
|
||||
### First-boot only (runs iff `/mnt/user/appdata/mc-cars/data/db` is empty)
|
||||
- `supabase/migrations/00-run-init.sh` — wrapper that runs `01-init.sql` via `psql` so we can pre-create Supabase service roles (`anon`, `authenticated`, `service_role`, `authenticator`, `supabase_auth_admin`, `supabase_storage_admin`) that the official images otherwise assume already exist on their `supabase/postgres` image. We use plain `postgres:15-alpine`, so we create them ourselves.
|
||||
- `supabase/migrations/01-init.sql` — vehicles table, RLS, bucket row, seed demo vehicles.
|
||||
|
||||
Postgres' official entrypoint only processes `/docker-entrypoint-initdb.d/` on an **empty** data dir. To re-run: wipe `./data/db`.
|
||||
Postgres' official entrypoint only processes `/docker-entrypoint-initdb.d/` on an **empty** data dir. To re-run: wipe `/mnt/user/appdata/mc-cars/data/db`.
|
||||
|
||||
### Post-boot (every time, idempotent)
|
||||
- `supabase/migrations/post-boot.sql` — seeds the admin `auth.users` row with `must_change_password=true`, creates the `vehicle-photos` bucket row if missing. Parameterized: `psql -v admin_email=... -v admin_password=...`.
|
||||
@@ -61,7 +62,8 @@ Both are run by the `post-init` service, which gates on `auth.users` and `storag
|
||||
|
||||
## 5. Kong traps
|
||||
|
||||
- On Windows/WSL2, Docker Desktop's `wslrelay` intercepts `127.0.0.1:8000`. We expose Kong on host **`54321`** (container `8000`). Don't move it back to 8000.
|
||||
- On Windows/WSL2, Docker Desktop's `wslrelay` intercepts `127.0.0.1:8000`. On the production host Kong is exposed on **`55521`** (container `8000`).
|
||||
- On a busy Unraid/Docker host all MC Cars ports live in the `555xx` range to avoid collisions.
|
||||
- Kong needs `KONG_PLUGINS: bundled,request-transformer,cors,key-auth,acl,basic-auth`. Omitting `acl` breaks Studio.
|
||||
- `kong.yml` has one `consumer` per role with `acl` groups; plugins `key-auth` + `acl` gate which `apikey` (anon vs service_role) can reach which route.
|
||||
|
||||
@@ -84,7 +86,9 @@ Password min length is enforced server-side by `GOTRUE_PASSWORD_MIN_LENGTH=10`.
|
||||
|
||||
## 7. Frontend conventions
|
||||
|
||||
- **Only** the anon key leaves the server. `frontend/Dockerfile` writes `config.js` at container start from `$SUPABASE_URL` / `$SUPABASE_ANON_KEY`. The service_role key is not mounted into `web`.
|
||||
- **Only** the anon key leaves the server. `frontend/99-config.sh` (bind-mounted into the nginx entrypoint dir) writes `config.js` at container start from `$SUPABASE_URL` / `$SUPABASE_ANON_KEY`. The service_role key is not mounted into `web`.
|
||||
- The `web` service uses `nginx:1.27-alpine` directly (no `build:`). Static files + nginx.conf + the entrypoint script are bind-mounted read-only. Updating the frontend is `git pull` + restart the container.
|
||||
- `.gitattributes` enforces `eol=lf` on `*.sh` files so the entrypoint doesn't break with CRLF on Windows checkouts.
|
||||
- `frontend/app.js` (public) creates a Supabase client with `persistSession: false` — the public site never needs a session.
|
||||
- `frontend/admin.js` uses `persistSession: true, storageKey: "mccars.auth"` and a single realtime channel `mccars-admin` that subscribes to leads/customers/vehicles.
|
||||
- `app.js` writes `vehicle_id` (uuid) **and** denormalized `vehicle_label` ("BMW M3") into the lead at submit time, so the admin UI renders even if the vehicle is later deleted.
|
||||
@@ -95,30 +99,30 @@ Password min length is enforced server-side by `GOTRUE_PASSWORD_MIN_LENGTH=10`.
|
||||
|
||||
| Symptom | Cause / Fix |
|
||||
| ------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `auth` container restart loop, "role supabase_auth_admin does not exist" | `00-run-init.sh` didn't run. Wipe `./data/db`, retry. |
|
||||
| `auth` container restart loop, "role supabase_auth_admin does not exist" | `00-run-init.sh` didn't run. Wipe `/mnt/user/appdata/mc-cars/data/db`, retry. |
|
||||
| `realtime` container exits, "wal_level must be logical" | `db` `command:` missing `wal_level=logical`. |
|
||||
| 502 on `/realtime/v1/` through Kong | Missing `acl` plugin in `KONG_PLUGINS`, or realtime not listed in `kong.yml` consumers. |
|
||||
| Admin login works but Leads tab is empty | RLS: session is `anon` instead of `authenticated`. Check that `signInWithPassword` succeeded and token is attached to subsequent requests. |
|
||||
| Booking form submits but no row appears | anon lacks `INSERT` grant on `public.leads`. Check `02-leads.sql` ran (see `post-init` logs). |
|
||||
| `.sh` files reject with `\r: command not found` | CRLF line endings. `dos2unix` or re-save as LF. |
|
||||
| Port 8000 "connection refused" on Windows | Docker Desktop wslrelay. Use `54321`. |
|
||||
| Port 8000 "connection refused" on Windows | Docker Desktop wslrelay. Use `55521` (production port). |
|
||||
|
||||
---
|
||||
|
||||
## 9. How to verify a working stack
|
||||
|
||||
```powershell
|
||||
```bash
|
||||
docker compose ps # all services up (post-init exited 0)
|
||||
curl http://localhost:54321/rest/v1/vehicles?select=brand -H "apikey: $env:ANON" # 6 demo cars
|
||||
curl http://localhost:55521/rest/v1/vehicles?select=brand -H "apikey: $ANON" # 6 demo cars
|
||||
```
|
||||
|
||||
In the browser:
|
||||
1. Open http://localhost:8080, submit the booking form.
|
||||
2. Open http://localhost:8080/admin.html, log in with `.env` bootstrap creds.
|
||||
1. Open http://\<host\>:55580, submit the booking form.
|
||||
2. Open http://\<host\>:55580/admin.html, log in with `.env` bootstrap creds.
|
||||
3. Rotate the password when prompted.
|
||||
4. The lead you submitted appears in "Aktive Leads" — in real time.
|
||||
5. Click **Qualifizieren**. Row disappears from active, a new row appears in **Kunden** with the `lead_id` displayed.
|
||||
6. Open Supabase Studio at http://localhost:3000 and confirm the `customers.lead_id` FK matches.
|
||||
6. Open Supabase Studio at http://\<host\>:55530 and confirm the `customers.lead_id` FK matches.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user