mslxdff 0.1.23 → 0.1.25
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/mslxdff.js +88 -23
- package/package.json +1 -1
package/bin/mslxdff.js
CHANGED
|
@@ -253,24 +253,60 @@ if (groupCmd && groupCmd !== "create") {
|
|
|
253
253
|
continue;
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
256
|
+
const entries = Object.entries(members).filter(([id]) => id !== "leader");
|
|
257
|
+
console.log(`${g.name} (${entries.length} member${entries.length === 1 ? "" : "s"})`);
|
|
258
|
+
// probe every member endpoint concurrently for reachability + latency;
|
|
259
|
+
// display order = state order so the sequence numbers stay stable for -group remove
|
|
260
|
+
const probes = await Promise.all(
|
|
261
|
+
entries.map(([id, m]) => probeHealth({ id, url: m?.url || id }))
|
|
262
|
+
);
|
|
263
|
+
const leaderEntry = members.leader ? Object.entries(members).find(([id]) => id === "leader") : null;
|
|
264
|
+
const leaderProbe = leaderEntry ? await probeHealth({ id: "leader", url: leaderEntry[1].url }) : null;
|
|
265
|
+
const display = leaderProbe ? [...probes, leaderProbe] : probes;
|
|
266
|
+
let seq = 0;
|
|
267
|
+
for (const r of display) {
|
|
265
268
|
const state = r.fail ? `fail ${r.fail}` : `ok ${r.ms}ms`;
|
|
266
|
-
|
|
267
|
-
|
|
269
|
+
if (r.id === "leader") {
|
|
270
|
+
console.log(` leader ${r.url} ${state}`);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
seq += 1;
|
|
274
|
+
const label = r.id && r.id !== r.url ? ` [${r.id}]` : "";
|
|
275
|
+
console.log(` ${seq}. ${r.url}${label} ${state}`);
|
|
268
276
|
}
|
|
269
277
|
}
|
|
270
278
|
console.log(`\njoined groups (${joinedList.length}):`);
|
|
271
279
|
for (const g of joinedList) console.log(` ${g.name} ${g.leaderUrl || "(this node is the leader)"}`);
|
|
280
|
+
} else if (action === "remove" && a) {
|
|
281
|
+
// leader-only: kick a member by its list sequence number (1-based, leader excluded)
|
|
282
|
+
const seq = Number(a);
|
|
283
|
+
if (!Number.isInteger(seq) || seq < 1) {
|
|
284
|
+
console.error(`usage: mslxdff -group remove <seq>`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
const joined = loadGroupsJoined().find((g) => !g.leaderUrl);
|
|
288
|
+
if (!joined) {
|
|
289
|
+
console.error("group remove requires being the leader — this node leads no group");
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
const members = groups.list()[joined.name]?.members || {};
|
|
293
|
+
const entries = Object.entries(members).filter(([id]) => id !== "leader");
|
|
294
|
+
const target = entries[seq - 1];
|
|
295
|
+
if (!target) {
|
|
296
|
+
console.error(`member #${seq} not found — group "${joined.name}" has ${entries.length} member(s)`);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
const [id, m] = target;
|
|
300
|
+
try {
|
|
301
|
+
const removed = groups.removeMember(joined.name, { url: m.url });
|
|
302
|
+
if (removed) console.log(`removed ${m.url} from "${joined.name}"`);
|
|
303
|
+
else console.log(`member ${m.url} already gone from "${joined.name}"`);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.error(`remove failed: ${errMsg(err)}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
272
308
|
} else {
|
|
273
|
-
console.error("usage: mslxdff -
|
|
309
|
+
console.error("usage: mslxdff -group sync | -group leave <name> | -group list | -group remove <seq> | -creategroup <name>");
|
|
274
310
|
}
|
|
275
311
|
process.exit(0);
|
|
276
312
|
}
|
|
@@ -331,11 +367,10 @@ function markJoined(entry) {
|
|
|
331
367
|
saveGroupsJoined([...list, entry]);
|
|
332
368
|
}
|
|
333
369
|
|
|
334
|
-
// -leavegroup | -leave-groups: leave every joined group
|
|
335
|
-
//
|
|
370
|
+
// -leavegroup | -leave-groups: leave every joined group as a member.
|
|
371
|
+
// Leaders cannot "leave" — they must disband with -delgroup <name>.
|
|
336
372
|
const leaveAll = args.includes("-leavegroup") || args.includes("--leavegroup") || args.includes("-leave-groups");
|
|
337
373
|
if (leaveAll) {
|
|
338
|
-
const groups = createGroupsService({});
|
|
339
374
|
const peers = createPeersService({});
|
|
340
375
|
const joined = loadGroupsJoined();
|
|
341
376
|
if (!joined.length) {
|
|
@@ -343,10 +378,11 @@ if (leaveAll) {
|
|
|
343
378
|
process.exit(0);
|
|
344
379
|
}
|
|
345
380
|
const myToken = (await loadToken()).token;
|
|
381
|
+
const leaders = [];
|
|
346
382
|
for (const g of joined) {
|
|
347
|
-
const peersRemoved = peers.removeByGroup(g.name);
|
|
348
383
|
if (g.leaderUrl) {
|
|
349
384
|
// member: deregister from the leader so we stop showing up in -group list
|
|
385
|
+
const peersRemoved = peers.removeByGroup(g.name);
|
|
350
386
|
try {
|
|
351
387
|
const res = await fetch(`${g.leaderUrl}/v1/groups/leave`, {
|
|
352
388
|
method: "POST",
|
|
@@ -362,15 +398,42 @@ if (leaveAll) {
|
|
|
362
398
|
console.log(`${g.name}: left locally (leader unreachable: ${errMsg(err)})`);
|
|
363
399
|
}
|
|
364
400
|
} else {
|
|
365
|
-
|
|
366
|
-
const disbanded = groups.delete(g.name);
|
|
367
|
-
const members = Object.values(disbanded?.members || {});
|
|
368
|
-
console.log(`${g.name}: disbanded (${members.length} member${members.length === 1 ? "" : "s"} removed)`);
|
|
401
|
+
leaders.push(g.name);
|
|
369
402
|
}
|
|
370
403
|
}
|
|
371
|
-
const left = joined.map((g) => g.name);
|
|
404
|
+
const left = joined.filter((g) => g.leaderUrl).map((g) => g.name);
|
|
372
405
|
saveGroupsJoined(loadGroupsJoined().filter((g) => !left.includes(g.name)));
|
|
373
406
|
console.log(`left ${left.length} group(s)`);
|
|
407
|
+
if (leaders.length) {
|
|
408
|
+
console.log(`\nskipped ${leaders.length} group(s) where this node is the leader:`);
|
|
409
|
+
for (const n of leaders) console.log(` ${n} — leaders can't leave; disband it with: mslxdff -delgroup ${n}`);
|
|
410
|
+
}
|
|
411
|
+
process.exit(0);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// -delgroup <name>: a leader disbands one of its groups (local groups state +
|
|
415
|
+
// any member records). Fails if this node is not the leader of that group.
|
|
416
|
+
const delGroupName = argValue("-delgroup", "--delgroup");
|
|
417
|
+
if (delGroupName) {
|
|
418
|
+
const groups = createGroupsService({});
|
|
419
|
+
const peers = createPeersService({});
|
|
420
|
+
const local = groups.list()[delGroupName];
|
|
421
|
+
if (!local) {
|
|
422
|
+
const joined0 = loadGroupsJoined().find((g) => g.name === delGroupName);
|
|
423
|
+
if (joined0?.leaderUrl) {
|
|
424
|
+
console.log(`"${delGroupName}" is led by ${joined0.leaderUrl} — you are a member, use -leavegroup to leave it`);
|
|
425
|
+
} else if (joined0) {
|
|
426
|
+
console.log(`"${delGroupName}" exists in local state but has no group definition — nothing to delete`);
|
|
427
|
+
} else {
|
|
428
|
+
console.log(`group "${delGroupName}" not found on this node`);
|
|
429
|
+
}
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
const disbanded = groups.delete(delGroupName);
|
|
433
|
+
const members = Object.values(disbanded?.members || {});
|
|
434
|
+
peers.removeByGroup(delGroupName);
|
|
435
|
+
saveGroupsJoined(loadGroupsJoined().filter((g) => g.name !== delGroupName));
|
|
436
|
+
console.log(`group "${delGroupName}" disbanded (${members.length} member${members.length === 1 ? "" : "s"} removed)`);
|
|
374
437
|
process.exit(0);
|
|
375
438
|
}
|
|
376
439
|
|
|
@@ -687,8 +750,10 @@ Usage:
|
|
|
687
750
|
mslxdff -addtogroup <leader-host> <name> join a group via its leader host (default port 8989)
|
|
688
751
|
mslxdff -group sync pull the freshest member list for all joined groups
|
|
689
752
|
mslxdff -group leave <name> leave a group (removes its members from this node)
|
|
690
|
-
mslxdff -group list list groups on this node
|
|
691
|
-
mslxdff -
|
|
753
|
+
mslxdff -group list list groups on this node (numbered members)
|
|
754
|
+
mslxdff -group remove <seq> leader only: kick a member by its list sequence number
|
|
755
|
+
mslxdff -leavegroup leave every joined group as a member (leaders: use -delgroup)
|
|
756
|
+
mslxdff -delgroup <name> disband a group this node leads (deletes it and its members)
|
|
692
757
|
mslxdff -resetban [ip] clear join-failure bans (all, or one ip)
|
|
693
758
|
mslxdff -help show this help
|
|
694
759
|
|