# hyeonsangjeon/channel-vault-nas architecture

> Architecture diagram of the GitHub repository hyeonsangjeon/channel-vault-nas, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-23). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/hyeonsangjeon/channel-vault-nas
- Repository: https://github.com/hyeonsangjeon/channel-vault-nas

## Overview

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: [`backend/app/database.py`](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/database.py) (SQLite), [`backend/app/routers`](https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/routers) (Routers), [`backend/app/services`](https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/services) (Services), [`backend/app/services/channel_sync.py`](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/channel_sync.py) (Channel Sync)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_frontend["Frontend"]
  node_browser(("Browser<br/>client"))
  node_spa["React UI<br/>frontend SPA<br/>[App.tsx]"]
  node_nginx["Nginx<br/>web reverse proxy<br/>[nginx.conf]"]
end

subgraph group_backend["Backend"]
  node_api["FastAPI<br/>API server<br/>[main.py]"]
  node_routers["Routers<br/>HTTP endpoints"]
  node_services["Services"]
  node_schemas["Schemas<br/>API contracts"]
  node_models["Models"]
  node_security{{"Token Gate<br/>auth guard<br/>[security.py]"}}
  node_sync["Channel Sync<br/>ingest pipeline<br/>[channel_sync.py]"]
  node_queue["Queue & Jobs<br/>job state<br/>[download_queue.py]"]
  node_library["Library Index<br/>filesystem index<br/>[library_index.py]"]
  node_storage["Storage Checks<br/>drift and pressure<br/>[storage_scanner.py]"]
end

subgraph group_data["State & Storage"]
  node_sqlite[("SQLite<br/>metadata index<br/>[database.py]")]
  node_archive[("NAS Archive<br/>filesystem archive")]
end

subgraph group_ops["Operations"]
  node_scheduler["Schedulers<br/>background triggers"]
  node_worker["Download Worker<br/>job executor<br/>[download_worker.py]"]
  node_tools{{"Media Tools<br/>external binaries<br/>[ytdlp_probe.py]"}}
  node_demo["Demo Workspace<br/>seed flow<br/>[demo_workspace.py]"]
end

node_browser -->|"use"| node_spa
node_spa -->|"served by"| node_nginx
node_nginx -->|"proxy /api /ws"| node_api
node_api -->|"exposes"| node_routers
node_routers -->|"guarded by"| node_security
node_routers -->|"validates"| node_schemas
node_routers -->|"delegates to"| node_services
node_services -->|"persists via"| node_models
node_models -->|"stored in"| node_sqlite
node_services -->|"reads/writes"| node_archive
node_services -->|"invokes"| node_tools
node_scheduler -->|"feeds"| node_queue
node_scheduler -->|"triggers"| node_sync
node_queue -->|"consumed by"| node_worker
node_worker -->|"executes"| node_tools
node_sync -->|"indexes"| node_sqlite
node_sync -->|"stages into"| node_archive
node_library -->|"scans"| node_archive
node_library -->|"reconciles"| node_sqlite
node_storage -->|"checks"| node_archive
node_storage -->|"snapshots"| node_sqlite
node_demo -->|"seeds"| node_sqlite
node_demo -->|"fixtures"| node_archive

click node_spa "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/frontend/src/App.tsx"
click node_nginx "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/frontend/nginx.conf"
click node_api "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/main.py"
click node_routers "https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/routers"
click node_services "https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/services"
click node_schemas "https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/schemas"
click node_models "https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/models"
click node_security "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/security.py"
click node_sqlite "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/database.py"
click node_scheduler "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/download_scheduler.py"
click node_worker "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/download_worker.py"
click node_tools "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/ytdlp_probe.py"
click node_sync "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/channel_sync.py"
click node_queue "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/download_queue.py"
click node_library "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/library_index.py"
click node_storage "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/storage_scanner.py"
click node_demo "https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/demo_workspace.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_browser,node_spa,node_nginx toneBlue
class node_api,node_routers,node_services,node_schemas,node_models,node_security,node_sync,node_queue,node_library,node_storage toneAmber
class node_sqlite,node_archive toneMint
class node_scheduler,node_worker,node_tools,node_demo toneRose
```

## Components

### Frontend

Operator SPA and browser-facing UI

- **Browser**: client · Operator or demo user accessing the console
- [React UI](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/frontend/src/App.tsx): frontend SPA · Single-page console for dashboard, channels, queue, library, insights, and settings · `frontend/src/App.tsx`
- [Nginx](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/frontend/nginx.conf): web reverse proxy · Serves the compiled SPA and proxies API and websocket traffic · `frontend/nginx.conf`

### Backend

FastAPI control plane and business logic

- [FastAPI](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/main.py): API server · Backend application entrypoint and control plane · `backend/app/main.py`
- [Routers](https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/routers): HTTP endpoints · Dashboard, channels, library, jobs, storage, operations, settings, imports, videos, events, and health APIs · `backend/app/routers`
- [Services](https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/services): domain services · Business logic for sync, queueing, indexing, storage reconciliation, demos, and support · `backend/app/services`
- [Schemas](https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/schemas): API contracts · Request and response shapes shared across endpoints · `backend/app/schemas`
- [Models](https://github.com/hyeonsangjeon/channel-vault-nas/tree/main/backend/app/models): persistence models · Database models for archive and runtime state · `backend/app/models`
- [Token Gate](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/security.py): auth guard · Optional operator-token access control for non-health routes · `backend/app/security.py`
- [Channel Sync](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/channel_sync.py): ingest pipeline · Registration, probing, metadata sync, and candidate generation for channels · `backend/app/services/channel_sync.py`
- [Queue & Jobs](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/download_queue.py): job state · Guarded candidate staging and job lifecycle tracking · `backend/app/services/download_queue.py`
- [Library Index](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/library_index.py): filesystem index · Scans, indexes, rescans, and reconciles archive contents against the database · `backend/app/services/library_index.py`
- [Storage Checks](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/storage_scanner.py): drift and pressure · 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](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/database.py): metadata index · Searchable database for channels, jobs, events, views, and storage snapshots · `backend/app/database.py`
- **NAS Archive**: filesystem archive · Durable media, thumbnails, subtitles, and sidecar files on mounted storage

### Operations

Schedulers, workers, runtime control, and support tooling

- [Schedulers](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/download_scheduler.py): background triggers · Download and metadata schedulers that launch bounded periodic work · `backend/app/services/download_scheduler.py`
- [Download Worker](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/download_worker.py): job executor · Consumes queued jobs and performs controlled downloads · `backend/app/services/download_worker.py`
- [Media Tools](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/ytdlp_probe.py): external binaries · yt-dlp, ffprobe, ffmpeg and related probes used during download and inspection · `backend/app/services/ytdlp_probe.py`
- [Demo Workspace](https://github.com/hyeonsangjeon/channel-vault-nas/blob/main/backend/app/services/demo_workspace.py): seed flow · Deterministic fixture workspace for onboarding and public demos · `backend/app/services/demo_workspace.py`

## Connections

- 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
