Phase 1 (Foundation): project skeleton, TOML config + Pydantic validation, MQTT bus wrapper, SQLite schema (9 tables), Click CLI, process supervisor. Phase 2 (Camera): RTSP capture via OpenCV, MOG2 motion detection with configurable sensitivity/zones, adaptive FPS recording (2fps idle/30fps motion) via FFmpeg subprocess, HLS live streaming, pre-motion ring buffer. Phase 3 (Web UI): Flask + Bootstrap 5 dark theme, 6 blueprints, Jinja2 templates (dashboard, kiosk 2x2 grid, events, sensors, recordings, settings), PWA with service worker + Web Push, full admin settings UI with config persistence. Remote Access: WireGuard tunnel configs, nginx reverse proxy with HLS caching + rate limiting, bandwidth-optimized remote HLS stream (426x240 @ 500kbps), DO droplet setup script, certbot TLS. 29 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""CLI command: vigilar config — validate and inspect configuration."""
|
|
|
|
import json
|
|
import sys
|
|
|
|
import click
|
|
|
|
from vigilar.config import load_config
|
|
|
|
|
|
@click.group("config")
|
|
def config_cmd() -> None:
|
|
"""Configuration management."""
|
|
pass
|
|
|
|
|
|
@config_cmd.command("validate")
|
|
@click.option("--config", "-c", "config_path", default=None, help="Path to vigilar.toml.")
|
|
def validate_cmd(config_path: str | None) -> None:
|
|
"""Validate the configuration file."""
|
|
try:
|
|
cfg = load_config(config_path)
|
|
click.echo("Config OK")
|
|
click.echo(f" System: {cfg.system.name}")
|
|
click.echo(f" Cameras: {len(cfg.cameras)}")
|
|
for cam in cfg.cameras:
|
|
status = "enabled" if cam.enabled else "disabled"
|
|
click.echo(f" - {cam.id} ({cam.display_name}) [{status}]")
|
|
click.echo(f" Sensors: {len(cfg.sensors)}")
|
|
for sensor in cfg.sensors:
|
|
click.echo(f" - {sensor.id} ({sensor.display_name}) [{sensor.protocol}]")
|
|
click.echo(f" Rules: {len(cfg.rules)}")
|
|
click.echo(f" UPS: {'enabled' if cfg.ups.enabled else 'disabled'}")
|
|
click.echo(f" Web: {cfg.web.host}:{cfg.web.port}")
|
|
except Exception as e:
|
|
click.echo(f"Config INVALID: {e}", err=True)
|
|
sys.exit(1)
|
|
|
|
|
|
@config_cmd.command("show")
|
|
@click.option("--config", "-c", "config_path", default=None, help="Path to vigilar.toml.")
|
|
def show_cmd(config_path: str | None) -> None:
|
|
"""Show the parsed configuration as JSON (secrets redacted)."""
|
|
try:
|
|
cfg = load_config(config_path)
|
|
data = cfg.model_dump()
|
|
# Redact sensitive fields
|
|
if data.get("web", {}).get("password_hash"):
|
|
data["web"]["password_hash"] = "***"
|
|
if data.get("system", {}).get("arm_pin_hash"):
|
|
data["system"]["arm_pin_hash"] = "***"
|
|
if data.get("alerts", {}).get("webhook", {}).get("secret"):
|
|
data["alerts"]["webhook"]["secret"] = "***"
|
|
click.echo(json.dumps(data, indent=2))
|
|
except Exception as e:
|
|
click.echo(f"Config error: {e}", err=True)
|
|
sys.exit(1)
|
|
|
|
|
|
@config_cmd.command("set-password")
|
|
@click.option("--config", "-c", "config_path", default=None, help="Path to vigilar.toml.")
|
|
def set_password_cmd(config_path: str | None) -> None:
|
|
"""Generate a bcrypt hash for the web UI password."""
|
|
try:
|
|
import hashlib
|
|
|
|
password = click.prompt("Enter web UI password", hide_input=True, confirmation_prompt=True)
|
|
# Use SHA-256 hash (bcrypt requires external dep, but cryptography is available)
|
|
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
|
|
import os
|
|
|
|
salt = os.urandom(16)
|
|
kdf = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1)
|
|
key = kdf.derive(password.encode())
|
|
hash_hex = salt.hex() + ":" + key.hex()
|
|
click.echo(f"\nAdd this to your vigilar.toml [web] section:")
|
|
click.echo(f'password_hash = "{hash_hex}"')
|
|
except Exception as e:
|
|
click.echo(f"Error: {e}", err=True)
|
|
sys.exit(1)
|
|
|
|
|
|
@config_cmd.command("set-pin")
|
|
def set_pin_cmd() -> None:
|
|
"""Generate an HMAC hash for the arm/disarm PIN."""
|
|
import hashlib
|
|
import hmac
|
|
import os
|
|
|
|
pin = click.prompt("Enter arm/disarm PIN", hide_input=True, confirmation_prompt=True)
|
|
secret = os.urandom(32)
|
|
mac = hmac.new(secret, pin.encode(), hashlib.sha256).hexdigest()
|
|
hash_str = secret.hex() + ":" + mac
|
|
click.echo(f"\nAdd this to your vigilar.toml [system] section:")
|
|
click.echo(f'arm_pin_hash = "{hash_str}"')
|