How to mirror your git repo?

Motivation
I have seen an uptick in Reddit posts around Github accounts being locked out. In particular, the account holder of one of my favorite Neovim plugins fzf-lua was randomly suspended. See PSA: Fzf-lua pulls cause an error, my GitHub account has been “flagged” for no apparent reason?. Most of the time, the accounts are reinstated. However, I now realize how easily that could disrupt my workflow and potentially cause me to lose work. Additionally, it is nice to have backups available in the case of Github outages.
Problem
I wanted to solve two problems:
- Create backups of my work that are easily accessible.
- Have my public facing projects hosted by an alternative provider.
Solution
I created a bash script mirror.sh that does the following:
- Creates a temporary directory
- Clones a list of repositories in the temporary directory using the –mirror option
- For each of the target destination repositories, push the repository using the –mirror option
- Delete the temporary directory
The benefit of this is that it is portable. I can run it on my local machine or schedule it to run on a remote machine.
mirror.sh
#!/usr/bin/env bash
set -e -o pipefail
git_push_args=("$*") # for example --dry-run
mirror_repos=(
'git-prompt-string'
'git-prompt-string-lualine.nvim'
'kitty-scrollback.nvim'
)
source='git@github.com:mikesmithgh'
targets=('git@codeberg.org:mikesmith' 'git@gitlab.com:mikesmithgl')
mirror_dir="$(mktemp -d)/mirror"
mkdir -p "$mirror_dir"
printf 'temporary mirror directory: %s\n' "$mirror_dir"
for repo_name in "${mirror_repos[@]}"; do
cd "$mirror_dir" || exit 1
source_repo="$source/$repo_name.git"
printf 'cloning %s ...\n' "$source_repo"
git clone --mirror "$source_repo"
cd "$repo_name.git" || exit 1
for target in "${targets[@]}"; do
target_repo="$target/$repo_name.git"
git remote set-url --push origin "$target_repo"
printf 'mirroring %s to: %s\n' "$source_repo" "$target_repo"
printf 'git push --mirror %s\n' "${git_push_args[@]}"
if ((${#git_push_args[@]})); then
git push --mirror
else
git push --mirror "${git_push_args[@]}"
fi
done
done
rm -rf "$mirror_dir"Mirror on a schedule
I configured a cronjob to run mirror.sh every eight hours on a remote virtual machine.
For the cloud hosting provider, I went with Oracle Cloud Infrastructure because they offer Always Free Resources.
The VM is running Ubuntu-22.04 on a VM.Standard.E2.1.Micro instance so that it falls into the always free category.
Setup
Create an SSH key, see Generating a new SSH key for instructions
- For example,
ssh-keygen -t ed25519 -C "mirror"Add the
id_ed25519.pubpublic key to your GitLab account, see Add an SSH key to your GitLab accountAdd the
id_ed25519.pubpublic key to your Codeberg account, see Add the SSH key to CodebergSetup
keychainInstall
keychainkeychain is a manager for ssh-agent, typically run from ~/.bash_profile. It allows your shells and cron jobs to easily share a single ssh-agent process.
keychainis necessary to allow the cronjob ssh access to the git repositories without the need of reentering the ssh passphrase every time.sudo apt update && sudo apt install keychainSetup files containing ssh-agent environment variables to allow passwordless ssh connections
keychain --noask --eval id_dsaThis will generate files under the
~/.keychainin the form of$HOSTNAME-$SHELL. In our case, we will use$HOSTNAME-sh.Add the following to
~/.bashrcto startkeychainand provide your passphrase on loginkeychain "$HOME/.ssh/id_ed25519" source "$HOME/.keychain/${HOSTNAME}-sh"
Add the cronjob to run
mirror.shevery 8 hoursRun the command
crontab -eand add the followingSHELL=/bin/bash 0 */8 * * * source "$HOME"/.keychain/$(hostname)-sh; /home/ubuntu/gitrepos/dotfiles/bash/scripts/mirror.sh 2>&1 | logger -t 'CRON(mirror)'This will run a cronjob every 8 hours that does the following:
- sources the
keychainfile to use ssh-agent environment variables which allows for passwordless ssh connections - runs
mirror.shto mirror the git repositories - pipes the results of
mirror.shintologgerto save the output as system logs/var/log/syslogwith a prefix ofCRON(mirror):
- sources the
Example
The following is an example of the repository kitty-scrollback.nvim being mirrored from Github to Gitlab and Codeberg.
kitty-scrollback.nvim @ Github
kitty-scrollback.nvim @ Gitlab
kitty-scrollback.nvim @ Codeberg
