# 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`). ```sh ./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 ` makes more. ```sh cargo test -p irongit # unit tests cargo run -p irongit -- admin check-storage # verify R2 credentials ``` ## Build ```sh ./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: ```sh irongit admin publish-cli target/x86_64-unknown-linux-musl/release/ig --version 0.1.0 ``` ## The ig CLI ```sh 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 import github my-github-name # bring a GitHub account over (uses your gh login) ig upgrade ``` Full reference at `/docs/cli`; git, SSH and LFS at `/docs/git`; the registry at `/docs/registry`; GitHub imports at `/docs/import`. ## Importing from GitHub `/new/import` (or `ig import github OWNER`) copies a GitHub user's or organization's repositories: every branch and tag, Git LFS objects (into R2), description, default branch, visibility and archived state; optionally the profile; and, for your own account, your GitHub-verified emails so imported commits count on your heatmap. It runs as a background job on the server (`backend/src/importer/`). GitHub tokens are held in memory only and OAuth import tokens are revoked when the job ends. Sign in with GitHub and "Connect GitHub" need `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` (see secrets.md). ## Language stats Repo pages show a GitHub-style language bar and profiles a chart of every language across the account's repositories. Files are classified with GitHub Linguist's own data (`backend/src/languages/linguist.json`): its language table (extensions, filenames, shebang interpreters, groups, colors), its vendored, documentation and generated rules, and its content heuristics for shared extensions like `.h` or `.sql`. `.gitattributes` overrides (`linguist-vendored`, `linguist-language=...`) are honored. Results are cached per commit and recomputed when the default branch moves. ```sh irongit admin languages huncholane/irongit --files # every file and why it counts or not irongit admin languages path/to/repo.git # any git directory, no database python3 scripts/gen-linguist.py v9.8.0 # update to a newer Linguist release ``` After regenerating the data, add a migration with `delete from repo_languages;` so every repository is recounted. ## Tests ```sh 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/languages/ language stats (Linguist data and rules) 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 Production is `https://git.hygo.ai` on the rybbit server (`ssh rybbit`), as the `irongit` service in `/opt/hygo/docker-compose.yml`: same shared Postgres (database `irongit`) and Caddy as the other hygo services, config in `/opt/hygo/irongit.env`, data in the `irongit-data` volume. ```sh ./deploy.sh # build the image, docker load it on rybbit, roll irongit, wait for healthy, # publish the bundled ig to R2 when cli/Cargo.toml's version changed ``` Pushing `main` to git.hygo.ai deploys automatically: `.githooks/pre-push` runs `./deploy.sh` first and stops the push if the deploy fails (enable it per clone with `git config core.hooksPath .githooks`; skip once with `git push --no-verify` or `SKIP_DEPLOY=1 git push`). It refuses to deploy uncommitted or untracked files, since the image is built from the folder. Roll back: ```sh ssh rybbit 'cd /opt/hygo && docker tag irongit:$(cat .irongit.prev) irongit:latest && docker compose up -d --no-deps irongit' ``` The DNS record is **DNS-only** (grey cloud): the Cloudflare proxy caps request bodies at 100 MB, which breaks large `docker push` layers and big HTTPS git pushes. Caddy gets its own Let's Encrypt certificate. Git over SSH is published directly on port 2222 (`ssh://git@git.hygo.ai:2222/owner/repo.git`). Admin commands run inside the container, e.g. `ssh rybbit docker exec irongit irongit admin promote `.