Add kiosk setup and deployment scripts (Phases 5 + 9)
Phase 5 — RPi Kiosk: - setup_kiosk.sh: full RPi OS Lite setup (X11, Chromium kiosk mode, auto-login, DPMS disabled, GPU memory split, screen rotation) - kiosk.service: systemd unit for reliable auto-start - update_kiosk.sh: reconfigure URL/rotation/resolution without re-setup - Handles both Bullseye and Bookworm RPi OS versions Phase 9 — Hardening + Deployment: - install.sh: full server setup (apt/pacman, vigilar user, venv, directories, permissions, mosquitto config, systemd units) - gen_cert.sh: TLS cert via mkcert or openssl fallback - gen_vapid_keys.sh: VAPID keys for Web Push notifications - setup_nut.sh: NUT configuration with USB UPS auto-detection - backup.sh: SQLite snapshot + config archive, cron-ready - uninstall.sh: clean removal with data preservation option - vigilar.service: hardened systemd unit (ProtectSystem, NoNewPrivileges, PrivateTmp, syscall filtering) - vigilar-mosquitto.conf: localhost-only MQTT broker config All scripts idempotent, bash -n validated, support Debian + Arch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
10b0cf4d0e
commit
ebcc49b474
135
kiosk/README.md
Normal file
135
kiosk/README.md
Normal file
@ -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
|
||||
<https://www.raspberrypi.com/software/>.
|
||||
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@<pi-ip>:~/kiosk/
|
||||
```
|
||||
|
||||
### 3. Run setup
|
||||
|
||||
```bash
|
||||
ssh pi@<pi-ip>
|
||||
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
|
||||
30
kiosk/kiosk.service
Normal file
30
kiosk/kiosk.service
Normal file
@ -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
|
||||
3
kiosk/kiosk_config.txt
Normal file
3
kiosk/kiosk_config.txt
Normal file
@ -0,0 +1,3 @@
|
||||
VIGILAR_URL=https://vigilar.local:49735/kiosk/
|
||||
ROTATION=0
|
||||
RESOLUTION=1920x1080
|
||||
385
kiosk/setup_kiosk.sh
Executable file
385
kiosk/setup_kiosk.sh
Executable file
@ -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}" <<CFGEOF
|
||||
VIGILAR_URL=${VIGILAR_URL}
|
||||
ROTATION=${ROTATION}
|
||||
RESOLUTION=${RESOLUTION}
|
||||
CFGEOF
|
||||
chown "${KIOSK_USER}:${KIOSK_USER}" "${CONFIG_FILE}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Write .xinitrc
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Writing ${KIOSK_HOME}/.xinitrc"
|
||||
cat > "${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}" <<SVCEOF
|
||||
[Unit]
|
||||
Description=Vigilar Kiosk (X11 + Chromium)
|
||||
After=network-online.target systemd-user-sessions.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${KIOSK_USER}
|
||||
Group=${KIOSK_USER}
|
||||
PAMName=login
|
||||
TTYPath=/dev/tty1
|
||||
StandardInput=tty
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SupplementaryGroups=video input render
|
||||
Environment=HOME=${KIOSK_HOME}
|
||||
WorkingDirectory=${KIOSK_HOME}
|
||||
ExecStart=/usr/bin/xinit ${KIOSK_HOME}/.xinitrc -- /usr/bin/X :0 vt1 -keeptty -nocursor
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SVCEOF
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable vigilar-kiosk.service
|
||||
info "systemd service enabled (vigilar-kiosk.service)"
|
||||
|
||||
# Disable .bash_profile auto-start when using systemd (comment it out)
|
||||
sed -i "s/^ exec startx/# exec startx/" "${BASH_PROFILE}"
|
||||
info "Disabled .bash_profile auto-start (systemd is the default)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 9. Configure auto-login on tty1
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Configuring auto-login on tty1 for '${KIOSK_USER}'"
|
||||
AUTOLOGIN_DIR="/etc/systemd/system/getty@tty1.service.d"
|
||||
mkdir -p "${AUTOLOGIN_DIR}"
|
||||
cat > "${AUTOLOGIN_DIR}/autologin.conf" <<ALEOF
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin ${KIOSK_USER} --noclear %I \$TERM
|
||||
ALEOF
|
||||
systemctl daemon-reload
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 10. Disable console blanking
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Disabling console screen blanking"
|
||||
# Kernel cmdline approach — add consoleblank=0
|
||||
CMDLINE="/boot/cmdline.txt"
|
||||
# On Bookworm, firmware cmdline may be at /boot/firmware/cmdline.txt
|
||||
if [[ -f /boot/firmware/cmdline.txt ]]; then
|
||||
CMDLINE="/boot/firmware/cmdline.txt"
|
||||
fi
|
||||
if [[ -f "${CMDLINE}" ]]; then
|
||||
if ! grep -q 'consoleblank=0' "${CMDLINE}"; then
|
||||
sed -i 's/$/ consoleblank=0/' "${CMDLINE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 11. GPU memory split
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Setting GPU memory to 128 MB"
|
||||
CONFIG_TXT="/boot/config.txt"
|
||||
if [[ -f /boot/firmware/config.txt ]]; then
|
||||
CONFIG_TXT="/boot/firmware/config.txt"
|
||||
fi
|
||||
if [[ -f "${CONFIG_TXT}" ]]; then
|
||||
if grep -q '^gpu_mem=' "${CONFIG_TXT}"; then
|
||||
sed -i 's/^gpu_mem=.*/gpu_mem=128/' "${CONFIG_TXT}"
|
||||
else
|
||||
echo "gpu_mem=128" >> "${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"
|
||||
196
kiosk/update_kiosk.sh
Executable file
196
kiosk/update_kiosk.sh
Executable file
@ -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:-<not set>}"
|
||||
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}" <<CFGEOF
|
||||
VIGILAR_URL=${VIGILAR_URL}
|
||||
ROTATION=${ROTATION}
|
||||
RESOLUTION=${RESOLUTION}
|
||||
CFGEOF
|
||||
chown "${KIOSK_USER}:${KIOSK_USER}" "${CONFIG_FILE}"
|
||||
|
||||
echo ""
|
||||
echo " URL: ${VIGILAR_URL}"
|
||||
echo " Rotation: ${ROTATION}"
|
||||
echo " Resolution: ${RESOLUTION}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Update boot config.txt if rotation or resolution changed
|
||||
# ---------------------------------------------------------------------------
|
||||
CONFIG_TXT="/boot/config.txt"
|
||||
if [[ -f /boot/firmware/config.txt ]]; then
|
||||
CONFIG_TXT="/boot/firmware/config.txt"
|
||||
fi
|
||||
|
||||
BOOT_CHANGED=false
|
||||
|
||||
if [[ -f "${CONFIG_TXT}" ]]; then
|
||||
# Rotation
|
||||
if [[ "${ROTATION}" != "${CURRENT_ROTATION}" ]]; 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
|
||||
BOOT_CHANGED=true
|
||||
fi
|
||||
|
||||
# Resolution
|
||||
if [[ "${RESOLUTION}" != "${CURRENT_RESOLUTION}" ]]; then
|
||||
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 ;;
|
||||
*) warn "Unknown resolution ${RESOLUTION}, skipping hdmi_mode" ;;
|
||||
esac
|
||||
if [[ -n "${HDMI_MODE}" ]]; then
|
||||
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
|
||||
BOOT_CHANGED=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Restart service if requested
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "${DO_RESTART}" == true ]]; then
|
||||
info "Restarting vigilar-kiosk service"
|
||||
systemctl restart vigilar-kiosk.service
|
||||
echo "Service restarted."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Done
|
||||
# ---------------------------------------------------------------------------
|
||||
info "Configuration updated!"
|
||||
if [[ "${BOOT_CHANGED}" == true ]]; then
|
||||
echo ""
|
||||
echo " Boot config was changed. A reboot is required for display changes."
|
||||
echo " Run: sudo reboot"
|
||||
elif [[ "${DO_RESTART}" != true ]]; then
|
||||
echo ""
|
||||
echo " To apply URL changes: sudo systemctl restart vigilar-kiosk"
|
||||
echo " For display changes: sudo reboot"
|
||||
fi
|
||||
93
scripts/backup.sh
Executable file
93
scripts/backup.sh
Executable file
@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Vigilar — Backup script
|
||||
# Backs up SQLite database, config, and secrets to a dated tar.gz archive.
|
||||
# Suitable for cron: 0 3 * * * /opt/vigilar/scripts/backup.sh
|
||||
|
||||
DATA_DIR="/var/vigilar/data"
|
||||
CONFIG_DIR="/etc/vigilar"
|
||||
BACKUP_DEST="${VIGILAR_BACKUP_DIR:-/var/vigilar/backups}"
|
||||
RETENTION_DAYS="${VIGILAR_BACKUP_RETENTION_DAYS:-30}"
|
||||
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
ARCHIVE_NAME="vigilar-backup-${TIMESTAMP}.tar.gz"
|
||||
|
||||
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; }
|
||||
|
||||
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 "$@"
|
||||
126
scripts/gen_cert.sh
Executable file
126
scripts/gen_cert.sh
Executable file
@ -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 "$@"
|
||||
100
scripts/gen_vapid_keys.sh
Executable file
100
scripts/gen_vapid_keys.sh
Executable file
@ -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 "$@"
|
||||
232
scripts/install.sh
Executable file
232
scripts/install.sh
Executable file
@ -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 "$@"
|
||||
203
scripts/setup_nut.sh
Executable file
203
scripts/setup_nut.sh
Executable file
@ -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 <<UPSCONF
|
||||
# Vigilar UPS configuration
|
||||
# Generated by setup_nut.sh
|
||||
|
||||
[ups]
|
||||
driver = ${UPS_DRIVER}
|
||||
port = ${UPS_PORT}
|
||||
desc = "${UPS_DESC}"
|
||||
pollinterval = 15
|
||||
UPSCONF
|
||||
|
||||
# /etc/nut/upsd.conf — daemon config (localhost only)
|
||||
sudo tee /etc/nut/upsd.conf > /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 "$@"
|
||||
130
scripts/uninstall.sh
Executable file
130
scripts/uninstall.sh
Executable file
@ -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 "$@"
|
||||
18
systemd/vigilar-mosquitto.conf
Normal file
18
systemd/vigilar-mosquitto.conf
Normal file
@ -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
|
||||
53
systemd/vigilar.service
Normal file
53
systemd/vigilar.service
Normal file
@ -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
|
||||
Loading…
Reference in New Issue
Block a user