How hyeonsangjeon/channel-vault-nas works
1. What this repo is
Channel Vault NAS is a two-tier self-hosted system for managing YouTube channel archives on a NAS. The architecture is centered on a FastAPI backend and a React/Vite frontend, with the filesystem as the durable archive and SQLite as the searchable index.
2. Top-level deployment shape
The runtime is split into two containers/services: api and web. The web container serves the compiled SPA through nginx and proxies /api and /ws to the backend. Persistent data is bind-mounted into separate host folders for:
- SQLite metadata
- downloaded media/archive files
- runtime overrides such as .env.runtime
3. Backend core layers
The backend follows a clear layered structure:
- routers/ expose HTTP endpoints for dashboard, channels, library, queue/jobs, storage, operations, settings, imports, videos, events, and health.
- schemas/ define API payloads.
- models/ hold persistence models.
- services/ contain most business logic.
- security.py enforces the optional operator token gate.
4. Primary domain model
The main domain is not “video downloading” in isolation; it is archive state management. Key entities and concepts include:
- channels and source registration
- metadata sync state
- missing/archived/queued video candidates
- worker runs and scheduler ticks
- library index entries for files and sidecars
- storage pressure/drift/orphan detection
- runtime settings and audit events
5. Main data flow: channel to archive
The central flow is:
1. register or probe a channel source
2. sync metadata
3. derive missing video candidates
4. stage jobs into a guarded download queue
5. run bounded worker passes for real downloads when enabled
6. write media and sidecars into the NAS folder structure
7. rescan/index the filesystem so the UI reflects actual archive state
6. Scheduler and worker subsystem
Downloads and metadata sync are both driven by background services:
- download_scheduler.py and metadata_scheduler.py trigger bounded periodic work
- download_worker.py performs queued jobs
- worker behavior is controlled by runtime flags and per-channel policy
This makes the system intentionally conservative: candidate generation can continue even when execution is paused.
7. Filesystem-first archive management
The archive layout is the canonical source of truth. Services such as storage_scanner, library_index, archive_rescan, archive_paths, storage_orphans, storage_pressure, and storage_drift turn real NAS folders into indexed state. The app reads media files, thumbnails, subtitles, and info.json sidecars from disk first, then stores only enough metadata in SQLite to search, filter, recover, and reconcile.
8. Library and recovery boundaries
The library subsystem treats “present on disk” and “known in database” as separate concerns. It supports:
- indexing existing NAS folders without re-downloading
- per-file preview through streaming endpoints
- saved views and portable view export/import
- detection of stale DB rows, missing files, and orphan sidecars
This is why recovery is possible even if the database is lost: the database can be rebuilt from the archive root.
9. Queue, jobs, and event tracking
Queue visibility is an explicit subsystem. jobs, download_queue, event_bus, and event_log track candidate creation, queued/running/completed/failed/cancelled states, worker summaries, scheduler activity, and audit/history surfaces. The UI uses this to show what changed and why, rather than hiding state inside downloader internals.
10. Settings and runtime control
Runtime configuration is partly static .env and partly mutable .env.runtime. The Settings area exposes:
- worker/scheduler enablement
- binary paths for yt-dlp, ffprobe, and related tools
- restart guidance and restart adapters for Docker Compose, systemd, supervisor, Synology, and QNAP
- runtime audit/tick logs
This is a control plane for operators, not a full admin/auth subsystem.
11. Frontend architecture
The frontend is a single-page operator console with major areas for Dashboard, Channels, Queue, Library, Insights, and Settings. It uses:
- React + Vite
- locale bundles for multiple languages
- feature-specific components like metrics tiles, queue flow, and channel constellation views
The UI is designed to reflect backend state and filesystem state, not to own business logic.
12. Demo and seed paths
There is a safe demo workspace flow in both backend and frontend. It seeds deterministic fixture data without contacting YouTube or starting downloads. This is important architecturally because the product supports empty-install onboarding and public demos without touching real archives.
13. Supporting infrastructure
The repo includes:
- Alembic migrations for database evolution
- Dockerfiles and Compose for local/NAS deployment
- GitHub Actions for CI, Pages docs, and release images
- Playwright-based browser smoke and screenshot fixtures
- scripts for deployment smoke, compose smoke, and public demo capture
These are not core product logic, but they strongly shape how the system is validated and released.
14. Security and exposure boundary
The project is explicitly guarded for localhost, LAN, VPN, or trusted reverse proxy use. The optional CVN_AUTH_TOKEN is an operator gate, not multi-user auth. The health endpoint remains open, while other API routes can require the token. This is a deliberate architectural boundary: the app assumes trusted network placement rather than public-internet hardening.
15. How to diagram it
A correct architecture diagram should show:
- browser SPA on top
- backend API as the central control plane
- SQLite as metadata/index storage
- NAS filesystem as the durable archive
- yt-dlp/ffmpeg/ffprobe as external worker tools
- background schedulers feeding queue and sync flows
- storage/library scanners reconciling disk with database
- demo/seed, settings, and support/export paths as supporting subsystems
Main entry points: database.py SQLite · routers Routers · services Services · channel_sync.py Channel Sync
Components 19
Frontend
Operator SPA and browser-facing UI
Backend
FastAPI control plane and business logic
- FastAPI · Backend application entrypoint and control plane
backend/app/main.py - Routers · Dashboard, channels, library, jobs, storage, operations, settings, imports, videos, events, and health APIs
backend/app/routers - Services · Business logic for sync, queueing, indexing, storage reconciliation, demos, and support
backend/app/services - Schemas · Request and response shapes shared across endpoints
backend/app/schemas - Models · Database models for archive and runtime state
backend/app/models - Token Gate · Optional operator-token access control for non-health routes
backend/app/security.py - Channel Sync · Registration, probing, metadata sync, and candidate generation for channels
backend/app/services/channel_sync.py - Queue & Jobs · Guarded candidate staging and job lifecycle tracking
backend/app/services/download_queue.py - Library Index · Scans, indexes, rescans, and reconciles archive contents against the database
backend/app/services/library_index.py - Storage Checks · Orphan detection, drift analysis, pressure forecasting, and mount health
backend/app/services/storage_scanner.py
State & Storage
SQLite index and NAS archive on disk
- SQLite · Searchable database for channels, jobs, events, views, and storage snapshots
backend/app/database.py - NAS Archive · Durable media, thumbnails, subtitles, and sidecar files on mounted storage
Operations
Schedulers, workers, runtime control, and support tooling
- Schedulers · Download and metadata schedulers that launch bounded periodic work
backend/app/services/download_scheduler.py - Download Worker · Consumes queued jobs and performs controlled downloads
backend/app/services/download_worker.py - Media Tools · yt-dlp, ffprobe, ffmpeg and related probes used during download and inspection
backend/app/services/ytdlp_probe.py - Demo Workspace · Deterministic fixture workspace for onboarding and public demos
backend/app/services/demo_workspace.py
Connections 23
- Browser → React UI: use · Users interact with the SPA
- React UI → Nginx: served by · Compiled frontend is delivered through nginx
- Nginx → FastAPI: proxy /api /ws · Frontend proxy forwards API and websocket traffic
- FastAPI → Routers: exposes · HTTP routes are mounted into the backend app
- Routers → Token Gate: guarded by · Most routes can require the operator token
- Routers → Schemas: validates · Endpoint payloads use shared schemas
- Routers → Services: delegates to · Routers hand off business logic to services
- Services → Models: persists via · Services read and write domain models
- Models → SQLite: stored in · SQLite holds searchable metadata and runtime state
- Services → NAS Archive: reads/writes · Archive files and sidecars live on the NAS filesystem
- Services → Media Tools: invokes · Media probing and downloads use external binaries
- Schedulers → Queue & Jobs: feeds · Periodic work stages jobs and sync candidates
- Schedulers → Channel Sync: triggers · Schedulers start metadata sync and candidate generation
- Queue & Jobs → Download Worker: consumed by · Queued jobs are executed by the worker
- Download Worker → Media Tools: executes · Worker uses yt-dlp/ffmpeg/ffprobe during downloads
- Channel Sync → SQLite: indexes · Channel and candidate state is tracked in the database
- Channel Sync → NAS Archive: stages into · Downloaded media is written into archive paths on disk
- Library Index → NAS Archive: scans · Filesystem-first inventory and rescan operate on the NAS archive
- Library Index → SQLite: reconciles · Disk state is compared with indexed metadata
- Storage Checks → NAS Archive: checks · Storage drift, orphans, and pressure are derived from disk state
- Storage Checks → SQLite: snapshots · Storage findings are recorded for search and history
- Demo Workspace → SQLite: seeds · Demo workspace creates deterministic database fixtures
- Demo Workspace → NAS Archive: fixtures · Demo flow can populate safe archive-like data