#!/usr/bin/env bash
# BubbleCam installer.
#
# Run this ON the target Raspberry Pi (Raspberry Pi OS Lite, 64-bit,
# Bookworm), with internet access, during initial image setup -- NOT in
# the field. It installs dependencies, copies this repo's files into
# place, enables the systemd services, and applies the power-management
# settings. Re-running it is safe (each step checks before acting) except
# where noted.
#
# What this script does NOT do for you:
#   - Confirm bubblecam/hardware/epaper.py's EPD_DRIVER_MODULE matches your
#     e-Paper HAT's hardware revision (check the sticker on the HAT's PCB).
#   - Determine bubblecam.toml's [camera].mode -- probe the real camera
#     with `v4l2-ctl --list-formats-ext` first and set that before running
#     this script (see README.md).

set -euo pipefail

if [[ "$(id -u)" -ne 0 ]]; then
  echo "Run as root (sudo $0)" >&2
  exit 1
fi

REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BOOT_DIR="/boot/firmware"
[[ -d "$BOOT_DIR" ]] || BOOT_DIR="/boot"  # older Raspberry Pi OS layout fallback

echo "=== 1. Installing apt dependencies ==="
apt-get update
apt-get install -y --no-install-recommends \
  ffmpeg v4l-utils i2c-tools python3-pip python3-dev python3-pil \
  python3-gpiozero python3-spidev python3-lgpio git \
  fonts-dejavu-core network-manager rfkill util-linux-extra

echo "=== 2. Installing Python dependencies ==="
# Bookworm's system Python is "externally managed" (PEP 668); these are
# small, well-contained packages with no system-package conflicts, so
# --break-system-packages is the pragmatic choice here over a venv (a venv
# would complicate systemd ExecStart paths for no real benefit on a
# single-purpose appliance image).
# NB: the package is RPi.bme280 (provides load_calibration_params/sample),
# NOT the unrelated `bme280` package, which has a different API.
pip3 install --break-system-packages --quiet \
  rpi_ws281x smbus2 RPi.bme280 astral

# Waveshare's e-Paper driver (waveshare_epd) is only distributed via their
# git repo, not PyPI. gpiozero/spidev/lgpio (its runtime deps on Bookworm+)
# come from apt above. Clone shallow to disk-backed /var/tmp ourselves --
# handing pip a git+https URL makes it clone to /tmp, which is RAM-backed
# tmpfs (~256MB on a Zero 2 W) and overflows on this repo's hundreds of MB
# of unrelated MCU examples.
if python3 -c "import waveshare_epd" >/dev/null 2>&1; then
  echo "  waveshare_epd already installed -- skipping."
else
  ws_tmp=$(mktemp -d /var/tmp/waveshare-epaper.XXXXXX)
  git clone --depth 1 https://github.com/waveshare/e-Paper "$ws_tmp/e-Paper"
  pip3 install --break-system-packages --quiet "$ws_tmp/e-Paper/RaspberryPi_JetsonNano/python"
  rm -rf "$ws_tmp"
fi

