nodio-cli 1.0.4 → 1.0.5
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/package.json +1 -1
- package/src/node/index.js +29 -2
- package/src/server/routes.js +15 -0
package/package.json
CHANGED
package/src/node/index.js
CHANGED
|
@@ -46,13 +46,35 @@ async function findFreePort(startPort, endPort) {
|
|
|
46
46
|
throw new Error(`no free port found in range ${startPort}-${endPort}`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function detectAdvertisedHost() {
|
|
50
|
+
const interfaces = os.networkInterfaces();
|
|
51
|
+
for (const addresses of Object.values(interfaces)) {
|
|
52
|
+
if (!Array.isArray(addresses)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const address of addresses) {
|
|
57
|
+
if (address.family === 'IPv4' && !address.internal) {
|
|
58
|
+
return address.address;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return '127.0.0.1';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isLoopbackHost(host) {
|
|
67
|
+
const normalized = String(host || '').trim().toLowerCase();
|
|
68
|
+
return normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1';
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
program
|
|
50
72
|
.name('nodio-node')
|
|
51
73
|
.description('Nodio donor node CLI')
|
|
52
74
|
.argument('[capacity]', 'optional capacity shorthand, e.g. 10gb')
|
|
53
75
|
.option('--node-id <id>', 'unique node ID (optional; auto-assigned and persisted if omitted)')
|
|
54
76
|
.option('--server <url>', 'central server URL', 'https://api.nodio.me')
|
|
55
|
-
.option('--host <host>', 'host/IP exposed to network', '
|
|
77
|
+
.option('--host <host>', 'host/IP exposed to network (default: auto-detected LAN IP)', 'auto')
|
|
56
78
|
.option('--port <port>', 'port to expose shard API (auto-picked when omitted)')
|
|
57
79
|
.option('--storage-dir <path>', 'local shard storage directory (defaults to ~/.nodio-nodes/node-<port>)')
|
|
58
80
|
.option('--capacity-gb <gb>', 'donated capacity in GB', '10')
|
|
@@ -70,6 +92,7 @@ program.action(async (capacityArg, options) => {
|
|
|
70
92
|
const parsedCapacityArg = parseCapacityGb(capacityArg);
|
|
71
93
|
const capacityGb = parsedCapacityArg ?? Number(options.capacityGb);
|
|
72
94
|
const heartbeatMs = Number(options.heartbeatMs);
|
|
95
|
+
const advertisedHost = options.host === 'auto' ? detectAdvertisedHost() : options.host;
|
|
73
96
|
const storageDir = options.storageDir
|
|
74
97
|
? path.resolve(options.storageDir)
|
|
75
98
|
: path.join(os.homedir(), '.nodio-nodes', `node-${port}`);
|
|
@@ -87,10 +110,14 @@ program.action(async (capacityArg, options) => {
|
|
|
87
110
|
throw new Error('heartbeat-ms must be greater than 0');
|
|
88
111
|
}
|
|
89
112
|
|
|
113
|
+
if (isLoopbackHost(advertisedHost)) {
|
|
114
|
+
console.warn('[nodio-node] warning: loopback host is advertised; only this machine can reach this donor');
|
|
115
|
+
}
|
|
116
|
+
|
|
90
117
|
const runtime = new NodioNodeRuntime({
|
|
91
118
|
nodeId: options.nodeId,
|
|
92
119
|
serverUrl: options.server,
|
|
93
|
-
publicUrl: `http://${
|
|
120
|
+
publicUrl: `http://${advertisedHost}:${port}`,
|
|
94
121
|
port,
|
|
95
122
|
storageDir,
|
|
96
123
|
capacityBytes: Math.floor(capacityGb * 1024 * 1024 * 1024),
|
package/src/server/routes.js
CHANGED
|
@@ -58,6 +58,10 @@ function buildRoutes(config) {
|
|
|
58
58
|
existingByNodeKey = await NodeModel.findOne({ nodeKey }).lean();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
if (existingByNodeKey && deviceKey && existingByNodeKey.deviceKey && existingByNodeKey.deviceKey !== deviceKey) {
|
|
62
|
+
return res.status(409).json({ error: 'nodeKey belongs to a different device' });
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
if (existingByNodeKey && nodeId && nodeId !== existingByNodeKey.nodeId) {
|
|
62
66
|
return res.status(409).json({ error: 'nodeKey is already associated with a different nodeId' });
|
|
63
67
|
}
|
|
@@ -65,6 +69,17 @@ function buildRoutes(config) {
|
|
|
65
69
|
let effectiveNodeId = nodeId || existingByNodeKey?.nodeId || null;
|
|
66
70
|
let claimedKnownNode = null;
|
|
67
71
|
|
|
72
|
+
if (effectiveNodeId && deviceKey) {
|
|
73
|
+
const existingByNodeId = await NodeModel.findOne({ nodeId: effectiveNodeId }).lean();
|
|
74
|
+
if (
|
|
75
|
+
existingByNodeId
|
|
76
|
+
&& existingByNodeId.deviceKey
|
|
77
|
+
&& existingByNodeId.deviceKey !== deviceKey
|
|
78
|
+
) {
|
|
79
|
+
return res.status(409).json({ error: 'nodeId belongs to a different device' });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
if (!effectiveNodeId && deviceKey && normalizedKnownNodeIds.length > 0) {
|
|
69
84
|
claimedKnownNode = await NodeModel.findOneAndUpdate(
|
|
70
85
|
{
|