#!/usr/bin/env bash # This script mirrors repositories from my private Forgejo instance to any # external Git hosts (in this case, Tangled.) # # 1. Set `FORGEJO_DATA_DIR` to your Forgejo's repositories directory, # `STATE_FILE` is how this script takes note of changes. # # 2. Create a repository (e.g. mary/mirror-config, configurable via # `CONFIG_REPO_PATH`) and write a `repos.txt` file containing the # repositories you want to mirror: # # mary/pkg-protobuf=git@tangled.sh:mary.my.id/pkg-protobuf # # comments and blank lines are ignored # # 3. Generate a new SSH key, or use your existing one, and set `GIT_SSH_COMMAND` # to make use of it. # # 4. Run this script as a cron job. set -euo pipefail GIT_SSH_COMMAND="ssh -i ~/.ssh/forgejo_mirror_bot -o StrictHostKeyChecking=no" export GIT_SSH_COMMAND STATE_FILE="$HOME/.forgejo-mirror-state" FORGEJO_DATA_DIR="$HOME/dockers/data/forgejo/git/repositories" CONFIG_REPO_PATH="$FORGEJO_DATA_DIR/mary/mirror-config.git" # Read repo mapping from config cd "$CONFIG_REPO_PATH" REPOS=$(git show HEAD:config.txt) # Load previous state declare -A last_hashes if [[ -f "$STATE_FILE" ]]; then while IFS='=' read -r repo hash; do last_hashes["$repo"]="$hash" done < "$STATE_FILE" fi # Prepare new state and track if anything changed declare -A new_hashes changes_made=false # Process each repo mapping while IFS= read -r line; do # Skip empty lines and comments [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue # Parse source=target format if [[ "$line" =~ ^([^=]+)=(.+)$ ]]; then source_repo="${BASH_REMATCH[1]}" target_url="${BASH_REMATCH[2]}" repo_path="$FORGEJO_DATA_DIR/$source_repo.git" if [ -d "$repo_path" ]; then cd "$repo_path" current_hash=$(git show-ref | sha256sum | cut -d' ' -f1) new_hashes["$source_repo"]="$current_hash" if [[ "${last_hashes[$source_repo]:-}" != "$current_hash" ]]; then echo "[-] Mirroring $source_repo -> $target_url (changed)" git push --mirror "$target_url" || { echo "Failed to mirror $source_repo" continue } changes_made=true else echo "[-] Skipping $source_repo (no changes)" fi else echo "[-] Repository $source_repo not found at $repo_path" fi else echo "[-] Invalid format: $line (expected source=target)" fi done <<< "$REPOS" # Only save new state if there were changes if [[ "$changes_made" == true ]]; then > "$STATE_FILE" for repo in "${!new_hashes[@]}"; do echo "$repo=${new_hashes[$repo]}" >> "$STATE_FILE" done fi