@decentnetwork/lan 0.1.197 → 0.1.199
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/proxy/hls-prefetch.js +78 -15
- package/dist/proxy/multi-exit-router.d.ts +1 -0
- package/dist/proxy/multi-exit-router.js +49 -2
- 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 +8 -3
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) {
|