Files
Termix/plugins/remote-desktop/backend/index.mjs
T
LukeGus c6bcfdb80d feat: move remote desktop (RDP/VNC/Telnet) into a first-party plugin
Relocates guacamole backend/frontend source and tests into
plugins/remote-desktop, following the ssh-terminal/docker pattern.
2026-09-21 20:25:52 -05:00

75 lines
2.7 KiB
JavaScript

/**
* Remote Desktop (RDP/VNC/Telnet) - first-party, in-process plugin.
*
* Runs on the main thread rather than in a worker for the same class of
* reason as ssh-terminal/docker (see src/backend/plugins/first-party.ts): it
* owns a WebSocket server (guacamole-lite) on port 30008, and its
* /connect-host/:hostId route does multi-step credential/tunnel resolution
* (jump-host SSH tunnels, a macOS VNC compatibility proxy) involving raw
* net.Socket plumbing the worker's structured-clone postMessage boundary
* cannot carry.
*
* guacd itself is not a process this plugin spawns: it is a separate
* external service (a sibling Docker container by default, or any
* network-reachable guacd) reached over plain TCP. See guacd-config.ts's
* resolveGuacdOptions(). There is nothing to supervise here - guacd's
* availability is handled as an external dependency with graceful
* degradation, same as before this plugin existed.
*
* Its backend logic is real TypeScript physically relocated here (routes.ts,
* guacamole-server.ts, token-service.ts, etc.), compiled by
* scripts/copy-bundled-plugins.cjs like docker's plugin backend, rather than
* kept as a thin wrapper over code left in core -- see that script's own
* comment for why. The one piece that stays in core is
* src/backend/database/routes/guacamole-dispatch.ts, since it is what
* database.ts mounts permanently at /guacamole regardless of whether this
* plugin is installed.
*/
import path from "node:path";
import { fileURLToPath } from "node:url";
async function loadRelative(relativePath) {
const here = path.dirname(fileURLToPath(import.meta.url));
const target = path.join(here, `${relativePath}.js`);
return import(new URL(`file://${target.replace(/\\/g, "/")}`).href);
}
let guacServerModule = null;
let dispatchModule = null;
export async function activate(ctx) {
const [server, routes, dispatch] = await Promise.all([
loadRelative("guacamole-server"),
loadRelative("routes"),
loadRelative("../../../backend/backend/database/routes/guacamole-dispatch"),
]);
guacServerModule = server;
dispatchModule = dispatch;
await server.startGuacamoleService();
dispatch.registerGuacamoleBridge({
router: routes.router,
restart: server.restartGuacServer,
createJoinToken: (guacamoleConnectionId, readOnly) =>
server.tokenService.createJoinToken(guacamoleConnectionId, readOnly),
getSessionInfo: server.getGuacSessionInfo,
});
ctx.log.info(
`Remote Desktop listening on ${server.GUAC_WS_PORT ?? 30008}`,
);
}
export async function deactivate() {
if (dispatchModule) {
dispatchModule.unregisterGuacamoleBridge();
dispatchModule = null;
}
if (guacServerModule) {
await guacServerModule.stopGuacamoleService();
guacServerModule = null;
}
}