#!/bin/bash # ═══ zy-repo-sync · 移动硬盘 git 仓库 → 京东服务器 Forgejo 在线仓库 ═══ # 冰朔 2026-09-01 令(瞄点000092轮):"给服务器上开发个啥,能让移动硬盘里的 # git仓库同步到远端服务器上去变成在线仓库"。服务器上的"啥"=Forgejo(guanghulab.com/code/); # 本工具=把任意本地 git 仓库接上去:不存在则建(默认私有)、挂干净远端、令牌只在内存。 # 纪律(同 zy-push):令牌只从 macOS 钥匙串读,永不落盘、永不进日志、永不进仓库; # 永不 force push;线上若分叉先 fetch 对齐,接不上就停等人处理。 # 超大仓库引导(>前置反代请求体限制,公网推会 HTTP 413):走 SSH 隧道直连京东 Forgejo 本体—— # ssh -f -N -L 13340:127.0.0.1:3340 jd-fd-primary # git push http://x-access-token:<令牌>@localhost:13340/bingshuo/<仓库名>.git main (注意:隧道内路径无 /code/ 前缀) # 推完 pkill -f "ssh.*13340"。之后增量推送走公网即可(体量小,前置代理可容)。 # 用法:zy-repo-sync.sh <仓库路径> <仓库名> [private|public] # 例:zy-repo-sync.sh "/Volumes/JZAO/铸渊-ICE-GL-ZY001" zhuyuan-ice-gl-zy001 private set -uo pipefail REPO_PATH="${1:?用法: zy-repo-sync.sh <仓库路径> <仓库名> [private|public]}" REPO_NAME="${2:?缺仓库名}" VISIBILITY="${3:-private}" API_BASE="https://guanghulab.com/code/api/v1" REMOTE_PATH="guanghulab.com/code/bingshuo/${REPO_NAME}.git" KC_SERVICE="zhuyuan-forgejo-token" KC_ACCOUNT="bingshuo" LOG="$HOME/Library/Logs/zhuyuan-repo-sync.log" log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG"; } [ -d "$REPO_PATH/.git" ] || { echo "FAIL 不是git仓库: $REPO_PATH"; exit 1; } cd "$REPO_PATH" || exit 1 BRANCH=$(git branch --show-current) [ -n "$BRANCH" ] || { echo "FAIL 无当前分支( detached? )"; exit 1; } TOKEN=$(security find-generic-password -s "$KC_SERVICE" -a "$KC_ACCOUNT" -w 2>/dev/null) [ -n "${TOKEN:-}" ] || { echo "FAIL 钥匙串无 ${KC_SERVICE}/${KC_ACCOUNT}(先跑 zy-push-install.sh)"; exit 1; } scrub() { sed "s|${TOKEN}|***|g"; } trap 'unset TOKEN PUSH_URL' EXIT # ── 1 · 仓库线上存在吗?不存在则建 ────────────────────────── CODE=$(curl -s -m 15 -o /tmp/zrs-check.$$ -w "%{http_code}" \ -H "Authorization: token $TOKEN" "$API_BASE/repos/bingshuo/$REPO_NAME") if [ "$CODE" = "404" ]; then PRIV=$([ "$VISIBILITY" = "private" ] && echo true || echo false) CODE=$(curl -s -m 20 -o /tmp/zrs-create.$$ -w "%{http_code}" \ -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ -d "{\"name\":\"$REPO_NAME\",\"private\":$PRIV,\"default_branch\":\"main\",\"auto_init\":false}" \ "$API_BASE/user/repos") if [ "$CODE" != "201" ]; then echo "FAIL 建仓失败 HTTP $CODE: $(head -c 300 /tmp/zrs-create.$$ | scrub)"; log "FAIL create $REPO_NAME HTTP $CODE"; exit 1 fi echo "已建仓 bingshuo/$REPO_NAME ($VISIBILITY)" elif [ "$CODE" != "200" ]; then echo "FAIL 查仓异常 HTTP $CODE: $(head -c 200 /tmp/zrs-check.$$ | scrub)"; exit 1 else echo "仓已存在 bingshuo/$REPO_NAME" fi rm -f /tmp/zrs-check.$$ /tmp/zrs-create.$$ # ── 2 · 挂干净远端(URL 不含令牌,可安全留在 .git/config)──── if git remote get-url origin >/dev/null 2>&1; then git remote set-url origin "https://$REMOTE_PATH" else git remote add origin "https://$REMOTE_PATH" fi # ── 3 · 推(永不 force);线上已有历史则先 fetch 对齐 ──────── PUSH_URL="https://x-access-token:${TOKEN}@${REMOTE_PATH}" REMOTE_HEAD=$(git ls-remote "$PUSH_URL" "refs/heads/$BRANCH" 2>/dev/null | awk '{print $1}') if [ -n "$REMOTE_HEAD" ] && [ "$REMOTE_HEAD" != "$(git rev-parse HEAD)" ]; then if ! git fetch -q "$PUSH_URL" "$BRANCH" 2>&1 | scrub; then echo "FAIL fetch 失败(网络或令牌)"; log "FAIL fetch $REPO_NAME"; exit 1 fi if git merge-base --is-ancestor FETCH_HEAD HEAD; then : # 本地已包含线上历史,直接快进推 else echo "FAIL 与线上分叉,需人工处理(不 force)"; log "DIVERGED $REPO_NAME"; exit 1 fi fi if git push -q "$PUSH_URL" "$BRANCH:$BRANCH" 2>&1 | scrub | head -3; then NEW_HEAD=$(git ls-remote "$PUSH_URL" "refs/heads/$BRANCH" 2>/dev/null | awk '{print $1}') if [ "$NEW_HEAD" = "$(git rev-parse HEAD)" ]; then echo "PASS 已同步 $BRANCH $(git rev-parse --short HEAD) → bingshuo/${REPO_NAME}(线上备份在位)" log "PASS $REPO_NAME $BRANCH $(git rev-parse --short HEAD)" else echo "FAIL 推后核验不一致"; log "FAIL verify $REPO_NAME"; exit 1 fi else echo "FAIL push 失败"; log "FAIL push $REPO_NAME"; exit 1 fi