@botbuddy/cli 1.8.3 → 1.8.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
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schema_version":1,"source_version":"1.8.3","source_identity":"8a450fe651f42fc96f9fd713da5fe707ff48a914c174496337495adf0c425588"}
|
package/src/docker-hygiene.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { readFileSync } from "node:fs";
|
|
|
8
8
|
import { hostname } from "node:os";
|
|
9
9
|
import { callToolJson } from "./api.mjs";
|
|
10
10
|
import { acquireStackLock, lockPathForProject, projectIdFromConfig } from "./stack-file-lock.mjs";
|
|
11
|
+
import { machineUuid } from "./machine-id.mjs";
|
|
11
12
|
|
|
12
13
|
export const SCHEMA_VERSION = 1;
|
|
13
14
|
export const DEFAULT_PROJECTED_ENDPOINTS = 10;
|
|
@@ -1118,6 +1119,7 @@ async function runDockerCommandWithBotBuddyLock(argv, {
|
|
|
1118
1119
|
runWorkflow = runDockerWorkflow,
|
|
1119
1120
|
workflowOptions = {},
|
|
1120
1121
|
machineHost = hostname(),
|
|
1122
|
+
machineId = machineUuid(),
|
|
1121
1123
|
monotonicNow = Date.now,
|
|
1122
1124
|
} = {}) {
|
|
1123
1125
|
const parsed = parseDockerArgs(argv);
|
|
@@ -1131,6 +1133,10 @@ async function runDockerCommandWithBotBuddyLock(argv, {
|
|
|
1131
1133
|
host: machineHost,
|
|
1132
1134
|
slot: parsed.opts.lockSlot,
|
|
1133
1135
|
mode: "lock",
|
|
1136
|
+
// BOT-1239: report THIS machine's unique id so the supabase_local stack lock states
|
|
1137
|
+
// which physical machine holds it (host is a hostname two machines can share). Only
|
|
1138
|
+
// sent when the probe found one — a NULL degrades to hostname grouping.
|
|
1139
|
+
...(machineId ? { machine_uuid: machineId } : {}),
|
|
1134
1140
|
ticket_id: parsed.opts.ticket,
|
|
1135
1141
|
no_pr_reason: "local OrbStack hygiene",
|
|
1136
1142
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// BOT-1239 — the machine-UNIQUE identity of THIS physical machine.
|
|
2
|
+
//
|
|
3
|
+
// A hostname is not machine-unique (two machines can both be "ubuntu"), so a
|
|
4
|
+
// supabase_local lock keyed only on hostname cannot be attributed to the machine
|
|
5
|
+
// that holds it. This reads the same machine-unique token a BotBuddy Helper attests
|
|
6
|
+
// at enrollment (container_hosts.hardware_uuid) so a lock the CLI acquires reports the
|
|
7
|
+
// SAME identity — letting /docker merge a machine's Helper with its own stack lock and
|
|
8
|
+
// never with a foreign machine's under a shared hostname.
|
|
9
|
+
//
|
|
10
|
+
// * macOS : IOPlatformUUID from `ioreg -rd1 -c IOPlatformExpertDevice` (what the
|
|
11
|
+
// Helper attests, so the two identities match for one machine).
|
|
12
|
+
// * Linux : /etc/machine-id (falling back to /var/lib/dbus/machine-id).
|
|
13
|
+
// * else / on any error : null — the lock then degrades to hostname grouping, never
|
|
14
|
+
// a wrong merge. This must NEVER throw or block a lock acquisition.
|
|
15
|
+
//
|
|
16
|
+
// `BOTBUDDY_MACHINE_UUID` overrides the probe (explicit operator control / tests).
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
|
|
20
|
+
const IOREG_UUID_RE = /"IOPlatformUUID"\s*=\s*"([^"]+)"/;
|
|
21
|
+
|
|
22
|
+
export function readMachineUuid({
|
|
23
|
+
platform = process.platform,
|
|
24
|
+
env = process.env,
|
|
25
|
+
exec = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8", timeout: 4000 }),
|
|
26
|
+
readFile = (p) => readFileSync(p, "utf8"),
|
|
27
|
+
} = {}) {
|
|
28
|
+
try {
|
|
29
|
+
const override = (env.BOTBUDDY_MACHINE_UUID || "").trim();
|
|
30
|
+
if (override) return override;
|
|
31
|
+
|
|
32
|
+
if (platform === "darwin") {
|
|
33
|
+
const out = exec("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
|
|
34
|
+
if (out && out.status === 0 && typeof out.stdout === "string") {
|
|
35
|
+
const m = out.stdout.match(IOREG_UUID_RE);
|
|
36
|
+
const uuid = m && m[1] ? m[1].trim() : "";
|
|
37
|
+
return uuid || null;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (platform === "linux") {
|
|
43
|
+
for (const path of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
44
|
+
try {
|
|
45
|
+
const id = String(readFile(path) || "").trim();
|
|
46
|
+
if (id) return id;
|
|
47
|
+
} catch { /* try the next source */ }
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
} catch {
|
|
54
|
+
// A machine id is a best-effort disambiguator; never let it fail an acquire.
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let _cached;
|
|
60
|
+
let _cachedComputed = false;
|
|
61
|
+
|
|
62
|
+
// Cached, zero-argument accessor for the real process. Tests call readMachineUuid
|
|
63
|
+
// directly with injected dependencies instead of hitting this cache.
|
|
64
|
+
export function machineUuid() {
|
|
65
|
+
if (!_cachedComputed) {
|
|
66
|
+
_cached = readMachineUuid();
|
|
67
|
+
_cachedComputed = true;
|
|
68
|
+
}
|
|
69
|
+
return _cached;
|
|
70
|
+
}
|