#!/usr/bin/env bash # RemoteRig — First-Time Raspberry Pi Zero 2 W Setup # Target: Debian/Raspberry Pi OS (bookworm) # Idempotent: safe to run multiple times # # Usage: # sudo ./setup-pi.sh [--config PATH] [--service-user USER] # # Options: # --config PATH Path to config.yaml template to copy to /opt/remoterig/ # --service-user USER Systemd service user (default: invoking sudo user, else pi) # --static-ip IP Static IP for wlan0 (default: 192.168.8.56/24) # --gateway IP Gateway for wlan0 (default: 192.168.8.1) # --help Show this help set -euo pipefail # --------------------------------------------------------------------------- # Defaults # --------------------------------------------------------------------------- CONFIG_TEMPLATE="" SERVICE_USER="${SUDO_USER:-pi}" # default to the invoking user (not every Pi has a 'pi' user) STATIC_IP="192.168.8.56/24" GATEWAY="192.168.8.1" MOSQUITTO_PKG="mosquitto mosquitto-clients" DEPLOY_DIR="/opt/remoterig" SERVICE_NAME="remoterig" SERVICE_FILE="scripts/remoterig.service" MOSQUITTO_CONF="/etc/mosquitto/conf.d/remoterig.conf" # --------------------------------------------------------------------------- # Help # --------------------------------------------------------------------------- usage() { sed -n '/^# Usage:/,/^$/p' "$0" | sed 's/^# //' exit 0 } # --------------------------------------------------------------------------- # Parse args # --------------------------------------------------------------------------- while [ $# -gt 0 ]; do case "$1" in --config) CONFIG_TEMPLATE="$2" shift 2 ;; --service-user) SERVICE_USER="$2" shift 2 ;; --static-ip) STATIC_IP="$2" shift 2 ;; --gateway) GATEWAY="$2" shift 2 ;; --help|-h) usage ;; *) echo "ERROR: unknown option: $1" >&2 usage ;; esac done # --------------------------------------------------------------------------- # Pre-flight checks # --------------------------------------------------------------------------- if [ "$(id -u)" -ne 0 ]; then echo "ERROR: must run as root (sudo ./setup-pi.sh)" >&2 exit 1 fi info() { echo "[INFO] $*"; } ok() { echo "[OK] $*"; } skip() { echo "[SKIP] $*"; } warn() { echo "[WARN] $*" >&2; } echo "==============================================" echo " RemoteRig Pi Zero 2 W Setup" echo " Target: ${STATIC_IP} via ${GATEWAY}" echo " Service user: ${SERVICE_USER}" echo "==============================================" # --------------------------------------------------------------------------- # 1. Update package list (always safe) # --------------------------------------------------------------------------- info "Updating package list..." apt-get update -qq # --------------------------------------------------------------------------- # 2. Install Mosquitto MQTT broker + clients # --------------------------------------------------------------------------- info "Installing Mosquitto..." if dpkg -l mosquitto mosquitto-clients >/dev/null 2>&1; then # Already installed — ensure latest apt-get install -y -qq ${MOSQUITTO_PKG} 2>/dev/null && \ ok "Mosquitto packages up to date" || \ warn "Mosquitto package update had warnings (non-fatal)" else apt-get install -y -qq ${MOSQUITTO_PKG} ok "Mosquitto installed" fi # --------------------------------------------------------------------------- # 3. Configure Mosquitto — anonymous on localhost, listener on 0.0.0.0:1883 # --------------------------------------------------------------------------- info "Configuring Mosquitto..." mkdir -p /etc/mosquitto/conf.d # Write idempotent config cat > "${MOSQUITTO_CONF}" <<'MQTTEOF' # RemoteRig Mosquitto configuration # Closed travel-router LAN — anonymous access is intentional # Listen on all interfaces (LAN + localhost) listener 1883 0.0.0.0 # No authentication (closed network, no internet access) allow_anonymous true MQTTEOF ok "Mosquitto config written: ${MOSQUITTO_CONF}" # --------------------------------------------------------------------------- # 4. Create /opt/remoterig directory # --------------------------------------------------------------------------- info "Creating deploy directory..." if [ -d "${DEPLOY_DIR}" ]; then skip "${DEPLOY_DIR} already exists" else mkdir -p "${DEPLOY_DIR}" ok "Created ${DEPLOY_DIR}" fi # --------------------------------------------------------------------------- # 5. Copy config.yaml template (if provided) # --------------------------------------------------------------------------- if [ -n "${CONFIG_TEMPLATE}" ] && [ -f "${CONFIG_TEMPLATE}" ]; then info "Copying config.yaml template..." if [ -f "${DEPLOY_DIR}/config.yaml" ]; then skip "${DEPLOY_DIR}/config.yaml already exists (not overwriting)" else cp "${CONFIG_TEMPLATE}" "${DEPLOY_DIR}/config.yaml" ok "Copied config.yaml to ${DEPLOY_DIR}/config.yaml" fi elif [ -n "${CONFIG_TEMPLATE}" ]; then warn "Config template '${CONFIG_TEMPLATE}' not found — skipping" else info "No config template provided — skipping" fi # Ensure service user owns the deploy directory chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DEPLOY_DIR}" 2>/dev/null || \ warn "Could not chown ${DEPLOY_DIR} to ${SERVICE_USER} (user may not exist yet)" # --------------------------------------------------------------------------- # 6. Install and enable systemd service # --------------------------------------------------------------------------- info "Installing systemd service..." # Locate the service file relative to this script's directory SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SRC_SERVICE="${SCRIPT_DIR}/remoterig.service" if [ ! -f "${SRC_SERVICE}" ]; then warn "Service file not found at ${SRC_SERVICE} — skipping service install" warn "Run this script from the repository root (scripts/setup-pi.sh)" else DST_SERVICE="/etc/systemd/system/${SERVICE_NAME}.service" # Copy if different if [ -f "${DST_SERVICE}" ]; then if cmp -s "${SRC_SERVICE}" "${DST_SERVICE}"; then skip "systemd service already installed and up to date" else cp "${SRC_SERVICE}" "${DST_SERVICE}" ok "systemd service updated" RELOAD_SYSTEMD=1 fi else cp "${SRC_SERVICE}" "${DST_SERVICE}" ok "systemd service installed" RELOAD_SYSTEMD=1 fi # Substitute the service user sed -i "s/^User=.*/User=${SERVICE_USER}/" "${DST_SERVICE}" if [ "${RELOAD_SYSTEMD:-0}" -eq 1 ]; then systemctl daemon-reload fi # Enable (idempotent) if systemctl is-enabled --quiet "${SERVICE_NAME}" 2>/dev/null; then skip "systemd service already enabled" else systemctl enable "${SERVICE_NAME}" ok "systemd service enabled" fi fi # --------------------------------------------------------------------------- # 6b. Install pull updater (Pi polls the Gitea dev release and self-updates) # --------------------------------------------------------------------------- info "Installing pull updater..." # deploy.sh + pi-update.sh live in the deploy dir (the updater calls them) for f in deploy.sh pi-update.sh; do if [ -f "${SCRIPT_DIR}/${f}" ]; then cp "${SCRIPT_DIR}/${f}" "${DEPLOY_DIR}/${f}" chmod +x "${DEPLOY_DIR}/${f}" ok "Installed ${DEPLOY_DIR}/${f}" else warn "${SCRIPT_DIR}/${f} not found — skipping" fi done # update.env template (don't clobber an existing one that may hold a token) UPDATE_ENV="${DEPLOY_DIR}/update.env" if [ -f "${UPDATE_ENV}" ]; then skip "${UPDATE_ENV} already exists (not overwriting)" else cat > "${UPDATE_ENV}" <<'ENVEOF' # RemoteRig updater config GITEA_BASE=https://code.cubecraftcreations.com REPO=CubeCraft-Creations/remote-rig # Read token — required only if the repo is private: GITEA_TOKEN= ENVEOF chmod 600 "${UPDATE_ENV}" ok "Wrote ${UPDATE_ENV} (set GITEA_TOKEN if the repo is private)" fi # Updater service + timer for unit in remoterig-update.service remoterig-update.timer; do if [ -f "${SCRIPT_DIR}/${unit}" ]; then cp "${SCRIPT_DIR}/${unit}" "/etc/systemd/system/${unit}" ok "Installed ${unit}" else warn "${SCRIPT_DIR}/${unit} not found — skipping" fi done systemctl daemon-reload if systemctl enable --now remoterig-update.timer 2>/dev/null; then ok "remoterig-update.timer enabled and started" else warn "Could not enable remoterig-update.timer" fi # --------------------------------------------------------------------------- # 7. Set static IP on wlan0 # --------------------------------------------------------------------------- info "Configuring static IP on wlan0..." # Check if wlan0 exists if ! ip link show wlan0 >/dev/null 2>&1; then warn "wlan0 interface not found — skipping static IP configuration" warn "Connect Wi-Fi first (raspi-config), then re-run this script" else STATIC_IP_SET=0 # --- Method A: NetworkManager (default on bookworm) --- if command -v nmcli >/dev/null 2>&1; then info "Using NetworkManager (nmcli)..." # Find the Wi-Fi connection profile WIFI_CON=$(nmcli -t -f NAME,TYPE con show 2>/dev/null | grep ':802-11-wireless' | cut -d: -f1 | head -1) if [ -n "${WIFI_CON}" ]; then CURRENT_IP=$(nmcli -t -f IP4.ADDRESS con show "${WIFI_CON}" 2>/dev/null | cut -d: -f2 | head -1 || true) if [ "${CURRENT_IP}" = "${STATIC_IP}" ]; then skip "wlan0 already set to ${STATIC_IP} via nmcli" STATIC_IP_SET=1 else nmcli con mod "${WIFI_CON}" ipv4.addresses "${STATIC_IP}" nmcli con mod "${WIFI_CON}" ipv4.gateway "${GATEWAY}" nmcli con mod "${WIFI_CON}" ipv4.dns "${GATEWAY}" nmcli con mod "${WIFI_CON}" ipv4.method manual nmcli con up "${WIFI_CON}" 2>/dev/null || true ok "wlan0 set to ${STATIC_IP} via nmcli (connection: ${WIFI_CON})" STATIC_IP_SET=1 fi else warn "No Wi-Fi connection profile found in NetworkManager" fi fi # --- Method B: dhcpcd (fallback for older PiOS) --- if [ ${STATIC_IP_SET} -eq 0 ] && command -v dhcpcd >/dev/null 2>&1; then info "Using dhcpcd..." DHCPCD_CONF="/etc/dhcpcd.conf" if grep -q "interface wlan0" "${DHCPCD_CONF}" 2>/dev/null; then skip "dhcpcd already has wlan0 config" else cat >> "${DHCPCD_CONF}" </dev/null; then cat >> "${INTERFACES_FILE}" </dev/null; then skip "Mosquitto already running" else systemctl enable mosquitto 2>/dev/null || true systemctl restart mosquitto ok "Mosquitto started" fi # Verify Mosquitto is listening sleep 1 if systemctl is-active --quiet mosquitto 2>/dev/null; then ok "Mosquitto is running and listening on :1883" else warn "Mosquitto may not have started — check: sudo systemctl status mosquitto" fi # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo "" echo "==============================================" echo " Setup complete!" echo "==============================================" echo " Mosquitto: $(systemctl is-active mosquitto 2>/dev/null || echo 'unknown')" echo " Service: ${SERVICE_NAME} (systemctl status ${SERVICE_NAME})" echo " Updater: remoterig-update.timer (systemctl status remoterig-update.timer)" echo " Deploy dir: ${DEPLOY_DIR}" echo " Static IP: ${STATIC_IP} on wlan0" echo "" echo " Deploys are pull-based: push to 'dev' on Gitea -> CI builds the" echo " arm64 binary -> the Pi's timer pulls + installs it automatically." echo "" echo " Next steps:" echo " 1. If the repo is private, set a read token:" echo " sudo sed -i 's/^GITEA_TOKEN=.*/GITEA_TOKEN=/' ${DEPLOY_DIR}/update.env" echo " 2. Trigger / wait for an update check:" echo " sudo systemctl start remoterig-update.service" echo " journalctl -u remoterig-update.service -n 30" echo " 3. Check health once deployed:" echo " curl http://${STATIC_IP%/*}:8080/health" echo "" echo " Manual one-off deploy (local binary) still works: scripts/deploy.sh" echo "=============================================="