@decentnetwork/lan 0.1.0 → 0.1.4
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.d.ts +48 -4
- package/dist/cli/commands.js +189 -12
- package/dist/cli/index.js +49 -6
- package/dist/config/loader.js +5 -0
- package/dist/daemon/ipc.d.ts +12 -1
- package/dist/daemon/ipc.js +14 -0
- package/dist/daemon/pending-friends.d.ts +52 -0
- package/dist/daemon/pending-friends.js +80 -0
- package/dist/daemon/server.d.ts +1 -0
- package/dist/daemon/server.js +68 -16
- package/dist/dora/dora-integration.d.ts +6 -0
- package/dist/dora/dora-integration.js +37 -0
- package/dist/types.d.ts +19 -0
- package/docs/CONFIGURATION.md +21 -1
- package/docs/INSTALL.md +10 -7
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -104,6 +104,31 @@ export declare function cmdFriendAccept(args: {
|
|
|
104
104
|
export declare function cmdFriendsList(args: {
|
|
105
105
|
configDir?: string;
|
|
106
106
|
}): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* List queued friend-requests held by the running daemon (the ones
|
|
109
|
+
* received while `friends.autoAccept` was false). Goes over IPC —
|
|
110
|
+
* the daemon must be up.
|
|
111
|
+
*/
|
|
112
|
+
export declare function cmdFriendsPending(args: {
|
|
113
|
+
configDir?: string;
|
|
114
|
+
}): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Accept a queued friend-request by userid. Routes through IPC;
|
|
117
|
+
* the daemon's own Carrier peer does the actual acceptFriendRequest
|
|
118
|
+
* call and drops the entry from disk. No daemon shutdown needed.
|
|
119
|
+
*/
|
|
120
|
+
export declare function cmdFriendsAccept(args: {
|
|
121
|
+
userid: string;
|
|
122
|
+
configDir?: string;
|
|
123
|
+
}): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Drop a queued friend-request without accepting. Sender doesn't
|
|
126
|
+
* get a notification — they just see no acceptance.
|
|
127
|
+
*/
|
|
128
|
+
export declare function cmdFriendsReject(args: {
|
|
129
|
+
userid: string;
|
|
130
|
+
configDir?: string;
|
|
131
|
+
}): Promise<void>;
|
|
107
132
|
/**
|
|
108
133
|
* Show audit log
|
|
109
134
|
*/
|
|
@@ -198,12 +223,20 @@ export declare function cmdDiag(args: {
|
|
|
198
223
|
configDir?: string;
|
|
199
224
|
}): Promise<void>;
|
|
200
225
|
/**
|
|
201
|
-
* Enable dora integration
|
|
202
|
-
*
|
|
203
|
-
*
|
|
226
|
+
* Enable dora integration. Accepts either `--address` (preferred — the
|
|
227
|
+
* full Carrier address of the dora server; we derive its userid and
|
|
228
|
+
* also send the one-time friend-request automatically) or `--userid`
|
|
229
|
+
* (legacy — expects the operator to have already friended the server).
|
|
230
|
+
*
|
|
231
|
+
* The one-shot `--address` path is the right UX: an operator setting
|
|
232
|
+
* up a fresh node only knows "use this dora server, here's its
|
|
233
|
+
* address" — they shouldn't have to know that dora is also a Carrier
|
|
234
|
+
* peer, that they need to friend it first, or what the difference
|
|
235
|
+
* between userid and address is.
|
|
204
236
|
*/
|
|
205
237
|
export declare function cmdDoraEnable(args: {
|
|
206
|
-
|
|
238
|
+
address?: string;
|
|
239
|
+
userid?: string;
|
|
207
240
|
configDir?: string;
|
|
208
241
|
}): Promise<void>;
|
|
209
242
|
/**
|
|
@@ -221,3 +254,14 @@ export declare function cmdDoraDisable(args: {
|
|
|
221
254
|
export declare function cmdDoraStatus(args: {
|
|
222
255
|
configDir?: string;
|
|
223
256
|
}): Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Set the dora auto-friend policy. Three modes:
|
|
259
|
+
* - 'none' (default): never auto-friend roster peers
|
|
260
|
+
* - 'all': friend every roster entry that carries an address
|
|
261
|
+
* - 'allow <id-or-name>...' / 'remove <id-or-name>...': whitelist
|
|
262
|
+
*/
|
|
263
|
+
export declare function cmdDoraAutofriend(args: {
|
|
264
|
+
mode: "none" | "all" | "allow" | "remove" | "list";
|
|
265
|
+
values?: string[];
|
|
266
|
+
configDir?: string;
|
|
267
|
+
}): Promise<void>;
|
package/dist/cli/commands.js
CHANGED
|
@@ -405,8 +405,15 @@ export async function cmdFriendRequest(args) {
|
|
|
405
405
|
const waitMs = args.waitMs ?? 30000;
|
|
406
406
|
console.log(`Waiting ${waitMs}ms for relay delivery...`);
|
|
407
407
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
408
|
+
const myPubkey = peer.pubkey();
|
|
409
|
+
const myUserid = peer.userid();
|
|
408
410
|
await peer.stop();
|
|
409
|
-
console.log(`Friend request sent
|
|
411
|
+
console.log(`Friend request sent.`);
|
|
412
|
+
console.log(`If the recipient's daemon is running with friends.autoAccept (the default), it has already accepted —`);
|
|
413
|
+
console.log(`no action needed on their side. They can confirm with 'agentnet diag' and look for userid ${myUserid}.`);
|
|
414
|
+
console.log(`If their daemon was offline, the request is queued via the express relay; their daemon will`);
|
|
415
|
+
console.log(`pick it up on next start (and still auto-accept). Only run 'agentnet friend-accept --pubkey ${myPubkey}'`);
|
|
416
|
+
console.log(`manually if their daemon is down AND they've disabled autoAccept.`);
|
|
410
417
|
}
|
|
411
418
|
/**
|
|
412
419
|
* Accept a pending friend request.
|
|
@@ -500,10 +507,10 @@ export async function cmdFriendsList(args) {
|
|
|
500
507
|
for (const friend of friends) {
|
|
501
508
|
console.log(``);
|
|
502
509
|
console.log(` ${friend.name || "(unnamed)"} status=${friend.status}`);
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
510
|
+
// userid is Carrier's first-class identifier; show that, hide
|
|
511
|
+
// the raw hex pubkey unless someone explicitly needs it.
|
|
512
|
+
const userid = friend.userid && friend.userid !== friend.pubkey ? friend.userid : friend.pubkey;
|
|
513
|
+
console.log(` userid: ${userid}`);
|
|
507
514
|
if (friend.address)
|
|
508
515
|
console.log(` address: ${friend.address}`);
|
|
509
516
|
if (friend.acceptedAt)
|
|
@@ -511,6 +518,69 @@ export async function cmdFriendsList(args) {
|
|
|
511
518
|
}
|
|
512
519
|
await peer.stop();
|
|
513
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* List queued friend-requests held by the running daemon (the ones
|
|
523
|
+
* received while `friends.autoAccept` was false). Goes over IPC —
|
|
524
|
+
* the daemon must be up.
|
|
525
|
+
*/
|
|
526
|
+
export async function cmdFriendsPending(args) {
|
|
527
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
528
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
529
|
+
if (daemonPid(config) === null) {
|
|
530
|
+
throw new Error("Daemon not running — pending friend-requests are held by the daemon. Start it with 'agentnet up' first.");
|
|
531
|
+
}
|
|
532
|
+
const res = await ipcCall(config, { op: "friends-pending" });
|
|
533
|
+
if (!res.ok)
|
|
534
|
+
throw new Error(`Daemon refused: ${res.error}`);
|
|
535
|
+
const list = res.data?.pending ?? [];
|
|
536
|
+
if (list.length === 0) {
|
|
537
|
+
console.log("No pending friend-requests.");
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
console.log(`Pending friend-requests (${list.length}):`);
|
|
541
|
+
for (const e of list) {
|
|
542
|
+
console.log("");
|
|
543
|
+
console.log(` ${e.name || "(unnamed)"}`);
|
|
544
|
+
console.log(` userid: ${e.userid}`);
|
|
545
|
+
if (e.hello)
|
|
546
|
+
console.log(` hello: ${e.hello}`);
|
|
547
|
+
console.log(` arrived: ${e.arrivedAt}`);
|
|
548
|
+
}
|
|
549
|
+
console.log("");
|
|
550
|
+
console.log("Accept with: agentnet friends accept --userid <USERID>");
|
|
551
|
+
console.log("Reject with: agentnet friends reject --userid <USERID>");
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Accept a queued friend-request by userid. Routes through IPC;
|
|
555
|
+
* the daemon's own Carrier peer does the actual acceptFriendRequest
|
|
556
|
+
* call and drops the entry from disk. No daemon shutdown needed.
|
|
557
|
+
*/
|
|
558
|
+
export async function cmdFriendsAccept(args) {
|
|
559
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
560
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
561
|
+
if (daemonPid(config) === null) {
|
|
562
|
+
throw new Error("Daemon not running. Start it with 'agentnet up' first — pending friend-requests are managed by the live daemon.");
|
|
563
|
+
}
|
|
564
|
+
const res = await ipcCall(config, { op: "friends-accept", userid: args.userid });
|
|
565
|
+
if (!res.ok)
|
|
566
|
+
throw new Error(`Daemon refused: ${res.error}`);
|
|
567
|
+
console.log(`Accepted ${args.userid}.`);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Drop a queued friend-request without accepting. Sender doesn't
|
|
571
|
+
* get a notification — they just see no acceptance.
|
|
572
|
+
*/
|
|
573
|
+
export async function cmdFriendsReject(args) {
|
|
574
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
575
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
576
|
+
if (daemonPid(config) === null) {
|
|
577
|
+
throw new Error("Daemon not running. Start it with 'agentnet up' first.");
|
|
578
|
+
}
|
|
579
|
+
const res = await ipcCall(config, { op: "friends-reject", userid: args.userid });
|
|
580
|
+
if (!res.ok)
|
|
581
|
+
throw new Error(`Daemon refused: ${res.error}`);
|
|
582
|
+
console.log(`Rejected ${args.userid}.`);
|
|
583
|
+
}
|
|
514
584
|
/**
|
|
515
585
|
* Show audit log
|
|
516
586
|
*/
|
|
@@ -861,27 +931,75 @@ export async function cmdDiag(args) {
|
|
|
861
931
|
console.log(JSON.stringify(res.data, null, 2));
|
|
862
932
|
}
|
|
863
933
|
/**
|
|
864
|
-
* Enable dora integration
|
|
865
|
-
*
|
|
866
|
-
*
|
|
934
|
+
* Enable dora integration. Accepts either `--address` (preferred — the
|
|
935
|
+
* full Carrier address of the dora server; we derive its userid and
|
|
936
|
+
* also send the one-time friend-request automatically) or `--userid`
|
|
937
|
+
* (legacy — expects the operator to have already friended the server).
|
|
938
|
+
*
|
|
939
|
+
* The one-shot `--address` path is the right UX: an operator setting
|
|
940
|
+
* up a fresh node only knows "use this dora server, here's its
|
|
941
|
+
* address" — they shouldn't have to know that dora is also a Carrier
|
|
942
|
+
* peer, that they need to friend it first, or what the difference
|
|
943
|
+
* between userid and address is.
|
|
867
944
|
*/
|
|
868
945
|
export async function cmdDoraEnable(args) {
|
|
946
|
+
if (!args.address && !args.userid) {
|
|
947
|
+
throw new Error("Need one of --address (preferred) or --userid");
|
|
948
|
+
}
|
|
949
|
+
// Derive userid from address when given an address. Address format:
|
|
950
|
+
// base58(pubkey || nospam || checksum); userid: base58(pubkey).
|
|
951
|
+
let userid = args.userid;
|
|
952
|
+
if (args.address) {
|
|
953
|
+
const { carrierIdFromAddress } = await import("@decentnetwork/peer");
|
|
954
|
+
try {
|
|
955
|
+
userid = carrierIdFromAddress(args.address);
|
|
956
|
+
}
|
|
957
|
+
catch (err) {
|
|
958
|
+
throw new Error(`--address didn't parse as a Carrier address: ${err instanceof Error ? err.message : err}`);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
869
961
|
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
870
962
|
const configPath = resolve(dir, "config.yaml");
|
|
871
963
|
const config = await ConfigLoader.load(configPath);
|
|
964
|
+
// Send the friend-request first when we have an address. We route
|
|
965
|
+
// it via the daemon's IPC if it's running, or fall back to a
|
|
966
|
+
// standalone short-lived Peer when it isn't (same logic as
|
|
967
|
+
// cmdFriendRequest).
|
|
968
|
+
if (args.address) {
|
|
969
|
+
console.log(`Friending dora server at ${args.address.slice(0, 24)}...`);
|
|
970
|
+
try {
|
|
971
|
+
await cmdFriendRequest({
|
|
972
|
+
address: args.address,
|
|
973
|
+
hello: "decentlan: enable dora",
|
|
974
|
+
waitMs: 8000,
|
|
975
|
+
configDir: args.configDir,
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
catch (err) {
|
|
979
|
+
// If the SDK already knows this address as an accepted friend
|
|
980
|
+
// it short-circuits — treat that as success, not failure.
|
|
981
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
982
|
+
if (!/already (accepted|a friend|friend)/i.test(msg)) {
|
|
983
|
+
console.warn(`(friend-request had a warning: ${msg})`);
|
|
984
|
+
console.warn(`Proceeding with config update anyway — re-run 'agentnet friend-request' later if you see "friend offline" in the daemon log.`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
872
988
|
const existing = config.dora ?? { enabled: false, userids: [], refreshIntervalMs: 60_000 };
|
|
873
989
|
const userids = new Set(existing.userids ?? []);
|
|
874
|
-
userids.add(
|
|
990
|
+
userids.add(userid);
|
|
875
991
|
config.dora = {
|
|
876
992
|
enabled: true,
|
|
877
993
|
userids: [...userids],
|
|
878
994
|
refreshIntervalMs: existing.refreshIntervalMs ?? 60_000,
|
|
879
995
|
};
|
|
880
996
|
await ConfigLoader.save(config, configPath);
|
|
881
|
-
console.log(`Dora enabled. Server userid added: ${
|
|
997
|
+
console.log(`Dora enabled. Server userid added: ${userid}`);
|
|
882
998
|
console.log(`Total dora servers configured: ${config.dora.userids?.length}`);
|
|
883
|
-
|
|
884
|
-
|
|
999
|
+
if (!args.address) {
|
|
1000
|
+
console.log(`Heads-up: passed --userid only; if you haven't friended the dora server, run 'agentnet friend-request --address <ADDRESS>' before starting the daemon.`);
|
|
1001
|
+
}
|
|
1002
|
+
console.log(`Restart the daemon (or just 'agentnet up') to take effect.`);
|
|
885
1003
|
}
|
|
886
1004
|
/**
|
|
887
1005
|
* Disable dora integration without losing the configured server list.
|
|
@@ -927,6 +1045,65 @@ export async function cmdDoraStatus(args) {
|
|
|
927
1045
|
}
|
|
928
1046
|
const refresh = dora.refreshIntervalMs ?? 60_000;
|
|
929
1047
|
console.log(` refresh: every ${Math.round(refresh / 1000)}s`);
|
|
1048
|
+
const af = dora.autoFriend ?? "none";
|
|
1049
|
+
if (af === "none" || af === "all") {
|
|
1050
|
+
console.log(` auto-friend: ${af}`);
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
console.log(` auto-friend: whitelist [${af.length}]: ${af.join(", ")}`);
|
|
1054
|
+
}
|
|
930
1055
|
console.log("");
|
|
931
1056
|
console.log("Note: live state (allocated IP, last refresh) requires querying a running daemon — not yet wired.");
|
|
932
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Set the dora auto-friend policy. Three modes:
|
|
1060
|
+
* - 'none' (default): never auto-friend roster peers
|
|
1061
|
+
* - 'all': friend every roster entry that carries an address
|
|
1062
|
+
* - 'allow <id-or-name>...' / 'remove <id-or-name>...': whitelist
|
|
1063
|
+
*/
|
|
1064
|
+
export async function cmdDoraAutofriend(args) {
|
|
1065
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
1066
|
+
const configPath = resolve(dir, "config.yaml");
|
|
1067
|
+
const config = await ConfigLoader.load(configPath);
|
|
1068
|
+
const dora = config.dora ?? { enabled: false, userids: [], refreshIntervalMs: 60_000 };
|
|
1069
|
+
const current = dora.autoFriend ?? "none";
|
|
1070
|
+
if (args.mode === "list") {
|
|
1071
|
+
if (current === "none" || current === "all") {
|
|
1072
|
+
console.log(`auto-friend: ${current}`);
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
console.log(`auto-friend: whitelist (${current.length})`);
|
|
1076
|
+
for (const t of current)
|
|
1077
|
+
console.log(` - ${t}`);
|
|
1078
|
+
}
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
let next;
|
|
1082
|
+
if (args.mode === "none" || args.mode === "all") {
|
|
1083
|
+
next = args.mode;
|
|
1084
|
+
}
|
|
1085
|
+
else if (args.mode === "allow") {
|
|
1086
|
+
const list = Array.isArray(current) ? new Set(current) : new Set();
|
|
1087
|
+
for (const v of args.values ?? [])
|
|
1088
|
+
list.add(v);
|
|
1089
|
+
next = [...list];
|
|
1090
|
+
}
|
|
1091
|
+
else if (args.mode === "remove") {
|
|
1092
|
+
const list = Array.isArray(current) ? new Set(current) : new Set();
|
|
1093
|
+
for (const v of args.values ?? [])
|
|
1094
|
+
list.delete(v);
|
|
1095
|
+
next = list.size === 0 ? "none" : [...list];
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
throw new Error(`unknown autofriend mode: ${args.mode}`);
|
|
1099
|
+
}
|
|
1100
|
+
config.dora = { ...dora, autoFriend: next };
|
|
1101
|
+
await ConfigLoader.save(config, configPath);
|
|
1102
|
+
if (next === "none" || next === "all") {
|
|
1103
|
+
console.log(`auto-friend set to '${next}'.`);
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
console.log(`auto-friend whitelist now (${next.length}): ${next.join(", ")}`);
|
|
1107
|
+
}
|
|
1108
|
+
console.log(`Takes effect on the next roster refresh (~60s) or daemon restart.`);
|
|
1109
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
10
10
|
// Belt-and-braces — also raise it here in case the CLI is run directly
|
|
11
11
|
// (e.g. `node dist/cli/index.js` rather than via dist/index.js).
|
|
12
12
|
EventEmitter.defaultMaxListeners = 100;
|
|
13
|
-
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDiag, cmdDnsInstall, cmdDnsHosts, } from "./commands.js";
|
|
13
|
+
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDnsInstall, cmdDnsHosts, } from "./commands.js";
|
|
14
14
|
async function main() {
|
|
15
15
|
await yargs(hideBin(process.argv))
|
|
16
16
|
.scriptName("agentnet")
|
|
@@ -126,8 +126,21 @@ async function main() {
|
|
|
126
126
|
configDir: argv["config-dir"],
|
|
127
127
|
});
|
|
128
128
|
})
|
|
129
|
-
.command("friends list", "List Carrier friends", (y) => y.option("config-dir", { type: "string" }), async (argv) => {
|
|
129
|
+
.command("friends list", "List Carrier friends (daemon must be down)", (y) => y.option("config-dir", { type: "string" }), async (argv) => {
|
|
130
130
|
await cmdFriendsList({ configDir: argv["config-dir"] });
|
|
131
|
+
})
|
|
132
|
+
.command("friends pending", "List queued friend-requests (over IPC; daemon must be up)", (y) => y.option("config-dir", { type: "string" }), async (argv) => {
|
|
133
|
+
await cmdFriendsPending({ configDir: argv["config-dir"] });
|
|
134
|
+
})
|
|
135
|
+
.command("friends accept", "Accept a queued friend-request by userid (over IPC; daemon stays up)", (y) => y
|
|
136
|
+
.option("userid", { type: "string", demandOption: true, describe: "Sender's Carrier userid (base58)" })
|
|
137
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
138
|
+
await cmdFriendsAccept({ userid: argv.userid, configDir: argv["config-dir"] });
|
|
139
|
+
})
|
|
140
|
+
.command("friends reject", "Drop a queued friend-request by userid (over IPC; daemon stays up)", (y) => y
|
|
141
|
+
.option("userid", { type: "string", demandOption: true })
|
|
142
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
143
|
+
await cmdFriendsReject({ userid: argv.userid, configDir: argv["config-dir"] });
|
|
131
144
|
})
|
|
132
145
|
.command("audit log", "View audit log", (y) => y.option("tail", { type: "number", default: 50 }).option("config-dir", { type: "string" }), async (argv) => {
|
|
133
146
|
await cmdAuditLog({ tail: argv.tail, configDir: argv["config-dir"] });
|
|
@@ -170,16 +183,46 @@ async function main() {
|
|
|
170
183
|
// parent handler — never invoked because demandCommand above
|
|
171
184
|
})
|
|
172
185
|
.command("dora", "Manage the dora (DHCP-style) registry integration (run 'agentnet dora --help')", (y) => y
|
|
173
|
-
.command("enable", "Enable dora
|
|
174
|
-
.option("
|
|
175
|
-
.option("
|
|
176
|
-
|
|
186
|
+
.command("enable", "Enable dora — preferred: --address <addr> (we friend + derive userid); legacy: --userid <id>", (yy) => yy
|
|
187
|
+
.option("address", { type: "string", describe: "Dora server's full Carrier address — friends it and derives the userid for you" })
|
|
188
|
+
.option("userid", { type: "string", describe: "Legacy: server's userid; only use if you've already friended it separately" })
|
|
189
|
+
.option("config-dir", { type: "string" })
|
|
190
|
+
.check((argv) => {
|
|
191
|
+
if (!argv.address && !argv.userid) {
|
|
192
|
+
throw new Error("Need one of --address (preferred) or --userid");
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}), async (argv) => {
|
|
196
|
+
await cmdDoraEnable({
|
|
197
|
+
address: argv.address,
|
|
198
|
+
userid: argv.userid,
|
|
199
|
+
configDir: argv["config-dir"],
|
|
200
|
+
});
|
|
177
201
|
})
|
|
178
202
|
.command("disable", "Disable dora integration (keep configured server list)", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
179
203
|
await cmdDoraDisable({ configDir: argv["config-dir"] });
|
|
180
204
|
})
|
|
181
205
|
.command("status", "Show dora configuration", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
182
206
|
await cmdDoraStatus({ configDir: argv["config-dir"] });
|
|
207
|
+
})
|
|
208
|
+
.command("autofriend <mode> [values..]", "Auto-friend policy: 'none' (default), 'all', 'allow <name|userid>...', 'remove <name|userid>...', or 'list'", (yy) => yy
|
|
209
|
+
.positional("mode", {
|
|
210
|
+
type: "string",
|
|
211
|
+
demandOption: true,
|
|
212
|
+
choices: ["none", "all", "allow", "remove", "list"],
|
|
213
|
+
describe: "Policy mode",
|
|
214
|
+
})
|
|
215
|
+
.positional("values", {
|
|
216
|
+
type: "string",
|
|
217
|
+
array: true,
|
|
218
|
+
describe: "For 'allow'/'remove': one or more names or userids",
|
|
219
|
+
})
|
|
220
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
221
|
+
await cmdDoraAutofriend({
|
|
222
|
+
mode: argv.mode,
|
|
223
|
+
values: argv.values,
|
|
224
|
+
configDir: argv["config-dir"],
|
|
225
|
+
});
|
|
183
226
|
})
|
|
184
227
|
.demandCommand(1, "Specify a dora subcommand (run 'agentnet dora --help')"), () => {
|
|
185
228
|
// parent handler — never invoked because demandCommand above
|
package/dist/config/loader.js
CHANGED
|
@@ -108,6 +108,11 @@ export class ConfigLoader {
|
|
|
108
108
|
enabled: false,
|
|
109
109
|
userids: [],
|
|
110
110
|
refreshIntervalMs: 60_000,
|
|
111
|
+
// Default: do NOT auto-friend roster peers. Dora is a naming
|
|
112
|
+
// / IP service, not a trust statement. Operators opt in with
|
|
113
|
+
// `agentnet dora autofriend all` (closed labs) or
|
|
114
|
+
// `agentnet dora autofriend allow <name|userid>` (whitelist).
|
|
115
|
+
autoFriend: "none",
|
|
111
116
|
},
|
|
112
117
|
};
|
|
113
118
|
}
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -29,11 +29,22 @@ export interface IpcHandlers {
|
|
|
29
29
|
* Used by `agentnet diag` to inspect a live daemon without
|
|
30
30
|
* restarting it with AGENTNET_LOG_LEVEL=debug. */
|
|
31
31
|
diag: () => Promise<Record<string, unknown>>;
|
|
32
|
+
/** List queued friend-requests that arrived while autoAccept was
|
|
33
|
+
* off. Returns an array of {userid, name, hello, arrivedAt}. */
|
|
34
|
+
friendsPending: () => Promise<Record<string, unknown>>;
|
|
35
|
+
/** Accept a queued friend-request by userid. Drops it from the
|
|
36
|
+
* queue and calls peer.acceptFriendRequest. Idempotent — no-op
|
|
37
|
+
* if userid isn't in the queue. */
|
|
38
|
+
friendsAccept: (userid: string) => Promise<void>;
|
|
39
|
+
/** Reject (drop) a queued friend-request by userid. Doesn't
|
|
40
|
+
* notify the sender — they'll just see no acceptance. */
|
|
41
|
+
friendsReject: (userid: string) => Promise<void>;
|
|
32
42
|
}
|
|
33
43
|
export interface IpcRequest {
|
|
34
|
-
op: "friend-request" | "ping" | "diag";
|
|
44
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject";
|
|
35
45
|
address?: string;
|
|
36
46
|
hello?: string;
|
|
47
|
+
userid?: string;
|
|
37
48
|
}
|
|
38
49
|
export interface IpcResponseOk {
|
|
39
50
|
ok: true;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -130,6 +130,20 @@ export class IpcServer {
|
|
|
130
130
|
}
|
|
131
131
|
case "diag":
|
|
132
132
|
return await this.handlers.diag();
|
|
133
|
+
case "friends-pending":
|
|
134
|
+
return await this.handlers.friendsPending();
|
|
135
|
+
case "friends-accept": {
|
|
136
|
+
if (!req.userid)
|
|
137
|
+
throw new Error("userid is required");
|
|
138
|
+
await this.handlers.friendsAccept(req.userid);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
case "friends-reject": {
|
|
142
|
+
if (!req.userid)
|
|
143
|
+
throw new Error("userid is required");
|
|
144
|
+
await this.handlers.friendsReject(req.userid);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
133
147
|
default:
|
|
134
148
|
throw new Error(`unknown op: ${req.op}`);
|
|
135
149
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-friend-request store.
|
|
3
|
+
*
|
|
4
|
+
* When `friends.autoAccept` is false (or for any other reason the
|
|
5
|
+
* operator wants to gate inbound friend requests), the daemon must
|
|
6
|
+
* NOT block the running session waiting for a CLI dance. It buffers
|
|
7
|
+
* each incoming request to disk and exposes IPC ops so an operator
|
|
8
|
+
* can list / accept / reject them on their own time, without
|
|
9
|
+
* stopping the daemon. Lost-on-restart pending requests would be
|
|
10
|
+
* frustrating, so the queue is persisted.
|
|
11
|
+
*
|
|
12
|
+
* File format: a single JSON array under
|
|
13
|
+
* `<configDir>/pending-friends.json`. Each entry is the same shape
|
|
14
|
+
* the SDK emits in the friend-request event, plus an arrivedAt
|
|
15
|
+
* timestamp.
|
|
16
|
+
*/
|
|
17
|
+
export interface PendingFriendEntry {
|
|
18
|
+
/** Carrier userid (base58, same 32 pubkey bytes as the hex form
|
|
19
|
+
* the SDK emits — we store the userid form because that's what
|
|
20
|
+
* every user-facing surface uses). */
|
|
21
|
+
userid: string;
|
|
22
|
+
/** Hex pubkey, kept for compatibility with SDK calls that expect
|
|
23
|
+
* that form (acceptFriendRequest takes the pubkey/userid string
|
|
24
|
+
* the original event carried). */
|
|
25
|
+
pubkey: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
hello?: string;
|
|
28
|
+
/** ISO 8601 timestamp; helps operators triage queues of pending
|
|
29
|
+
* requests. */
|
|
30
|
+
arrivedAt: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class PendingFriendsStore {
|
|
33
|
+
private filePath;
|
|
34
|
+
private logger;
|
|
35
|
+
private entries;
|
|
36
|
+
constructor(filePath: string);
|
|
37
|
+
private load;
|
|
38
|
+
private save;
|
|
39
|
+
list(): PendingFriendEntry[];
|
|
40
|
+
/**
|
|
41
|
+
* Add (or replace) a pending request. The same userid arriving
|
|
42
|
+
* twice replaces the older entry — operators see the latest
|
|
43
|
+
* `hello` / `name` / `arrivedAt`, never a stale duplicate.
|
|
44
|
+
*/
|
|
45
|
+
add(entry: PendingFriendEntry): void;
|
|
46
|
+
/**
|
|
47
|
+
* Drop an entry by userid. Returns the removed entry (so the
|
|
48
|
+
* caller has the original pubkey to pass to the SDK's
|
|
49
|
+
* acceptFriendRequest), or null if not present.
|
|
50
|
+
*/
|
|
51
|
+
remove(userid: string): PendingFriendEntry | null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending-friend-request store.
|
|
3
|
+
*
|
|
4
|
+
* When `friends.autoAccept` is false (or for any other reason the
|
|
5
|
+
* operator wants to gate inbound friend requests), the daemon must
|
|
6
|
+
* NOT block the running session waiting for a CLI dance. It buffers
|
|
7
|
+
* each incoming request to disk and exposes IPC ops so an operator
|
|
8
|
+
* can list / accept / reject them on their own time, without
|
|
9
|
+
* stopping the daemon. Lost-on-restart pending requests would be
|
|
10
|
+
* frustrating, so the queue is persisted.
|
|
11
|
+
*
|
|
12
|
+
* File format: a single JSON array under
|
|
13
|
+
* `<configDir>/pending-friends.json`. Each entry is the same shape
|
|
14
|
+
* the SDK emits in the friend-request event, plus an arrivedAt
|
|
15
|
+
* timestamp.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
18
|
+
import { dirname } from "path";
|
|
19
|
+
import { Logger } from "../utils/logger.js";
|
|
20
|
+
export class PendingFriendsStore {
|
|
21
|
+
filePath;
|
|
22
|
+
logger;
|
|
23
|
+
entries = [];
|
|
24
|
+
constructor(filePath) {
|
|
25
|
+
this.filePath = filePath;
|
|
26
|
+
this.logger = new Logger({ prefix: "PendingFriends" });
|
|
27
|
+
this.load();
|
|
28
|
+
}
|
|
29
|
+
load() {
|
|
30
|
+
if (!existsSync(this.filePath))
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const raw = readFileSync(this.filePath, "utf-8");
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
if (Array.isArray(parsed)) {
|
|
36
|
+
this.entries = parsed.filter((e) => typeof e === "object" &&
|
|
37
|
+
e !== null &&
|
|
38
|
+
typeof e.userid === "string");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
this.logger.warn(`Could not parse ${this.filePath}: ${err}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
save() {
|
|
46
|
+
try {
|
|
47
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
48
|
+
writeFileSync(this.filePath, JSON.stringify(this.entries, null, 2), "utf-8");
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
this.logger.warn(`Could not write ${this.filePath}: ${err}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
list() {
|
|
55
|
+
return [...this.entries];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add (or replace) a pending request. The same userid arriving
|
|
59
|
+
* twice replaces the older entry — operators see the latest
|
|
60
|
+
* `hello` / `name` / `arrivedAt`, never a stale duplicate.
|
|
61
|
+
*/
|
|
62
|
+
add(entry) {
|
|
63
|
+
this.entries = this.entries.filter((e) => e.userid !== entry.userid);
|
|
64
|
+
this.entries.push(entry);
|
|
65
|
+
this.save();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Drop an entry by userid. Returns the removed entry (so the
|
|
69
|
+
* caller has the original pubkey to pass to the SDK's
|
|
70
|
+
* acceptFriendRequest), or null if not present.
|
|
71
|
+
*/
|
|
72
|
+
remove(userid) {
|
|
73
|
+
const idx = this.entries.findIndex((e) => e.userid === userid);
|
|
74
|
+
if (idx < 0)
|
|
75
|
+
return null;
|
|
76
|
+
const [removed] = this.entries.splice(idx, 1);
|
|
77
|
+
this.save();
|
|
78
|
+
return removed;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/daemon/server.d.ts
CHANGED
package/dist/daemon/server.js
CHANGED
|
@@ -16,6 +16,7 @@ import { ConnectProxy } from "../proxy/connect-proxy.js";
|
|
|
16
16
|
import { DoraIntegration } from "../dora/dora-integration.js";
|
|
17
17
|
import { DnsServer } from "../dns/server.js";
|
|
18
18
|
import { IpcServer, ipcSocketPath } from "./ipc.js";
|
|
19
|
+
import { PendingFriendsStore } from "./pending-friends.js";
|
|
19
20
|
import { Logger } from "../utils/logger.js";
|
|
20
21
|
export class DaemonServer {
|
|
21
22
|
config;
|
|
@@ -35,6 +36,7 @@ export class DaemonServer {
|
|
|
35
36
|
doraIntegration;
|
|
36
37
|
ipcServer;
|
|
37
38
|
dnsServer;
|
|
39
|
+
pendingFriends;
|
|
38
40
|
startedAt = 0;
|
|
39
41
|
isRunning = false;
|
|
40
42
|
pidFile;
|
|
@@ -107,6 +109,31 @@ export class DaemonServer {
|
|
|
107
109
|
friendRequest: async (address, hello) => {
|
|
108
110
|
await this.peerManager.sendFriendRequest(address, hello);
|
|
109
111
|
},
|
|
112
|
+
friendsPending: async () => {
|
|
113
|
+
const entries = this.pendingFriends?.list() ?? [];
|
|
114
|
+
// Hide the legacy hex `pubkey` field from the wire — the
|
|
115
|
+
// CLI side only needs userid + metadata to display + accept.
|
|
116
|
+
return {
|
|
117
|
+
pending: entries.map((e) => ({
|
|
118
|
+
userid: e.userid,
|
|
119
|
+
name: e.name,
|
|
120
|
+
hello: e.hello,
|
|
121
|
+
arrivedAt: e.arrivedAt,
|
|
122
|
+
})),
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
friendsAccept: async (userid) => {
|
|
126
|
+
const entry = this.pendingFriends?.remove(userid);
|
|
127
|
+
if (!entry)
|
|
128
|
+
throw new Error(`No pending friend-request for userid ${userid}`);
|
|
129
|
+
await this.peerManager.acceptFriendRequest(entry.pubkey);
|
|
130
|
+
},
|
|
131
|
+
friendsReject: async (userid) => {
|
|
132
|
+
const entry = this.pendingFriends?.remove(userid);
|
|
133
|
+
if (!entry)
|
|
134
|
+
throw new Error(`No pending friend-request for userid ${userid}`);
|
|
135
|
+
// No-op at the Carrier level — sender just sees no acceptance.
|
|
136
|
+
},
|
|
110
137
|
diag: async () => {
|
|
111
138
|
// Snapshot of everything an operator needs to debug why
|
|
112
139
|
// packets aren't moving: forwarding counters, friend
|
|
@@ -188,24 +215,49 @@ export class DaemonServer {
|
|
|
188
215
|
});
|
|
189
216
|
}
|
|
190
217
|
// Live friend-request handling. The daemon stays up and accepts
|
|
191
|
-
// (or
|
|
192
|
-
// ceremony where the operator has to stop the daemon. Default
|
|
193
|
-
// auto-accept; the Carrier friend store IS the trust
|
|
194
|
-
// requests only reach a peer whose address was
|
|
218
|
+
// (or queues) every incoming request — no `friend-accept --wait`
|
|
219
|
+
// ceremony where the operator has to stop the daemon. Default
|
|
220
|
+
// is auto-accept; the Carrier friend store IS the trust
|
|
221
|
+
// boundary, and requests only reach a peer whose address was
|
|
222
|
+
// deliberately shared.
|
|
223
|
+
//
|
|
224
|
+
// When autoAccept is off, the request is persisted to
|
|
225
|
+
// pending-friends.json on disk. The operator inspects/handles
|
|
226
|
+
// it later via the IPC-routed CLI:
|
|
227
|
+
// agentnet friends pending
|
|
228
|
+
// agentnet friends accept --userid <userid>
|
|
229
|
+
// agentnet friends reject --userid <userid>
|
|
230
|
+
// Pending requests survive daemon restarts.
|
|
195
231
|
const autoAcceptFriends = this.config.friends?.autoAccept ?? true;
|
|
232
|
+
const pendingPath = resolve(this.config.carrier.dataDir, "..", "pending-friends.json");
|
|
233
|
+
this.pendingFriends = new PendingFriendsStore(pendingPath);
|
|
234
|
+
const pubkeyHexToUserid = async (pubkeyHex) => {
|
|
235
|
+
const { carrierIdFromPublicKey } = await import("@decentnetwork/peer");
|
|
236
|
+
const bytes = Buffer.from(pubkeyHex, "hex");
|
|
237
|
+
return carrierIdFromPublicKey(new Uint8Array(bytes));
|
|
238
|
+
};
|
|
196
239
|
this.peerManager.on("friend-request", (req) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
this.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`(friends.autoAccept=false
|
|
208
|
-
|
|
240
|
+
void pubkeyHexToUserid(req.pubkey).then((userid) => {
|
|
241
|
+
const who = `${req.name || "(unnamed)"} ${userid}`;
|
|
242
|
+
const hello = req.hello ? ` hello="${req.hello.slice(0, 60)}"` : "";
|
|
243
|
+
if (autoAcceptFriends) {
|
|
244
|
+
this.logger.info(`Friend request from ${who}${hello} — auto-accepting`);
|
|
245
|
+
this.peerManager?.acceptFriendRequest(req.pubkey).catch((err) => {
|
|
246
|
+
this.logger.warn(`Auto-accept failed for ${who}: ${err}`);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
this.logger.info(`Friend request from ${who}${hello} — queued (friends.autoAccept=false). ` +
|
|
251
|
+
`Run 'agentnet friends pending' to triage.`);
|
|
252
|
+
this.pendingFriends?.add({
|
|
253
|
+
userid,
|
|
254
|
+
pubkey: req.pubkey,
|
|
255
|
+
name: req.name,
|
|
256
|
+
hello: req.hello,
|
|
257
|
+
arrivedAt: new Date().toISOString(),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
209
261
|
});
|
|
210
262
|
// NOTE: auto-IPAM (hash-derived virtual IP per userid) was tried and
|
|
211
263
|
// reverted — it produced IPs like 10.86.175.35 that conflicted with
|
|
@@ -87,4 +87,10 @@ export declare class DoraIntegration {
|
|
|
87
87
|
* re-register the peer or fall back to a manual friend-request.
|
|
88
88
|
*/
|
|
89
89
|
private maybeFriend;
|
|
90
|
+
/**
|
|
91
|
+
* Decide whether a roster entry is allowed by the autoFriend
|
|
92
|
+
* policy. Names match case-insensitively against the entry's
|
|
93
|
+
* `name`; userids match exact base58.
|
|
94
|
+
*/
|
|
95
|
+
private policyAllows;
|
|
90
96
|
}
|
|
@@ -304,6 +304,22 @@ export class DoraIntegration {
|
|
|
304
304
|
this.friendRequested.add(entry.userid);
|
|
305
305
|
return;
|
|
306
306
|
}
|
|
307
|
+
// Policy gate. Dora is a name service, not a trust statement.
|
|
308
|
+
// Default ("none") means we DON'T auto-friend roster peers —
|
|
309
|
+
// an operator on a multi-tenant dora isn't asking to be
|
|
310
|
+
// mutually-friended with everyone else just because they share
|
|
311
|
+
// a name service. "all" reproduces the old behavior (closed
|
|
312
|
+
// labs). Otherwise the policy is a whitelist of names or
|
|
313
|
+
// userids; only matches are friended.
|
|
314
|
+
const policy = this.opts.config.autoFriend ?? "none";
|
|
315
|
+
if (!this.policyAllows(entry, policy)) {
|
|
316
|
+
// Mark as "seen" so we don't re-evaluate the policy every
|
|
317
|
+
// 60s for entries we deliberately skip — but DON'T mark as
|
|
318
|
+
// "friendRequested" since the operator may flip the policy
|
|
319
|
+
// later and we should pick them up on next refresh after a
|
|
320
|
+
// daemon restart.
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
307
323
|
if (!entry.address) {
|
|
308
324
|
this.logger.warn(`Roster entry ${entry.name} (${entry.userid.slice(0, 12)}...) has no address — can't auto-friend. ` +
|
|
309
325
|
`Have them re-register against a newer dora server, or run 'agentnet friend-request' manually.`);
|
|
@@ -322,4 +338,25 @@ export class DoraIntegration {
|
|
|
322
338
|
this.friendRequested.delete(entry.userid);
|
|
323
339
|
});
|
|
324
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Decide whether a roster entry is allowed by the autoFriend
|
|
343
|
+
* policy. Names match case-insensitively against the entry's
|
|
344
|
+
* `name`; userids match exact base58.
|
|
345
|
+
*/
|
|
346
|
+
policyAllows(entry, policy) {
|
|
347
|
+
if (policy === "all")
|
|
348
|
+
return true;
|
|
349
|
+
if (policy === "none")
|
|
350
|
+
return false;
|
|
351
|
+
if (!Array.isArray(policy) || policy.length === 0)
|
|
352
|
+
return false;
|
|
353
|
+
const nameLower = entry.name.toLowerCase();
|
|
354
|
+
for (const token of policy) {
|
|
355
|
+
if (token === entry.userid)
|
|
356
|
+
return true;
|
|
357
|
+
if (token.toLowerCase() === nameLower)
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
325
362
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -142,6 +142,25 @@ export interface DoraConfig {
|
|
|
142
142
|
userids?: string[];
|
|
143
143
|
/** How often to re-fetch the full roster from dora. Default 60_000ms. */
|
|
144
144
|
refreshIntervalMs?: number;
|
|
145
|
+
/**
|
|
146
|
+
* Policy for auto-friending peers found in the dora roster. Dora is
|
|
147
|
+
* a NAMING and IP-ALLOCATION service — it does NOT imply trust.
|
|
148
|
+
* On a large dora (many tenants, customer machines, etc.) you do
|
|
149
|
+
* NOT want to auto-friend everyone.
|
|
150
|
+
*
|
|
151
|
+
* - `"none"` (default): never auto-friend roster peers. The operator
|
|
152
|
+
* initiates friend-requests explicitly with `agentnet
|
|
153
|
+
* friend-request --address <addr>`. The dora server itself is
|
|
154
|
+
* always friended out-of-band when you run `agentnet dora enable
|
|
155
|
+
* --address`.
|
|
156
|
+
* - `"all"`: friend every roster entry that carries an address.
|
|
157
|
+
* Useful for a closed lab of trusted peers; **dangerous on a
|
|
158
|
+
* shared / public dora.**
|
|
159
|
+
* - `string[]`: whitelist by peer NAME (matches `name` in the roster
|
|
160
|
+
* record) or by userid (base58). Matching entries are auto-
|
|
161
|
+
* friended on every refresh; everything else is left alone.
|
|
162
|
+
*/
|
|
163
|
+
autoFriend?: "none" | "all" | string[];
|
|
145
164
|
}
|
|
146
165
|
export interface DecentAgentNetConfig {
|
|
147
166
|
node: NodeConfig;
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -112,7 +112,27 @@ ACL-gated; see `agentnet proxy --help`.
|
|
|
112
112
|
- **`userids`** — list of dora servers to try, in order. First
|
|
113
113
|
responder wins.
|
|
114
114
|
- **`refreshIntervalMs`** — how often to re-pull the roster from
|
|
115
|
-
dora (default 60s).
|
|
115
|
+
dora (default 60s).
|
|
116
|
+
- **`autoFriend`** — policy for friending peers found in the dora
|
|
117
|
+
roster. Dora is a name service, NOT a trust statement; on a
|
|
118
|
+
multi-tenant dora you do not want the daemon to silently friend
|
|
119
|
+
every other peer.
|
|
120
|
+
- `"none"` (default) — never auto-friend roster peers; only the
|
|
121
|
+
dora server itself is friended (you do that explicitly with
|
|
122
|
+
`agentnet dora enable --address`).
|
|
123
|
+
- `"all"` — friend every roster entry that carries an address.
|
|
124
|
+
Reasonable for closed labs of mutually-trusted peers.
|
|
125
|
+
- `string[]` — whitelist by peer name OR userid. Only matching
|
|
126
|
+
entries get auto-friended.
|
|
127
|
+
|
|
128
|
+
CLI:
|
|
129
|
+
```
|
|
130
|
+
agentnet dora autofriend none # default
|
|
131
|
+
agentnet dora autofriend all # closed lab
|
|
132
|
+
agentnet dora autofriend allow mac-dev snoopy peerX-userid # whitelist
|
|
133
|
+
agentnet dora autofriend remove snoopy
|
|
134
|
+
agentnet dora autofriend list
|
|
135
|
+
```
|
|
116
136
|
|
|
117
137
|
## `policy.yaml` reference
|
|
118
138
|
|
package/docs/INSTALL.md
CHANGED
|
@@ -41,17 +41,20 @@ agentnet --help
|
|
|
41
41
|
# 1. Generate this machine's Carrier identity + default config
|
|
42
42
|
agentnet init --name my-laptop
|
|
43
43
|
|
|
44
|
-
# 2.
|
|
45
|
-
#
|
|
46
|
-
|
|
44
|
+
# 2. Point at the dora name server. Pass the FULL ADDRESS (whoever
|
|
45
|
+
# runs dora publishes it). This one command friends dora,
|
|
46
|
+
# derives its userid, and writes the config — no separate
|
|
47
|
+
# friend-request step needed.
|
|
48
|
+
agentnet dora enable --address <dora's Carrier address>
|
|
47
49
|
|
|
48
|
-
# 3.
|
|
49
|
-
agentnet dora enable --userid 98rsHv17h8G6AP9RagyrBiT1kmw4cn8MFPEembS6ZVjv
|
|
50
|
-
|
|
51
|
-
# 4. Start the daemon (needs sudo for the TUN device)
|
|
50
|
+
# 3. Start the daemon (needs sudo for the TUN device)
|
|
52
51
|
sudo $(which agentnet) up --real-tun
|
|
53
52
|
```
|
|
54
53
|
|
|
54
|
+
Three commands, no "userid vs address" gymnastics. The daemon will
|
|
55
|
+
register with dora, pull the roster, and auto-friend every other
|
|
56
|
+
peer in the network.
|
|
57
|
+
|
|
55
58
|
The daemon will:
|
|
56
59
|
|
|
57
60
|
1. Open a Carrier connection
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|