#!/usr/bin/env bash # # AutoFirmer instance setup for Raspberry Pi OS / Debian / Ubuntu. # # Standalone: download this one file and run it. It clones the repo into a # subfolder next to itself, installs, builds, and points the instance at the # master dashboard. Safe to re-run - it pulls and rebuilds instead of cloning. # # curl -fsSLO https://git.juicerroom.com/senofy/autofirmer-expanded/raw/branch/master/setup-linux.sh # bash setup-linux.sh set -euo pipefail REPO_URL="https://git.juicerroom.com/senofy/autofirmer-expanded.git" DEFAULT_MASTER="https://master.juicerroom.com" HERE="$(cd "$(dirname "$0")" && pwd)" TARGET="$HERE/autofirmer" NODE_MAJOR_MIN=20 ok() { printf ' [ok] %s\n' "$1"; } info() { printf ' %s\n' "$1"; } warn() { printf ' [!] %s\n' "$1" >&2; } die() { printf '\n [X] %s\n\n' "$1" >&2; exit 1; } echo echo " ============================================" echo " AutoFirmer - Linux setup" echo " ============================================" echo # ── sudo ──────────────────────────────────────────────────────────────────── command -v apt-get >/dev/null 2>&1 || die "this script expects apt (Raspberry Pi OS, Debian, Ubuntu)" SUDO="" if [ "$(id -u)" -ne 0 ]; then command -v sudo >/dev/null 2>&1 || die "not root and sudo is not installed" SUDO="sudo" info "some steps need sudo; you may be prompted" fi # ── system packages ───────────────────────────────────────────────────────── # xdotool raises the browser window (pygetwindow has no X11 backend, so the # clicker drives xdotool instead). scrot backs pyautogui's screen reads. # build-essential/python3-dev are only the fallback path for better-sqlite3 if # no arm64 prebuilt binary matches this Node ABI. info "installing system packages..." $SUDO apt-get update -qq $SUDO apt-get install -y -qq \ git curl ca-certificates \ python3 python3-venv python3-dev \ xdotool scrot \ build-essential ok "system packages" # ── node ──────────────────────────────────────────────────────────────────── node_major() { command -v node >/dev/null 2>&1 && node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; } if [ "$(node_major)" -lt "$NODE_MAJOR_MIN" ]; then # Raspberry Pi OS ships a Node too old for Next 16, so take the LTS line # from NodeSource. LTS also matters for better-sqlite3, which publishes # prebuilt arm64 binaries only for released ABIs. info "installing Node 22 LTS (found major $(node_major))..." curl -fsSL https://deb.nodesource.com/setup_22.x | $SUDO -E bash - >/dev/null $SUDO apt-get install -y -qq nodejs fi [ "$(node_major)" -ge "$NODE_MAJOR_MIN" ] || die "Node $NODE_MAJOR_MIN+ required, found $(node -v 2>/dev/null || echo none)" ok "node $(node -v) npm $(npm -v)" ok "python $(python3 -V 2>&1 | awk '{print $2}')" # ── memory ────────────────────────────────────────────────────────────────── TOTAL_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo) SWAP_MB=$(awk '/SwapTotal/ {print int($2/1024)}' /proc/meminfo) if [ "$TOTAL_MB" -lt 3500 ] && [ "$SWAP_MB" -lt 1024 ]; then warn "${TOTAL_MB}MB RAM and only ${SWAP_MB}MB swap - 'next build' may be OOM-killed." warn "Consider raising CONF_SWAPSIZE in /etc/dphys-swapfile to 2048 and rebooting." fi # ── prompts ───────────────────────────────────────────────────────────────── echo read -r -p " Master dashboard URL [$DEFAULT_MASTER]: " MASTER_URL MASTER_URL="${MASTER_URL:-$DEFAULT_MASTER}" read -r -p " Instance name [$(hostname)]: " INSTANCE INSTANCE="${INSTANCE:-$(hostname)}" echo info "installing to: $TARGET" echo # ── 1. clone / pull ───────────────────────────────────────────────────────── # The branch is named explicitly: a bare clone follows the remote's default # branch, which is not necessarily master. if [ -d "$TARGET/.git" ]; then info "[1/6] existing checkout - switching to master and pulling..." git -C "$TARGET" fetch origin git -C "$TARGET" checkout master git -C "$TARGET" pull --ff-only origin master else info "[1/6] cloning $REPO_URL ..." git clone --branch master "$REPO_URL" "$TARGET" fi cd "$TARGET" # ── 2. npm ────────────────────────────────────────────────────────────────── info "[2/6] installing npm dependencies (a few minutes on a Pi)..." npm install # ── 3. build ──────────────────────────────────────────────────────────────── info "[3/6] building..." npm run build # ── 4. settings ───────────────────────────────────────────────────────────── info "[4/6] writing instance settings..." node scripts/seed-settings.js "$MASTER_URL" "$INSTANCE" # ── 5. python ─────────────────────────────────────────────────────────────── # A virtualenv rather than a system pip install: Raspberry Pi OS Bookworm # enforces PEP 668, so pip into the system interpreter fails outright with # "externally-managed-environment". info "[5/6] installing clicker dependencies into a virtualenv..." VENV="$TARGET/clicker/.venv" [ -d "$VENV" ] || python3 -m venv "$VENV" "$VENV/bin/python" -m pip install --quiet --upgrade pip if "$VENV/bin/python" -m pip install --quiet -r clicker/requirements.txt; then echo "$VENV/bin/python" > scripts/.python-cmd ok "clicker dependencies ready" else warn "pip install failed - the dashboard still works, the clicker will not" fi # ── 6. autostart ──────────────────────────────────────────────────────────── echo info "[6/6] Auto-start and auto-update" info " Starts AutoFirmer and the clicker at login, and checks master every" info " 5 minutes - rebuilding and restarting when it moves. A failed build" info " is never deployed." read -r -p " Set this up now? [Y/n] " DOAUTO AUTOSTART=0 if [ "${DOAUTO,,}" != "n" ]; then if bash scripts/install-autostart.sh; then AUTOSTART=1; else warn "auto-start setup failed - start by hand with: node scripts/start-all.mjs" fi else info " Skipped. Run scripts/install-autostart.sh later to enable it." fi # ── summary ───────────────────────────────────────────────────────────────── echo echo " ============================================" echo " Done." echo " ============================================" echo info "Instance name : $INSTANCE" info "Reporting to : $MASTER_URL" info "Installed in : $TARGET" info "Dashboard at : http://localhost:3000" echo if [ "$AUTOSTART" -eq 1 ]; then info "Running now, and again at every login (PM2)." info "Handy: pm2 list | pm2 logs autofirmer | pm2 logs clicker" else info "Start it with : node scripts/start-all.mjs" fi echo info "Still manual:" info " - Load the Chrome extension: chrome://extensions -> Developer mode" info " -> Load unpacked -> $TARGET/extension" info " - Add your firm credentials on the dashboard's Settings page" if [ -n "${WAYLAND_DISPLAY:-}" ] && [ -z "${DISPLAY:-}" ]; then echo warn "This is a Wayland session - the clicker needs X11." warn "sudo raspi-config -> Advanced Options -> Wayland -> X11, then reboot." fi echo