From aba21bf8b717e188dbcb9271dfb9f9e31205bfab Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:18:57 +0800 Subject: [PATCH] fix: replace MAC address with CPU serial / disk serial / DMI UUID for box_id Made-with: Cursor --- lib/fingerprint.js | 119 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 21 deletions(-) diff --git a/lib/fingerprint.js b/lib/fingerprint.js index 7254495..07942d0 100644 --- a/lib/fingerprint.js +++ b/lib/fingerprint.js @@ -1,43 +1,120 @@ 'use strict'; const fs = require('fs'); +const path = require('path'); const crypto = require('crypto'); -const os = require('os'); +const { execSync } = require('child_process'); /** * 生成硬件唯一指纹作为 box_id,优先级: - * 1. /etc/machine-id (systemd 生成,现代 Linux 标配) - * 2. /proc/sys/kernel/random/boot_id (内核 boot UUID,重启会变但稳定) - * 3. 第一块网卡 MAC 地址的 SHA-256 前 16 字节 - * 4. 随机 UUID(最后兜底,存入配置防止每次变化) + * + * 1. /etc/machine-id — systemd 生成,现代 Linux 标配,重装系统才变 + * 2. CPU Serial — /proc/cpuinfo 中的 Serial 字段(ARM/RPi 常见) + * 3. 主存储设备序列号 — /sys/block//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 { 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 (_) {} + return null; +} - // 2. boot_id +// ── 2. CPU Serial(ARM / Raspberry Pi)─────────────────────────────────────── +function getCpuSerial() { try { - const id = fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim().replace(/-/g, ''); - if (id && id.length >= 16) return id; + const info = fs.readFileSync('/proc/cpuinfo', 'utf8'); + 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 (_) {} + return null; +} - // 3. MAC 地址 - try { - const interfaces = os.networkInterfaces(); - for (const name of Object.keys(interfaces)) { - for (const iface of interfaces[name]) { - if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') { - return crypto.createHash('sha256').update(iface.mac).digest('hex').slice(0, 32); - } +// ── 3. 主存储设备序列号 ──────────────────────────────────────────────────────── +function getDiskSerial() { + // 按优先级依次尝试常见块设备 + const candidates = [ + '/sys/block/sda/device/serial', + '/sys/block/sdb/device/serial', + '/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 (_) {} - // 4. 随机 UUID 兜底 - return crypto.randomUUID().replace(/-/g, ''); + return null; +} + +// ── 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 };