diff --git a/kiosk/README.md b/kiosk/README.md new file mode 100644 index 0000000..5bed705 --- /dev/null +++ b/kiosk/README.md @@ -0,0 +1,135 @@ +# Vigilar Kiosk — Raspberry Pi TV Display + +Turn a Raspberry Pi into a dedicated security-camera display showing the +Vigilar 2x2 camera grid on any HDMI-connected TV. + +## Hardware + +- Raspberry Pi Zero 2W, 3, 4, or 5 +- Micro-HDMI (Zero 2W) or HDMI cable to TV +- Power supply (5V, 2.5A+) +- MicroSD card (8 GB+) + +## Quick Start + +### 1. Flash Raspberry Pi OS + +1. Download **Raspberry Pi OS Lite (64-bit)** (no desktop) from + . +2. Flash to MicroSD with Raspberry Pi Imager. +3. In Imager's settings (gear icon), enable SSH and set a password for the + `pi` user. Optionally configure Wi-Fi. +4. Boot the Pi. + +### 2. Copy kiosk files to the Pi + +From the machine running Vigilar: + +```bash +scp -r kiosk/ pi@:~/kiosk/ +``` + +### 3. Run setup + +```bash +ssh pi@ +cd ~/kiosk +sudo bash setup_kiosk.sh +``` + +The script will: +- Install X11, Chromium, and unclutter +- Create a `vigilar` user +- Ask for the Vigilar server URL (default: `https://vigilar.local:49735/kiosk/`) +- Configure auto-login and kiosk auto-start via systemd +- Set GPU memory, screen blanking, hostname, and SSH + +### 4. Reboot + +```bash +sudo reboot +``` + +The Pi will boot directly into the fullscreen camera grid. + +## Reconfiguring + +Change URL, rotation, or resolution without re-running full setup: + +```bash +# Interactive +sudo ./update_kiosk.sh + +# Non-interactive +sudo ./update_kiosk.sh --url https://192.168.1.50:49735/kiosk/ --restart + +# Change rotation (requires reboot) +sudo ./update_kiosk.sh --rotation 90 +sudo reboot +``` + +## Files + +| File | Purpose | +|------|---------| +| `setup_kiosk.sh` | Full initial setup script | +| `update_kiosk.sh` | Reconfigure URL/rotation/resolution | +| `kiosk.service` | systemd unit (installed by setup) | +| `kiosk_config.txt` | Template config (copied to Pi) | + +On the Pi after setup: + +| File | Purpose | +|------|---------| +| `/home/vigilar/kiosk_config.txt` | Active configuration | +| `/home/vigilar/.xinitrc` | X session startup | +| `/home/vigilar/.bash_profile` | Fallback auto-start | +| `/etc/systemd/system/vigilar-kiosk.service` | systemd service | + +## Management + +```bash +# Check status +sudo systemctl status vigilar-kiosk + +# View logs +sudo journalctl -u vigilar-kiosk -f + +# Restart kiosk +sudo systemctl restart vigilar-kiosk + +# Stop kiosk +sudo systemctl stop vigilar-kiosk + +# Switch from systemd to .bash_profile method +sudo systemctl disable vigilar-kiosk +# Then uncomment 'exec startx' in /home/vigilar/.bash_profile +``` + +## Troubleshooting + +### Black screen after boot +- Check that the Vigilar server is reachable: `curl -k https://vigilar.local:49735/kiosk/` +- Check service logs: `sudo journalctl -u vigilar-kiosk --no-pager -n 50` +- Verify X can start: `sudo -u vigilar startx -- -nocursor` + +### "Cannot open display" errors +- Ensure the Pi is booting to tty1 and the service has TTY access +- Check: `sudo systemctl status vigilar-kiosk` + +### Certificate errors in Chromium +- The `.xinitrc` includes `--ignore-certificate-errors` for self-signed certs +- For production, use a proper certificate on the Vigilar server + +### Screen stays on when it should sleep +- Screen blanking is deliberately disabled for a security kiosk +- To re-enable: remove `consoleblank=0` from cmdline.txt, remove `xset` lines from `.xinitrc` + +### Resolution or rotation not applied +- Rotation and resolution set in `config.txt` require a reboot +- Check current settings: `cat /boot/firmware/config.txt` (Bookworm) or `cat /boot/config.txt` + +### Low memory on Pi Zero 2W +- Chromium with `--incognito` and `--disk-cache-dir=/dev/null` minimises memory use +- The setup sets `gpu_mem=128` which is a good balance +- If OOM occurs, try `gpu_mem=64` in config.txt diff --git a/kiosk/kiosk.service b/kiosk/kiosk.service new file mode 100644 index 0000000..7b47c2b --- /dev/null +++ b/kiosk/kiosk.service @@ -0,0 +1,30 @@ +[Unit] +Description=Vigilar Kiosk (X11 + Chromium) +After=network-online.target systemd-user-sessions.service +Wants=network-online.target + +[Service] +Type=simple +User=vigilar +Group=vigilar +PAMName=login +TTYPath=/dev/tty1 +StandardInput=tty +StandardOutput=journal +StandardError=journal + +# Ensure we have access to the display hardware +SupplementaryGroups=video input render + +Environment=HOME=/home/vigilar +Environment=XDG_RUNTIME_DIR=/run/user/1001 +WorkingDirectory=/home/vigilar + +ExecStartPre=/bin/bash -c 'source /home/vigilar/kiosk_config.txt' +ExecStart=/usr/bin/xinit /home/vigilar/.xinitrc -- /usr/bin/X :0 vt1 -keeptty -nocursor + +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/kiosk/kiosk_config.txt b/kiosk/kiosk_config.txt new file mode 100644 index 0000000..0235e0b --- /dev/null +++ b/kiosk/kiosk_config.txt @@ -0,0 +1,3 @@ +VIGILAR_URL=https://vigilar.local:49735/kiosk/ +ROTATION=0 +RESOLUTION=1920x1080 diff --git a/kiosk/setup_kiosk.sh b/kiosk/setup_kiosk.sh new file mode 100755 index 0000000..8e09b14 --- /dev/null +++ b/kiosk/setup_kiosk.sh @@ -0,0 +1,385 @@ +#!/usr/bin/env bash +# Vigilar Kiosk — Full Raspberry Pi setup script +# Run on a fresh Raspberry Pi OS Lite (64-bit, no desktop). +# Safe to re-run (idempotent). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KIOSK_USER="vigilar" +KIOSK_HOME="/home/${KIOSK_USER}" +CONFIG_FILE="${KIOSK_HOME}/kiosk_config.txt" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +info() { printf '\n\033[1;34m>>> %s\033[0m\n' "$*"; } +warn() { printf '\033[1;33mWARN: %s\033[0m\n' "$*"; } +error() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; } + +need_root() { + if [[ $EUID -ne 0 ]]; then + error "This script must be run as root (use sudo)." + fi +} + +# --------------------------------------------------------------------------- +# Pre-flight +# --------------------------------------------------------------------------- +need_root +info "Vigilar Kiosk Setup" +echo "This script turns a Raspberry Pi into a dedicated security-camera display." +echo "" + +# Detect Debian version +if [[ -f /etc/os-release ]]; then + # shellcheck source=/dev/null + . /etc/os-release + DEBIAN_VERSION="${VERSION_ID:-11}" +else + DEBIAN_VERSION="11" +fi +info "Detected Debian version: ${DEBIAN_VERSION} (${PRETTY_NAME:-unknown})" + +# --------------------------------------------------------------------------- +# 1. Ask for Vigilar server URL +# --------------------------------------------------------------------------- +DEFAULT_URL="https://vigilar.local:49735/kiosk/" +read -r -p "Vigilar server URL [${DEFAULT_URL}]: " USER_URL +VIGILAR_URL="${USER_URL:-$DEFAULT_URL}" + +# --------------------------------------------------------------------------- +# 2. Ask for display settings +# --------------------------------------------------------------------------- +read -r -p "Screen rotation (0/90/180/270) [0]: " USER_ROTATION +ROTATION="${USER_ROTATION:-0}" + +read -r -p "Screen resolution (e.g. 1920x1080, 1280x720) [1920x1080]: " USER_RES +RESOLUTION="${USER_RES:-1920x1080}" + +echo "" +info "Configuration summary" +echo " URL: ${VIGILAR_URL}" +echo " Rotation: ${ROTATION}" +echo " Resolution: ${RESOLUTION}" +echo "" +read -r -p "Continue? [Y/n]: " CONFIRM +if [[ "${CONFIRM,,}" == "n" ]]; then + echo "Aborted." + exit 0 +fi + +# --------------------------------------------------------------------------- +# 3. Install packages +# --------------------------------------------------------------------------- +info "Updating package lists" +apt-get update -qq + +info "Installing X11, Chromium, and utilities" +PACKAGES=( + xserver-xorg + xinit + x11-xserver-utils + chromium-browser + unclutter +) + +# On Bookworm the package may just be 'chromium' +if [[ "${DEBIAN_VERSION}" -ge 12 ]]; then + # Try chromium-browser first; fall back to chromium + if ! apt-cache show chromium-browser &>/dev/null; then + PACKAGES=("${PACKAGES[@]/chromium-browser/chromium}") + fi +fi + +apt-get install -y -qq "${PACKAGES[@]}" + +# --------------------------------------------------------------------------- +# 4. Create kiosk user +# --------------------------------------------------------------------------- +if id "${KIOSK_USER}" &>/dev/null; then + info "User '${KIOSK_USER}' already exists" +else + info "Creating user '${KIOSK_USER}'" + useradd -m -s /bin/bash -G video,input,render,tty "${KIOSK_USER}" +fi + +# Ensure group memberships even if user existed +usermod -aG video,input,render,tty "${KIOSK_USER}" + +# --------------------------------------------------------------------------- +# 5. Write config file +# --------------------------------------------------------------------------- +info "Writing ${CONFIG_FILE}" +cat > "${CONFIG_FILE}" < "${KIOSK_HOME}/.xinitrc" <<'XINITEOF' +#!/usr/bin/env bash +# Vigilar kiosk X session +set -euo pipefail + +CONFIG="$HOME/kiosk_config.txt" +if [[ -f "$CONFIG" ]]; then + # shellcheck source=/dev/null + source "$CONFIG" +fi +URL="${VIGILAR_URL:-https://vigilar.local:49735/kiosk/}" + +# Disable screen blanking and DPMS +xset s off +xset s noblank +xset -dpms + +# Hide cursor after 0.5 s of inactivity +unclutter -idle 0.5 -root & + +# Handle rotation +ROTATION="${ROTATION:-0}" +case "${ROTATION}" in + 90) xrandr --output "$(xrandr | grep ' connected' | head -1 | awk '{print $1}')" --rotate left ;; + 180) xrandr --output "$(xrandr | grep ' connected' | head -1 | awk '{print $1}')" --rotate inverted ;; + 270) xrandr --output "$(xrandr | grep ' connected' | head -1 | awk '{print $1}')" --rotate right ;; + *) ;; # 0 = normal, no action needed +esac + +# Determine chromium binary +CHROMIUM="" +for candidate in chromium-browser chromium; do + if command -v "$candidate" &>/dev/null; then + CHROMIUM="$candidate" + break + fi +done +if [[ -z "$CHROMIUM" ]]; then + echo "ERROR: No chromium binary found" >&2 + exit 1 +fi + +# Launch Chromium in kiosk mode +# --ignore-certificate-errors is needed for self-signed TLS certs on vigilar.local +exec "$CHROMIUM" \ + --noerrdialogs \ + --disable-infobars \ + --kiosk \ + --incognito \ + --disable-translate \ + --no-first-run \ + --fast \ + --fast-start \ + --disable-features=TranslateUI \ + --disk-cache-dir=/dev/null \ + --check-for-update-interval=31536000 \ + --ignore-certificate-errors \ + --disable-component-update \ + --disable-background-networking \ + --disable-sync \ + --autoplay-policy=no-user-gesture-required \ + "$URL" +XINITEOF +chmod +x "${KIOSK_HOME}/.xinitrc" +chown "${KIOSK_USER}:${KIOSK_USER}" "${KIOSK_HOME}/.xinitrc" + +# --------------------------------------------------------------------------- +# 7. Write .bash_profile for auto-start (fallback method) +# --------------------------------------------------------------------------- +info "Writing ${KIOSK_HOME}/.bash_profile (auto-start X on tty1)" +# Preserve existing content if any +BASH_PROFILE="${KIOSK_HOME}/.bash_profile" +MARKER="# --- Vigilar kiosk auto-start ---" + +# Remove old block if present +if [[ -f "${BASH_PROFILE}" ]]; then + sed -i "/${MARKER}/,/# --- end Vigilar kiosk ---/d" "${BASH_PROFILE}" +fi + +cat >> "${BASH_PROFILE}" <<'BPEOF' +# --- Vigilar kiosk auto-start --- +if [[ -z "${DISPLAY:-}" ]] && [[ "$(tty)" == "/dev/tty1" ]]; then + exec startx -- -nocursor +fi +# --- end Vigilar kiosk --- +BPEOF +chown "${KIOSK_USER}:${KIOSK_USER}" "${BASH_PROFILE}" + +# --------------------------------------------------------------------------- +# 8. Install systemd service (default method) +# --------------------------------------------------------------------------- +info "Installing systemd service" +SERVICE_SRC="${SCRIPT_DIR}/kiosk.service" +SERVICE_DEST="/etc/systemd/system/vigilar-kiosk.service" + +if [[ -f "${SERVICE_SRC}" ]]; then + cp "${SERVICE_SRC}" "${SERVICE_DEST}" +else + # Generate inline if source file not found + cat > "${SERVICE_DEST}" < "${AUTOLOGIN_DIR}/autologin.conf" <> "${CONFIG_TXT}" + fi +fi + +# --------------------------------------------------------------------------- +# 12. Screen rotation and resolution in config.txt +# --------------------------------------------------------------------------- +info "Configuring display rotation and resolution" +if [[ -f "${CONFIG_TXT}" ]]; then + # Rotation via display_rotate (legacy) or display_lcd_rotate + if [[ "${ROTATION}" != "0" ]]; then + ROTATE_VAL=0 + case "${ROTATION}" in + 90) ROTATE_VAL=1 ;; + 180) ROTATE_VAL=2 ;; + 270) ROTATE_VAL=3 ;; + esac + if grep -q '^display_rotate=' "${CONFIG_TXT}"; then + sed -i "s/^display_rotate=.*/display_rotate=${ROTATE_VAL}/" "${CONFIG_TXT}" + else + echo "display_rotate=${ROTATE_VAL}" >> "${CONFIG_TXT}" + fi + fi + + # Resolution via hdmi_group and hdmi_mode + # Parse WxH + RES_W="${RESOLUTION%%x*}" + RES_H="${RESOLUTION##*x}" + HDMI_MODE="" + case "${RES_W}x${RES_H}" in + 1920x1080) HDMI_MODE=16 ;; + 1280x720) HDMI_MODE=4 ;; + 1680x1050) HDMI_MODE=58 ;; + 1280x1024) HDMI_MODE=35 ;; + 1024x768) HDMI_MODE=16 ;; # DMT mode 16 + *) warn "Unknown resolution ${RESOLUTION}, skipping hdmi_mode config" ;; + esac + + if [[ -n "${HDMI_MODE}" ]]; then + # hdmi_group=1 = CEA (TVs), hdmi_group=2 = DMT (monitors) + HDMI_GROUP=1 + if [[ "${RES_W}x${RES_H}" == "1680x1050" ]] || [[ "${RES_W}x${RES_H}" == "1280x1024" ]]; then + HDMI_GROUP=2 + fi + for KEY_VAL in "hdmi_group=${HDMI_GROUP}" "hdmi_mode=${HDMI_MODE}"; do + KEY="${KEY_VAL%%=*}" + if grep -q "^${KEY}=" "${CONFIG_TXT}"; then + sed -i "s/^${KEY}=.*/${KEY_VAL}/" "${CONFIG_TXT}" + else + echo "${KEY_VAL}" >> "${CONFIG_TXT}" + fi + done + fi +fi + +# --------------------------------------------------------------------------- +# 13. Enable SSH +# --------------------------------------------------------------------------- +info "Enabling SSH" +systemctl enable ssh 2>/dev/null || systemctl enable sshd 2>/dev/null || true +systemctl start ssh 2>/dev/null || systemctl start sshd 2>/dev/null || true + +# --------------------------------------------------------------------------- +# 14. Set hostname +# --------------------------------------------------------------------------- +info "Setting hostname to 'vigilar-kiosk'" +hostnamectl set-hostname vigilar-kiosk 2>/dev/null || { + echo "vigilar-kiosk" > /etc/hostname + sed -i "s/127\.0\.1\.1.*/127.0.1.1\tvigilar-kiosk/" /etc/hosts +} + +# --------------------------------------------------------------------------- +# Done +# --------------------------------------------------------------------------- +info "Setup complete!" +echo "" +echo " Kiosk URL: ${VIGILAR_URL}" +echo " User: ${KIOSK_USER}" +echo " Service: vigilar-kiosk.service (systemd, enabled)" +echo " Config: ${CONFIG_FILE}" +echo "" +echo " To start now: sudo systemctl start vigilar-kiosk" +echo " To check status: sudo systemctl status vigilar-kiosk" +echo " To view logs: sudo journalctl -u vigilar-kiosk -f" +echo " To reconfigure: sudo ./update_kiosk.sh" +echo "" +echo " A reboot is recommended: sudo reboot" diff --git a/kiosk/update_kiosk.sh b/kiosk/update_kiosk.sh new file mode 100755 index 0000000..d40e3fc --- /dev/null +++ b/kiosk/update_kiosk.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# Vigilar Kiosk — Update/reconfigure script +# Change the URL, rotation, or resolution without re-running full setup. +set -euo pipefail + +KIOSK_USER="vigilar" +KIOSK_HOME="/home/${KIOSK_USER}" +CONFIG_FILE="${KIOSK_HOME}/kiosk_config.txt" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +info() { printf '\n\033[1;34m>>> %s\033[0m\n' "$*"; } +warn() { printf '\033[1;33mWARN: %s\033[0m\n' "$*"; } +error() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; } + +need_root() { + if [[ $EUID -ne 0 ]]; then + error "This script must be run as root (use sudo)." + fi +} + +# --------------------------------------------------------------------------- +# Load current config +# --------------------------------------------------------------------------- +need_root + +CURRENT_URL="" +CURRENT_ROTATION="0" +CURRENT_RESOLUTION="1920x1080" + +if [[ -f "${CONFIG_FILE}" ]]; then + # shellcheck source=/dev/null + source "${CONFIG_FILE}" + CURRENT_URL="${VIGILAR_URL:-}" + CURRENT_ROTATION="${ROTATION:-0}" + CURRENT_RESOLUTION="${RESOLUTION:-1920x1080}" +fi + +info "Vigilar Kiosk — Update Configuration" +echo "" +echo "Current settings:" +echo " URL: ${CURRENT_URL:-}" +echo " Rotation: ${CURRENT_ROTATION}" +echo " Resolution: ${CURRENT_RESOLUTION}" +echo "" + +# --------------------------------------------------------------------------- +# Parse arguments or ask interactively +# --------------------------------------------------------------------------- +usage() { + echo "Usage: $0 [--url URL] [--rotation 0|90|180|270] [--resolution WxH]" + echo "" + echo "Options:" + echo " --url URL Vigilar server URL" + echo " --rotation DEGREE Screen rotation (0, 90, 180, 270)" + echo " --resolution WxH Screen resolution (e.g. 1920x1080)" + echo " --restart Restart the kiosk service after update" + echo " -h, --help Show this help" + exit 0 +} + +NEW_URL="" +NEW_ROTATION="" +NEW_RESOLUTION="" +DO_RESTART=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --url) NEW_URL="$2"; shift 2 ;; + --rotation) NEW_ROTATION="$2"; shift 2 ;; + --resolution) NEW_RESOLUTION="$2"; shift 2 ;; + --restart) DO_RESTART=true; shift ;; + -h|--help) usage ;; + *) warn "Unknown option: $1"; shift ;; + esac +done + +# If no arguments given, ask interactively +if [[ -z "${NEW_URL}" && -z "${NEW_ROTATION}" && -z "${NEW_RESOLUTION}" ]]; then + read -r -p "Vigilar URL [${CURRENT_URL}]: " NEW_URL + read -r -p "Rotation (0/90/180/270) [${CURRENT_ROTATION}]: " NEW_ROTATION + read -r -p "Resolution [${CURRENT_RESOLUTION}]: " NEW_RESOLUTION +fi + +# Fall back to current values +VIGILAR_URL="${NEW_URL:-$CURRENT_URL}" +ROTATION="${NEW_ROTATION:-$CURRENT_ROTATION}" +RESOLUTION="${NEW_RESOLUTION:-$CURRENT_RESOLUTION}" + +# Validate rotation +case "${ROTATION}" in + 0|90|180|270) ;; + *) error "Invalid rotation: ${ROTATION}. Must be 0, 90, 180, or 270." ;; +esac + +# Validate resolution format +if [[ ! "${RESOLUTION}" =~ ^[0-9]+x[0-9]+$ ]]; then + error "Invalid resolution format: ${RESOLUTION}. Expected WxH (e.g. 1920x1080)." +fi + +# --------------------------------------------------------------------------- +# Write config +# --------------------------------------------------------------------------- +info "Writing ${CONFIG_FILE}" +cat > "${CONFIG_FILE}" <&2; exit 1; } + +main() { + info "=== Vigilar Backup ===" + + # Create backup destination + sudo mkdir -p "$BACKUP_DEST" + + # Collect files to back up + local items=() + + # SQLite database + if [[ -d "$DATA_DIR" ]]; then + # Use sqlite3 .backup for a consistent snapshot if sqlite3 is available + local db_file="${DATA_DIR}/vigilar.db" + if [[ -f "$db_file" ]] && command -v sqlite3 &>/dev/null; then + local db_snapshot="/tmp/vigilar-backup-${TIMESTAMP}.db" + info "Creating consistent database snapshot" + sqlite3 "$db_file" ".backup '${db_snapshot}'" + items+=("$db_snapshot") + elif [[ -f "$db_file" ]]; then + warn "sqlite3 not found, copying database file directly (may be inconsistent if running)" + items+=("$db_file") + fi + + # Also back up any WAL/SHM files + for f in "${db_file}-wal" "${db_file}-shm"; do + if [[ -f "$f" ]]; then + items+=("$f") + fi + done + else + warn "Data directory ${DATA_DIR} not found, skipping database" + fi + + # Config directory (includes secrets, certs, toml) + if [[ -d "$CONFIG_DIR" ]]; then + items+=("$CONFIG_DIR") + else + warn "Config directory ${CONFIG_DIR} not found, skipping config" + fi + + if [[ ${#items[@]} -eq 0 ]]; then + fail "Nothing to back up" + fi + + # Create archive + local archive_path="${BACKUP_DEST}/${ARCHIVE_NAME}" + info "Creating archive: ${archive_path}" + sudo tar -czf "$archive_path" "${items[@]}" 2>/dev/null + + # Secure the backup (contains secrets) + sudo chmod 0600 "$archive_path" + sudo chown root:root "$archive_path" + + # Clean up temp db snapshot + if [[ -f "/tmp/vigilar-backup-${TIMESTAMP}.db" ]]; then + rm -f "/tmp/vigilar-backup-${TIMESTAMP}.db" + fi + + local size + size="$(du -h "$archive_path" | cut -f1)" + ok "Backup complete: ${archive_path} (${size})" + + # Prune old backups + if [[ "$RETENTION_DAYS" -gt 0 ]]; then + info "Pruning backups older than ${RETENTION_DAYS} days" + local pruned + pruned="$(sudo find "$BACKUP_DEST" -name 'vigilar-backup-*.tar.gz' -mtime +"$RETENTION_DAYS" -delete -print | wc -l)" + if [[ "$pruned" -gt 0 ]]; then + ok "Pruned ${pruned} old backup(s)" + fi + fi +} + +main "$@" diff --git a/scripts/gen_cert.sh b/scripts/gen_cert.sh new file mode 100755 index 0000000..855c775 --- /dev/null +++ b/scripts/gen_cert.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Vigilar — Self-signed TLS certificate generator +# Uses mkcert if available, otherwise falls back to openssl. + +CONFIG_DIR="/etc/vigilar" +CERT_DIR="${CONFIG_DIR}/certs" +CERT_FILE="${CERT_DIR}/cert.pem" +KEY_FILE="${CERT_DIR}/key.pem" +CONFIG_FILE="${CONFIG_DIR}/vigilar.toml" +VIGILAR_GROUP="vigilar" + +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[ OK ]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31m[FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +get_lan_ip() { + # Try to detect the primary LAN IP + ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1 +} + +generate_with_mkcert() { + local lan_ip="$1" + info "Using mkcert to generate certificate" + + local san_args=("vigilar.local" "localhost" "127.0.0.1") + if [[ -n "$lan_ip" ]]; then + san_args+=("$lan_ip") + fi + + mkcert -cert-file "$CERT_FILE" -key-file "$KEY_FILE" "${san_args[@]}" + ok "Certificate generated with mkcert" +} + +generate_with_openssl() { + local lan_ip="$1" + info "Using openssl to generate self-signed certificate" + + local san="DNS:vigilar.local,DNS:localhost,IP:127.0.0.1" + if [[ -n "$lan_ip" ]]; then + san="${san},IP:${lan_ip}" + fi + + openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ + -keyout "$KEY_FILE" \ + -out "$CERT_FILE" \ + -sha256 -days 3650 -nodes \ + -subj "/CN=vigilar.local" \ + -addext "subjectAltName=${san}" \ + 2>/dev/null + + ok "Self-signed certificate generated with openssl" +} + +update_config() { + if [[ ! -f "$CONFIG_FILE" ]]; then + warn "Config file not found at ${CONFIG_FILE}, skipping config update" + return + fi + + # Uncomment the tls_cert and tls_key lines if they are commented out + if grep -q '^# *tls_cert' "$CONFIG_FILE"; then + sudo sed -i 's|^# *tls_cert *=.*|tls_cert = "/etc/vigilar/certs/cert.pem"|' "$CONFIG_FILE" + sudo sed -i 's|^# *tls_key *=.*|tls_key = "/etc/vigilar/certs/key.pem"|' "$CONFIG_FILE" + ok "Config updated with TLS cert paths" + elif grep -q '^tls_cert' "$CONFIG_FILE"; then + ok "Config already has TLS cert paths" + else + # Append after the [web] section port line + sudo sed -i '/^\[web\]/,/^$/{/^port/a\tls_cert = "/etc/vigilar/certs/cert.pem"\ntls_key = "/etc/vigilar/certs/key.pem" + }' "$CONFIG_FILE" + ok "Config updated with TLS cert paths" + fi +} + +main() { + info "=== Vigilar TLS Certificate Generator ===" + + sudo mkdir -p "$CERT_DIR" + + if [[ -f "$CERT_FILE" && -f "$KEY_FILE" ]]; then + warn "Certificates already exist at ${CERT_DIR}/" + read -rp "Overwrite? [y/N] " answer + if [[ ! "$answer" =~ ^[Yy]$ ]]; then + info "Keeping existing certificates" + exit 0 + fi + fi + + local lan_ip + lan_ip="$(get_lan_ip)" || lan_ip="" + if [[ -n "$lan_ip" ]]; then + info "Detected LAN IP: ${lan_ip}" + else + warn "Could not detect LAN IP, skipping IP SAN" + fi + + if command -v mkcert &>/dev/null; then + generate_with_mkcert "$lan_ip" + elif command -v openssl &>/dev/null; then + generate_with_openssl "$lan_ip" + else + fail "Neither mkcert nor openssl found. Install one and retry." + fi + + # Set permissions — readable by vigilar group + sudo chown root:"${VIGILAR_GROUP}" "$CERT_FILE" "$KEY_FILE" + sudo chmod 0640 "$KEY_FILE" + sudo chmod 0644 "$CERT_FILE" + + update_config + + echo + ok "TLS certificate ready" + info " Cert: ${CERT_FILE}" + info " Key: ${KEY_FILE}" + if [[ -n "$lan_ip" ]]; then + info " SANs: vigilar.local, localhost, 127.0.0.1, ${lan_ip}" + else + info " SANs: vigilar.local, localhost, 127.0.0.1" + fi +} + +main "$@" diff --git a/scripts/gen_vapid_keys.sh b/scripts/gen_vapid_keys.sh new file mode 100755 index 0000000..2b9ab8e --- /dev/null +++ b/scripts/gen_vapid_keys.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Vigilar — VAPID key generator for Web Push notifications +# Uses py-vapid (from the vigilar venv) or falls back to openssl. + +VENV_DIR="/opt/vigilar/venv" +SECRETS_DIR="/etc/vigilar/secrets" +PRIVATE_KEY_FILE="${SECRETS_DIR}/vapid_private.pem" +PUBLIC_KEY_FILE="${SECRETS_DIR}/vapid_public.txt" +VIGILAR_GROUP="vigilar" + +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[ OK ]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31m[FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +generate_with_py_vapid() { + info "Generating VAPID keys with py-vapid" + local python="${VENV_DIR}/bin/python" + + "$python" -c " +from py_vapid import Vapid +import base64 + +v = Vapid() +v.generate_keys() +v.save_key('${PRIVATE_KEY_FILE}') + +raw = v.public_key.public_bytes( + encoding=__import__('cryptography.hazmat.primitives.serialization', fromlist=['Encoding']).Encoding.X962, + format=__import__('cryptography.hazmat.primitives.serialization', fromlist=['PublicFormat']).PublicFormat.UncompressedPoint, +) +pub_b64 = base64.urlsafe_b64encode(raw).rstrip(b'=').decode() +print(pub_b64) +" | tee "$PUBLIC_KEY_FILE" +} + +generate_with_openssl() { + info "Generating VAPID keys with openssl" + + # Generate ECDSA P-256 private key in PEM format + openssl ecparam -name prime256v1 -genkey -noout -out "$PRIVATE_KEY_FILE" 2>/dev/null + + # Extract the public key in uncompressed point format, base64url-encode it + local pub_b64 + pub_b64="$(openssl ec -in "$PRIVATE_KEY_FILE" -pubout -outform DER 2>/dev/null \ + | tail -c 65 \ + | base64 -w 0 \ + | tr '+/' '-_' \ + | tr -d '=')" + + echo "$pub_b64" | tee "$PUBLIC_KEY_FILE" +} + +main() { + info "=== Vigilar VAPID Key Generator ===" + + sudo mkdir -p "$SECRETS_DIR" + + if [[ -f "$PRIVATE_KEY_FILE" ]]; then + warn "VAPID private key already exists at ${PRIVATE_KEY_FILE}" + read -rp "Overwrite? [y/N] " answer + if [[ ! "$answer" =~ ^[Yy]$ ]]; then + info "Keeping existing key" + if [[ -f "$PUBLIC_KEY_FILE" ]]; then + info "Public key (base64url):" + cat "$PUBLIC_KEY_FILE" + fi + exit 0 + fi + fi + + local public_key + if [[ -x "${VENV_DIR}/bin/python" ]] && "${VENV_DIR}/bin/python" -c "import py_vapid" 2>/dev/null; then + public_key="$(generate_with_py_vapid)" + elif command -v openssl &>/dev/null; then + public_key="$(generate_with_openssl)" + else + fail "Neither py-vapid nor openssl found." + fi + + # Secure the private key + sudo chown root:root "$PRIVATE_KEY_FILE" + sudo chmod 0600 "$PRIVATE_KEY_FILE" + + # Public key file is not sensitive + sudo chown root:"${VIGILAR_GROUP}" "$PUBLIC_KEY_FILE" + sudo chmod 0644 "$PUBLIC_KEY_FILE" + + echo + ok "VAPID keys generated" + info " Private key: ${PRIVATE_KEY_FILE}" + info " Public key (base64url):" + echo " ${public_key}" + echo + info "Use the public key above in your web app's push subscription config." +} + +main "$@" diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..2a7549d --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Vigilar Home Security — Installation Script +# Supports Debian/Ubuntu (apt) and Arch Linux (pacman). + +VIGILAR_USER="vigilar" +VIGILAR_GROUP="vigilar" +INSTALL_DIR="/opt/vigilar" +VENV_DIR="${INSTALL_DIR}/venv" +CONFIG_DIR="/etc/vigilar" +DATA_DIR="/var/vigilar" +SYSTEMD_DIR="/etc/systemd/system" +MOSQUITTO_CONF_DIR="/etc/mosquitto/conf.d" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[ OK ]\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31m[FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +need_cmd() { + command -v "$1" &>/dev/null || fail "Required command not found: $1" +} + +detect_pkg_manager() { + if command -v apt-get &>/dev/null; then + echo "apt" + elif command -v pacman &>/dev/null; then + echo "pacman" + else + fail "Unsupported package manager. This script supports apt (Debian/Ubuntu) and pacman (Arch)." + fi +} + +# --------------------------------------------------------------------------- +# 1. System dependencies +# --------------------------------------------------------------------------- + +install_system_deps() { + local pkg_mgr + pkg_mgr="$(detect_pkg_manager)" + info "Detected package manager: ${pkg_mgr}" + + case "$pkg_mgr" in + apt) + sudo apt-get update -qq + sudo apt-get install -y -qq \ + ffmpeg mosquitto python3 python3-venv python3-pip nut-client + ;; + pacman) + sudo pacman -Sy --needed --noconfirm \ + ffmpeg mosquitto python python-virtualenv nut + ;; + esac + ok "System dependencies installed" +} + +# --------------------------------------------------------------------------- +# 2. System user & group +# --------------------------------------------------------------------------- + +create_user() { + if id "$VIGILAR_USER" &>/dev/null; then + ok "User '${VIGILAR_USER}' already exists" + return + fi + info "Creating system user '${VIGILAR_USER}'" + sudo useradd --system --home-dir "$INSTALL_DIR" --shell /usr/sbin/nologin \ + --create-home "$VIGILAR_USER" + ok "User '${VIGILAR_USER}' created" +} + +# --------------------------------------------------------------------------- +# 3. Directories & permissions +# --------------------------------------------------------------------------- + +create_directories() { + info "Creating directories" + + # Data directories — owned by vigilar + sudo mkdir -p "${DATA_DIR}/data" "${DATA_DIR}/recordings" "${DATA_DIR}/hls" + sudo chown -R "${VIGILAR_USER}:${VIGILAR_GROUP}" "$DATA_DIR" + sudo chmod -R 0750 "$DATA_DIR" + + # Config directories + sudo mkdir -p "${CONFIG_DIR}/secrets" "${CONFIG_DIR}/certs" + sudo chown root:root "${CONFIG_DIR}" + sudo chmod 0755 "${CONFIG_DIR}" + + # Secrets — root-owned, restricted + sudo chown root:root "${CONFIG_DIR}/secrets" + sudo chmod 0700 "${CONFIG_DIR}/secrets" + + # Certs — readable by vigilar + sudo chown root:"${VIGILAR_GROUP}" "${CONFIG_DIR}/certs" + sudo chmod 0750 "${CONFIG_DIR}/certs" + + # Install dir + sudo mkdir -p "$INSTALL_DIR" + sudo chown "${VIGILAR_USER}:${VIGILAR_GROUP}" "$INSTALL_DIR" + + ok "Directories created" +} + +# --------------------------------------------------------------------------- +# 4. Python venv & package +# --------------------------------------------------------------------------- + +install_venv() { + if [[ -d "$VENV_DIR" ]]; then + info "Venv already exists at ${VENV_DIR}, upgrading" + else + info "Creating Python venv at ${VENV_DIR}" + sudo -u "$VIGILAR_USER" python3 -m venv "$VENV_DIR" + fi + + info "Installing vigilar package into venv" + sudo -u "$VIGILAR_USER" "${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel -q + sudo -u "$VIGILAR_USER" "${VENV_DIR}/bin/pip" install "${PROJECT_DIR}" -q + + ok "Vigilar installed into ${VENV_DIR}" +} + +# --------------------------------------------------------------------------- +# 5. Storage encryption key +# --------------------------------------------------------------------------- + +generate_storage_key() { + local key_file="${CONFIG_DIR}/secrets/storage.key" + if [[ -f "$key_file" ]]; then + ok "Storage encryption key already exists" + return + fi + info "Generating storage encryption key" + sudo dd if=/dev/urandom bs=32 count=1 2>/dev/null | sudo tee "$key_file" > /dev/null + sudo chmod 0600 "$key_file" + sudo chown root:root "$key_file" + ok "Storage key written to ${key_file}" +} + +# --------------------------------------------------------------------------- +# 6. Sample config +# --------------------------------------------------------------------------- + +install_config() { + local dest="${CONFIG_DIR}/vigilar.toml" + if [[ -f "$dest" ]]; then + ok "Config already exists at ${dest}" + return + fi + info "Copying sample config" + sudo cp "${PROJECT_DIR}/config/vigilar.toml" "$dest" + sudo chmod 0644 "$dest" + sudo chown root:"${VIGILAR_GROUP}" "$dest" + ok "Config installed to ${dest}" +} + +# --------------------------------------------------------------------------- +# 7. Systemd units +# --------------------------------------------------------------------------- + +install_systemd() { + info "Installing systemd service" + sudo cp "${PROJECT_DIR}/systemd/vigilar.service" "${SYSTEMD_DIR}/vigilar.service" + sudo chmod 0644 "${SYSTEMD_DIR}/vigilar.service" + sudo systemctl daemon-reload + sudo systemctl enable vigilar.service + ok "vigilar.service enabled" +} + +# --------------------------------------------------------------------------- +# 8. Mosquitto configuration +# --------------------------------------------------------------------------- + +configure_mosquitto() { + local conf="${MOSQUITTO_CONF_DIR}/vigilar.conf" + info "Configuring mosquitto for localhost-only" + sudo mkdir -p "$MOSQUITTO_CONF_DIR" + sudo cp "${PROJECT_DIR}/systemd/vigilar-mosquitto.conf" "$conf" + sudo chmod 0644 "$conf" + + sudo systemctl enable mosquitto.service + sudo systemctl restart mosquitto.service + ok "Mosquitto configured and restarted" +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +main() { + info "=== Vigilar Home Security — Installer ===" + info "Project dir: ${PROJECT_DIR}" + echo + + install_system_deps + create_user + create_directories + install_venv + generate_storage_key + install_config + install_systemd + configure_mosquitto + + echo + ok "=== Installation complete ===" + echo + info "Summary:" + info " Service user: ${VIGILAR_USER}" + info " Venv: ${VENV_DIR}" + info " Config: ${CONFIG_DIR}/vigilar.toml" + info " Data: ${DATA_DIR}/" + info " Secrets: ${CONFIG_DIR}/secrets/" + info " Systemd unit: ${SYSTEMD_DIR}/vigilar.service" + echo + info "Next steps:" + info " 1. Edit /etc/vigilar/vigilar.toml — set camera RTSP URLs, passwords, etc." + info " 2. Run: sudo ${SCRIPT_DIR}/gen_cert.sh — generate TLS certs" + info " 3. Run: sudo ${SCRIPT_DIR}/gen_vapid_keys.sh — generate VAPID keys for push" + info " 4. Run: sudo ${SCRIPT_DIR}/setup_nut.sh — configure UPS monitoring" + info " 5. Start: sudo systemctl start vigilar" + info " 6. Open: https://vigilar.local:49735" +} + +main "$@" diff --git a/scripts/setup_nut.sh b/scripts/setup_nut.sh new file mode 100755 index 0000000..e0e2b2e --- /dev/null +++ b/scripts/setup_nut.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Vigilar — NUT (Network UPS Tools) configuration helper +# Detects USB UPS devices and configures NUT for standalone monitoring. + +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[ OK ]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } +fail() { printf '\033[1;31m[FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +detect_pkg_manager() { + if command -v apt-get &>/dev/null; then + echo "apt" + elif command -v pacman &>/dev/null; then + echo "pacman" + else + fail "Unsupported package manager." + fi +} + +install_nut() { + if command -v upsc &>/dev/null; then + ok "NUT already installed" + return + fi + + info "Installing NUT" + local pkg_mgr + pkg_mgr="$(detect_pkg_manager)" + + case "$pkg_mgr" in + apt) + sudo apt-get update -qq + sudo apt-get install -y -qq nut nut-client nut-server + ;; + pacman) + sudo pacman -Sy --needed --noconfirm nut + ;; + esac + ok "NUT installed" +} + +detect_ups() { + info "Scanning for USB UPS devices..." + + local driver="" + local port="" + local desc="" + + # Try nut-scanner first + if command -v nut-scanner &>/dev/null; then + local scan_output + scan_output="$(sudo nut-scanner -U 2>/dev/null)" || true + if [[ -n "$scan_output" ]]; then + driver="$(echo "$scan_output" | grep -oP 'driver\s*=\s*"\K[^"]+' | head -1)" || true + port="$(echo "$scan_output" | grep -oP 'port\s*=\s*"\K[^"]+' | head -1)" || true + desc="$(echo "$scan_output" | grep -oP 'desc\s*=\s*"\K[^"]+' | head -1)" || true + fi + fi + + # Fallback: check for common USB UPS vendor IDs + if [[ -z "$driver" ]]; then + if lsusb 2>/dev/null | grep -qi "051d"; then + driver="usbhid-ups" + port="auto" + desc="APC UPS (auto-detected via lsusb)" + elif lsusb 2>/dev/null | grep -qi "0764"; then + driver="usbhid-ups" + port="auto" + desc="CyberPower UPS (auto-detected via lsusb)" + elif lsusb 2>/dev/null | grep -qi "0463"; then + driver="usbhid-ups" + port="auto" + desc="Eaton UPS (auto-detected via lsusb)" + elif lsusb 2>/dev/null | grep -qi "06da"; then + driver="usbhid-ups" + port="auto" + desc="Phoenixtec/Tripp Lite UPS (auto-detected via lsusb)" + fi + fi + + if [[ -z "$driver" ]]; then + warn "No USB UPS detected. Using generic usbhid-ups driver with auto port." + warn "You may need to edit /etc/nut/ups.conf manually." + driver="usbhid-ups" + port="auto" + desc="UPS (not auto-detected)" + else + ok "Detected UPS: ${desc:-unknown}" + fi + + # Export for use in config generation + UPS_DRIVER="$driver" + UPS_PORT="$port" + UPS_DESC="${desc:-UPS}" +} + +generate_configs() { + info "Generating NUT configuration" + + # /etc/nut/nut.conf — standalone mode + sudo tee /etc/nut/nut.conf > /dev/null <<'NUTCONF' +# Vigilar NUT configuration — standalone mode +MODE=standalone +NUTCONF + + # /etc/nut/ups.conf — UPS definition + sudo tee /etc/nut/ups.conf > /dev/null < /dev/null <<'UPSDCONF' +# Vigilar upsd configuration +LISTEN 127.0.0.1 3493 +UPSDCONF + + # /etc/nut/upsd.users — local monitoring user + sudo tee /etc/nut/upsd.users > /dev/null <<'USERSCONF' +[vigilar] + password = vigilar_local + upsmon master +USERSCONF + + # /etc/nut/upsmon.conf — monitoring config + sudo tee /etc/nut/upsmon.conf > /dev/null <<'MONCONF' +# Vigilar upsmon configuration +MONITOR ups@localhost 1 vigilar vigilar_local master +MINSUPPLIES 1 +SHUTDOWNCMD "/sbin/shutdown -h +0" +POLLFREQ 15 +POLLFREQALERT 5 +HOSTSYNC 15 +DEADTIME 45 +RBWARNTIME 43200 +NOCOMMWARNTIME 600 +FINALDELAY 5 +MONCONF + + # Secure the config files + sudo chmod 0640 /etc/nut/ups.conf /etc/nut/upsd.conf /etc/nut/upsd.users /etc/nut/upsmon.conf + sudo chown root:nut /etc/nut/ups.conf /etc/nut/upsd.conf /etc/nut/upsd.users /etc/nut/upsmon.conf 2>/dev/null || true + + ok "NUT configuration files written" +} + +enable_services() { + info "Enabling and starting NUT services" + + # Service names vary by distro + local driver_svc="nut-driver" + local server_svc="nut-server" + local monitor_svc="nut-monitor" + + # On Arch, the services may be named differently + if ! systemctl list-unit-files "${server_svc}.service" &>/dev/null; then + if systemctl list-unit-files "upsd.service" &>/dev/null; then + server_svc="upsd" + monitor_svc="upsmon" + driver_svc="nut-driver-enumerator" + fi + fi + + # Enable and start + for svc in "$driver_svc" "$server_svc" "$monitor_svc"; do + if systemctl list-unit-files "${svc}.service" &>/dev/null; then + sudo systemctl enable "${svc}.service" 2>/dev/null || true + sudo systemctl restart "${svc}.service" 2>/dev/null || warn "Could not start ${svc}.service — check UPS connection" + fi + done + + ok "NUT services enabled" +} + +main() { + info "=== Vigilar NUT Setup ===" + echo + + install_nut + + UPS_DRIVER="" + UPS_PORT="" + UPS_DESC="" + detect_ups + + generate_configs + enable_services + + echo + ok "=== NUT setup complete ===" + info "Test with: upsc ups@localhost" + info "Vigilar will monitor UPS at 127.0.0.1:3493 (ups name: 'ups')" +} + +main "$@" diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..5c837dc --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Vigilar — Uninstall script +# Stops services, removes venv, systemd units, and mosquitto config. +# Data and config are preserved by default. + +VIGILAR_USER="vigilar" +INSTALL_DIR="/opt/vigilar" +VENV_DIR="${INSTALL_DIR}/venv" +CONFIG_DIR="/etc/vigilar" +DATA_DIR="/var/vigilar" +SYSTEMD_DIR="/etc/systemd/system" +MOSQUITTO_CONF="/etc/mosquitto/conf.d/vigilar.conf" + +info() { printf '\033[1;34m[INFO]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[ OK ]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*"; } + +# --------------------------------------------------------------------------- +# 1. Stop and disable services +# --------------------------------------------------------------------------- + +stop_services() { + info "Stopping and disabling services" + + if systemctl is-active vigilar.service &>/dev/null; then + sudo systemctl stop vigilar.service + fi + if systemctl is-enabled vigilar.service &>/dev/null; then + sudo systemctl disable vigilar.service + fi + + ok "Services stopped" +} + +# --------------------------------------------------------------------------- +# 2. Remove systemd units +# --------------------------------------------------------------------------- + +remove_systemd() { + info "Removing systemd unit" + if [[ -f "${SYSTEMD_DIR}/vigilar.service" ]]; then + sudo rm -f "${SYSTEMD_DIR}/vigilar.service" + sudo systemctl daemon-reload + fi + ok "Systemd unit removed" +} + +# --------------------------------------------------------------------------- +# 3. Remove mosquitto config +# --------------------------------------------------------------------------- + +remove_mosquitto_conf() { + info "Removing Vigilar mosquitto config" + if [[ -f "$MOSQUITTO_CONF" ]]; then + sudo rm -f "$MOSQUITTO_CONF" + sudo systemctl restart mosquitto.service 2>/dev/null || true + fi + ok "Mosquitto config removed" +} + +# --------------------------------------------------------------------------- +# 4. Remove venv and install dir +# --------------------------------------------------------------------------- + +remove_venv() { + info "Removing venv at ${VENV_DIR}" + if [[ -d "$VENV_DIR" ]]; then + sudo rm -rf "$VENV_DIR" + fi + if [[ -d "$INSTALL_DIR" ]]; then + sudo rm -rf "$INSTALL_DIR" + fi + ok "Venv removed" +} + +# --------------------------------------------------------------------------- +# 5. Remove system user +# --------------------------------------------------------------------------- + +remove_user() { + if id "$VIGILAR_USER" &>/dev/null; then + info "Removing system user '${VIGILAR_USER}'" + sudo userdel "$VIGILAR_USER" 2>/dev/null || true + ok "User removed" + fi +} + +# --------------------------------------------------------------------------- +# 6. Optionally remove data +# --------------------------------------------------------------------------- + +remove_data() { + if [[ -d "$DATA_DIR" ]]; then + echo + warn "Data directory exists: ${DATA_DIR}" + warn "This contains recordings, database, and HLS segments." + read -rp "Delete data directory? [y/N] " answer + if [[ "$answer" =~ ^[Yy]$ ]]; then + sudo rm -rf "$DATA_DIR" + ok "Data directory removed" + else + info "Data preserved at ${DATA_DIR}" + fi + fi +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +main() { + info "=== Vigilar Uninstaller ===" + echo + + stop_services + remove_systemd + remove_mosquitto_conf + remove_venv + remove_user + remove_data + + echo + ok "=== Uninstall complete ===" + info "Config and secrets preserved at ${CONFIG_DIR}/" + info "To remove config/secrets too: sudo rm -rf ${CONFIG_DIR}" +} + +main "$@" diff --git a/systemd/vigilar-mosquitto.conf b/systemd/vigilar-mosquitto.conf new file mode 100644 index 0000000..f153f30 --- /dev/null +++ b/systemd/vigilar-mosquitto.conf @@ -0,0 +1,18 @@ +# Mosquitto configuration for Vigilar +# Localhost-only, no authentication, no persistence. +# Drop this file in /etc/mosquitto/conf.d/ + +# Bind to loopback only — no network exposure +listener 1883 127.0.0.1 +# No authentication needed for localhost +allow_anonymous true + +# Disable persistence — Vigilar state lives in SQLite +persistence false + +# Logging +log_dest syslog +log_type error +log_type warning +log_type notice +connection_messages true diff --git a/systemd/vigilar.service b/systemd/vigilar.service new file mode 100644 index 0000000..db04293 --- /dev/null +++ b/systemd/vigilar.service @@ -0,0 +1,53 @@ +[Unit] +Description=Vigilar Home Security System +Documentation=https://github.com/vigilar/vigilar +After=network.target mosquitto.service +Requires=mosquitto.service +Wants=nut-monitor.service + +[Service] +Type=simple +User=vigilar +Group=vigilar + +Environment=VIGILAR_CONFIG=/etc/vigilar/vigilar.toml +ExecStart=/opt/vigilar/venv/bin/vigilar start --config /etc/vigilar/vigilar.toml + +Restart=on-failure +RestartSec=10 +WatchdogSec=120 + +# Security hardening +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +PrivateDevices=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectKernelLogs=yes +ProtectControlGroups=yes +ProtectClock=yes +ProtectHostname=yes +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +LockPersonality=yes +MemoryDenyWriteExecute=no +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@privileged @resources + +# Allow write to data directories +ReadWritePaths=/var/vigilar/data /var/vigilar/recordings /var/vigilar/hls + +# Read-only access to config and secrets +ReadOnlyPaths=/etc/vigilar + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=vigilar + +[Install] +WantedBy=multi-user.target