mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
|
import { GuacamoleTokenService } from "./token-service.js";
|
|
import { RemoteSessions } from "./sessions.js";
|
|
import {
|
|
createGuacamoleServer,
|
|
type RecordingsWriter,
|
|
} from "./guacamole-server.js";
|
|
import { resolveGuacdOptions } from "./guacd-config.js";
|
|
import { registerRoutes } from "./routes.js";
|
|
import { createLiveSessions } from "./live-sessions.js";
|
|
import {
|
|
hostPayloadLegacy,
|
|
normalizeImportedHost,
|
|
PORT_KEY,
|
|
readHostSettings,
|
|
} from "./host-settings.js";
|
|
import { createLogger } from "./log.js";
|
|
|
|
export async function activate(ctx: PluginContext) {
|
|
const log = createLogger(ctx.log);
|
|
const tokens = new GuacamoleTokenService(log);
|
|
const sessions = new RemoteSessions();
|
|
ctx.disposables.add(() => sessions.clear());
|
|
|
|
// The admin setting, unless GUACD_URL / GUACD_HOST / GUACD_PORT override it.
|
|
let guacdUrl = (await ctx.settings.get<string>("guacdUrl")) ?? "";
|
|
const guacd = () => resolveGuacdOptions(guacdUrl || undefined);
|
|
const enabled = async () =>
|
|
(await ctx.settings.get<boolean>("enabled")) !== false;
|
|
|
|
// Optional: without session-recording nothing is recorded.
|
|
const recordings = (): RecordingsWriter | null => {
|
|
const service =
|
|
ctx.services.get<Partial<RecordingsWriter>>("recordings.writer");
|
|
return typeof service.createFinished === "function" &&
|
|
typeof service.enabledFor === "function"
|
|
? (service as RecordingsWriter)
|
|
: null;
|
|
};
|
|
|
|
// guacamole-lite prints every new connection to stdout.
|
|
const consoleLog = console.log;
|
|
console.log = (...args: unknown[]) => {
|
|
if (
|
|
typeof args[0] === "string" &&
|
|
args[0].startsWith("New client connection")
|
|
)
|
|
return;
|
|
consoleLog(...args);
|
|
};
|
|
ctx.disposables.add(() => {
|
|
console.log = consoleLog;
|
|
});
|
|
|
|
const server = createGuacamoleServer({
|
|
log,
|
|
tokens,
|
|
sessions,
|
|
guacd,
|
|
recordings,
|
|
asUser: (userId, fn) => ctx.asUser(userId, fn),
|
|
onOpen: (hostId) => ctx.hosts.trackSession(hostId),
|
|
});
|
|
ctx.disposables.add(() => server.close());
|
|
|
|
ctx.settings.onChange("guacdUrl", (value) => {
|
|
guacdUrl = typeof value === "string" ? value : "";
|
|
server.restart();
|
|
});
|
|
|
|
registerRoutes(ctx.http.router(), {
|
|
ctx,
|
|
log,
|
|
tokens,
|
|
sessions,
|
|
guacd,
|
|
enabled,
|
|
recordings,
|
|
});
|
|
|
|
// guacamole-lite owns its own WebSocketServer, so it takes the raw upgrade.
|
|
// Public because a display authenticates with the encrypted, single-use
|
|
// token minted by an authenticated route, not with a session JWT.
|
|
ctx.ws.upgrade(
|
|
"/display",
|
|
(request, socket, head) =>
|
|
server.handleUpgrade(
|
|
request as Parameters<typeof server.handleUpgrade>[0],
|
|
socket as Parameters<typeof server.handleUpgrade>[1],
|
|
head as Buffer,
|
|
),
|
|
{ public: true },
|
|
);
|
|
|
|
for (const protocol of ["rdp", "vnc", "telnet"] as const) {
|
|
ctx.services.provide(
|
|
"sessions.live",
|
|
createLiveSessions(protocol, {
|
|
sessions,
|
|
tokens,
|
|
endSession: server.endSession,
|
|
}),
|
|
{ name: protocol },
|
|
);
|
|
}
|
|
|
|
ctx.registry.provide(
|
|
"remote-desktop.hostImportNormalizer",
|
|
normalizeImportedHost,
|
|
);
|
|
ctx.registry.provide("remote-desktop.hostPayloadLegacy", hostPayloadLegacy);
|
|
|
|
// Core's status dot pings these hosts on the protocol's own port.
|
|
for (const protocol of ["rdp", "vnc", "telnet"] as const) {
|
|
ctx.hosts.status.registerPort(protocol, async (hostId) => {
|
|
const settings = await readHostSettings(ctx, hostId);
|
|
return settings[PORT_KEY[protocol]] as number;
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function deactivate() {}
|