The clicker had no Linux support at all: pygetwindow has no X11 backend, so _other_activate returned "window management is unsupported" and every step failed before it could click. focus.py now has a third backend that raises the browser with xdotool and reads the focused window's WM_CLASS to verify it came forward - the same activate-then-confirm shape as the macOS and Windows paths. setup-linux.sh mirrors setup-windows.bat with apt instead of winget. Three things are specific to this platform rather than incidental: - Node comes from NodeSource. Pi OS ships one too old for Next 16, and the LTS line is also what better-sqlite3 publishes prebuilt arm64 binaries for. - Python dependencies go in a virtualenv. Pi OS Bookworm enforces PEP 668, so pip into the system interpreter fails with externally-managed-environment. - Autostart uses an XDG ~/.config/autostart entry, the direct analogue of the Windows Startup folder: no sudo, and it runs inside the graphical session, which the clicker needs for DISPLAY. Wayland is called out in four places - the session guard, diagnose.py, the installer and both READMEs - because Pi OS on a Pi 5 defaults to it and the clicker simply cannot work there. Wayland does not let one client synthesise input into another, so this is a switch-to-X11 situation, not a bug to fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
171 lines
8.3 KiB
Bash
Executable File
171 lines
8.3 KiB
Bash
Executable File
#!/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
|