nodio-cli 1.0.2 → 1.0.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/package.json +1 -1
- package/src/node/index.js +1 -1
- package/src/node/runtime.js +11 -2
- package/src/node/storage.js +37 -0
- package/src/server/routes.js +58 -17
package/package.json
CHANGED
package/src/node/index.js
CHANGED
package/src/node/runtime.js
CHANGED
|
@@ -85,11 +85,18 @@ class NodioNodeRuntime {
|
|
|
85
85
|
res.json({ ok: true, nodeId: this.nodeId });
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
await new Promise((resolve) => {
|
|
89
|
-
app.listen(this.port, () => {
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
const server = app.listen({ port: this.port, exclusive: true }, () => {
|
|
90
90
|
console.log(`Nodio node ${this.nodeId} listening on port ${this.port}`);
|
|
91
91
|
resolve();
|
|
92
92
|
});
|
|
93
|
+
|
|
94
|
+
server.once('error', (error) => {
|
|
95
|
+
if (error && error.code === 'EADDRINUSE') {
|
|
96
|
+
return reject(new Error(`port ${this.port} is already in use`));
|
|
97
|
+
}
|
|
98
|
+
return reject(error);
|
|
99
|
+
});
|
|
93
100
|
});
|
|
94
101
|
}
|
|
95
102
|
|
|
@@ -101,11 +108,13 @@ class NodioNodeRuntime {
|
|
|
101
108
|
async registerNode() {
|
|
102
109
|
const deviceKey = await getOrCreateDeviceKey();
|
|
103
110
|
const nodeKey = await this.shardStore.getOrCreateNodeKey();
|
|
111
|
+
const knownNodeIds = await this.shardStore.discoverKnownNodeIds();
|
|
104
112
|
|
|
105
113
|
const response = await axios.post(`${this.serverUrl}/api/nodes/register`, {
|
|
106
114
|
nodeId: this.nodeId,
|
|
107
115
|
deviceKey,
|
|
108
116
|
nodeKey,
|
|
117
|
+
knownNodeIds,
|
|
109
118
|
url: this.publicUrl,
|
|
110
119
|
capacityBytes: this.capacityBytes,
|
|
111
120
|
freeBytes: await this.freeBytes()
|
package/src/node/storage.js
CHANGED
|
@@ -67,6 +67,43 @@ class LocalShardStore {
|
|
|
67
67
|
return identity.nodeId || null;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
async discoverKnownNodeIds() {
|
|
71
|
+
const known = new Set();
|
|
72
|
+
|
|
73
|
+
const current = await this.readIdentity();
|
|
74
|
+
if (current.nodeId) {
|
|
75
|
+
known.add(current.nodeId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const parentDir = path.dirname(this.baseDir);
|
|
79
|
+
let entries = [];
|
|
80
|
+
try {
|
|
81
|
+
entries = await fs.readdir(parentDir, { withFileTypes: true });
|
|
82
|
+
} catch {
|
|
83
|
+
return [...known];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry.isDirectory()) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const identityPath = path.join(parentDir, entry.name, 'node-identity.json');
|
|
92
|
+
try {
|
|
93
|
+
// eslint-disable-next-line no-await-in-loop
|
|
94
|
+
const raw = await fs.readFile(identityPath, 'utf-8');
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
if (parsed?.nodeId && typeof parsed.nodeId === 'string') {
|
|
97
|
+
known.add(parsed.nodeId);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [...known];
|
|
105
|
+
}
|
|
106
|
+
|
|
70
107
|
async saveAssignedNodeId(nodeId) {
|
|
71
108
|
if (!nodeId) {
|
|
72
109
|
return;
|
package/src/server/routes.js
CHANGED
|
@@ -31,7 +31,7 @@ function buildRoutes(config) {
|
|
|
31
31
|
|
|
32
32
|
router.post('/nodes/register', async (req, res, next) => {
|
|
33
33
|
try {
|
|
34
|
-
const { nodeId, deviceKey, nodeKey, url, capacityBytes, freeBytes } = req.body;
|
|
34
|
+
const { nodeId, deviceKey, nodeKey, knownNodeIds, url, capacityBytes, freeBytes } = req.body;
|
|
35
35
|
|
|
36
36
|
if (!url) {
|
|
37
37
|
return res.status(400).json({ error: 'url is required' });
|
|
@@ -45,6 +45,13 @@ function buildRoutes(config) {
|
|
|
45
45
|
if (deviceKey && typeof deviceKey !== 'string') {
|
|
46
46
|
return res.status(400).json({ error: 'deviceKey must be a string when provided' });
|
|
47
47
|
}
|
|
48
|
+
if (knownNodeIds !== undefined && !Array.isArray(knownNodeIds)) {
|
|
49
|
+
return res.status(400).json({ error: 'knownNodeIds must be an array when provided' });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalizedKnownNodeIds = Array.isArray(knownNodeIds)
|
|
53
|
+
? [...new Set(knownNodeIds.filter((value) => typeof value === 'string' && value.length > 0))]
|
|
54
|
+
: [];
|
|
48
55
|
|
|
49
56
|
let existingByNodeKey = null;
|
|
50
57
|
if (nodeKey) {
|
|
@@ -55,24 +62,58 @@ function buildRoutes(config) {
|
|
|
55
62
|
return res.status(409).json({ error: 'nodeKey is already associated with a different nodeId' });
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
let effectiveNodeId = nodeId || existingByNodeKey?.nodeId || null;
|
|
66
|
+
let claimedKnownNode = null;
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
if (!effectiveNodeId && deviceKey && normalizedKnownNodeIds.length > 0) {
|
|
69
|
+
claimedKnownNode = await NodeModel.findOneAndUpdate(
|
|
70
|
+
{
|
|
71
|
+
deviceKey,
|
|
72
|
+
status: 'offline',
|
|
73
|
+
nodeId: { $in: normalizedKnownNodeIds }
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
$set: {
|
|
77
|
+
url,
|
|
78
|
+
capacityBytes: capacity,
|
|
79
|
+
freeBytes: free,
|
|
80
|
+
status: 'online',
|
|
81
|
+
lastHeartbeatAt: new Date(),
|
|
82
|
+
...(nodeKey ? { nodeKey } : {})
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
new: true,
|
|
87
|
+
sort: { createdAt: 1 }
|
|
72
88
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (claimedKnownNode) {
|
|
92
|
+
effectiveNodeId = claimedKnownNode.nodeId;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!effectiveNodeId) {
|
|
97
|
+
effectiveNodeId = `donor-${uuidv4().slice(0, 8)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const node = claimedKnownNode
|
|
101
|
+
|| await NodeModel.findOneAndUpdate(
|
|
102
|
+
{ nodeId: effectiveNodeId },
|
|
103
|
+
{
|
|
104
|
+
$set: {
|
|
105
|
+
nodeId: effectiveNodeId,
|
|
106
|
+
...(deviceKey ? { deviceKey } : {}),
|
|
107
|
+
...(nodeKey ? { nodeKey } : {}),
|
|
108
|
+
url,
|
|
109
|
+
capacityBytes: capacity,
|
|
110
|
+
freeBytes: free,
|
|
111
|
+
status: 'online',
|
|
112
|
+
lastHeartbeatAt: new Date()
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
116
|
+
);
|
|
76
117
|
|
|
77
118
|
res.json({
|
|
78
119
|
nodeId: node.nodeId,
|