#!/usr/bin/env bash # ============================================================================== # sync.sh — 把工作成果推回 Hugging Face(agent 崩潰後的唯一保險) # # ./sync.sh "訊息" # 提交並推到 STORAGE_REPO # ./sync.sh --status # 只看狀態 # ./sync.sh --pull # 從遠端拉回最新(崩潰後續作第一步) # # ★ 本專案只有一個 repo:HelloSun/qwen35-4b-ssd-streaming。 # 原則:只要改到能跑驗證的階段、或每次改完程式並編譯過,就立刻 ./sync.sh。 # 不要累積 —— 這台機器不穩,agent 可能隨時崩潰,工作成果必須在外面。 # # Token: 環境變數 HF_TOKEN,或檔案 .hf_token(該檔已在 .gitignore 中) # ============================================================================== set -uo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" STORAGE_REPO="${STORAGE_REPO:-HelloSun/qwen35-4b-ssd-streaming}" msg="" MODE=push for a in "$@"; do case "$a" in --status) MODE=status ;; --pull) MODE=pull ;; *) msg="$a" ;; esac done [[ -z "$msg" ]] && msg="sync: $(date -Iseconds)" cd "$REPO_DIR" || { echo "no repo dir"; exit 1; } # ── token ── if [[ -z "${HF_TOKEN:-}" && -f "$REPO_DIR/.hf_token" ]]; then HF_TOKEN="$(tr -d ' \n\r' < "$REPO_DIR/.hf_token")" fi if [[ -z "${HF_TOKEN:-}" ]]; then echo "[sync] 找不到 HF_TOKEN"; exit 1 fi export HF_TOKEN git config user.email "${HF_GIT_EMAIL:-agent@huggingface.co}" git config user.name "${HF_GIT_NAME:-Auto Upload Agent}" ensure_remote() { local name="$1" repo="$2" local url="https://oauth2:${HF_TOKEN}@huggingface.co/${repo}" if git remote get-url "$name" >/dev/null 2>&1; then git remote set-url "$name" "$url" else git remote add "$name" "$url" fi } ensure_remote storage "$STORAGE_REPO" case "$MODE" in status) echo "── git status ──"; git status --porcelain | head -50 echo "── last commits ──"; git log --oneline -8 echo "── remotes ──"; git remote -v | sed 's/oauth2:[^@]*/oauth2:***/' exit 0 ;; pull) echo "[sync] 拉取最新進度 ..." git fetch storage --prune 2>&1 | tail -3 # 用 rebase 而不是 checkout: 遠端可能有我們還沒有的 commit(例如別的 agent 推的), # 直接 checkout 會丟掉本機尚未推送的工作。rebase 兩邊都保留。 git rebase storage/main 2>&1 | tail -3 && echo "[sync] 已 rebase 到 storage/main" exit 0 ;; esac # ── 排除大檔(模型 / KV 檔 / 日誌雜訊)── cat > "$REPO_DIR/.gitignore.sync" <<'EOF' models/ *.gguf *.swap *.cache validate/*.log validate/memwatch.log validate/build-*.log node_modules/ __pycache__/ *.pyc STATE.json .hf_token EOF [[ -f "$REPO_DIR/.gitignore" ]] || cp "$REPO_DIR/.gitignore.sync" "$REPO_DIR/.gitignore" git add -A 2>&1 | tail -3 if git diff --cached --quiet; then echo "[sync] 沒有變更" exit 0 fi git commit -q -m "$msg" || { echo "[sync] commit 失敗"; exit 1; } echo "[sync] committed: $(git rev-parse --short HEAD)" echo "[sync] pushing -> $STORAGE_REPO" if git push -q storage HEAD:main 2>&1 | tail -3; then echo "[sync] ✔ pushed -> $STORAGE_REPO ($(git rev-parse --short HEAD))" else echo "[sync] ✘ push 失敗 -> $STORAGE_REPO" exit 1 fi