irongit

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

irongit/README.md
149 lines5.9 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## Tests
88
89```sh
90cargo test --workspace # unit tests (server, CLI, shared types)
91scripts/e2e/ssh-lfs.sh # SSH, LFS end to end against a running server
92scripts/e2e/backup.sh # backup to R2 and restore every bundle
93```
94
95## Layout
96
97```
98backend/
99 migrations/ Postgres schema
100 src/main.rs startup, subcommands (serve, hook, admin)
101 src/auth.rs sessions, personal access tokens, extractors
102 src/perm.rs every access decision (repos, images, orgs)
103 src/storage.rs R2 over the S3 API (presigned URLs, multipart)
104 src/git.rs git CLI wrapper used by the web UI
105 src/git_http.rs smart HTTP (v0, v1, v2)
106 src/transport.rs spawning upload-pack/receive-pack with hooks and limits
107 src/hook.rs pre-receive size and quota checks
108 src/push.rs post-push: events, default branch, heatmap contributions
109 src/ssh.rs built-in SSH server
110 src/lfs.rs Git LFS batch API
111 src/registry/ OCI distribution API (docker push/pull)
112 src/api/ JSON API for the CLI, device login, CLI releases
113 src/backup.rs scheduled backups to R2
114 src/web/ server-rendered pages (maud) poured into the Astro shell
115cli/ the ig CLI
116shared/ types shared by the API and the CLI
117frontend/ Astro: landing, docs, the page shell, CSS and client JS
118```
119
120## Deploying
121
122Production is `https://git.hygo.ai` on the rybbit server (`ssh rybbit`), as
123the `irongit` service in `/opt/hygo/docker-compose.yml`: same shared Postgres
124(database `irongit`) and Caddy as the other hygo services, config in
125`/opt/hygo/irongit.env`, data in the `irongit-data` volume.
126
127```sh
128./deploy.sh # build the image, docker load it on rybbit, roll irongit, wait for healthy,
129 # publish the bundled ig to R2 when cli/Cargo.toml's version changed
130```
131
132Pushing `main` to git.hygo.ai deploys automatically: `.githooks/pre-push` runs
133`./deploy.sh` first and stops the push if the deploy fails (enable it per
134clone with `git config core.hooksPath .githooks`; skip once with
135`git push --no-verify` or `SKIP_DEPLOY=1 git push`). It refuses to deploy
136uncommitted or untracked files, since the image is built from the folder.
137
138Roll back:
139
140```sh
141ssh rybbit 'cd /opt/hygo && docker tag irongit:$(cat .irongit.prev) irongit:latest && docker compose up -d --no-deps irongit'
142```
143
144The DNS record is **DNS-only** (grey cloud): the Cloudflare proxy caps request
145bodies at 100 MB, which breaks large `docker push` layers and big HTTPS git
146pushes. Caddy gets its own Let's Encrypt certificate. Git over SSH is
147published directly on port 2222 (`ssh://git@git.hygo.ai:2222/owner/repo.git`).
148Admin commands run inside the container, e.g.
149`ssh rybbit docker exec irongit irongit admin promote <user>`.