@decentnetwork/lan 0.1.198 → 0.1.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/WINTUN-LICENSE.txt +84 -0
- package/bin/WINTUN-SOURCE.json +10 -0
- package/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/bin/windows-amd64/tun-helper-windows-amd64.exe +0 -0
- package/bin/windows-amd64/wintun.dll +0 -0
- package/bin/windows-arm64/tun-helper-windows-arm64.exe +0 -0
- package/bin/windows-arm64/wintun.dll +0 -0
- package/dist/cli/commands.d.ts +14 -9
- package/dist/cli/commands.js +98 -10
- package/dist/cli/index.js +3 -3
- package/dist/console/console.js +11 -1
- package/dist/daemon/ipc.d.ts +9 -8
- package/dist/daemon/ipc.js +33 -19
- package/dist/daemon/server.js +58 -14
- package/dist/tun/route-manager.d.ts +12 -2
- package/dist/tun/route-manager.js +80 -14
- package/dist/tun/tun-device.d.ts +4 -0
- package/dist/tun/tun-device.js +37 -20
- package/docs/CONFIGURATION.md +2 -2
- package/docs/INSTALL-WINDOWS.md +137 -0
- package/docs/INSTALL.md +14 -22
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Perfect for:
|
|
|
15
15
|
### Prerequisites
|
|
16
16
|
|
|
17
17
|
- **Node.js 20+**
|
|
18
|
-
- **Linux or
|
|
18
|
+
- **Linux, macOS, or native 64-bit Windows** (x64 / ARM64)
|
|
19
19
|
- **Carrier identity** (generate with `agentnet init`)
|
|
20
20
|
|
|
21
21
|
### Installation
|
|
@@ -224,8 +224,8 @@ agentnet openclaw diagnose --target <name>.agentnet
|
|
|
224
224
|
|
|
225
225
|
## Limitations (MVP v0.1)
|
|
226
226
|
|
|
227
|
-
-
|
|
228
|
-
-
|
|
227
|
+
- Native Windows uses Wintun and must run from an Administrator PowerShell
|
|
228
|
+
- Kernel NAT/masquerade is Linux-only; the cross-platform CONNECT exit proxy does not need it
|
|
229
229
|
- No remote desktop protocol support (TBD)
|
|
230
230
|
- No video/media optimization (rely on Carrier relay)
|
|
231
231
|
- Static IPAM (blockchain registry planned for v1.0)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Prebuilt Binaries License
|
|
2
|
+
-------------------------
|
|
3
|
+
|
|
4
|
+
1. DEFINITIONS. "Software" means the precise contents of the "wintun.dll"
|
|
5
|
+
files that are included in the .zip file that contains this document as
|
|
6
|
+
downloaded from wintun.net/builds.
|
|
7
|
+
|
|
8
|
+
2. LICENSE GRANT. WireGuard LLC grants to you a non-exclusive and
|
|
9
|
+
non-transferable right to use Software for lawful purposes under certain
|
|
10
|
+
obligations and limited rights as set forth in this agreement.
|
|
11
|
+
|
|
12
|
+
3. RESTRICTIONS. Software is owned and copyrighted by WireGuard LLC. It is
|
|
13
|
+
licensed, not sold. Title to Software and all associated intellectual
|
|
14
|
+
property rights are retained by WireGuard. You must not:
|
|
15
|
+
a. reverse engineer, decompile, disassemble, extract from, or otherwise
|
|
16
|
+
modify the Software;
|
|
17
|
+
b. modify or create derivative work based upon Software in whole or in
|
|
18
|
+
parts, except insofar as only the API interfaces of the "wintun.h" file
|
|
19
|
+
distributed alongside the Software (the "Permitted API") are used;
|
|
20
|
+
c. remove any proprietary notices, labels, or copyrights from the Software;
|
|
21
|
+
d. resell, redistribute, lease, rent, transfer, sublicense, or otherwise
|
|
22
|
+
transfer rights of the Software without the prior written consent of
|
|
23
|
+
WireGuard LLC, except insofar as the Software is distributed alongside
|
|
24
|
+
other software that uses the Software only via the Permitted API;
|
|
25
|
+
e. use the name of WireGuard LLC, the WireGuard project, the Wintun
|
|
26
|
+
project, or the names of its contributors to endorse or promote products
|
|
27
|
+
derived from the Software without specific prior written consent.
|
|
28
|
+
|
|
29
|
+
4. LIMITED WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF
|
|
30
|
+
ANY KIND. WIREGUARD LLC HEREBY EXCLUDES AND DISCLAIMS ALL IMPLIED OR
|
|
31
|
+
STATUTORY WARRANTIES, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
32
|
+
FOR A PARTICULAR PURPOSE, QUALITY, NON-INFRINGEMENT, TITLE, RESULTS,
|
|
33
|
+
EFFORTS, OR QUIET ENJOYMENT. THERE IS NO WARRANTY THAT THE PRODUCT WILL BE
|
|
34
|
+
ERROR-FREE OR WILL FUNCTION WITHOUT INTERRUPTION. YOU ASSUME THE ENTIRE
|
|
35
|
+
RISK FOR THE RESULTS OBTAINED USING THE PRODUCT. TO THE EXTENT THAT
|
|
36
|
+
WIREGUARD LLC MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW,
|
|
37
|
+
THE SCOPE AND DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER
|
|
38
|
+
SUCH LAW. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
|
|
39
|
+
WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR
|
|
40
|
+
A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE
|
|
41
|
+
EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
|
|
42
|
+
|
|
43
|
+
5. LIMITATION OF LIABILITY. To the extent not prohibited by law, in no event
|
|
44
|
+
WireGuard LLC or any third-party-developer will be liable for any lost
|
|
45
|
+
revenue, profit or data or for special, indirect, consequential, incidental
|
|
46
|
+
or punitive damages, however caused regardless of the theory of liability,
|
|
47
|
+
arising out of or related to the use of or inability to use Software, even
|
|
48
|
+
if WireGuard LLC has been advised of the possibility of such damages.
|
|
49
|
+
Solely you are responsible for determining the appropriateness of using
|
|
50
|
+
Software and accept full responsibility for all risks associated with its
|
|
51
|
+
exercise of rights under this agreement, including but not limited to the
|
|
52
|
+
risks and costs of program errors, compliance with applicable laws, damage
|
|
53
|
+
to or loss of data, programs or equipment, and unavailability or
|
|
54
|
+
interruption of operations. The foregoing limitations will apply even if
|
|
55
|
+
the above stated warranty fails of its essential purpose. You acknowledge,
|
|
56
|
+
that it is in the nature of software that software is complex and not
|
|
57
|
+
completely free of errors. In no event shall WireGuard LLC or any
|
|
58
|
+
third-party-developer be liable to you under any theory for any damages
|
|
59
|
+
suffered by you or any user of Software or for any special, incidental,
|
|
60
|
+
indirect, consequential or similar damages (including without limitation
|
|
61
|
+
damages for loss of business profits, business interruption, loss of
|
|
62
|
+
business information or any other pecuniary loss) arising out of the use or
|
|
63
|
+
inability to use Software, even if WireGuard LLC has been advised of the
|
|
64
|
+
possibility of such damages and regardless of the legal or quitable theory
|
|
65
|
+
(contract, tort, or otherwise) upon which the claim is based.
|
|
66
|
+
|
|
67
|
+
6. TERMINATION. This agreement is affected until terminated. You may
|
|
68
|
+
terminate this agreement at any time. This agreement will terminate
|
|
69
|
+
immediately without notice from WireGuard LLC if you fail to comply with
|
|
70
|
+
the terms and conditions of this agreement. Upon termination, you must
|
|
71
|
+
delete Software and all copies of Software and cease all forms of
|
|
72
|
+
distribution of Software.
|
|
73
|
+
|
|
74
|
+
7. SEVERABILITY. If any provision of this agreement is held to be
|
|
75
|
+
unenforceable, this agreement will remain in effect with the provision
|
|
76
|
+
omitted, unless omission would frustrate the intent of the parties, in
|
|
77
|
+
which case this agreement will immediately terminate.
|
|
78
|
+
|
|
79
|
+
8. RESERVATION OF RIGHTS. All rights not expressly granted in this agreement
|
|
80
|
+
are reserved by WireGuard LLC. For example, WireGuard LLC reserves the
|
|
81
|
+
right at any time to cease development of Software, to alter distribution
|
|
82
|
+
details, features, specifications, capabilities, functions, licensing
|
|
83
|
+
terms, release dates, APIs, ABIs, general availability, or other
|
|
84
|
+
characteristics of the Software.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.14.1",
|
|
3
|
+
"source": "https://www.wintun.net/builds/wintun-0.14.1.zip",
|
|
4
|
+
"sourceSha256": "07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51",
|
|
5
|
+
"files": {
|
|
6
|
+
"bin/windows-amd64/wintun.dll": "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce",
|
|
7
|
+
"bin/windows-arm64/wintun.dll": "f7ba89005544be9d85231a9e0d5f23b2d15b3311667e2dad0debd344918a3f80",
|
|
8
|
+
"bin/WINTUN-LICENSE.txt": "183adac21e7d96c508c8fd34d394b7b6708bc81564ad1bad61ab66143a008cd2"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -461,16 +461,21 @@ export declare function cmdDoraAutofriend(args: {
|
|
|
461
461
|
* doesn't pick up an empty /root/.agentnet.
|
|
462
462
|
* macOS → writes /Library/LaunchDaemons/com.decentlan.agentnet.plist,
|
|
463
463
|
* launchctl load (RunAtLoad + KeepAlive).
|
|
464
|
+
* Windows → writes a PowerShell launcher in the config directory and
|
|
465
|
+
* registers an elevated SYSTEM startup task with schtasks.exe.
|
|
464
466
|
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
* `--uninstall` reverses each
|
|
467
|
+
* Unix services stream stdout/stderr to the journal or
|
|
468
|
+
* /var/log/agentnet.log; Windows logs beside config.yaml.
|
|
469
|
+
* `--uninstall` reverses each platform's installation.
|
|
468
470
|
*
|
|
469
|
-
* Requires root
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
471
|
+
* Requires root on Unix or an Administrator PowerShell on Windows.
|
|
472
|
+
*/
|
|
473
|
+
export declare function windowsServiceLauncherScript(args: {
|
|
474
|
+
nodeBin: string;
|
|
475
|
+
cliEntry: string;
|
|
476
|
+
configDir: string;
|
|
477
|
+
logPath: string;
|
|
478
|
+
}): string;
|
|
474
479
|
export declare function cmdServiceInstall(args: {
|
|
475
480
|
uninstall?: boolean;
|
|
476
481
|
configDir?: string;
|
|
@@ -490,7 +495,7 @@ export declare function cmdServiceStatus(_args: {
|
|
|
490
495
|
* no sudo — use that for picking up an upgrade; use this when the service
|
|
491
496
|
* itself is wedged/stopped and you want launchd/systemd to bring it back.
|
|
492
497
|
*/
|
|
493
|
-
export declare function cmdServiceRestart(
|
|
498
|
+
export declare function cmdServiceRestart(args: {
|
|
494
499
|
configDir?: string;
|
|
495
500
|
}): Promise<void>;
|
|
496
501
|
/**
|
package/dist/cli/commands.js
CHANGED
|
@@ -909,7 +909,7 @@ export async function cmdProxyEnable(args) {
|
|
|
909
909
|
};
|
|
910
910
|
await ConfigLoader.save(config, configPath);
|
|
911
911
|
console.log(`Proxy enabled on port ${config.proxy.port}.`);
|
|
912
|
-
console.log(`Listener will bind to
|
|
912
|
+
console.log(`Listener will bind to this node's runtime AgentNet IP on next 'agentnet up'.`);
|
|
913
913
|
if ((config.proxy.allowHosts ?? []).length === 0) {
|
|
914
914
|
console.log(`(No host allowlist set — any host will be accepted as a CONNECT target.)`);
|
|
915
915
|
console.log(`To restrict: agentnet proxy allow-host '*.binance.com'`);
|
|
@@ -947,7 +947,7 @@ export async function cmdProxyStatus(args) {
|
|
|
947
947
|
return;
|
|
948
948
|
}
|
|
949
949
|
console.log("Proxy: enabled");
|
|
950
|
-
console.log(` listen:
|
|
950
|
+
console.log(` listen: <runtime AgentNet IP>:${proxy.port}`);
|
|
951
951
|
console.log(` protocol: HTTP CONNECT`);
|
|
952
952
|
const allowHosts = proxy.allowHosts ?? [];
|
|
953
953
|
if (allowHosts.length === 0) {
|
|
@@ -2100,16 +2100,27 @@ export async function cmdDoraAutofriend(args) {
|
|
|
2100
2100
|
* doesn't pick up an empty /root/.agentnet.
|
|
2101
2101
|
* macOS → writes /Library/LaunchDaemons/com.decentlan.agentnet.plist,
|
|
2102
2102
|
* launchctl load (RunAtLoad + KeepAlive).
|
|
2103
|
+
* Windows → writes a PowerShell launcher in the config directory and
|
|
2104
|
+
* registers an elevated SYSTEM startup task with schtasks.exe.
|
|
2103
2105
|
*
|
|
2104
|
-
*
|
|
2105
|
-
*
|
|
2106
|
-
* `--uninstall` reverses each
|
|
2106
|
+
* Unix services stream stdout/stderr to the journal or
|
|
2107
|
+
* /var/log/agentnet.log; Windows logs beside config.yaml.
|
|
2108
|
+
* `--uninstall` reverses each platform's installation.
|
|
2107
2109
|
*
|
|
2108
|
-
* Requires root
|
|
2109
|
-
* unit will run as User=root; on macOS the LaunchDaemon already runs
|
|
2110
|
-
* as root. The CLI complains loudly with the missing-sudo hint if
|
|
2111
|
-
* the writes fail with EACCES.
|
|
2110
|
+
* Requires root on Unix or an Administrator PowerShell on Windows.
|
|
2112
2111
|
*/
|
|
2112
|
+
export function windowsServiceLauncherScript(args) {
|
|
2113
|
+
const psq = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
2114
|
+
return `$ErrorActionPreference = 'Continue'
|
|
2115
|
+
$env:AGENTNET_SERVICE_WRAPPER = '1'
|
|
2116
|
+
while ($true) {
|
|
2117
|
+
& ${psq(args.nodeBin)} ${psq(args.cliEntry)} up --real-tun --config-dir ${psq(args.configDir)} *>> ${psq(args.logPath)}
|
|
2118
|
+
$code = $LASTEXITCODE
|
|
2119
|
+
"$(Get-Date -Format o) agentnet exited with code $code; restarting in 5 seconds" | Out-File -FilePath ${psq(args.logPath)} -Append -Encoding utf8
|
|
2120
|
+
Start-Sleep -Seconds 5
|
|
2121
|
+
}
|
|
2122
|
+
`;
|
|
2123
|
+
}
|
|
2113
2124
|
export async function cmdServiceInstall(args) {
|
|
2114
2125
|
// Detect sudo's $HOME=/root trap. When `service install` is run via
|
|
2115
2126
|
// sudo and no --config-dir is provided, derive the home dir from
|
|
@@ -2150,6 +2161,46 @@ export async function cmdServiceInstall(args) {
|
|
|
2150
2161
|
const nodeBin = process.execPath;
|
|
2151
2162
|
const nodeDir = dirname(nodeBin);
|
|
2152
2163
|
const servicePath = `${nodeDir}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`;
|
|
2164
|
+
if (process.platform === "win32") {
|
|
2165
|
+
const taskName = "Decent AgentNet";
|
|
2166
|
+
const scriptPath = resolve(dir, "agentnet-service.ps1");
|
|
2167
|
+
const logPath = resolve(dir, "agentnet-service.log");
|
|
2168
|
+
if (args.uninstall) {
|
|
2169
|
+
spawnSync("schtasks.exe", ["/End", "/TN", taskName], { stdio: "ignore" });
|
|
2170
|
+
const removed = spawnSync("schtasks.exe", ["/Delete", "/F", "/TN", taskName], { stdio: "inherit" });
|
|
2171
|
+
try {
|
|
2172
|
+
const { unlinkSync, existsSync } = await import("fs");
|
|
2173
|
+
if (existsSync(scriptPath))
|
|
2174
|
+
unlinkSync(scriptPath);
|
|
2175
|
+
}
|
|
2176
|
+
catch {
|
|
2177
|
+
// Task removal is the important part; script cleanup is best effort.
|
|
2178
|
+
}
|
|
2179
|
+
if ((removed.status ?? 1) !== 0)
|
|
2180
|
+
throw new Error(`Could not remove Windows scheduled task '${taskName}'`);
|
|
2181
|
+
console.log(`Removed Windows startup task '${taskName}'.`);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const cliEntry = resolve(dirname(fileURLToPath(import.meta.url)), "index.js");
|
|
2185
|
+
const psq = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
2186
|
+
const script = windowsServiceLauncherScript({ nodeBin, cliEntry, configDir: dir, logPath });
|
|
2187
|
+
const fs = await import("fs/promises");
|
|
2188
|
+
await fs.mkdir(dir, { recursive: true });
|
|
2189
|
+
await fs.writeFile(scriptPath, script, "utf-8");
|
|
2190
|
+
const taskCommand = `powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${scriptPath}"`;
|
|
2191
|
+
const created = spawnSync("schtasks.exe", [
|
|
2192
|
+
"/Create", "/F", "/TN", taskName, "/SC", "ONSTART", "/RU", "SYSTEM", "/RL", "HIGHEST", "/TR", taskCommand,
|
|
2193
|
+
], { stdio: "inherit" });
|
|
2194
|
+
if ((created.status ?? 1) !== 0) {
|
|
2195
|
+
throw new Error("Could not create the AgentNet Windows startup task. Open PowerShell as Administrator and retry.");
|
|
2196
|
+
}
|
|
2197
|
+
const started = spawnSync("schtasks.exe", ["/Run", "/TN", taskName], { stdio: "inherit" });
|
|
2198
|
+
if ((started.status ?? 1) !== 0)
|
|
2199
|
+
throw new Error(`Created '${taskName}' but could not start it`);
|
|
2200
|
+
console.log(`Installed and started Windows startup task '${taskName}'.`);
|
|
2201
|
+
console.log(`Logs: Get-Content -Wait ${psq(logPath)}`);
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2153
2204
|
if (process.platform === "linux") {
|
|
2154
2205
|
const unitPath = "/etc/systemd/system/agentnet.service";
|
|
2155
2206
|
if (args.uninstall) {
|
|
@@ -2303,6 +2354,15 @@ export async function cmdServiceStatus(_args) {
|
|
|
2303
2354
|
process.stdout.write(r.stdout || "");
|
|
2304
2355
|
return;
|
|
2305
2356
|
}
|
|
2357
|
+
if (process.platform === "win32") {
|
|
2358
|
+
const r = spawnSync("schtasks.exe", ["/Query", "/TN", "Decent AgentNet", "/V", "/FO", "LIST"], { encoding: "utf-8" });
|
|
2359
|
+
if ((r.status ?? 0) !== 0) {
|
|
2360
|
+
console.log("Not installed. Open PowerShell as Administrator and run 'agentnet service install'.");
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
process.stdout.write(r.stdout || "");
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2306
2366
|
console.log(`'service status' isn't wired up for ${process.platform}.`);
|
|
2307
2367
|
}
|
|
2308
2368
|
/**
|
|
@@ -2312,7 +2372,7 @@ export async function cmdServiceStatus(_args) {
|
|
|
2312
2372
|
* no sudo — use that for picking up an upgrade; use this when the service
|
|
2313
2373
|
* itself is wedged/stopped and you want launchd/systemd to bring it back.
|
|
2314
2374
|
*/
|
|
2315
|
-
export async function cmdServiceRestart(
|
|
2375
|
+
export async function cmdServiceRestart(args) {
|
|
2316
2376
|
const { spawnSync } = await import("child_process");
|
|
2317
2377
|
if (process.platform === "linux") {
|
|
2318
2378
|
const r = spawnSync("systemctl", ["restart", "agentnet"], { stdio: "inherit" });
|
|
@@ -2344,6 +2404,34 @@ export async function cmdServiceRestart(_args) {
|
|
|
2344
2404
|
console.log(`Restarted ${label}. Logs: tail -f /var/log/agentnet.log`);
|
|
2345
2405
|
return;
|
|
2346
2406
|
}
|
|
2407
|
+
if (process.platform === "win32") {
|
|
2408
|
+
// Prefer the daemon's graceful restart path: it closes Carrier sessions,
|
|
2409
|
+
// stdin-signals the Wintun helper, releases IPC/routes, and then exits.
|
|
2410
|
+
// The scheduled-task wrapper notices the exit and relaunches it. A raw
|
|
2411
|
+
// schtasks /End is only the recovery fallback when IPC is unavailable.
|
|
2412
|
+
try {
|
|
2413
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
2414
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
2415
|
+
if (daemonPid(config) !== null) {
|
|
2416
|
+
const res = await ipcCall(config, { op: "self-restart" });
|
|
2417
|
+
if (res.ok) {
|
|
2418
|
+
console.log(`Graceful restart requested for '${"Decent AgentNet"}'.`);
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
catch {
|
|
2424
|
+
// No reachable daemon: fall through and recover the scheduled task.
|
|
2425
|
+
}
|
|
2426
|
+
const taskName = "Decent AgentNet";
|
|
2427
|
+
spawnSync("schtasks.exe", ["/End", "/TN", taskName], { stdio: "ignore" });
|
|
2428
|
+
const run = spawnSync("schtasks.exe", ["/Run", "/TN", taskName], { stdio: "inherit" });
|
|
2429
|
+
if ((run.status ?? 1) !== 0) {
|
|
2430
|
+
throw new Error(`Could not restart '${taskName}'. Open PowerShell as Administrator and run 'agentnet service install'.`);
|
|
2431
|
+
}
|
|
2432
|
+
console.log(`Restarted '${taskName}'.`);
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2347
2435
|
throw new Error(`'service restart' isn't wired up for ${process.platform}.`);
|
|
2348
2436
|
}
|
|
2349
2437
|
/**
|
package/dist/cli/index.js
CHANGED
|
@@ -116,10 +116,10 @@ async function main() {
|
|
|
116
116
|
.demandCommand(1, "Specify a dns subcommand (run 'agentnet dns --help')"), () => {
|
|
117
117
|
// parent handler — never invoked because demandCommand
|
|
118
118
|
})
|
|
119
|
-
// Persistent system service — wraps systemctl (Linux)
|
|
120
|
-
//
|
|
119
|
+
// Persistent system service — wraps systemctl (Linux), launchctl
|
|
120
|
+
// (macOS), or a highest-privilege startup task (Windows) so a new operator runs ONE command to install
|
|
121
121
|
// + start + persist instead of hand-writing a unit/plist.
|
|
122
|
-
.command("service", "Install/uninstall/inspect the daemon as a system service
|
|
122
|
+
.command("service", "Install/uninstall/inspect the daemon as a system service/startup task", (y) => y
|
|
123
123
|
.command("install", "Write the unit/plist, enable + start (needs sudo)", (yy) => yy
|
|
124
124
|
.option("uninstall", { type: "boolean", default: false, describe: "Reverse the install" })
|
|
125
125
|
.option("config-dir", { type: "string" }), async (argv) => {
|
package/dist/console/console.js
CHANGED
|
@@ -881,6 +881,16 @@ var ConfigLoader = class {
|
|
|
881
881
|
}
|
|
882
882
|
};
|
|
883
883
|
|
|
884
|
+
// src/daemon/ipc.ts
|
|
885
|
+
import { createHash } from "crypto";
|
|
886
|
+
function ipcSocketPath(dataDir, platform = process.platform) {
|
|
887
|
+
if (platform === "win32") {
|
|
888
|
+
const id = createHash("sha256").update(dataDir.toLowerCase()).digest("hex").slice(0, 16);
|
|
889
|
+
return `\\\\.\\pipe\\agentnet-${id}`;
|
|
890
|
+
}
|
|
891
|
+
return `${dataDir.replace(/\/+$/, "")}/daemon.sock`;
|
|
892
|
+
}
|
|
893
|
+
|
|
884
894
|
// src/console/data.ts
|
|
885
895
|
function fileSize(n) {
|
|
886
896
|
if (!n || n < 0) return "0 B";
|
|
@@ -910,7 +920,7 @@ var DaemonClient = class _DaemonClient {
|
|
|
910
920
|
const dir = configDir ?? resolve2(homedir3(), ".agentnet");
|
|
911
921
|
const config = await ConfigLoader.load(resolve2(dir, "config.yaml"));
|
|
912
922
|
const dataDir = (config.carrier.dataDir || resolve2(dir, "carrier")).replace(/\/+$/, "");
|
|
913
|
-
return new _DaemonClient(
|
|
923
|
+
return new _DaemonClient(ipcSocketPath(dataDir));
|
|
914
924
|
}
|
|
915
925
|
call(req, timeoutMs = 5e3) {
|
|
916
926
|
return new Promise((res, rej) => {
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IPC server —
|
|
2
|
+
* IPC server — local-socket bridge so the CLI can drive the
|
|
3
3
|
* running daemon's Carrier identity without spawning a second Peer.
|
|
4
4
|
*
|
|
5
5
|
* Why: the daemon owns the keypair while it's up. A `node dist/cli
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* response, then both sides close. Keeping it one-shot avoids
|
|
16
16
|
* dealing with multiplexing or reconnect logic for v0.1.
|
|
17
17
|
*
|
|
18
|
-
* Socket location: <dataDir>/daemon.sock
|
|
19
|
-
*
|
|
20
|
-
* connect when the daemon ran via sudo (otherwise
|
|
21
|
-
* root-owned). That's acceptable for a single-user box;
|
|
22
|
-
* hardening can come later via
|
|
18
|
+
* Socket location: <dataDir>/daemon.sock on Unix; a stable named pipe
|
|
19
|
+
* derived from dataDir on Windows. Unix permissions are loosened to 0666
|
|
20
|
+
* so the user's CLI can connect when the daemon ran via sudo (otherwise
|
|
21
|
+
* the socket is root-owned). That's acceptable for a single-user box;
|
|
22
|
+
* multi-user hardening can come later via peer-credential filtering.
|
|
23
23
|
*/
|
|
24
24
|
export interface IpcHandlers {
|
|
25
25
|
/** Send a friend-request via the daemon's Carrier peer. Returns
|
|
@@ -63,7 +63,7 @@ export interface IpcHandlers {
|
|
|
63
63
|
/** Sign `text` (UTF-8) with this node's Carrier identity key and return the
|
|
64
64
|
* detached signature (hex) plus our userid. Backs "Sign in with Decent":
|
|
65
65
|
* the UI server's local-only /connect flow calls this on Approve. Reachable
|
|
66
|
-
* ONLY over this
|
|
66
|
+
* ONLY over this local socket/pipe — never exposed on a TCP interface. */
|
|
67
67
|
sign: (text: string) => Promise<{
|
|
68
68
|
sig: string;
|
|
69
69
|
userid: string;
|
|
@@ -147,9 +147,10 @@ export declare class IpcServer {
|
|
|
147
147
|
constructor(socketPath: string, handlers: IpcHandlers);
|
|
148
148
|
start(): Promise<void>;
|
|
149
149
|
stop(): Promise<void>;
|
|
150
|
+
private isWindowsNamedPipe;
|
|
150
151
|
private handleConnection;
|
|
151
152
|
private dispatch;
|
|
152
153
|
}
|
|
153
154
|
/** Derive the socket path that pairs with a given carrier data dir.
|
|
154
155
|
* Kept as a one-liner helper so daemon and client agree. */
|
|
155
|
-
export declare function ipcSocketPath(dataDir: string): string;
|
|
156
|
+
export declare function ipcSocketPath(dataDir: string, platform?: NodeJS.Platform): string;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IPC server —
|
|
2
|
+
* IPC server — local-socket bridge so the CLI can drive the
|
|
3
3
|
* running daemon's Carrier identity without spawning a second Peer.
|
|
4
4
|
*
|
|
5
5
|
* Why: the daemon owns the keypair while it's up. A `node dist/cli
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
* response, then both sides close. Keeping it one-shot avoids
|
|
16
16
|
* dealing with multiplexing or reconnect logic for v0.1.
|
|
17
17
|
*
|
|
18
|
-
* Socket location: <dataDir>/daemon.sock
|
|
19
|
-
*
|
|
20
|
-
* connect when the daemon ran via sudo (otherwise
|
|
21
|
-
* root-owned). That's acceptable for a single-user box;
|
|
22
|
-
* hardening can come later via
|
|
18
|
+
* Socket location: <dataDir>/daemon.sock on Unix; a stable named pipe
|
|
19
|
+
* derived from dataDir on Windows. Unix permissions are loosened to 0666
|
|
20
|
+
* so the user's CLI can connect when the daemon ran via sudo (otherwise
|
|
21
|
+
* the socket is root-owned). That's acceptable for a single-user box;
|
|
22
|
+
* multi-user hardening can come later via peer-credential filtering.
|
|
23
23
|
*/
|
|
24
24
|
import { createServer } from "net";
|
|
25
25
|
import { chmodSync, existsSync, unlinkSync } from "fs";
|
|
26
|
+
import { createHash } from "crypto";
|
|
26
27
|
import { Logger } from "../utils/logger.js";
|
|
27
28
|
export class IpcServer {
|
|
28
29
|
socketPath;
|
|
@@ -38,7 +39,7 @@ export class IpcServer {
|
|
|
38
39
|
// A stale socket from a previously-crashed daemon will make
|
|
39
40
|
// listen() throw EADDRINUSE — unlink it first. Pidfile-guard
|
|
40
41
|
// already ensures no LIVE daemon is running, so deletion is safe.
|
|
41
|
-
if (existsSync(this.socketPath)) {
|
|
42
|
+
if (!this.isWindowsNamedPipe() && existsSync(this.socketPath)) {
|
|
42
43
|
try {
|
|
43
44
|
unlinkSync(this.socketPath);
|
|
44
45
|
}
|
|
@@ -57,11 +58,13 @@ export class IpcServer {
|
|
|
57
58
|
// 0666 so non-root CLI can reach a sudo-launched daemon. SO_PEERCRED
|
|
58
59
|
// / SO_PEEREID would be a tighter filter, but for v0.1 the trust
|
|
59
60
|
// boundary is "anyone with shell access to this box".
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
if (!this.isWindowsNamedPipe()) {
|
|
62
|
+
try {
|
|
63
|
+
chmodSync(this.socketPath, 0o666);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
this.logger.warn(`chmod ${this.socketPath}: ${err}`);
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
69
|
this.logger.info(`Listening on ${this.socketPath}`);
|
|
67
70
|
}
|
|
@@ -72,14 +75,19 @@ export class IpcServer {
|
|
|
72
75
|
this.server.close(() => resolve());
|
|
73
76
|
});
|
|
74
77
|
this.server = undefined;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (!this.isWindowsNamedPipe()) {
|
|
79
|
+
try {
|
|
80
|
+
if (existsSync(this.socketPath))
|
|
81
|
+
unlinkSync(this.socketPath);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// best-effort
|
|
85
|
+
}
|
|
81
86
|
}
|
|
82
87
|
}
|
|
88
|
+
isWindowsNamedPipe() {
|
|
89
|
+
return this.socketPath.startsWith("\\\\.\\pipe\\");
|
|
90
|
+
}
|
|
83
91
|
handleConnection(sock) {
|
|
84
92
|
let buf = "";
|
|
85
93
|
let handled = false;
|
|
@@ -266,7 +274,13 @@ export class IpcServer {
|
|
|
266
274
|
}
|
|
267
275
|
/** Derive the socket path that pairs with a given carrier data dir.
|
|
268
276
|
* Kept as a one-liner helper so daemon and client agree. */
|
|
269
|
-
export function ipcSocketPath(dataDir) {
|
|
277
|
+
export function ipcSocketPath(dataDir, platform = process.platform) {
|
|
278
|
+
if (platform === "win32") {
|
|
279
|
+
// Named pipes are the Windows equivalent of Unix-domain sockets. Hash the
|
|
280
|
+
// configured data directory so multiple AgentNet profiles do not collide.
|
|
281
|
+
const id = createHash("sha256").update(dataDir.toLowerCase()).digest("hex").slice(0, 16);
|
|
282
|
+
return `\\\\.\\pipe\\agentnet-${id}`;
|
|
283
|
+
}
|
|
270
284
|
// Node's net.listen on Unix sockets supports paths up to ~104 bytes
|
|
271
285
|
// on macOS; <dataDir>/daemon.sock fits comfortably for sane homedirs.
|
|
272
286
|
return `${dataDir.replace(/\/+$/, "")}/daemon.sock`;
|
package/dist/daemon/server.js
CHANGED
|
@@ -690,17 +690,34 @@ export class DaemonServer {
|
|
|
690
690
|
this.logger.error(`Self-restart cleanup failed: ${err instanceof Error ? err.message : err}`);
|
|
691
691
|
}
|
|
692
692
|
try {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
693
|
+
if (process.platform === "win32") {
|
|
694
|
+
// A scheduled-task installation has a PowerShell supervisor
|
|
695
|
+
// that relaunches us after exit; spawning here as well would
|
|
696
|
+
// create two daemons sharing one Carrier identity. Foreground
|
|
697
|
+
// Windows runs still need a detached replacement.
|
|
698
|
+
if (process.env.AGENTNET_SERVICE_WRAPPER !== "1") {
|
|
699
|
+
const child = spawn(node, argv, {
|
|
700
|
+
detached: true,
|
|
701
|
+
stdio: "ignore",
|
|
702
|
+
env: process.env,
|
|
703
|
+
windowsHide: true,
|
|
704
|
+
});
|
|
705
|
+
child.unref();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
// sh -c so we can shell-quote argv safely and use sleep.
|
|
710
|
+
// Node's detached flag reparents the relauncher to PID 1.
|
|
711
|
+
// At this point stop() has released IPC, TUN and the pidfile;
|
|
712
|
+
// the extra delay also lets relay writes flush remotely.
|
|
713
|
+
const cmd = `sleep 1 && exec ${shellQuote([node, ...argv])}`;
|
|
714
|
+
const child = spawn("sh", ["-c", cmd], {
|
|
715
|
+
detached: true,
|
|
716
|
+
stdio: "ignore",
|
|
717
|
+
env: process.env,
|
|
718
|
+
});
|
|
719
|
+
child.unref();
|
|
720
|
+
}
|
|
704
721
|
}
|
|
705
722
|
catch (err) {
|
|
706
723
|
this.logger.error(`Self-restart spawn failed: ${err instanceof Error ? err.message : err}`);
|
|
@@ -1151,7 +1168,7 @@ export class DaemonServer {
|
|
|
1151
1168
|
else if (this.config.proxy.egress) {
|
|
1152
1169
|
this.logger.warn(`Proxy egress=${this.config.proxy.egress} requested but no physical NIC address found; using OS default egress`);
|
|
1153
1170
|
}
|
|
1154
|
-
|
|
1171
|
+
const proxyOptions = {
|
|
1155
1172
|
bindIp: tunIp,
|
|
1156
1173
|
port: this.config.proxy.port,
|
|
1157
1174
|
allowHosts: this.config.proxy.allowHosts ?? [],
|
|
@@ -1174,8 +1191,35 @@ export class DaemonServer {
|
|
|
1174
1191
|
this.auditLog.logProxyClose({ srcIp: src, srcName, target, bytesTransferred });
|
|
1175
1192
|
}
|
|
1176
1193
|
: undefined,
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1194
|
+
};
|
|
1195
|
+
// Windows reports New-NetIPAddress success slightly before Winsock
|
|
1196
|
+
// accepts bind() on that address. A one-shot start therefore races
|
|
1197
|
+
// with Wintun configuration and fails EADDRNOTAVAIL even though the
|
|
1198
|
+
// address becomes usable moments later. Retry only that transient
|
|
1199
|
+
// error; EADDRINUSE and policy/config errors still fail fast.
|
|
1200
|
+
const maxAttempts = process.platform === "win32" ? 40 : 1;
|
|
1201
|
+
let bindError;
|
|
1202
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1203
|
+
const candidate = new ConnectProxy(proxyOptions);
|
|
1204
|
+
try {
|
|
1205
|
+
await candidate.start();
|
|
1206
|
+
this.connectProxy = candidate;
|
|
1207
|
+
bindError = undefined;
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
catch (err) {
|
|
1211
|
+
bindError = err;
|
|
1212
|
+
const code = err.code;
|
|
1213
|
+
if (code !== "EADDRNOTAVAIL" || attempt === maxAttempts)
|
|
1214
|
+
break;
|
|
1215
|
+
if (attempt === 1 || attempt % 10 === 0) {
|
|
1216
|
+
this.logger.warn(`Proxy address ${tunIp} not bindable yet; retrying (${attempt}/${maxAttempts})`);
|
|
1217
|
+
}
|
|
1218
|
+
await new Promise((resolveRetry) => setTimeout(resolveRetry, 250));
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
if (!this.connectProxy)
|
|
1222
|
+
throw bindError;
|
|
1179
1223
|
this.logger.info(`Proxy listening on ${tunIp}:${this.config.proxy.port}`);
|
|
1180
1224
|
}
|
|
1181
1225
|
catch (err) {
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TUN device + route management.
|
|
3
|
-
* Linux uses `ip` (iproute2)
|
|
3
|
+
* Linux uses `ip` (iproute2), macOS uses `ifconfig` + `route`, and
|
|
4
|
+
* Windows uses the built-in NetTCPIP PowerShell cmdlets.
|
|
4
5
|
*/
|
|
5
6
|
import type { TunDeviceConfig } from "./types.js";
|
|
7
|
+
export type RoutePlatform = "linux" | "darwin" | "win32";
|
|
8
|
+
export interface RouteManagerOptions {
|
|
9
|
+
platform?: RoutePlatform;
|
|
10
|
+
commandRunner?: (command: string) => string;
|
|
11
|
+
}
|
|
6
12
|
export declare class RouteManager {
|
|
7
13
|
private logger;
|
|
8
14
|
private createdInterface;
|
|
9
15
|
private platform;
|
|
10
|
-
|
|
16
|
+
private commandRunner;
|
|
17
|
+
constructor(opts?: RouteManagerOptions);
|
|
11
18
|
/**
|
|
12
19
|
* Create and configure TUN interface (Linux only — on macOS the helper
|
|
13
20
|
* binary creates the utun device implicitly).
|
|
@@ -18,6 +25,7 @@ export declare class RouteManager {
|
|
|
18
25
|
* `name` here should be the ACTUAL device name (e.g. utun12 on macOS).
|
|
19
26
|
*/
|
|
20
27
|
configureTun(config: TunDeviceConfig): Promise<void>;
|
|
28
|
+
private configureTunWindows;
|
|
21
29
|
private configureTunLinux;
|
|
22
30
|
private configureTunDarwin;
|
|
23
31
|
/**
|
|
@@ -43,6 +51,8 @@ export declare class RouteManager {
|
|
|
43
51
|
setupMasquerade(subnet: string, outInterface: string, tunInterface: string): Promise<void>;
|
|
44
52
|
cleanupMasquerade(subnet: string, outInterface: string, tunInterface: string): Promise<void>;
|
|
45
53
|
private exec;
|
|
54
|
+
private powerShell;
|
|
55
|
+
private powerShellQuote;
|
|
46
56
|
/**
|
|
47
57
|
* Convert "10.86.0.0/16" -> 16
|
|
48
58
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TUN device + route management.
|
|
3
|
-
* Linux uses `ip` (iproute2)
|
|
3
|
+
* Linux uses `ip` (iproute2), macOS uses `ifconfig` + `route`, and
|
|
4
|
+
* Windows uses the built-in NetTCPIP PowerShell cmdlets.
|
|
4
5
|
*/
|
|
5
6
|
import { execSync } from "child_process";
|
|
6
7
|
import { Logger } from "../utils/logger.js";
|
|
@@ -8,22 +9,23 @@ export class RouteManager {
|
|
|
8
9
|
logger;
|
|
9
10
|
createdInterface = null;
|
|
10
11
|
platform;
|
|
11
|
-
|
|
12
|
+
commandRunner;
|
|
13
|
+
constructor(opts = {}) {
|
|
12
14
|
this.logger = new Logger({ prefix: "RouteManager" });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
else {
|
|
17
|
-
this.platform = "linux";
|
|
15
|
+
const detected = opts.platform ?? process.platform;
|
|
16
|
+
if (detected !== "linux" && detected !== "darwin" && detected !== "win32") {
|
|
17
|
+
throw new Error(`Unsupported route platform: ${detected}`);
|
|
18
18
|
}
|
|
19
|
+
this.platform = detected;
|
|
20
|
+
this.commandRunner = opts.commandRunner ?? ((command) => execSync(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }));
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Create and configure TUN interface (Linux only — on macOS the helper
|
|
22
24
|
* binary creates the utun device implicitly).
|
|
23
25
|
*/
|
|
24
26
|
async createTun(config) {
|
|
25
|
-
if (this.platform === "darwin") {
|
|
26
|
-
// utun is auto-created by the helper; just configure
|
|
27
|
+
if (this.platform === "darwin" || this.platform === "win32") {
|
|
28
|
+
// utun/Wintun is auto-created by the helper; just configure.
|
|
27
29
|
await this.configureTun(config);
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
@@ -48,8 +50,34 @@ export class RouteManager {
|
|
|
48
50
|
if (this.platform === "darwin") {
|
|
49
51
|
return this.configureTunDarwin(config);
|
|
50
52
|
}
|
|
53
|
+
if (this.platform === "win32") {
|
|
54
|
+
return this.configureTunWindows(config);
|
|
55
|
+
}
|
|
51
56
|
return this.configureTunLinux(config);
|
|
52
57
|
}
|
|
58
|
+
async configureTunWindows(config) {
|
|
59
|
+
const { name, ip, subnet } = config;
|
|
60
|
+
const mtu = config.mtu ?? 900;
|
|
61
|
+
const alias = this.powerShellQuote(name);
|
|
62
|
+
const address = this.powerShellQuote(ip);
|
|
63
|
+
const destination = this.powerShellQuote(subnet);
|
|
64
|
+
// Use a /32 local address and add the AgentNet /16 explicitly. This mirrors
|
|
65
|
+
// the macOS point-to-point setup and avoids Windows creating a competing
|
|
66
|
+
// connected /16 route with an arbitrary metric.
|
|
67
|
+
this.powerShell([
|
|
68
|
+
"$ErrorActionPreference='Stop'",
|
|
69
|
+
`Enable-NetAdapter -Name ${alias} -Confirm:$false -ErrorAction SilentlyContinue`,
|
|
70
|
+
`Get-NetIPAddress -InterfaceAlias ${alias} -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -like '10.86.*' } | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue`,
|
|
71
|
+
`Get-NetRoute -InterfaceAlias ${alias} -DestinationPrefix ${destination} -ErrorAction SilentlyContinue | Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue`,
|
|
72
|
+
`New-NetIPAddress -InterfaceAlias ${alias} -IPAddress ${address} -PrefixLength 32 -AddressFamily IPv4 -PolicyStore ActiveStore | Out-Null`,
|
|
73
|
+
`Set-NetIPInterface -InterfaceAlias ${alias} -AddressFamily IPv4 -NlMtuBytes ${mtu}`,
|
|
74
|
+
`New-NetRoute -DestinationPrefix ${destination} -InterfaceAlias ${alias} -NextHop 0.0.0.0 -RouteMetric 1 -PolicyStore ActiveStore | Out-Null`,
|
|
75
|
+
`Get-NetFirewallRule -Name 'DecentAgentNet-Inbound' -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue`,
|
|
76
|
+
`New-NetFirewallRule -Name 'DecentAgentNet-Inbound' -DisplayName 'Decent AgentNet virtual network' -Direction Inbound -Action Allow -InterfaceAlias ${alias} -Profile Any | Out-Null`,
|
|
77
|
+
].join("; "));
|
|
78
|
+
this.createdInterface = name;
|
|
79
|
+
this.logger.info(`Assigned ${ip}/32 to ${name}, route ${subnet}, mtu=${mtu} (Windows)`);
|
|
80
|
+
}
|
|
53
81
|
async configureTunLinux(config) {
|
|
54
82
|
const { name, ip, subnet } = config;
|
|
55
83
|
const mtu = config.mtu ?? 900;
|
|
@@ -162,6 +190,13 @@ export class RouteManager {
|
|
|
162
190
|
* Remove TUN interface (Linux only — on macOS utun goes away when helper exits).
|
|
163
191
|
*/
|
|
164
192
|
async removeTun(name) {
|
|
193
|
+
if (this.platform === "win32") {
|
|
194
|
+
// Wintun adapters are intentionally persistent and reused by name. The
|
|
195
|
+
// helper ending its session is enough; cleanup() removes IP/routes.
|
|
196
|
+
this.logger.debug(`Windows Wintun adapter ${name} retained for reuse`);
|
|
197
|
+
this.createdInterface = null;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
165
200
|
if (this.platform === "darwin") {
|
|
166
201
|
// utun is auto-destroyed when the helper subprocess exits
|
|
167
202
|
this.logger.debug(`macOS utun ${name} cleaned up by helper exit`);
|
|
@@ -221,6 +256,21 @@ export class RouteManager {
|
|
|
221
256
|
// utun device cleanup is handled when helper exits
|
|
222
257
|
return;
|
|
223
258
|
}
|
|
259
|
+
if (this.platform === "win32") {
|
|
260
|
+
const alias = this.powerShellQuote(name);
|
|
261
|
+
const destination = this.powerShellQuote(subnet);
|
|
262
|
+
const removeIp = ip
|
|
263
|
+
? `Get-NetIPAddress -InterfaceAlias ${alias} -IPAddress ${this.powerShellQuote(ip)} -AddressFamily IPv4 -ErrorAction SilentlyContinue | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue`
|
|
264
|
+
: `Get-NetIPAddress -InterfaceAlias ${alias} -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -like '10.86.*' } | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue`;
|
|
265
|
+
this.powerShell([
|
|
266
|
+
"$ErrorActionPreference='SilentlyContinue'",
|
|
267
|
+
`Get-NetRoute -InterfaceAlias ${alias} -DestinationPrefix ${destination} -ErrorAction SilentlyContinue | Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue`,
|
|
268
|
+
removeIp,
|
|
269
|
+
`Get-NetFirewallRule -Name 'DecentAgentNet-Inbound' -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue`,
|
|
270
|
+
].join("; "));
|
|
271
|
+
this.createdInterface = null;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
224
274
|
try {
|
|
225
275
|
this.exec(`ip route del ${subnet} dev ${name}`);
|
|
226
276
|
}
|
|
@@ -242,6 +292,10 @@ export class RouteManager {
|
|
|
242
292
|
* Enable IP forwarding (Linux only)
|
|
243
293
|
*/
|
|
244
294
|
async enableIpForwarding() {
|
|
295
|
+
if (this.platform === "win32") {
|
|
296
|
+
this.logger.warn("Kernel IP forwarding/NAT is not enabled on Windows; the AgentNet CONNECT exit proxy does not require it");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
245
299
|
if (this.platform === "darwin") {
|
|
246
300
|
try {
|
|
247
301
|
this.exec("sysctl -w net.inet.ip.forwarding=1");
|
|
@@ -264,7 +318,10 @@ export class RouteManager {
|
|
|
264
318
|
}
|
|
265
319
|
async disableIpForwarding() {
|
|
266
320
|
try {
|
|
267
|
-
if (this.platform === "
|
|
321
|
+
if (this.platform === "win32") {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
else if (this.platform === "darwin") {
|
|
268
325
|
this.exec("sysctl -w net.inet.ip.forwarding=0");
|
|
269
326
|
}
|
|
270
327
|
else {
|
|
@@ -281,8 +338,8 @@ export class RouteManager {
|
|
|
281
338
|
* Not implemented on macOS in MVP.
|
|
282
339
|
*/
|
|
283
340
|
async setupMasquerade(subnet, outInterface, tunInterface) {
|
|
284
|
-
if (this.platform === "darwin") {
|
|
285
|
-
this.logger.warn(
|
|
341
|
+
if (this.platform === "darwin" || this.platform === "win32") {
|
|
342
|
+
this.logger.warn(`Masquerade not implemented on ${this.platform} in MVP`);
|
|
286
343
|
return;
|
|
287
344
|
}
|
|
288
345
|
this.exec(`iptables -t nat -A POSTROUTING -o ${outInterface} -j MASQUERADE`);
|
|
@@ -291,7 +348,7 @@ export class RouteManager {
|
|
|
291
348
|
this.logger.info(`Masquerade setup for ${subnet} (${tunInterface} -> ${outInterface})`);
|
|
292
349
|
}
|
|
293
350
|
async cleanupMasquerade(subnet, outInterface, tunInterface) {
|
|
294
|
-
if (this.platform === "darwin")
|
|
351
|
+
if (this.platform === "darwin" || this.platform === "win32")
|
|
295
352
|
return;
|
|
296
353
|
for (const cmd of [
|
|
297
354
|
`iptables -t nat -D POSTROUTING -o ${outInterface} -j MASQUERADE`,
|
|
@@ -310,13 +367,22 @@ export class RouteManager {
|
|
|
310
367
|
exec(command) {
|
|
311
368
|
this.logger.debug(`Executing: ${command}`);
|
|
312
369
|
try {
|
|
313
|
-
return
|
|
370
|
+
return this.commandRunner(command);
|
|
314
371
|
}
|
|
315
372
|
catch (error) {
|
|
316
373
|
const message = error instanceof Error ? error.message : String(error);
|
|
317
374
|
throw new Error(`Command failed: ${command}\n${message}`);
|
|
318
375
|
}
|
|
319
376
|
}
|
|
377
|
+
powerShell(script) {
|
|
378
|
+
// -EncodedCommand is UTF-16LE and avoids cmd.exe quoting/injection hazards
|
|
379
|
+
// from interface aliases or Windows paths.
|
|
380
|
+
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
381
|
+
return this.exec(`powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`);
|
|
382
|
+
}
|
|
383
|
+
powerShellQuote(value) {
|
|
384
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
385
|
+
}
|
|
320
386
|
/**
|
|
321
387
|
* Convert "10.86.0.0/16" -> 16
|
|
322
388
|
*/
|
package/dist/tun/tun-device.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export interface TunDeviceOptions {
|
|
|
14
14
|
mockMode?: boolean;
|
|
15
15
|
helperPath?: string;
|
|
16
16
|
}
|
|
17
|
+
/** Relative helper path inside the package's bin/ directory. Windows keeps
|
|
18
|
+
* each architecture in its own directory because the matching driver DLL must
|
|
19
|
+
* be named exactly `wintun.dll` beside the executable. */
|
|
20
|
+
export declare function helperBinaryRelativePath(platform: NodeJS.Platform, arch: string): string | null;
|
|
17
21
|
export declare class TunDevice extends EventEmitter {
|
|
18
22
|
private config;
|
|
19
23
|
private logger;
|
package/dist/tun/tun-device.js
CHANGED
|
@@ -13,6 +13,24 @@ import { resolve, dirname } from "path";
|
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
15
15
|
import { Logger } from "../utils/logger.js";
|
|
16
|
+
/** Relative helper path inside the package's bin/ directory. Windows keeps
|
|
17
|
+
* each architecture in its own directory because the matching driver DLL must
|
|
18
|
+
* be named exactly `wintun.dll` beside the executable. */
|
|
19
|
+
export function helperBinaryRelativePath(platform, arch) {
|
|
20
|
+
if (platform === "linux" && arch === "x64")
|
|
21
|
+
return "tun-helper-linux-amd64";
|
|
22
|
+
if (platform === "linux" && arch === "arm64")
|
|
23
|
+
return "tun-helper-linux-arm64";
|
|
24
|
+
if (platform === "darwin" && arch === "x64")
|
|
25
|
+
return "tun-helper-darwin-amd64";
|
|
26
|
+
if (platform === "darwin" && arch === "arm64")
|
|
27
|
+
return "tun-helper-darwin-arm64";
|
|
28
|
+
if (platform === "win32" && arch === "x64")
|
|
29
|
+
return "windows-amd64/tun-helper-windows-amd64.exe";
|
|
30
|
+
if (platform === "win32" && arch === "arm64")
|
|
31
|
+
return "windows-arm64/tun-helper-windows-arm64.exe";
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
16
34
|
export class TunDevice extends EventEmitter {
|
|
17
35
|
config;
|
|
18
36
|
logger;
|
|
@@ -81,7 +99,19 @@ export class TunDevice extends EventEmitter {
|
|
|
81
99
|
clearTimeout(timer);
|
|
82
100
|
res();
|
|
83
101
|
});
|
|
84
|
-
helper
|
|
102
|
+
// Closing stdin is the helper's cross-platform graceful-shutdown
|
|
103
|
+
// signal. In particular, Node maps kill("SIGTERM") to an immediate
|
|
104
|
+
// TerminateProcess on Windows, which can interrupt Wintun inside a DLL
|
|
105
|
+
// call (0xc0000005) and leave an orphaned adapter. EOF lets main.go
|
|
106
|
+
// return normally and run its deferred session/adapter Close(). Keep
|
|
107
|
+
// SIGTERM/SIGKILL only as a fallback for a wedged helper.
|
|
108
|
+
if (helper.stdin && !helper.stdin.destroyed)
|
|
109
|
+
helper.stdin.end();
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
if (helper.exitCode === null && helper.signalCode === null) {
|
|
112
|
+
helper.kill("SIGTERM");
|
|
113
|
+
}
|
|
114
|
+
}, 1000).unref?.();
|
|
85
115
|
});
|
|
86
116
|
}
|
|
87
117
|
this.emit("closed");
|
|
@@ -178,8 +208,9 @@ export class TunDevice extends EventEmitter {
|
|
|
178
208
|
async openReal() {
|
|
179
209
|
const helperPath = this.helperPath || this.findHelperBinary();
|
|
180
210
|
if (!helperPath || !existsSync(helperPath)) {
|
|
211
|
+
const expected = helperBinaryRelativePath(process.platform, process.arch);
|
|
181
212
|
throw new Error(`TUN helper binary not found: ${helperPath || "(none)"}\n` +
|
|
182
|
-
`
|
|
213
|
+
`Expected package helper: ${expected || `${process.platform}/${process.arch} (unsupported)`}`);
|
|
183
214
|
}
|
|
184
215
|
// On Linux the helper creates a FIXED-name interface (agentnet0). If a
|
|
185
216
|
// prior helper died uncleanly — e.g. "read tun: file descriptor in bad
|
|
@@ -301,27 +332,13 @@ export class TunDevice extends EventEmitter {
|
|
|
301
332
|
resolve(moduleDir, "../../../bin"),
|
|
302
333
|
resolve(process.cwd(), "bin"),
|
|
303
334
|
];
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (platform === "linux" && arch === "x64") {
|
|
308
|
-
suffix = "linux-amd64";
|
|
309
|
-
}
|
|
310
|
-
else if (platform === "darwin" && arch === "arm64") {
|
|
311
|
-
suffix = "darwin-arm64";
|
|
312
|
-
}
|
|
313
|
-
else if (platform === "darwin" && arch === "x64") {
|
|
314
|
-
suffix = "darwin-amd64";
|
|
315
|
-
}
|
|
316
|
-
else if (platform === "linux" && arch === "arm64") {
|
|
317
|
-
suffix = "linux-arm64";
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
this.logger.warn(`Unsupported platform: ${platform}/${arch}`);
|
|
335
|
+
const relativePath = helperBinaryRelativePath(process.platform, process.arch);
|
|
336
|
+
if (!relativePath) {
|
|
337
|
+
this.logger.warn(`Unsupported platform: ${process.platform}/${process.arch}`);
|
|
321
338
|
return null;
|
|
322
339
|
}
|
|
323
340
|
for (const dir of candidates) {
|
|
324
|
-
const path = resolve(dir,
|
|
341
|
+
const path = resolve(dir, relativePath);
|
|
325
342
|
if (existsSync(path)) {
|
|
326
343
|
return path;
|
|
327
344
|
}
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -77,8 +77,8 @@ dora:
|
|
|
77
77
|
|
|
78
78
|
### `network`
|
|
79
79
|
|
|
80
|
-
- **`interface`** — TUN device name on Linux
|
|
81
|
-
assigned `utun*` regardless of this field.
|
|
80
|
+
- **`interface`** — TUN device name on Linux and native Windows (Wintun).
|
|
81
|
+
macOS uses kernel-assigned `utun*` regardless of this field.
|
|
82
82
|
- **`ip`** — fallback IP if dora can't be reached. **When dora is
|
|
83
83
|
enabled and reachable, dora's allocation overrides this.**
|
|
84
84
|
- **`subnet`** — `/16` is the only tested size today. Don't change
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Install AgentNet natively on Windows
|
|
2
|
+
|
|
3
|
+
AgentNet runs directly in 64-bit Windows. WSL is not required. The npm package
|
|
4
|
+
contains a Windows helper and WireGuard's signed `wintun.dll`; AgentNet chooses
|
|
5
|
+
the x64 or ARM64 files automatically.
|
|
6
|
+
|
|
7
|
+
Supported targets:
|
|
8
|
+
|
|
9
|
+
- Windows 10 or 11, x64 or ARM64
|
|
10
|
+
- Windows Server 2016 or newer, x64 or ARM64
|
|
11
|
+
- Node.js 20 or newer
|
|
12
|
+
|
|
13
|
+
The TUN adapter and routes require Administrator rights. Carrier, the AgentNet
|
|
14
|
+
protocol, ACLs, chat, the built-in CONNECT exit proxy, and the local router are
|
|
15
|
+
the same as on Linux and macOS.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
1. Install the current LTS release of [Node.js](https://nodejs.org/).
|
|
20
|
+
2. Open **PowerShell as Administrator**.
|
|
21
|
+
3. Run:
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
npm install -g @decentnetwork/lan
|
|
25
|
+
agentnet init --name my-windows-pc
|
|
26
|
+
agentnet service install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`service install` creates the `agentnet0` Wintun adapter, registers the
|
|
30
|
+
`10.86.0.0/16` route, and installs an elevated `Decent AgentNet` startup task.
|
|
31
|
+
The launcher restarts the daemon after a failure and starts it again at boot.
|
|
32
|
+
|
|
33
|
+
No separate Wintun installer is needed. Do not download a driver DLL from a
|
|
34
|
+
third-party site.
|
|
35
|
+
|
|
36
|
+
## Verify
|
|
37
|
+
|
|
38
|
+
In the same Administrator PowerShell:
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
agentnet service status
|
|
42
|
+
agentnet diag
|
|
43
|
+
Get-NetAdapter -Name agentnet0
|
|
44
|
+
Get-NetRoute -DestinationPrefix 10.86.0.0/16
|
|
45
|
+
ping 10.86.156.164
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Replace the final address with a live AgentNet peer. The first dora roster and
|
|
49
|
+
Carrier sessions can take 60–90 seconds after a new installation.
|
|
50
|
+
|
|
51
|
+
Follow the service log with:
|
|
52
|
+
|
|
53
|
+
```powershell
|
|
54
|
+
Get-Content -Wait "$env:USERPROFILE\.agentnet\agentnet-service.log"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Useful lifecycle commands:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
agentnet service restart
|
|
61
|
+
agentnet service status
|
|
62
|
+
agentnet service install --uninstall
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Run these commands from an Administrator PowerShell. `agentnet restart` is a
|
|
66
|
+
different command: it asks an already-running daemon to re-exec over local IPC;
|
|
67
|
+
`agentnet service restart` works even when that daemon is stuck.
|
|
68
|
+
|
|
69
|
+
## Make the Windows machine an exit node
|
|
70
|
+
|
|
71
|
+
An exit node does not need WSL, Windows NAT, or the client-side HLS accelerator.
|
|
72
|
+
Enable AgentNet's application-level CONNECT proxy and restart the service:
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
agentnet proxy enable
|
|
76
|
+
agentnet service restart
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The proxy listens on the node's AgentNet virtual IP at port `8888` by default.
|
|
80
|
+
AgentNet friend/ACL policy remains the authorization boundary. On the machine
|
|
81
|
+
watching CCTV, run the multi-exit router with HLS acceleration:
|
|
82
|
+
|
|
83
|
+
```powershell
|
|
84
|
+
agentnet proxy router --listen 0.0.0.0 --hls-accel
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Then point Chrome at `http://127.0.0.1:8889`. `--hls-accel` belongs on the
|
|
88
|
+
watching/client router, not on each exit node.
|
|
89
|
+
|
|
90
|
+
## Foreground debugging
|
|
91
|
+
|
|
92
|
+
Stop the startup task first so two daemons do not share one identity:
|
|
93
|
+
|
|
94
|
+
```powershell
|
|
95
|
+
schtasks.exe /End /TN "Decent AgentNet"
|
|
96
|
+
$env:DECENT_DEBUG = "1"
|
|
97
|
+
$env:DECENT_DEBUG_VERBOSE = "1"
|
|
98
|
+
agentnet up --real-tun
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Press `Ctrl+C` when finished, then restore the task with
|
|
102
|
+
`agentnet service restart`.
|
|
103
|
+
|
|
104
|
+
## Troubleshooting
|
|
105
|
+
|
|
106
|
+
| symptom | fix |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `Access is denied`, route creation fails, or Wintun cannot create an adapter | Close the terminal and reopen PowerShell with **Run as administrator**. |
|
|
109
|
+
| `Missing TUN helper` or `wintun.dll` is missing | Reinstall the current npm package. Antivirus quarantine is also worth checking. The helper and DLL must remain together in their `bin/windows-*` directory. |
|
|
110
|
+
| Service is installed but `agentnet diag` cannot connect | Run commands from Administrator PowerShell and inspect `agentnet-service.log`; then use `agentnet service restart`. |
|
|
111
|
+
| Only this Windows host cannot reach peers | Check `Get-NetRoute -DestinationPrefix 10.86.0.0/16` and ensure another VPN has not installed a more specific `10.86.*` route. |
|
|
112
|
+
| Carrier shows a peer online but ping/messages stop after a restart | Upgrade both ends to the current AgentNet version; session rekey convergence requires compatible daemon versions. |
|
|
113
|
+
| Native mode is blocked by enterprise policy | Use the previous WSL2 deployment as a fallback; do not run native and WSL daemons with the same identity at the same time. |
|
|
114
|
+
|
|
115
|
+
## Implementation and trust
|
|
116
|
+
|
|
117
|
+
- The helper is written in Go and uses the official
|
|
118
|
+
[`golang.zx2c4.com/wintun`](https://git.zx2c4.com/wintun-go/) binding.
|
|
119
|
+
- Release packaging accepts only the official Wintun 0.14.1 archive with
|
|
120
|
+
SHA-256 `07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51`.
|
|
121
|
+
- The release package includes Wintun's license and an asset manifest; npm
|
|
122
|
+
publishing fails if either architecture's DLL is missing or changed.
|
|
123
|
+
|
|
124
|
+
The native helper is cross-compiled and unit-tested from the main repository.
|
|
125
|
+
|
|
126
|
+
### Native validation
|
|
127
|
+
|
|
128
|
+
Release `0.1.200` was validated on real Windows 11 x64 (build 26200) with
|
|
129
|
+
Node.js 24.15.0 on July 15, 2026. The smoke test covered global npm install,
|
|
130
|
+
Wintun creation, `/32` address and `/16` route, firewall, named-pipe CLI,
|
|
131
|
+
dora roster, cross-node ICMP and chat, graceful service restart, adapter
|
|
132
|
+
recreation, and HTTPS CONNECT egress. After restart, allow roughly 30–90
|
|
133
|
+
seconds for dora roster/session convergence before treating an early
|
|
134
|
+
`no-ipam-record` ping as a failure.
|
|
135
|
+
|
|
136
|
+
Windows ARM64 is cross-compiled and packaged but has not yet had equivalent
|
|
137
|
+
real-hardware validation.
|
package/docs/INSTALL.md
CHANGED
|
@@ -13,27 +13,18 @@ transparently between any two daemons that are Carrier friends.
|
|
|
13
13
|
| | minimum |
|
|
14
14
|
|---|---|
|
|
15
15
|
| Node.js | **20+** |
|
|
16
|
-
| OS | Linux (`x86_64`, `aarch64`), macOS (`aarch64`, `x86_64`), or
|
|
17
|
-
| Root
|
|
16
|
+
| OS | Linux (`x86_64`, `aarch64`), macOS (`aarch64`, `x86_64`), or native 64-bit Windows 10/11/Server (`x64`, `ARM64`) ([Windows guide](INSTALL-WINDOWS.md)) |
|
|
17
|
+
| Privilege | Root/sudo on Unix or Administrator on Windows — needed to create and configure the TUN device. |
|
|
18
18
|
| Outbound network | TCP and UDP to public Carrier bootstrap nodes (ports 33445 + 443). No inbound port needed; no public IP needed. |
|
|
19
19
|
|
|
20
20
|
### Windows
|
|
21
21
|
|
|
22
|
-
Windows
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
Windows runs AgentNet natively using the signed Wintun driver bundled with the
|
|
23
|
+
npm package. Node selects the correct x64 or ARM64 helper automatically; WSL
|
|
24
|
+
is no longer required. Installation and service commands must run from an
|
|
25
|
+
Administrator PowerShell.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
this is what the one-command `wsl --install` needs.
|
|
28
|
-
- **Fast (direct UDP) path needs Windows 11, version 22H2 or later.** There you
|
|
29
|
-
turn on WSL **mirrored networking mode**, which lets the direct UDP
|
|
30
|
-
hole-punch succeed (~200 ms — smooth for live video). On **Windows 10**
|
|
31
|
-
mirrored mode doesn't exist, so WSL's NAT blocks inbound UDP and the SDK
|
|
32
|
-
falls back to the TCP relay: it still works, just slower (fine for SSH, chat,
|
|
33
|
-
and most browsing; live sports may stutter).
|
|
34
|
-
|
|
35
|
-
👉 **Non-technical step-by-step for Windows users: [INSTALL-WINDOWS.md](INSTALL-WINDOWS.md)**
|
|
36
|
-
(install WSL → install Node.js → install decentlan, three steps).
|
|
27
|
+
👉 **Native Windows step-by-step: [INSTALL-WINDOWS.md](INSTALL-WINDOWS.md)**
|
|
37
28
|
|
|
38
29
|
## Install
|
|
39
30
|
|
|
@@ -42,8 +33,8 @@ npm install -g @decentnetwork/lan
|
|
|
42
33
|
```
|
|
43
34
|
|
|
44
35
|
This installs the `agentnet` CLI on `$PATH` along with platform-
|
|
45
|
-
specific TUN helper binaries (
|
|
46
|
-
|
|
36
|
+
specific TUN helper binaries (Linux/macOS x64 + ARM64 and Windows x64 + ARM64).
|
|
37
|
+
The Windows directories also contain the matching signed `wintun.dll`. Confirm:
|
|
47
38
|
|
|
48
39
|
```bash
|
|
49
40
|
agentnet --help
|
|
@@ -59,8 +50,8 @@ sudo agentnet service install --config-dir $HOME/.agentnet
|
|
|
59
50
|
|
|
60
51
|
That's it. `agentnet init` already points at the public dora and sends
|
|
61
52
|
the one-time friend-request; `service install` writes a systemd unit
|
|
62
|
-
(Linux)
|
|
63
|
-
|
|
53
|
+
(Linux), LaunchDaemon (macOS), or elevated startup task (Windows) with
|
|
54
|
+
automatic restart.
|
|
64
55
|
|
|
65
56
|
Wait ~60–90s, then verify:
|
|
66
57
|
|
|
@@ -105,7 +96,7 @@ The daemon will:
|
|
|
105
96
|
1. Open a Carrier connection
|
|
106
97
|
2. Register with dora and get a virtual IP (e.g. `10.86.1.12`)
|
|
107
98
|
3. Pull the network roster and auto-friend every other peer in it
|
|
108
|
-
4. Bring up `agentnet0` on Linux (or `utun*` on macOS) at that IP
|
|
99
|
+
4. Bring up `agentnet0` on Linux/Windows (or `utun*` on macOS) at that IP
|
|
109
100
|
5. Start a local DNS server for `*.decent` (default) so you can
|
|
110
101
|
`ping my-laptop.decent`, `ssh peer-name.decent`, etc.
|
|
111
102
|
|
|
@@ -237,7 +228,8 @@ to stop. Drop the `.plist` file to disable persistence.
|
|
|
237
228
|
|
|
238
229
|
## Running CLI commands once the daemon is up
|
|
239
230
|
|
|
240
|
-
The CLI talks to the daemon over a Unix socket in the config-dir
|
|
231
|
+
The CLI talks to the daemon over a Unix socket in the config-dir, or a named
|
|
232
|
+
pipe derived from that directory on Windows.
|
|
241
233
|
Most commands (`diag`, `ipam list`, `friends list`, `friends pending`,
|
|
242
234
|
`resolve`, `dora autofriend`) just read or write config files — they
|
|
243
235
|
**don't need sudo**:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.200",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"README.md",
|
|
25
25
|
"LICENSE",
|
|
26
26
|
"docs/INSTALL.md",
|
|
27
|
+
"docs/INSTALL-WINDOWS.md",
|
|
27
28
|
"docs/CONFIGURATION.md"
|
|
28
29
|
],
|
|
29
30
|
"sideEffects": false,
|
|
@@ -60,12 +61,16 @@
|
|
|
60
61
|
"scripts": {
|
|
61
62
|
"build": "tsc -p tsconfig.json && chmod +x dist/cli/index.js && node scripts/build-ui.mjs && node scripts/build-console.mjs",
|
|
62
63
|
"build:ui": "node scripts/build-ui.mjs",
|
|
63
|
-
"build:helper": "cd helper/tun-helper && go build -o ../../bin/tun-helper-$(go env GOOS)-$(go env GOARCH) .",
|
|
64
|
-
"build:helper:linux-amd64": "cd helper/tun-helper && GOOS=linux GOARCH=amd64 go build -o ../../bin/tun-helper-linux-amd64 .",
|
|
65
|
-
"build:helper:linux-arm64": "cd helper/tun-helper && GOOS=linux GOARCH=arm64 go build -o ../../bin/tun-helper-linux-arm64 .",
|
|
66
|
-
"build:helper:darwin-arm64": "cd helper/tun-helper && GOOS=darwin GOARCH=arm64 go build -o ../../bin/tun-helper-darwin-arm64 .",
|
|
67
|
-
"build:helper:darwin-amd64": "cd helper/tun-helper && GOOS=darwin GOARCH=amd64 go build -o ../../bin/tun-helper-darwin-amd64 .",
|
|
68
|
-
"build:
|
|
64
|
+
"build:helper": "cd helper/tun-helper && go build -buildvcs=false -o ../../bin/tun-helper-$(go env GOOS)-$(go env GOARCH) .",
|
|
65
|
+
"build:helper:linux-amd64": "cd helper/tun-helper && GOOS=linux GOARCH=amd64 go build -buildvcs=false -o ../../bin/tun-helper-linux-amd64 .",
|
|
66
|
+
"build:helper:linux-arm64": "cd helper/tun-helper && GOOS=linux GOARCH=arm64 go build -buildvcs=false -o ../../bin/tun-helper-linux-arm64 .",
|
|
67
|
+
"build:helper:darwin-arm64": "cd helper/tun-helper && GOOS=darwin GOARCH=arm64 go build -buildvcs=false -o ../../bin/tun-helper-darwin-arm64 .",
|
|
68
|
+
"build:helper:darwin-amd64": "cd helper/tun-helper && GOOS=darwin GOARCH=amd64 go build -buildvcs=false -o ../../bin/tun-helper-darwin-amd64 .",
|
|
69
|
+
"build:helper:windows-amd64": "mkdir -p bin/windows-amd64 && cd helper/tun-helper && GOOS=windows GOARCH=amd64 go build -buildvcs=false -o ../../bin/windows-amd64/tun-helper-windows-amd64.exe .",
|
|
70
|
+
"build:helper:windows-arm64": "mkdir -p bin/windows-arm64 && cd helper/tun-helper && GOOS=windows GOARCH=arm64 go build -buildvcs=false -o ../../bin/windows-arm64/tun-helper-windows-arm64.exe .",
|
|
71
|
+
"build:helpers:all": "npm run build:helper:linux-amd64 && npm run build:helper:linux-arm64 && npm run build:helper:darwin-arm64 && npm run build:helper:darwin-amd64 && npm run build:helper:windows-amd64 && npm run build:helper:windows-arm64",
|
|
72
|
+
"fetch:wintun": "node scripts/fetch-wintun.mjs",
|
|
73
|
+
"verify:wintun": "node scripts/verify-wintun.mjs",
|
|
69
74
|
"build:all": "npm run build && npm run build:helper",
|
|
70
75
|
"clean": "rm -rf dist bin",
|
|
71
76
|
"dev": "node --loader ts-node/esm src/index.ts",
|
|
@@ -74,7 +79,7 @@
|
|
|
74
79
|
"test:coverage": "vitest --coverage",
|
|
75
80
|
"typecheck": "tsc --noEmit",
|
|
76
81
|
"lint": "eslint src --ext .ts",
|
|
77
|
-
"prepublishOnly": "rm -rf dist && npm run build && npm run typecheck && npm run build:helpers:all",
|
|
82
|
+
"prepublishOnly": "rm -rf dist && npm run build && npm run typecheck && npm run build:helpers:all && npm run verify:wintun",
|
|
78
83
|
"build:console": "node scripts/build-console.mjs"
|
|
79
84
|
},
|
|
80
85
|
"dependencies": {
|