I recently wanted to mirror all of the repos I have previously starred on github to my personal forgejo instance.

To do this, I created a bash script named mirror-github-stars-to-forgejo.sh:


# See https://justyn.io/til/mirror-starred-repositories-from-github-to-gitea-forgejo/ for information

# Change these, you'll need an access token from forgejo/gitea too


fetch_and_mirror() {
    local page_url="${GITHUB_API_URL}?per_page=${PER_PAGE}&page=${PAGE}"

    # Fetch the current page of repositories
    response=$(curl -s -I "$page_url")

    # Extract links from the response headers
    links=$(echo "$response" | grep -i '^Link:' | sed -e 's/Link: //')

    # Parse the repositories and mirror them
    curl -s "$page_url" | jq -r '.[] | [.name, .html_url] | @tsv' | \
    while IFS=$'\t' read -r repo url; do
        echo "Mirroring repository: $name"
        json_payload=$(cat <<EOF
  "clone_addr": "$url",
  "repo_name": "$name",
  "mirror": true,
  "repo_owner": "mirrors",
  "description": "Mirror of $url",
  "private": false,
  "issues": false,
  "pull_requests": false
        curl -s -u $FORGEJO_USER:$FORGEJO_TOKEN \
            -X POST \
            -H "Content-Type: application/json" \
            --data "$json_payload" \

    # Check if there's a 'next' link
    if echo "$links" | grep -q 'rel="next"'; then
        # Give forgejo and my disk a break
        echo "Waiting 5 minutes before continuing to the next page"
        sleep 300
        fetch_and_mirror # Recursively call the function to process the next page

# Start the process

You basically just need to update the variables at the top and then run the script. It'll loop through all of the github starred repos for a user (mine by default) and send a request to the forgejo/gitea api to add a new mirrored repo to the mirrors organization.

Note: This was a one-time thing so I didn't add any checking to see if a repo already exists. I did try running it twice though and it just returns an error that the repo exists, so it's relatively safe to run more than once if the parameters don't change.

Note 2: Depending on how many repos you have starred, this will probably use a lot of disk space and i/o. Also keep in mind that the repo will be pulled every 8 hours by default which may not be desirable. I wouldn't run this against an instance that isn't yours or that you don't have approval to do so.