docs: dashboard_ro role + two-stage rollout; mark Phase 1 done
Updates STAGING_FLEETOPS_ARCHITECTURE.md to reflect the dedicated read-only dashboard_ro role (replacing the grafana_ro reuse), the explicit v_trips matview grant, DEFAULT PRIVILEGES, host-only password, and the two-stage plan (staging now, live prod connection later). Notes migrations 17+18 applied; Phase 0 read-only role complete, webhook deploys still pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cbbe3dab87
commit
2603f0e726
1 changed files with 15 additions and 8 deletions
|
|
@ -82,7 +82,7 @@ Traefik host `fleetapi.rahamafresh.com`). Staging mirrors it:
|
||||||
| Port | 8890 | **8891** |
|
| Port | 8890 | **8891** |
|
||||||
| Code mount | `~/dashboard_api/` | `~/dashboard_api_staging/` (WIP checkout) |
|
| Code mount | `~/dashboard_api/` | `~/dashboard_api_staging/` (WIP checkout) |
|
||||||
| Deploy script | `~/deploy_dashboard_api.sh` | **`deploy_dashboard_api_staging.sh`** (checked into this repo) |
|
| Deploy script | `~/deploy_dashboard_api.sh` | **`deploy_dashboard_api_staging.sh`** (checked into this repo) |
|
||||||
| DB role | app role (read/write) | **read-only** (`dashboard_ro` / `grafana_ro`) |
|
| DB role | app role (read/write) | **read-only** `dashboard_ro` (dedicated) |
|
||||||
| `v_trips` refresher | **owns it** | **disabled** |
|
| `v_trips` refresher | **owns it** | **disabled** |
|
||||||
| CORS origins | `fleetnow.rahamafresh.com`, `fleetintelligence.…`, `liveposition.…`, **+ `fleetops.rahamafresh.com`** | `fleetnow.fivetitude.com`, `fleetops.fivetitude.com` |
|
| CORS origins | `fleetnow.rahamafresh.com`, `fleetintelligence.…`, `liveposition.…`, **+ `fleetops.rahamafresh.com`** | `fleetnow.fivetitude.com`, `fleetops.fivetitude.com` |
|
||||||
|
|
||||||
|
|
@ -105,9 +105,9 @@ the JSONB/GeoJSON return style of the existing `/webhook/*` routes:
|
||||||
| `GET /analytics/filters` | `reporting.v_filter_*` (alias of `GET /webhook/fleet-dashboard`) |
|
| `GET /analytics/filters` | `reporting.v_filter_*` (alias of `GET /webhook/fleet-dashboard`) |
|
||||||
|
|
||||||
Aggregations that aren't thin wrappers get a **new numbered migration** — never edit an applied
|
Aggregations that aren't thin wrappers get a **new numbered migration** — never edit an applied
|
||||||
one. The fuel roll-up ships as `migrations/17_fleetops_fuel_view.sql` (the live migration head is
|
one. The fuel roll-up ships as `migrations/17_fleetops_fuel_view.sql` (the live migration head was
|
||||||
**16**, not 13 as older docs imply); it also `GRANT`s `SELECT` to `grafana_ro` for the staging
|
**16**, not 13 as older docs imply; 17 + 18 are now applied). `dashboard_ro` reads `v_fuel_daily`
|
||||||
read-only role.
|
via the schema-wide `SELECT` grant in `scripts/dashboard_ro_role.sql`.
|
||||||
|
|
||||||
> **Reuse the existing reporting layer.** The analytics building blocks are `reporting.*`
|
> **Reuse the existing reporting layer.** The analytics building blocks are `reporting.*`
|
||||||
> (migrations 11/14) and the surviving `tracksolid.v_*` views (migration 07). The `ops.*` and
|
> (migrations 11/14) and the surviving `tracksolid.v_*` views (migration 07). The `ops.*` and
|
||||||
|
|
@ -163,9 +163,16 @@ Production is touched **only** by a merge to `main`. That branch discipline is w
|
||||||
Staging hits the **production database**, so isolation is enforced at the **DB-role level**,
|
Staging hits the **production database**, so isolation is enforced at the **DB-role level**,
|
||||||
not by a separate DB:
|
not by a separate DB:
|
||||||
|
|
||||||
- The staging `dashboard_api` connects as a **read-only role** — reuse `grafana_ro`, or add a
|
- The staging `dashboard_api` connects as a **dedicated read-only role `dashboard_ro`**
|
||||||
dedicated `dashboard_ro` with `GRANT SELECT` on `reporting.*` and the `tracksolid.v_*`
|
(`scripts/dashboard_ro_role.sql` + `scripts/bootstrap_dashboard_ro.sh`). It grants exactly
|
||||||
views. Accidental writes from staging are then impossible.
|
what the API reads — `SELECT` on `reporting.*` + `tracksolid.*`, an explicit `SELECT` on the
|
||||||
|
`reporting.v_trips` **materialized view** (matviews aren't covered by `GRANT ... ON ALL
|
||||||
|
TABLES`), `EXECUTE` on the `reporting.fn_*` map functions, and `ALTER DEFAULT PRIVILEGES` so
|
||||||
|
future objects are auto-readable ("dynamic"). No write/REFRESH privilege, so accidental writes
|
||||||
|
are impossible. The password is generated on the host into `~/.dashboard_ro.pw` (0600), never
|
||||||
|
in the repo. **Two-stage:** stage 1 backs the *staging* bridge (done); stage 2 migrates the
|
||||||
|
*live prod* `fleetapi.rahamafresh.com` connection off the app role onto `dashboard_ro` (which
|
||||||
|
already grants the full read surface, incl. the live `fn_*` path).
|
||||||
- The **`reporting.v_trips` materialized-view refresher is disabled on staging** — production
|
- The **`reporting.v_trips` materialized-view refresher is disabled on staging** — production
|
||||||
owns it. The refresher needs write perms and is already pg-advisory-lock guarded (key
|
owns it. The refresher needs write perms and is already pg-advisory-lock guarded (key
|
||||||
`920_145`, FIX-D02); a read-only staging role would only log errors, so disable it explicitly
|
`920_145`, FIX-D02); a read-only staging role would only log errors, so disable it explicitly
|
||||||
|
|
@ -182,7 +189,7 @@ client's production domains **last**.
|
||||||
|
|
||||||
| Phase | Scope | Exit criterion |
|
| Phase | Scope | Exit criterion |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **0 — Foundation** | This document; migrate all Coolify apps to Forgejo webhook deploys; provision the read-only DB role | Every existing Coolify app redeploys via webhook; read-only role can `SELECT` `reporting.*` + `tracksolid.v_*` and nothing else |
|
| **0 — Foundation** | This document; provision the read-only `dashboard_ro` role (**done**); migrate all Coolify apps to Forgejo webhook deploys (**pending**) | `dashboard_ro` can `SELECT` `reporting.*` + `tracksolid.*` + `v_trips` and nothing else; every Coolify app redeploys via webhook |
|
||||||
| **1 — Staging backbone** | Staging `dashboard_api` bridge (`deploy_dashboard_api_staging.sh`, 8891, `fleetapi.fivetitude.com`, read-only, refresher off, staging CORS) | `curl https://fleetapi.fivetitude.com/health` ok; verifiably read-only; no staging rows in `reporting.refresh_log` |
|
| **1 — Staging backbone** | Staging `dashboard_api` bridge (`deploy_dashboard_api_staging.sh`, 8891, `fleetapi.fivetitude.com`, read-only, refresher off, staging CORS) | `curl https://fleetapi.fivetitude.com/health` ok; verifiably read-only; no staging rows in `reporting.refresh_log` |
|
||||||
| **2 — FleetNow staging** | FleetNow repo: `staging` branch + parameterized API base + `fleetnow.fivetitude.com` Coolify app | Renders against staging API; `staging` push deploys staging only, `main` merge deploys prod only; prod FleetNow untouched |
|
| **2 — FleetNow staging** | FleetNow repo: `staging` branch + parameterized API base + `fleetnow.fivetitude.com` Coolify app | Renders against staging API; `staging` push deploys staging only, `main` merge deploys prod only; prod FleetNow untouched |
|
||||||
| **3 — FleetOps backend** | `/analytics/*` endpoints in `dashboard_api_rev.py` + `migrations/17_fleetops_fuel_view.sql`; refresher made disable-able (`VTRIPS_REFRESH_INTERVAL_S<=0`); tested on the staging API | Every route returns correct shape on `fleetapi.fivetitude.com`; fuel route returns "needs data" flags |
|
| **3 — FleetOps backend** | `/analytics/*` endpoints in `dashboard_api_rev.py` + `migrations/17_fleetops_fuel_view.sql`; refresher made disable-able (`VTRIPS_REFRESH_INTERVAL_S<=0`); tested on the staging API | Every route returns correct shape on `fleetapi.fivetitude.com`; fuel route returns "needs data" flags |
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue