252 lines
10 KiB
JavaScript
252 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* ════════════════════════════════════════════════════════════════
|
|
* 国内搬家包 · 中文 manifest 生成器
|
|
* Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
|
|
* 守护: 铸渊 · ICE-GL-ZY001
|
|
* ════════════════════════════════════════════════════════════════
|
|
*
|
|
* 由 .github/workflows/migrate-to-cn-build.yml 调用. 在 git archive
|
|
* 完成后, 解压 tar.gz 看实际内容, 生成给冰朔看的中文清单 + 计算 sha256.
|
|
*
|
|
* 用法:
|
|
* node scripts/migration/build-manifest.js \
|
|
* --tarball /tmp/repo.tar.gz \
|
|
* --out-dir /tmp/migration-out \
|
|
* --tag YYYYMMDD-HHMM
|
|
*
|
|
* 产出 (写到 --out-dir):
|
|
* - lighthouse-migration-<tag>.tar.gz (重命名 + 移动)
|
|
* - lighthouse-migration-<tag>.sha256 (sha256 校验文件)
|
|
* - MIGRATION-MANIFEST.md (中文清单)
|
|
* - manifest.json (机读元数据)
|
|
*
|
|
* 设计理念:
|
|
* - cc-001 涌现洁净: tar 不含 .git/objects, 国内仓库重起一段干净历史
|
|
* - cc-004 中文一次性: MIGRATION-MANIFEST.md 给冰朔看, 不是给开发者
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
function parseArgs(argv) {
|
|
const args = {};
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a === '--tarball') args.tarball = argv[++i];
|
|
else if (a === '--out-dir') args.outDir = argv[++i];
|
|
else if (a === '--tag') args.tag = argv[++i];
|
|
else if (a === '--commit') args.commit = argv[++i];
|
|
else if (a === '--branch') args.branch = argv[++i];
|
|
else if (a === '-h' || a === '--help') {
|
|
console.log(fs.readFileSync(__filename, 'utf8').split('\n').slice(0, 25).join('\n'));
|
|
process.exit(0);
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function fail(msg) {
|
|
console.error(`❌ ${msg}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function sha256File(p) {
|
|
const h = crypto.createHash('sha256');
|
|
h.update(fs.readFileSync(p));
|
|
return h.digest('hex');
|
|
}
|
|
|
|
function humanSize(bytes) {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
}
|
|
|
|
function listTar(tarball) {
|
|
// 用 tar -tzf 列内容, 不解压. 安全 — tarball 是我们刚 git archive 出来的.
|
|
// maxBuffer 64MB: git archive 出的搬家包路径列表通常远小于此 (10MB 已经能装 ~5 万个文件).
|
|
// 如果以后仓库膨胀到这个量级, 应换成 stream + readline 处理.
|
|
const r = spawnSync('tar', ['-tzf', tarball], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
if (r.status !== 0) fail(`tar -tzf 失败: ${r.stderr}`);
|
|
return r.stdout.split('\n').filter(Boolean);
|
|
}
|
|
|
|
function classify(entries) {
|
|
// 按顶层目录分类, 给冰朔看大概有什么
|
|
const groups = {};
|
|
let totalFiles = 0;
|
|
for (const e of entries) {
|
|
if (e.endsWith('/')) continue; // 目录条目
|
|
totalFiles++;
|
|
const top = e.split('/')[0];
|
|
groups[top] = (groups[top] || 0) + 1;
|
|
}
|
|
return { groups, totalFiles };
|
|
}
|
|
|
|
const args = parseArgs(process.argv);
|
|
if (!args.tarball || !args.outDir || !args.tag) {
|
|
fail('用法: build-manifest.js --tarball X --out-dir Y --tag Z');
|
|
}
|
|
if (!fs.existsSync(args.tarball)) fail(`找不到 tarball: ${args.tarball}`);
|
|
if (!fs.existsSync(args.outDir)) fs.mkdirSync(args.outDir, { recursive: true });
|
|
|
|
const stat = fs.statSync(args.tarball);
|
|
const sha = sha256File(args.tarball);
|
|
const entries = listTar(args.tarball);
|
|
const { groups, totalFiles } = classify(entries);
|
|
|
|
// 重命名 tarball
|
|
const finalName = `lighthouse-migration-${args.tag}.tar.gz`;
|
|
const finalPath = path.join(args.outDir, finalName);
|
|
fs.copyFileSync(args.tarball, finalPath);
|
|
|
|
// sha256 文件 (兼容 sha256sum -c 格式)
|
|
const shaFile = path.join(args.outDir, `lighthouse-migration-${args.tag}.sha256`);
|
|
fs.writeFileSync(shaFile, `${sha} ${finalName}\n`);
|
|
|
|
// 顶层分类排序
|
|
const sortedGroups = Object.entries(groups).sort((a, b) => b[1] - a[1]);
|
|
|
|
// MIGRATION-MANIFEST.md (中文清单)
|
|
const md = path.join(args.outDir, 'MIGRATION-MANIFEST.md');
|
|
const generatedAt = new Date().toISOString();
|
|
const lines = [];
|
|
lines.push('# 光湖国内搬家包 · 中文清单');
|
|
lines.push('');
|
|
lines.push('> 主权: TCS-0002∞ · 国作登字-2026-A-00037559');
|
|
lines.push('> 守护: 铸渊 · ICE-GL-ZY001');
|
|
lines.push(`> 生成时间: ${generatedAt}`);
|
|
lines.push(`> 包名: \`${finalName}\``);
|
|
lines.push(`> 大小: ${humanSize(stat.size)}`);
|
|
lines.push(`> SHA-256: \`${sha}\``);
|
|
if (args.commit) lines.push(`> 来源 commit: \`${args.commit}\``);
|
|
if (args.branch) lines.push(`> 来源分支: \`${args.branch}\``);
|
|
lines.push('');
|
|
lines.push('## 一、这是什么');
|
|
lines.push('');
|
|
lines.push('这是 GitHub 上 `qinfendebingshuo/guanghulab` 仓库的**工作树快照**, 用于把整个项目搬到');
|
|
lines.push('国内自托管 git (Forgejo) 上. 让国内开发不用穿境外网络也能拉代码.');
|
|
lines.push('');
|
|
lines.push('**注意**: 这个包**不含 .git 历史**. 国内仓库会以这个快照为「初始 commit」重起一段历史 ——');
|
|
lines.push('这是为了防止 GitHub 那边的 remote tracking 残留污染国内仓库 (cc-001 涌现洁净).');
|
|
lines.push('');
|
|
lines.push('## 二、包里有什么 (顶层目录文件数)');
|
|
lines.push('');
|
|
lines.push('| 顶层目录/文件 | 文件数 |');
|
|
lines.push('|---|---:|');
|
|
for (const [name, count] of sortedGroups) {
|
|
lines.push(`| \`${name}\` | ${count} |`);
|
|
}
|
|
lines.push(`| **总计** | **${totalFiles}** |`);
|
|
lines.push('');
|
|
lines.push('## 三、包里没什么 (打包时被排除的)');
|
|
lines.push('');
|
|
lines.push('为了体积小 + 安全, 以下内容**不在**这个包里:');
|
|
lines.push('');
|
|
lines.push('- `.git/` 历史 (走 archive 不带历史, 国内重起新历史)');
|
|
lines.push('- `node_modules/` 依赖 (恢复后服务器自己跑 npm install)');
|
|
lines.push('- `**/checkpoints/` 模型 checkpoint (走 COS 桶单独拉)');
|
|
lines.push('- `**/*.bin` `**/*.safetensors` 模型权重');
|
|
lines.push('- `secrets/` 密钥目录 (永远不进搬家包)');
|
|
lines.push('- `**/__pycache__/` `**/*.pyc` Python 缓存');
|
|
lines.push('- `**/.DS_Store` macOS 元数据');
|
|
lines.push('');
|
|
lines.push('## 四、冰朔接下来怎么操作 (3 步)');
|
|
lines.push('');
|
|
lines.push('### 第 1 步 · 上传到腾讯云 COS');
|
|
lines.push('');
|
|
lines.push('1. 打开腾讯云控制台 → 对象存储 COS');
|
|
lines.push('2. 进桶: `sy-finetune-corpus-1317346199` (你已有,GPU 训练那个桶)');
|
|
lines.push('3. 创建文件夹 `lighthouse-migration/` (如果还没有的话)');
|
|
lines.push('4. 进 `lighthouse-migration/` → 「上传文件」→ 选这两个:');
|
|
lines.push(` - \`${finalName}\``);
|
|
lines.push(` - \`lighthouse-migration-${args.tag}.sha256\``);
|
|
lines.push('5. 等上传完 (国内带宽几十 MB, 通常 1-2 分钟)');
|
|
lines.push('');
|
|
lines.push('### 第 2 步 · 触发恢复 workflow');
|
|
lines.push('');
|
|
lines.push('1. 回到 GitHub → Actions 标签');
|
|
lines.push('2. 左侧选「📥 国内搬家·从COS恢复」');
|
|
lines.push('3. 右上 Run workflow → 输入框填: `从COS恢复` (这是误触锁,不填就 dry-run)');
|
|
lines.push('4. Run workflow');
|
|
lines.push('5. 等约 5-8 分钟,过程中可以在 Actions 页看实时日志');
|
|
lines.push('');
|
|
lines.push('### 第 3 步 · 抄初始凭据 + 删凭据文件');
|
|
lines.push('');
|
|
lines.push('恢复跑完后:');
|
|
lines.push('');
|
|
lines.push('- 浏览器打开 `https://guanghulab.com/git` → 国内仓库镜像就绪');
|
|
lines.push('- 服务器 `/data/guanghulab/_logs/forgejo-credentials-FIRST-BOOT.txt` 里有初始 user/pass');
|
|
lines.push('- 用 ssh -L 隧道访问, 抄完密码后 **`sudo rm` 删那个凭据文件** (安全要求)');
|
|
lines.push('');
|
|
lines.push('## 五、怎么手动解 (如果 workflow 跑不了)');
|
|
lines.push('');
|
|
lines.push('应急时(workflow 出问题), 可以在国内服务器上手解:');
|
|
lines.push('');
|
|
lines.push('```bash');
|
|
lines.push('# 假设包已经下载到 /tmp/');
|
|
lines.push(`sha256sum -c lighthouse-migration-${args.tag}.sha256`);
|
|
lines.push(`mkdir -p /data/guanghulab/repo-mirror`);
|
|
lines.push(`tar -xzf ${finalName} -C /data/guanghulab/repo-mirror/`);
|
|
lines.push('# 然后跑 server/setup/domain-cn/forgejo/setup-forgejo.sh');
|
|
lines.push('```');
|
|
lines.push('');
|
|
lines.push('---');
|
|
lines.push('');
|
|
lines.push(`*生成于 ${generatedAt} · build-manifest.js · 第 6 棒 · 国内搬家完结*`);
|
|
lines.push('');
|
|
fs.writeFileSync(md, lines.join('\n'));
|
|
|
|
// 机读 manifest.json
|
|
const mj = path.join(args.outDir, 'manifest.json');
|
|
fs.writeFileSync(mj, JSON.stringify({
|
|
_sovereign: 'TCS-0002∞ · 国作登字-2026-A-00037559',
|
|
_守护: '铸渊 · ICE-GL-ZY001',
|
|
generated_at: generatedAt,
|
|
tag: args.tag,
|
|
tarball: finalName,
|
|
size_bytes: stat.size,
|
|
sha256: sha,
|
|
total_files: totalFiles,
|
|
top_level_groups: Object.fromEntries(sortedGroups),
|
|
source: {
|
|
commit: args.commit || null,
|
|
branch: args.branch || null,
|
|
},
|
|
excluded: [
|
|
'.git/',
|
|
'node_modules/',
|
|
'**/checkpoints/',
|
|
'**/*.bin',
|
|
'**/*.safetensors',
|
|
'secrets/',
|
|
'**/__pycache__/',
|
|
'**/*.pyc',
|
|
'**/.DS_Store',
|
|
],
|
|
cos_target: {
|
|
bucket: 'sy-finetune-corpus-1317346199',
|
|
prefix: 'lighthouse-migration/',
|
|
region: 'ap-guangzhou',
|
|
},
|
|
next_workflow: '.github/workflows/migrate-to-cn-restore.yml',
|
|
}, null, 2) + '\n');
|
|
|
|
console.log('═══════════════════════════════════════════════════════════');
|
|
console.log(' [build-manifest] 国内搬家包就绪');
|
|
console.log('═══════════════════════════════════════════════════════════');
|
|
console.log(` tarball: ${finalPath} (${humanSize(stat.size)})`);
|
|
console.log(` sha256: ${sha}`);
|
|
console.log(` 文件数: ${totalFiles}`);
|
|
console.log(` 顶层分类: ${sortedGroups.slice(0, 5).map(([n, c]) => `${n}=${c}`).join(' ')}...`);
|
|
console.log(` 中文清单: ${md}`);
|
|
console.log(` 机读 manifest: ${mj}`);
|
|
console.log('═══════════════════════════════════════════════════════════');
|