---
image: "/img/code-mirror.jpg"
date: "2019-03-13T08:37:34-05:00"
imageOverlayColor: "#000"
imageOverlayOpacity: 0.7
heroBackgroundColor: "#333"
description: For those of us who want to self-host and display their work in a "socially expectable" manner.
title: Mirroring Gitea to Other Repository Management Services (GitHub, GitLab, etc.)
draft: false
---

I have a [Gitea][gitea] instance I self-host at home. I keep most of my
repositories there, but I recognize that most other developers and potential
employers will want to see [my work _on_ GitHub][me-on-github].

<!--more-->

**NOTE**: Gitea now supports this out-of-the-box and probably fits your
use-case: https://docs.gitea.io/en-us/repo-mirror/

# TL;DR

- Setup an SSH key for your Gitea instance on the relevant external repositories
- Leverage `post-receive` git hooks in Gitea to push changes to the relevant
  external repositories while identifying with your new SSH key

Also, most of the magic is [here](#post-receive-script).

# Mirroring

In order to achieve this with minimal effort, I was going to need to utilize
mirroring. Mirroring is a popular git concept that allow a repository to
effectively exist in multiple places at once, though generally mirrors are
read-only.

Mirroring isn't exactly a specific procedure or command, it's simply the act of
reflecting all (or some specific subset) of commits that exist elsewhere,
generally in an automated fashion. Forks might be considered "mirrors" of
a repository at a given point in time.

GitLab, for example, [supports mirroring pretty seamlessly][0]. Gitea, however,
is pretty minimal, which is one of its perks to me. That does not, however, mean
that it is lacking in features.

Gitea supports a few [git hooks][1], which are a simple way to run a script when
something happens. As far as a repository manager is concerned, the only real
hooks that matter are the following (which Gitea supports):

- `pre-receive`: Runs when a client pushes code to the repository. You can use
  this to prevent, for example, code that fails linters, doesn't pass tests,
  or even that can't be merged using a specific merge strategy.
- `update`: Runs for each branch being updated when a client pushes code to the
  repository. This is similar to pre-receive, but allows for more fine-grained
  control. Maybe you want to only make the previous restrictions on your
  `master` branch. This would be the way to do it.
- `post-receive`: Runs after your `pre-receive` and `update` hooks have finished
  when a client pushes code to the repository. This is what we'll be
  leveraging to push code downstream!

With that lengthy introduction, let's dive in!

# Setup

Alrighty, this has a few simple steps, so let's outline what we need to do
first:

1. Setup SSH keys for Gitea and your other repository management services
   1. Generate fresh keys (`ssh-keygen -f gitea` will generate a private key in
      the `gitea` file and a public key in the `gitea.pub` file)
   2. Add the public key (`gitea.pub`) to your "mirrors-to-be" repositories _with
      write access_
   - **Note**: I recommend at the very least to create one Gitea key and add it
     to the individual repositories, though individual keys for each repository
     is tighter security in case your Gitea instance becomes compromised
   - **Note**: Your "mirrors-to-be" repositories must be blank or have related
     histories!
2. Setup the `post-receive` hook on your Gitea repository to push using the
   newly generated private key to the mirror(s)

I'm not going to explain much on how to add Deploy Keys for the various
repository management systems out there, so here's a link [explaining the process
for GitHub][2].

# Hookin' Around

Now we're all set for the magic! Also, for reference and sanity, I'm running
Gitea in Docker on an Arch Linux server with the following version (but this
should work pretty much regardless):

- Gitea Version: `3b612ce built with go1.11.5 : bindata, sqlite, sqlite_unlock_notify`
- Git Version: `2.18.1`

Let's go ahead and open up our Gitea repository's index page.

![My dotfiles repository index](/img/scrots/gitea-mirroring/repo-index.png)

And head to the repository's "Settings" tab... (oh yes, you'll need to have the proper permissions on
the repository itself!)

![Click the "Settings" tab in the
top-right](/img/scrots/gitea-mirroring/repo-index-hl-settings.png)

And now to the "Git Hooks" tab...

![Click the "Git Hooks"
tab](/img/scrots/gitea-mirroring/repo-settings-hl-git-hooks.png)

Let's edit the "Post Receive" hook...

![Edit the "Post Receieve"
hook](/img/scrots/gitea-mirroring/repo-hooks-hl-post-receive-edit.png)

And you will be presented with a form where you can put any kind of script you
want! Remember the SSH keys you generated so long ago? We're going to need the
contents of the private key now. Here are the script contents you're going to
use, replacing the variables as necessary.

## Post-Receive Script

```bash
#!/usr/bin/env bash

downstream_repo="git@github.com:lytedev/dotfiles.git"
# if tmp worries you, put it somewhere else!
pkfile="/tmp/gitea_dotfiles_to_github_dotfiles_id_rsa"

if [ ! -e "$pkfile" ]; then # unindented block for heredoc's sake
cat > "$pkfile" << PRIVATEKEY
# ==> REMOVE THIS ENTIRE LINE & PASTE YOUR PRIVATE KEY HERE <==
PRIVATEKEY
fi

chmod 400 "$pkfile"
export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i \"$pkfile\""
# if you want strict host key checking, just add the host to the known_hosts for
# your Gitea server/user beforehand
git push --mirror "$downstream_repo"
```

Click "Update Hook" and you're all set! Now just push to the repo and watch it
magically become mirrored to the downstream repository!

[me-on-github]: https://github.com/lytedev
[gitea]: https://gitea.io/en-us/
[0]: https://docs.gitlab.com/ee/workflow/repository_mirroring.html
[1]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
[2]: https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys
