homesec 0.1.0__tar.gz → 1.0.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.
- homesec-1.0.0/.dockerignore +57 -0
- {homesec-0.1.0 → homesec-1.0.0}/.env.example +11 -12
- homesec-1.0.0/.github/workflows/ci.yml +65 -0
- homesec-1.0.0/.github/workflows/release.yaml +67 -0
- homesec-1.0.0/.github/workflows/validate-pr-title.yaml +20 -0
- homesec-1.0.0/.gitignore +11 -0
- homesec-1.0.0/CHANGELOG.md +7 -0
- homesec-1.0.0/Dockerfile +96 -0
- homesec-1.0.0/Makefile +98 -0
- {homesec-0.1.0 → homesec-1.0.0}/PKG-INFO +66 -31
- {homesec-0.1.0 → homesec-1.0.0}/README.md +65 -30
- {homesec-0.1.0 → homesec-1.0.0}/alembic/env.py +14 -3
- homesec-1.0.0/alembic/script.py.mako +26 -0
- homesec-1.0.0/alembic/versions/e6f25df0df90_initial.py +67 -0
- homesec-1.0.0/config/example.yaml +194 -0
- homesec-1.0.0/docker-compose.yml +42 -0
- homesec-1.0.0/docker-entrypoint.sh +20 -0
- {homesec-0.1.0 → homesec-1.0.0}/pyproject.toml +42 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/__init__.py +1 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/app.py +34 -36
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/cli.py +14 -11
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/config/loader.py +11 -11
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/config/validation.py +2 -5
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/errors.py +2 -4
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/health/server.py +29 -27
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/interfaces.py +11 -6
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/logging_setup.py +9 -5
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/maintenance/cleanup_clips.py +2 -3
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/alert.py +2 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/clip.py +8 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/config.py +9 -13
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/events.py +14 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/filter.py +1 -3
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/vlm.py +1 -2
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/pipeline/core.py +15 -32
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/alert_policies/__init__.py +3 -4
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/alert_policies/default.py +3 -2
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/alert_policies/noop.py +1 -2
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/analyzers/__init__.py +3 -4
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/analyzers/openai.py +34 -43
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/filters/__init__.py +3 -4
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/filters/yolo.py +27 -29
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/notifiers/__init__.py +2 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/notifiers/mqtt.py +16 -17
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/notifiers/multiplex.py +3 -2
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/notifiers/sendgrid_email.py +6 -8
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/storage/__init__.py +3 -4
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/storage/dropbox.py +20 -17
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/storage/local.py +3 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/utils.py +2 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/repository/clip_repository.py +5 -4
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/sources/base.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/sources/local_folder.py +9 -7
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/sources/rtsp.py +22 -10
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/state/postgres.py +34 -35
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/telemetry/db_log_handler.py +3 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/conftest.py +6 -4
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/filter.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/notifier.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/state_store.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/storage.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/vlm.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_app.py +3 -4
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_clip_repository.py +10 -5
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_clip_sources.py +41 -65
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_config.py +1 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_dropbox_storage.py +3 -1
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_event_store.py +2 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_health.py +35 -35
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_integration.py +61 -58
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_local_folder_deduplication.py +0 -1
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_mqtt_notifier.py +6 -2
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_openai_vlm.py +15 -5
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_pipeline.py +49 -31
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_plugin_registration.py +2 -6
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_state_store.py +2 -4
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_yolo_filter.py +0 -2
- {homesec-0.1.0 → homesec-1.0.0}/uv.lock +2 -2
- homesec-0.1.0/.gitignore +0 -4
- homesec-0.1.0/Makefile +0 -61
- homesec-0.1.0/config/example.yaml +0 -85
- homesec-0.1.0/config/local.yaml +0 -68
- homesec-0.1.0/config/production.yaml +0 -113
- homesec-0.1.0/config/sample.yaml +0 -66
- homesec-0.1.0/docker-compose.postgres.yml +0 -17
- {homesec-0.1.0 → homesec-1.0.0}/AGENTS.md +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/DESIGN.md +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/LICENSE +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/alembic.ini +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/config/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/health/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/maintenance/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/__init__.py +1 -1
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/source.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/models/storage.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/pipeline/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/pipeline/alert_policy.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/plugins/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/py.typed +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/repository/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/sources/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/sources/ftp.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/state/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/storage_paths.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/telemetry/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/telemetry/db/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/telemetry/db/log_table.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/src/homesec/telemetry/postgres_settings.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/conftest.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/__init__.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/mocks/event_store.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_alert_policy.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_cleanup_clips.py +1 -1
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_notifiers.py +0 -0
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_pipeline_events.py +1 -1
- {homesec-0.1.0 → homesec-1.0.0}/tests/homesec/test_rtsp_helpers.py +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Git
|
|
2
|
+
.git
|
|
3
|
+
.gitignore
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
*.so
|
|
10
|
+
.Python
|
|
11
|
+
.venv
|
|
12
|
+
venv
|
|
13
|
+
ENV
|
|
14
|
+
env
|
|
15
|
+
|
|
16
|
+
# Development
|
|
17
|
+
.env
|
|
18
|
+
.env.*
|
|
19
|
+
.mypy_cache
|
|
20
|
+
.pytest_cache
|
|
21
|
+
.ruff_cache
|
|
22
|
+
*.egg-info
|
|
23
|
+
dist
|
|
24
|
+
build
|
|
25
|
+
|
|
26
|
+
# IDE
|
|
27
|
+
.idea
|
|
28
|
+
.vscode
|
|
29
|
+
*.swp
|
|
30
|
+
*.swo
|
|
31
|
+
|
|
32
|
+
# Tests
|
|
33
|
+
tests
|
|
34
|
+
pytest.ini
|
|
35
|
+
|
|
36
|
+
# Documentation
|
|
37
|
+
docs
|
|
38
|
+
DESIGN.md
|
|
39
|
+
CLAUDE.md
|
|
40
|
+
|
|
41
|
+
# Local data (should be mounted as volumes)
|
|
42
|
+
recordings
|
|
43
|
+
storage
|
|
44
|
+
*.mp4
|
|
45
|
+
*.avi
|
|
46
|
+
*.mov
|
|
47
|
+
video_cache
|
|
48
|
+
yolo_cache
|
|
49
|
+
ftp_incoming
|
|
50
|
+
|
|
51
|
+
# Notebooks
|
|
52
|
+
notebooks
|
|
53
|
+
*.ipynb
|
|
54
|
+
|
|
55
|
+
# Config examples (actual config mounted at runtime)
|
|
56
|
+
config/*.yaml
|
|
57
|
+
!config/example.yaml
|
|
@@ -30,20 +30,19 @@ MQTT_PASSWORD="..."
|
|
|
30
30
|
# SendGrid email (if using sendgrid_email notifier)
|
|
31
31
|
SENDGRID_API_KEY="..."
|
|
32
32
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Run a local Postgres with: make db-up
|
|
40
|
-
POSTGRES_USER="telemetry"
|
|
41
|
-
POSTGRES_PASSWORD="telemetry"
|
|
42
|
-
POSTGRES_DB="telemetry"
|
|
33
|
+
# --- Database (optional) ---
|
|
34
|
+
# If DB_DSN is set, the process will store clip state/events in Postgres.
|
|
35
|
+
# Run a local Postgres with: make db
|
|
36
|
+
POSTGRES_USER="homesec"
|
|
37
|
+
POSTGRES_PASSWORD="homesec"
|
|
38
|
+
POSTGRES_DB="homesec"
|
|
43
39
|
POSTGRES_PORT="5432"
|
|
44
40
|
|
|
45
|
-
#
|
|
46
|
-
DB_DSN="postgresql+asyncpg://
|
|
41
|
+
# For local dev (make db + make run):
|
|
42
|
+
DB_DSN="postgresql+asyncpg://homesec:homesec@localhost:5432/homesec"
|
|
43
|
+
|
|
44
|
+
# For docker-compose (make up) - set automatically, but can override:
|
|
45
|
+
# DB_DSN="postgresql+asyncpg://homesec:homesec@postgres:5432/homesec"
|
|
47
46
|
|
|
48
47
|
# Optional tuning:
|
|
49
48
|
# DB_LOG_LEVEL="INFO" # only logs >= this level go to DB
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
ci:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
services:
|
|
12
|
+
postgres:
|
|
13
|
+
image: postgres:16-alpine
|
|
14
|
+
env:
|
|
15
|
+
POSTGRES_DB: homesec
|
|
16
|
+
POSTGRES_USER: homesec
|
|
17
|
+
POSTGRES_PASSWORD: homesec
|
|
18
|
+
ports:
|
|
19
|
+
- "5432:5432"
|
|
20
|
+
# No health check - Postgres boots while lint/typecheck run
|
|
21
|
+
|
|
22
|
+
env:
|
|
23
|
+
DB_DSN: postgresql+asyncpg://homesec:homesec@localhost:5432/homesec
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Setup uv and Python
|
|
30
|
+
uses: astral-sh/setup-uv@v3
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.14"
|
|
33
|
+
enable-cache: true
|
|
34
|
+
cache-dependency-glob: "uv.lock"
|
|
35
|
+
|
|
36
|
+
- name: Cache venv
|
|
37
|
+
uses: actions/cache@v4
|
|
38
|
+
with:
|
|
39
|
+
path: .venv
|
|
40
|
+
key: venv-${{ runner.os }}-py3.14-${{ hashFiles('uv.lock') }}
|
|
41
|
+
|
|
42
|
+
- name: Sync dependencies
|
|
43
|
+
run: uv sync
|
|
44
|
+
|
|
45
|
+
# Lint and typecheck run while Postgres boots
|
|
46
|
+
- name: Lint
|
|
47
|
+
run: make lint
|
|
48
|
+
|
|
49
|
+
- name: Type check
|
|
50
|
+
run: make typecheck
|
|
51
|
+
|
|
52
|
+
# Wait for Postgres before migrations
|
|
53
|
+
- name: Wait for Postgres
|
|
54
|
+
run: |
|
|
55
|
+
for i in {1..30}; do
|
|
56
|
+
pg_isready -h localhost -p 5432 -U homesec && break
|
|
57
|
+
echo "Waiting for Postgres..."
|
|
58
|
+
sleep 1
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
- name: Run migrations
|
|
62
|
+
run: uv run alembic -c alembic.ini upgrade head
|
|
63
|
+
|
|
64
|
+
- name: Test
|
|
65
|
+
run: make test
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
dry_run:
|
|
7
|
+
description: 'Dry run (no actual release)'
|
|
8
|
+
required: false
|
|
9
|
+
default: 'false'
|
|
10
|
+
type: boolean
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Setup Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.14"
|
|
29
|
+
|
|
30
|
+
- name: Install python-semantic-release
|
|
31
|
+
run: pip install python-semantic-release
|
|
32
|
+
|
|
33
|
+
- name: Semantic Release (dry run)
|
|
34
|
+
if: ${{ inputs.dry_run == 'true' }}
|
|
35
|
+
run: semantic-release version --noop
|
|
36
|
+
|
|
37
|
+
- name: Semantic Release
|
|
38
|
+
if: ${{ inputs.dry_run != 'true' }}
|
|
39
|
+
env:
|
|
40
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
run: |
|
|
42
|
+
git config user.name "github-actions[bot]"
|
|
43
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
44
|
+
semantic-release version
|
|
45
|
+
|
|
46
|
+
- name: Build Package
|
|
47
|
+
if: ${{ inputs.dry_run != 'true' }}
|
|
48
|
+
run: |
|
|
49
|
+
pip install build
|
|
50
|
+
python -m build
|
|
51
|
+
|
|
52
|
+
- name: Publish to PyPI
|
|
53
|
+
if: ${{ inputs.dry_run != 'true' }}
|
|
54
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
55
|
+
with:
|
|
56
|
+
verbose: true
|
|
57
|
+
|
|
58
|
+
- name: Create GitHub Release
|
|
59
|
+
if: ${{ inputs.dry_run != 'true' }}
|
|
60
|
+
env:
|
|
61
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
62
|
+
run: |
|
|
63
|
+
VERSION=$(grep 'version = ' pyproject.toml | head -1 | cut -d'"' -f2)
|
|
64
|
+
gh release create "v$VERSION" \
|
|
65
|
+
--title "v$VERSION" \
|
|
66
|
+
--generate-notes \
|
|
67
|
+
dist/*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Validate PR Title
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
types:
|
|
6
|
+
- opened
|
|
7
|
+
- edited
|
|
8
|
+
- synchronize
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
pull-requests: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
main:
|
|
15
|
+
name: Validate PR title
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: amannn/action-semantic-pull-request@v5
|
|
19
|
+
env:
|
|
20
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
homesec-1.0.0/.gitignore
ADDED
homesec-1.0.0/Dockerfile
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# HomeSec Dockerfile
|
|
2
|
+
# Multi-stage build for minimal image size
|
|
3
|
+
#
|
|
4
|
+
# Build: docker build -t homesec .
|
|
5
|
+
# Run: docker run \
|
|
6
|
+
# -v ./config.yaml:/config/config.yaml \
|
|
7
|
+
# -v ./.env:/config/.env \
|
|
8
|
+
# -v ./recordings:/data/recordings \
|
|
9
|
+
# -v ./storage:/data/storage \
|
|
10
|
+
# -v ./yolo_cache:/app/yolo_cache \
|
|
11
|
+
# -p 8080:8080 homesec
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# Stage 1: Builder
|
|
15
|
+
# =============================================================================
|
|
16
|
+
FROM python:3.14-slim-bookworm AS builder
|
|
17
|
+
|
|
18
|
+
# Install build dependencies
|
|
19
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
20
|
+
build-essential \
|
|
21
|
+
curl \
|
|
22
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
23
|
+
|
|
24
|
+
# Install uv for fast dependency management
|
|
25
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
26
|
+
|
|
27
|
+
WORKDIR /app
|
|
28
|
+
|
|
29
|
+
# Copy dependency files first for better caching
|
|
30
|
+
COPY pyproject.toml uv.lock* LICENSE README.md ./
|
|
31
|
+
|
|
32
|
+
# Install dependencies into a virtual environment
|
|
33
|
+
RUN uv venv /app/.venv
|
|
34
|
+
ENV VIRTUAL_ENV=/app/.venv
|
|
35
|
+
ENV PATH="/app/.venv/bin:$PATH"
|
|
36
|
+
|
|
37
|
+
RUN uv sync --frozen --no-dev --no-install-project
|
|
38
|
+
|
|
39
|
+
# Copy source code
|
|
40
|
+
COPY src/ ./src/
|
|
41
|
+
COPY alembic/ ./alembic/
|
|
42
|
+
COPY alembic.ini ./
|
|
43
|
+
|
|
44
|
+
# Install the project
|
|
45
|
+
RUN uv sync --frozen --no-dev
|
|
46
|
+
|
|
47
|
+
# =============================================================================
|
|
48
|
+
# Stage 2: Runtime
|
|
49
|
+
# =============================================================================
|
|
50
|
+
FROM python:3.14-slim-bookworm AS runtime
|
|
51
|
+
|
|
52
|
+
# Install runtime dependencies
|
|
53
|
+
# - ffmpeg: required for RTSP source video processing
|
|
54
|
+
# - libgl1: required by OpenCV
|
|
55
|
+
# - libglib2.0-0: required by OpenCV
|
|
56
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
57
|
+
ffmpeg \
|
|
58
|
+
libgl1 \
|
|
59
|
+
libglib2.0-0 \
|
|
60
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
61
|
+
|
|
62
|
+
# Create non-root user for security
|
|
63
|
+
RUN useradd --create-home --shell /bin/bash homesec
|
|
64
|
+
|
|
65
|
+
WORKDIR /app
|
|
66
|
+
|
|
67
|
+
# Copy virtual environment from builder
|
|
68
|
+
COPY --from=builder /app/.venv /app/.venv
|
|
69
|
+
COPY --from=builder /app/alembic /app/alembic
|
|
70
|
+
COPY --from=builder /app/alembic.ini /app/alembic.ini
|
|
71
|
+
|
|
72
|
+
# Copy entrypoint script
|
|
73
|
+
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
|
|
74
|
+
|
|
75
|
+
# Set up environment
|
|
76
|
+
ENV VIRTUAL_ENV=/app/.venv
|
|
77
|
+
ENV PATH="/app/.venv/bin:$PATH"
|
|
78
|
+
ENV PYTHONUNBUFFERED=1
|
|
79
|
+
|
|
80
|
+
# Create directories for volume mounts and make entrypoint executable
|
|
81
|
+
RUN chmod +x /app/docker-entrypoint.sh \
|
|
82
|
+
&& mkdir -p /config /data/recordings /data/storage /app/yolo_cache \
|
|
83
|
+
&& chown -R homesec:homesec /config /data /app
|
|
84
|
+
|
|
85
|
+
# Switch to non-root user
|
|
86
|
+
USER homesec
|
|
87
|
+
|
|
88
|
+
# Health check endpoint
|
|
89
|
+
EXPOSE 8080
|
|
90
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
91
|
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1
|
|
92
|
+
|
|
93
|
+
# Entrypoint runs migrations then starts app
|
|
94
|
+
# Config and env are expected to be mounted at /config/
|
|
95
|
+
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
|
96
|
+
CMD ["run", "--config", "/config/config.yaml", "--log_level", "INFO"]
|
homesec-1.0.0/Makefile
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
SHELL := /bin/bash
|
|
2
|
+
.SHELLFLAGS := -eu -o pipefail -c
|
|
3
|
+
|
|
4
|
+
.PHONY: help up down docker-build docker-push run db test typecheck lint check db-migrate db-migration publish
|
|
5
|
+
|
|
6
|
+
help:
|
|
7
|
+
@echo "Targets:"
|
|
8
|
+
@echo ""
|
|
9
|
+
@echo " Docker:"
|
|
10
|
+
@echo " make up Start HomeSec + Postgres"
|
|
11
|
+
@echo " make down Stop all services"
|
|
12
|
+
@echo " make docker-build Build Docker image"
|
|
13
|
+
@echo " make docker-push Push to DockerHub"
|
|
14
|
+
@echo ""
|
|
15
|
+
@echo " Local dev:"
|
|
16
|
+
@echo " make run Run HomeSec locally (requires Postgres)"
|
|
17
|
+
@echo " make db Start just Postgres"
|
|
18
|
+
@echo " make test Run tests"
|
|
19
|
+
@echo " make typecheck Run mypy"
|
|
20
|
+
@echo " make lint Run ruff linter"
|
|
21
|
+
@echo " make check Run lint + typecheck + test"
|
|
22
|
+
@echo ""
|
|
23
|
+
@echo " Database:"
|
|
24
|
+
@echo " make db-migrate Run migrations"
|
|
25
|
+
@echo " make db-migration m=\"description\" Generate new migration"
|
|
26
|
+
@echo ""
|
|
27
|
+
@echo " Release:"
|
|
28
|
+
@echo " make publish Build and upload to PyPI"
|
|
29
|
+
|
|
30
|
+
# Config
|
|
31
|
+
HOMESEC_CONFIG ?= config/config.yaml
|
|
32
|
+
HOMESEC_LOG_LEVEL ?= INFO
|
|
33
|
+
DOCKER_IMAGE ?= homesec
|
|
34
|
+
DOCKER_TAG ?= latest
|
|
35
|
+
DOCKERHUB_USER ?= $(shell echo $${DOCKERHUB_USER:-})
|
|
36
|
+
|
|
37
|
+
# Docker
|
|
38
|
+
up:
|
|
39
|
+
docker compose up -d --build
|
|
40
|
+
|
|
41
|
+
down:
|
|
42
|
+
docker compose down
|
|
43
|
+
|
|
44
|
+
docker-build:
|
|
45
|
+
docker build -t $(DOCKER_IMAGE):$(DOCKER_TAG) .
|
|
46
|
+
|
|
47
|
+
docker-push: docker-build
|
|
48
|
+
@if [ -z "$(DOCKERHUB_USER)" ]; then \
|
|
49
|
+
echo "Error: DOCKERHUB_USER not set. Run: export DOCKERHUB_USER=yourusername"; \
|
|
50
|
+
exit 1; \
|
|
51
|
+
fi
|
|
52
|
+
docker tag $(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKERHUB_USER)/$(DOCKER_IMAGE):$(DOCKER_TAG)
|
|
53
|
+
docker tag $(DOCKER_IMAGE):$(DOCKER_TAG) $(DOCKERHUB_USER)/$(DOCKER_IMAGE):latest
|
|
54
|
+
docker push $(DOCKERHUB_USER)/$(DOCKER_IMAGE):$(DOCKER_TAG)
|
|
55
|
+
docker push $(DOCKERHUB_USER)/$(DOCKER_IMAGE):latest
|
|
56
|
+
|
|
57
|
+
# Local dev
|
|
58
|
+
run:
|
|
59
|
+
@echo "Running database migrations..."
|
|
60
|
+
@uv run alembic -c alembic.ini upgrade head
|
|
61
|
+
uv run python -m homesec.cli run --config $(HOMESEC_CONFIG) --log_level $(HOMESEC_LOG_LEVEL)
|
|
62
|
+
|
|
63
|
+
db:
|
|
64
|
+
docker compose up -d postgres
|
|
65
|
+
|
|
66
|
+
test:
|
|
67
|
+
uv run pytest tests/homesec/ -v
|
|
68
|
+
|
|
69
|
+
typecheck:
|
|
70
|
+
uv run mypy --package homesec --strict
|
|
71
|
+
|
|
72
|
+
lint:
|
|
73
|
+
uv run ruff check src tests
|
|
74
|
+
uv run ruff format --check src tests
|
|
75
|
+
|
|
76
|
+
lint-fix:
|
|
77
|
+
uv run ruff check --fix src tests
|
|
78
|
+
uv run ruff format src tests
|
|
79
|
+
|
|
80
|
+
check: lint typecheck test
|
|
81
|
+
|
|
82
|
+
# Database
|
|
83
|
+
db-migrate:
|
|
84
|
+
uv run --with alembic --with sqlalchemy --with asyncpg --with python-dotenv alembic -c alembic.ini upgrade head
|
|
85
|
+
|
|
86
|
+
db-migration:
|
|
87
|
+
@if [ -z "$(m)" ]; then \
|
|
88
|
+
echo "Error: message required. Run: make db-migration m=\"your description\""; \
|
|
89
|
+
exit 1; \
|
|
90
|
+
fi
|
|
91
|
+
uv run --with alembic --with sqlalchemy --with asyncpg --with python-dotenv alembic -c alembic.ini revision --autogenerate -m "$(m)"
|
|
92
|
+
|
|
93
|
+
# Release
|
|
94
|
+
publish: check
|
|
95
|
+
rm -rf dist build
|
|
96
|
+
uv run --with build python -m build
|
|
97
|
+
uv run --with twine python -m twine check dist/*
|
|
98
|
+
uv run --with twine python -m twine upload dist/*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: homesec
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Pluggable async home security camera pipeline with detection, VLM analysis, and alerts.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lan17/homesec
|
|
6
6
|
Project-URL: Source, https://github.com/lan17/homesec
|
|
@@ -247,11 +247,9 @@ Description-Content-Type: text/markdown
|
|
|
247
247
|
[](https://www.python.org/)
|
|
248
248
|
[](https://peps.python.org/pep-0561/)
|
|
249
249
|
|
|
250
|
-
HomeSec is a
|
|
251
|
-
|
|
252
|
-
(VLM) for a structured summary, and sends alerts via MQTT or email. The design
|
|
253
|
-
leans toward reliability: clips land on disk first, state/event writes are
|
|
254
|
-
best-effort, and non-critical stages can fail without losing the alert.
|
|
250
|
+
HomeSec is a self-hosted, extensible network video recorder that puts you in control. Store clips wherever you want, analyze them with AI, and get smart notifications—all while keeping your footage private and off third-party clouds.
|
|
251
|
+
|
|
252
|
+
Under the hood, it's a pluggable async pipeline for home security cameras. It records short clips, runs object detection, optionally calls a vision-language model (VLM) for a structured summary, and sends alerts via MQTT or email. The design leans toward reliability: clips land on disk first, state/event writes are best-effort, and non-critical stages can fail without losing the alert.
|
|
255
253
|
|
|
256
254
|
## Highlights
|
|
257
255
|
|
|
@@ -261,6 +259,7 @@ best-effort, and non-critical stages can fail without losing the alert.
|
|
|
261
259
|
- Policy-driven alerts with per-camera overrides
|
|
262
260
|
- Fan-out notifiers (MQTT for Home Assistant, SendGrid email)
|
|
263
261
|
- Postgres-backed state + events with graceful degradation
|
|
262
|
+
- Built around small, stable interfaces so new plugins drop in cleanly
|
|
264
263
|
- Health endpoint plus optional Postgres telemetry logging
|
|
265
264
|
|
|
266
265
|
## Pipeline at a glance
|
|
@@ -277,32 +276,42 @@ ClipSource -> (Upload + Filter) -> VLM (optional) -> Alert Policy -> Notifier(s)
|
|
|
277
276
|
|
|
278
277
|
### Requirements
|
|
279
278
|
|
|
280
|
-
-
|
|
281
|
-
- ffmpeg in PATH (required for RTSP source)
|
|
282
|
-
- Postgres for state/events (`make db-up` starts a local instance). The pipeline
|
|
283
|
-
continues if the DB is down, but a DSN is still required.
|
|
279
|
+
- Docker and Docker Compose
|
|
284
280
|
- Optional: MQTT broker, Dropbox credentials, OpenAI-compatible API key
|
|
285
281
|
|
|
286
282
|
### Setup
|
|
287
283
|
|
|
288
|
-
1.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
284
|
+
1. Create a config file:
|
|
285
|
+
```bash
|
|
286
|
+
cp config/example.yaml config/config.yaml
|
|
287
|
+
# Edit config/config.yaml with your settings
|
|
288
|
+
```
|
|
289
|
+
2. Set environment variables:
|
|
290
|
+
```bash
|
|
291
|
+
cp .env.example .env
|
|
292
|
+
# Edit .env with your credentials
|
|
293
|
+
```
|
|
294
|
+
3. Start HomeSec + Postgres:
|
|
295
|
+
```bash
|
|
296
|
+
make up
|
|
297
|
+
```
|
|
298
|
+
4. Stop:
|
|
299
|
+
```bash
|
|
300
|
+
make down
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Running without Docker
|
|
304
|
+
|
|
305
|
+
If you prefer to run locally:
|
|
306
|
+
|
|
307
|
+
1. Install Python 3.10+ and ffmpeg
|
|
308
|
+
2. `uv sync`
|
|
309
|
+
3. `make db` (starts Postgres)
|
|
310
|
+
4. `make run`
|
|
302
311
|
|
|
303
312
|
## Configuration
|
|
304
313
|
|
|
305
|
-
Configs are YAML and validated with Pydantic.
|
|
314
|
+
Configs are YAML and validated with Pydantic. See `config/example.yaml` for all options.
|
|
306
315
|
|
|
307
316
|
Minimal example (RTSP + Dropbox + MQTT):
|
|
308
317
|
|
|
@@ -362,8 +371,8 @@ per_camera_alert:
|
|
|
362
371
|
A few things worth knowing:
|
|
363
372
|
- Secrets never go in YAML. Use env var names (`*_env`) and set values in your shell or `.env`.
|
|
364
373
|
- At least one notifier must be enabled (`mqtt` or `sendgrid_email`).
|
|
365
|
-
- Built-in YOLO classes: `person`, `
|
|
366
|
-
`
|
|
374
|
+
- Built-in YOLO classes: `person`, `car`, `truck`, `motorcycle`, `bicycle`,
|
|
375
|
+
`dog`, `cat`, `bird`, `backpack`, `handbag`, `suitcase`.
|
|
367
376
|
- Local storage for development:
|
|
368
377
|
|
|
369
378
|
```yaml
|
|
@@ -377,14 +386,35 @@ storage:
|
|
|
377
386
|
- For a quick local run, pair `local_folder` with `local` storage and drop a clip
|
|
378
387
|
into `recordings/`.
|
|
379
388
|
|
|
389
|
+
## Extensible by design
|
|
390
|
+
|
|
391
|
+
HomeSec is intentionally modular. Each major capability is an interface
|
|
392
|
+
(`ClipSource`, `StorageBackend`, `ObjectFilter`, `VLMAnalyzer`, `AlertPolicy`,
|
|
393
|
+
`Notifier`) defined in `src/homesec/interfaces.py`, and plugins are discovered at
|
|
394
|
+
runtime via entry points. This keeps the core pipeline small while making it
|
|
395
|
+
easy to add new backends without editing core code.
|
|
396
|
+
|
|
397
|
+
What this means in practice:
|
|
398
|
+
- Swap storage or notifications by changing config, not code.
|
|
399
|
+
- Add a new plugin type as a separate package and register it.
|
|
400
|
+
- Keep config validation strict by pairing each plugin with a Pydantic model.
|
|
401
|
+
|
|
402
|
+
Extension points (all pluggable):
|
|
403
|
+
- Sources: RTSP motion detection, FTP uploads, local folders
|
|
404
|
+
- Storage backends: Dropbox, local disk, or your own
|
|
405
|
+
- Filters: object detection (YOLO or custom models)
|
|
406
|
+
- VLM analyzers: OpenAI-compatible APIs or local models
|
|
407
|
+
- Alert policies: per-camera rules and thresholds
|
|
408
|
+
- Notifiers: MQTT, email, or anything else you can send from Python
|
|
409
|
+
|
|
380
410
|
## CLI
|
|
381
411
|
|
|
382
412
|
- Run the pipeline:
|
|
383
|
-
`uv run python -m homesec.cli run --config config/
|
|
413
|
+
`uv run python -m homesec.cli run --config config/config.yaml --log_level INFO`
|
|
384
414
|
- Validate config:
|
|
385
|
-
`uv run python -m homesec.cli validate --config config/
|
|
415
|
+
`uv run python -m homesec.cli validate --config config/config.yaml`
|
|
386
416
|
- Cleanup (reanalyze and optionally delete empty clips):
|
|
387
|
-
`uv run python -m homesec.cli cleanup --config config/
|
|
417
|
+
`uv run python -m homesec.cli cleanup --config config/config.yaml --older_than_days 7 --dry_run True`
|
|
388
418
|
|
|
389
419
|
## Built-in plugins
|
|
390
420
|
|
|
@@ -399,6 +429,11 @@ storage:
|
|
|
399
429
|
HomeSec discovers plugins via entry points in the `homesec.plugins` group. A plugin
|
|
400
430
|
module just needs to import and register itself.
|
|
401
431
|
|
|
432
|
+
Each plugin provides:
|
|
433
|
+
- A unique name (used in config)
|
|
434
|
+
- A Pydantic config model for validation
|
|
435
|
+
- A factory that builds the concrete implementation
|
|
436
|
+
|
|
402
437
|
```python
|
|
403
438
|
# my_package/filters/custom.py
|
|
404
439
|
from pydantic import BaseModel
|
|
@@ -430,7 +465,7 @@ my_filters = "my_package.filters.custom"
|
|
|
430
465
|
|
|
431
466
|
- Health endpoint: `GET /health` (configurable in `health.host`/`health.port`)
|
|
432
467
|
- Optional telemetry logs to Postgres when `DB_DSN` is set:
|
|
433
|
-
- Start local DB: `make db
|
|
468
|
+
- Start local DB: `make db`
|
|
434
469
|
- Run migrations: `make db-migrate`
|
|
435
470
|
|
|
436
471
|
## Development
|