@decentnetwork/lan 0.1.43 → 0.1.45
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/config/default-doras.yaml +35 -0
- package/dist/acl/audit.d.ts +14 -1
- package/dist/acl/audit.js +60 -3
- package/dist/config/loader.d.ts +10 -5
- package/dist/config/loader.js +18 -16
- package/dist/daemon/server.js +13 -6
- package/dist/types.d.ts +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Default dora registries shipped with @decentnetwork/lan.
|
|
2
|
+
#
|
|
3
|
+
# `agentnet init` seeds these into a new client's ~/.agentnet/config.yaml
|
|
4
|
+
# (dora.userids) and sends each a one-time friend-request, so a fresh
|
|
5
|
+
# install auto-joins the shared network with zero extra commands.
|
|
6
|
+
#
|
|
7
|
+
# They are FEDERATED with NON-OVERLAPPING IP segments: the client merges
|
|
8
|
+
# every roster, and any single registry staying up is enough to get an IP
|
|
9
|
+
# allocation — redundancy against any one dora being down.
|
|
10
|
+
#
|
|
11
|
+
# To add/replace a registry, edit this file (data, not code) and republish,
|
|
12
|
+
# or override locally with `agentnet dora enable --userid <id> --address <addr>`.
|
|
13
|
+
#
|
|
14
|
+
# Each entry needs:
|
|
15
|
+
# userid — to talk to the registry over Carrier (goes into dora.userids)
|
|
16
|
+
# address — so `agentnet init` can send the one-time friend-request
|
|
17
|
+
# name — label only (logs / comments)
|
|
18
|
+
# segment — informational: the IP band this registry allocates from
|
|
19
|
+
doras:
|
|
20
|
+
- name: dora-mac
|
|
21
|
+
userid: 98rsHv17h8G6AP9RagyrBiT1kmw4cn8MFPEembS6ZVjv
|
|
22
|
+
address: Jt7w1pKkyLT5GVue9h6ZPkjg1EeuuTbD6JVSLycXLsdm6nvBGSUd
|
|
23
|
+
segment: 10.86.1.10-10.86.63.254
|
|
24
|
+
- name: dora-beagle
|
|
25
|
+
userid: AxKFEZFLDi23EmnJFNP6gjUM4CaNMPfWUvbFR9ixtMBN
|
|
26
|
+
address: NsuN81dZdEoyvwEFgWaHkT8SPJB6UWeRmdYcCGFV5CdbbPXoK2RM
|
|
27
|
+
segment: 10.86.64.10-10.86.127.254
|
|
28
|
+
- name: dora-sh
|
|
29
|
+
userid: GMEMLmCWLMBK6BJiMkbLPNkEjF4S2xRf1SqR9hM8fWV3
|
|
30
|
+
address: ajg1ZMBw86UyujmEJzqKSCbi3wwEtg6tdGFTdESakyqujyxmqJZK
|
|
31
|
+
segment: 10.86.128.10-10.86.191.254
|
|
32
|
+
- name: dora-tokyo
|
|
33
|
+
userid: AB6BZfbrTFWw9eUoVpHdJqhhRnY8bTttp4CHTZ2Xfzxi
|
|
34
|
+
address: MAW2eBqBuQ6SmaXTrnZRRayQjAj3aLatwPy4xmBp7spnJeV569op
|
|
35
|
+
segment: 10.86.192.10-10.86.254.254
|
package/dist/acl/audit.d.ts
CHANGED
|
@@ -8,7 +8,13 @@ export declare class AuditLog {
|
|
|
8
8
|
private logger;
|
|
9
9
|
private buffer;
|
|
10
10
|
private bufferLimit;
|
|
11
|
-
|
|
11
|
+
private maxBytes;
|
|
12
|
+
private keepFiles;
|
|
13
|
+
private curBytes;
|
|
14
|
+
constructor(filePath: string, opts?: {
|
|
15
|
+
maxBytes?: number;
|
|
16
|
+
keepFiles?: number;
|
|
17
|
+
});
|
|
12
18
|
/**
|
|
13
19
|
* Log an access attempt (allowed or denied)
|
|
14
20
|
*/
|
|
@@ -67,4 +73,11 @@ export declare class AuditLog {
|
|
|
67
73
|
*/
|
|
68
74
|
readSince(timestamp: number): AuditEntry[];
|
|
69
75
|
private write;
|
|
76
|
+
/**
|
|
77
|
+
* Size-based rotation: audit.log -> audit.log.1 -> ... -> audit.log.N,
|
|
78
|
+
* dropping the oldest. Bounds total on-disk audit data to
|
|
79
|
+
* maxBytes * (keepFiles + 1). Best-effort — a failure here must never
|
|
80
|
+
* break the daemon, so errors are logged and writing continues.
|
|
81
|
+
*/
|
|
82
|
+
private rotate;
|
|
70
83
|
}
|
package/dist/acl/audit.js
CHANGED
|
@@ -2,20 +2,40 @@
|
|
|
2
2
|
* Audit logger for ACL decisions and connection events
|
|
3
3
|
* Append-only JSONL file format
|
|
4
4
|
*/
|
|
5
|
-
import { appendFileSync, readFileSync, mkdirSync, existsSync } from "fs";
|
|
5
|
+
import { appendFileSync, readFileSync, mkdirSync, existsSync, statSync, renameSync, rmSync, } from "fs";
|
|
6
6
|
import { dirname } from "path";
|
|
7
7
|
import { Logger } from "../utils/logger.js";
|
|
8
|
+
// Defaults chosen so a busy exit (proxy_open/proxy_close per CONNECT — can
|
|
9
|
+
// be hundreds/sec for HLS video) can never fill the disk: at most
|
|
10
|
+
// maxBytes * (keepFiles + 1) on disk (~200 MB by default), oldest dropped.
|
|
11
|
+
const DEFAULT_MAX_BYTES = 50 * 1024 * 1024; // rotate when the live file passes 50 MB
|
|
12
|
+
const DEFAULT_KEEP_FILES = 3; // audit.log.1 .. audit.log.3
|
|
8
13
|
export class AuditLog {
|
|
9
14
|
filePath;
|
|
10
15
|
logger;
|
|
11
16
|
buffer = [];
|
|
12
17
|
bufferLimit = 100; // Keep last N entries in memory for fast reads
|
|
13
|
-
|
|
18
|
+
maxBytes;
|
|
19
|
+
keepFiles;
|
|
20
|
+
curBytes = 0; // running size of the live file (avoids stat() per write)
|
|
21
|
+
constructor(filePath, opts) {
|
|
14
22
|
this.filePath = filePath;
|
|
15
23
|
this.logger = new Logger({ prefix: "AuditLog" });
|
|
24
|
+
this.maxBytes = opts?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
25
|
+
this.keepFiles = opts?.keepFiles ?? DEFAULT_KEEP_FILES;
|
|
16
26
|
// Ensure parent dir exists
|
|
17
27
|
const dir = dirname(filePath);
|
|
18
28
|
mkdirSync(dir, { recursive: true });
|
|
29
|
+
// Seed the byte counter from any existing file so a daemon that
|
|
30
|
+
// restarts onto an already-large log rotates promptly instead of
|
|
31
|
+
// letting it grow further.
|
|
32
|
+
try {
|
|
33
|
+
if (existsSync(this.filePath))
|
|
34
|
+
this.curBytes = statSync(this.filePath).size;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
this.curBytes = 0;
|
|
38
|
+
}
|
|
19
39
|
}
|
|
20
40
|
/**
|
|
21
41
|
* Log an access attempt (allowed or denied)
|
|
@@ -134,11 +154,48 @@ export class AuditLog {
|
|
|
134
154
|
this.buffer = this.buffer.slice(-this.bufferLimit);
|
|
135
155
|
}
|
|
136
156
|
// Append to file (JSONL)
|
|
157
|
+
const line = JSON.stringify(entry) + "\n";
|
|
137
158
|
try {
|
|
138
|
-
|
|
159
|
+
// Rotate BEFORE the write that would push us over the cap, so the
|
|
160
|
+
// live file never exceeds maxBytes by more than one line.
|
|
161
|
+
if (this.curBytes + Buffer.byteLength(line) > this.maxBytes) {
|
|
162
|
+
this.rotate();
|
|
163
|
+
}
|
|
164
|
+
appendFileSync(this.filePath, line, "utf-8");
|
|
165
|
+
this.curBytes += Buffer.byteLength(line);
|
|
139
166
|
}
|
|
140
167
|
catch (error) {
|
|
141
168
|
this.logger.error(`Failed to write audit entry: ${error}`);
|
|
142
169
|
}
|
|
143
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Size-based rotation: audit.log -> audit.log.1 -> ... -> audit.log.N,
|
|
173
|
+
* dropping the oldest. Bounds total on-disk audit data to
|
|
174
|
+
* maxBytes * (keepFiles + 1). Best-effort — a failure here must never
|
|
175
|
+
* break the daemon, so errors are logged and writing continues.
|
|
176
|
+
*/
|
|
177
|
+
rotate() {
|
|
178
|
+
try {
|
|
179
|
+
// Drop the oldest, then shift each backup up by one.
|
|
180
|
+
const oldest = `${this.filePath}.${this.keepFiles}`;
|
|
181
|
+
if (existsSync(oldest))
|
|
182
|
+
rmSync(oldest, { force: true });
|
|
183
|
+
for (let i = this.keepFiles - 1; i >= 1; i--) {
|
|
184
|
+
const src = `${this.filePath}.${i}`;
|
|
185
|
+
if (existsSync(src))
|
|
186
|
+
renameSync(src, `${this.filePath}.${i + 1}`);
|
|
187
|
+
}
|
|
188
|
+
if (existsSync(this.filePath))
|
|
189
|
+
renameSync(this.filePath, `${this.filePath}.1`);
|
|
190
|
+
this.curBytes = 0;
|
|
191
|
+
this.logger.info(`Rotated audit log (cap ${Math.round(this.maxBytes / 1048576)} MB, keep ${this.keepFiles})`);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
// Couldn't rotate (permissions, races). Don't let the file grow
|
|
195
|
+
// unbounded: reset the counter so we retry on the next write, but
|
|
196
|
+
// keep the daemon running.
|
|
197
|
+
this.logger.error(`Audit log rotation failed: ${error}`);
|
|
198
|
+
this.curBytes = 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
144
201
|
}
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -10,15 +10,20 @@ import type { DecentAgentNetConfig } from "../types.js";
|
|
|
10
10
|
* merges all their rosters and any one staying up is enough to get an
|
|
11
11
|
* IP allocation — redundancy against any single registry being down.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* The list is loaded from the data file `config/default-doras.yaml` that
|
|
14
|
+
* ships in the npm package (servers are config, not code — edit that file
|
|
15
|
+
* and republish to change the default network). The hardcoded array below
|
|
16
|
+
* is only a last-resort fallback if the data file is missing/unreadable.
|
|
17
|
+
*
|
|
18
|
+
* Each entry needs `userid` (to talk to the server over Carrier) and
|
|
19
|
+
* `address` (so `agentnet init` can send the one-time friend-request).
|
|
16
20
|
*/
|
|
17
|
-
export
|
|
21
|
+
export interface DefaultDora {
|
|
18
22
|
name: string;
|
|
19
23
|
userid: string;
|
|
20
24
|
address: string;
|
|
21
|
-
}
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_DORAS: DefaultDora[];
|
|
22
27
|
export declare const DEFAULT_DORA_USERID: string;
|
|
23
28
|
export declare const DEFAULT_DORA_ADDRESS: string;
|
|
24
29
|
export declare class ConfigLoader {
|
package/dist/config/loader.js
CHANGED
|
@@ -39,27 +39,29 @@ const DEFAULT_BOOTSTRAP_NODES = [
|
|
|
39
39
|
const DEFAULT_EXPRESS_NODES = [
|
|
40
40
|
{ host: "lens.beagle.chat", port: 443, pk: "ECbs4GxwGzxGerNkmqDJFibEmevu8jAXqAZtikccvD95" },
|
|
41
41
|
];
|
|
42
|
-
|
|
43
|
-
* Public dora servers baked into `agentnet init` so an operator can join
|
|
44
|
-
* the canonical Decent AgentNet without first hunting down a registry
|
|
45
|
-
* address. Override by editing `dora.userids` (and re-running
|
|
46
|
-
* `agentnet dora enable --address <yours>`) — or by running a private
|
|
47
|
-
* dora and pointing your own peers at it.
|
|
48
|
-
*
|
|
49
|
-
* These are FEDERATED with NON-OVERLAPPING IP segments, so the client
|
|
50
|
-
* merges all their rosters and any one staying up is enough to get an
|
|
51
|
-
* IP allocation — redundancy against any single registry being down.
|
|
52
|
-
*
|
|
53
|
-
* Both fields are required per entry: the daemon needs `userid` to talk
|
|
54
|
-
* to the server over Carrier, and `address` so `agentnet init` can send
|
|
55
|
-
* the one-time friend-request that establishes the Carrier session.
|
|
56
|
-
*/
|
|
57
|
-
export const DEFAULT_DORAS = [
|
|
42
|
+
const DEFAULT_DORAS_FALLBACK = [
|
|
58
43
|
{ name: "dora-mac", userid: "98rsHv17h8G6AP9RagyrBiT1kmw4cn8MFPEembS6ZVjv", address: "Jt7w1pKkyLT5GVue9h6ZPkjg1EeuuTbD6JVSLycXLsdm6nvBGSUd" }, // 10.86.1.10–63.254
|
|
59
44
|
{ name: "dora-beagle", userid: "AxKFEZFLDi23EmnJFNP6gjUM4CaNMPfWUvbFR9ixtMBN", address: "NsuN81dZdEoyvwEFgWaHkT8SPJB6UWeRmdYcCGFV5CdbbPXoK2RM" }, // 10.86.64.10–127.254
|
|
60
45
|
{ name: "dora-sh", userid: "GMEMLmCWLMBK6BJiMkbLPNkEjF4S2xRf1SqR9hM8fWV3", address: "ajg1ZMBw86UyujmEJzqKSCbi3wwEtg6tdGFTdESakyqujyxmqJZK" }, // 10.86.128.10–191.254
|
|
61
46
|
{ name: "dora-tokyo", userid: "AB6BZfbrTFWw9eUoVpHdJqhhRnY8bTttp4CHTZ2Xfzxi", address: "MAW2eBqBuQ6SmaXTrnZRRayQjAj3aLatwPy4xmBp7spnJeV569op" }, // 10.86.192.10–254.254
|
|
62
47
|
];
|
|
48
|
+
function loadDefaultDoras() {
|
|
49
|
+
// dist/config/loader.js → package root is two levels up; data file ships
|
|
50
|
+
// at <pkg>/config/default-doras.yaml. Same path holds when running from
|
|
51
|
+
// src/ in dev. Fall back to the embedded list if it can't be read.
|
|
52
|
+
try {
|
|
53
|
+
const file = resolve(dirname(new URL(import.meta.url).pathname), "../../config/default-doras.yaml");
|
|
54
|
+
const parsed = yaml.load(readFileSync(file, "utf-8"));
|
|
55
|
+
const doras = (parsed?.doras ?? []).filter((d) => d && d.userid && d.address);
|
|
56
|
+
if (doras.length > 0)
|
|
57
|
+
return doras;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// missing/unreadable/malformed — use the fallback below
|
|
61
|
+
}
|
|
62
|
+
return DEFAULT_DORAS_FALLBACK;
|
|
63
|
+
}
|
|
64
|
+
export const DEFAULT_DORAS = loadDefaultDoras();
|
|
63
65
|
// Back-compat single-value exports (first/primary dora).
|
|
64
66
|
export const DEFAULT_DORA_USERID = DEFAULT_DORAS[0].userid;
|
|
65
67
|
export const DEFAULT_DORA_ADDRESS = DEFAULT_DORAS[0].address;
|
package/dist/daemon/server.js
CHANGED
|
@@ -551,12 +551,19 @@ export class DaemonServer {
|
|
|
551
551
|
allowHosts: this.config.proxy.allowHosts ?? [],
|
|
552
552
|
allowConnectPorts: this.config.proxy.allowConnectPorts,
|
|
553
553
|
resolvePeerName: (srcIp) => this.ipam.resolveIp(srcIp)?.name,
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
554
|
+
// Per-tunnel audit is opt-in: on a busy exit it floods the
|
|
555
|
+
// log (the 13 GB audit.log bug). Off by default; ACL and
|
|
556
|
+
// connect events are still always audited.
|
|
557
|
+
onTunnelOpen: this.config.proxy.auditTunnels
|
|
558
|
+
? ({ src, srcName, target }) => {
|
|
559
|
+
this.auditLog.logProxyOpen({ srcIp: src, srcName, target });
|
|
560
|
+
}
|
|
561
|
+
: undefined,
|
|
562
|
+
onTunnelClose: this.config.proxy.auditTunnels
|
|
563
|
+
? ({ src, srcName, target, bytesTransferred }) => {
|
|
564
|
+
this.auditLog.logProxyClose({ srcIp: src, srcName, target, bytesTransferred });
|
|
565
|
+
}
|
|
566
|
+
: undefined,
|
|
560
567
|
});
|
|
561
568
|
await this.connectProxy.start();
|
|
562
569
|
this.logger.info(`Proxy listening on ${tunIp}:${this.config.proxy.port}`);
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
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",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
20
|
"bin",
|
|
21
|
+
"config/default-doras.yaml",
|
|
21
22
|
"README.md",
|
|
22
23
|
"LICENSE",
|
|
23
24
|
"docs/INSTALL.md",
|