Git with a cup of coffee, inspired by the tea (a.k.a Gitea >_<)
Gitfee is a self-hosted code host. You can browse repositories in a web UI, clone and push over HTTPS and SSH, keep repositories private, and share them with collaborators.
I started it over a work holiday, mostly to have something to do with the free time. It is a hobby project first. It is also the excuse I needed to learn three things properly: how Git actually works underneath the porcelain, how to write Rust that has to survive real network traffic, and how the current generation of Next.js fits together.
That said, I am not treating it as a toy. The parts that exist are built the
way I would build them at work: authorization lives in one function, the tests
drive the real git and ssh binaries against live sockets, and every
security guard has a test that fails when the guard is removed. If it keeps
going in that direction, there is no reason it could not run in production one
day. It is not there yet, and the last section says exactly what is missing.
Browsing. Repository list, file tree, file view with syntax highlighting, commit history, commit diffs (unified and split), and rendered READMEs. You can also create a repository, and create or edit a file and commit it, from the browser.
Git over the wire. Clone, fetch, and push over both HTTPS and SSH. The SSH side
is an embedded server running in the same process as the HTTP API, not a system
sshd, so there are no host user accounts to manage.
Accounts and access. Register and sign in with a server-side session. Personal access tokens for Git over HTTPS, and public keys for Git over SSH, both managed from the settings pages. Repositories can be public or private, and a private repository is invisible to anyone who cannot read it: you get the same answer whether it is private or does not exist.
Collaborators. A repository owner can grant another user read, write, or
admin. Read lets you clone a private repository, write lets you push, and
admin lets you manage the collaborator list. The same grant is enforced by the
web API, by Git over HTTPS, and by Git over SSH, because all three ask the same
function.
Storage. SQLite by default, PostgreSQL if you point it at one. Sessions live in the database, or in Redis if you prefer.
The backend is Rust: axum for HTTP, git2 for repository access, sqlx for
the database, and russh for the embedded SSH server. It is split into four
crates: gitfee-git (bare repository reads and writes), gitfee-db
(persistence and the access policy), gitfee-server (the HTTP API and Git smart
HTTP), and gitfee-ssh (the SSH daemon).
The frontend is Next.js with React, Tailwind, and HeroUI. Syntax highlighting is done with Shiki on the server.
just # runs the API and the web dev server together
just seed # registers a demo user and creates a demo repository
just check # tests, clippy, and a TypeScript checkThe web UI comes up on http://127.0.0.1:4484 and the API on port 4483. The
embedded SSH server listens on 4487. Repositories are written to
./data/repos, and the SQLite database to ./data/gitfee.db.
For PostgreSQL and Redis instead of the defaults:
just db-up
GITFEE_DATABASE_URL=postgres://... GITFEE_SESSION_BACKEND=redis justThe immediate one is repository transfer, which means moving a repository between owners on disk and deciding what happens to a push that is in flight while it moves. It is also the first operation an admin collaborator should not be allowed to perform, which is when the access ladder finally needs a level above admin.
After that, roughly in the order I expect to want them:
- Issues, then pull requests
- Organizations and teams
- Webhooks, and something CI-shaped
- Wiki, releases, and search
These are the points where the project would change character rather than just grow. I am somewhere inside the second one.
- Host my own code. Browse, clone, push, keep things private. Done.
- Collaborate on it. More than one person, with real roles. Issues and pull requests, so a change can be discussed before it lands. In progress.
- Trust it with other people's code. Organizations, an admin surface, rate limits, an audit log, CSRF protection on the cookie-authenticated routes, and a security review by someone who is not me.
- Operate it. Real schema migrations instead of
CREATE TABLE IF NOT EXISTSat startup, backups, metrics, structured logs, a container image, tagged releases, and documentation written for someone who did not build it. - Call it production. Only after 3 and 4 have survived a few months of me running it for something I would be annoyed to lose.
The gaps, because they are the difference between this and something you should point a domain name at:
- The schema is created at startup. There is no migration story, so upgrading across a schema change is on you.
- Cookie-authenticated mutation routes have no CSRF token.
SameSite=Laxand JSON-only bodies make it defensible for now, not correct. - No rate limiting anywhere, on sign-in or anything else.
- No backups, no metrics, no structured logging.
- Nothing has been security reviewed by anyone but me.
- PostgreSQL works and is tested by hand, but the test suite only exercises SQLite.
If you run it, run it on your own machine, for your own repositories, and expect to read the code when something breaks.