fix: replace MAC address with CPU serial / disk serial / DMI UUID for box_id
Made-with: Cursor
This commit is contained in:
@@ -1,43 +1,120 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const os = require('os');
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成硬件唯一指纹作为 box_id,优先级:
|
* 生成硬件唯一指纹作为 box_id,优先级:
|
||||||
* 1. /etc/machine-id (systemd 生成,现代 Linux 标配)
|
*
|
||||||
* 2. /proc/sys/kernel/random/boot_id (内核 boot UUID,重启会变但稳定)
|
* 1. /etc/machine-id — systemd 生成,现代 Linux 标配,重装系统才变
|
||||||
* 3. 第一块网卡 MAC 地址的 SHA-256 前 16 字节
|
* 2. CPU Serial — /proc/cpuinfo 中的 Serial 字段(ARM/RPi 常见)
|
||||||
* 4. 随机 UUID(最后兜底,存入配置防止每次变化)
|
* 3. 主存储设备序列号 — /sys/block/<dev>/device/serial(sda/nvme/mmcblk)
|
||||||
|
* 4. DMI 产品 UUID — /sys/class/dmi/id/product_uuid(x86 主板)
|
||||||
|
* 5. 随机 UUID 持久化 — 生成后写入 /etc/clawd/.box_id,确保重启不变
|
||||||
|
*
|
||||||
|
* 注意:MAC 地址故意排除,网卡更换/虚拟化/Docker 都会导致其变化。
|
||||||
*/
|
*/
|
||||||
function getBoxId() {
|
|
||||||
// 1. /etc/machine-id
|
const PERSIST_FILE = '/etc/clawd/.box_id';
|
||||||
|
|
||||||
|
// ── 1. /etc/machine-id ───────────────────────────────────────────────────────
|
||||||
|
function getMachineId() {
|
||||||
try {
|
try {
|
||||||
const id = fs.readFileSync('/etc/machine-id', 'utf8').trim();
|
const id = fs.readFileSync('/etc/machine-id', 'utf8').trim();
|
||||||
if (id && id.length >= 16) return id;
|
if (id && /^[0-9a-f]{32}$/i.test(id)) return id;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. boot_id
|
// ── 2. CPU Serial(ARM / Raspberry Pi)───────────────────────────────────────
|
||||||
|
function getCpuSerial() {
|
||||||
try {
|
try {
|
||||||
const id = fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim().replace(/-/g, '');
|
const info = fs.readFileSync('/proc/cpuinfo', 'utf8');
|
||||||
if (id && id.length >= 16) return id;
|
const match = info.match(/^Serial\s*:\s*([0-9a-fA-F]{8,})$/m);
|
||||||
|
if (match) {
|
||||||
|
const serial = match[1].replace(/^0+/, ''); // 去掉前导零
|
||||||
|
if (serial.length >= 8) return serial.padStart(16, '0');
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. MAC 地址
|
// ── 3. 主存储设备序列号 ────────────────────────────────────────────────────────
|
||||||
try {
|
function getDiskSerial() {
|
||||||
const interfaces = os.networkInterfaces();
|
// 按优先级依次尝试常见块设备
|
||||||
for (const name of Object.keys(interfaces)) {
|
const candidates = [
|
||||||
for (const iface of interfaces[name]) {
|
'/sys/block/sda/device/serial',
|
||||||
if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
'/sys/block/sdb/device/serial',
|
||||||
return crypto.createHash('sha256').update(iface.mac).digest('hex').slice(0, 32);
|
'/sys/block/nvme0n1/device/serial',
|
||||||
}
|
'/sys/block/mmcblk0/device/serial', // SD 卡(RPi)
|
||||||
|
'/sys/block/vda/device/serial', // 虚拟磁盘
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const p of candidates) {
|
||||||
|
try {
|
||||||
|
const serial = fs.readFileSync(p, 'utf8').trim().replace(/\s+/g, '');
|
||||||
|
if (serial && serial.length >= 4 && serial !== '0000000000000000') {
|
||||||
|
return crypto.createHash('sha256').update('disk:' + serial).digest('hex').slice(0, 32);
|
||||||
}
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底:尝试 lsblk(需要 util-linux,大多数发行版自带)
|
||||||
|
try {
|
||||||
|
const out = execSync(
|
||||||
|
"lsblk --nodeps -o SERIAL --noheadings 2>/dev/null | head -1",
|
||||||
|
{ timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] }
|
||||||
|
).toString().trim();
|
||||||
|
if (out && out.length >= 4) {
|
||||||
|
return crypto.createHash('sha256').update('disk:' + out).digest('hex').slice(0, 32);
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// 4. 随机 UUID 兜底
|
return null;
|
||||||
return crypto.randomUUID().replace(/-/g, '');
|
}
|
||||||
|
|
||||||
|
// ── 4. DMI 产品 UUID(x86 主板)──────────────────────────────────────────────
|
||||||
|
function getDmiUuid() {
|
||||||
|
try {
|
||||||
|
const uuid = fs.readFileSync('/sys/class/dmi/id/product_uuid', 'utf8').trim();
|
||||||
|
// 排除全零/全F等无效值
|
||||||
|
if (uuid && uuid !== '00000000-0000-0000-0000-000000000000'
|
||||||
|
&& uuid !== 'ffffffff-ffff-ffff-ffff-ffffffffffff') {
|
||||||
|
return uuid.replace(/-/g, '').toLowerCase();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 5. 持久化随机 UUID 兜底 ───────────────────────────────────────────────────
|
||||||
|
function getPersistentUUID() {
|
||||||
|
// 先尝试读已有的
|
||||||
|
try {
|
||||||
|
const id = fs.readFileSync(PERSIST_FILE, 'utf8').trim();
|
||||||
|
if (id && id.length >= 16) return id;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// 生成新的并写入
|
||||||
|
const id = crypto.randomUUID().replace(/-/g, '');
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(PERSIST_FILE), { recursive: true });
|
||||||
|
fs.writeFileSync(PERSIST_FILE, id, 'utf8');
|
||||||
|
} catch (e) {
|
||||||
|
// 写不进去也没关系,本次用内存值(重启后会变,但这是最后兜底)
|
||||||
|
console.warn('[fingerprint] 无法持久化 box_id:', e.message);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 主函数 ────────────────────────────────────────────────────────────────────
|
||||||
|
function getBoxId() {
|
||||||
|
return getMachineId()
|
||||||
|
|| getCpuSerial()
|
||||||
|
|| getDiskSerial()
|
||||||
|
|| getDmiUuid()
|
||||||
|
|| getPersistentUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getBoxId };
|
module.exports = { getBoxId };
|
||||||
|
|||||||
Reference in New Issue
Block a user