173 lines
6.9 KiB
Bash
Executable File
173 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ════════════════════════════════════════════════════════════════
|
|
# 光湖搬家 · restore-from-cos
|
|
# Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559
|
|
# 守护: 铸渊 · ICE-GL-ZY001
|
|
#
|
|
# 在国内灯塔服务器上跑, 从 COS 拉回搬家包并 mirror push 到本地 Gitea.
|
|
#
|
|
# 用法:
|
|
# bash restore-from-cos.sh \
|
|
# --bucket guanghulab-migration-1317346199 \
|
|
# --region ap-guangzhou \
|
|
# --snapshot-id 2026-05-06T13-15-41 \
|
|
# --gitea-url http://127.0.0.1:3000 \
|
|
# --gitea-user bingshuo \
|
|
# --gitea-token <PAT> \
|
|
# [--gitea-org guanghu] \
|
|
# [--gitea-repo guanghulab]
|
|
#
|
|
# 环境变量:
|
|
# COS_SECRET_ID / COS_SECRET_KEY 必填
|
|
# CACHE_DIR 默认 /data/cos-cache
|
|
# ════════════════════════════════════════════════════════════════
|
|
|
|
set -euo pipefail
|
|
|
|
BUCKET=""
|
|
REGION=""
|
|
SNAPSHOT_ID=""
|
|
GITEA_URL=""
|
|
GITEA_USER=""
|
|
GITEA_TOKEN=""
|
|
GITEA_ORG=""
|
|
GITEA_REPO="guanghulab"
|
|
CACHE_DIR="${CACHE_DIR:-/data/cos-cache}"
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--bucket) BUCKET="$2"; shift 2 ;;
|
|
--region) REGION="$2"; shift 2 ;;
|
|
--snapshot-id) SNAPSHOT_ID="$2"; shift 2 ;;
|
|
--gitea-url) GITEA_URL="$2"; shift 2 ;;
|
|
--gitea-user) GITEA_USER="$2"; shift 2 ;;
|
|
--gitea-token) GITEA_TOKEN="$2"; shift 2 ;;
|
|
--gitea-org) GITEA_ORG="$2"; shift 2 ;;
|
|
--gitea-repo) GITEA_REPO="$2"; shift 2 ;;
|
|
--cache-dir) CACHE_DIR="$2"; shift 2 ;;
|
|
-h|--help)
|
|
head -n 30 "$0"; exit 0 ;;
|
|
*)
|
|
echo "❌ 未知参数: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
for v in BUCKET REGION SNAPSHOT_ID GITEA_URL GITEA_USER GITEA_TOKEN; do
|
|
if [ -z "${!v}" ]; then echo "❌ 必填: --${v,,}" >&2; exit 1; fi
|
|
done
|
|
|
|
: "${COS_SECRET_ID:?COS_SECRET_ID 未设置}"
|
|
: "${COS_SECRET_KEY:?COS_SECRET_KEY 未设置}"
|
|
|
|
# 没传 org 就用 user
|
|
GITEA_OWNER="${GITEA_ORG:-$GITEA_USER}"
|
|
|
|
mkdir -p "$CACHE_DIR/$SNAPSHOT_ID"
|
|
cd "$CACHE_DIR/$SNAPSHOT_ID"
|
|
|
|
echo "═══════════════════════════════════════════════════════════"
|
|
echo " 光湖搬家 · restore-from-cos"
|
|
echo " bucket: $BUCKET"
|
|
echo " region: $REGION"
|
|
echo " id: $SNAPSHOT_ID"
|
|
echo " cache: $CACHE_DIR/$SNAPSHOT_ID"
|
|
echo " gitea: $GITEA_URL → $GITEA_OWNER/$GITEA_REPO"
|
|
echo "═══════════════════════════════════════════════════════════"
|
|
|
|
# ─── 1. 装 coscmd (若没装) ────────────────────────────────
|
|
if ! command -v coscmd >/dev/null 2>&1; then
|
|
echo "[init] 安装 coscmd ..."
|
|
pip3 install -q --index-url https://pypi.tuna.tsinghua.edu.cn/simple coscmd || \
|
|
pip3 install -q coscmd
|
|
fi
|
|
|
|
# 写 coscmd 配置
|
|
cat > "$HOME/.cos.conf" <<EOF
|
|
[common]
|
|
secret_id = $COS_SECRET_ID
|
|
secret_key = $COS_SECRET_KEY
|
|
bucket = $BUCKET
|
|
region = $REGION
|
|
max_thread = 5
|
|
EOF
|
|
chmod 600 "$HOME/.cos.conf"
|
|
|
|
# ─── 2. 下载所有包 ───────────────────────────────────────
|
|
echo "[1/4] 从 COS 拉包 (断点续传) ..."
|
|
PREFIX="snapshots/$SNAPSHOT_ID/"
|
|
for f in manifest.json guanghulab-snapshot.tar.gz mcp-tools-bundle.tar.gz workflows-bundle.tar.gz secrets-template.json; do
|
|
if [ -f "$f" ]; then
|
|
echo " [skip] 本地已存在 $f"
|
|
continue
|
|
fi
|
|
echo " → $f"
|
|
coscmd download "$PREFIX$f" "$f" || echo " (可选包 $f 不存在, 跳过)"
|
|
done
|
|
|
|
# ─── 3. 校验 sha256 ──────────────────────────────────────
|
|
if [ -f manifest.json ] && command -v jq >/dev/null 2>&1 && command -v sha256sum >/dev/null 2>&1; then
|
|
echo "[2/4] 校验 sha256 ..."
|
|
jq -r '.files[] | "\(.sha256) \(.name)"' manifest.json > checksums.txt
|
|
if sha256sum -c checksums.txt; then
|
|
echo " ✅ 全部通过"
|
|
else
|
|
echo " ❌ 校验失败" >&2
|
|
exit 3
|
|
fi
|
|
else
|
|
echo "[2/4] 跳过校验 (无 manifest.json 或 jq/sha256sum)"
|
|
fi
|
|
|
|
# ─── 4. 解包 + 准备 mirror ───────────────────────────────
|
|
echo "[3/4] 解包主仓库 ..."
|
|
WORK_DIR="$CACHE_DIR/$SNAPSHOT_ID/repo"
|
|
rm -rf "$WORK_DIR"
|
|
mkdir -p "$WORK_DIR"
|
|
tar xzf guanghulab-snapshot.tar.gz -C "$WORK_DIR"
|
|
cd "$WORK_DIR"
|
|
|
|
if [ ! -d .git ]; then
|
|
echo "❌ 解出的仓库没有 .git, 不是 mirror 候选; 终止" >&2
|
|
exit 4
|
|
fi
|
|
|
|
# ─── 5. 在 Gitea 上确保仓库存在 ──────────────────────────
|
|
echo "[4/4] 推到 Gitea ..."
|
|
# 先确保 owner / repo 存在
|
|
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
"$GITEA_URL/api/v1/repos/$GITEA_OWNER/$GITEA_REPO" || true)
|
|
if [ "$HTTP_CODE" = "404" ]; then
|
|
echo " Gitea 仓库不存在, 自动创建 ..."
|
|
# 试创建在 user 下
|
|
curl -fsS -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
-d "{\"name\":\"$GITEA_REPO\",\"private\":true,\"auto_init\":false,\"description\":\"光湖代码仓库 · 国内 mirror · 国作登字-2026-A-00037559\"}" \
|
|
"$GITEA_URL/api/v1/user/repos" > /dev/null
|
|
fi
|
|
|
|
# 推送 (mirror, 包含所有分支与 tag)
|
|
# 用 git credential helper 而不是 token-in-URL, 避免 token 进进程参数 / git config
|
|
echo " git push --mirror → $GITEA_URL/$GITEA_OWNER/$GITEA_REPO.git"
|
|
git remote remove gitea 2>/dev/null || true
|
|
git remote add gitea "$GITEA_URL/$GITEA_OWNER/$GITEA_REPO.git"
|
|
|
|
# 把凭据通过 -c http.extraHeader 传 (单次生效, 不写盘)
|
|
# 注: header 仍会出现在 strace, 但不会落到 .git/config 或 process args
|
|
GIT_AUTH_HEADER="Authorization: token $GITEA_TOKEN"
|
|
git -c http.extraHeader="$GIT_AUTH_HEADER" push --mirror gitea \
|
|
2>&1 | tee "$CACHE_DIR/$SNAPSHOT_ID/mirror-push.log" || {
|
|
echo "⚠️ mirror push 部分失败, 详情见 mirror-push.log"
|
|
echo " 常见原因: LFS 文件过大 / 单个 pack > 1GB"
|
|
echo " 建议: 在 Gitea Site Admin → Settings → LFS 启用 LFS, 重试"
|
|
}
|
|
unset GIT_AUTH_HEADER
|
|
|
|
echo
|
|
echo "═══════════════════════════════════════════════════════════"
|
|
echo " ✅ restore-from-cos 完成"
|
|
echo " 下一步: 浏览器访问 $GITEA_URL/$GITEA_OWNER/$GITEA_REPO"
|
|
echo " 然后跑 scripts/migration/convert-workflows.js"
|
|
echo "═══════════════════════════════════════════════════════════"
|