@fibscope/agent 0.1.2 → 0.1.3
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/.env.example +2 -0
- package/README.md +5 -3
- package/index.mjs +34 -10
- package/lib/public-peer-address.mjs +38 -0
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -5,3 +5,5 @@ FIBSCOPE_AGENT_CHAIN=testnet
|
|
|
5
5
|
FIBSCOPE_AGENT_FIBER_RPC_URL=http://127.0.0.1:8227
|
|
6
6
|
FIBSCOPE_AGENT_PUSH_INTERVAL_MS=15000
|
|
7
7
|
FIBSCOPE_AGENT_PUBLIC_PROFILE=false
|
|
8
|
+
# Optional override; by default the agent discovers its own public IPv4.
|
|
9
|
+
# FIBSCOPE_AGENT_PUBLIC_IP=54.179.226.154
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ The agent also supports `.fiber/agent.json`:
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
The
|
|
51
|
+
The agent discovers its own public IPv4 through `https://api.ipify.org`, combines it with the local FNN P2P port, and includes both the IP and resulting public multiaddr in registration and report requests. Loopback, wildcard, private, and documentation addresses are never published. Set `FIBSCOPE_AGENT_PUBLIC_IP` only when intentionally overriding automatic discovery, or `FIBSCOPE_AGENT_PUBLIC_IP_URL` to use another compatible JSON or plain-text discovery endpoint.
|
|
52
52
|
|
|
53
53
|
## Commands
|
|
54
54
|
|
|
@@ -74,9 +74,11 @@ The agent pushes to `POST /agent/v1/report` with:
|
|
|
74
74
|
|
|
75
75
|
```json
|
|
76
76
|
{
|
|
77
|
-
"agentVersion": "0.1.
|
|
77
|
+
"agentVersion": "0.1.3",
|
|
78
78
|
"chain": "testnet",
|
|
79
79
|
"collectedAt": 1710000000000,
|
|
80
|
+
"publicIp": "54.179.226.154",
|
|
81
|
+
"peerAddress": "/ip4/54.179.226.154/tcp/8228",
|
|
80
82
|
"node": {},
|
|
81
83
|
"peers": [],
|
|
82
84
|
"channels": [],
|
|
@@ -117,7 +119,7 @@ The Agent Setup page generates this command from a masked password field. The `f
|
|
|
117
119
|
## NPM Usage
|
|
118
120
|
|
|
119
121
|
```bash
|
|
120
|
-
npm install -g @fibscope/agent
|
|
122
|
+
npm install -g @fibscope/agent pm2 && agent init && pm2 start "$(command -v agent)" --name fibscope-agent --interpreter node -- start && pm2 save
|
|
121
123
|
```
|
|
122
124
|
|
|
123
125
|
Or run without a global install:
|
package/index.mjs
CHANGED
|
@@ -31,15 +31,17 @@ import { createTelemetryCollector } from "./lib/observe/collector.mjs";
|
|
|
31
31
|
import { recommend } from "./lib/bridge/recommendation.mjs";
|
|
32
32
|
import { virtualizePayment } from "./lib/route/route.mjs";
|
|
33
33
|
import { chooseFnnInstance, describeFnnInstance, discoverFnnInstances, rpcResponds } from "./lib/fnn-discovery.mjs";
|
|
34
|
-
import { buildObservedPeerAddress,
|
|
34
|
+
import { buildObservedPeerAddress, discoverPublicIpv4, publishablePeerAddresses } from "./lib/public-peer-address.mjs";
|
|
35
35
|
|
|
36
|
-
const AGENT_VERSION = "0.1.
|
|
36
|
+
const AGENT_VERSION = "0.1.3";
|
|
37
37
|
const DEFAULT_CONFIG_PATH = ".fiber/agent.json";
|
|
38
38
|
const DEFAULT_STATUS_PATH = ".fiber/agent-status.json";
|
|
39
39
|
const DEFAULT_PUSH_INTERVAL_MS = 15_000;
|
|
40
40
|
const MAX_BACKOFF_MS = 300_000;
|
|
41
41
|
const DEFAULT_PULSE_MODULE = new URL("./lib/pulse/index.mjs", import.meta.url).href;
|
|
42
42
|
const DEFAULT_FNN_PREFLIGHT_TIMEOUT_MS = 2_500;
|
|
43
|
+
const DEFAULT_PUBLIC_IP_DISCOVERY_URL = "https://api.ipify.org?format=json";
|
|
44
|
+
const DEFAULT_PUBLIC_IP_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
43
45
|
const DEFAULT_REPORT_URL = "https://ejchwtvdxpyojxsursmh.supabase.co/functions/v1/fibscope-gateway/agent/v1/report";
|
|
44
46
|
const AGENT_ENV_PATHS = [resolve("agent/.env"), resolve(".env")];
|
|
45
47
|
const ENV_REPORT_URL = "FIBGATE";
|
|
@@ -367,9 +369,8 @@ export async function createRuntime(options = {}) {
|
|
|
367
369
|
const statusPath = resolve(options.status ?? DEFAULT_STATUS_PATH);
|
|
368
370
|
const config = await loadConfig(configPath, options);
|
|
369
371
|
await ensureFnnPreflight(config, statusPath, options);
|
|
372
|
+
await resolveAgentPublicIdentity(config);
|
|
370
373
|
const remoteConfig = await fetchAgentConfig(config).catch(() => undefined);
|
|
371
|
-
config.observedPublicIp = remoteConfig?.observedPublicIp ?? config.observedPublicIp;
|
|
372
|
-
config.observedPeerAddress = remoteConfig?.observedPeerAddress ?? config.observedPeerAddress;
|
|
373
374
|
if (!config.nodeDisplayName && remoteConfig?.node?.displayName) {
|
|
374
375
|
config.nodeDisplayName = remoteConfig.node.displayName;
|
|
375
376
|
}
|
|
@@ -481,6 +482,8 @@ export async function collectReport(config, previousReport = undefined) {
|
|
|
481
482
|
agentVersion: AGENT_VERSION,
|
|
482
483
|
chain: config.chain ?? "testnet",
|
|
483
484
|
collectedAt,
|
|
485
|
+
publicIp: config.publicIp,
|
|
486
|
+
peerAddress: config.publicPeerAddress,
|
|
484
487
|
node: state.node,
|
|
485
488
|
peers: state.peers ?? [],
|
|
486
489
|
channels: state.channels ?? [],
|
|
@@ -509,8 +512,8 @@ async function enrichReportNode(node, config) {
|
|
|
509
512
|
...await inferPeerAddressesFromLocalFnn(config),
|
|
510
513
|
]);
|
|
511
514
|
const addresses = publishablePeerAddresses([
|
|
512
|
-
config.
|
|
513
|
-
buildObservedPeerAddress(config.
|
|
515
|
+
config.publicPeerAddress,
|
|
516
|
+
buildObservedPeerAddress(config.publicIp, [config.fnnP2pListenAddr, ...candidates]),
|
|
514
517
|
...candidates,
|
|
515
518
|
]);
|
|
516
519
|
const enriched = {
|
|
@@ -2294,6 +2297,8 @@ async function loadConfig(configPath, options = {}) {
|
|
|
2294
2297
|
ckbIndexerUrl: process.env.FIBSCOPE_AGENT_CKB_INDEXER_URL,
|
|
2295
2298
|
walletPath: process.env.FIBSCOPE_AGENT_WALLET_PATH ?? envValue(ENV_WALLET_PATH),
|
|
2296
2299
|
peerAddress: process.env.FIBSCOPE_AGENT_PEER_ADDRESS,
|
|
2300
|
+
publicIp: process.env.FIBSCOPE_AGENT_PUBLIC_IP,
|
|
2301
|
+
publicIpDiscoveryUrl: process.env.FIBSCOPE_AGENT_PUBLIC_IP_URL,
|
|
2297
2302
|
};
|
|
2298
2303
|
label = "agent/.env";
|
|
2299
2304
|
} else if (!existsSync(configPath)) {
|
|
@@ -2315,10 +2320,6 @@ function resolveReportUrl(config = {}) {
|
|
|
2315
2320
|
|
|
2316
2321
|
async function fetchAgentConfig(config) {
|
|
2317
2322
|
const configUrl = new URL(config.configUrl ?? config.reportUrl.replace(/\/report\/?$/, "/config"));
|
|
2318
|
-
configUrl.searchParams.set("p2pPort", String(peerTcpPort([
|
|
2319
|
-
config.fnnP2pListenAddr,
|
|
2320
|
-
...configuredPeerAddresses(config),
|
|
2321
|
-
], 8228)));
|
|
2322
2323
|
const response = await fetch(configUrl, {
|
|
2323
2324
|
headers: {
|
|
2324
2325
|
"x-agent-token": config.agentToken,
|
|
@@ -2368,6 +2369,8 @@ async function registerAgentNode(config, report, remoteConfig) {
|
|
|
2368
2369
|
displayName: config.nodeDisplayName ?? remoteConfig?.node?.displayName,
|
|
2369
2370
|
publicProfileEnabled: Boolean(report.publicProfileEnabled),
|
|
2370
2371
|
overwrite: Boolean(config.overwriteNodeRegistration),
|
|
2372
|
+
publicIp: report.publicIp ?? config.publicIp,
|
|
2373
|
+
peerAddress: report.peerAddress ?? config.publicPeerAddress,
|
|
2371
2374
|
node: report.node ?? {},
|
|
2372
2375
|
}),
|
|
2373
2376
|
});
|
|
@@ -2505,6 +2508,8 @@ function normalizeConfig(config, label) {
|
|
|
2505
2508
|
ckbRpcUrl: process.env.FIBSCOPE_AGENT_CKB_RPC_URL ?? envValue(ENV_CKB_RPC_URL) ?? config.ckbRpcUrl,
|
|
2506
2509
|
ckbIndexerUrl: process.env.FIBSCOPE_AGENT_CKB_INDEXER_URL ?? config.ckbIndexerUrl,
|
|
2507
2510
|
walletPath: process.env.FIBSCOPE_AGENT_WALLET_PATH ?? envValue(ENV_WALLET_PATH) ?? config.walletPath,
|
|
2511
|
+
publicIp: process.env.FIBSCOPE_AGENT_PUBLIC_IP ?? config.publicIp,
|
|
2512
|
+
publicIpDiscoveryUrl: process.env.FIBSCOPE_AGENT_PUBLIC_IP_URL ?? config.publicIpDiscoveryUrl,
|
|
2508
2513
|
pushIntervalMs: positiveNumber(config.pushIntervalMs, DEFAULT_PUSH_INTERVAL_MS),
|
|
2509
2514
|
commandPollMs: positiveNumber(config.commandPollMs, DEFAULT_COMMAND_POLL_MS),
|
|
2510
2515
|
knownPeerReconnectMs: positiveNumber(config.knownPeerReconnectMs, DEFAULT_KNOWN_PEER_RECONNECT_MS),
|
|
@@ -2516,6 +2521,25 @@ function normalizeConfig(config, label) {
|
|
|
2516
2521
|
return normalized;
|
|
2517
2522
|
}
|
|
2518
2523
|
|
|
2524
|
+
async function resolveAgentPublicIdentity(config) {
|
|
2525
|
+
try {
|
|
2526
|
+
const publicIp = await discoverPublicIpv4({
|
|
2527
|
+
publicIp: config.publicIp,
|
|
2528
|
+
endpoint: config.publicIpDiscoveryUrl ?? DEFAULT_PUBLIC_IP_DISCOVERY_URL,
|
|
2529
|
+
timeoutMs: config.publicIpDiscoveryTimeoutMs ?? DEFAULT_PUBLIC_IP_DISCOVERY_TIMEOUT_MS,
|
|
2530
|
+
});
|
|
2531
|
+
config.publicIp = publicIp;
|
|
2532
|
+
config.publicPeerAddress = buildObservedPeerAddress(publicIp, [
|
|
2533
|
+
config.fnnP2pListenAddr,
|
|
2534
|
+
...configuredPeerAddresses(config),
|
|
2535
|
+
]);
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
console.warn(`Fibscope agent public IP unavailable: ${message(error)}`);
|
|
2538
|
+
config.publicIp = undefined;
|
|
2539
|
+
config.publicPeerAddress = publishablePeerAddresses(configuredPeerAddresses(config))[0];
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2519
2543
|
async function bindAgentConfigToFnn(config, label, options = {}) {
|
|
2520
2544
|
const next = { ...config };
|
|
2521
2545
|
if (options.fiberRpcUrl) {
|
|
@@ -5,6 +5,39 @@ export function buildObservedPeerAddress(observedPublicIp, candidates = [], fall
|
|
|
5
5
|
return `/ip4/${ip}/tcp/${port}`;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export async function discoverPublicIpv4(options = {}) {
|
|
9
|
+
const configured = normalizePublicIpv4(options.publicIp);
|
|
10
|
+
if (configured) return configured;
|
|
11
|
+
|
|
12
|
+
const endpoint = String(options.endpoint ?? "https://api.ipify.org?format=json").trim();
|
|
13
|
+
if (!endpoint) throw new Error("Public IPv4 discovery endpoint is not configured.");
|
|
14
|
+
const timeoutMs = positiveTimeout(options.timeoutMs, 5_000);
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
17
|
+
try {
|
|
18
|
+
const response = await (options.fetchImpl ?? fetch)(endpoint, {
|
|
19
|
+
headers: { accept: "application/json, text/plain;q=0.9" },
|
|
20
|
+
signal: controller.signal,
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
23
|
+
const body = await response.text();
|
|
24
|
+
let candidate = body;
|
|
25
|
+
try {
|
|
26
|
+
candidate = JSON.parse(body)?.ip ?? body;
|
|
27
|
+
} catch {
|
|
28
|
+
// Configurable plain-text IP endpoints are supported too.
|
|
29
|
+
}
|
|
30
|
+
const publicIp = normalizePublicIpv4(candidate);
|
|
31
|
+
if (!publicIp) throw new Error("response did not contain a public IPv4 address");
|
|
32
|
+
return publicIp;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
35
|
+
throw new Error(`Public IPv4 discovery failed at ${endpoint}: ${detail}`);
|
|
36
|
+
} finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
8
41
|
export function peerTcpPort(values, fallback = 8228) {
|
|
9
42
|
for (const value of flatStrings(values)) {
|
|
10
43
|
const match = value.match(/\/tcp\/(\d{1,5})(?:\/|$)/i);
|
|
@@ -52,3 +85,8 @@ function flatStrings(values) {
|
|
|
52
85
|
const input = Array.isArray(values) ? values.flat(Infinity) : [values];
|
|
53
86
|
return input.map((value) => String(value ?? "").trim()).filter(Boolean);
|
|
54
87
|
}
|
|
88
|
+
|
|
89
|
+
function positiveTimeout(value, fallback) {
|
|
90
|
+
const number = Number(value);
|
|
91
|
+
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
92
|
+
}
|