echo "=== 3. Copying application files ==="
mkdir -p /opt/bubblecam
cp -r "$REPO_DIR/opt/bubblecam/." /opt/bubblecam/
chmod +x /opt/bubblecam/bin/*.py

mkdir -p /data/video /data/sensors /data/bubblecam
# Deliberately no chown: /data is exFAT (so the card can be read on Windows
# -- see README "SD card partitioning"), and exFAT has no on-disk ownership.
# It comes from the uid=/gid= mount options in /etc/fstab, and chown there
# fails with EPERM, which under `set -e` aborts this installer.

echo "=== 4. Installing systemd units ==="
cp "$REPO_DIR"/etc/systemd/system/bubblecam-*.service /etc/systemd/system/
cp "$REPO_DIR/etc/tmpfiles.d/bubblecam.conf" /etc/tmpfiles.d/bubblecam.conf
systemd-tmpfiles --create /etc/tmpfiles.d/bubblecam.conf

echo "=== 5. Installing bubblecam-arm ==="
cp "$REPO_DIR/usr/local/sbin/bubblecam-arm" /usr/local/sbin/bubblecam-arm
chmod +x /usr/local/sbin/bubblecam-arm

echo "=== 6. Installing config (won't overwrite an existing hand-edited copy) ==="
if [[ -f "$BOOT_DIR/bubblecam.toml" ]]; then
  echo "  $BOOT_DIR/bubblecam.toml already exists -- leaving it alone."
  echo "  (compare against $REPO_DIR/boot/firmware/bubblecam.toml if you want to see what's new)"
else
  cp "$REPO_DIR/boot/firmware/bubblecam.toml" "$BOOT_DIR/bubblecam.toml"
fi

echo "=== 7. Applying config.txt overlays (idempotent) ==="
if grep -q "BubbleCam BEGIN" "$BOOT_DIR/config.txt" 2>/dev/null; then
  echo "  config.txt already has the BubbleCam block -- skipping."
else
  cat "$REPO_DIR/boot/firmware/config.txt.bubblecam-append" >> "$BOOT_DIR/config.txt"
  echo "  Appended. A reboot is required for these to take effect."
fi

echo "=== 7b. Loading i2c-dev module (provides /dev/i2c-1 for BME280/RTC) ==="
# dtparam=i2c_arm=on enables the bus hardware, but the /dev/i2c-* device
# nodes that smbus2/i2cdetect use come from the i2c-dev kernel module.
if ! grep -q '^i2c-dev' /etc/modules; then
  echo i2c-dev >> /etc/modules
fi
modprobe i2c-dev || true

echo "=== 8. Power-management: disabling unneeded services ==="
systemctl disable --now bluetooth.service hciuart.service triggerhappy.service 2>/dev/null || true
systemctl mask bluetooth.service 2>/dev/null || true

if systemctl list-unit-files | grep -q '^fake-hwclock'; then
  systemctl disable --now fake-hwclock.service 2>/dev/null || true
fi

if command -v dphys-swapfile >/dev/null 2>&1; then
  dphys-swapfile swapoff 2>/dev/null || true
  systemctl disable dphys-swapfile 2>/dev/null || true
fi

echo "=== 8b. Quieting background SD-card writers ==="
# Trixie's zram swap periodically writes compressed pages back to the SD
# card (rpi-zram-writeback). The journal's final entry before one overnight
# SD wedge was exactly this service starting -- and random background
# writes contending with sustained video capture are unwanted regardless.
# Swap stays in RAM (zram itself untouched); only the SD writeback goes.
systemctl disable --now rpi-zram-writeback.timer 2>/dev/null || true
systemctl mask rpi-zram-writeback.service 2>/dev/null || true
# Desktop housekeeping timers have no business on a sealed appliance and
# each one is surprise SD I/O: apt updates, man-db rebuilds, dpkg backups.
systemctl disable --now apt-daily.timer apt-daily-upgrade.timer \
  man-db.timer dpkg-db-backup.timer e2scrub_all.timer 2>/dev/null || true

echo "=== 8b. Wi-Fi: disabling power save ==="
# The BCM43430's power save makes wlan0 drop its association and then fail
# to find the SSID again (observed: repeated "locally_generated=1"
# disconnects followed by 'ssid-not-found'), which strands the Pi
# unreachable during pre-deployment checkout. NetworkManager's
# wifi.powersave=2 means "disable".
mkdir -p /etc/NetworkManager/conf.d
cat > /etc/NetworkManager/conf.d/bubblecam-wifi-powersave.conf <<'EOF'
[connection]
wifi.powersave = 2
EOF

echo "=== 8c. Enabling sysrq (needed for the watchdog's last-resort reset) ==="
# bin/watchdog.py falls back to `echo b > /proc/sysrq-trigger` when a
# graceful reboot stalls -- which is what happens when systemd blocks
# unmounting a hung /data. That write is ignored unless sysrq is enabled.
cat > /etc/sysctl.d/98-bubblecam-sysrq.conf <<'EOF'
kernel.sysrq=1
EOF
sysctl -p /etc/sysctl.d/98-bubblecam-sysrq.conf >/dev/null 2>&1 || true

echo "=== 9. Enabling the hardware watchdog (catches full hangs the software watchdog can't) ==="
if ! grep -q "^RuntimeWatchdogSec=" /etc/systemd/system.conf; then
  echo "RuntimeWatchdogSec=15s" >> /etc/systemd/system.conf
  echo "  Added RuntimeWatchdogSec=15s to /etc/systemd/system.conf."
else
  echo "  RuntimeWatchdogSec already set -- leaving it alone."
fi
# RebootWatchdogSec arms the hardware watchdog during shutdown/reboot: if
# a reboot hangs (e.g. blocked unmounting a filesystem whose SD card has
# died), the hardware resets the board after this long instead of hanging
# forever. Complements the software watchdog's exec-free sysrq path.
if ! grep -q "^RebootWatchdogSec=" /etc/systemd/system.conf; then
  echo "RebootWatchdogSec=2min" >> /etc/systemd/system.conf
  echo "  Added RebootWatchdogSec=2min to /etc/systemd/system.conf."
else
  echo "  RebootWatchdogSec already set -- leaving it alone."
fi

echo "=== 10. Journald: persistent but capped ==="
mkdir -p /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/bubblecam.conf <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=50M
EOF

echo "=== 11. Enabling BubbleCam services ==="
systemctl daemon-reload

camera_mode=$(python3 -c "
import sys
sys.path.insert(0, '/opt/bubblecam')
import os
os.environ.setdefault('BUBBLECAM_CONFIG', '$BOOT_DIR/bubblecam.toml')
from bubblecam import config
print(config.load()['camera']['mode'])
")
echo "  camera.mode = $camera_mode"

if [[ "$camera_mode" == dual_node_* ]]; then
  camera_units=(bubblecam-camera@left.service bubblecam-camera@right.service)
else
  camera_units=(bubblecam-camera@primary.service)
fi

# Camera units stay enabled at boot (fail-safe: a Pi that reboots
# mid-mission resumes recording immediately); bubblecam-record-switch then
# stops them within a poll or two if the physical switch says stop.
systemctl enable --now \
  bubblecam-rtc-sync.service \
  bubblecam-led.service \
  bubblecam-sensors.service \
  bubblecam-lumen.service \
  bubblecam-epaper.service \
  bubblecam-watchdog.service \
  bubblecam-record-switch.service \
  "${camera_units[@]}"

echo
echo "=== Done ==="
echo "Reboot to apply config.txt changes: sudo reboot"
echo "After reboot, check status with: systemctl status 'bubblecam-*'"
