The setup pages build commands that get pasted into a terminal on a *different* machine, but they built them from location.origin. Open the console by LAN IP or over a tunnel and the enrolment one-liner told the target machine to curl an address it may not resolve at all. /docs and /enroll/:token are now served through a template that stamps in config.baseUrl — PUBLIC_URL where set, the proxied request host otherwise — with location.origin left as a fallback for the unsubstituted file. The remaining 127.0.0.1 references are the loopback VNC port on the machine being registered, which is meant to be literal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
347 lines
16 KiB
HTML
347 lines
16 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="color-scheme" content="dark">
|
|
<title>Registering a device — Remote Support</title>
|
|
<link rel="stylesheet" href="/styles.css">
|
|
<style>
|
|
.docs-shell { max-width: 860px; margin: 0 auto; padding: 28px 20px 80px; position: relative; z-index: 1; }
|
|
.docs-shell h2 { margin: 34px 0 10px; font-size: 17px; }
|
|
.docs-shell h3 { margin: 22px 0 8px; font-size: 14px; color: var(--text); }
|
|
.docs-shell p { margin: 8px 0; font-size: 14px; line-height: 1.65; color: var(--text-dim, var(--text)); }
|
|
.docs-shell ul { margin: 8px 0 8px 18px; font-size: 14px; line-height: 1.7; }
|
|
.docs-shell table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13.5px; }
|
|
.docs-shell th, .docs-shell td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border, #2a2a2a); vertical-align: top; }
|
|
.docs-shell th { color: var(--text-faint); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
|
|
.os-panel[hidden] { display: none; }
|
|
.copy-wrap { position: relative; }
|
|
.copy-wrap button { position: absolute; top: 8px; right: 8px; }
|
|
/* The button floats over the block, so keep the long one-liners from
|
|
scrolling underneath it. */
|
|
.copy-wrap pre.code { padding-right: 76px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="bg-field" id="bg-field" aria-hidden="true"></div>
|
|
|
|
<div class="docs-shell">
|
|
|
|
<div class="card-header" style="padding-left:0;border:0">
|
|
<div class="brand-mark">RC</div>
|
|
<div>
|
|
<strong style="font-size:14px">Remote Support</strong>
|
|
<span class="brand-sub">Documentation</span>
|
|
</div>
|
|
<div style="margin-left:auto"><a class="btn ghost small" href="/">Back to console</a></div>
|
|
</div>
|
|
|
|
<span class="card-kicker">Setup guide</span>
|
|
<h1 style="margin:6px 0 4px">Registering a device</h1>
|
|
<p class="faint">
|
|
How to put a machine under remote support, on macOS, Windows and Linux — what to install,
|
|
what the one-command installer actually does, and how to keep it running.
|
|
</p>
|
|
|
|
<h2>Before you start</h2>
|
|
<p>
|
|
Every registered machine needs two things: a <strong>VNC server</strong> serving its screen on
|
|
<span class="mono">127.0.0.1:5900</span>, and the <strong>agent</strong>, which dials out to this
|
|
hub and forwards that port over an authenticated WebSocket. The agent is what makes NAT and
|
|
firewalls stop mattering — nothing inbound is ever opened on the machine.
|
|
</p>
|
|
<p>
|
|
You also need an <strong>enrollment link</strong>. In the console, open
|
|
<strong>Machines → Invite a machine</strong>. The link carries a one-time token and expires;
|
|
open it on the machine being registered and it shows the exact command with the token already
|
|
filled in.
|
|
</p>
|
|
|
|
<div class="notice">
|
|
<strong>The alternative is direct mode</strong>, where this hub connects straight to the
|
|
machine's VNC port. That only works when the hub can route to it. If the machine is on another
|
|
subnet, behind NAT, or reachable only through a VPN or SSH tunnel, use the agent — a direct
|
|
entry will just time out.
|
|
</div>
|
|
|
|
<h2>Pick a platform</h2>
|
|
<nav class="tabs" id="os-tabs" style="margin-bottom:16px">
|
|
<button data-os="macos" class="active">macOS</button>
|
|
<button data-os="windows">Windows</button>
|
|
<button data-os="linux">Linux</button>
|
|
</nav>
|
|
|
|
<!-- ------------------------------------------------------------ macOS -->
|
|
<section class="os-panel" data-os="macos">
|
|
<h3>1. Turn on Screen Sharing</h3>
|
|
<p>
|
|
<strong>System Settings → General → Sharing → Screen Sharing</strong>. Under
|
|
<em>Computer Settings</em>, enable <em>VNC viewers may control screen with password</em> and
|
|
set a password — the hub authenticates with it and never passes it to the browser.
|
|
</p>
|
|
<p class="faint">
|
|
macOS serves VNC on port 5900 on all interfaces. The agent only ever connects to it over
|
|
loopback, so you can firewall the port off from the network.
|
|
</p>
|
|
|
|
<h3>2. Install Node.js 22 or newer</h3>
|
|
<p>
|
|
<span class="mono">brew install node</span>, or the installer from
|
|
<a href="https://nodejs.org" target="_blank" rel="noopener">nodejs.org</a>. Check with
|
|
<span class="mono">node -v</span>.
|
|
</p>
|
|
|
|
<h3>3. Run the installer</h3>
|
|
<p>Paste this in Terminal, replacing <span class="mono">TOKEN</span> with the one from your enrollment link:</p>
|
|
<div class="copy-wrap">
|
|
<pre class="code" id="cmd-macos"></pre>
|
|
<button class="small" data-copy="cmd-macos">Copy</button>
|
|
</div>
|
|
<p>
|
|
It installs to <span class="mono">~/.rcs-agent</span>, enrolls the machine, and loads a
|
|
<strong>launchd</strong> agent so it starts at login and restarts if it dies.
|
|
</p>
|
|
|
|
<h3>Managing it</h3>
|
|
<table>
|
|
<tr><th>Do this</th><th>Command</th></tr>
|
|
<tr><td>Stop</td><td class="mono">launchctl unload ~/Library/LaunchAgents/com.freakma.rcs-agent.plist</td></tr>
|
|
<tr><td>Start</td><td class="mono">launchctl load ~/Library/LaunchAgents/com.freakma.rcs-agent.plist</td></tr>
|
|
<tr><td>Watch the log</td><td class="mono">tail -f ~/.rcs-agent/agent.log</td></tr>
|
|
<tr><td>Remove</td><td class="mono">launchctl unload …plist && rm -rf ~/.rcs-agent</td></tr>
|
|
</table>
|
|
</section>
|
|
|
|
<!-- ---------------------------------------------------------- Windows -->
|
|
<section class="os-panel" data-os="windows" hidden>
|
|
<h3>1. Install a VNC server</h3>
|
|
<p>
|
|
Windows has none built in. <a href="https://www.tightvnc.com" target="_blank" rel="noopener">TightVNC</a>
|
|
or UltraVNC both work. During setup, register it as a system service so it survives reboots,
|
|
and set a password.
|
|
</p>
|
|
|
|
<h3>2. Run the installer</h3>
|
|
<p>In PowerShell, with <span class="mono">TOKEN</span> from your enrollment link:</p>
|
|
<div class="copy-wrap">
|
|
<pre class="code" id="cmd-windows"></pre>
|
|
<button class="small" data-copy="cmd-windows">Copy</button>
|
|
</div>
|
|
<p>
|
|
No prerequisites. If Node.js 22+ happens to be installed the script uses it; otherwise it
|
|
downloads <span class="mono">rcs-agent.exe</span>, a self-contained build with its own
|
|
runtime inside. Everything lands in
|
|
<span class="mono">%LOCALAPPDATA%\RemoteControlSupport</span> and no administrator rights
|
|
are needed.
|
|
</p>
|
|
|
|
<h3>Windows 7 and Server 2008 R2</h3>
|
|
<p>
|
|
Those ship PowerShell 2.0, which has no <span class="mono">irm</span>, and .NET 3.5, which
|
|
does not negotiate TLS 1.2 by default — the short command fails on both counts. Use this
|
|
instead, from <strong>cmd.exe</strong>:
|
|
</p>
|
|
<div class="copy-wrap">
|
|
<pre class="code" id="cmd-windows-legacy"></pre>
|
|
<button class="small" data-copy="cmd-windows-legacy">Copy</button>
|
|
</div>
|
|
<p class="faint">
|
|
If it still cannot connect, the machine is missing
|
|
<a href="https://support.microsoft.com/kb/3154518" target="_blank" rel="noopener">KB3154518</a>,
|
|
which adds TLS 1.2 to .NET 3.5. As a last resort,
|
|
<a href="/download/agent.exe" download="rcs-agent.exe">download the agent</a> on another
|
|
machine, copy it across, and run
|
|
<span class="mono">rcs-agent.exe enroll <link></span> then
|
|
<span class="mono">rcs-agent.exe run</span>.
|
|
</p>
|
|
|
|
<h3>Managing it</h3>
|
|
<table>
|
|
<tr><th>Do this</th><th>Command</th></tr>
|
|
<tr><td>Check it exists</td><td class="mono">schtasks /query /tn RemoteControlSupportAgent</td></tr>
|
|
<tr><td>Start</td><td class="mono">schtasks /run /tn RemoteControlSupportAgent</td></tr>
|
|
<tr><td>Stop</td><td class="mono">schtasks /end /tn RemoteControlSupportAgent</td></tr>
|
|
<tr><td>Remove</td><td class="mono">schtasks /delete /tn RemoteControlSupportAgent /f</td></tr>
|
|
</table>
|
|
|
|
<div class="notice">
|
|
<strong>Do not install the agent as a Windows service</strong> on a machine where
|
|
<em>ask first</em> is enabled. Services run in session 0 and cannot draw on the interactive
|
|
desktop, so the consent prompt would never appear and every session would time out waiting
|
|
for an answer. The installer registers a <em>logon task</em> precisely to avoid this — it
|
|
runs in the console session, where the prompt is visible.
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ------------------------------------------------------------ Linux -->
|
|
<section class="os-panel" data-os="linux" hidden>
|
|
<h3>1. Start a VNC server on the loopback</h3>
|
|
<p>For an existing X session — what you want for support, since it shows the real screen:</p>
|
|
<div class="copy-wrap">
|
|
<pre class="code" id="cmd-linux-x11vnc"></pre>
|
|
<button class="small" data-copy="cmd-linux-x11vnc">Copy</button>
|
|
</div>
|
|
<p class="faint">
|
|
On Wayland, <span class="mono">x11vnc</span> cannot see the screen. Either log in on an Xorg
|
|
session, or use a Wayland-native server such as <span class="mono">wayvnc</span>.
|
|
</p>
|
|
|
|
<h3>2. Install Node.js 22 or newer</h3>
|
|
<table>
|
|
<tr><th>Distro</th><th>Command</th></tr>
|
|
<tr><td>Debian / Ubuntu</td><td class="mono">curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs</td></tr>
|
|
<tr><td>Fedora / RHEL</td><td class="mono">sudo dnf install nodejs</td></tr>
|
|
<tr><td>Arch</td><td class="mono">sudo pacman -S nodejs</td></tr>
|
|
</table>
|
|
|
|
<h3>3. Run the installer</h3>
|
|
<div class="copy-wrap">
|
|
<pre class="code" id="cmd-linux"></pre>
|
|
<button class="small" data-copy="cmd-linux">Copy</button>
|
|
</div>
|
|
<p>
|
|
It installs to <span class="mono">~/.rcs-agent</span> and registers a
|
|
<strong>systemd user service</strong>. It also enables lingering, so the agent keeps running
|
|
when nobody is logged in — without that, the machine drops off the console at logout, which
|
|
is exactly when you tend to need it.
|
|
</p>
|
|
|
|
<h3>Managing it</h3>
|
|
<table>
|
|
<tr><th>Do this</th><th>Command</th></tr>
|
|
<tr><td>Status</td><td class="mono">systemctl --user status rcs-agent</td></tr>
|
|
<tr><td>Logs</td><td class="mono">journalctl --user -u rcs-agent -f</td></tr>
|
|
<tr><td>Restart</td><td class="mono">systemctl --user restart rcs-agent</td></tr>
|
|
<tr><td>Remove</td><td class="mono">systemctl --user disable --now rcs-agent && rm -rf ~/.rcs-agent</td></tr>
|
|
</table>
|
|
</section>
|
|
|
|
<h2>Registering a machine that cannot run the agent</h2>
|
|
<p>
|
|
Printers, appliances, locked-down boxes, anything too old for a current runtime — the agent
|
|
does not have to run <em>on</em> the machine it serves. Run it on any other machine on the same
|
|
LAN and point it across:
|
|
</p>
|
|
<div class="copy-wrap">
|
|
<pre class="code" id="cmd-proxy"></pre>
|
|
<button class="small" data-copy="cmd-proxy">Copy</button>
|
|
</div>
|
|
<p class="faint">
|
|
That host now needs to reach the target's VNC port, and it becomes the machine whose uptime
|
|
determines whether the target is reachable.
|
|
</p>
|
|
|
|
<h2>Checking it worked</h2>
|
|
<ul>
|
|
<li>The machine appears in <strong>Machines</strong> in the console, marked online.</li>
|
|
<li>Its hostname and OS show up — the agent reports both when it registers.</li>
|
|
<li>Clicking <strong>Connect</strong> opens the screen.</li>
|
|
</ul>
|
|
|
|
<h2>When it does not</h2>
|
|
<table>
|
|
<tr><th>Symptom</th><th>Cause</th></tr>
|
|
<tr>
|
|
<td class="mono">timed out connecting to …:5900</td>
|
|
<td>This hub cannot route to that address. A direct-mode entry on a subnet the hub cannot
|
|
reach — register it with the agent instead.</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="mono">cannot reach …:5900 (ECONNREFUSED)</td>
|
|
<td>Routing is fine, nothing is listening. The VNC server is not running.</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="mono">VNC authentication failed</td>
|
|
<td>Wrong stored password. Note that VNC authentication only uses the first 8 characters.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Enrollment says the link expired</td>
|
|
<td>Enrollment tokens are one-time and time-limited. Mint a new one.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Machine shows offline right after install</td>
|
|
<td>The agent is not running. Check the service commands for your platform above.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Sessions hang on "waiting for consent"</td>
|
|
<td><em>Ask first</em> is on and the agent cannot show a dialog — it is running headless, as
|
|
a Windows service, or on Linux without <span class="mono">zenity</span>/<span class="mono">kdialog</span>.</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>What the installer does</h2>
|
|
<p>
|
|
Both scripts are served by this hub with its address and your token already substituted in, so
|
|
there is nothing to fill in. They are worth reading before you pipe them into a shell —
|
|
<a id="raw-sh" href="/install.sh">install.sh</a> and
|
|
<a id="raw-ps1" href="/install.ps1">install.ps1</a> are the exact text that runs. In order they:
|
|
</p>
|
|
<ul>
|
|
<li>check for a usable runtime, and stop with instructions rather than guessing;</li>
|
|
<li>warn if nothing is listening on <span class="mono">127.0.0.1:5900</span>;</li>
|
|
<li>download the agent into a per-user directory — no root, no administrator;</li>
|
|
<li>enroll, which exchanges the one-time token for an agent key stored with <span class="mono">0600</span> permissions;</li>
|
|
<li>register a login-scoped service so it comes back after a reboot.</li>
|
|
</ul>
|
|
|
|
<p class="login-foot" style="margin-top:30px">
|
|
Access control, support links and session policy are covered in the project README.
|
|
</p>
|
|
</div>
|
|
|
|
<script src="/particles.js"></script>
|
|
<script>
|
|
// Substituted by the server with this hub's public address. These commands run
|
|
// on a different machine, so location.origin would be wrong whenever the reader
|
|
// reached this page by IP or over a tunnel. Falls back only if served unmodified.
|
|
const stamped = '__HUB__';
|
|
const origin = stamped.indexOf('__') === 0 ? location.origin : stamped;
|
|
|
|
const commands = {
|
|
'cmd-macos': `curl -fsSL ${origin}/install.sh?token=TOKEN | sh`,
|
|
'cmd-linux': `curl -fsSL ${origin}/install.sh?token=TOKEN | sh`,
|
|
'cmd-windows': `irm ${origin}/install.ps1?token=TOKEN | iex`,
|
|
'cmd-windows-legacy': `powershell -c "[Net.ServicePointManager]::SecurityProtocol=3072; (New-Object Net.WebClient).DownloadString('${origin}/install.ps1?token=TOKEN') | iex"`,
|
|
'cmd-linux-x11vnc': 'x11vnc -localhost -rfbport 5900 -forever -shared -passwd YOURPASSWORD',
|
|
'cmd-proxy': `curl -fsSL ${origin}/download/agent.js -o rcs-agent.js\n`
|
|
+ `node rcs-agent.js enroll ${origin}/enroll/TOKEN --vnc-host 192.168.1.50 --name "old-box"\n`
|
|
+ 'node rcs-agent.js run',
|
|
};
|
|
|
|
for (const [id, text] of Object.entries(commands)) {
|
|
const el = document.getElementById(id);
|
|
if (el) el.textContent = text;
|
|
}
|
|
|
|
document.querySelectorAll('[data-copy]').forEach((button) => {
|
|
button.onclick = () => {
|
|
navigator.clipboard.writeText(commands[button.dataset.copy]).then(() => {
|
|
const old = button.textContent;
|
|
button.textContent = 'Copied';
|
|
setTimeout(() => { button.textContent = old; }, 1500);
|
|
});
|
|
};
|
|
});
|
|
|
|
const tabs = document.getElementById('os-tabs');
|
|
tabs.onclick = (event) => {
|
|
const button = event.target.closest('button[data-os]');
|
|
if (!button) return;
|
|
tabs.querySelectorAll('button').forEach((b) => b.classList.toggle('active', b === button));
|
|
document.querySelectorAll('.os-panel').forEach((panel) => {
|
|
panel.hidden = panel.dataset.os !== button.dataset.os;
|
|
});
|
|
};
|
|
|
|
// Land on the tab for whatever the reader is sitting at.
|
|
const guess = /Mac/i.test(navigator.platform) ? 'macos'
|
|
: /Win/i.test(navigator.platform) ? 'windows'
|
|
: /Linux/i.test(navigator.platform) ? 'linux' : null;
|
|
if (guess) tabs.querySelector(`button[data-os="${guess}"]`)?.click();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|