Files
autofirmer-expanded/scripts/install-autostart.sh
T
Brandon LiandClaude Opus 5 153b1273c3 Refuse to run the Linux setup as root
setup-linux.sh accepted being run as root and simply set SUDO="". That quietly
broke everything user-scoped: PM2 keeps its process list in $HOME/.pm2 and the
login entry goes in $HOME/.config/autostart, so under sudo both landed in /root.
The install reported success, then `pm2 list` as the real user showed nothing -
it was talking to a different daemon - and the autostart entry sat in a home
directory that never logs in.

Both scripts now refuse, and name the account to use when SUDO_USER says how
they were invoked. setup-linux.sh still uses sudo for apt, which is the only
part that needs it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 13:21:59 -05:00

126 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Register AutoFirmer to start at login and keep itself updated. Linux/Pi
# counterpart of install-autostart.ps1 - the same two mechanisms, expressed the
# way this desktop does them:
#
# * a ~/.config/autostart entry runs scripts/start-all.sh at login
# * PM2's own cron restart drives the update checks
#
# No sudo, nothing system-wide. Everything runs inside the graphical login
# session, which is not incidental: the clicker synthesises real X11 input and
# needs a desktop with DISPLAY set. A systemd system service has neither.
#
# ./scripts/install-autostart.sh [--interval-minutes 1] [--skip-clicker]
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# Default 1: the no-change path is a single `git fetch`, so a per-minute check
# costs nothing and matches the VPS's own 60s deploy timer.
INTERVAL=1
SKIP_CLICKER=0
while [ $# -gt 0 ]; do
case "$1" in
--interval-minutes) INTERVAL="$2"; shift 2 ;;
--skip-clicker) SKIP_CLICKER=1; shift ;;
*) echo "unknown option: $1" >&2; exit 1 ;;
esac
done
info() { printf ' %s\n' "$1"; }
warn() { printf ' ! %s\n' "$1" >&2; }
# Same reason as setup-linux.sh: PM2's process list lives in $HOME/.pm2 and the
# login entry in $HOME/.config/autostart. As root both go to /root, invisible to
# the account that actually logs in.
if [ "$(id -u)" -eq 0 ]; then
echo " [X] do not run this as root${SUDO_USER:+ - run it as $SUDO_USER}." >&2
echo " PM2 and the login entry are per-user; as root they land in /root" >&2
echo " and your own 'pm2 list' will be empty." >&2
exit 1
fi
info "project root: $ROOT"
# ── PM2 ─────────────────────────────────────────────────────────────────────
if ! command -v pm2 >/dev/null 2>&1; then
info 'installing PM2 globally...'
npm install -g pm2
command -v pm2 >/dev/null 2>&1 || { echo "PM2 installed but not on PATH" >&2; exit 1; }
fi
info "pm2: $(command -v pm2)"
cd "$ROOT"
# Run Next directly rather than through `npm start`, so PM2 supervises the
# server itself instead of an npm wrapper that spawns it.
NEXT_BIN="$ROOT/node_modules/next/dist/bin/next"
[ -f "$NEXT_BIN" ] || { echo "next not found at $NEXT_BIN - run npm install first" >&2; exit 1; }
pm2 delete autofirmer >/dev/null 2>&1 || true
pm2 start "$NEXT_BIN" --name autofirmer --interpreter node -- start
info 'pm2: autofirmer registered'
if [ "$SKIP_CLICKER" -eq 0 ]; then
PY_CMD_FILE="$ROOT/scripts/.python-cmd"
PY_CMD="$( [ -f "$PY_CMD_FILE" ] && cat "$PY_CMD_FILE" || echo python3 )"
PY_EXE="$($PY_CMD -c 'import sys;print(sys.executable)' 2>/dev/null || true)"
if [ -n "$PY_EXE" ] && [ -x "$PY_EXE" ]; then
pm2 delete clicker >/dev/null 2>&1 || true
if pm2 start "$ROOT/clicker/runner.py" --name clicker --interpreter "$PY_EXE"; then
info "pm2: clicker registered ($PY_EXE)"
else
warn 'pm2 start clicker failed - the dashboard is unaffected'
fi
else
warn 'python not found - skipping the clicker'
fi
fi
# ── Update checks (PM2 cron) ────────────────────────────────────────────────
pm2 delete autofirmer-update >/dev/null 2>&1 || true
if pm2 start "$ROOT/scripts/update-check.mjs" --name autofirmer-update \
--no-autorestart --cron-restart "*/$INTERVAL * * * *"; then
info "update checks: every $INTERVAL minutes (*/$INTERVAL * * * *)"
else
warn 'could not register the update checker - run scripts/update-check.mjs by hand'
fi
pm2 save >/dev/null
info 'pm2: process list saved'
# ── Start at login (XDG autostart) ──────────────────────────────────────────
# The desktop equivalent of the Windows Startup folder: runs inside the
# graphical session, so DISPLAY is set and the clicker can reach the X server.
AUTOSTART_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/autostart"
DESKTOP_FILE="$AUTOSTART_DIR/autofirmer.desktop"
mkdir -p "$AUTOSTART_DIR"
cat > "$DESKTOP_FILE" <<DESKTOP
[Desktop Entry]
Type=Application
Name=AutoFirmer
Comment=Start the AutoFirmer dashboard, clicker and update checker
Exec=$ROOT/scripts/start-all.sh
Terminal=false
X-GNOME-Autostart-enabled=true
DESKTOP
info "login entry: $DESKTOP_FILE"
# ── Wayland warning ─────────────────────────────────────────────────────────
# Worth saying now rather than letting every automation fail later.
if [ -n "${WAYLAND_DISPLAY:-}" ] && [ -z "${DISPLAY:-}" ]; then
echo
warn 'This is a Wayland session. The dashboard is fine, but the clicker cannot'
warn 'work: xdotool and pyautogui both speak X11, which Wayland does not expose.'
warn 'Switch with: sudo raspi-config -> Advanced Options -> Wayland -> X11, then reboot.'
fi
echo
info 'Done. No sudo was needed and nothing system-wide was changed.'
info 'Running now, and again at every login.'
info "Updates checked every $INTERVAL minutes; see scripts/update.log"
info 'Useful: pm2 list | pm2 logs autofirmer | pm2 logs clicker'
info "To disable autostart: rm $DESKTOP_FILE"