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>
114 lines
4.7 KiB
Bash
Executable File
114 lines
4.7 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 5] [--skip-clicker]
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
INTERVAL=5
|
|
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; }
|
|
|
|
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"
|