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>
This commit is contained in:
+90
-30
@@ -36,47 +36,81 @@
|
||||
until you stop the agent.
|
||||
</p>
|
||||
|
||||
<ol class="steps">
|
||||
<nav class="tabs" id="os-tabs" style="margin:16px 0 4px">
|
||||
<button data-os="macos" class="active">macOS</button>
|
||||
<button data-os="windows">Windows</button>
|
||||
<button data-os="linux">Linux</button>
|
||||
</nav>
|
||||
|
||||
<ol class="steps os-panel" data-os="macos">
|
||||
<li>
|
||||
<strong>Make sure a VNC server is running</strong> on this machine, listening on
|
||||
<span class="mono">127.0.0.1:5900</span>.
|
||||
<strong>Turn on Screen Sharing</strong> — System Settings → General → Sharing.
|
||||
<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>.
|
||||
Under Computer Settings, allow VNC viewers and set a password.
|
||||
</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>
|
||||
<strong>Paste this into Terminal</strong>:
|
||||
<pre class="code" id="cmd-macos"></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>
|
||||
<button class="small" data-copy="cmd-macos">Copy</button>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="notice" style="margin-top:14px">
|
||||
<strong>On Windows 7 or Server 2008 R2?</strong>
|
||||
<ol class="steps os-panel" data-os="windows" hidden>
|
||||
<li>
|
||||
<strong>Install TightVNC or UltraVNC</strong>, set a password, and let it register as a
|
||||
service so it survives a reboot.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Paste this into PowerShell</strong>:
|
||||
<pre class="code" id="cmd-windows"></pre>
|
||||
<div class="row wrap" style="margin-top:10px">
|
||||
<button class="small" data-copy="cmd-windows">Copy</button>
|
||||
</div>
|
||||
<div class="faint" style="margin-top:8px">
|
||||
Nothing else to install — if Node.js is missing the script fetches a self-contained
|
||||
agent instead.
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<ol class="steps os-panel" data-os="linux" hidden>
|
||||
<li>
|
||||
<strong>Start a VNC server on the loopback</strong>:
|
||||
<pre class="code" id="cmd-linux-x11vnc"></pre>
|
||||
</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>Paste this into a terminal</strong>:
|
||||
<pre class="code" id="cmd-linux"></pre>
|
||||
<div class="row wrap" style="margin-top:10px">
|
||||
<button class="small" data-copy="cmd-linux">Copy</button>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="notice hidden" id="legacy-windows" style="margin-top:14px">
|
||||
<strong>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.
|
||||
Those have PowerShell 2.0 and no TLS 1.2, so the command above fails. Run this from
|
||||
<span class="mono">cmd.exe</span> instead:
|
||||
</div>
|
||||
<pre class="code" id="cmd-windows-legacy"></pre>
|
||||
<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>
|
||||
<button class="small" data-copy="cmd-windows-legacy">Copy</button>
|
||||
<a class="btn small" href="/download/agent.exe" download="rcs-agent.exe">Download the agent instead</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="login-foot">
|
||||
Link expires <span id="expiry"></span> · once enrolled, this machine appears in the operator console
|
||||
Link expires <span id="expiry"></span> · <a href="/docs" target="_blank" rel="noopener">full setup guide</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,10 +122,20 @@
|
||||
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`;
|
||||
// The hub stamps its own address and this token into the install scripts, so
|
||||
// each of these really is the whole command — nothing to fill in by hand.
|
||||
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',
|
||||
};
|
||||
|
||||
for (const [id, text] of Object.entries(commands)) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = text;
|
||||
}
|
||||
|
||||
function copy(text, button) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
@@ -101,11 +145,27 @@
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
document.querySelectorAll('[data-copy]').forEach((button) => {
|
||||
button.onclick = () => copy(commands[button.dataset.copy], button);
|
||||
});
|
||||
|
||||
const tabs = document.getElementById('os-tabs');
|
||||
const legacyNotice = document.getElementById('legacy-windows');
|
||||
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;
|
||||
});
|
||||
legacyNotice.classList.toggle('hidden', button.dataset.os !== 'windows');
|
||||
};
|
||||
|
||||
// Open on the tab for whatever this machine is — it is the machine being set up.
|
||||
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();
|
||||
|
||||
fetch(`/api/public/invite/${encodeURIComponent(token)}`)
|
||||
.then((r) => r.json().then((body) => ({ ok: r.ok, body })))
|
||||
|
||||
Reference in New Issue
Block a user