@decentnetwork/lan 0.1.18 → 0.1.21
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/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/dist/cli/commands.js +31 -1
- package/dist/daemon/ipc.d.ts +7 -1
- package/dist/daemon/ipc.js +2 -0
- package/dist/daemon/server.d.ts +1 -0
- package/dist/daemon/server.js +62 -15
- package/dist/proxy/connect-proxy.d.ts +11 -0
- package/dist/proxy/connect-proxy.js +17 -0
- package/docs/INSTALL.md +123 -10
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/cli/commands.js
CHANGED
|
@@ -787,7 +787,36 @@ export async function cmdProxyAllowHost(args) {
|
|
|
787
787
|
config.proxy = { ...proxy, allowHosts: [...allowHosts] };
|
|
788
788
|
await ConfigLoader.save(config, configPath);
|
|
789
789
|
console.log(`Added '${args.host}' to proxy allow-hosts.`);
|
|
790
|
-
|
|
790
|
+
await applyProxyReloadIfRunning(config);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* If the daemon is up, push the freshly-saved proxy allowlist into the
|
|
794
|
+
* running proxy over IPC — no restart, no Carrier-session churn. Prints
|
|
795
|
+
* the outcome. When the daemon is down (or the proxy isn't running yet),
|
|
796
|
+
* tells the operator a restart is needed.
|
|
797
|
+
*/
|
|
798
|
+
async function applyProxyReloadIfRunning(config) {
|
|
799
|
+
if (daemonPid(config) === null) {
|
|
800
|
+
console.log("Daemon is down — change takes effect on next 'agentnet up'.");
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
try {
|
|
804
|
+
const res = await ipcCall(config, { op: "proxy-reload" });
|
|
805
|
+
if (!res.ok) {
|
|
806
|
+
console.log(`(live reload failed: ${res.error}) — restart the daemon to apply.`);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const data = res.data;
|
|
810
|
+
if (data.applied) {
|
|
811
|
+
console.log(`Applied live to the running proxy — no restart needed.`);
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
console.log(`Not applied live: ${data.reason ?? "proxy not running"}.`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
catch (err) {
|
|
818
|
+
console.log(`(live reload error: ${err instanceof Error ? err.message : err}) — restart to apply.`);
|
|
819
|
+
}
|
|
791
820
|
}
|
|
792
821
|
/**
|
|
793
822
|
* Remove a host glob from the proxy allowlist.
|
|
@@ -803,6 +832,7 @@ export async function cmdProxyRevokeHost(args) {
|
|
|
803
832
|
await ConfigLoader.save(config, configPath);
|
|
804
833
|
if (filtered.length < before) {
|
|
805
834
|
console.log(`Removed '${args.host}' from proxy allow-hosts.`);
|
|
835
|
+
await applyProxyReloadIfRunning(config);
|
|
806
836
|
}
|
|
807
837
|
else {
|
|
808
838
|
console.log(`No exact match for '${args.host}' in allow-hosts.`);
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -39,9 +39,15 @@ export interface IpcHandlers {
|
|
|
39
39
|
/** Reject (drop) a queued friend-request by userid. Doesn't
|
|
40
40
|
* notify the sender — they'll just see no acceptance. */
|
|
41
41
|
friendsReject: (userid: string) => Promise<void>;
|
|
42
|
+
/** Re-read proxy allowlist from config and apply it to the running
|
|
43
|
+
* proxy WITHOUT restarting the daemon. Lets `agentnet proxy
|
|
44
|
+
* allow-host` take effect instantly instead of forcing a daemon
|
|
45
|
+
* restart (which drops every Carrier session). Returns the applied
|
|
46
|
+
* allowlist for the CLI to echo. */
|
|
47
|
+
proxyReload: () => Promise<Record<string, unknown>>;
|
|
42
48
|
}
|
|
43
49
|
export interface IpcRequest {
|
|
44
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject";
|
|
50
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "proxy-reload";
|
|
45
51
|
address?: string;
|
|
46
52
|
hello?: string;
|
|
47
53
|
userid?: string;
|
package/dist/daemon/ipc.js
CHANGED
package/dist/daemon/server.d.ts
CHANGED
package/dist/daemon/server.js
CHANGED
|
@@ -18,6 +18,7 @@ import { DnsServer } from "../dns/server.js";
|
|
|
18
18
|
import { IpcServer, ipcSocketPath } from "./ipc.js";
|
|
19
19
|
import { PendingFriendsStore } from "./pending-friends.js";
|
|
20
20
|
import { Logger } from "../utils/logger.js";
|
|
21
|
+
import { ConfigLoader } from "../config/loader.js";
|
|
21
22
|
export class DaemonServer {
|
|
22
23
|
config;
|
|
23
24
|
useMockTun;
|
|
@@ -40,9 +41,13 @@ export class DaemonServer {
|
|
|
40
41
|
startedAt = 0;
|
|
41
42
|
isRunning = false;
|
|
42
43
|
pidFile;
|
|
44
|
+
configDir;
|
|
43
45
|
constructor(opts) {
|
|
44
46
|
this.config = opts.config;
|
|
45
47
|
this.useMockTun = opts.useMockTun ?? true; // Default to mock; override for production
|
|
48
|
+
// Remember where config.yaml lives so live-reload ops (e.g.
|
|
49
|
+
// proxy-reload) can re-read it without a daemon restart.
|
|
50
|
+
this.configDir = opts.configDir ?? ConfigLoader.defaultConfigDir();
|
|
46
51
|
this.logger = new Logger({ prefix: "Daemon" });
|
|
47
52
|
}
|
|
48
53
|
async start() {
|
|
@@ -145,6 +150,31 @@ export class DaemonServer {
|
|
|
145
150
|
throw new Error(`No pending friend-request for userid ${userid}`);
|
|
146
151
|
// No-op at the Carrier level — sender just sees no acceptance.
|
|
147
152
|
},
|
|
153
|
+
proxyReload: async () => {
|
|
154
|
+
// Re-read the proxy allowlist from disk and push it into the
|
|
155
|
+
// running proxy without a daemon restart (which would drop
|
|
156
|
+
// every Carrier session). If the proxy isn't running yet,
|
|
157
|
+
// tell the caller so it can fall back to "restart needed".
|
|
158
|
+
const fresh = await ConfigLoader.load(resolve(this.configDir, "config.yaml"));
|
|
159
|
+
const allowHosts = fresh.proxy?.allowHosts ?? [];
|
|
160
|
+
const allowConnectPorts = fresh.proxy?.allowConnectPorts;
|
|
161
|
+
if (!this.connectProxy) {
|
|
162
|
+
return {
|
|
163
|
+
applied: false,
|
|
164
|
+
reason: fresh.proxy?.enabled
|
|
165
|
+
? "proxy enabled in config but not running — restart the daemon once to start the listener"
|
|
166
|
+
: "proxy is disabled — run 'agentnet proxy enable' then restart once",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
this.connectProxy.updateAllowlist(allowHosts, allowConnectPorts);
|
|
170
|
+
// Keep the in-memory config in sync so a later diag reflects it.
|
|
171
|
+
if (this.config.proxy) {
|
|
172
|
+
this.config.proxy.allowHosts = allowHosts;
|
|
173
|
+
if (allowConnectPorts)
|
|
174
|
+
this.config.proxy.allowConnectPorts = allowConnectPorts;
|
|
175
|
+
}
|
|
176
|
+
return { applied: true, allowHosts };
|
|
177
|
+
},
|
|
148
178
|
diag: async () => {
|
|
149
179
|
// Snapshot of everything an operator needs to debug why
|
|
150
180
|
// packets aren't moving: forwarding counters, friend
|
|
@@ -413,26 +443,43 @@ export class DaemonServer {
|
|
|
413
443
|
// explicitly enabled it via `agentnet proxy enable`. Binds to the
|
|
414
444
|
// virtual IP so reach is gated by the existing per-peer ACL — we
|
|
415
445
|
// don't add a second auth layer at the HTTP level.
|
|
446
|
+
//
|
|
447
|
+
// Bind to `tunIp` (the dora-ALLOCATED IP), NOT config.network.ip.
|
|
448
|
+
// They diverge when dora hands out an address different from the
|
|
449
|
+
// init default: the listener would try to bind config's
|
|
450
|
+
// 10.86.1.10 while the TUN is actually at 10.86.1.15, and
|
|
451
|
+
// Node throws EADDRNOTAVAIL because that address isn't local.
|
|
452
|
+
//
|
|
453
|
+
// Also: a proxy bind failure must NOT take down the whole daemon.
|
|
454
|
+
// Packet forwarding, DNS, dora, friends — all work fine without
|
|
455
|
+
// the proxy. Catch + log so an EADDRNOTAVAIL (or port-in-use)
|
|
456
|
+
// degrades gracefully to "no proxy" instead of a crash loop.
|
|
416
457
|
if (this.config.proxy?.enabled) {
|
|
417
458
|
if (this.useMockTun) {
|
|
418
459
|
this.logger.warn("Proxy enabled but daemon is in mock-TUN mode; skipping proxy listener");
|
|
419
460
|
}
|
|
420
461
|
else {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
462
|
+
try {
|
|
463
|
+
this.connectProxy = new ConnectProxy({
|
|
464
|
+
bindIp: tunIp,
|
|
465
|
+
port: this.config.proxy.port,
|
|
466
|
+
allowHosts: this.config.proxy.allowHosts ?? [],
|
|
467
|
+
allowConnectPorts: this.config.proxy.allowConnectPorts,
|
|
468
|
+
resolvePeerName: (srcIp) => this.ipam.resolveIp(srcIp)?.name,
|
|
469
|
+
onTunnelOpen: ({ src, srcName, target }) => {
|
|
470
|
+
this.auditLog.logProxyOpen({ srcIp: src, srcName, target });
|
|
471
|
+
},
|
|
472
|
+
onTunnelClose: ({ src, srcName, target, bytesTransferred }) => {
|
|
473
|
+
this.auditLog.logProxyClose({ srcIp: src, srcName, target, bytesTransferred });
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
await this.connectProxy.start();
|
|
477
|
+
this.logger.info(`Proxy listening on ${tunIp}:${this.config.proxy.port}`);
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
this.connectProxy = undefined;
|
|
481
|
+
this.logger.error(`Proxy failed to start on ${tunIp}:${this.config.proxy.port} — continuing without it: ${err instanceof Error ? err.message : err}`);
|
|
482
|
+
}
|
|
436
483
|
}
|
|
437
484
|
}
|
|
438
485
|
this.isRunning = true;
|
|
@@ -65,6 +65,17 @@ export declare class ConnectProxy {
|
|
|
65
65
|
start(): Promise<void>;
|
|
66
66
|
stop(): Promise<void>;
|
|
67
67
|
getStats(): ConnectProxyStats;
|
|
68
|
+
/**
|
|
69
|
+
* Update the host allowlist (and optionally the port allowlist) of a
|
|
70
|
+
* RUNNING proxy without restarting it. handleConnect reads
|
|
71
|
+
* `this.opts.allowHosts` / `allowConnectPorts` at request time, so a
|
|
72
|
+
* plain field swap takes effect on the next CONNECT — no need to tear
|
|
73
|
+
* down the listener or, more importantly, the daemon's Carrier
|
|
74
|
+
* sessions. Lets `agentnet proxy allow-host` apply instantly instead
|
|
75
|
+
* of forcing a daemon restart that would drop every peer session and
|
|
76
|
+
* trigger a slow reconvergence storm.
|
|
77
|
+
*/
|
|
78
|
+
updateAllowlist(allowHosts: string[], allowConnectPorts?: number[]): void;
|
|
68
79
|
private handleConnect;
|
|
69
80
|
private refuse;
|
|
70
81
|
}
|
|
@@ -86,6 +86,23 @@ export class ConnectProxy {
|
|
|
86
86
|
getStats() {
|
|
87
87
|
return { ...this.stats };
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Update the host allowlist (and optionally the port allowlist) of a
|
|
91
|
+
* RUNNING proxy without restarting it. handleConnect reads
|
|
92
|
+
* `this.opts.allowHosts` / `allowConnectPorts` at request time, so a
|
|
93
|
+
* plain field swap takes effect on the next CONNECT — no need to tear
|
|
94
|
+
* down the listener or, more importantly, the daemon's Carrier
|
|
95
|
+
* sessions. Lets `agentnet proxy allow-host` apply instantly instead
|
|
96
|
+
* of forcing a daemon restart that would drop every peer session and
|
|
97
|
+
* trigger a slow reconvergence storm.
|
|
98
|
+
*/
|
|
99
|
+
updateAllowlist(allowHosts, allowConnectPorts) {
|
|
100
|
+
this.opts.allowHosts = allowHosts;
|
|
101
|
+
if (allowConnectPorts)
|
|
102
|
+
this.opts.allowConnectPorts = allowConnectPorts;
|
|
103
|
+
this.logger.info(`Allowlist updated live: ${allowHosts.length} host glob(s)` +
|
|
104
|
+
(allowConnectPorts ? `, ports ${allowConnectPorts.join(",")}` : ""));
|
|
105
|
+
}
|
|
89
106
|
handleConnect(req, clientSocket, head) {
|
|
90
107
|
const src = clientSocket.remoteAddress ?? "?";
|
|
91
108
|
const srcName = this.opts.resolvePeerName?.(src);
|
package/docs/INSTALL.md
CHANGED
|
@@ -102,33 +102,146 @@ ping <peer-name>.decent # if you ran 'dns install'
|
|
|
102
102
|
|
|
103
103
|
## Running the daemon as a service
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
foreground binary; rough sketches:
|
|
105
|
+
The daemon is a foreground binary. Pick one of:
|
|
107
106
|
|
|
108
|
-
###
|
|
107
|
+
### Option A — `nohup` (fastest; no auto-restart)
|
|
109
108
|
|
|
110
|
-
```
|
|
111
|
-
#
|
|
109
|
+
```bash
|
|
110
|
+
# pre-create the log so the unprivileged user can tail it
|
|
111
|
+
sudo touch /var/log/agentnet.log
|
|
112
|
+
sudo chmod 666 /var/log/agentnet.log
|
|
113
|
+
|
|
114
|
+
# IMPORTANT: wrap the whole pipeline in `sudo sh -c '...'`.
|
|
115
|
+
# Otherwise `>>` is interpreted by your unprivileged shell and the
|
|
116
|
+
# redirect fails with "Permission denied" before sudo elevates.
|
|
117
|
+
sudo sh -c 'nohup agentnet up --real-tun --config-dir /home/YOURUSER/.agentnet >> /var/log/agentnet.log 2>&1 &'
|
|
118
|
+
|
|
119
|
+
# verify
|
|
120
|
+
ps -ef | grep -E 'agentnet up|tun-helper' | grep -v grep
|
|
121
|
+
tail -f /var/log/agentnet.log
|
|
122
|
+
|
|
123
|
+
# stop
|
|
124
|
+
sudo pkill -f 'agentnet up'; sudo pkill -f tun-helper
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Always pass `--config-dir /home/YOURUSER/.agentnet` when launching with
|
|
128
|
+
`sudo` — `sudo` changes `$HOME` to `/root`, so without an explicit
|
|
129
|
+
`--config-dir` the daemon (and any one-off CLI invocations) will look
|
|
130
|
+
at `/root/.agentnet/` and report "Not initialized."
|
|
131
|
+
|
|
132
|
+
### Option B — systemd (Linux, persistent + auto-restart)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
sudo tee /etc/systemd/system/agentnet.service <<'EOF'
|
|
112
136
|
[Unit]
|
|
113
137
|
Description=Decent AgentNet daemon
|
|
114
138
|
After=network-online.target
|
|
115
139
|
Wants=network-online.target
|
|
116
140
|
|
|
117
141
|
[Service]
|
|
118
|
-
|
|
142
|
+
Type=simple
|
|
119
143
|
User=root
|
|
144
|
+
ExecStart=/usr/local/bin/agentnet up --real-tun --config-dir /home/YOURUSER/.agentnet
|
|
120
145
|
Restart=on-failure
|
|
121
146
|
RestartSec=5
|
|
147
|
+
StandardOutput=append:/var/log/agentnet.log
|
|
148
|
+
StandardError=append:/var/log/agentnet.log
|
|
122
149
|
|
|
123
150
|
[Install]
|
|
124
151
|
WantedBy=multi-user.target
|
|
152
|
+
EOF
|
|
153
|
+
|
|
154
|
+
sudo systemctl daemon-reload
|
|
155
|
+
sudo systemctl enable --now agentnet
|
|
156
|
+
sudo systemctl status agentnet
|
|
157
|
+
journalctl -u agentnet -f # live logs (alternative to /var/log/agentnet.log)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Persists across reboots. If you ever previously launched via
|
|
161
|
+
`systemd-run --unit=agentnet ...` (transient unit), the unit file
|
|
162
|
+
above replaces it cleanly.
|
|
163
|
+
|
|
164
|
+
### Option C — launchd (macOS, persistent + auto-restart)
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
sudo tee /Library/LaunchDaemons/com.decentlan.agentnet.plist <<EOF
|
|
168
|
+
<?xml version="1.0"?>
|
|
169
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
170
|
+
<plist version="1.0"><dict>
|
|
171
|
+
<key>Label</key><string>com.decentlan.agentnet</string>
|
|
172
|
+
<key>UserName</key><string>root</string>
|
|
173
|
+
<key>ProgramArguments</key><array>
|
|
174
|
+
<string>/usr/local/bin/agentnet</string>
|
|
175
|
+
<string>up</string>
|
|
176
|
+
<string>--real-tun</string>
|
|
177
|
+
<string>--config-dir</string>
|
|
178
|
+
<string>/Users/YOURUSER/.agentnet</string>
|
|
179
|
+
</array>
|
|
180
|
+
<key>RunAtLoad</key><true/>
|
|
181
|
+
<key>KeepAlive</key><true/>
|
|
182
|
+
<key>StandardOutPath</key><string>/var/log/agentnet.log</string>
|
|
183
|
+
<key>StandardErrorPath</key><string>/var/log/agentnet.log</string>
|
|
184
|
+
</dict></plist>
|
|
185
|
+
EOF
|
|
186
|
+
sudo launchctl load /Library/LaunchDaemons/com.decentlan.agentnet.plist
|
|
187
|
+
tail -f /var/log/agentnet.log
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`sudo launchctl unload /Library/LaunchDaemons/com.decentlan.agentnet.plist`
|
|
191
|
+
to stop. Drop the `.plist` file to disable persistence.
|
|
192
|
+
|
|
193
|
+
## Running CLI commands once the daemon is up
|
|
194
|
+
|
|
195
|
+
The CLI talks to the daemon over a Unix socket in the config-dir.
|
|
196
|
+
Most commands (`diag`, `ipam list`, `friends list`, `friends pending`,
|
|
197
|
+
`resolve`, `dora autofriend`) just read or write config files — they
|
|
198
|
+
**don't need sudo**:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
agentnet diag
|
|
202
|
+
agentnet ipam list
|
|
203
|
+
agentnet friends list
|
|
204
|
+
agentnet dora autofriend all
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The few that DO need sudo (writing `/etc/resolver/` on macOS, or
|
|
208
|
+
`/etc/dnsmasq.d/` on Linux) require an explicit `--config-dir`
|
|
209
|
+
because `sudo`'s `$HOME` is `/root`:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
sudo agentnet dns install --config-dir /home/YOURUSER/.agentnet
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## DNS on Linux when dnsPort isn't 53
|
|
216
|
+
|
|
217
|
+
`agentnet init` defaults to `dnsPort: 5354` (5353 is reserved for
|
|
218
|
+
mDNS / Avahi; we step around it). systemd-resolved only forwards to
|
|
219
|
+
upstreams on port 53, so `agentnet dns install` on Linux will warn
|
|
220
|
+
and refuse rather than write a broken config. Two workarounds:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Workaround 1 — static /etc/hosts (simple; re-run on roster changes)
|
|
224
|
+
agentnet dns hosts | sudo tee -a /etc/hosts > /dev/null
|
|
225
|
+
|
|
226
|
+
# Workaround 2 — dnsmasq forward
|
|
227
|
+
agentnet dns install # prints a dnsmasq snippet; paste into
|
|
228
|
+
# /etc/dnsmasq.d/decent.conf, restart dnsmasq
|
|
125
229
|
```
|
|
126
230
|
|
|
127
|
-
|
|
231
|
+
macOS has no port-53 restriction — `agentnet dns install` works
|
|
232
|
+
out of the box there.
|
|
233
|
+
|
|
234
|
+
## Troubleshooting
|
|
128
235
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
236
|
+
| symptom | likely cause | fix |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| `Not initialized. Run 'agentnet init' first.` after running with sudo | `sudo` set `$HOME=/root`; CLI is looking at `/root/.agentnet/` | add `--config-dir /home/YOURUSER/.agentnet` |
|
|
239
|
+
| `/var/log/agentnet.log: Permission denied` when starting with `sudo nohup ... >> ...` | `>>` runs in your unprivileged shell before sudo elevates | wrap in `sudo sh -c '...'`, OR pre-create the log file with `chmod 666` |
|
|
240
|
+
| `Unit agentnet.service not found.` after a successful `systemctl restart` | previous launch was a transient `systemd-run` unit (auto-removed on stop) | install the persistent unit file (Option B above) |
|
|
241
|
+
| `dora friend is offline` for >60s right after restart | Carrier session re-handshake takes 30–90s; not a real failure | wait, or `agentnet diag` to confirm `friends[].status == "online"` |
|
|
242
|
+
| `ipam list` shows only the self entry | dora roster fetch hasn't merged yet, or dora server is down | wait one refresh interval (60s default), then check the dora server; also confirm `dora.autoFriend` isn't `none` |
|
|
243
|
+
| `ping <peer>.decent` resolves but times out | TUN routing OK but the remote daemon is older without the auto-learn-IPAM fix | upgrade the remote to `@decentnetwork/lan@0.1.13+` |
|
|
244
|
+
| `ping <peer>.decent` fails with `Unknown host` even though `nslookup -port=5354 <peer>.decent 127.0.0.1` works | macOS hasn't reloaded `/etc/resolver/` | `sudo killall -HUP mDNSResponder` |
|
|
132
245
|
|
|
133
246
|
## Uninstall
|
|
134
247
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
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",
|