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
JavaScript
;
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