#!/usr/bin/env bash # Mirror local working repos to the RustForge server, WITHOUT touching them. # # The working repos under /workspace have a strict "no remotes, no pushes" # rule (other agents work in them). So instead of pushing, this script: # # 1. keeps a private bare mirror of each repo in $MIRROR_DIR # (git clone --mirror the first time, git remote update afterwards; # the only remote that ever exists is inside the mirror itself), # 2. rsyncs that bare mirror over ssh to /srv/git/.git on the server, # 3. fixes ownership there to the `rustforge` user. # # Safe to run as often as you like (idempotent). Usage: # scripts/sync-mirrors.sh # sync every repo in the list below # scripts/sync-mirrors.sh rust-kernel # sync just one set -euo pipefail SSH_HOST="${RUSTFORGE_SSH_HOST:-rust@88.99.212.146}" SSH_PORT="${RUSTFORGE_SSH_PORT:-2222}" MIRROR_DIR="${RUSTFORGE_MIRROR_DIR:-/workspace/.rustforge-mirrors}" REMOTE_ROOT="${RUSTFORGE_REMOTE_ROOT:-/srv/git}" # name=source-path. Missing sources are skipped with a note. REPOS=( "rust-kernel=/workspace/rust-kernel" "rustforge=/workspace/rustforge" "rustbelt-os=/workspace/rustbelt-os" ) SSH=(ssh -o BatchMode=yes -p "$SSH_PORT") log() { printf '[sync] %s\n' "$*"; } sync_one() { local name="$1" src="$2" local mirror="$MIRROR_DIR/$name.git" local remote="$REMOTE_ROOT/$name.git" if ! git -C "$src" rev-parse --git-dir >/dev/null 2>&1; then log "$name: $src is not a git repo, skipping" return 0 fi # 1. Local bare mirror (reads $src, never writes to it). if [[ ! -d "$mirror" ]]; then log "$name: creating mirror $mirror" git clone --quiet --mirror --no-hardlinks "$src" "$mirror" else log "$name: refreshing mirror" git -C "$mirror" remote update --prune >/dev/null fi # Don't publish the stash or anything outside branches and tags. git -C "$mirror" for-each-ref --format='%(refname)' \ | { grep -Ev '^refs/(heads|tags)/' || true; } \ | while read -r ref; do git -C "$mirror" update-ref -d "$ref"; done # Follow the working repo's current branch for HEAD (the default branch). local head head="$(git -C "$src" symbolic-ref -q HEAD || true)" if [[ -n "$head" ]] && git -C "$mirror" rev-parse -q --verify "$head" >/dev/null; then git -C "$mirror" symbolic-ref HEAD "$head" fi # 2. Make sure the server-side bare repo exists (its own config and hooks). "${SSH[@]}" "$SSH_HOST" "sudo test -d '$remote' || sudo -u rustforge git init --quiet --bare '$remote'" # 3. Copy objects first, then refs/HEAD, so the server never sees a ref # pointing at an object that hasn't arrived yet. The server keeps its own # config/hooks/description (the mirror's config points at /workspace). local rsync=(rsync -a --chown=rustforge:rustforge --rsync-path="sudo rsync" -e "${SSH[*]}") "${rsync[@]}" "$mirror/objects/" "$SSH_HOST:$remote/objects/" "${rsync[@]}" --delete \ --exclude=/config --exclude=/hooks/ --exclude=/description --exclude=/FETCH_HEAD \ "$mirror/" "$SSH_HOST:$remote/" # 4. Ownership (rsync --chown already does this; this is belt and braces). "${SSH[@]}" "$SSH_HOST" "sudo chown -R rustforge:rustforge '$remote'" log "$name: $(git -C "$mirror" rev-parse --abbrev-ref HEAD) @ $(git -C "$mirror" rev-parse --short HEAD) -> $SSH_HOST:$remote" } mkdir -p "$MIRROR_DIR" only="${1:-}" for entry in "${REPOS[@]}"; do name="${entry%%=*}" src="${entry#*=}" [[ -n "$only" && "$only" != "$name" ]] && continue sync_one "$name" "$src" done