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).
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.
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.)
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.
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.
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.
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
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):
mc anonymous set download on blogs and lesson-videosonly
backfills current (blogs ✓ 2026-07-14; lesson-videos empty = empty disk, nothing to serve yet)
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Parent epic: #413. Migrates LAST (per
docs/minio-storage-migration.md).Scope: the
spikersoft-angular/docker-stack.ymlroot 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_passlocation 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).
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.
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.)
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.
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.
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.
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
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.configon angular master: #177's deny block live and now includesebooks(added with the #618 read-authz work) —location ~ ^/spikersoft/(quarantine|uploads|dlls|assets|ai|art_pipe|ebooks) → 404spikersoft-angular/docker-stack.ymlConsequence for the #178 re-apply: the original gate list said
mc anonymous set downloadon blogs/ebooks/lesson-videos. Doing that forebookstoday 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):
mc anonymous set downloadonblogsandlesson-videosonlyThis stays the LAST bind in the epic, per the original sequencing.