fabrica
fabrica (Latin: forge) is a self-hosted git server — a single Rust binary that serves your repositories over HTTPS (read) and SSH (read + write) behind a fast, themeable, htmx-driven web UI. Private by default, no sign-up required.
It is already a complete forge, feature-comparable to Forgejo/Gitea, with CI as the one planned feature still to come: code browsing, nested groups, three-level visibility, issues and pull requests with server-side merge machinery, and a full account-settings area — all from one binary, one config file, and one data directory.
Features
- Code browsing — files, history, diffs, branches, and tags, with syntax highlighting everywhere code appears (including inside diffs and rendered markdown) and per-commit signature verification (SSH and GPG).
- Nested groups — organize repositories under arbitrarily (up to 5 levels) nested, user-owned groups. Renames and moves are metadata-only.
- Three-level visibility — public / internal / private, with a per-instance default and explicit collaborators (read / write / admin). Private repos 404 rather than 403, never leaking their existence.
- Issues & pull requests — toggleable per repository, with GitHub-style scoped labels
(
kind: value), assignees, comments, locking, and open/closed state. PRs include branch comparison, diff review, and server-side merge / squash / rebase. - Accounts — optional web self-registration (toggleable, with optional hCaptcha/reCAPTCHA) alongside admin invites; account settings for username, email, password, SSH/GPG keys, API tokens, and profile.
- Admin dashboard — instance stats, user/repo/group management, sign-up invites, and config overrides.
- Themeable, no build step — server-rendered
maudtemplates enhanced with vendored htmx; every interactive element works without JavaScript. Themes are single CSS files of--fb-*custom properties; drop one in andSIGHUPto load it. - JSON API under
/api/v1, and a CLI that operates directly on the store (no IPC with a running server).
Planned: CI — the seams already exist (routes, tables, and the post-receive hook
entry point, inert behind ui.show_runs = false); the runner (HCL config, Docker execution) is
the next thing to be built.
Out of scope: orgs, forks, stars, followers, notifications, wikis, releases-as-a-feature, git-LFS, webhooks, mirroring, federation.
Requirements
The git binary (≥ 2.41) must be on PATH at runtime — fabrica shells out to it for pack
transport (upload-pack / receive-pack) and maintenance, while using libgit2 in-process for
browsing. Every deployment path below guarantees it; verify with fabrica doctor.
Quick start
Docker / Compose
docker compose up
Builds the image and starts fabrica on :8080 (HTTP) and :2222 (SSH) with named volumes for
data and repos. SQLite is the default database; add --profile postgres and point
FABRICA__DATABASE__URL at it to use Postgres.
From source (Nix)
The toolchain (Rust nightly via fenix) is pinned in the flake:
nix develop # enter the dev shell
just check # fmt + check + clippy + test (the quality gate)
cargo run -- serve # serve with the built-in defaults
serve runs in the foreground (correct for systemd/Docker) and applies pending migrations on
startup. Create the first administrator:
cargo run -- user add alice alice@example.com --admin
Then open http://localhost:8080.
Configuration
Copy fabrica.example.toml, edit it, and point the binary at it with
--config <path> (or place it at /etc/fabrica/fabrica.toml or under $XDG_CONFIG_HOME/fabrica/).
Every key can be overridden by an environment variable FABRICA__<SECTION>__<KEY> (double
underscores nest), and any *_file key reads its value from a file — for agenix, systemd-creds,
or Docker secrets. Unknown keys are a hard error.
fabrica config check # validate
fabrica config show # print the merged config, secrets redacted
On first run fabrica generates its HS256 secret and SSH host key. See
docs/configuration.md and docs/deployment.md
for details — including the reverse-proxy rule that pack routes must not be buffered.
CLI
Every subcommand touches the store and filesystem directly; there is no running-server IPC.
| Command | Purpose |
|---|---|
fabrica serve |
Run the HTTP + SSH server (foreground) |
fabrica user |
Manage accounts (add, passwd, admin, verify, disable, del) |
fabrica repo |
Manage repositories (add, rename, visibility, collaborator, archive, path…) |
fabrica group |
Manage groups |
fabrica key / token |
Manage SSH/GPG keys and API tokens |
fabrica config |
Validate / show the effective configuration |
fabrica migrate |
Apply database migrations |
fabrica doctor |
Check the runtime environment (git on PATH, etc.) |
Architecture
A single root binary (src/main.rs) dispatches into library crates under crates/, with a
strictly one-way dependency direction:
model ← store / git / auth ← web / api / ssh / cli
model(domain types, no I/O) ·config(figment TOML+env) ·store(sqlx, SQLite and Postgres over one portable schema) ·git(libgit2 reads +gitsubprocess pack transport)highlight(tree-sitter) ·auth(argon2id, sessions, JWT) ·mail(lettre)ssh(russh) ·web(axum + maud + htmx) ·api(/api/v1JSON) ·cli(clap)
Repositories are stored on disk by ULID ({repo_dir}/{id[0..2]}/{id}.git); the database is the
only name→path authority, so renames and group moves are metadata-only. See
spec.md for the full design contract and docs/decisions.md
for the running log of design decisions and deviations.
Development
just check # cargo fmt --check + check + clippy -D warnings + test
nix flake check # the superset: clippy, fmt, test, deny, doc
The workspace forbids unsafe_code and denies unwrap/panic/todo outside tests. cargo test alone suffices with no external services (Postgres tests are feature-gated). Commits
follow Conventional Commits, scoped by crate/area.