cctv-stack 0.3.0__tar.gz → 0.5.0__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.
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/.gitignore +19 -3
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/PKG-INFO +204 -123
- cctv_stack-0.5.0/README.md +257 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/pyproject.toml +11 -1
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/__init__.py +2 -2
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/atomic.py +3 -3
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/cleanup.py +6 -5
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/clip.py +2 -2
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/config.py +45 -6
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/config_cli.py +82 -21
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/db.py +108 -2
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/ffprobe.py +3 -2
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/health.py +41 -3
- cctv_stack-0.5.0/src/cctv/host.py +490 -0
- cctv_stack-0.5.0/src/cctv/host_cli.py +121 -0
- cctv_stack-0.5.0/src/cctv/migrations/0003_disk_samples.sql +16 -0
- cctv_stack-0.5.0/src/cctv/monitoring.py +332 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/paths.py +6 -6
- cctv_stack-0.5.0/src/cctv/status.py +1164 -0
- cctv_stack-0.5.0/src/cctv/status_service.py +359 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/storage.py +9 -9
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/timeutil.py +4 -4
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/uploader.py +5 -5
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/worker.py +1 -1
- cctv_stack-0.3.0/README.md +0 -178
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/LICENSE +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/dispatch.py +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/frigate.py +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/logging.py +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/materialise.py +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/migrations/0001_init.sql +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/migrations/0002_finding_kinds.sql +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/mqtt.py +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/reviews.py +0 -0
- {cctv_stack-0.3.0 → cctv_stack-0.5.0}/src/cctv/sweep.py +0 -0
|
@@ -5,6 +5,9 @@ __pycache__/
|
|
|
5
5
|
build/
|
|
6
6
|
dist/
|
|
7
7
|
|
|
8
|
+
# mkdocs output. Generated by `uv run mkdocs build`; the published copy is built by CI.
|
|
9
|
+
site/
|
|
10
|
+
|
|
8
11
|
# Environments
|
|
9
12
|
.venv/
|
|
10
13
|
venv/
|
|
@@ -14,6 +17,13 @@ venv/
|
|
|
14
17
|
.ruff_cache/
|
|
15
18
|
.mypy_cache/
|
|
16
19
|
|
|
20
|
+
# Editor state is per-machine, not project configuration
|
|
21
|
+
.vscode/
|
|
22
|
+
|
|
23
|
+
# Personal agent memory and local agent settings. This repository is public; nothing here is
|
|
24
|
+
# project configuration and nothing here belongs in its history.
|
|
25
|
+
.claude/
|
|
26
|
+
|
|
17
27
|
# uv.lock is deliberately COMMITTED — do not ignore it (ADR-0018).
|
|
18
28
|
.uv-cache/
|
|
19
29
|
|
|
@@ -32,6 +42,13 @@ data/
|
|
|
32
42
|
# is the site's file, not the repository's. `cameras.example.yml` is committed; this is not.
|
|
33
43
|
config/cameras.yml
|
|
34
44
|
|
|
45
|
+
# The generated monitoring config. It carries the heartbeat token and is derived from
|
|
46
|
+
# cameras.yml at start-up, so editing it by hand is wasted work as well.
|
|
47
|
+
config/gatus/
|
|
48
|
+
|
|
49
|
+
# Generated by install.sh from probes of this machine (lld/07 §5).
|
|
50
|
+
config/host.yml
|
|
51
|
+
|
|
35
52
|
# Generated Frigate config, plus Frigate's own runtime state, because the directory is
|
|
36
53
|
# mounted as its /config.
|
|
37
54
|
#
|
|
@@ -51,6 +68,5 @@ config/frigate.generated.yml
|
|
|
51
68
|
# deliberately ignored and is deleted once its VERIFY items are measurements.
|
|
52
69
|
spike/
|
|
53
70
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
# data and Frigate's state out of git while leaving the harness itself reviewable.
|
|
71
|
+
# Internal planning and notes — not for public consumption.
|
|
72
|
+
.private/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cctv-stack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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,258 @@ Description-Content-Type: text/markdown
|
|
|
232
234
|
|
|
233
235
|
# cctv-stack
|
|
234
236
|
|
|
237
|
+
[](https://pypi.org/project/cctv-stack/)
|
|
238
|
+
[](https://pypi.org/project/cctv-stack/)
|
|
235
239
|
[](https://github.com/aravindmyd/cctv-stack/actions/workflows/ci.yml)
|
|
236
|
-
[](https://github.com/aravindmyd/cctv-stack/actions/workflows/publish.yml)
|
|
237
240
|
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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.5.0`.** Clips, B2 upload, local retention, the aggregate status page
|
|
256
|
+
and alerting through Gatus/ntfy are implemented. Cloud retention and the one-command
|
|
257
|
+
installer are not — see [`CONTRIBUTING.md`](CONTRIBUTING.md) for the exact state and roadmap.
|
|
248
258
|
|
|
249
259
|
---
|
|
250
260
|
|
|
251
|
-
##
|
|
261
|
+
## Install
|
|
252
262
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
+
Optional extras:
|
|
268
270
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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.5.0
|
|
273
285
|
```
|
|
274
286
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
Tags published per release: `:0.5.0`, `:0.5`, 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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
| `cctv-status` | Serve the aggregate status page (`:8084/status`), health and metrics |
|
|
301
|
+
| `cctv-host` | Detect this machine's host profile and Frigate's `shm_size` |
|
|
302
|
+
|
|
303
|
+
The four services are long-running and configuration-driven; they take no command-line
|
|
304
|
+
arguments. Run them under systemd, Docker, or Compose.
|
|
305
|
+
|
|
306
|
+
With Compose, the answer to *is my CCTV working?* is at
|
|
307
|
+
`http://<host>:8084/status`; the dashboard and history are at `http://<host>:3001` (Gatus),
|
|
308
|
+
and alerts arrive through ntfy at `http://<host>:8085`.
|
|
282
309
|
|
|
283
310
|
## Quick start
|
|
284
311
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
first real run pulls the Frigate image and builds the CCTV image, so expect several
|
|
288
|
-
minutes.
|
|
312
|
+
The only required input is a `cameras.yml`. This minimal file is valid; `${VAR}` values
|
|
313
|
+
are read from the environment and an unset variable is a hard error.
|
|
289
314
|
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
315
|
+
```yaml
|
|
316
|
+
site:
|
|
317
|
+
location: home
|
|
318
|
+
timezone: UTC
|
|
319
|
+
|
|
320
|
+
cameras:
|
|
321
|
+
front-door:
|
|
322
|
+
display_name: Front Door
|
|
323
|
+
rtsp:
|
|
324
|
+
main: ${CAMERA_FRONT_DOOR_RTSP}
|
|
325
|
+
sub: ${CAMERA_FRONT_DOOR_SUB}
|
|
294
326
|
```
|
|
295
327
|
|
|
296
|
-
|
|
328
|
+
Validate it and generate Frigate's config:
|
|
297
329
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
| `config/cameras.yml` | Camera names, clip padding, retention policy | **No** — gitignored |
|
|
330
|
+
```bash
|
|
331
|
+
export CAMERA_FRONT_DOOR_RTSP='rtsp://user:pass@192.168.1.10:554/stream1'
|
|
332
|
+
export CAMERA_FRONT_DOOR_SUB='rtsp://user:pass@192.168.1.10:554/stream2'
|
|
302
333
|
|
|
303
|
-
|
|
304
|
-
|
|
334
|
+
cctv-config validate --cameras cameras.yml
|
|
335
|
+
cctv-config generate --cameras cameras.yml --out frigate.yml
|
|
336
|
+
```
|
|
305
337
|
|
|
306
|
-
|
|
307
|
-
|
|
338
|
+
`validate` checks the camera model and the environment substitution. `generate` writes a
|
|
339
|
+
Frigate config with a `GENERATED FILE — DO NOT EDIT` header.
|
|
308
340
|
|
|
309
|
-
|
|
341
|
+
### Running the services
|
|
310
342
|
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
data
|
|
314
|
-
|
|
343
|
+
```bash
|
|
344
|
+
export CCTV_CONFIG_DIR="$PWD"
|
|
345
|
+
export CCTV_DATA_DIR="$PWD/data"
|
|
346
|
+
export CCTV_FRIGATE_URL="http://127.0.0.1:5000"
|
|
315
347
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
**UTC** — both are deliberate ([ADR-0017](docs/adr/0017-time-handling.md)).
|
|
348
|
+
cctv-clip-worker
|
|
349
|
+
```
|
|
319
350
|
|
|
320
|
-
|
|
321
|
-
|
|
351
|
+
Run each service in its own process. `cctv-clip-worker` works without MQTT (sweep-only);
|
|
352
|
+
set `CCTV_MQTT_HOST` to receive reviews immediately. `cctv-uploader` reports `DISABLED`
|
|
353
|
+
and uploads nothing when `CCTV_STORAGE_BUCKET` is empty; a bucket with no key exits `78`.
|
|
322
354
|
|
|
323
|
-
|
|
355
|
+
> `ffprobe` must be on `PATH` for the clip worker. The container image includes it; a
|
|
356
|
+
> `pip install` does not install ffmpeg.
|
|
324
357
|
|
|
325
|
-
|
|
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 |
|
|
358
|
+
## Configuration
|
|
334
359
|
|
|
335
|
-
|
|
360
|
+
There are two kinds of configuration, and they must not be mixed:
|
|
336
361
|
|
|
337
|
-
|
|
|
362
|
+
| File | Holds | Secret? |
|
|
338
363
|
|---|---|---|
|
|
339
|
-
|
|
|
340
|
-
|
|
|
341
|
-
| Cleanup | `http://127.0.0.1:8083/health` | Local retention state |
|
|
342
|
-
| Frigate API, MQTT | not published | Reachable only on the compose network |
|
|
364
|
+
| `cameras.yml` | Site name, timezone, cameras, clip policy, retention | No — but `${VAR}` values come from the environment |
|
|
365
|
+
| environment / `.env` | RTSP URLs, B2 keys, paths, ports, intervals | **Yes** |
|
|
343
366
|
|
|
344
|
-
|
|
367
|
+
The full template is [`.env.example`](.env.example). The settings most consumers need:
|
|
345
368
|
|
|
346
|
-
|
|
369
|
+
| Variable | Default | Meaning |
|
|
370
|
+
|---|---|---|
|
|
371
|
+
| `CCTV_CONFIG_DIR` | `/config` | Directory containing `cameras.yml` |
|
|
372
|
+
| `CCTV_DATA_DIR` | `/data` | Root for `clips/` and the state database |
|
|
373
|
+
| `CCTV_DB` | `$CCTV_DATA_DIR/cctv.db` | SQLite state store |
|
|
374
|
+
| `CCTV_FRIGATE_URL` | `http://frigate:5000` | Frigate API base URL. Port 5000 must stay on a private network |
|
|
375
|
+
| `CCTV_MQTT_HOST` | unset | Unset means sweep-only; no broker required |
|
|
376
|
+
| `CCTV_SWEEP_INTERVAL_S` | `60` | Worst-case delay before a missed review is noticed |
|
|
377
|
+
| `CCTV_STORAGE_BUCKET` | empty | Empty means cloud backup `DISABLED` |
|
|
378
|
+
| `CCTV_STORAGE_KEY_ID` | empty | B2 application key ID |
|
|
379
|
+
| `CCTV_STORAGE_APPLICATION_KEY` | empty | B2 application key |
|
|
380
|
+
| `CCTV_STORAGE_REGION` | `us-west-004` | B2 region |
|
|
381
|
+
| `CCTV_UPLOADER_HEALTH_PORT` | `8082` | Uploader `/health` and `/metrics` |
|
|
382
|
+
| `CCTV_CLEANUP_HEALTH_PORT` | `8083` | Cleanup `/health` and `/metrics` |
|
|
383
|
+
| `INTERNAL_API_TOKEN` | empty | Enables the uploader admin endpoints when set |
|
|
384
|
+
|
|
385
|
+
Keep `.env` out of version control. RTSP URLs and B2 keys are credentials.
|
|
386
|
+
|
|
387
|
+
Camera names must be lowercase kebab-case (`front-door`): they become directory names and
|
|
388
|
+
cloud object keys.
|
|
389
|
+
|
|
390
|
+
## What it produces
|
|
347
391
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
392
|
+
```text
|
|
393
|
+
$CCTV_DATA_DIR/
|
|
394
|
+
├── clips/
|
|
395
|
+
│ └── <camera>/YYYY/MM/DD/HH-MM-SS.mp4
|
|
396
|
+
│ <camera>/YYYY/MM/DD/HH-MM-SS.mp4.json
|
|
397
|
+
└── cctv.db
|
|
398
|
+
```
|
|
351
399
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
[`docs/runbooks/rotate-credentials.md`](docs/runbooks/rotate-credentials.md).
|
|
400
|
+
The clips tree is rendered in the **site timezone**. The sidecar records the review id,
|
|
401
|
+
event ids, requested boundaries, measured coverage, codecs, size and SHA-256. The cloud
|
|
402
|
+
object key is `<location>/clips/<path-inside-clips>`.
|
|
356
403
|
|
|
357
|
-
|
|
358
|
-
|
|
404
|
+
A clip appears **after** the visit ends: Frigate keeps the review open until its alert
|
|
405
|
+
cutoff passes. There is no partial clip by design.
|
|
359
406
|
|
|
360
|
-
|
|
407
|
+
### Health endpoints
|
|
361
408
|
|
|
362
|
-
|
|
|
409
|
+
| Service | Endpoint | Notes |
|
|
363
410
|
|---|---|---|
|
|
364
|
-
|
|
|
365
|
-
|
|
|
366
|
-
|
|
|
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 |
|
|
411
|
+
| `cctv-uploader` | `http://127.0.0.1:8082/health`, `/metrics` | `DISABLED` without a bucket |
|
|
412
|
+
| `cctv-cleanup` | `http://127.0.0.1:8083/health`, `/metrics` | Local retention state |
|
|
413
|
+
| Uploader admin | `/admin/queue`, `/admin/retry` | Mounted only when `INTERNAL_API_TOKEN` is set |
|
|
369
414
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
415
|
+
Bind these to loopback. Nothing in the package needs an inbound port from the LAN.
|
|
416
|
+
|
|
417
|
+
## Python API
|
|
418
|
+
|
|
419
|
+
The supported contract is the CLI, the environment variables, and the HTTP health
|
|
420
|
+
endpoints. The importable modules are tested, but pre-1.0 names may change between minor
|
|
421
|
+
versions.
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
from cctv.config import ConfigError, load_cameras_config
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
config = load_cameras_config("cameras.yml")
|
|
428
|
+
except ConfigError as exc:
|
|
429
|
+
raise SystemExit(f"invalid camera config: {exc}")
|
|
430
|
+
|
|
431
|
+
print(config.site.location)
|
|
432
|
+
for name, camera in config.enabled_cameras().items():
|
|
433
|
+
print(name, camera.display_name, camera.record.mode)
|
|
373
434
|
```
|
|
374
435
|
|
|
375
|
-
|
|
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).
|
|
436
|
+
Storage is available behind the `storage` extra:
|
|
379
437
|
|
|
380
|
-
|
|
438
|
+
```python
|
|
439
|
+
import os
|
|
440
|
+
from cctv.storage import S3StorageAdapter
|
|
381
441
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
442
|
+
adapter = S3StorageAdapter(
|
|
443
|
+
bucket=os.environ["CCTV_STORAGE_BUCKET"],
|
|
444
|
+
access_key_id=os.environ["CCTV_STORAGE_KEY_ID"],
|
|
445
|
+
secret_access_key=os.environ["CCTV_STORAGE_APPLICATION_KEY"],
|
|
446
|
+
region=os.environ.get("CCTV_STORAGE_REGION", "us-west-004"),
|
|
447
|
+
)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
`cctv.__version__` reports the installed package version.
|
|
387
451
|
|
|
388
|
-
|
|
389
|
-
|
|
452
|
+
## Complete installation
|
|
453
|
+
|
|
454
|
+
For a full CCTV site — Frigate, MQTT, the worker, uploader and cleanup — use the Compose
|
|
455
|
+
deployment in this repository:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
git clone https://github.com/aravindmyd/cctv-stack.git
|
|
459
|
+
cd cctv-stack
|
|
460
|
+
./install.sh
|
|
390
461
|
```
|
|
391
462
|
|
|
392
|
-
The
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
463
|
+
The first run creates `.env` and `config/cameras.yml`; edit both and run it again. See
|
|
464
|
+
[`docs/install.md`](docs/install.md) for the operator procedure and
|
|
465
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) for how releases are built and delivered.
|
|
466
|
+
|
|
467
|
+
## Compatibility
|
|
468
|
+
|
|
469
|
+
| | |
|
|
470
|
+
|---|---|
|
|
471
|
+
| Python | 3.12 or newer |
|
|
472
|
+
| Frigate | 0.18 API and MQTT contract (verified) |
|
|
473
|
+
| Platforms | Linux x86_64/arm64; macOS for development |
|
|
474
|
+
| Cloud backup | Backblaze B2 through its S3-compatible endpoint; optional |
|
|
475
|
+
| Inbound network | None required |
|
|
396
476
|
|
|
397
477
|
## Documentation
|
|
398
478
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
- [
|
|
402
|
-
- [
|
|
403
|
-
- [
|
|
404
|
-
- [
|
|
405
|
-
- [
|
|
479
|
+
Full documentation: **https://aravindmyd.github.io/cctv-stack/**
|
|
480
|
+
|
|
481
|
+
- [Install](docs/install.md) — Docker Compose from the published image, or pip
|
|
482
|
+
- [Configure](docs/configure.md) — `cameras.yml`, `.env`, adding a camera
|
|
483
|
+
- [Frigate UI login](docs/frigate-ui.md)
|
|
484
|
+
- [Health](docs/health.md) — endpoints and what to check first
|
|
485
|
+
- [Recover failed uploads](docs/recover-uploads.md)
|
|
486
|
+
- [Glossary](docs/glossary.md) — the vocabulary these docs use
|
|
487
|
+
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — development, CI and release process
|
|
406
488
|
|
|
407
489
|
## License
|
|
408
490
|
|
|
409
|
-
Apache-2.0 — see [`LICENSE`](LICENSE).
|
|
410
|
-
[`docs/plans/opensourcing.md`](docs/plans/opensourcing.md).
|
|
491
|
+
Apache-2.0 — see [`LICENSE`](LICENSE).
|