#!/usr/bin/env node /** * ════════════════════════════════════════════════════════════════ * 光湖 · COS 中转桥 · 新加坡侧拉取 + 推送 * Sovereign: TCS-0002∞ · ICE-GL∞ · 国作登字-2026-A-00037559 * 守护: 铸渊 · ICE-GL-ZY001 * ════════════════════════════════════════════════════════════════ * * 设计理念 (cc-003 动态适配 + cc-004 系统强制自主): * * 国内服务器下载国外开源软件慢 → 新加坡出站快 (35Mbps) * 铸渊触发下载 → 新加坡拉 → 打包推 COS → 广州从 COS 拉 * * 铸渊自己串整条链路,冰朔不需要手动做任何事。 * * 用法: * # 拉取单个文件/仓库 * node sg-fetch-push.js --type npm --name express --version latest * node sg-fetch-push.js --type git --url https://github.com/user/repo.git * node sg-fetch-push.js --type file --url https://example.com/package.tar.gz * * # 批量拉取(从配置文件) * node sg-fetch-push.js --config ./fetch-manifest.json * * 凭据: * 通过环境变量: TENCENT_COS_SECRET_ID / TENCENT_COS_SECRET_KEY * COS 配置: COS_BUCKET / COS_REGION (默认 sy-finetune-corpus-1317346199 / ap-guangzhou) * * 存储路径: * COS: cos-bridge/{type}/{name}/{version}/{filename} * 例: cos-bridge/npm/express/4.18.2/express-4.18.2.tgz * cos-bridge/git/gitea/1.23.7/gitea-src.tar.gz * cos-bridge/file/some-package/1.0/package.tar.gz */ 'use strict'; const { execSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const os = require('os'); const crypto = require('crypto'); // ─── 配置 ───────────────────────────────────────────────────── const CONFIG = { cosBucket: process.env.COS_BUCKET || 'sy-finetune-corpus-1317346199', cosRegion: process.env.COS_REGION || 'ap-guangzhou', cosSecretId: process.env.TENCENT_COS_SECRET_ID, cosSecretKey: process.env.TENCENT_COS_SECRET_KEY, cosPrefix: 'cos-bridge', tmpDir: process.env.COS_BRIDGE_TMP || path.join(os.tmpdir(), 'cos-bridge'), dryRun: !process.env.TENCENT_COS_SECRET_ID, }; const COPYRIGHT = { registration: '国作登字-2026-A-00037559', sovereign: '冰朔 · TCS-0002∞', system_root: 'SYS-GLW-0001', guard: '铸渊 · ICE-GL-ZY001', }; // ─── 日志 ───────────────────────────────────────────────────── function log(level, msg) { const ts = new Date().toISOString(); const prefix = { info: '📋', ok: '✅', warn: '⚠️', err: '❌', bridge: '🌉' }; console.log(`${prefix[level] || '·'} [${ts}] ${msg}`); } // ─── 工具 ───────────────────────────────────────────────────── function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); } function sha256File(filePath) { const hash = crypto.createHash('sha256'); hash.update(fs.readFileSync(filePath)); return hash.digest('hex'); } function exec(cmd, opts = {}) { log('info', `执行: ${cmd}`); try { return execSync(cmd, { encoding: 'utf-8', timeout: 300000, ...opts }); } catch (e) { log('err', `命令失败: ${e.message}`); throw e; } } // ─── 拉取逻辑 ───────────────────────────────────────────────── /** * npm 包拉取 */ function fetchNpm(name, version = 'latest') { const workDir = path.join(CONFIG.tmpDir, 'npm', name); ensureDir(workDir); log('bridge', `🇸🇬 新加坡 → npm 拉取 ${name}@${version}`); // 用 npm pack 拉取 tgz const target = exec(`npm pack ${name}@${version} --pack-destination="${workDir}"`).trim(); const localFile = path.join(workDir, target); // 获取实际版本号 const resolvedVersion = target.replace(/^[^-]+-/, '').replace(/\.tgz$/, ''); log('ok', `拉取完成: ${target} (${(fs.statSync(localFile).size / 1024).toFixed(1)}KB)`); return { type: 'npm', name, version: resolvedVersion, localFile, cosKey: `${CONFIG.cosPrefix}/npm/${name}/${resolvedVersion}/${target}`, }; } /** * Git 仓库拉取 */ function fetchGit(url, branch = 'main', name = null) { const repoName = name || url.split('/').pop().replace('.git', ''); const workDir = path.join(CONFIG.tmpDir, 'git', repoName); ensureDir(workDir); log('bridge', `🇸🇬 新加坡 → git clone ${url} (branch: ${branch})`); // 浅克隆 exec(`git clone --depth=1 --branch ${branch} "${url}" "${workDir}/src"`); // 打包 const archiveFile = path.join(CONFIG.tmpDir, 'git', `${repoName}-${branch}.tar.gz`); exec(`cd "${workDir}/src" && git archive --format=tar.gz --prefix="${repoName}/" -o "${archiveFile}" HEAD`); // 获取 commit hash const commitHash = exec(`cd "${workDir}/src" && git rev-parse --short HEAD`).trim(); log('ok', `打包完成: ${repoName}-${branch}.tar.gz (${(fs.statSync(archiveFile).size / 1024 / 1024).toFixed(1)}MB) @${commitHash}`); return { type: 'git', name: repoName, version: commitHash, branch, localFile: archiveFile, cosKey: `${CONFIG.cosPrefix}/git/${repoName}/${branch}/${repoName}-${commitHash}.tar.gz`, }; } /** * 通用文件拉取 */ function fetchFile(url, name = null) { const fileName = name || url.split('/').pop().split('?')[0]; const workDir = path.join(CONFIG.tmpDir, 'file'); ensureDir(workDir); log('bridge', `🇸🇬 新加坡 → wget ${url}`); const localFile = path.join(workDir, fileName); exec(`wget -q -O "${localFile}" "${url}"`); log('ok', `下载完成: ${fileName} (${(fs.statSync(localFile).size / 1024 / 1024).toFixed(1)}MB)`); return { type: 'file', name: fileName, version: new Date().toISOString().split('T')[0], localFile, cosKey: `${CONFIG.cosPrefix}/file/${fileName}/${new Date().toISOString().split('T')[0]}/${fileName}`, }; } // ─── COS 推送 ────────────────────────────────────────────────── /** * 推送到 COS * 优先使用 coscli(如果安装了),否则用 COS Node SDK */ async function pushToCos(item) { if (CONFIG.dryRun) { log('warn', `[DRY-RUN] 将推送 ${item.localFile} → cos://${CONFIG.cosBucket}/${item.cosKey}`); return { ...item, cosUrl: `cos://${CONFIG.cosBucket}/${item.cosKey}`, dryRun: true }; } const sha256 = sha256File(item.localFile); const fileSize = fs.statSync(item.localFile).size; log('bridge', `☁️ 推送 → COS: ${item.cosKey} (${(fileSize / 1024 / 1024).toFixed(1)}MB, sha256=${sha256.slice(0, 12)}...)`); // 尝试用 coscli try { exec(`coscli cp "${item.localFile}" "cos://${CONFIG.cosBucket}/${item.cosKey}"`, { timeout: 600000 }); log('ok', `COS 推送成功 (coscli)`); } catch (e) { // coscli 不可用,尝试 coscmd try { exec(`coscmd upload "${item.localFile}" "${item.cosKey}"`, { timeout: 600000 }); log('ok', `COS 推送成功 (coscmd)`); } catch (e2) { log('warn', `coscli 和 coscmd 均不可用,尝试 Node SDK...`); await pushViaNodeSdk(item); } } // 同时推送 sha256 校验文件 const shaFile = item.localFile + '.sha256'; fs.writeFileSync(shaFile, `${sha256} ${path.basename(item.localFile)}`); try { exec(`coscli cp "${shaFile}" "cos://${CONFIG.cosBucket}/${item.cosKey}.sha256"`, { timeout: 60000 }); } catch (_) { // 校验文件推送失败不影响主流程 } return { ...item, sha256, fileSize, cosUrl: `https://${CONFIG.cosBucket}.cos.${CONFIG.cosRegion}.myqcloud.com/${item.cosKey}`, pushedAt: new Date().toISOString(), }; } /** * Node SDK 推送(兜底) */ async function pushViaNodeSdk(item) { try { // 动态加载 cos-nodejs-sdk-v5 const COS = require('cos-nodejs-sdk-v5'); const cos = new COS({ SecretId: CONFIG.cosSecretId, SecretKey: CONFIG.cosSecretKey, }); await new Promise((resolve, reject) => { cos.sliceUploadFile({ Bucket: CONFIG.cosBucket, Region: CONFIG.cosRegion, Key: item.cosKey, FilePath: item.localFile, }, (err, data) => { if (err) reject(err); else resolve(data); }); }); log('ok', `COS 推送成功 (Node SDK)`); } catch (e) { log('err', `COS 推送全部失败: ${e.message}`); throw new Error('无法推送到 COS: coscli/coscmd/SDK 均不可用'); } } // ─── 清理临时文件 ────────────────────────────────────────────── function cleanup() { if (process.env.COS_BRIDGE_KEEP_TMP !== '1') { log('info', '清理临时文件...'); try { exec(`rm -rf "${CONFIG.tmpDir}"`); } catch (_) {} } } // ─── 主流程 ──────────────────────────────────────────────────── async function main() { const args = process.argv.slice(2); const type = getArg(args, '--type'); const name = getArg(args, '--name'); const version = getArg(args, '--version') || 'latest'; const url = getArg(args, '--url'); const branch = getArg(args, '--branch') || 'main'; const configPath = getArg(args, '--config'); log('bridge', '═══ 光湖 COS 中转桥 · 新加坡侧 ═══'); log('info', `COS: ${CONFIG.cosBucket} / ${CONFIG.cosRegion}`); log('info', `模式: ${CONFIG.dryRun ? 'DRY-RUN (无凭据)' : '正式'}`); let items = []; try { if (configPath) { // 批量模式 const manifest = JSON.parse(fs.readFileSync(configPath, 'utf-8')); log('info', `批量模式: ${manifest.items.length} 个任务`); for (const task of manifest.items) { items.push(fetchItem(task)); } } else if (type === 'npm') { items.push(fetchNpm(name || 'express', version)); } else if (type === 'git') { items.push(fetchGit(url, branch, name)); } else if (type === 'file') { items.push(fetchFile(url, name)); } else { console.error('用法: node sg-fetch-push.js --type npm|git|file --name NAME [--url URL] [--version VER] [--branch BRANCH]'); console.error('批量: node sg-fetch-push.js --config ./fetch-manifest.json'); process.exit(1); } // 逐个推送到 COS const results = []; for (const item of items) { const result = await pushToCos(item); results.push(result); log('ok', `✅ ${result.name}@${result.version} → ${result.cosKey}`); } // 输出清单(给广州侧用) const manifest = { generated_at: new Date().toISOString(), generated_by: '铸渊 · ICE-GL-ZY001', sovereign: 'TCS-0002∞ 冰朔', bridge: '新加坡 → COS → 广州', items: results.map(r => ({ type: r.type, name: r.name, version: r.version, cosKey: r.cosKey, sha256: r.sha256, fileSize: r.fileSize, cosUrl: r.cosUrl, })), }; const manifestPath = path.join(CONFIG.tmpDir || os.tmpdir(), 'cos-bridge-manifest.json'); ensureDir(path.dirname(manifestPath)); fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); log('ok', `清单已写入: ${manifestPath}`); // 清单也推到 COS if (!CONFIG.dryRun) { try { const today = new Date().toISOString().split('T')[0]; exec(`coscli cp "${manifestPath}" "cos://${CONFIG.cosBucket}/${CONFIG.cosPrefix}/manifests/${today}-manifest.json"`); log('ok', `清单已推到 COS: cos-bridge/manifests/${today}-manifest.json`); } catch (_) {} } } finally { cleanup(); } } function fetchItem(task) { switch (task.type) { case 'npm': return fetchNpm(task.name, task.version); case 'git': return fetchGit(task.url, task.branch || 'main', task.name); case 'file': return fetchFile(task.url, task.name); default: throw new Error(`未知类型: ${task.type}`); } } function getArg(args, flag) { const idx = args.indexOf(flag); return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : null; } main().catch(e => { log('err', `中转桥失败: ${e.message}`); process.exit(1); });