Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save fnuecke/f31c955cccdbc0889cf282369c92ac5a to your computer and use it in GitHub Desktop.
Save fnuecke/f31c955cccdbc0889cf282369c92ac5a to your computer and use it in GitHub Desktop.
This script automatically mirrors GitHub starred repositories to a Gitea instance.
#!/bin/bash
#########################################################################
# GitHub to Gitea Starred Repository Mirror
#
# This script automatically mirrors your GitHub starred repositories to
# a Gitea instance.
#
# - Fetches all repositories starred on GitHub
# - Recreate organization / owner (as org) to mirror repos into
# - Creates mirror repositories on Gitea for each starred GitHub repo
# - Handles pagination for users with many starred repositories
#
# Requirements:
# - GitHub Personal Access Token with permissions to read starred repos
# - Gitea Access Token with organization and repository write access
# - curl, jq installed on your system
#########################################################################
# Read variables from .env file.
if ! [ -f ".env" ]; then
echo "No .env file found to source variables from."
echo "Please create a .env file with the variables:"
echo 'GITHUB_USER="jane_doe"'
echo 'GITHUB_TOKEN="github_pat_..."'
echo 'GITEA_URL="https://gitea.example.com"'
echo 'GITEA_USER="jane_doe"'
echo 'GITEA_TOKEN="..."'
exit 1
fi
source '.env'
# Make sure all variables are set.
required_variables=("GITHUB_USER" "GITHUB_TOKEN" "GITEA_URL" "GITEA_USER" "GITEA_TOKEN")
for variable_name in "${required_variables[@]}"; do
if ! [ -v $variable_name ]; then
echo "$variable_name is not set."
exit 1
fi
done
# $1: method (e.g. POST)
# $2: endpoint (e.g. users/some_user/starred?page=1)
# $3: data (e.g. '{"key": "value"}')
function github_query() {
method=$1
endpoint=$2
if [ -v 3 ]; then
data=$3
curl -s -X "$method" -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" -d "$data" "https://api.github.com/$endpoint"
else
curl -s -X "$method" -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/$endpoint"
fi
}
function gitea_query() {
method=$1
endpoint=$2
if [ -v 3 ]; then
data=$3
curl -s -X "$method" -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d "$data" "$GITEA_URL/api/v1/$endpoint"
else
curl -s -X "$method" -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" "$GITEA_URL/api/v1/$endpoint"
fi
}
echo "Fetching Gitea organisations..."
page=1
existing_orgs=()
while true; do
orgs_page=$(gitea_query "GET" "orgs?page=$page")
if [ "$(echo "$orgs_page" | jq '. | length')" = "0" ]; then
break
fi
page=$((page + 1))
while read -r org; do
name=$(echo "$org" | jq -r '.name')
existing_orgs+=("$name")
done < <(echo "$orgs_page" | jq -c '.[]')
done
echo "Found ${#existing_orgs[@]} organisations on Gitea."
# Flatten for regex-check below.
existing_orgs=$(printf "%s\n" "${existing_orgs[@]}")
echo "Fetching Gitea repositories..."
page=1
existing_repos=()
while true; do
repos_page=$(gitea_query "GET" "repos/search?page=$page")
if [ "$(echo "$repos_page" | jq '.data | length')" = "0" ]; then
break
fi
page=$((page + 1))
while read -r repo; do
name=$(echo "$repo" | jq -r '.full_name')
existing_repos+=("$name")
done < <(echo "$repos_page" | jq -c '.data[]')
done
echo "Found ${#existing_repos[@]} repositories on Gitea."
# Flatten for regex-check below.
existing_repos=$(printf "%s\n" "${existing_repos[@]}")
echo "Fetching starred repositories from GitHub..."
page=1
while true; do
starred_page=$(github_query "GET" "users/$GITHUB_USER/starred?per_page=100&page=$page")
if [ "$(echo "$starred_page" | jq '. | length')" = "0" ]; then
break
fi
page=$((page + 1))
while read -r repo; do
# Read values in one query for efficiency, needs readarray because of multi-line output.
readarray -t repo_info < <(echo "$repo" | jq -r '.name, .clone_url, .description // "", .owner.login')
name=${repo_info[0]}
url=${repo_info[1]}
description=${repo_info[2]}
owner=${repo_info[3]}
# Skip if repo already exists in Gitea.
if echo "$existing_repos" | grep -q "^$owner/$name$"; then
continue
fi
echo "Found new repository to mirror: $owner/$name..."
if ! echo "$existing_orgs" | grep -q "^$owner$"; then
echo "Creating new organisation: $owner..."
query=$(jq -n --arg name "$owner" '{"username": $name, "website": "https://github.com/\($name)"}')
result=$(gitea_query "POST" "orgs" "$query")
id=$(echo "$result" | jq -r '.id')
if [ -z "$id" ] || [ "$id" = "null" ]; then
echo "Error creating Gitea organisation: $result"
continue
fi
fi
query=$(jq -n --arg name "$name" --arg url "$url" --arg description "$description" --arg owner "$owner" --arg user "$GITHUB_USER" --arg token "$GITHUB_TOKEN" '{
"clone_addr": $url,
"repo_owner": $owner,
"repo_name": $name,
"description": $description,
"mirror": true,
"private": false,
"lfs": true,
"auth_username": $user,
"auth_token": $token
}')
result=$(gitea_query "POST" "repos/migrate" "$query")
gitea_repo_url=$(echo "$result" | jq -r '.clone_url')
if [ -z "$gitea_repo_url" ] || [ "$gitea_repo_url" = "null" ]; then
echo "Error creating mirror: $result"
continue
fi
echo "Successfully set up mirror for $owner/$name."
done < <(echo "$starred_page" | jq -c '.[]')
done
echo "Mirroring process completed!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment