Both installers ended by telling you to load the folder unpacked, which is the one install that can never update itself. They now offer the policy install as a last step instead, and only fall back to those instructions when it is declined or fails. Windows writes to the user's own policy hive rather than HKLM, so the step needs no elevation and no separate admin shell; -Scope Machine is still there for every-account installs. Linux gets the counterpart script — a managed policy file under /etc, which is root-only because Chrome reads policy from nowhere else on that platform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
195 lines
9.5 KiB
Bash
Executable File
195 lines
9.5 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/7] 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/7] cloning $REPO_URL ..."
|
|
git clone --branch master "$REPO_URL" "$TARGET"
|
|
fi
|
|
|
|
cd "$TARGET"
|
|
|
|
# ── 2. npm ──────────────────────────────────────────────────────────────────
|
|
info "[2/7] installing npm dependencies (a few minutes on a Pi)..."
|
|
npm install
|
|
|
|
# ── 3. build ────────────────────────────────────────────────────────────────
|
|
info "[3/7] building..."
|
|
npm run build
|
|
|
|
# ── 4. settings ─────────────────────────────────────────────────────────────
|
|
info "[4/7] 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/7] 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/7] 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
|
|
|
|
# ── 7. extension ────────────────────────────────────────────────────────────
|
|
# Chrome refuses to side-load a CRX and will not update an unpacked folder, so
|
|
# the extension goes in the way a self-hosted one has to: a managed-policy file
|
|
# naming our update server. That lives under /etc, hence sudo — there is no
|
|
# per-user equivalent on Linux.
|
|
echo
|
|
info "[7/7] Chrome extension"
|
|
info " Installs it from the update server and lets Chrome keep it current"
|
|
info " by itself. Needs sudo - the policy file lives under /etc."
|
|
read -r -p " Set this up now? [Y/n] " DOEXT
|
|
EXTPOLICY=0
|
|
if [ "${DOEXT,,}" != "n" ]; then
|
|
if sudo bash scripts/install-extension-policy.sh; then EXTPOLICY=1; else
|
|
warn "extension policy failed - load $TARGET/extension unpacked for now"
|
|
fi
|
|
else
|
|
info " Skipped. Run: sudo scripts/install-extension-policy.sh"
|
|
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:"
|
|
if [ "$EXTPOLICY" -eq 1 ]; then
|
|
info " - Restart Chrome, and remove any unpacked copy of the extension"
|
|
info " (it shares the same id now, and Chrome will not load both)"
|
|
else
|
|
info " - Load the Chrome extension: chrome://extensions -> Developer mode"
|
|
info " -> Load unpacked -> $TARGET/extension"
|
|
fi
|
|
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
|