Files
remote-control-support-webapp/public/enroll.html
T
rmancinasandClaude Opus 5 cecaf74a0a
Build and Deploy Remote Control Support / Test (push) Successful in 10s
Build and Deploy Remote Control Support / Build Image (push) Successful in 37s
Build and Deploy Remote Control Support / Deploy to Portainer (push) Successful in 6s
feat(agent): ship a Node 12 executable for Windows Server 2008 R2
Machines that cannot install Node 22 had no way to run the agent at all, which
left them stuck on direct mode — and direct mode only works when the hub can
route to the VNC port, which it often cannot.

agent.js now resolves the two Node 22 globals it uses through fallbacks: `ws`
for the control and tunnel sockets, and http/https for the single enrolment
POST. Node 22 loads neither, since `globalThis.WebSocket || require('ws')`
short-circuits. The require and the http/https references are static so the
bundler can follow them.

A new Docker stage bundles that file with a Node 12 runtime — the last line
supporting Windows 7 and Server 2008 R2 — into one self-contained .exe, served
from /download/agent.exe and linked from the enrolment page. The route answers
503 rather than 404 in a dev checkout, where the build has not run.

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

135 lines
5.2 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>Set up remote support</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="bg-field" id="bg-field" aria-hidden="true"></div>
<div class="center-shell">
<div class="center-card">
<div class="card-header">
<div class="brand-mark">RC</div>
<div>
<strong style="font-size:14px">Remote Support</strong>
<span class="brand-sub">Machine enrollment</span>
</div>
</div>
<div class="card-body">
<span class="card-kicker">One-time setup</span>
<h1>Set up remote support</h1>
<div id="state-loading" class="faint">Checking this link…</div>
<div id="state-invalid" class="notice error hidden"></div>
<div id="state-ok" class="hidden">
<p class="faint">
This registers this computer so it can be supported remotely. It stays connected
until you stop the agent.
</p>
<ol class="steps">
<li>
<strong>Make sure a VNC server is running</strong> on this machine, listening on
<span class="mono">127.0.0.1:5900</span>.
<div class="faint" style="margin-top:6px">
macOS: System Settings → General → Sharing → Screen Sharing.<br>
Windows: install TightVNC or UltraVNC.<br>
Linux: <span class="mono">x11vnc -localhost -rfbport 5900</span>.
</div>
</li>
<li>
<strong>Install <a href="https://nodejs.org" target="_blank" rel="noopener">Node.js 22+</a></strong> if it is not already there.
</li>
<li>
<strong>Run this</strong> in a terminal:
<pre class="code" id="oneliner"></pre>
<div class="row wrap" style="margin-top:10px">
<button class="small" id="copy-unix">Copy for macOS / Linux</button>
<button class="small" id="copy-win">Copy for Windows</button>
</div>
</li>
</ol>
<div class="notice" style="margin-top:14px">
<strong>On Windows 7 or Server 2008 R2?</strong>
<div class="faint" style="margin-top:6px">
Node.js will not install on those — the last version that supported them is long
out of date. Use the standalone build instead: one file, nothing to install.
</div>
<div class="row wrap" style="margin-top:10px">
<a class="small" href="/download/agent.exe" download="rcs-agent.exe">Download rcs-agent.exe</a>
</div>
<div class="faint" style="margin-top:10px">Then in a Command Prompt, in the folder you saved it to:</div>
<pre class="code" id="exe-oneliner"></pre>
<div class="row wrap" style="margin-top:10px">
<button class="small" id="copy-exe">Copy</button>
</div>
</div>
<p class="login-foot">
Link expires <span id="expiry"></span> · once enrolled, this machine appears in the operator console
</p>
</div>
</div>
</div>
</div>
<script>
const token = location.pathname.split('/').filter(Boolean).pop();
const origin = location.origin;
const link = `${origin}/enroll/${token}`;
const unix = `curl -fsSL ${origin}/download/agent.js -o rcs-agent.js \\\n && node rcs-agent.js enroll ${link} \\\n && node rcs-agent.js run`;
const win = `iwr ${origin}/download/agent.js -OutFile rcs-agent.js; `
+ `node rcs-agent.js enroll ${link}; node rcs-agent.js run`;
const exe = `rcs-agent.exe enroll ${link}\r\nrcs-agent.exe run`;
function copy(text, button) {
navigator.clipboard.writeText(text).then(() => {
const old = button.textContent;
button.textContent = 'Copied';
setTimeout(() => { button.textContent = old; }, 1500);
});
}
document.getElementById('oneliner').textContent = unix;
document.getElementById('copy-unix').onclick = (e) => copy(unix, e.currentTarget);
document.getElementById('copy-win').onclick = (e) => copy(win, e.currentTarget);
document.getElementById('exe-oneliner').textContent = exe;
document.getElementById('copy-exe').onclick = (e) => copy(exe, e.currentTarget);
fetch(`/api/public/invite/${encodeURIComponent(token)}`)
.then((r) => r.json().then((body) => ({ ok: r.ok, body })))
.then(({ ok, body }) => {
document.getElementById('state-loading').classList.add('hidden');
if (!ok || !body.usable || body.kind !== 'enroll') {
const box = document.getElementById('state-invalid');
box.textContent = body.error || `This enrollment link is ${body.reason || 'not valid'}.`;
box.classList.remove('hidden');
return;
}
const expiry = document.getElementById('expiry');
expiry.textContent = body.expiresAt ? new Date(body.expiresAt).toLocaleString() : 'never';
document.getElementById('state-ok').classList.remove('hidden');
})
.catch(() => {
document.getElementById('state-loading').textContent = 'Could not reach the server.';
});
</script>
<script type="module">
import { mountParticles } from '/particles.js';
mountParticles(document.getElementById('bg-field'));
</script>
</body>
</html>