The managed-device requirement is documented for Windows and macOS only, and a Pi proved it: aarch64 Chromium fetched updates.xml with installedby=policy and installed the CRX from a plain /etc policy file, with no enrolment, no Google account and no Chrome. So setup-linux.sh writes the policy directly for whichever browsers are present, and enrolment is a Windows concern again. That also retires the ARM special case. Chromium could not be enrolled, which I read as Chromium not being able to install this at all; the two are unrelated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
201 lines
9.9 KiB
Bash
Executable File
201 lines
9.9 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 will not update an unpacked folder, and only force-installs an off-store
|
|
# extension on a managed browser. Enrolling in Chrome Enterprise Core is what
|
|
# makes this machine's Chrome managed; the extension then arrives from the Admin
|
|
# console already set to force install. The token file lives under /etc, hence
|
|
# sudo.
|
|
echo
|
|
# A policy file and nothing else. The managed-device requirement that makes this
|
|
# hard on Windows - domain join, Entra, Chrome Enterprise Core - is documented
|
|
# for Windows and macOS only; Linux has no equivalent. So a plain Pi running
|
|
# Chromium force-installs the extension from our update server with no
|
|
# enrolment, no Google account, and no Chrome. Verified on aarch64 Chromium.
|
|
info "[7/7] Browser extension"
|
|
info " Installs it from the update server into whichever of Chrome,"
|
|
info " Chromium, Edge and Brave are here, and lets them keep it updated."
|
|
info " Needs sudo - browser policy 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 - re-run: sudo scripts/install-extension-policy.sh"
|
|
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 the browser. The extension installs itself from"
|
|
info " https://ext.juicerroom.com within a minute or two."
|
|
else
|
|
info " - Install the extension: sudo scripts/install-extension-policy.sh"
|
|
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
|