Files
remote-control-support-webapp/scripts/install.sh
T
rmancinasandClaude Opus 5 cfda6b19b7 feat(install): one-command device registration for macOS, Windows and Linux
Registering a machine meant reading a multi-step page, installing Node, and
running three commands in the right order. Now it is one line per platform.

The hub serves scripts/install.sh and scripts/install.ps1 with its own address
and the enrolment token substituted in, so the published command carries
everything and there is nothing to fill in:

  curl -fsSL https://support.freakma.com/install.sh?token=TOKEN | sh
  irm https://support.freakma.com/install.ps1?token=TOKEN | iex

That output is piped straight into a shell, so the token — the only untrusted
value in either file — is refused unless it matches the base64url shape that
randomToken produces.

Each script checks for a usable runtime and stops with instructions rather than
guessing, warns when nothing is serving RFB on the loopback, installs per-user
with no root or administrator, and registers a login-scoped service: launchd on
macOS, a lingering systemd user service on Linux, a logon task on Windows. The
Windows script uses Node 22 when it is present and falls back to the bundled
executable otherwise, which is what lets one command cover both Windows 11 and
Server 2008 R2.

It registers a logon task rather than a service on purpose: services run in
session 0 and cannot draw on the interactive desktop, so the "ask first" consent
prompt would never appear.

Also adds /docs — a per-OS setup guide with service management and a
troubleshooting table — and reworks the enrolment page into OS tabs that open on
whichever platform the reader is sitting at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 11:37:12 -07:00

168 lines
5.6 KiB
Bash

#!/bin/sh
# Remote Control Support — agent installer for macOS and Linux.
#
# Served from the hub, which substitutes HUB and TOKEN below before sending it,
# so the published one-liner carries the enrolment token in its URL:
#
# curl -fsSL https://support.freakma.com/install.sh?token=TOKEN | sh
#
# Fetched without a token it still works, taking one as an argument instead:
#
# curl -fsSL https://support.freakma.com/install.sh | sh -s -- TOKEN
#
# Everything lands under ~/.rcs-agent. Nothing here needs root.
set -eu
HUB="__HUB__"
TOKEN="__TOKEN__"
# Written long-hand rather than `[ $# -gt 0 ] && TOKEN="$1"`: under `set -e` the
# short-circuit form leaves the script's exit status at 1 when no argument was
# given, which is the normal case for the piped one-liner.
if [ $# -gt 0 ]; then TOKEN="$1"; fi
INSTALL_DIR="$HOME/.rcs-agent"
AGENT="$INSTALL_DIR/agent.js"
CONFIG="$INSTALL_DIR/config.json"
SERVICE_NAME="rcs-agent"
say() { printf '%s\n' "$*"; }
warn() { printf '\033[33m!\033[0m %s\n' "$*" >&2; }
die() { printf '\033[31mx\033[0m %s\n' "$*" >&2; exit 1; }
step() { printf '\033[36m>\033[0m %s\n' "$*"; }
[ -n "$TOKEN" ] || die "no enrolment token. Open the invite link on this machine, or pass one:
curl -fsSL $HUB/install.sh | sh -s -- YOUR_TOKEN"
case "$(uname -s)" in
Darwin) PLATFORM=macos ;;
Linux) PLATFORM=linux ;;
*) die "$(uname -s) is not supported by this script — see $HUB/docs" ;;
esac
# ------------------------------------------------------------------ node
# The agent uses the global WebSocket, which is only on by default from Node 22.
command -v node >/dev/null 2>&1 || die "Node.js 22 or newer is required and was not found.
macOS: brew install node (or https://nodejs.org)
Debian: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs
Fedora: sudo dnf install nodejs"
NODE_MAJOR=$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)
[ "$NODE_MAJOR" -ge 22 ] 2>/dev/null || die "Node.js 22 or newer is required — this machine has $(node -v).
Upgrade it, or run the agent on another machine on this LAN and point it here:
node agent.js enroll <link> --vnc-host $(hostname)"
# ------------------------------------------------------------------- vnc
# Advisory only: the VNC server can be started after enrolment, and on a fresh
# macOS the Screen Sharing toggle is usually the last thing someone does.
vnc_listening() {
if command -v nc >/dev/null 2>&1; then nc -z -w 2 127.0.0.1 5900 >/dev/null 2>&1; return $?; fi
return 1
}
if ! vnc_listening; then
case "$PLATFORM" in
macos) warn "nothing is listening on 127.0.0.1:5900 — turn on System Settings > General > Sharing > Screen Sharing" ;;
linux) warn "nothing is listening on 127.0.0.1:5900 — start one, e.g. x11vnc -localhost -rfbport 5900" ;;
esac
fi
# --------------------------------------------------------------- install
step "installing to $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
curl -fsSL "$HUB/download/agent.js" -o "$AGENT" || die "could not download the agent from $HUB"
step "enrolling with $HUB"
node "$AGENT" enroll "$HUB/enroll/$TOKEN" --config "$CONFIG"
# --------------------------------------------------------------- service
NODE_BIN=$(command -v node)
install_launchd() {
PLIST="$HOME/Library/LaunchAgents/com.freakma.rcs-agent.plist"
mkdir -p "$HOME/Library/LaunchAgents"
cat > "$PLIST" <<PLIST_EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.freakma.rcs-agent</string>
<key>ProgramArguments</key>
<array>
<string>$NODE_BIN</string>
<string>$AGENT</string>
<string>run</string>
<string>--config</string>
<string>$CONFIG</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>$INSTALL_DIR/agent.log</string>
<key>StandardErrorPath</key><string>$INSTALL_DIR/agent.log</string>
</dict>
</plist>
PLIST_EOF
launchctl unload "$PLIST" >/dev/null 2>&1 || true
launchctl load "$PLIST"
say ""
say "Running as a launchd agent. Useful commands:"
say " launchctl unload $PLIST # stop"
say " launchctl load $PLIST # start"
say " tail -f $INSTALL_DIR/agent.log"
}
install_systemd_user() {
UNIT_DIR="$HOME/.config/systemd/user"
mkdir -p "$UNIT_DIR"
cat > "$UNIT_DIR/$SERVICE_NAME.service" <<UNIT_EOF
[Unit]
Description=Remote Control Support agent
After=network-online.target
[Service]
ExecStart=$NODE_BIN $AGENT run --config $CONFIG
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
UNIT_EOF
systemctl --user daemon-reload
systemctl --user enable --now "$SERVICE_NAME"
say ""
say "Running as a systemd user service. Useful commands:"
say " systemctl --user status $SERVICE_NAME"
say " journalctl --user -u $SERVICE_NAME -f"
# Without lingering the service dies at logout, which is exactly when remote
# support is most likely to be needed.
loginctl enable-linger "$(id -un)" >/dev/null 2>&1 \
|| warn "run 'sudo loginctl enable-linger $(id -un)' so the agent survives logout"
}
install_manual() {
say ""
say "No supported service manager found. Start the agent by hand:"
say " node $AGENT run --config $CONFIG"
}
step "setting it to start automatically"
case "$PLATFORM" in
macos) install_launchd ;;
linux)
if command -v systemctl >/dev/null 2>&1 && systemctl --user show-environment >/dev/null 2>&1; then
install_systemd_user
else
install_manual
fi
;;
esac
say ""
say "Done. This machine is registered and should now appear in the console."
say "Config: $CONFIG"