Migrating a GitHub repository to Tangled
The backstory
GitHub seems to be beyond its peak i feel. The outages have become sort of a trending thing, and at the same time they keep adding new AI features instead of stability.
I had mentioned this in past weeknotes as well; there seems to be a sudden influx of git alternatives. Not just the usuals like Forgejo, GitLab, etc., but a lot of other new platforms (gitlawb , Cursor Origin , etc.).
Of course a lot of them are aimed at AI agents since they have different requirements in terms of identity and throughput, but a lot are also born out of users’ frustration with GitHub (me partly included).
Signing up for Tangled
“Tangled is a decentralized code hosting and collaboration platform” (docs )
It builds on top of the AT Proto universe, therefore creating an “account” is as simple as logging in with your existing atmosphere account. Here’s mine: tangled.org/frytg.digital .
Tangled then adds their records to your identity (see example here ). Along with other AT Proto projects everything is public. So far there are no scoped or private repos.
On their platform code lives on Knots (which you can also self-host ).
To migrate a repository, make sure your public key(s) are added to Tangled on tangled.org/settings/keys .
Migrating a repository
Also see Tangled docs “Migrating an existing repository”
Make sure your
mainbranch is clean and pushedGo to tangled.org/repo/new and create a new repository
- The resulting site will show you the created did (
git@tangled.org:did:plc:...)
- The resulting site will show you the created did (
Use
git-sync(see install guide here ) to copy themainbranch over – depending on your repo size this may take a whilegit-sync sync --branch main "git@github.com:frytg/pkgy.git" "git@tangled.org:did:plc:mc3ztkdooczcg7ojmovuel22"Optionally, check current remote setup with
git remote -vUpdate the remote URL – you can use the full path
git@tangled.org:user.tngl.sh/my-projector the did from previous stepsgit remote set-url origin git@tangled.org:did:plc:mc3ztkdooczcg7ojmovuel22Test with
git push origin main
You should see something like this:
Welcome to Tangled’s hosted knot! 🧶
Everything up-to-date
Syncing Tangled to GitHub
To keep the old repository on GitHub in sync, I like to use a Spindle workflow. Spindles are the Tangled version of GitHub Actions.
To make this work, first set up a deploy key.
Go to your newly created repo -> Settings -> Pipelines and choose the default hosted Spindle, then save. Afterwards create a new SSH key and save it to Secrets in GITHUB_DEPLOY_KEY. GitHub allows deploy keys to be used only once, so you’ll need a new one per repo.
Then on GitHub in your old repo, go to Settings -> Deploy keys and add the public key version. Make sure Allow write access is toggled on.
Spindle Workflow
Create a new file in .tangled/workflows/github-mirror.yml and update the GIT_REMOTE_REPO env and user.name.
# Spindle docs: https://docs.tangled.org/spindles#spindles
# Mirror pushes from Tangled to GitHub
# Add GITHUB_DEPLOY_KEY under Settings → Secrets and assign a spindle.
when:
- event: ['push', 'manual']
branch: ['main']
engine: microvm
image: nixos
clone:
depth: 10
dependencies:
- git
- openssh
registry:
nixpkgs: github:nixos/nixpkgs/nixos-unstable
environment:
GIT_REMOTE_REPO: 'git@github.com:frytg/pkgy.git'
steps:
- name: 'Mirror to GitHub'
command: |
set -euo pipefail
if [ -z "${GITHUB_DEPLOY_KEY:-}" ]; then
echo "GITHUB_DEPLOY_KEY not set — skipping"
exit 0
fi
echo "TANGLED_REF_NAME: ${TANGLED_REF_NAME}"
echo "TANGLED_SHA: ${TANGLED_SHA}"
echo "HEAD: $(git rev-parse HEAD)"
echo "GIT_REMOTE_REPO: ${GIT_REMOTE_REPO}"
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
touch "$HOME/.ssh/known_hosts"
eval "$(ssh-agent -s)" > /dev/null
ssh-add - <<< "${GITHUB_DEPLOY_KEY}"
ssh-keyscan -t rsa,ed25519 github.com >> "$HOME/.ssh/known_hosts"
git config user.name "Daniel Freytag"
git config user.email "ci@frytg.digital"
git remote add mirror "${GIT_REMOTE_REPO}"
git push --force mirror "HEAD:refs/heads/${TANGLED_REF_NAME}"
Push this file and see workflow progress in the web UI. Usually the process takes about 30 seconds.