118 lines
3.7 KiB
JavaScript
118 lines
3.7 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
/**
|
||
|
|
* License 激活脚本 (用户首次使用)
|
||
|
|
* 用法:
|
||
|
|
* echo "<LICENSE_KEY>" | node activate.js
|
||
|
|
* 或:
|
||
|
|
* node activate.js --key=BW-XXXX-XXXX-...
|
||
|
|
*
|
||
|
|
* 行为:
|
||
|
|
* 1. 读取 License Key (stdin 或 --key, 不走 argv 暴露)
|
||
|
|
* 2. 生成设备指纹
|
||
|
|
* 3. 向 Worker 激活 → 拿 JWT Token (7 天有效)
|
||
|
|
* 4. 保存到 ~/.claude/.bw-token (600 权限)
|
||
|
|
*/
|
||
|
|
const fs = require("fs");
|
||
|
|
const path = require("path");
|
||
|
|
const os = require("os");
|
||
|
|
const https = require("https");
|
||
|
|
const { fingerprint } = require("./fingerprint");
|
||
|
|
|
||
|
|
const API_BASE = process.env.BW_API_BASE || "https://bookworm-router.bookworm-api.workers.dev";
|
||
|
|
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
||
|
|
const TOKEN_FILE = path.join(CLAUDE_DIR, ".bw-token");
|
||
|
|
|
||
|
|
async function readLicenseKey() {
|
||
|
|
// 优先从 --key= 读取 (但不推荐, argv 会暴露)
|
||
|
|
const argKey = process.argv.find(a => a.startsWith("--key="));
|
||
|
|
if (argKey) return argKey.slice(6).trim();
|
||
|
|
|
||
|
|
// 从 stdin 读
|
||
|
|
return new Promise((resolve) => {
|
||
|
|
let data = "";
|
||
|
|
process.stdin.setEncoding("utf8");
|
||
|
|
process.stdin.on("data", c => data += c);
|
||
|
|
process.stdin.on("end", () => resolve(data.trim()));
|
||
|
|
setTimeout(() => resolve(data.trim()), 15000);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function post(url, body) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const u = new URL(url);
|
||
|
|
const data = JSON.stringify(body);
|
||
|
|
const req = https.request({
|
||
|
|
hostname: u.hostname,
|
||
|
|
port: 443,
|
||
|
|
path: u.pathname,
|
||
|
|
method: "POST",
|
||
|
|
timeout: 15000,
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
"Content-Length": Buffer.byteLength(data)
|
||
|
|
}
|
||
|
|
}, (res) => {
|
||
|
|
let buf = "";
|
||
|
|
res.on("data", c => buf += c);
|
||
|
|
res.on("end", () => {
|
||
|
|
try { resolve({ status: res.statusCode, body: JSON.parse(buf) }); }
|
||
|
|
catch { resolve({ status: res.statusCode, body: buf }); }
|
||
|
|
});
|
||
|
|
});
|
||
|
|
req.on("error", reject);
|
||
|
|
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
||
|
|
req.write(data);
|
||
|
|
req.end();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
(async () => {
|
||
|
|
const key = await readLicenseKey();
|
||
|
|
if (!key || !/^BW-[A-Z0-9-]{20,}$/.test(key)) {
|
||
|
|
console.error("[FAIL] License Key 为空或格式错误");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const dev = fingerprint();
|
||
|
|
console.log("[INFO] 正在激活...");
|
||
|
|
|
||
|
|
try {
|
||
|
|
const r = await post(`${API_BASE}/license/activate`, {
|
||
|
|
licenseKey: key,
|
||
|
|
deviceFp: dev,
|
||
|
|
os: `${os.platform()} ${os.release()}`
|
||
|
|
});
|
||
|
|
|
||
|
|
if (r.status !== 200) {
|
||
|
|
const err = r.body?.error || "unknown";
|
||
|
|
const msg = r.body?.message || "";
|
||
|
|
console.error(`[FAIL] 激活失败 (${r.status}): ${err} ${msg}`);
|
||
|
|
if (err === "device_limit") {
|
||
|
|
console.error(" 已达设备上限, 请联系管理员增加或吊销旧设备");
|
||
|
|
} else if (err === "invalid_license") {
|
||
|
|
console.error(" License Key 无效或已被吊销");
|
||
|
|
} else if (err === "expired") {
|
||
|
|
console.error(" License 已过期, 请续费");
|
||
|
|
}
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
try { fs.mkdirSync(CLAUDE_DIR, { recursive: true, mode: 0o700 }); } catch {}
|
||
|
|
fs.writeFileSync(TOKEN_FILE, JSON.stringify({
|
||
|
|
token: r.body.token,
|
||
|
|
license_uuid: r.body.license_uuid,
|
||
|
|
expires_at: Date.now() + r.body.expires_in * 1000,
|
||
|
|
license_expires_at: r.body.license_expires_at
|
||
|
|
}), { mode: 0o600 });
|
||
|
|
|
||
|
|
console.log("[OK] 激活成功");
|
||
|
|
console.log(` License: ${r.body.license_uuid}`);
|
||
|
|
console.log(` License 过期: ${r.body.license_expires_at}`);
|
||
|
|
console.log(` Token 有效期: 7 天 (自动续期)`);
|
||
|
|
} catch (e) {
|
||
|
|
console.error(`[FAIL] 网络错误: ${e.message}`);
|
||
|
|
console.error(" 请检查网络和代理设置");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
})();
|