konnoskonnosdocs

Migrate from GitHub

Migrate from GitHub

scripts/migrate-github-to-konnos.sh moves every GitHub organization where you're an admin, plus every personal repo, into a fresh konnos instance. Single bash script, dry-run mode, idempotent on re-run, NDJSON log of every operation.

This guide walks you through using it. The script itself has inline --help text if you want the bare reference.

What gets migrated

  • Branches, tags, commit history (full)
  • Issues, pull requests, comments, labels, milestones
  • Releases (with attached tarballs)
  • Wiki content
  • Git LFS objects

What doesn't

  • GitHub Actions run history (your .forgejo/workflows/*.yml files are migrated as code; runs aren't replayed)
  • GitHub Discussions
  • GitHub Sponsors / billing
  • GitHub Apps and OAuth Apps registered against your repos
  • Pages site content (the source files migrate; the rendered Pages publish chain doesn't)

Prerequisites

A working konnos instance (self-host.md) with an admin user.

Two tokens:

GitHub PAT (classic)

github.com/settings/tokens → Generate new token (classic). Required scopes:

  • repo (full)
  • admin:org (auto-includes read:org, write:org)
  • read:user

Set a 7–14 day expiration; revoke when migration is done.

konnos access token

https://code.your-domain.com/-/user/settings/applications → Manage Access Tokens → Generate. Required permissions:

  • Organization: Read and Write
  • Repository: Read and Write
  • User: Read and Write

(For destination remapping that creates new orgs on the konnos side, the script also needs admin-org-creation rights — flagged automatically if missing during a dry-run.)

Setup

git clone https://code.konnos.org/konnos/konnos.git
cd konnos
cp .env.migration.example .env.migration

Edit .env.migration:

GITHUB_USER=your-github-username
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
KONNOS_URL=https://code.your-domain.com
KONNOS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
KONNOS_OWNER=your-konnos-username
 
# Optional: leave defaults until you need them
GITHUB_ORGS=
REPOS_FILTER=
ORGS_ONLY=false
ORGS_SKIP=false
THROTTLE=1
SKIP_ORGS=
ORG_REMAP=
REPO_REMAP=

KONNOS_OWNER is the destination user whose namespace receives your personal repos (the GitHub repos you own directly, not org-owned). On a fresh konnos instance that's typically the admin user you set up at install.

Dry-run first — always

./scripts/migrate-github-to-konnos.sh --dry-run

The script connects to GitHub, lists every org you admin and every personal repo, prints what it WOULD migrate without doing anything. The output is also logged to konnos-migration-<UTC>.json (NDJSON).

Read the dry-run output. Verify:

  • Every org you expect to see is listed
  • Every repo you expect to migrate is listed
  • The destination paths are what you want (<owner>/<repo> on konnos)

If anything's wrong, use the remap flags below.

Real run

./scripts/migrate-github-to-konnos.sh

Each repo migrates serially with a 1-second throttle between API calls (configurable via THROTTLE). LFS-heavy repos take longer because the konnos instance fetches LFS objects from GitHub during import.

You'll see one log line per operation:

[14:32:15] repo migrated  your-org/cool-project           HTTP 201

Re-runs are safe. The script checks "does this destination repo already exist?" before each migration and skips if it does. If a single repo failed mid-run (network blip, rate limit), just re-run.

Destination remapping

By default, GitHub orgs land on konnos under the same name (github.com/foo/barcode.your-domain.com/foo/bar), and personal repos land under KONNOS_OWNER. Three knobs for non-1:1 mappings:

Skip an org entirely

You don't want every GitHub org in your konnos. Maybe one is empty, archived, or off-strategy.

SKIP_ORGS=archived-org,abandoned-experiment

The script logs org skip for those and moves on.

Map a GitHub org to a different konnos owner

You want repos from multiple GitHub orgs to consolidate under a single konnos user (or a single konnos org).

ORG_REMAP=old-team:flndrn,linus-panda:flndrn,ghost-side-project:flndrn

old-team/repo1 and linus-panda/repo2 both land under flndrn on konnos. If flndrn is the destination konnos user, repos go to flndrn/repo1 and flndrn/repo2.

Send a specific personal repo to a specific konnos org

You have a personal GitHub repo whose home should actually be a konnos org. For example, you used to develop personal/krypco under your account, but on konnos it belongs under the krypco org.

REPO_REMAP=your-github-username/krypco:krypco/krypco

Format: src_owner/src_repo:dst_owner/dst_repo. Multiple comma-separated.

Migration order matters for collisions

If your-github-username/pandit and oldorg/pandit both exist on GitHub, only one ends up at flndrn/pandit on konnos. The personal repo migrates first (it's usually the most current), so oldorg/pandit is auto-skipped with repo skip because flndrn/pandit already exists.

Verify this is the right outcome by reading the dry-run log. If you'd rather the org's version win, swap them with the --repos-filter knob across two runs:

# run 1: only the org version of pandit
GITHUB_ORGS=oldorg REPOS_FILTER=pandit ORGS_ONLY=true ./scripts/migrate-github-to-konnos.sh
 
# run 2: everything else
./scripts/migrate-github-to-konnos.sh

What to verify post-migration

# A few sanity checks against the konnos instance:
curl -sH "Authorization: token $KONNOS_TOKEN" "$KONNOS_URL/api/v1/repos/search?owner=flndrn" | jq '.data | length'
# That number should match how many repos you expected under flndrn.
 
# Spot-check a single repo:
git clone "$KONNOS_URL/flndrn/some-repo.git" /tmp/check-clone
cd /tmp/check-clone
git lfs ls-files       # if the source had LFS, these should download
git log --oneline | head -20

If something looks off, check the NDJSON log:

jq -s 'group_by(.kind + "/" + .status) | map({(.[0].kind + "/" + .[0].status): length}) | add' \
  konnos-migration-*.json
# {"org/skip": 1, "repo/migrated": 23, "repo/skip": 3}

After migration succeeds

Three follow-up tasks:

1. Repoint your local clones

cd ~/your-projects/repo
git remote set-url origin ssh://git@code.your-domain.com:2222/your-konnos-owner/repo.git
# or HTTPS:
git remote set-url origin https://code.your-domain.com/your-konnos-owner/repo.git

For SSH, add your public key to konnos first. After that, git operations don't prompt for credentials.

2. Repoint your CI / deploy chain

If you had GitHub-sourced auto-deploy (Dokploy, fly.io, Vercel, etc.), update each app's source to its konnos URL. The script's NDJSON log lists every <owner>/<repo> migrated — handy for batch repointing.

For konnos's own auto-deploy (Dokploy gitea provider), each repointed app needs:

  • Source updated to your-konnos-owner/repo on the gitea provider
  • A new webhook on the konnos repo pointing at Dokploy's deploy URL

See /forge/runner/README.md for runner setup if you want CI inside konnos itself.

3. Archive (don't delete) the GitHub originals

Don't delete the GitHub repos right away. Archive them — read-only, free, and a passive backup for 30+ days while you build confidence in the konnos copies. After a few weeks of running on konnos with no surprises, delete the GitHub copies if you want.

Common failure modes

SymptomCauseFix
LFS object not foundGitHub LFS quota exceeded mid-fetchWait until quota resets next month, re-run script (idempotent). Or upgrade your GitHub LFS plan temporarily.
403 Forbidden on org listingPAT missing admin:org scopeRegenerate PAT with full admin:org scope checked.
404 on a repo createkonnos instance API unreachable, network blipCheck KONNOS_URL resolves + responds. Re-run.
409 already existsRepo with that name already on konnos (e.g., from a partial earlier run)Expected for re-runs; script logs skip. If you want to force re-migrate, delete the konnos-side repo first via the UI.
instance address is emptyMisleading error from upstream — usually means the destination admin token has expired or its scopes shrankRegenerate the konnos token, update .env.migration.

Revoking tokens after success

# GitHub: github.com/settings/tokens — revoke the migration PAT
# konnos: code.your-domain.com/-/user/settings/applications — revoke the access token

The PAT's 7-day expiration handles this automatically if you set one. The konnos token has no auto-expiry; revoke manually.