# Architecture
## Overview
```
┌─────────────────────────────────────────────────────────────┐
│ cctv binary │
│ │
│ ┌───────┐ ┌────────┐ ┌────────┐ ┌───────────────┐ │
│ │ CLI │──►│ Config │──►│ Daemon │──►│ TUI / Dump / │ │
│ │ parse │ │ load │ │ init │ │ Daemonize │ │
│ └───────┘ └────────┘ └────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
│
HTTP + SSE
│
┌───────────▼──────────┐
│ coolercontrold │
│ (system daemon) │
└──────────────────────┘
```
## Layered Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ TUI Layer tui/ │
│ ┌────────┐ ┌─────────┐ ┌──────┐ ┌───────┐ ┌──────┐ │
│ │ Device │ │ Profile │ │ Mode │ │ Alert │ │ Task │ │
│ │ Page │ │ Page │ │ Page │ │ Page │ │ Page │ │
│ └───┬────┘ └────┬────┘ └──┬───┘ └───┬───┘ └──┬───┘ │
│ │ │ │ │ │ │
│ └───────────┴────┬────┴─────────┴─────────┘ │
│ ▼ │
│ daemon.state() (read) │
│ daemon.change_mode() (write) │
│ daemon.update_profile() (write) │
├──────────────────────────────────────────────────────────────┤
│ Daemon Layer daemon/ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Daemon { client, config, profiles, modes, state } │ │
│ │ │ │ │ │
│ │ │ Arc<RwLock<State>> │ │
│ │ │ ┌─────────────────┐ │ │
│ │ │ │ devices, alerts, │ │ │
│ │ │ │ current_mode_uid,│ │ │
│ │ │ │ tasks │ │ │
│ │ │ └────────▲─────────┘ │ │
│ │ │ │ Message │ │
│ │ │ ┌────────┴─────────┐ │ │
│ │ │ │ message handler │ │ │
│ │ │ │ thread │ │ │
│ │ │ └────────▲─────────┘ │ │
│ │ │ │ mpsc │ │
│ │ │ ┌─────────────┼──────────┐ │ │
│ │ │ ┌───┴───┐ ┌──────┴──┐ ┌─────┴──┐ │ │
│ │ │ │ SSE │ │ SSE │ │ SSE │ │ │
│ │ │ │ modes │ │ status │ │ alerts │ │ │
│ │ │ └───┬───┘ └────┬────┘ └────┬───┘ │ │
│ │ │ └───────────┼───────────┘ │ │
│ └───────────┼─────────────────────┼──────────────────┘ │
├──────────────┼─────────────────────┼─────────────────────────┤
│ Domain Layer │ domain/ │ │
│ │ │ │
│ Device::load() Profile::load() Mode::load() Alert::load()
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ api structs ──► domain structs (richer models) │
├──────────────────────────────────────────────────────────────┤
│ API Layer api/ │
│ │
│ Raw serde types: Devices, CCStatus, Profiles, Functions, │
│ Modes, Alerts, Sse │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Client { agent, url, cookie } │ │
│ │ GET/POST/PUT ──► coolercontrold HTTP API │ │
│ └───────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
## SSE Event Flow
```
coolercontrold cctv
───────────── ──────────────────────
GET /sse/modes ◄──────────── SSE thread (modes)
GET /sse/status ◄──────────── SSE thread (status)
GET /sse/alerts ◄──────────── SSE thread (alerts)
│ │
│ event: mode/status/alert │
│ data: { json } │
├────────────────────────────►│
│ ▼
│ parse into Sse enum
│ │
│ Message::Sse(event)
│ │ mpsc channel
│ ▼
│ ┌──────────────┐
│ │ msg handler │
│ │ thread │
│ └──────┬───────┘
│ │
│ ┌──────────────┼──────────────┐
│ ▼ ▼ ▼
│ handle_mode handle_status handle_alert
│ │ │ │
│ ▼ ▼ ▼
│ update mode merge into update state
│ UIDs in State device history + append logs
│ │ │ │
│ └──────┬───────┘ │
│ ▼ │
│ run_matching_tasks() ◄────────┘
│ (leader only)
│ │
│ ┌──────┴──────┐
│ ▼ ▼
│ Script exec ChangeMode / ActivatePrevious
│ │
│ POST /modes-active/{uid} │
│ ◄─────────────────────────┘
```
## Task Automation
```
┌─────────────┐ ┌───────────────────┐ ┌──────────────────────┐
│ Trigger │ │ Task │ │ Action │
├─────────────┤ ├───────────────────┤ ├──────────────────────┤
│ Alert │────►│ name │────►│ Script { path } │
│ { uid, │ │ state (lifecycle) │ │ ChangeMode { uid } │
│ state } │ │ trigger │ │ ActivatePreviousMode │
│ │ │ action │ └──────────────────────┘
│ ModeChange │ └───────────────────┘
│ { uid } │
└─────────────┘
Task lifecycle: Applied ──► Draft ──► Unapplied ──► Applied
└──► Deleted
Leader election: abstract unix socket "cctv-{url}"
only the leader instance executes tasks
```
## Config Resolution
```
┌──────────────────────────────────────┐
│ /etc/coolercontrol/config.toml │ daemon_address, port
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ $XDG_CONFIG_HOME/coolercontrol/ │ time_range_s, username,
│ cctv.json │ skip_splash, tasks,
│ (or $CCTV_CONFIG_FILEPATH) │ address/port overrides
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ Environment variables │ CCTV_DAEMON_PASSWORD
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────┐
│ Config │
│ { url, │
│ time_range, username, password, │
│ skip_splash, tasks } │
└──────────────────────────────────┘
```
## Directory Structure
```
src/
main.rs Entry point, CLI dispatch
cli.rs CLI subcommands
client.rs HTTP client wrapper
logging.rs Logging setup
config/
mod.rs Config loading and saving
api/ Raw wire types, deserialization only
alert.rs
device.rs
function.rs
mode.rs
profile.rs Profile PUT shim
sse.rs
status.rs
mod.rs
domain/ Domain types and business logic created from API data
alert.rs
device.rs
mode.rs
profile.rs
mod.rs
daemon/ Runtime state and coordination
cctv.rs Daemon struct, init, refresh
state.rs Mutable state, SSE handlers
client.rs SSE stream reader
task.rs Task, Trigger, Action types
mod.rs
tui/
mod.rs Render and input loop
pages/
mod.rs Page trait, shared state
alert.rs
device.rs
mode.rs
profile.rs Profile editor
task.rs Task editor
```