the-wireguard-effect
Version:
Cross platform wireguard api client for nodejs built on wireguard-go with effect-ts
168 lines • 8.65 kB
JavaScript
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