RustForge

rustforge

RustForge: the read-only, cgit-style git web host serving this site

rustforge / scripts / sync-mirrors.sh
3508 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env bash
# Mirror local working repos to the forge 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/<name>.git on the server,
#   3. fixes ownership there to the `forge` 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="${FORGE_SSH_HOST:-rust@88.99.212.146}"
SSH_PORT="${FORGE_SSH_PORT:-2222}"
MIRROR_DIR="${FORGE_MIRROR_DIR:-/workspace/.forge-mirrors}"
REMOTE_ROOT="${FORGE_REMOTE_ROOT:-/srv/git}"

# name=source-path. Missing sources are skipped with a note.
REPOS=(
  "rust-kernel=/workspace/rust-kernel"
  "rust-githost=/workspace/rust-githost"
  "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 forge 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=forge:forge --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 forge:forge '$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