cctv-stack 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/.gitignore +9 -3
  2. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/PKG-INFO +198 -123
  3. cctv_stack-0.3.1/README.md +251 -0
  4. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/pyproject.toml +9 -1
  5. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/__init__.py +2 -2
  6. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/atomic.py +3 -3
  7. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/cleanup.py +6 -5
  8. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/clip.py +2 -2
  9. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/config.py +45 -6
  10. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/config_cli.py +82 -21
  11. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/db.py +2 -2
  12. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/ffprobe.py +3 -2
  13. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/health.py +2 -2
  14. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/paths.py +6 -6
  15. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/storage.py +9 -9
  16. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/timeutil.py +4 -4
  17. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/uploader.py +5 -5
  18. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/worker.py +1 -1
  19. cctv_stack-0.3.0/README.md +0 -178
  20. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/LICENSE +0 -0
  21. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/dispatch.py +0 -0
  22. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/frigate.py +0 -0
  23. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/logging.py +0 -0
  24. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/materialise.py +0 -0
  25. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/migrations/0001_init.sql +0 -0
  26. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/migrations/0002_finding_kinds.sql +0 -0
  27. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/mqtt.py +0 -0
  28. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/reviews.py +0 -0
  29. {cctv_stack-0.3.0 → cctv_stack-0.3.1}/src/cctv/sweep.py +0 -0
@@ -14,6 +14,13 @@ venv/
14
14
  .ruff_cache/
15
15
  .mypy_cache/
16
16
 
17
+ # Editor state is per-machine, not project configuration
18
+ .vscode/
19
+
20
+ # Personal agent memory and local agent settings. This repository is public; nothing here is
21
+ # project configuration and nothing here belongs in its history.
22
+ .claude/
23
+
17
24
  # uv.lock is deliberately COMMITTED — do not ignore it (ADR-0018).
18
25
  .uv-cache/
19
26
 
@@ -51,6 +58,5 @@ config/frigate.generated.yml
51
58
  # deliberately ignored and is deleted once its VERIFY items are measurements.
52
59
  spike/
53
60
 
54
- # dev-synthetic/ is **committed**. It is the camera-free harness M2 requires and the
55
- # stack the e2e CI job runs. Its own .gitignore keeps the generated fixture, runtime
56
- # data and Frigate's state out of git while leaving the harness itself reviewable.
61
+ # Internal planning and notes — not for public consumption.
62
+ .private/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cctv-stack
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: A packaging-first self-hosted CCTV stack built on Frigate.
5
5
  Project-URL: Homepage, https://github.com/aravindmyd/cctv-stack
6
6
  Project-URL: Repository, https://github.com/aravindmyd/cctv-stack
@@ -222,6 +222,8 @@ Requires-Python: >=3.12
222
222
  Requires-Dist: pyyaml>=6.0
223
223
  Requires-Dist: tzdata>=2024.1
224
224
  Provides-Extra: dev
225
+ Requires-Dist: mkdocs-material>=9.6; extra == 'dev'
226
+ Requires-Dist: mkdocs<2,>=1.6; extra == 'dev'
225
227
  Requires-Dist: pytest>=8.0; extra == 'dev'
226
228
  Requires-Dist: ruff>=0.6; extra == 'dev'
227
229
  Provides-Extra: mqtt
@@ -232,179 +234,252 @@ Description-Content-Type: text/markdown
232
234
 
233
235
  # cctv-stack
234
236
 
