irongit

Git hosting and a container registry in one Rust binary (axum + Astro)

27 commits
Clone ▾
HTTPS
SSH
CLI
README.md

irongit

Git hosting and a container registry in one binary, built on the astrum template (axum + Astro). Users and organizations share one namespace; repos and images are public or private independently; profiles have a contribution heatmap; ig is the CLI.

What runs where

Piece Where it lives
Accounts, permissions, metadata, sessions, tokens Postgres
Git repositories (bare) Server disk, DATA_DIR/repos/{id}.git (by id, so renames never move files)
Git LFS objects R2 lfs/, uploaded and downloaded by clients through presigned URLs
Image layers and configs R2 registry/blobs/, pulls are 307 redirects to presigned URLs
Image manifests and tags Postgres
Avatars R2 avatars/
CLI releases R2 releases/cli/
Backups (git bundles + pg_dump) R2 backups/
Logs DATA_DIR/logs/ (server.*.log, frontend.*.log, hooks.*.log)

Git transport and browsing go through the system git binary (it must be on PATH). Pushes run a pre-receive hook (irongit hook pre-receive, the same binary) that rejects files over MAX_FILE_MB and pushes over the owner's quota, pointing people at Git LFS.

Develop

Requirements: Rust, bun, git, watchexec, Postgres (local dev uses the container on 5432, database irongit).

./dev.sh          # http://localhost:7878, git SSH on 2222

Configuration is .env at the repo root; every variable is described in secrets.md (gitignored). The first account registered becomes site admin; irongit admin promote <user> makes more.

cargo test -p irongit             # unit tests
cargo run -p irongit -- admin check-storage   # verify R2 credentials

Build

./build.sh
# target/release/irongit                          server (x86_64 Linux)
# target/x86_64-unknown-linux-musl/release/ig      CLI, static

Publish a CLI build so install.sh and ig upgrade serve it:

irongit admin publish-cli target/x86_64-unknown-linux-musl/release/ig --version 0.1.0

The ig CLI

curl -fsSL http://localhost:7878/install.sh | sh    # installs ig and docker-credential-ig
ig login                       # approve in the browser; sets up git and docker helpers
ig repo create api --private
ig repo clone you/api
docker push localhost:7878/you/api:1.0
ig image visibility you/api public
ig ssh-key add                 # ~/.ssh/id_ed25519.pub by default
ig upgrade

Full reference at /docs/cli; git, SSH and LFS at /docs/git; the registry at /docs/registry.

Tests

cargo test --workspace            # unit tests (server, CLI, shared types)
scripts/e2e/ssh-lfs.sh            # SSH, LFS end to end against a running server
scripts/e2e/backup.sh             # backup to R2 and restore every bundle

Layout

backend/
  migrations/        Postgres schema
  src/main.rs        startup, subcommands (serve, hook, admin)
  src/auth.rs        sessions, personal access tokens, extractors
  src/perm.rs        every access decision (repos, images, orgs)
  src/storage.rs     R2 over the S3 API (presigned URLs, multipart)
  src/git.rs         git CLI wrapper used by the web UI
  src/git_http.rs    smart HTTP (v0, v1, v2)
  src/transport.rs   spawning upload-pack/receive-pack with hooks and limits
  src/hook.rs        pre-receive size and quota checks
  src/push.rs        post-push: events, default branch, heatmap contributions
  src/ssh.rs         built-in SSH server
  src/lfs.rs         Git LFS batch API
  src/registry/      OCI distribution API (docker push/pull)
  src/api/           JSON API for the CLI, device login, CLI releases
  src/backup.rs      scheduled backups to R2
  src/web/           server-rendered pages (maud) poured into the Astro shell
cli/                 the ig CLI
shared/              types shared by the API and the CLI
frontend/            Astro: landing, docs, the page shell, CSS and client JS

Deploying

Repos need a persistent disk, so the server runs on a VM, not serverless. Put the git and registry hostname on Cloudflare DNS-only (grey cloud): the proxy caps request bodies at 100 MB, which breaks large docker push layers and big HTTPS git pushes. Set SECRET_KEY, PUBLIC_URL, R2_BUCKET=irongit and the SMTP and Google variables in production.