[Infra][MinIO] Migrate Angular nginx root bind — presigned/proxy, LAST (epic #413) #531

Open
opened 2026-07-13 00:40:42 +00:00 by spikerj · 7 comments
Owner

Parent epic: #413. Migrates LAST (per docs/minio-storage-migration.md).

Scope: the spikersoft-angular/docker-stack.yml root bind /mnt/fusionio/spikersoft → /usr/share/nginx/html/spikersoft (nginx serving uploaded assets directly).

Plan (decide per route once writers are on S3): either presigned/proxied S3 URLs issued by the backend, or an nginx→MinIO proxy_pass location block (no bind, nginx stays a dumb proxy). After this lands the Angular container is volume-free and placement-free.

Blocked by: uploads batch + API media serving tickets (all writers/readers must be on S3 first).

Parent epic: #413. **Migrates LAST** (per `docs/minio-storage-migration.md`). **Scope:** the `spikersoft-angular/docker-stack.yml` root bind `/mnt/fusionio/spikersoft → /usr/share/nginx/html/spikersoft` (nginx serving uploaded assets directly). **Plan (decide per route once writers are on S3):** either presigned/proxied S3 URLs issued by the backend, or an nginx→MinIO `proxy_pass` location block (no bind, nginx stays a dumb proxy). After this lands the Angular container is volume-free and placement-free. Blocked by: uploads batch + API media serving tickets (all writers/readers must be on S3 first).
Author
Owner

Angular PRs up: #177 (SECURITY, ungated — deny the private trees the root bind exposes; see the new security ticket) and #178 (GATED — proxy blogs/ebooks/lesson-videos to MinIO with Range pass-through; ^~ is load-bearing vs the .png regex location; gates listed in the PR incl. mc anonymous download on the three public buckets + the infra bind-drop one-liner). After #178 + bind drop, the Angular container is volume-free — the last bind in the epic.

Angular PRs up: #177 (SECURITY, ungated — deny the private trees the root bind exposes; see the new security ticket) and #178 (GATED — proxy blogs/ebooks/lesson-videos to MinIO with Range pass-through; ^~ is load-bearing vs the .png regex location; gates listed in the PR incl. mc anonymous download on the three public buckets + the infra bind-drop one-liner). After #178 + bind drop, the Angular container is volume-free — the last bind in the epic.
Author
Owner

SECURITY — deploy needed, 2026-07-13 ~20:42Z QA watch. The nginx deny fix (angular 10f80c18, merged 20:29Z in PR #177) is NOT live yet: learn.spikersoft.com still runs the pre-fix config. Live discriminator: /spikersoft/quarantine/, /uploads/, /dlls/ all return 403 (= old config, just autoindex-off on directory URLs) — the fixed config returns 404 (explicit deny). A 403 directory listing block does NOT protect the files: anything under quarantine (scan-FAILED files — likely malware), raw upload staging, dlls, assets, ai, art_pipe is still publicly downloadable by direct path on the live site. The angular service spec is unchanged since 17:01Z (pre-merge, still root-bind, no deny rules).

Recommend deploying the frontend container ahead of the gated MinIO-proxy rework — the deny block is a self-contained config change with no dependency on the #531 proxying (public trees keep working off the bind). Until then this is an open, known exposure with the fix sitting in master. (Verified via Traefik router Host learn.spikersoft.com; did not fetch any quarantine payloads, only status-code discrimination.)

SECURITY — deploy needed, 2026-07-13 ~20:42Z QA watch. The nginx deny fix (angular 10f80c18, merged 20:29Z in PR #177) is NOT live yet: learn.spikersoft.com still runs the pre-fix config. Live discriminator: /spikersoft/quarantine/, /uploads/, /dlls/ all return 403 (= old config, just autoindex-off on directory URLs) — the fixed config returns 404 (explicit deny). A 403 directory listing block does NOT protect the files: anything under quarantine (scan-FAILED files — likely malware), raw upload staging, dlls, assets, ai, art_pipe is still publicly downloadable by direct path on the live site. The angular service spec is unchanged since 17:01Z (pre-merge, still root-bind, no deny rules). Recommend deploying the frontend container ahead of the gated MinIO-proxy rework — the deny block is a self-contained config change with no dependency on the #531 proxying (public trees keep working off the bind). Until then this is an open, known exposure with the fix sitting in master. (Verified via Traefik router Host learn.spikersoft.com; did not fetch any quarantine payloads, only status-code discrimination.)
Author
Owner

HOLD: #178 merged ahead of its gates — verified live that minio.spikersoft.com/blogs/ returns AccessDenied (no anonymous read) and the old config is still serving (quarantine 403s = bind directory, not the #177 404). Frontend CI deploys on master push, so the merged proxy config is heading to prod and will 403 ALL blog/ebook/video media. Revert PR #179 is up (reverts ONLY the proxy commit — #177's security deny stays). Re-apply by reverting the revert once: mc anonymous set download on the 3 buckets + backfills + runbook phase 2.

HOLD: #178 merged ahead of its gates — verified live that minio.spikersoft.com/blogs/ returns AccessDenied (no anonymous read) and the old config is still serving (quarantine 403s = bind directory, not the #177 404). Frontend CI deploys on master push, so the merged proxy config is heading to prod and will 403 ALL blog/ebook/video media. Revert PR #179 is up (reverts ONLY the proxy commit — #177's security deny stays). Re-apply by reverting the revert once: mc anonymous set download on the 3 buckets + backfills + runbook phase 2.
Author
Owner

Race outcome: revert #179 MERGED before any of the three queued Frontend CI deploys landed — probes show the ORIGINAL config still serving (quarantine + blogs both return the bind's 403), so no media breakage occurred at any point. Master now = #177 deny + no proxy. When the current CI queue drains, the deploy should flip /spikersoft/quarantine/ to 404 (#177/#542 verification) while media serving stays on the bind. #178 re-applies via revert-of-revert once gates hold.

Race outcome: revert #179 MERGED before any of the three queued Frontend CI deploys landed — probes show the ORIGINAL config still serving (quarantine + blogs both return the bind's 403), so no media breakage occurred at any point. Master now = #177 deny + no proxy. When the current CI queue drains, the deploy should flip /spikersoft/quarantine/ to 404 (#177/#542 verification) while media serving stays on the bind. #178 re-applies via revert-of-revert once gates hold.
Author
Owner

QA watch 2026-07-13 ~20:48Z — revert verified, and it UNBLOCKS the security deploy. Revert 2d7caa63 (PR #179) removed only the proxy commit (d5fd39f3); the security deny block from 10f80c18 is still in master's nginx.config (verified: location ~ ^/spikersoft/(quarantine|uploads|dlls|assets|ai|art_pipe) → return 404; no proxy_pass/minio references remain). So the 'next deploy breaks media' hazard is gone: master's config is the ORIGINAL bind-serving for public trees + explicit denies for private trees.

That makes the frontend redeploy both SAFE and STILL URGENT — the live exposure (quarantine/uploads/dlls fetchable by path, re-verified 403 = old config at 20:47Z) persists until it ships. Nothing now stands between master and closing the hole.

QA watch 2026-07-13 ~20:48Z — revert verified, and it UNBLOCKS the security deploy. Revert 2d7caa63 (PR #179) removed only the proxy commit (d5fd39f3); the security deny block from 10f80c18 is still in master's nginx.config (verified: location ~ ^/spikersoft/(quarantine|uploads|dlls|assets|ai|art_pipe) → return 404; no proxy_pass/minio references remain). So the 'next deploy breaks media' hazard is gone: master's config is the ORIGINAL bind-serving for public trees + explicit denies for private trees. That makes the frontend redeploy both SAFE and STILL URGENT — the live exposure (quarantine/uploads/dlls fetchable by path, re-verified 403 = old config at 20:47Z) persists until it ships. Nothing now stands between master and closing the hole.
Author
Owner

Frontend deploy pipeline is green and the revert-state config is LIVE (private trees 404, public trees on the bind). #178 re-apply path when cutover gates hold: revert-the-revert of angular #179, after mc anonymous set download on blogs/ebooks/lesson-videos + backfills + #529 phase 2. — macbook-claude-session

Frontend deploy pipeline is green and the revert-state config is LIVE (private trees 404, public trees on the bind). #178 re-apply path when cutover gates hold: revert-the-revert of angular #179, after mc anonymous set download on blogs/ebooks/lesson-videos + backfills + #529 phase 2. — macbook-claude-session
Author
Owner

Epic #413 accuracy pass (2026-07-18) — still OPEN, but the re-apply plan needs a RESCOPE: ebooks must NOT get anonymous bucket download anymore.

Current verified state:

  • nginx.config on angular master: #177's deny block live and now includes ebooks (added with the #618 read-authz work) — location ~ ^/spikersoft/(quarantine|uploads|dlls|assets|ai|art_pipe|ebooks) → 404
  • No MinIO proxy in the config (revert #179 stands); the root bind is still in spikersoft-angular/docker-stack.yml
  • Book covers/content are served through the API with BookAccess authorization since #618/PR-B — not by nginx at all

Consequence for the #178 re-apply: the original gate list said mc anonymous set download on blogs/ebooks/lesson-videos. Doing that for ebooks today would re-expose private books around the BookAccess authz layer — the exact hole #618 closed. The revert-of-revert must be rescoped to proxy blogs + lesson-videos only; the ebooks serving path stays API-side and its cutover rides #696 phase 3.

Updated gate list for re-applying the proxy (blogs + lesson-videos only):

  1. mc anonymous set download on blogs and lesson-videos only
  2. backfills current (blogs ✓ 2026-07-14; lesson-videos empty = empty disk, nothing to serve yet)
  3. #529 phase 2 for the writer-side binds
  4. then drop the root bind → Angular container volume-free

This stays the LAST bind in the epic, per the original sequencing.

**Epic #413 accuracy pass (2026-07-18) — still OPEN, but the re-apply plan needs a RESCOPE: ebooks must NOT get anonymous bucket download anymore.** Current verified state: - `nginx.config` on angular master: #177's deny block live **and now includes `ebooks`** (added with the #618 read-authz work) — `location ~ ^/spikersoft/(quarantine|uploads|dlls|assets|ai|art_pipe|ebooks) → 404` - No MinIO proxy in the config (revert #179 stands); the root bind is still in `spikersoft-angular/docker-stack.yml` - Book covers/content are served through the **API with BookAccess authorization** since #618/PR-B — not by nginx at all **Consequence for the #178 re-apply:** the original gate list said `mc anonymous set download` on **blogs/ebooks/lesson-videos**. Doing that for `ebooks` today would re-expose private books *around* the BookAccess authz layer — the exact hole #618 closed. The revert-of-revert must be rescoped to proxy **blogs + lesson-videos only**; the ebooks serving path stays API-side and its cutover rides **#696 phase 3**. Updated gate list for re-applying the proxy (blogs + lesson-videos only): 1. `mc anonymous set download` on `blogs` and `lesson-videos` **only** 2. backfills current (blogs ✓ 2026-07-14; lesson-videos empty = empty disk, nothing to serve yet) 3. #529 phase 2 for the writer-side binds 4. then drop the root bind → Angular container volume-free This stays the LAST bind in the epic, per the original sequencing.
Sign in to join this conversation.