237
+ [![PyPI](https://img.shields.io/pypi/v/cctv-stack.svg)](https://pypi.org/project/cctv-stack/)
238
+ [![Python versions](https://img.shields.io/pypi/pyversions/cctv-stack.svg)](https://pypi.org/project/cctv-stack/)
235
239
  [![CI](https://github.com/aravindmyd/cctv-stack/actions/workflows/ci.yml/badge.svg)](https://github.com/aravindmyd/cctv-stack/actions/workflows/ci.yml)
236
- [![Publish](https://github.com/aravindmyd/cctv-stack/actions/workflows/publish.yml/badge.svg)](https://github.com/aravindmyd/cctv-stack/actions/workflows/publish.yml)
237
240
 
238
- Self-hosted CCTV built on **Frigate**, adding the four things Frigate deliberately leaves
239
- to the operator:
241
+ `cctv-stack` is a Python toolkit and container image for a self-hosted CCTV pipeline built
242
+ on **[Frigate](https://github.com/blakeblackshear/frigate)**.
240
243
 
241
- 1. **Canonical clips** — every review becomes one file at a deterministic path.
242
- 2. **Durable cloud backup** — clips are uploaded to Backblaze B2 exactly once.
243
- 3. **Independent retention** — local cleanup that never deletes an un-uploaded clip.
244
- 4. **Health surfaces** — each service reports whether it is actually working.
244
+ Frigate does camera ingest, detection, recording and review segmentation. `cctv-stack`
245
+ adds the four pieces it leaves to the operator:
245
246
 
246
- Every installation is independent: no central hub, no cross-site data, no inbound
247
- connectivity requirement.
247
+ 1. **Canonical clips** — one deterministic file per review item.
248
+ 2. **Durable backup** — clips are uploaded to Backblaze B2 exactly once.
249
+ 3. **Upload-aware retention** — local cleanup never deletes a clip that is not safely
250
+ uploaded.
251
+ 4. **Health surfaces** — the uploader and cleanup expose `/health` and `/metrics`.
252
+
253
+ It is not a replacement for Frigate. It is the missing layer around it.
254
+
255
+ **Current release: `0.3.0`.** Clips, B2 upload, local retention and health are
256
+ implemented. Cloud retention, an aggregate status page and the one-command installer are
257
+ not — see [`CONTRIBUTING.md`](CONTRIBUTING.md) for the exact state and roadmap.
248
258
 
249
259
  ---
250
260
 
251
- ## Status
261
+ ## Install
252
262
 
253
- | Capability | State |
254
- |---|---|
255
- | Detect → canonical clip | ✅ |
256
- | Upload to Backblaze B2, exactly once | ✅ |
257
- | Local retention (upload-aware) | ✅ |
258
- | Cloud retention and reconciliation | ⬜ |
259
- | Aggregate status page and alerting | ⬜ |
260
- | One-command client installer and signed bundle | ⬜ |
261
- | Backup, restore, upgrade | ⬜ |
263
+ ```bash
264
+ pip install cctv-stack
265
+ ```
262
266
 
263
- v0.3 is usable for a local-only installation or a B2-backed one. The deferred work, and
264
- why it is deferred, is in [`docs/plans/backlog.md`](docs/plans/backlog.md). The client-site
265
- delivery plan is [`docs/plans/packaging.md`](docs/plans/packaging.md).
267
+ Requires **Python 3.12+**. The distribution is `cctv-stack`; the import package is `cctv`.
266
268
 
267
- ## How it fits together
269
+ Optional extras:
268
270
 
269
- ```text
270
- cameras ──RTSP──> Frigate ──review──> clip-worker ──> data/clips/... ──> uploader ──> B2
271
- │ │
272
- └── recordings (recovery window) └── cleanup (only after upload)
271
+ | Extra | Adds | Needed by |
272
+ |---|---|---|
273
+ | `mqtt` | `paho-mqtt` | `cctv-clip-worker` when subscribing to `frigate/reviews` |
274
+ | `storage` | `boto3` | `cctv-uploader` for B2/S3 |
275
+ | `dev` | pytest, ruff | Contributors |
276
+
277
+ ```bash
278
+ pip install "cctv-stack[mqtt,storage]"
279
+ ```
280
+
281
+ ### Container image
282
+
283
+ ```bash
284
+ docker pull ghcr.io/aravindmyd/cctv-stack:0.3.0
273
285
  ```
274
286
 
275
- Frigate does camera ingest, detection, recording and review segmentation. The
276
- `clip-worker` cuts **one canonical clip per review item**. The uploader pushes it once,
277
- and cleanup deletes a local clip only after its upload is terminal-success (INV-1).
287
+ Tags published per release: `:0.3.0`, `:0.3`, and the commit tag `:sha-<short>`.
288
+ A moving `:edge` tag follows `master`. If the registry package is private, run
289
+ `docker login ghcr.io` first. The image includes `ffmpeg`/`ffprobe`.
278
290
 
279
- MQTT is the low-latency path. The Frigate review sweep is the guarantee: if the broker or
280
- the worker was down, the next sweep still materialises the clip. A review arriving twice
281
- produces one clip, not two.
291
+ ## Commands
292
+
293
+ | Command | What it does |
294
+ |---|---|
295
+ | `cctv-config validate --cameras cameras.yml` | Validate the camera configuration. Exits 78 (`EX_CONFIG`) on an invalid config |
296
+ | `cctv-config generate --cameras cameras.yml --out frigate.yml` | Generate Frigate's configuration from `cameras.yml` |
297
+ | `cctv-clip-worker` | Turn completed Frigate reviews into canonical clips |
298
+ | `cctv-uploader` | Upload clips to B2 through the persisted retry state machine |
299
+ | `cctv-cleanup` | Enforce local retention after a successful upload |
300
+
301
+ The three services are long-running and configuration-driven; they take no command-line
302
+ arguments. Run them under systemd, Docker, or Compose.
282
303
 
283
304
  ## Quick start
284
305
 
285
- **Requirements:** `git`, Linux x86_64 or arm64 (macOS is supported for development),
286
- Docker Engine with the Compose v2 plugin, and disk space for Frigate's recordings. The
287
- first real run pulls the Frigate image and builds the CCTV image, so expect several
288
- minutes.
306
+ The only required input is a `cameras.yml`. This minimal file is valid; `${VAR}` values
307
+ are read from the environment and an unset variable is a hard error.
289
308
 
290
- ```bash
291
- git clone https://github.com/aravindmyd/cctv-stack.git
292
- cd cctv-stack
293
- ./install.sh
309
+ ```yaml
310
+ site:
311
+ location: home
312
+ timezone: UTC
313
+
314
+ cameras:
315
+ front-door:
316
+ display_name: Front Door
317
+ rtsp:
318
+ main: ${CAMERA_FRONT_DOOR_RTSP}
319
+ sub: ${CAMERA_FRONT_DOOR_SUB}
294
320
  ```
295
321
 
296
- The first run creates the two files that only you can fill in, then exits:
322
+ Validate it and generate Frigate's config:
297
323
 
298
- | File | Contains | Committed? |
299
- |---|---|---|
300
- | `.env` | Camera RTSP URLs, timezone, optional B2 credentials | **No** — gitignored |
301
- | `config/cameras.yml` | Camera names, clip padding, retention policy | **No** — gitignored |
324
+ ```bash
325
+ export CAMERA_FRONT_DOOR_RTSP='rtsp://user:pass@192.168.1.10:554/stream1'
326
+ export CAMERA_FRONT_DOOR_SUB='rtsp://user:pass@192.168.1.10:554/stream2'
302
327
 
303
- Edit both, then run `./install.sh` again. It validates the configuration, generates
304
- Frigate's config, builds the image, starts the stack, and waits until Frigate answers.
328
+ cctv-config validate --cameras cameras.yml
329
+ cctv-config generate --cameras cameras.yml --out frigate.yml
330
+ ```
305
331
 
306
- Frigate's UI is HTTPS on `https://127.0.0.1:8971` (loopback only). The certificate is
307
- self-signed, so the browser warns once. Port 5000 and MQTT are never published.
332
+ `validate` checks the camera model and the environment substitution. `generate` writes a
333
+ Frigate config with a `GENERATED FILE — DO NOT EDIT` header.
308
334
 
309
- Clips land at:
335
+ ### Running the services
310
336
 
311
- ```text
312
- data/clips/<camera>/YYYY/MM/DD/HH-MM-SS.mp4
313
- data/clips/<camera>/YYYY/MM/DD/HH-MM-SS.mp4.json
314
- ```
337
+ ```bash
338
+ export CCTV_CONFIG_DIR="$PWD"
339
+ export CCTV_DATA_DIR="$PWD/data"
340
+ export CCTV_FRIGATE_URL="http://127.0.0.1:5000"
315
341
 
316
- The sidecar records the review, event ids, requested boundaries, coverage, size and
317
- SHA-256. The clips tree uses the **site timezone**; Frigate's own recordings tree uses
318
- **UTC** — both are deliberate ([ADR-0017](docs/adr/0017-time-handling.md)).
342
+ cctv-clip-worker
343
+ ```
319
344
 
320
- A clip appears **after** the visit ends, not during it: Frigate keeps the review open
321
- until the alert cutoff passes. There is no partial clip by design.
345
+ Run each service in its own process. `cctv-clip-worker` works without MQTT (sweep-only);
346
+ set `CCTV_MQTT_HOST` to receive reviews immediately. `cctv-uploader` reports `DISABLED`
347
+ and uploads nothing when `CCTV_STORAGE_BUCKET` is empty; a bucket with no key exits `78`.
322
348
 
323
- ## What runs
349
+ > `ffprobe` must be on `PATH` for the clip worker. The container image includes it; a
350
+ > `pip install` does not install ffmpeg.
324
351
 
325
- | Service | Role |
326
- |---|---|
327
- | **Frigate** | Camera ingest, detection, recording and review segmentation |
328
- | **clip-worker** | Materialises completed reviews at the canonical path, with a sidecar |
329
- | **uploader** | Uploads to B2 through a storage adapter, with a persisted retry state machine. Reports `DISABLED` when no bucket is configured and exits `78` on a half-configuration |
330
- | **cleanup** | Enforces local retention; refuses to delete a clip whose upload is not terminal-success |
331
- | **config** | One-shot generator: `cameras.yml` → Frigate's config (INV-3) |
332
- | **mosquitto** | MQTT broker, internal only |
333
- | **status-collector** | ⬜ not built yet |
352
+ ## Configuration
334
353
 
335
- ### Health surfaces
354
+ There are two kinds of configuration, and they must not be mixed:
336
355
 
337
- | Surface | Address | Notes |
356
+ | File | Holds | Secret? |
338
357
  |---|---|---|
339
- | Frigate UI | `https://127.0.0.1:8971` | HTTPS, self-signed, loopback only |
340
- | Uploader | `http://127.0.0.1:8082/health` | `DISABLED` without a B2 bucket |
341
- | Cleanup | `http://127.0.0.1:8083/health` | Local retention state |
342
- | Frigate API, MQTT | not published | Reachable only on the compose network |
358
+ | `cameras.yml` | Site name, timezone, cameras, clip policy, retention | No — but `${VAR}` values come from the environment |
359
+ | environment / `.env` | RTSP URLs, B2 keys, paths, ports, intervals | **Yes** |
343
360
 
344
- ## Configuration
361
+ The full template is [`.env.example`](.env.example). The settings most consumers need:
345
362
 
346
- The split is deliberate and load-bearing:
363
+ | Variable | Default | Meaning |
364
+ |---|---|---|
365
+ | `CCTV_CONFIG_DIR` | `/config` | Directory containing `cameras.yml` |
366
+ | `CCTV_DATA_DIR` | `/data` | Root for `clips/` and the state database |
367
+ | `CCTV_DB` | `$CCTV_DATA_DIR/cctv.db` | SQLite state store |
368
+ | `CCTV_FRIGATE_URL` | `http://frigate:5000` | Frigate API base URL. Port 5000 must stay on a private network |
369
+ | `CCTV_MQTT_HOST` | unset | Unset means sweep-only; no broker required |
370
+ | `CCTV_SWEEP_INTERVAL_S` | `60` | Worst-case delay before a missed review is noticed |
371
+ | `CCTV_STORAGE_BUCKET` | empty | Empty means cloud backup `DISABLED` |
372
+ | `CCTV_STORAGE_KEY_ID` | empty | B2 application key ID |
373
+ | `CCTV_STORAGE_APPLICATION_KEY` | empty | B2 application key |
374
+ | `CCTV_STORAGE_REGION` | `us-west-004` | B2 region |
375
+ | `CCTV_UPLOADER_HEALTH_PORT` | `8082` | Uploader `/health` and `/metrics` |
376
+ | `CCTV_CLEANUP_HEALTH_PORT` | `8083` | Cleanup `/health` and `/metrics` |
377
+ | `INTERNAL_API_TOKEN` | empty | Enables the uploader admin endpoints when set |
378
+
379
+ Keep `.env` out of version control. RTSP URLs and B2 keys are credentials.
380
+
381
+ Camera names must be lowercase kebab-case (`front-door`): they become directory names and
382
+ cloud object keys.
383
+
384
+ ## What it produces
347
385
 
348
- - **`config/cameras.yml`** is the single source of truth for cameras (INV-3). Frigate's
349
- config is generated from it and must never be hand-edited.
350
- - **`.env`** holds location values and secrets: RTSP URLs, B2 keys, ports, data path.
386
+ ```text
387
+ $CCTV_DATA_DIR/
388
+ ├── clips/
389
+ │ └── <camera>/YYYY/MM/DD/HH-MM-SS.mp4
390
+ │ <camera>/YYYY/MM/DD/HH-MM-SS.mp4.json
391
+ └── cctv.db
392
+ ```
351
393
 
352
- Both real files are gitignored. `.env.example` and `config/cameras.example.yml` are the
353
- templates. Never commit the filled-in versions, and never paste an RTSP URL or B2 key into
354
- an issue. If a credential is exposed, follow
355
- [`docs/runbooks/rotate-credentials.md`](docs/runbooks/rotate-credentials.md).
394
+ The clips tree is rendered in the **site timezone**. The sidecar records the review id,
395
+ event ids, requested boundaries, measured coverage, codecs, size and SHA-256. The cloud
396
+ object key is `<location>/clips/<path-inside-clips>`.
356
397
 
357
- Camera names are lowercase kebab-case because they become filesystem paths and cloud
358
- object keys.
398
+ A clip appears **after** the visit ends: Frigate keeps the review open until its alert
399
+ cutoff passes. There is no partial clip by design.
359
400
 
360
- ## Published artefacts
401
+ ### Health endpoints
361
402
 
362
- | Artefact | Where | Published when |
403
+ | Service | Endpoint | Notes |
363
404
  |---|---|---|
364
- | Branch image | `ghcr.io/aravindmyd/cctv-stack:edge` | Every merge to `main`/`master` |
365
- | Commit image | `ghcr.io/aravindmyd/cctv-stack:sha-<short>` | Every merge |
366
- | Version image | `ghcr.io/aravindmyd/cctv-stack:<version>` | A `v*` tag |
367
- | Python package | `pypi.org/project/cctv-stack` | A `v*` tag |
368
- | Wheel and sdist | Workflow artefact, and GitHub Release on tags | Every merge / tag |
405
+ | `cctv-uploader` | `http://127.0.0.1:8082/health`, `/metrics` | `DISABLED` without a bucket |
406
+ | `cctv-cleanup` | `http://127.0.0.1:8083/health`, `/metrics` | Local retention state |
407
+ | Uploader admin | `/admin/queue`, `/admin/retry` | Mounted only when `INTERNAL_API_TOKEN` is set |
369
408
 
370
- ```bash
371
- docker pull ghcr.io/aravindmyd/cctv-stack:edge
372
- pip install cctv-stack
409
+ Bind these to loopback. Nothing in the package needs an inbound port from the LAN.
410
+
411
+ ## Python API
412
+
413
+ The supported contract is the CLI, the environment variables, and the HTTP health
414
+ endpoints. The importable modules are tested, but pre-1.0 names may change between minor
415
+ versions.
416
+
417
+ ```python
418
+ from cctv.config import ConfigError, load_cameras_config
419
+
420
+ try:
421
+ config = load_cameras_config("cameras.yml")
422
+ except ConfigError as exc:
423
+ raise SystemExit(f"invalid camera config: {exc}")
424
+
425
+ print(config.site.location)
426
+ for name, camera in config.enabled_cameras().items():
427
+ print(name, camera.display_name, camera.record.mode)
373
428
  ```
374
429
 
375
- The image is the runtime. `pip install cctv-stack` installs the four CLI entry points
376
- (`cctv-clip-worker`, `cctv-config`, `cctv-uploader`, `cctv-cleanup`) but **not** Frigate,
377
- Compose, or site configuration. The complete client-site install is the signed release
378
- bundle in [`docs/plans/packaging.md`](docs/plans/packaging.md).
430
+ Storage is available behind the `storage` extra:
379
431
 
380
- ## Development
432
+ ```python
433
+ import os
434
+ from cctv.storage import S3StorageAdapter
381
435
 
382
- ```bash
383
- uv sync
384
- uv run pytest # unit suite; live tests skip without Docker/S3
385
- uv run ruff check .
386
- uv run ruff format --check .
436
+ adapter = S3StorageAdapter(
437
+ bucket=os.environ["CCTV_STORAGE_BUCKET"],
438
+ access_key_id=os.environ["CCTV_STORAGE_KEY_ID"],
439
+ secret_access_key=os.environ["CCTV_STORAGE_APPLICATION_KEY"],
440
+ region=os.environ.get("CCTV_STORAGE_REGION", "us-west-004"),
441
+ )
442
+ ```
443
+
444
+ `cctv.__version__` reports the installed package version.
445
+
446
+ ## Complete installation
387
447
 
388
- ./tests/integration/run.sh # live S3 suite against RustFS; needs Docker
389
- ./tests/e2e/run.sh # synthetic camera → real clip; needs Docker + Frigate
448
+ For a full CCTV site — Frigate, MQTT, the worker, uploader and cleanup — use the Compose
449
+ deployment in this repository:
450
+
451
+ ```bash
452
+ git clone https://github.com/aravindmyd/cctv-stack.git
453
+ cd cctv-stack
454
+ ./install.sh
390
455
  ```
391
456
 
392
- The unit suite is **717 passed, 19 skipped** without a configured S3 endpoint or a running
393
- Docker daemon. CI runs pre-commit, unit tests on Python 3.12 and 3.13, the pip-install
394
- fallback, live integration, and the synthetic e2e pipeline. The publish workflow builds
395
- the multi-arch image and the Python distributions.
457
+ The first run creates `.env` and `config/cameras.yml`; edit both and run it again. See
458
+ [`docs/install.md`](docs/install.md) for the operator procedure and
459
+ [`CONTRIBUTING.md`](CONTRIBUTING.md) for how releases are built and delivered.
460
+
461
+ ## Compatibility
462
+
463
+ | | |
464
+ |---|---|
465
+ | Python | 3.12 or newer |
466
+ | Frigate | 0.18 API and MQTT contract (verified) |
467
+ | Platforms | Linux x86_64/arm64; macOS for development |
468
+ | Cloud backup | Backblaze B2 through its S3-compatible endpoint; optional |
469
+ | Inbound network | None required |
396
470
 
397
471
  ## Documentation
398
472
 
399
- - [`docs/index.md`](docs/index.md) — documentation map and reading order
400
- - [`docs/architecture/hld.md`](docs/architecture/hld.md) — how the system fits together
401
- - [`docs/architecture/invariants.md`](docs/architecture/invariants.md) — rules that must never break
402
- - [`docs/runbooks/install.md`](docs/runbooks/install.md) — the target operator runbook (M6; the current installer is the two-run flow above)
403
- - [`docs/plans/roadmap.md`](docs/plans/roadmap.md) — what gets built, in what order
404
- - [`docs/plans/backlog.md`](docs/plans/backlog.md) — deliberately deferred work
405
- - [`docs/plans/packaging.md`](docs/plans/packaging.md) — client-site delivery and release artefacts
473
+ Full documentation: **https://aravindmyd.github.io/cctv-stack/**
474
+
475
+ - [Install](docs/install.md) — Docker Compose from the published image, or pip
476
+ - [Configure](docs/configure.md) — `cameras.yml`, `.env`, adding a camera
477
+ - [Frigate UI login](docs/frigate-ui.md)
478
+ - [Health](docs/health.md) — endpoints and what to check first
479
+ - [Recover failed uploads](docs/recover-uploads.md)
480
+ - [Glossary](docs/glossary.md) — the vocabulary these docs use
481
+ - [`CONTRIBUTING.md`](CONTRIBUTING.md) — development, CI and release process
406
482
 
407
483
  ## License
408
484
 
409
- Apache-2.0 — see [`LICENSE`](LICENSE). The reasoning is in
410
- [`docs/plans/opensourcing.md`](docs/plans/opensourcing.md).
485
+ Apache-2.0 — see [`LICENSE`](LICENSE).