mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
feat: convert ssh tunnels into a plugin
This commit is contained in:
1 parent
c85c1fdb86
commit
1682861853
220 files changed
+36928
-11374
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
import { withSshConnection } from "../ssh.js";
|
||||
import { createCurrentNotificationChannelRepository } from "../../../../../src/backend/database/repositories/factory.js";
|
||||
import { getSnippet, resolveSnippetCommandFor } from "../snippets.js";
|
||||
import { runTunnelAction } from "../tunnels.js";
|
||||
import { sendAutomationNotification } from "../notify.js";
|
||||
import { automationFetch } from "../http.js";
|
||||
import { renderRecord, renderTemplate } from "../template.js";
|
||||
@@ -241,25 +242,15 @@ async function runTunnel(
|
||||
const name = renderTemplate(step.tunnelName, context.template);
|
||||
if (context.dryRun) return ok(`Would ${step.action} tunnel ${name}`);
|
||||
|
||||
try {
|
||||
const manager =
|
||||
await import("../../../../../src/backend/hosts/tunnel/manager.js");
|
||||
const config = manager.tunnelConfigs?.get(name);
|
||||
if (!config) return fail(`Tunnel "${name}" is not configured`);
|
||||
|
||||
if (step.action === "connect") {
|
||||
await manager.connectSSHTunnel(config);
|
||||
return ok(`Tunnel ${name} connected`);
|
||||
}
|
||||
|
||||
// shouldRetry false, otherwise the manager immediately reconnects the
|
||||
// tunnel the automation just asked it to drop.
|
||||
manager.manualDisconnects.add(name);
|
||||
await manager.handleDisconnect(name, config, false);
|
||||
return ok(`Tunnel ${name} disconnected`);
|
||||
} catch (error) {
|
||||
return fail(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
// A disconnect goes through the tunnels plugin's manual stop, which also
|
||||
// holds off its retries, so the tunnel stays down.
|
||||
const result = await runTunnelAction(context.userId, step.action, name);
|
||||
if (!result.ok) return fail(result.error);
|
||||
return ok(
|
||||
step.action === "connect"
|
||||
? `Tunnel ${name} connected`
|
||||
: `Tunnel ${name} disconnected`,
|
||||
);
|
||||
}
|
||||
|
||||
async function runWol(
|
||||
|
||||
@@ -1,17 +1,48 @@
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { setPluginSsh } from "./ssh.js";
|
||||
import { setPluginServices } from "./snippets.js";
|
||||
import { setTunnelServices } from "./tunnels.js";
|
||||
import { startAutomationsService, stopAutomationsService } from "./routes.js";
|
||||
import {
|
||||
startAutomationScheduler,
|
||||
stopAutomationScheduler,
|
||||
} from "./scheduler.js";
|
||||
import { onInternalEvent } from "./triggers.js";
|
||||
|
||||
/** What the tunnels plugin emits when a tunnel drops without being asked to. */
|
||||
interface TunnelDisconnectedEvent {
|
||||
userId?: string;
|
||||
hostId?: number;
|
||||
tunnelName?: string;
|
||||
}
|
||||
|
||||
export async function activate(ctx: PluginContext) {
|
||||
setPluginSsh(ctx.ssh);
|
||||
ctx.disposables.add(() => setPluginSsh(null));
|
||||
setPluginServices(ctx.services);
|
||||
ctx.disposables.add(() => setPluginServices(null));
|
||||
setTunnelServices(ctx.services);
|
||||
ctx.disposables.add(() => setTunnelServices(null));
|
||||
|
||||
// Nothing arrives here while the tunnels plugin is off, which is the whole
|
||||
// of the optional dependency.
|
||||
ctx.events.on("plugin.tunnels.tunnel_disconnected", (payload) => {
|
||||
const event = payload as TunnelDisconnectedEvent;
|
||||
if (!event?.userId) return;
|
||||
void onInternalEvent({
|
||||
event: "tunnel_disconnected",
|
||||
userId: event.userId,
|
||||
hostId: event.hostId,
|
||||
details: { tunnelName: event.tunnelName },
|
||||
}).catch((error: unknown) => {
|
||||
ctx.log.warn(
|
||||
`Tunnel disconnect trigger failed: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
startAutomationsService(
|
||||
// The webhook route authenticates on its own per-automation token rather
|
||||
// than a session, which is the point of an inbound webhook.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { PluginServices } from "@termix/plugin-sdk/backend";
|
||||
|
||||
let current: PluginServices | null = null;
|
||||
|
||||
/** Set in activate, cleared on deactivate. */
|
||||
export function setTunnelServices(services: PluginServices | null): void {
|
||||
current = services;
|
||||
}
|
||||
|
||||
/** The tunnels plugin, as ctx.services.get("tunnels.access") returns it. */
|
||||
interface TunnelsAccess {
|
||||
start: (name: string) => Promise<void>;
|
||||
stop: (name: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface TunnelActionResult {
|
||||
ok: boolean;
|
||||
/** True when the tunnels plugin is off or the user may not use it. */
|
||||
unavailable?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects or disconnects a saved tunnel as userId. Optional: without the
|
||||
* tunnels plugin the step fails with a clear message rather than the plugin
|
||||
* failing to activate.
|
||||
*/
|
||||
export async function runTunnelAction(
|
||||
userId: string,
|
||||
action: "connect" | "disconnect",
|
||||
name: string,
|
||||
): Promise<TunnelActionResult> {
|
||||
if (!current) {
|
||||
return {
|
||||
ok: false,
|
||||
unavailable: true,
|
||||
error: "The tunnels plugin is not available",
|
||||
};
|
||||
}
|
||||
|
||||
let tunnels: TunnelsAccess;
|
||||
try {
|
||||
tunnels = current.get<TunnelsAccess>("tunnels.access", { userId });
|
||||
} catch {
|
||||
return {
|
||||
ok: false,
|
||||
unavailable: true,
|
||||
error: "The tunnels plugin is not available",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
if (action === "connect") await tunnels.start(name);
|
||||
else await tunnels.stop(name);
|
||||
return { ok: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
unavailable: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user