#!/usr/bin/env node import { execFileSync } from "node:child_process"; import { mkdirSync, openSync, closeSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; const host = process.argv[2]; const remotePath = process.argv[3] ?? "/home/pi/.config/ding-approval-tui/config.yaml"; const destination = resolve(process.env.DINGTALK_CREDENTIALS_FILE ?? ".secrets/dingtalk.json"); if (!host || host.startsWith("-") || !/^[a-zA-Z0-9_.@:-]+$/.test(host)) { throw new Error("用法:npm run import:dingcli -- 用户@主机 [远程配置路径]"); } if (!remotePath.startsWith("/") || /[\r\n\0]/.test(remotePath)) { throw new Error("远程配置路径必须是绝对路径"); } const yaml = execFileSync("ssh", ["-o", "BatchMode=yes", host, "cat", remotePath], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"], maxBuffer: 1024 * 1024, }); const appKey = yamlValue(yaml, "app_key"); const appSecret = yamlValue(yaml, "app_secret"); if (!appKey || !appSecret) throw new Error("远端 dingCLI 配置缺少 app_key 或 app_secret"); mkdirSync(dirname(destination), { recursive: true, mode: 0o700 }); const fd = openSync(destination, "wx", 0o600); try { writeFileSync(fd, `${JSON.stringify({ clientId: appKey, clientSecret: appSecret }, null, 2)}\n`); } finally { closeSync(fd); } console.error(`钉钉凭证已安全导入 ${destination}(未复制其他配置)`); function yamlValue(source, key) { const match = source.match(new RegExp(`^\\s*${key}:\\s*(.+?)\\s*$`, "m")); if (!match) return undefined; const value = match[1].trim(); if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { return value.slice(1, -1); } return value.replace(/\s+#.*$/, "").trim(); }