irongit

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

irongit/README.md
170 lines7.0 KBMarkdown
1# irongit
2
3Git hosting and a container registry in one binary, built on the astrum
4template (axum + Astro). Users and organizations share one namespace; repos
5and images are public or private independently; profiles have a contribution
6heatmap; `ig` is the CLI.
7
8## What runs where
9
10| Piece | Where it lives |
11| --- | --- |
12| Accounts, permissions, metadata, sessions, tokens | Postgres |
13| Git repositories (bare) | Server disk, `DATA_DIR/repos/{id}.git` (by id, so renames never move files) |
14| Git LFS objects | R2 `lfs/`, uploaded and downloaded by clients through presigned URLs |
15| Image layers and configs | R2 `registry/blobs/`, pulls are 307 redirects to presigned URLs |
16| Image manifests and tags | Postgres |
17| Avatars | R2 `avatars/` |
18| CLI releases | R2 `releases/cli/` |
19| Backups (git bundles + pg_dump) | R2 `backups/` |
20| Logs | `DATA_DIR/logs/` (`server.*.log`, `frontend.*.log`, `hooks.*.log`) |
21
22Git transport and browsing go through the system `git` binary (it must be on
23PATH). Pushes run a pre-receive hook (`irongit hook pre-receive`, the same
24binary) that rejects files over `MAX_FILE_MB` and pushes over the owner's
25quota, pointing people at Git LFS.
26
27## Develop
28
29Requirements: Rust, bun, git, watchexec, Postgres (local dev uses the
30container on 5432, database `irongit`).
31
32```sh
33./dev.sh # http://localhost:7878, git SSH on 2222
34```
35
36Configuration is `.env` at the repo root; every variable is described in
37`secrets.md` (gitignored). The first account registered becomes site admin;
38`irongit admin promote <user>` makes more.
39
40```sh
41cargo test -p irongit # unit tests
42cargo run -p irongit -- admin check-storage # verify R2 credentials
43```
44
45## Build
46
47```sh
48./build.sh
49# target/release/irongit server (x86_64 Linux)
50# target/x86_64-unknown-linux-musl/release/ig CLI, static
51```
52
53Publish a CLI build so `install.sh` and `ig upgrade` serve it:
54
55```sh
56irongit admin publish-cli target/x86_64-unknown-linux-musl/release/ig --version 0.1.0
57```
58
59## The ig CLI
60
61```sh
62curl -fsSL http://localhost:7878/install.sh | sh # installs ig and docker-credential-ig
63ig login # approve in the browser; sets up git and docker helpers
64ig repo create api --private
65ig repo clone you/api
66docker push localhost:7878/you/api:1.0
67ig image visibility you/api public
68ig ssh-key add # ~/.ssh/id_ed25519.pub by default
69ig import github my-github-name # bring a GitHub account over (uses your gh login)
70ig upgrade
71```
72
73Full reference at `/docs/cli`; git, SSH and LFS at `/docs/git`; the
74registry at `/docs/registry`; GitHub imports at `/docs/import`.
75
76## Importing from GitHub
77
78`/new/import` (or `ig import github OWNER`) copies a GitHub user's or
79organization's repositories: every branch and tag, Git LFS objects (into R2),
80description, default branch, visibility and archived state; optionally the
81profile; and, for your own account, your GitHub-verified emails so imported
82commits count on your heatmap. It runs as a background job on the server
83(`backend/src/importer/`). GitHub tokens are held in memory only and OAuth
84import tokens are revoked when the job ends. Sign in with GitHub and "Connect
85GitHub" need `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` (see secrets.md).
86
87## Language stats
88
89Repo pages show a GitHub-style language bar and profiles a chart of every
90language across the account's repositories. Files are classified with
91GitHub Linguist's own data (`backend/src/languages/linguist.json`): its
92language table (extensions, filenames, shebang interpreters, groups, colors),
93its vendored, documentation and generated rules, and its content heuristics
94for shared extensions like `.h` or `.sql`. `.gitattributes` overrides
95(`linguist-vendored`, `linguist-language=...`) are honored. Results are
96cached per commit and recomputed when the default branch moves.
97
98```sh
99irongit admin languages huncholane/irongit --files # every file and why it counts or not
100irongit admin languages path/to/repo.git # any git directory, no database
101python3 scripts/gen-linguist.py v9.8.0 # update to a newer Linguist release
102```
103
104After regenerating the data, add a migration with `delete from
105repo_languages;` so every repository is recounted.
106
107## Tests
108
109```sh
110cargo test --workspace # unit tests (server, CLI, shared types)
111scripts/e2e/ssh-lfs.sh # SSH, LFS end to end against a running server
112scripts/e2e/backup.sh # backup to R2 and restore every bundle
113```
114
115## Layout
116
117```
118backend/
119 migrations/ Postgres schema
120 src/main.rs startup, subcommands (serve, hook, admin)
121 src/auth.rs sessions, personal access tokens, extractors
122 src/perm.rs every access decision (repos, images, orgs)
123 src/storage.rs R2 over the S3 API (presigned URLs, multipart)
124 src/git.rs git CLI wrapper used by the web UI
125 src/git_http.rs smart HTTP (v0, v1, v2)
126 src/transport.rs spawning upload-pack/receive-pack with hooks and limits
127 src/hook.rs pre-receive size and quota checks
128 src/push.rs post-push: events, default branch, heatmap contributions
129 src/languages/ language stats (Linguist data and rules)
130 src/ssh.rs built-in SSH server
131 src/lfs.rs Git LFS batch API
132 src/registry/ OCI distribution API (docker push/pull)
133 src/api/ JSON API for the CLI, device login, CLI releases
134 src/backup.rs scheduled backups to R2
135 src/web/ server-rendered pages (maud) poured into the Astro shell
136cli/ the ig CLI
137shared/ types shared by the API and the CLI
138frontend/ Astro: landing, docs, the page shell, CSS and client JS
139```
140
141## Deploying
142
143Production is `https://git.hygo.ai` on the rybbit server (`ssh rybbit`), as
144the `irongit` service in `/opt/hygo/docker-compose.yml`: same shared Postgres
145(database `irongit`) and Caddy as the other hygo services, config in
146`/opt/hygo/irongit.env`, data in the `irongit-data` volume.
147
148```sh
149./deploy.sh # build the image, docker load it on rybbit, roll irongit, wait for healthy,
150 # publish the bundled ig to R2 when cli/Cargo.toml's version changed
151```
152
153Pushing `main` to git.hygo.ai deploys automatically: `.githooks/pre-push` runs
154`./deploy.sh` first and stops the push if the deploy fails (enable it per
155clone with `git config core.hooksPath .githooks`; skip once with
156`git push --no-verify` or `SKIP_DEPLOY=1 git push`). It refuses to deploy
157uncommitted or untracked files, since the image is built from the folder.
158
159Roll back:
160
161```sh
162ssh rybbit 'cd /opt/hygo && docker tag irongit:$(cat .irongit.prev) irongit:latest && docker compose up -d --no-deps irongit'
163```
164
165The DNS record is **DNS-only** (grey cloud): the Cloudflare proxy caps request
166bodies at 100 MB, which breaks large `docker push` layers and big HTTPS git
167pushes. Caddy gets its own Let's Encrypt certificate. Git over SSH is
168published directly on port 2222 (`ssh://git@git.hygo.ai:2222/owner/repo.git`).
169Admin commands run inside the container, e.g.
170`ssh rybbit docker exec irongit irongit admin promote <user>`.