6.2 KiB
ROADMAP
Goal: Build a self-hosted security camera footage viewer — Go backend, modern dark UI, Docker Compose deployment, responsive for desktop and mobile.
Constraints
- Target hardware: Intel Atom (low CPU budget). Minimise transcoding at all costs.
- Video files are raw H.265 bitstreams (
.265). Browsers cannot play them natively. - Remuxing (copy stream into MP4 container) is acceptable — no re-encode.
- No Node.js build step. Tailwind CSS loaded from CDN.
Footage directory layout (read-only mount)
<FOOTAGE_ROOT>/
YYYYMMDD/
images/ A<YYYYMMDD><HHMMSS><seq>.jpg
record/ A<YYYYMMDD>_<HHMMSS>_<HHMMSS>.265
FOOTAGE_ROOTis configured as an env var / volume indocker-compose.yml.- The app must not write into the footage tree (thumbnails cached in memory).
Priority 1 — Project scaffold
Objective: Establish a working Go module, Dockerfile, and docker-compose skeleton so every subsequent task has a solid base to build on.
- Go module
github.com/domagojzecevic/cammonitor(or local path). cmd/server/main.goentry point,chirouter wired up, static health endpoint.- Multi-stage
Dockerfile(builder →gcr.io/distroless/staticordebian:slimfor ffmpeg). docker-compose.ymlwith:appservice (Go binary)FOOTAGE_ROOTvolume bind-mount configurable via.envDB_PATHfor SQLite file- Port mapping (default
8080)
README.mdwith quick-start instructions.
Priority 2 — Auth (multi-user, SQLite)
Objective: Secure the app behind a login wall with session cookies and an admin user-management page.
- SQLite schema:
userstable (id, username, bcrypt_password_hash, is_admin, created_at). - SQLite schema:
sessionstable (token, user_id, expires_at). - First-run bootstrap: if no users exist, create an admin user from env vars
(
ADMIN_USER,ADMIN_PASS). - Login page (
/login): username + password form, setssessioncookie on success. - Logout endpoint (
/logout). - Auth middleware protecting all non-login routes.
- Admin user-management page (
/admin/users): list, add, delete users (admin only). - Session expiry configurable via env var (
SESSION_TTL, default 24 h).
Priority 3 — Footage scanner
Objective: Efficiently list available days, images, and videos from the footage tree.
- On startup and periodically (configurable interval, default 5 min), scan
FOOTAGE_ROOT. - Build an in-memory index:
map[date]DayEntrywhereDayEntryholds sorted slices of image paths and video paths. - Expose a simple internal API used by HTTP handlers to query the index.
- Parse filenames to extract timestamps for display and sorting.
- Handle missing
images/orrecord/sub-directories gracefully.
Priority 4 — Image browser
Objective: Browse JPEG images for a selected day with thumbnail strip and arrow navigation.
- Route
/day/{date}/images— lists all images for the day. - Thumbnail endpoint
/thumb/image/{relpath}: resize JPEG to 160×90 px in Go (golang.org/x/imageor stdlibimage+nfnt/resizeor manual scaling), cache result in memory (bounded LRU, max 500 entries). - Full-image endpoint
/raw/image/{relpath}: serve JPEG directly from disk (no processing). - UI:
- Fixed top strip showing all image thumbnails for the day (horizontally scrollable).
- Main area shows the currently selected image at full width.
- Left / right arrow buttons (keyboard and on-screen) to step through images.
- Active thumbnail highlighted in the strip.
- Deep-linkable URL per image (
?idx=N).
Priority 5 — Video browser
Objective: Browse and play H.265 videos for a selected day with thumbnail strip, arrow navigation, and on-demand remux streaming.
- Route
/day/{date}/videos— lists all videos for the day. - Thumbnail endpoint
/thumb/video/{relpath}: extract first frame viaffmpeg -i <pipe or file> -vframes 1 -f image2 pipe:1, scale to 160×90, cache in memory (bounded LRU, max 200 entries). - Stream endpoint
/stream/video/{relpath}:ffmpeg -i {input} -c:v copy -movflags frag_keyframe+empty_moov -f mp4 pipe:1piped directly to the HTTP response (Content-Type: video/mp4). No disk writes. One ffmpeg process per active stream (kill on client disconnect). - UI:
- Fixed top strip showing all video thumbnails for the day (horizontally scrollable, shows clip duration parsed from filename).
- Main area: HTML5
<video>player (controls, autoplay on navigation). - Left / right arrow buttons (keyboard and on-screen) to step through videos.
- Active thumbnail highlighted in the strip.
- Deep-linkable URL per video (
?idx=N).
Priority 6 — Responsive layout & day navigation
Objective: Unified dark-theme shell that adapts to desktop and mobile viewports.
- Tailwind CSS dark palette (slate-900 background, slate-700 cards, accent indigo-500).
- Desktop (≥ 768 px): collapsible left sidebar listing available days grouped by month; clicking a day navigates to its images or videos tab.
- Mobile (< 768 px): sidebar hidden; top navigation bar with a hamburger/date picker drawer; bottom tab bar for Images / Videos switch.
- Shared header: app name, logged-in username, logout button.
- Smooth transitions between images/videos on the same day without full-page reloads (HTMX swap or minimal vanilla JS fetch).
- Keyboard shortcuts:
←/→navigate items,I/Vswitch tabs.
Acceptance Criteria (overall)
docker compose upstarts a working app with no extra steps beyond setting env vars.- Login required before any footage is visible.
- Images load directly from disk with no processing delay.
- Videos play in the browser within ~2 s of clicking (remux start latency).
- Thumbnail strip visible and scrollable on both desktop and mobile.
- Arrow navigation works on desktop (keyboard) and mobile (touch buttons).
- All pages pass basic responsiveness check at 375 px and 1280 px viewport widths.
go vet ./...andgo test ./...pass cleanly.
Out of scope (this cycle)
- Live / real-time camera feed.
- Motion detection or alerting.
- Video download / export.
- Per-camera filtering (footage layout assumed to be one camera per day folder for now).
- HTTPS termination (expected to sit behind a reverse proxy or Tailscale).