UNPKG

the-wireguard-effect

Version:

Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts

168 lines 8.65 kB
import * as Array from "effect/Array"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Function from "effect/Function"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as PlatformError from "effect/PlatformError"; import * as Schedule from "effect/Schedule"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import * as String from "effect/String"; import * as Tuple from "effect/Tuple"; import * as ChildProcess from "effect/unstable/process/ChildProcess"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; /** @internal */ export const TypeId = /*#__PURE__*/Symbol.for("@leonitousconforti/the-wireguard-effect/WireguardControl"); /** @internal */ export const WireguardControl = /*#__PURE__*/Context.Service("@leonitousconforti/the-wireguard-effect/WireguardControl"); /** * @since 1.0.0 * @category Constructors */ export const makeUserspaceLayer = /*#__PURE__*/Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const up = (wireguardConfig, wireguardInterface) => wireguardInterface.setConfig(wireguardConfig); const down = (_wireguardConfig, wireguardInterface) => fs.remove(wireguardInterface.SocketLocation).pipe(Effect.as(wireguardInterface)); const upScoped = (wireguardConfig, wireguardInterface) => { const _down = down(wireguardConfig, wireguardInterface).pipe(Effect.orDie); const _up = wireguardInterface.setConfig(wireguardConfig); return Effect.acquireRelease(_up, () => _down); }; return WireguardControl.of({ [TypeId]: TypeId, up, down, upScoped }); }); /** * @since 1.0.0 * @category Constructors */ export const makeBundledWgQuickLayer = options => Effect.gen(function* () { const executor = yield* ChildProcessSpawner.ChildProcessSpawner; const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; // process.platform === "win32" && command.includes("-wireguard-go.exe") const execCommandWireguardGoWindows = (command, ...args) => Effect.gen(function* () { const scope = Scope.makeUnsafe(); const subprocess = yield* ChildProcess.make(command, args, { forceKillAfter: "5 seconds", stdin: "ignore", detached: true }).pipe(Scope.provide(scope)); yield* subprocess.unref.pipe(Effect.asVoid); yield* subprocess.all.pipe(Stream.decodeText({ encoding: "utf-8" }), Stream.splitLines, Stream.takeUntil(String.includes("UAPI listener started")), Stream.timeout("1 minutes"), Stream.runDrain); return subprocess; }).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, executor)); const execCommand = (command, ...args) => Function.pipe(executor.spawn(options.sudo && process.platform !== "win32" ? ChildProcess.make("sudo", [command, ...args]) : ChildProcess.make(command, args)), Effect.flatMap(process => Effect.all([process.stdout.pipe(Stream.decodeText({ encoding: "utf-8" }), Stream.splitLines, Stream.runCollect), process.stderr.pipe(Stream.decodeText({ encoding: "utf-8" }), Stream.splitLines, Stream.runCollect), process.exitCode], { concurrency: 3 })), Effect.flatMap(([stdout, stderr, exitCode]) => { return exitCode === 0 ? Effect.void : Effect.fail(new PlatformError.SystemError({ _tag: "Unknown", module: "Command", pathOrDescriptor: command, method: `${command} ${args.join(" ")}`, description: `Process exited with code ${exitCode}: ${stdout.join("\n")}, ${stderr.join("\n")}` })); }), Effect.timeout("10 seconds"), Effect.scoped); const internalUp = (wireguardConfig, wireguardInterface) => Effect.gen(function* () { const tempDirectory = yield* fs.makeTempDirectory(); const file = path.join(tempDirectory, `${wireguardInterface.Name}.conf`); yield* wireguardConfig.writeToFile(file).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path)); const arch = process.arch === "x64" ? "amd64" : process.arch; const extension = process.platform === "win32" ? ".exe" : ""; const wireguardGoUrl = new URL(`../${process.platform}-${arch}-wireguard-go${extension}`, import.meta.url); const bundledWireguardGoExecutablePath = yield* path.fromFileUrl(wireguardGoUrl); yield* fs.access(bundledWireguardGoExecutablePath, { ok: true }); const wgQuickUrl = new URL(`../${process.platform}-wg-quick`, import.meta.url); const bundledWgQuickExecutablePath = yield* path.fromFileUrl(wgQuickUrl); if (process.platform !== "win32") yield* fs.access(bundledWgQuickExecutablePath, { ok: true }); const wgWindowsUrl = new URL(`../win32-${arch}-wireguard.exe`, import.meta.url); const bundledWgWindowsExecutablePath = yield* path.fromFileUrl(wgWindowsUrl); if (process.platform === "win32") yield* fs.access(bundledWgWindowsExecutablePath, { ok: true }); const wgQuickCommandNix = [bundledWgQuickExecutablePath, "up", file]; const wgQuickCommandWin = [bundledWgWindowsExecutablePath, "/installtunnelservice", file]; const wgQuickCommand = process.platform === "win32" ? wgQuickCommandWin : wgQuickCommandNix; if (process.platform === "win32") { const retrySchedule = Schedule.max([Schedule.recurs(5), Schedule.exponential(2_000)]); const runningWireguardGoProcess = yield* execCommandWireguardGoWindows(bundledWireguardGoExecutablePath, wireguardInterface.Name); yield* Effect.retry(wireguardInterface.setConfig(wireguardConfig), retrySchedule); yield* execCommand(wgQuickCommand[0], ...wgQuickCommand.slice(1)); return Tuple.make(wireguardInterface, runningWireguardGoProcess); } else { yield* execCommand(bundledWireguardGoExecutablePath, wireguardInterface.Name); yield* wireguardInterface.setConfig(wireguardConfig); yield* execCommand(wgQuickCommand[0], ...wgQuickCommand.slice(1)); return Tuple.make(wireguardInterface); } }); const down = (wireguardConfig, wireguardInterface, wireguardGoSubprocess) => Effect.gen(function* () { if (wireguardGoSubprocess) yield* wireguardGoSubprocess.kill(); const tempDirectory = yield* fs.makeTempDirectory(); const file = path.join(tempDirectory, `${wireguardInterface.Name}.conf`); yield* wireguardConfig.writeToFile(file).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path)); const arch = process.arch === "x64" ? "amd64" : process.arch; const wgQuickUrl = new URL(`../${process.platform}-wg-quick`, import.meta.url); const bundledWgQuickExecutablePath = yield* path.fromFileUrl(wgQuickUrl); if (process.platform !== "win32") yield* fs.access(bundledWgQuickExecutablePath, { ok: true }); const wgWindowsUrl = new URL(`../win32-${arch}-wireguard.exe`, import.meta.url); const bundledWgWindowsExecutablePath = yield* path.fromFileUrl(wgWindowsUrl); if (process.platform === "win32") yield* fs.access(bundledWgWindowsExecutablePath, { ok: true }); const wgQuickCommandWin = [bundledWgWindowsExecutablePath, "/uninstalltunnelservice", wireguardInterface.Name]; const wgQuickCommandNix = [bundledWgQuickExecutablePath, "down", file]; const wgQuickCommand = process.platform === "win32" ? wgQuickCommandWin : wgQuickCommandNix; yield* execCommand(wgQuickCommand[0], ...wgQuickCommand.slice(1)); return wireguardInterface; }); const upScoped = (wireguardConfig, wireguardInterface) => { const _down = wireguardGoSubprocess => down(wireguardConfig, wireguardInterface, wireguardGoSubprocess).pipe(Effect.orDie); const _up = internalUp(wireguardConfig, wireguardInterface); return Effect.map(Effect.acquireRelease(_up, data => { if (Array.isArray(data) && Tuple.isTupleOf(data, 2)) { const wireguardGoSubprocess = data[1]; return _down(wireguardGoSubprocess); } else { return _down(); } }), data => data[0]); }; const up = (wireguardConfig, wireguardInterface) => Effect.map(internalUp(wireguardConfig, wireguardInterface), data => data[0]); return WireguardControl.of({ [TypeId]: TypeId, up, down, upScoped }); }); /** * @since 1.0.0 * @category Layers */ export const UserspaceLayer = /*#__PURE__*/Layer.effect(WireguardControl, makeUserspaceLayer); /** * @since 1.0.0 * @category Layers */ export const BundledWgQuickLayer = /*#__PURE__*/Layer.effect(WireguardControl, /*#__PURE__*/makeBundledWgQuickLayer({ sudo: true })); //# sourceMappingURL=wireguardControl.js.map