flnxi-Wu/lib/license/fingerprint-service.ts
WuYongJie ba98bf22cc feat: 添加许可证系统、Agent部署模块和Harbor镜像管理
- 新增许可证服务器 (license-server/)
- 新增 Agent 部署模块 (flnxi-agent/)
- 添加许可证验证和激活页面
- 添加 Harbor 镜像同步脚本
- 更新素材系统支持许可证验证
2025-12-15 21:19:46 +08:00

192 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import type { HardwareFingerprint } from './types';
// 实例 ID 存储路径
const INSTANCE_ID_FILE = path.join(process.cwd(), 'data', '.instance-id');
/**
* 获取 machine-id
* Linux: /etc/machine-id 或 /var/lib/dbus/machine-id
* Windows: 使用 hostname + 网卡信息生成
* Docker: 使用容器 ID 或挂载的 machine-id
*/
async function getMachineId(): Promise<string> {
// 尝试读取 Linux machine-id
const machineIdPaths = [
'/etc/machine-id',
'/var/lib/dbus/machine-id',
];
for (const filePath of machineIdPaths) {
try {
const machineId = fs.readFileSync(filePath, 'utf8').trim();
if (machineId) {
return machineId;
}
} catch {
// 文件不存在,继续尝试下一个
}
}
// 尝试读取 Docker 容器 ID
try {
const cgroupContent = fs.readFileSync('/proc/self/cgroup', 'utf8');
const match = cgroupContent.match(/docker[/-]([a-f0-9]{64})/);
if (match) {
return match[1].substring(0, 32);
}
} catch {
// 不在 Docker 容器中
}
// Windows 或其他系统:使用 hostname + 网卡信息生成
const hostname = os.hostname();
const networkInterfaces = os.networkInterfaces();
const macs: string[] = [];
for (const interfaces of Object.values(networkInterfaces)) {
if (interfaces) {
for (const iface of interfaces) {
if (!iface.internal && iface.mac !== '00:00:00:00:00:00') {
macs.push(iface.mac);
}
}
}
}
const data = `${hostname}-${macs.sort().join('-')}`;
return crypto.createHash('md5').update(data).digest('hex');
}
/**
* 获取主网卡 MAC 地址
*/
function getPrimaryMacAddress(): string {
const networkInterfaces = os.networkInterfaces();
// 优先查找常见的网卡名称
const preferredNames = ['eth0', 'en0', 'ens33', 'enp0s3', 'Ethernet', 'Wi-Fi'];
for (const name of preferredNames) {
const interfaces = networkInterfaces[name];
if (interfaces) {
for (const iface of interfaces) {
if (!iface.internal && iface.mac !== '00:00:00:00:00:00') {
return iface.mac;
}
}
}
}
// 如果没有找到,返回第一个非内部网卡的 MAC
for (const interfaces of Object.values(networkInterfaces)) {
if (interfaces) {
for (const iface of interfaces) {
if (!iface.internal && iface.mac !== '00:00:00:00:00:00') {
return iface.mac;
}
}
}
}
return '00:00:00:00:00:00';
}
/**
* 获取或创建实例 ID
* 实例 ID 在首次激活时生成,并持久化存储
*/
async function getOrCreateInstanceId(): Promise<string> {
// 确保目录存在
const dir = path.dirname(INSTANCE_ID_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// 尝试读取现有的实例 ID
try {
const instanceId = fs.readFileSync(INSTANCE_ID_FILE, 'utf8').trim();
if (instanceId) {
return instanceId;
}
} catch {
// 文件不存在
}
// 生成新的实例 ID
const instanceId = crypto.randomBytes(16).toString('hex');
fs.writeFileSync(INSTANCE_ID_FILE, instanceId);
return instanceId;
}
/**
* 生成硬件指纹
*/
export async function generateFingerprint(): Promise<HardwareFingerprint> {
const [machineId, instanceId] = await Promise.all([
getMachineId(),
getOrCreateInstanceId(),
]);
return {
machineId,
hostname: os.hostname(),
macAddress: getPrimaryMacAddress(),
instanceId,
};
}
/**
* 获取实例 ID
*/
export async function getInstanceId(): Promise<string> {
return getOrCreateInstanceId();
}
/**
* 计算两个指纹的匹配分数
* 返回 0-100 的分数
*/
export function calculateFingerprintMatchScore(
stored: HardwareFingerprint,
current: HardwareFingerprint
): number {
let score = 0;
// machineId 权重 40%
if (stored.machineId === current.machineId) {
score += 40;
}
// hostname 权重 20%
if (stored.hostname === current.hostname) {
score += 20;
}
// macAddress 权重 30%
if (stored.macAddress === current.macAddress) {
score += 30;
}
// instanceId 权重 10%
if (stored.instanceId === current.instanceId) {
score += 10;
}
return score;
}
/**
* 检查指纹是否匹配(60% 阈值)
*/
export function isFingerprintMatch(
stored: HardwareFingerprint,
current: HardwareFingerprint
): boolean {
return calculateFingerprintMatchScore(stored, current) >= 60;
}