docs: update AGENT.md + ARQUITECTURE.md — inline entrypoint, NPM full config, env vars, Unraid notes
This commit is contained in:
@@ -86,9 +86,11 @@ Password min length is enforced server-side by `GOTRUE_PASSWORD_MIN_LENGTH=10`.
|
||||
|
||||
## 7. Frontend conventions
|
||||
|
||||
- **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.
|
||||
- **Only** the anon key leaves the server. `config.js` is generated at container start via an inline `entrypoint:` command in `docker-compose.yml` (writes `window.MCCARS_CONFIG={SUPABASE_URL,SUPABASE_ANON_KEY}`). The service_role key is never mounted into the `web` container.
|
||||
- **No entrypoint script file.** An earlier design used `frontend/99-config.sh` bind-mounted into `/docker-entrypoint.d/`. Abandoned because Unraid's filesystem does not preserve Unix execute bits on bind mounts — the script was silently skipped. The inline `entrypoint:` in compose needs no file permissions.
|
||||
- `frontend/config.js` is **git-ignored** (generated at runtime, would commit stale `localhost` values otherwise).
|
||||
- `index.html` / `admin.html` load `config.js` with a `?v=<timestamp>` query string (generated by a small inline `document.write` snippet) to defeat both browser and proxy caching.
|
||||
- The `web` service uses `nginx:1.27-alpine` directly (no `build:`). Static files and `nginx.conf` are bind-mounted. Updating the frontend is `git pull` + `docker compose up -d --force-recreate web`.
|
||||
- `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.
|
||||
@@ -106,6 +108,9 @@ Password min length is enforced server-side by `GOTRUE_PASSWORD_MIN_LENGTH=10`.
|
||||
| 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 `55521` (production port). |
|
||||
| `config.js` not generated, website uses fallback URL | Unraid doesn't preserve execute bits — entrypoint script skipped. Fixed: `config.js` is now written by an inline `entrypoint:` command in docker-compose, no file needed. |
|
||||
| Website loads but API calls fail ("Failed to fetch") | `config.js` cached by NPM or browser with old `localhost` URL. The `?v=timestamp` cache-buster on the `<script>` tag prevents this. Hard-refresh or check NPM "Cache Assets" toggle. |
|
||||
| Git pull fails with "Your local changes would be overwritten" | `.env` was modified on the NAS. Use `git checkout -- .env && git pull`, then re-apply the two URL lines with `sed`. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+74
-11
@@ -212,8 +212,6 @@ Host port mapping: `55521:8000` (8000 blocked by Docker Desktop's wslrelay on Wi
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── Dockerfile (legacy, not used in Portainer deploy)
|
||||
├── 99-config.sh (entrypoint: writes config.js at boot)
|
||||
├── nginx.conf (serves /, gzip, cache headers)
|
||||
├── index.html app.js (public site, anon key, persistSession:false)
|
||||
├── admin.html admin.js (admin CRM, persistSession:true)
|
||||
@@ -223,8 +221,10 @@ frontend/
|
||||
└── datenschutz.html
|
||||
```
|
||||
|
||||
- `config.js` is generated at container start by `99-config.sh` (bind-mounted into `/docker-entrypoint.d/`) from `$SUPABASE_URL` and `$SUPABASE_ANON_KEY` only. The service_role key is never mounted into the web container.
|
||||
- The `web` service uses `nginx:1.27-alpine` directly with bind-mounted files (no `build:` step). This is Portainer-compatible: updating the frontend is `git pull` + container restart.
|
||||
- `config.js` is generated at container start by an **inline `entrypoint:` command** in `docker-compose.yml`. It writes `window.MCCARS_CONFIG={SUPABASE_URL,SUPABASE_ANON_KEY}` from the container's environment variables. No separate script file is used — Unraid's filesystem does not preserve Unix execute bits on bind mounts, so a mounted `.sh` file would be silently ignored.
|
||||
- `config.js` is **git-ignored**. The repo never ships a file with hardcoded URLs.
|
||||
- Both `index.html` and `admin.html` load `config.js` with `?v=<timestamp>` appended via an inline `document.write` snippet, ensuring proxies and browsers never cache a stale URL.
|
||||
- The `web` service uses `nginx:1.27-alpine` directly with bind-mounted files (no `build:` step). This is Portainer-compatible: updating the frontend is `git pull` + `docker compose up -d --force-recreate web`.
|
||||
- `admin.js` is ES modules, imports `@supabase/supabase-js` from `esm.sh` (CDN, pinned version). One Supabase client per page.
|
||||
- State lives in a single `state` object. Admin re-renders the active tab on realtime events.
|
||||
- Force-password-change modal is the only state that can preempt the rest of the admin UI after login.
|
||||
@@ -246,15 +246,78 @@ What's code/config (committed):
|
||||
|
||||
**Portainer deployment:**
|
||||
1. Clone repo to `/mnt/user/appdata/mc-cars`
|
||||
2. `chmod +x` the `.sh` files
|
||||
3. `mkdir -p data/{db,storage}`
|
||||
2. `mkdir -p data/{db,storage}`
|
||||
3. Edit `.env`: set `SITE_URL` and `SUPABASE_PUBLIC_URL` to your domain (see below)
|
||||
4. Portainer → Stacks → Add stack → paste compose + env vars → Deploy
|
||||
|
||||
**Nginx Proxy Manager (single public domain):**
|
||||
- Proxy `/` → `mccars-web:80` (or `<host>:55580`)
|
||||
- Custom locations `/auth/v1/`, `/rest/v1/`, `/realtime/v1/`, `/storage/v1/` → `mccars-kong:8000` (or `<host>:55521`)
|
||||
- Do **not** expose `/pg/` or Studio publicly
|
||||
- Update `.env` URLs to `https://cars.yourdomain.com`
|
||||
> No `chmod` needed. The entrypoint that writes `config.js` is an inline shell command in `docker-compose.yml`, not a bind-mounted script, so file permissions are irrelevant.
|
||||
|
||||
**Environment: two variables to change per environment**
|
||||
|
||||
| Variable | Local dev | Production (NAS) |
|
||||
|---|---|---|
|
||||
| `SITE_URL` | `http://localhost:55580` | `https://your.domain.com` |
|
||||
| `SUPABASE_PUBLIC_URL` | `http://localhost:55521` | `https://your.domain.com` |
|
||||
|
||||
All other GoTrue vars (`API_EXTERNAL_URL`, `GOTRUE_SITE_URL`, `GOTRUE_URI_ALLOW_LIST`) are derived from these two in `docker-compose.yml`.
|
||||
|
||||
**Nginx Proxy Manager (NPM) — required settings for single-domain HTTPS:**
|
||||
|
||||
Create one proxy host for your domain (e.g. `demo.lago.dev`):
|
||||
|
||||
*Details tab:*
|
||||
- Scheme: `http`
|
||||
- Forward Hostname / IP: `<NAS IP>` (e.g. `192.168.178.3`)
|
||||
- Forward Port: `55580`
|
||||
- Cache Assets: **OFF** (if left ON, NPM caches `config.js` and serves stale URLs)
|
||||
- Websockets Support: **ON** (required for Realtime)
|
||||
- Block Common Exploits: ON
|
||||
|
||||
*SSL tab:*
|
||||
- SSL Certificate: your domain cert
|
||||
- Force SSL: **ON**
|
||||
- HTTP/2 Support: **ON**
|
||||
|
||||
*Advanced tab (⚙️) — paste this exactly:*
|
||||
|
||||
```nginx
|
||||
location /auth/v1/ {
|
||||
proxy_pass http://192.168.178.3:55521;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /rest/v1/ {
|
||||
proxy_pass http://192.168.178.3:55521;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /realtime/v1/ {
|
||||
proxy_pass http://192.168.178.3:55521;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
location /storage/v1/ {
|
||||
proxy_pass http://192.168.178.3:55521;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
```
|
||||
|
||||
This routes `https://your.domain.com/` → nginx (static site) and all API paths → Kong. Do **not** expose `/pg/` or Studio publicly.
|
||||
|
||||
For real deployments:
|
||||
1. Generate a new `JWT_SECRET` and matching `ANON_KEY` / `SERVICE_ROLE_KEY` (see Supabase self-hosting docs).
|
||||
|
||||
Reference in New Issue
Block a user