vigilar/vigilar/cli/cmd_config.py
Aaron D. Lee 845a85d618 Initial commit: Vigilar DIY home security system
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>
2026-04-02 23:11:27 -04:00

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}"')