@decentnetwork/lan 0.1.136 → 0.1.138
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/config/default-doras.yaml +0 -4
- package/config/default-exits.yaml +0 -6
- package/dist/cli/commands.d.ts +32 -0
- package/dist/cli/commands.js +139 -0
- package/dist/cli/index.js +19 -1
- package/dist/config/loader.js +0 -1
- package/dist/console/console.js +0 -2
- package/dist/daemon/ipc.d.ts +12 -1
- package/dist/daemon/ipc.js +9 -0
- package/dist/daemon/message-store.d.ts +22 -6
- package/dist/daemon/message-store.js +30 -3
- package/dist/daemon/server.d.ts +19 -0
- package/dist/daemon/server.js +210 -5
- package/dist/proxy/connect-proxy.d.ts +17 -0
- package/dist/proxy/connect-proxy.js +29 -1
- package/dist/proxy/multi-exit-router.js +130 -1
- package/dist/ui/desktop/app.js +67 -39
- package/dist/ui/desktop/index.html +1 -1
- package/dist/ui/server.js +22 -4
- package/docs/INSTALL.md +20 -6
- package/package.json +2 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -17,10 +17,6 @@
|
|
|
17
17
|
# name — label only (logs / comments)
|
|
18
18
|
# segment — informational: the IP band this registry allocates from
|
|
19
19
|
doras:
|
|
20
|
-
- name: dora-mac
|
|
21
|
-
userid: 98rsHv17h8G6AP9RagyrBiT1kmw4cn8MFPEembS6ZVjv
|
|
22
|
-
address: Jt7w1pKkyLT5GVue9h6ZPkjg1EeuuTbD6JVSLycXLsdm6nvBGSUd
|
|
23
|
-
segment: 10.86.1.10-10.86.63.254
|
|
24
20
|
- name: dora-beagle
|
|
25
21
|
userid: AxKFEZFLDi23EmnJFNP6gjUM4CaNMPfWUvbFR9ixtMBN
|
|
26
22
|
address: NsuN81dZdEoyvwEFgWaHkT8SPJB6UWeRmdYcCGFV5CdbbPXoK2RM
|
|
@@ -47,9 +47,3 @@ exits:
|
|
|
47
47
|
userid: 2wErj1XreXt1UchE3FGhuvkZ4GoBpo8JGMn8X49nm2ec
|
|
48
48
|
virtual_ip: 10.86.166.16
|
|
49
49
|
region: us
|
|
50
|
-
# snoopy has NO public IP (LAN 10.0.0.115) — proves a NAT'd node can serve
|
|
51
|
-
# as an exit purely over the Carrier tunnel.
|
|
52
|
-
- name: snoopy
|
|
53
|
-
userid: BeMbuKf1poh3U4rjw3eeUAKZsbN2qBSjVZdPSvehmHsw
|
|
54
|
-
virtual_ip: 10.86.156.164
|
|
55
|
-
region: us
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -149,6 +149,17 @@ export declare function cmdFriendsAccept(args: {
|
|
|
149
149
|
userid: string;
|
|
150
150
|
configDir?: string;
|
|
151
151
|
}): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Show or set friends.autoAccept. With no `mode`, prints the current value.
|
|
154
|
+
* With on|off: if the daemon is up, toggles it live over IPC (no restart) and
|
|
155
|
+
* persists; if it's down, edits config.yaml so the next start picks it up.
|
|
156
|
+
* When OFF, incoming friend-requests queue for manual approval via
|
|
157
|
+
* `agentnet friends pending` (or the UI's Requests panel).
|
|
158
|
+
*/
|
|
159
|
+
export declare function cmdFriendsAutoAccept(args: {
|
|
160
|
+
mode?: string;
|
|
161
|
+
configDir?: string;
|
|
162
|
+
}): Promise<void>;
|
|
152
163
|
/**
|
|
153
164
|
* Drop a queued friend-request without accepting. Sender doesn't
|
|
154
165
|
* get a notification — they just see no acceptance.
|
|
@@ -207,6 +218,27 @@ export declare function cmdProxyRevokeHost(args: {
|
|
|
207
218
|
export declare function cmdProxyListHosts(args: {
|
|
208
219
|
configDir?: string;
|
|
209
220
|
}): Promise<void>;
|
|
221
|
+
/**
|
|
222
|
+
* `agentnet proxy who` — show who is using THIS node's exit right now:
|
|
223
|
+
* per-client active tunnels, total tunnels opened, bytes moved, and refusals.
|
|
224
|
+
* Reads live stats from the running daemon (diag → proxy). Lets an exit
|
|
225
|
+
* operator spot a heavy user before deciding to `agentnet revoke` them.
|
|
226
|
+
*/
|
|
227
|
+
export declare function cmdProxyWho(args: {
|
|
228
|
+
configDir?: string;
|
|
229
|
+
}): Promise<void>;
|
|
230
|
+
/**
|
|
231
|
+
* `agentnet proxy block|allow <peer>` — live per-peer exit access control.
|
|
232
|
+
* Under the default "allow" policy, every friend can tunnel through this exit;
|
|
233
|
+
* `block` adds a deny rule on the proxy port (applied to the running daemon
|
|
234
|
+
* instantly, no restart), `allow` removes it. Accepts a userid or a friendly
|
|
235
|
+
* name (resolved via the live roster).
|
|
236
|
+
*/
|
|
237
|
+
export declare function cmdProxyAccess(args: {
|
|
238
|
+
peer: string;
|
|
239
|
+
allow: boolean;
|
|
240
|
+
configDir?: string;
|
|
241
|
+
}): Promise<void>;
|
|
210
242
|
/**
|
|
211
243
|
* Print the env-var line for a peer to use this node's proxy.
|
|
212
244
|
*/
|
package/dist/cli/commands.js
CHANGED
|
@@ -811,6 +811,49 @@ export async function cmdFriendsAccept(args) {
|
|
|
811
811
|
throw new Error(`Daemon refused: ${res.error}`);
|
|
812
812
|
console.log(`Accepted ${args.userid}.`);
|
|
813
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Show or set friends.autoAccept. With no `mode`, prints the current value.
|
|
816
|
+
* With on|off: if the daemon is up, toggles it live over IPC (no restart) and
|
|
817
|
+
* persists; if it's down, edits config.yaml so the next start picks it up.
|
|
818
|
+
* When OFF, incoming friend-requests queue for manual approval via
|
|
819
|
+
* `agentnet friends pending` (or the UI's Requests panel).
|
|
820
|
+
*/
|
|
821
|
+
export async function cmdFriendsAutoAccept(args) {
|
|
822
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
823
|
+
const configPath = resolve(dir, "config.yaml");
|
|
824
|
+
const config = await ConfigLoader.load(configPath);
|
|
825
|
+
if (!args.mode) {
|
|
826
|
+
const cur = config.friends?.autoAccept ?? true;
|
|
827
|
+
console.log(`friends.autoAccept: ${cur ? "on" : "off"}`);
|
|
828
|
+
console.log(cur
|
|
829
|
+
? " Incoming friend-requests are accepted automatically."
|
|
830
|
+
: " Incoming friend-requests QUEUE for manual approval — 'agentnet friends pending' or the UI.");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const enabled = args.mode === "on";
|
|
834
|
+
let live = false;
|
|
835
|
+
if (daemonPid(config) !== null) {
|
|
836
|
+
// Try the live toggle (no session drop). An older daemon (started before
|
|
837
|
+
// this op existed) rejects it — fall back to persisting config so a restart
|
|
838
|
+
// applies it, rather than erroring out.
|
|
839
|
+
const res = await ipcCall(config, { op: "friends-autoaccept", enabled });
|
|
840
|
+
if (res.ok)
|
|
841
|
+
live = true;
|
|
842
|
+
}
|
|
843
|
+
if (!live) {
|
|
844
|
+
config.friends = { ...(config.friends ?? {}), autoAccept: enabled };
|
|
845
|
+
await ConfigLoader.save(config, configPath);
|
|
846
|
+
}
|
|
847
|
+
console.log(`friends.autoAccept set to ${enabled ? "on" : "off"}${live ? " (live)" : ""}.`);
|
|
848
|
+
if (!live) {
|
|
849
|
+
console.log("Saved to config.yaml — restart the daemon to apply (the running daemon predates the live toggle).");
|
|
850
|
+
}
|
|
851
|
+
if (!enabled) {
|
|
852
|
+
console.log("Incoming friend-requests will then queue — approve with:");
|
|
853
|
+
console.log(" agentnet friends pending # see who's waiting");
|
|
854
|
+
console.log(" agentnet friends accept <userid> # or use the UI Requests panel");
|
|
855
|
+
}
|
|
856
|
+
}
|
|
814
857
|
/**
|
|
815
858
|
* Drop a queued friend-request without accepting. Sender doesn't
|
|
816
859
|
* get a notification — they just see no acceptance.
|
|
@@ -997,6 +1040,102 @@ export async function cmdProxyListHosts(args) {
|
|
|
997
1040
|
for (const h of allowHosts)
|
|
998
1041
|
console.log(` ${h}`);
|
|
999
1042
|
}
|
|
1043
|
+
/**
|
|
1044
|
+
* `agentnet proxy who` — show who is using THIS node's exit right now:
|
|
1045
|
+
* per-client active tunnels, total tunnels opened, bytes moved, and refusals.
|
|
1046
|
+
* Reads live stats from the running daemon (diag → proxy). Lets an exit
|
|
1047
|
+
* operator spot a heavy user before deciding to `agentnet revoke` them.
|
|
1048
|
+
*/
|
|
1049
|
+
export async function cmdProxyWho(args) {
|
|
1050
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
1051
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
1052
|
+
const pid = daemonPid(config);
|
|
1053
|
+
if (pid === null) {
|
|
1054
|
+
console.error("No running daemon found for this config — start it with 'agentnet up'.");
|
|
1055
|
+
process.exit(1);
|
|
1056
|
+
}
|
|
1057
|
+
const res = await ipcCall(config, { op: "diag" });
|
|
1058
|
+
if (!res.ok)
|
|
1059
|
+
throw new Error(`Daemon diag failed: ${res.error}`);
|
|
1060
|
+
const proxy = res.data?.proxy;
|
|
1061
|
+
if (!proxy) {
|
|
1062
|
+
console.log("The CONNECT proxy isn't running on this node (nobody can use it as an exit).");
|
|
1063
|
+
console.log("Enable it with: agentnet proxy enable");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const fmtBytes = (n) => {
|
|
1067
|
+
if (n >= 1024 ** 3)
|
|
1068
|
+
return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
1069
|
+
if (n >= 1024 ** 2)
|
|
1070
|
+
return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
1071
|
+
if (n >= 1024)
|
|
1072
|
+
return `${(n / 1024).toFixed(0)} KB`;
|
|
1073
|
+
return `${n} B`;
|
|
1074
|
+
};
|
|
1075
|
+
const ago = (ms) => {
|
|
1076
|
+
if (!ms)
|
|
1077
|
+
return "—";
|
|
1078
|
+
const s = Math.max(0, Math.floor((Date.now() - ms) / 1000));
|
|
1079
|
+
if (s < 60)
|
|
1080
|
+
return `${s}s ago`;
|
|
1081
|
+
if (s < 3600)
|
|
1082
|
+
return `${Math.floor(s / 60)}m ago`;
|
|
1083
|
+
return `${Math.floor(s / 3600)}h ago`;
|
|
1084
|
+
};
|
|
1085
|
+
console.log(`Exit proxy: ${proxy.active} active tunnel(s), ${proxy.totalOpened} opened total, ` +
|
|
1086
|
+
`${fmtBytes(proxy.bytesTransferred)} moved, ${proxy.totalRefused} refused.`);
|
|
1087
|
+
if (!proxy.peers.length) {
|
|
1088
|
+
console.log("No clients have used this exit yet.");
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
console.log("");
|
|
1092
|
+
console.log(" CLIENT ACTIVE OPENED BYTES REFUSED LAST");
|
|
1093
|
+
for (const p of proxy.peers) {
|
|
1094
|
+
const who = (p.srcName ? `${p.srcName} (${p.src})` : p.src).padEnd(22).slice(0, 22);
|
|
1095
|
+
console.log(` ${who} ${String(p.active).padStart(6)} ${String(p.totalOpened).padStart(6)} ` +
|
|
1096
|
+
`${fmtBytes(p.bytesTransferred).padEnd(11)} ${String(p.totalRefused).padStart(7)} ${ago(p.lastSeenMs)}`);
|
|
1097
|
+
}
|
|
1098
|
+
console.log("");
|
|
1099
|
+
console.log("Cut off a heavy user: agentnet proxy block <userid-or-name>");
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* `agentnet proxy block|allow <peer>` — live per-peer exit access control.
|
|
1103
|
+
* Under the default "allow" policy, every friend can tunnel through this exit;
|
|
1104
|
+
* `block` adds a deny rule on the proxy port (applied to the running daemon
|
|
1105
|
+
* instantly, no restart), `allow` removes it. Accepts a userid or a friendly
|
|
1106
|
+
* name (resolved via the live roster).
|
|
1107
|
+
*/
|
|
1108
|
+
export async function cmdProxyAccess(args) {
|
|
1109
|
+
const dir = args.configDir || ConfigLoader.defaultConfigDir();
|
|
1110
|
+
const config = await ConfigLoader.load(resolve(dir, "config.yaml"));
|
|
1111
|
+
const pid = daemonPid(config);
|
|
1112
|
+
if (pid === null) {
|
|
1113
|
+
console.error("No running daemon found for this config — start it with 'agentnet up'.");
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
// Resolve a friendly name to its userid so the deny rule keys on the stable id.
|
|
1117
|
+
let userid = args.peer;
|
|
1118
|
+
try {
|
|
1119
|
+
const { peers } = await fetchLiveIpam(config);
|
|
1120
|
+
const stripDomain = (s) => s.replace(/\.[A-Za-z][A-Za-z0-9-]*$/, "");
|
|
1121
|
+
const hit = peers.find((p) => p.carrierId === args.peer || p.name === args.peer || p.name === stripDomain(args.peer));
|
|
1122
|
+
if (hit?.carrierId)
|
|
1123
|
+
userid = hit.carrierId;
|
|
1124
|
+
}
|
|
1125
|
+
catch {
|
|
1126
|
+
// fall back to the raw arg — the daemon matches on userid OR name
|
|
1127
|
+
}
|
|
1128
|
+
const res = await ipcCall(config, { op: "proxy-access", userid, enabled: args.allow });
|
|
1129
|
+
if (!res.ok)
|
|
1130
|
+
throw new Error(`Daemon refused: ${res.error}`);
|
|
1131
|
+
if (args.allow) {
|
|
1132
|
+
console.log(`✓ ${args.peer} may use this exit again (block removed, live).`);
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
console.log(`✓ ${args.peer} is now BLOCKED from this exit (live — existing tunnels drop on next reconnect).`);
|
|
1136
|
+
console.log(" Re-allow with: agentnet proxy allow " + args.peer);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1000
1139
|
/**
|
|
1001
1140
|
* Print the env-var line for a peer to use this node's proxy.
|
|
1002
1141
|
*/
|
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, cmdFriendsPending, cmdFriendsAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdProxyRouter, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
13
|
+
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyWho, cmdProxyAccess, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdProxyRouter, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
14
14
|
async function main() {
|
|
15
15
|
await yargs(hideBin(process.argv))
|
|
16
16
|
.scriptName("agentnet")
|
|
@@ -270,6 +270,11 @@ async function main() {
|
|
|
270
270
|
.positional("alias", { type: "string", demandOption: true, describe: "Local display name" })
|
|
271
271
|
.option("config-dir", { type: "string" }), async (argv) => {
|
|
272
272
|
await cmdFriendAlias({ userid: argv.userid, alias: argv.alias, configDir: argv["config-dir"] });
|
|
273
|
+
})
|
|
274
|
+
.command("autoaccept [mode]", "Auto-accept incoming friend-requests on|off (off = queue for manual approve). No arg shows current.", (yy) => yy
|
|
275
|
+
.positional("mode", { type: "string", choices: ["on", "off"], describe: "on = auto-accept; off = queue for manual approval" })
|
|
276
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
277
|
+
await cmdFriendsAutoAccept({ mode: argv.mode, configDir: argv["config-dir"] });
|
|
273
278
|
})
|
|
274
279
|
.demandCommand(1, "Specify a friends subcommand (run 'agentnet friends --help')"), () => {
|
|
275
280
|
// parent handler — never invoked because demandCommand above
|
|
@@ -312,6 +317,19 @@ async function main() {
|
|
|
312
317
|
})
|
|
313
318
|
.command("list-hosts", "List the CONNECT host allowlist", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
314
319
|
await cmdProxyListHosts({ configDir: argv["config-dir"] });
|
|
320
|
+
})
|
|
321
|
+
.command("who", "Show who is using THIS node's exit (per-peer tunnels + bytes)", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
|
|
322
|
+
await cmdProxyWho({ configDir: argv["config-dir"] });
|
|
323
|
+
})
|
|
324
|
+
.command("block <peer>", "Block a peer from using THIS node's exit (live, no restart)", (yy) => yy
|
|
325
|
+
.positional("peer", { type: "string", demandOption: true })
|
|
326
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
327
|
+
await cmdProxyAccess({ peer: argv.peer, allow: false, configDir: argv["config-dir"] });
|
|
328
|
+
})
|
|
329
|
+
.command("allow <peer>", "Re-allow a previously-blocked peer to use THIS node's exit (live)", (yy) => yy
|
|
330
|
+
.positional("peer", { type: "string", demandOption: true })
|
|
331
|
+
.option("config-dir", { type: "string" }), async (argv) => {
|
|
332
|
+
await cmdProxyAccess({ peer: argv.peer, allow: true, configDir: argv["config-dir"] });
|
|
315
333
|
})
|
|
316
334
|
.command("use", "Print env vars to route HTTPS via a peer's proxy node", (yy) => yy
|
|
317
335
|
.option("peer", { type: "string", demandOption: true, describe: "Peer name or carrier ID" })
|
package/dist/config/loader.js
CHANGED
|
@@ -47,7 +47,6 @@ const DEFAULT_EXPRESS_NODES = [
|
|
|
47
47
|
{ host: "tokyo.fi.chat", port: 8443, pk: "EzpBtoUkjeMQfuLGWwTUbvzCn9rK4J648Ziy21EKxefo" },
|
|
48
48
|
];
|
|
49
49
|
const DEFAULT_DORAS_FALLBACK = [
|
|
50
|
-
{ name: "dora-mac", userid: "98rsHv17h8G6AP9RagyrBiT1kmw4cn8MFPEembS6ZVjv", address: "Jt7w1pKkyLT5GVue9h6ZPkjg1EeuuTbD6JVSLycXLsdm6nvBGSUd" }, // 10.86.1.10–63.254
|
|
51
50
|
{ name: "dora-beagle", userid: "AxKFEZFLDi23EmnJFNP6gjUM4CaNMPfWUvbFR9ixtMBN", address: "NsuN81dZdEoyvwEFgWaHkT8SPJB6UWeRmdYcCGFV5CdbbPXoK2RM" }, // 10.86.64.10–127.254
|
|
52
51
|
{ name: "dora-sh", userid: "GMEMLmCWLMBK6BJiMkbLPNkEjF4S2xRf1SqR9hM8fWV3", address: "ajg1ZMBw86UyujmEJzqKSCbi3wwEtg6tdGFTdESakyqujyxmqJZK" }, // 10.86.128.10–191.254
|
|
53
52
|
{ name: "dora-tokyo", userid: "AB6BZfbrTFWw9eUoVpHdJqhhRnY8bTttp4CHTZ2Xfzxi", address: "MAW2eBqBuQ6SmaXTrnZRRayQjAj3aLatwPy4xmBp7spnJeV569op" }, // 10.86.192.10–254.254
|
package/dist/console/console.js
CHANGED
|
@@ -692,8 +692,6 @@ var DEFAULT_EXPRESS_NODES = [
|
|
|
692
692
|
{ host: "tokyo.fi.chat", port: 8443, pk: "EzpBtoUkjeMQfuLGWwTUbvzCn9rK4J648Ziy21EKxefo" }
|
|
693
693
|
];
|
|
694
694
|
var DEFAULT_DORAS_FALLBACK = [
|
|
695
|
-
{ name: "dora-mac", userid: "98rsHv17h8G6AP9RagyrBiT1kmw4cn8MFPEembS6ZVjv", address: "Jt7w1pKkyLT5GVue9h6ZPkjg1EeuuTbD6JVSLycXLsdm6nvBGSUd" },
|
|
696
|
-
// 10.86.1.10–63.254
|
|
697
695
|
{ name: "dora-beagle", userid: "AxKFEZFLDi23EmnJFNP6gjUM4CaNMPfWUvbFR9ixtMBN", address: "NsuN81dZdEoyvwEFgWaHkT8SPJB6UWeRmdYcCGFV5CdbbPXoK2RM" },
|
|
698
696
|
// 10.86.64.10–127.254
|
|
699
697
|
{ name: "dora-sh", userid: "GMEMLmCWLMBK6BJiMkbLPNkEjF4S2xRf1SqR9hM8fWV3", address: "ajg1ZMBw86UyujmEJzqKSCbi3wwEtg6tdGFTdESakyqujyxmqJZK" },
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -51,6 +51,10 @@ export interface IpcHandlers {
|
|
|
51
51
|
friendRemove: (userid: string) => Promise<void>;
|
|
52
52
|
/** Set/clear a local display alias for a friend. */
|
|
53
53
|
friendSetAlias: (userid: string, alias?: string) => Promise<void>;
|
|
54
|
+
/** Toggle whether incoming friend-requests are auto-accepted. Applies live
|
|
55
|
+
* (no restart) and persists to config.yaml. When off, requests queue to
|
|
56
|
+
* friends-pending for manual accept via `agentnet friends pending` / the UI. */
|
|
57
|
+
friendsAutoAccept: (enabled: boolean) => Promise<Record<string, unknown>>;
|
|
54
58
|
/** Update our own profile (display name + description); persists + re-pushes. */
|
|
55
59
|
setProfile: (info: {
|
|
56
60
|
name?: string;
|
|
@@ -80,6 +84,12 @@ export interface IpcHandlers {
|
|
|
80
84
|
* restart (which drops every Carrier session). Returns the applied
|
|
81
85
|
* allowlist for the CLI to echo. */
|
|
82
86
|
proxyReload: () => Promise<Record<string, unknown>>;
|
|
87
|
+
/** Block or allow a specific peer's access to THIS node's exit proxy port,
|
|
88
|
+
* LIVE (mutates the running ACL + persists policy.yaml). `allow=true`
|
|
89
|
+
* removes the block; `allow=false` adds a deny rule so the peer can no
|
|
90
|
+
* longer tunnel through this exit. Lets an operator cut off a heavy user
|
|
91
|
+
* without restarting the daemon. */
|
|
92
|
+
proxyAccess: (userid: string, allow: boolean) => Promise<Record<string, unknown>>;
|
|
83
93
|
/** Re-exec the running daemon with its original argv. The new
|
|
84
94
|
* process inherits the current process's privileges (so a
|
|
85
95
|
* root-owned daemon re-execs as root with no sudo prompt),
|
|
@@ -90,7 +100,7 @@ export interface IpcHandlers {
|
|
|
90
100
|
selfRestart: () => Promise<Record<string, unknown>>;
|
|
91
101
|
}
|
|
92
102
|
export interface IpcRequest {
|
|
93
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "set-profile" | "file-send" | "chat-mark-read" | "subscribe" | "sign" | "proxy-reload" | "self-restart";
|
|
103
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "chat-mark-read" | "subscribe" | "sign" | "proxy-reload" | "proxy-access" | "self-restart";
|
|
94
104
|
address?: string;
|
|
95
105
|
hello?: string;
|
|
96
106
|
userid?: string;
|
|
@@ -102,6 +112,7 @@ export interface IpcRequest {
|
|
|
102
112
|
before?: number;
|
|
103
113
|
limit?: number;
|
|
104
114
|
ts?: number;
|
|
115
|
+
enabled?: boolean;
|
|
105
116
|
}
|
|
106
117
|
export interface IpcResponseOk {
|
|
107
118
|
ok: true;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -204,6 +204,11 @@ export class IpcServer {
|
|
|
204
204
|
await this.handlers.friendSetAlias(req.userid, req.alias);
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
|
+
case "friends-autoaccept": {
|
|
208
|
+
if (typeof req.enabled !== "boolean")
|
|
209
|
+
throw new Error("enabled (boolean) is required");
|
|
210
|
+
return await this.handlers.friendsAutoAccept(req.enabled);
|
|
211
|
+
}
|
|
207
212
|
case "set-profile": {
|
|
208
213
|
await this.handlers.setProfile({ name: req.name, description: req.description });
|
|
209
214
|
return;
|
|
@@ -228,6 +233,10 @@ export class IpcServer {
|
|
|
228
233
|
}
|
|
229
234
|
case "proxy-reload":
|
|
230
235
|
return await this.handlers.proxyReload();
|
|
236
|
+
case "proxy-access":
|
|
237
|
+
if (!req.userid)
|
|
238
|
+
throw new Error("proxy-access requires a userid");
|
|
239
|
+
return await this.handlers.proxyAccess(req.userid, req.enabled !== false);
|
|
231
240
|
case "self-restart":
|
|
232
241
|
return await this.handlers.selfRestart();
|
|
233
242
|
default:
|
|
@@ -14,15 +14,21 @@ export interface ChatMessage {
|
|
|
14
14
|
ts: number;
|
|
15
15
|
/** Stable per-message id (ts + per-process sequence) for UI keys / dedup. */
|
|
16
16
|
id: string;
|
|
17
|
+
/** Outgoing-text delivery state. "queued" means the peer was offline when the
|
|
18
|
+
* user hit send, so the daemon stored it and will deliver it (in order) the
|
|
19
|
+
* moment the friend reconnects. Cleared once actually sent. */
|
|
20
|
+
status?: "queued" | "sent" | "failed";
|
|
17
21
|
/** Present when this entry is a file transfer rather than a text message.
|
|
18
22
|
* `name`/`size` describe the file; received files (dir:"in") are saved to
|
|
19
23
|
* <configDir>/downloads/<name> and downloadable via the UI. For outgoing
|
|
20
24
|
* files, `status` tracks delivery and `sent` is the acked byte count (live
|
|
21
|
-
* progress) — the receiver confirms every byte before status becomes "sent".
|
|
25
|
+
* progress) — the receiver confirms every byte before status becomes "sent".
|
|
26
|
+
* "queued" mirrors text: the peer was offline, so the bytes live in
|
|
27
|
+
* <configDir>/outbox/<id> until the friend reconnects. */
|
|
22
28
|
file?: {
|
|
23
29
|
name: string;
|
|
24
30
|
size: number;
|
|
25
|
-
status?: "sending" | "sent" | "failed";
|
|
31
|
+
status?: "queued" | "sending" | "sent" | "failed";
|
|
26
32
|
sent?: number;
|
|
27
33
|
};
|
|
28
34
|
}
|
|
@@ -35,19 +41,29 @@ export declare class MessageStore {
|
|
|
35
41
|
private seq;
|
|
36
42
|
constructor(path: string);
|
|
37
43
|
private load;
|
|
38
|
-
/** Append a message and schedule a flush. Returns the stored message.
|
|
39
|
-
|
|
44
|
+
/** Append a message and schedule a flush. Returns the stored message.
|
|
45
|
+
* Pass `status: "queued"` for an outgoing text the peer wasn't online to
|
|
46
|
+
* receive — the daemon flushes it on reconnect. */
|
|
47
|
+
append(peer: string, dir: "in" | "out", text: string, ts?: number, status?: "queued" | "sent" | "failed"): ChatMessage;
|
|
48
|
+
/** Set (or, with undefined, clear) the delivery status on a text message.
|
|
49
|
+
* Used to flip a "queued" message to delivered once it's actually sent.
|
|
50
|
+
* No-op if the id isn't found or is a file entry. Returns true if patched. */
|
|
51
|
+
setStatus(peer: string, id: string, status?: "queued" | "sent" | "failed"): boolean;
|
|
52
|
+
/** Outgoing messages still awaiting delivery (peer was offline), oldest
|
|
53
|
+
* first — both queued text and queued file chips. The daemon drains this
|
|
54
|
+
* on a friend's reconnect. */
|
|
55
|
+
queuedOutgoing(peer: string): ChatMessage[];
|
|
40
56
|
/** Append a file-transfer entry (shown as a file chip in the UI). */
|
|
41
57
|
appendFile(peer: string, dir: "in" | "out", file: {
|
|
42
58
|
name: string;
|
|
43
59
|
size: number;
|
|
44
|
-
status?: "sending" | "sent" | "failed";
|
|
60
|
+
status?: "queued" | "sending" | "sent" | "failed";
|
|
45
61
|
sent?: number;
|
|
46
62
|
}, ts?: number): ChatMessage;
|
|
47
63
|
/** Patch an existing file message's transfer fields (status / sent bytes).
|
|
48
64
|
* No-op if the id isn't found. Returns true if it patched. */
|
|
49
65
|
patchFile(peer: string, id: string, patch: {
|
|
50
|
-
status?: "sending" | "sent" | "failed";
|
|
66
|
+
status?: "queued" | "sending" | "sent" | "failed";
|
|
51
67
|
sent?: number;
|
|
52
68
|
}): boolean;
|
|
53
69
|
private push;
|
|
@@ -43,9 +43,36 @@ export class MessageStore {
|
|
|
43
43
|
this.logger.warn(`Could not load ${this.path}: ${err}`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
/** Append a message and schedule a flush. Returns the stored message.
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
/** Append a message and schedule a flush. Returns the stored message.
|
|
47
|
+
* Pass `status: "queued"` for an outgoing text the peer wasn't online to
|
|
48
|
+
* receive — the daemon flushes it on reconnect. */
|
|
49
|
+
append(peer, dir, text, ts = Date.now(), status) {
|
|
50
|
+
const msg = { dir, text, ts, id: `${ts}-${this.seq++}` };
|
|
51
|
+
if (status)
|
|
52
|
+
msg.status = status;
|
|
53
|
+
return this.push(peer, msg);
|
|
54
|
+
}
|
|
55
|
+
/** Set (or, with undefined, clear) the delivery status on a text message.
|
|
56
|
+
* Used to flip a "queued" message to delivered once it's actually sent.
|
|
57
|
+
* No-op if the id isn't found or is a file entry. Returns true if patched. */
|
|
58
|
+
setStatus(peer, id, status) {
|
|
59
|
+
const arr = this.byPeer.get(peer);
|
|
60
|
+
const msg = arr?.find((m) => m.id === id);
|
|
61
|
+
if (!msg || msg.file)
|
|
62
|
+
return false;
|
|
63
|
+
if (status)
|
|
64
|
+
msg.status = status;
|
|
65
|
+
else
|
|
66
|
+
delete msg.status;
|
|
67
|
+
this.scheduleSave();
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/** Outgoing messages still awaiting delivery (peer was offline), oldest
|
|
71
|
+
* first — both queued text and queued file chips. The daemon drains this
|
|
72
|
+
* on a friend's reconnect. */
|
|
73
|
+
queuedOutgoing(peer) {
|
|
74
|
+
const arr = this.byPeer.get(peer) ?? [];
|
|
75
|
+
return arr.filter((m) => m.dir === "out" && (m.status === "queued" || m.file?.status === "queued"));
|
|
49
76
|
}
|
|
50
77
|
/** Append a file-transfer entry (shown as a file chip in the UI). */
|
|
51
78
|
appendFile(peer, dir, file, ts = Date.now()) {
|
package/dist/daemon/server.d.ts
CHANGED
|
@@ -44,6 +44,15 @@ export declare class DaemonServer {
|
|
|
44
44
|
/** Outgoing file transfers in flight: fileId → the chat message tracking it,
|
|
45
45
|
* so progress/complete/cancel events can patch its status + sent bytes. */
|
|
46
46
|
private readonly activeSends;
|
|
47
|
+
/** Directory holding queued (offline) outgoing file bytes: <configDir>/outbox.
|
|
48
|
+
* One file per queued message, named by its msgId. Set in start(). */
|
|
49
|
+
private outboxDir;
|
|
50
|
+
/** Peers whose outbox is currently being drained, so overlapping
|
|
51
|
+
* friend-connection "connected" events don't double-send. */
|
|
52
|
+
private readonly flushingOutbox;
|
|
53
|
+
/** Largest file we'll hold in the offline outbox. Live transfers handle any
|
|
54
|
+
* size; the queue is meant for small things the peer can grab on reconnect. */
|
|
55
|
+
private static readonly OUTBOX_MAX_FILE_BYTES;
|
|
47
56
|
private startedAt;
|
|
48
57
|
private isRunning;
|
|
49
58
|
private tunRebuildInProgress;
|
|
@@ -83,6 +92,16 @@ export declare class DaemonServer {
|
|
|
83
92
|
* restarts). Also ensures a friend-meta entry exists so the friend shows up
|
|
84
93
|
* in the UI list even before it's been renamed. */
|
|
85
94
|
private logChat;
|
|
95
|
+
/** On startup, re-queue any offline file transfer that was mid-flight when the
|
|
96
|
+
* daemon last stopped: an out chip stuck at "sending" whose bytes still sit in
|
|
97
|
+
* the outbox. Flips it back to "queued" so the next reconnect redelivers it
|
|
98
|
+
* (and the bytes aren't orphaned). */
|
|
99
|
+
private reconcileOutboxOnStart;
|
|
100
|
+
/** Deliver everything queued for `userid` while they were offline — queued
|
|
101
|
+
* text and queued files, oldest first. Called when a friend reconnects.
|
|
102
|
+
* Stops early if the link drops again, leaving the remainder queued for the
|
|
103
|
+
* next reconnect (true store-and-forward). Re-entrancy-guarded per peer. */
|
|
104
|
+
private flushOutbox;
|
|
86
105
|
/** Per-peer timestamp of the last self-heal friend-request re-send, so
|
|
87
106
|
* the watchdog escalates at most once per SELF_HEAL_REFRIEND_MS. */
|
|
88
107
|
private reFriendAt;
|