mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
feat: convert wake-on-lan into a plugin (B20)
This commit is contained in:
1 parent
d4ec444558
commit
2ac7779eda
50 files changed
+17844
-199
No files matched your search
@@ -0,0 +1,15 @@
|
||||
import type { Router } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { createWakeOnLanService } from "./service.js";
|
||||
import { registerWakeOnLanRoutes } from "./routes.js";
|
||||
|
||||
export async function activate(ctx: PluginContext) {
|
||||
const service = createWakeOnLanService(ctx);
|
||||
ctx.services.provide("wake-on-lan.send", service);
|
||||
registerWakeOnLanRoutes(ctx.http.router<Router>(), ctx, service);
|
||||
ctx.log.info("Wake-on-LAN routes mounted at /plugin-api/wake-on-lan");
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
// Everything above was registered through ctx and is disposed by core.
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import dgram from "dgram";
|
||||
|
||||
const MAC_REGEX = /^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$/;
|
||||
|
||||
function parseMac(mac: string): Buffer {
|
||||
return Buffer.from(mac.replace(/[:-]/g, ""), "hex");
|
||||
}
|
||||
|
||||
export function buildMagicPacket(mac: string): Buffer {
|
||||
const macBytes = parseMac(mac);
|
||||
const packet = Buffer.alloc(102);
|
||||
packet.fill(0xff, 0, 6);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
macBytes.copy(packet, 6 + i * 6);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
export function isValidMac(mac: string): boolean {
|
||||
return MAC_REGEX.test(mac);
|
||||
}
|
||||
|
||||
export function sendMagicPacket(
|
||||
mac: string,
|
||||
broadcastAddress = "255.255.255.255",
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isValidMac(mac)) {
|
||||
return reject(new Error("Invalid MAC address"));
|
||||
}
|
||||
|
||||
const packet = buildMagicPacket(mac);
|
||||
const socket = dgram.createSocket("udp4");
|
||||
|
||||
socket.once("error", (err) => {
|
||||
socket.close();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
socket.bind(() => {
|
||||
socket.setBroadcast(true);
|
||||
socket.send(packet, 0, packet.length, 9, broadcastAddress, (err) => {
|
||||
socket.close();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Request, Response, Router } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import type { WakeOnLanV1 } from "./service.js";
|
||||
|
||||
export function registerWakeOnLanRoutes(
|
||||
router: Router,
|
||||
ctx: PluginContext,
|
||||
service: WakeOnLanV1,
|
||||
): void {
|
||||
router.use(ctx.rbac.require("send") as never);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/wake-on-lan/host/{id}/wake:
|
||||
* post:
|
||||
* summary: Send a Wake-on-LAN magic packet to a host
|
||||
* tags:
|
||||
* - Wake-on-LAN
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Packet sent
|
||||
* 400:
|
||||
* description: No valid MAC address configured
|
||||
* 404:
|
||||
* description: Host not found
|
||||
*/
|
||||
router.post("/host/:id/wake", async (req: Request, res: Response) => {
|
||||
const hostId = Number.parseInt(String(req.params.id), 10);
|
||||
if (!Number.isInteger(hostId)) {
|
||||
return res.status(400).json({ error: "Invalid host id" });
|
||||
}
|
||||
try {
|
||||
await service.wake(hostId);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const status = message === "Host not found" ? 404 : 400;
|
||||
res.status(status).json({ error: message });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { isValidMac, sendMagicPacket } from "./magic-packet.js";
|
||||
|
||||
export interface WakeOnLanV1 {
|
||||
wake: (hostId: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export function createWakeOnLanService(ctx: PluginContext): WakeOnLanV1 {
|
||||
return {
|
||||
async wake(hostId: number): Promise<void> {
|
||||
const host = await ctx.hosts.get(hostId);
|
||||
if (!host) {
|
||||
throw new Error("Host not found");
|
||||
}
|
||||
|
||||
const macAddress = await ctx.settings.getHost<string>(
|
||||
hostId,
|
||||
"macAddress",
|
||||
);
|
||||
if (!macAddress || !isValidMac(macAddress)) {
|
||||
throw new Error("No valid MAC address configured");
|
||||
}
|
||||
const broadcastAddress = await ctx.settings.getHost<string>(
|
||||
hostId,
|
||||
"broadcastAddress",
|
||||
);
|
||||
|
||||
await ctx.capabilities.require("network:broadcast");
|
||||
await sendMagicPacket(macAddress, broadcastAddress || undefined);
|
||||
ctx.log.info(`Wake-on-LAN packet sent to host ${hostId}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user