UNPKG

the-wireguard-effect

Version:

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

232 lines (231 loc) 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeUserspaceLayer = exports.makeBundledWgQuickLayer = exports.WireguardControl = exports.UserspaceLayer = exports.TypeId = exports.BundledWgQuickLayer = void 0; var Command = _interopRequireWildcard(require("@effect/platform/Command")); var CommandExecutor = _interopRequireWildcard(require("@effect/platform/CommandExecutor")); var PlatformError = _interopRequireWildcard(require("@effect/platform/Error")); var FileSystem = _interopRequireWildcard(require("@effect/platform/FileSystem")); var Path = _interopRequireWildcard(require("@effect/platform/Path")); var Array = _interopRequireWildcard(require("effect/Array")); var Chunk = _interopRequireWildcard(require("effect/Chunk")); var Context = _interopRequireWildcard(require("effect/Context")); var Effect = _interopRequireWildcard(require("effect/Effect")); var Function = _interopRequireWildcard(require("effect/Function")); var Layer = _interopRequireWildcard(require("effect/Layer")); var Predicate = _interopRequireWildcard(require("effect/Predicate")); var Schedule = _interopRequireWildcard(require("effect/Schedule")); var Stream = _interopRequireWildcard(require("effect/Stream")); var String = _interopRequireWildcard(require("effect/String")); var Tuple = _interopRequireWildcard(require("effect/Tuple")); var exec = _interopRequireWildcard(require("node:child_process")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /** @internal */ const TypeId = exports.TypeId = /*#__PURE__*/Symbol.for("@leonitousconforti/the-wireguard-effect/WireguardControl"); /** @internal */ const WireguardControl = exports.WireguardControl = /*#__PURE__*/Context.GenericTag("@leonitousconforti/the-wireguard-effect/WireguardControl"); /** * @since 1.0.0 * @category Constructors */ const makeUserspaceLayer = () => { const up = (wireguardConfig, wireguardInterface) => wireguardInterface.setConfig(wireguardConfig); const down = (_wireguardConfig, wireguardInterface) => Effect.map(Effect.flatMap(FileSystem.FileSystem, fs => fs.remove(wireguardInterface.SocketLocation)), () => 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 */ exports.makeUserspaceLayer = makeUserspaceLayer; const makeBundledWgQuickLayer = options => { // process.platform === "win32" && command.includes("-wireguard-go.exe") const execCommandWireguardGoWindows = (command, ...args) => Effect.asyncEffect(resume => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const stdout = yield* fileSystem.makeTempFile(); const stderr = yield* fileSystem.makeTempFile(); const { fd: stdoutFd } = yield* fileSystem.open(stdout, { flag: "r+" }); const { fd: stderrFd } = yield* fileSystem.open(stderr, { flag: "r+" }); const subprocess = exec.spawn(command, args, { detached: true, stdio: ["ignore", stdoutFd, stderrFd] }); subprocess.unref(); subprocess.on("exit", onExit); subprocess.on("close", onClose); subprocess.on("error", onError); subprocess.on("disconnect", onDisconnect); yield* Function.pipe(fileSystem.stream(stdout), Stream.decodeText("utf-8"), Stream.splitLines, Stream.takeUntil(String.includes("UAPI listener started")), Stream.runDrain); subprocess.off("exit", onExit); subprocess.off("close", onClose); subprocess.off("error", onError); subprocess.off("disconnect", onDisconnect); resume(Effect.succeed(subprocess)); function onError(error) { subprocess.off("exit", onExit); subprocess.off("close", onClose); subprocess.off("error", onError); subprocess.off("disconnect", onDisconnect); resume(Effect.gen(function* () { const stdoutString = yield* fileSystem.readFileString(stdout, "utf-8"); const stderrString = yield* fileSystem.readFileString(stderr, "utf-8"); return yield* Effect.fail(PlatformError.SystemError({ reason: "Unknown", module: "Command", method: "wireguard-go.exe", pathOrDescriptor: command, message: `${error.message}\n${stdoutString}\n${stderrString}` })); })); } function onExit(code) { if (Predicate.isNotNull(code)) { return onError(new Error(`Process exited unexpectedly with code ${code}`)); } else { return onError(new Error("Process exited unexpectedly.")); } } function onClose(code) { if (Predicate.isNotNull(code)) { return onError(new Error(`Process closed unexpectedly with code ${code}`)); } else { return onError(new Error("Process closed unexpectedly.")); } } function onDisconnect() { return onError(new Error("Process disconnected unexpectedly.")); } return Effect.sync(() => { subprocess.off("exit", onExit); subprocess.off("close", onClose); subprocess.off("error", onError); subprocess.off("disconnect", onDisconnect); }); }).pipe(Effect.scoped)).pipe(Effect.timeout("1 minutes")); const execCommand = (command, ...args) => Function.pipe(CommandExecutor.CommandExecutor, Effect.flatMap(executor => executor.start(options.sudo && process.platform !== "win32" ? Command.make("sudo", command, ...args) : Command.make(command, ...args))), Effect.flatMap(process => Effect.all([process.stdout.pipe(Stream.decodeText("utf-8"), Stream.splitLines, Stream.runCollect, Effect.map(Chunk.toReadonlyArray)), process.stderr.pipe(Stream.decodeText("utf-8"), Stream.splitLines, Stream.runCollect, Effect.map(Chunk.toReadonlyArray)), process.exitCode], { concurrency: 3 })), Effect.flatMap(([stdout, stderr, exitCode]) => { return exitCode === 0 ? Effect.void : Effect.fail(PlatformError.SystemError({ reason: "Unknown", module: "Command", pathOrDescriptor: command, method: `${command} ${args.join(" ")}`, message: `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 path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; const tempDirectory = yield* fs.makeTempDirectory(); const file = path.join(tempDirectory, `${wireguardInterface.Name}.conf`); yield* wireguardConfig.writeToFile(file); 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.compose(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* () { const path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; if (wireguardGoSubprocess) wireguardGoSubprocess.kill(); const tempDirectory = yield* fs.makeTempDirectory(); const file = path.join(tempDirectory, `${wireguardInterface.Name}.conf`); yield* wireguardConfig.writeToFile(file); 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 = Tuple.getSecond(data); 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 */ exports.makeBundledWgQuickLayer = makeBundledWgQuickLayer; const UserspaceLayer = exports.UserspaceLayer = /*#__PURE__*/Layer.sync(WireguardControl, makeUserspaceLayer); /** * @since 1.0.0 * @category Layers */ const BundledWgQuickLayer = exports.BundledWgQuickLayer = /*#__PURE__*/Layer.sync(WireguardControl, () => makeBundledWgQuickLayer({ sudo: true })); //# sourceMappingURL=wireguardControl.js.map