fabrica

hanna/fabrica

docs: add README

bf1c5f1 · hanna committed on 2026-07-26

Project overview, feature list, quick start (Docker + Nix), configuration,
CLI table, architecture, and development notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
1 files changed · +141 −0UnifiedSplit
README.md +141 −0
@@ -0,0 +1,141 @@
1+# fabrica
2+
3+*fabrica* (Latin: **forge**) is a self-hosted git server — a single Rust binary that serves
4+your repositories over **HTTPS** (read) and **SSH** (read + write) behind a fast, themeable,
5+htmx-driven web UI. Private by default, no sign-up required.
6+
7+It aims to be a **complete forge minus CI**, feature-comparable to Forgejo/Gitea: code
8+browsing, nested groups, three-level visibility, issues and pull requests with server-side
9+merge machinery, and a full account-settings area — all from one binary, one config file, and
10+one data directory.
11+
12+## Features
13+
14+- **Code browsing** — files, history, diffs, branches, and tags, with syntax highlighting
15+ everywhere code appears (including inside diffs and rendered markdown) and per-commit
16+ signature verification (SSH and GPG).
17+- **Nested groups** — organize repositories under arbitrarily (up to 5 levels) nested,
18+ user-owned groups. Renames and moves are metadata-only.
19+- **Three-level visibility** — public / internal / private, with a per-instance default and
20+ explicit collaborators (read / write / admin). Private repos 404 rather than 403, never
21+ leaking their existence.
22+- **Issues & pull requests** — toggleable per repository, with GitHub-style **scoped labels**
23+ (`kind: value`), assignees, comments, locking, and open/closed state. PRs include branch
24+ comparison, diff review, and server-side **merge / squash / rebase**.
25+- **Accounts** — optional web self-registration (toggleable, with optional hCaptcha/reCAPTCHA)
26+ alongside admin invites; account settings for username, email, password, SSH/GPG keys, API
27+ tokens, and profile.
28+- **Admin dashboard** — instance stats, user/repo/group management, sign-up invites, and
29+ config overrides.
30+- **Themeable, no build step** — server-rendered [`maud`](https://maud.lambda.xyz/) templates
31+ enhanced with vendored htmx; every interactive element works without JavaScript. Themes are
32+ single CSS files of `--fb-*` custom properties; drop one in and `SIGHUP` to load it.
33+- **JSON API** under `/api/v1`, and a CLI that operates directly on the store (no IPC with a
34+ running server).
35+
36+**Out of scope:** orgs, forks, stars, followers, notifications, wikis, releases-as-a-feature,
37+git-LFS, webhooks, mirroring, federation. **CI is deferred** — the seams exist (inert behind
38+`ui.show_runs = false`) but the runner is not built yet.
39+
40+## Requirements
41+
42+The `git` binary (**≥ 2.41**) must be on `PATH` at runtime — fabrica shells out to it for pack
43+transport (`upload-pack` / `receive-pack`) and maintenance, while using libgit2 in-process for
44+browsing. Every deployment path below guarantees it; verify with `fabrica doctor`.
45+
46+## Quick start
47+
48+### Docker / Compose
49+
50+```sh
51+docker compose up
52+```
53+
54+Builds the image and starts fabrica on `:8080` (HTTP) and `:2222` (SSH) with named volumes for
55+data and repos. SQLite is the default database; add `--profile postgres` and point
56+`FABRICA__DATABASE__URL` at it to use Postgres.
57+
58+### From source (Nix)
59+
60+The toolchain (Rust nightly via fenix) is pinned in the flake:
61+
62+```sh
63+nix develop # enter the dev shell
64+just check # fmt + check + clippy + test (the quality gate)
65+cargo run -- serve # serve with the built-in defaults
66+```
67+
68+`serve` runs in the foreground (correct for systemd/Docker) and applies pending migrations on
69+startup. Create the first administrator:
70+
71+```sh
72+cargo run -- user add alice alice@example.com --admin
73+```
74+
75+Then open <http://localhost:8080>.
76+
77+## Configuration
78+
79+Copy [`fabrica.example.toml`](fabrica.example.toml), edit it, and point the binary at it with
80+`--config <path>` (or place it at `/etc/fabrica/fabrica.toml` or under `$XDG_CONFIG_HOME/fabrica/`).
81+Every key can be overridden by an environment variable `FABRICA__<SECTION>__<KEY>` (double
82+underscores nest), and any `*_file` key reads its value from a file — for agenix, systemd-creds,
83+or Docker secrets. Unknown keys are a hard error.
84+
85+```sh
86+fabrica config check # validate
87+fabrica config show # print the merged config, secrets redacted
88+```
89+
90+On first run fabrica generates its HS256 secret and SSH host key. See
91+[`docs/configuration.md`](docs/configuration.md) and [`docs/deployment.md`](docs/deployment.md)
92+for details — including the reverse-proxy rule that pack routes **must not be buffered**.
93+
94+## CLI
95+
96+Every subcommand touches the store and filesystem directly; there is no running-server IPC.
97+
98+| Command | Purpose |
99+|---|---|
100+| `fabrica serve` | Run the HTTP + SSH server (foreground) |
101+| `fabrica user` | Manage accounts (add, passwd, admin, verify, disable, del) |
102+| `fabrica repo` | Manage repositories (add, rename, visibility, collaborator, archive, path…) |
103+| `fabrica group` | Manage groups |
104+| `fabrica key` / `token` | Manage SSH/GPG keys and API tokens |
105+| `fabrica config` | Validate / show the effective configuration |
106+| `fabrica migrate` | Apply database migrations |
107+| `fabrica doctor` | Check the runtime environment (git on PATH, etc.) |
108+
109+## Architecture
110+
111+A single root binary (`src/main.rs`) dispatches into library crates under `crates/`, with a
112+strictly one-way dependency direction:
113+
114+```
115+model ← store / git / auth ← web / api / ssh / cli
116+```
117+
118+- `model` (domain types, no I/O) · `config` (figment TOML+env) · `store` (sqlx, SQLite **and**
119+ Postgres over one portable schema) · `git` (libgit2 reads + `git` subprocess pack transport)
120+- `highlight` (tree-sitter) · `auth` (argon2id, sessions, JWT) · `mail` (lettre)
121+- `ssh` (russh) · `web` (axum + maud + htmx) · `api` (`/api/v1` JSON) · `cli` (clap)
122+
123+Repositories are stored on disk by ULID (`{repo_dir}/{id[0..2]}/{id}.git`); the database is the
124+only name→path authority, so renames and group moves are metadata-only. See
125+[`spec.md`](spec.md) for the full design contract and [`docs/decisions.md`](docs/decisions.md)
126+for the running log of design decisions and deviations.
127+
128+## Development
129+
130+```sh
131+just check # cargo fmt --check + check + clippy -D warnings + test
132+nix flake check # the superset: clippy, fmt, test, deny, doc
133+```
134+
135+The workspace forbids `unsafe_code` and denies `unwrap`/`panic`/`todo` outside tests. `cargo
136+test` alone suffices with no external services (Postgres tests are feature-gated). Commits
137+follow Conventional Commits, scoped by crate/area.
138+
139+## License
140+
141+[MPL-2.0](LICENSE).