# Plan Status: **active** Goal: implement the scope defined in `ROADMAP.md` — a small, login-gated, container-based file browser that lets an admin mount arbitrary host folders, browse them in a light/dark web UI, and download or stream video files with zero server-side transcoding. ## Scope - Go backend, SQLite for users/sessions, server-rendered HTML templates + minimal vanilla JS, single Docker image, plain HTTP behind a reverse proxy. - Zero unauthenticated access to any route except the login page: login is gated first, then a mandatory password-change gate for any account with `must_change_password=true` (including the seeded admin), then file browsing/download/streaming for both `admin` and `user` roles. - Multiple named roots are Docker volume mounts declared in a config file/env var (container path + display name), read at startup; adding a root requires a config edit + container restart (documented limitation, not solved in-app). - Read-only browsing: list folders/files, breadcrumb navigation, strict containment inside each root (no traversal outside the mount, no symlink escape). - Download and video streaming serve raw bytes with full HTTP `Range` support; no transcoding, thumbnailing, or format conversion anywhere. - Admin-only user management: create/list/reset-password/disable users; non-admin gets 403 on admin routes. - Documentation: `README.md` is kept current with every behavior/interface/ workflow/config change in the same commit that makes the change (no follow-up doc tasks). - Local-testing-only opt-in to disable the `Secure` session-cookie attribute (default: secure/on), so login works over plain `http://` for local development without a reverse proxy. ## Out of scope (this cycle) - Per-user/per-root permission restrictions. - Any write operations from the UI (upload/rename/delete/move). - Cross-folder search. - App-managed TLS (assumed reverse proxy). ## Acceptance Criteria - No file listing, file content, download, stream, or admin route is ever reachable without a valid authenticated session — verified by an explicit test hitting each such route unauthenticated and expecting a redirect/401, never data. - Login with the seeded admin (`boss@zecevic-ninja.com` / `Start1234!`) forces a password change before any other page is reachable, and this same forced-change gate applies to any user created later with a temporary password. - Both `admin` and `user` roles can browse, download, and stream all configured roots; only `admin` can reach user-management screens. - Downloaded file bytes are byte-identical to the source file; video `Range` requests return `206 Partial Content` with correct byte ranges, enabling native browser seeking with no server-side transcoding. - Path handling cannot escape a configured root via `../` traversal or symlink. - `docker build` + `docker run` (with a sample volume mount and root config) produces a working, login-gated instance, and `README.md` is sufficient for an operator to deploy it without reading source. ## Implementation Phases ### Phase 1 — Scaffolding & config (T-001) - Go module layout, root-config loading (container path + display name from file/env), Dockerfile skeleton, docker-compose example, README skeleton. ### Phase 2 — Data & auth foundation (T-002) - SQLite schema/migrations for users + sessions, bcrypt hashing, seed admin with `must_change_password=true`, session-cookie middleware, login/logout, forced password-change gate blocking all other routes. ### Phase 3 — Folder browsing UI (T-003) - Root listing, directory navigation with breadcrumbs, strict path containment, light/dark theme, minimal vanilla JS/CSS. ### Phase 4 — Download & video streaming (T-004) - Raw-byte download endpoint, full HTTP Range support for video seek/resume, zero transcoding. ### Phase 5 — Admin user management (T-005) - Admin-only list/create/reset/disable-user screens, role-based route protection (403 for non-admin). ### Phase 6 — Packaging & docs finalization (T-006) - Finalize multi-stage Dockerfile and docker-compose example, complete `README.md` end to end (build/run, root config format, first-login/admin credential flow, forced password change, reverse-proxy TLS + Range passthrough requirement). ### Phase 7 — Local-testing insecure-cookie opt-in (T-007) - Added after T-001..T-006 shipped: an explicit, off-by-default env var that disables the `Secure` attribute on the session cookie so login works over plain `http://` for local development (e.g. non-`https://` access, including non-`localhost` origins where the browser doesn't treat the origin as a secure context). Default behavior (Secure cookie) is unchanged for anyone who doesn't set the var. `README.md` gets a new "Local development over HTTP" section spelling out that this switch must never be enabled on a real/production/exposed deployment. ### Phase 8 — Fix video type detection for real containers (T-008) - Bug fix against the shipped T-004 behavior: `isVideoFile` and the `Content-Type` served for downloads/streams currently rely on `mime.TypeByExtension`, which returns nothing useful for `.mp4`/`.mkv`/etc. inside the actual Alpine-based runtime image (no OS MIME database present, and Go's built-in fallback table has no video entries). Replace this with an app-internal extension → MIME-type table covering at least `.mp4`, `.m4v`, `.mov`, `.webm`, `.mkv`, `.avi`, `.mpg`/`.mpeg`, `.wmv`, `.flv`, used consistently for both the "stream" link visibility check and the `Content-Type` header on file delivery, independent of the OS/container MIME database. `README.md` documents the supported video extensions and the `.mkv`-in-browser caveat (downloads always work; native in-browser playback of the MKV container is browser-dependent and not guaranteed, since no transcoding is performed per the project's no-encoding requirement). Tasks are sequential (T-001 → T-006); T-007 and T-008 are additive follow-on tasks built on top of the already-shipped T-002 and T-004 work, respectively. Each phase's acceptance criteria and required documentation updates are tracked per-task in `.ai/TASKS.md`. ## Validation Run before finishing each task, broader pass before commit: - `go fmt ./...` - `go vet ./...` - `go test ./...` Task-specific manual/E2E checks (documented per task in `.ai/TASKS.md`): - Unauthenticated access attempts against every protected route. - Forced password-change flow for the seeded admin and for a newly created user. - Path-traversal / symlink-escape attempts against the browsing routes. - Checksum comparison of a downloaded file against its source. - `Range`-header request against a video file, confirming `206 Partial Content` and correct byte range. - `docker build` + `docker run` smoke test with a sample mounted folder. - With the insecure-cookie opt-in unset (default), confirm the session cookie still has `Secure` set. With it explicitly enabled, confirm login over plain `http://` succeeds and the cookie no longer has `Secure` set, while `HttpOnly`/`SameSite`/expiry/forced-password-change behavior is unchanged. - Inside the actual production Docker image (not the `golang:*` build image, which does have a fuller MIME setup) — request an `.mp4` file and confirm `Content-Type: video/mp4` and a visible "stream" link; same for `.mkv` with the correct Matroska `Content-Type`; confirm a `Range` request against the `.mp4` still returns `206 Partial Content`.