fabrica

hanna/fabrica

7969 bytes
Raw
1[package]
2name = "fabrica"
3description = "A self-hosted git server: a single binary serving repos over HTTPS and SSH with a themeable web UI."
4version.workspace = true
5edition.workspace = true
6rust-version.workspace = true
7license.workspace = true
8authors.workspace = true
9repository.workspace = true
10publish.workspace = true
11
12[[bin]]
13name = "fabrica"
14path = "src/main.rs"
15
16[dependencies]
17# The binary wires the servers together: `cli` owns the command tree and hands
18# `serve` back to us so that no library crate has to depend on `web`.
19cli = { path = "crates/cli" }
20web = { path = "crates/web" }
21ssh = { path = "crates/ssh" }
22config = { path = "crates/config" }
23store = { path = "crates/store" }
24mirror = { path = "crates/mirror" }
25# Direct, optional dep only to expose git's `vendored` feature at the binary
26# (feature unification then applies it to the transitive `git` too).
27git = { path = "crates/git", optional = true }
28anyhow = { workspace = true }
29tokio = { workspace = true }
30
31[features]
32# Statically link libgit2 (for the Docker image, which has no system libgit2).
33vendored = ["dep:git", "git/vendored"]
34
35[lints]
36workspace = true
37
38[workspace]
39members = ["crates/*"]
40resolver = "3"
41
42[workspace.package]
43version = "0.1.0"
44edition = "2024"
45rust-version = "1.85"
46license = "MPL-2.0"
47authors = ["fabrica contributors"]
48repository = "https://git.example.com/fabrica"
49publish = false
50
51[workspace.dependencies]
52# In-tree crates. Dependency direction is strictly one-way and enforced:
53# model <- store / git / auth <- web / api / ssh / cli
54# No crate may depend on `web`; `model` depends on nothing in-tree.
55config = { path = "crates/config" }
56store = { path = "crates/store" }
57model = { path = "crates/model" }
58blob = { path = "crates/blob" }
59git = { path = "crates/git" }
60highlight = { path = "crates/highlight" }
61auth = { path = "crates/auth" }
62mail = { path = "crates/mail" }
63ssh = { path = "crates/ssh" }
64web = { path = "crates/web" }
65api = { path = "crates/api" }
66cli = { path = "crates/cli" }
67mirror = { path = "crates/mirror" }
68
69# External dependencies. Versions are centralised here; crates opt in with
70# `<dep> = { workspace = true }` so the whole workspace stays on one version.
71figment = { version = "0.10", features = ["toml", "env"] }
72serde = { version = "1", features = ["derive"] }
73serde_json = "1"
74toml = "0.8"
75thiserror = "2"
76url = "2"
77clap = { version = "4", features = ["derive"] }
78anyhow = "1"
79# Database. `any` gives one code path over both backends; TLS is deliberately
80# omitted for now (Postgres-over-TLS is a phase-17 deployment concern) to keep
81# the dependency tree small and the license set clean — see docs/decisions.md.
82sqlx = { version = "0.9", default-features = false, features = [
83 "runtime-tokio",
84 "any",
85 "sqlite",
86 "postgres",
87 "migrate",
88 "macros",
89] }
90ulid = "1"
91tokio = { version = "1", features = [
92 "macros",
93 "rt-multi-thread",
94 "signal",
95 "net",
96 "time",
97 "fs",
98 "process",
99 "io-util",
100] }
101tempfile = "3"
102# Git object model (refs, trees, blobs, commits, diffs, signatures). Linked
103# against the system libgit2 (1.9) via pkg-config; the Nix build sets
104# LIBGIT2_NO_VENDOR=1. Client-only for transport — pack transport spawns `git`.
105git2 = { version = "0.20", default-features = false }
106# Authentication primitives. Argon2id password hashing; SHA-256 for session and
107# invite token digests; HMAC-SHA256 for hand-rolled HS256 JWTs (avoiding a `ring`
108# dependency, whose license set is not in our allow-list — see docs/decisions.md);
109# base64url for token encoding; OsRng for salts and session bytes.
110argon2 = { version = "0.5", features = ["std"] }
111sha2 = "0.10"
112hmac = "0.12"
113base64 = "0.22"
114rand_core = { version = "0.6", features = ["getrandom"] }
115# Interactive, non-echoing password entry for the CLI.
116rpassword = "7"
117# Glob matching for the `.gitattributes` resolver (gitignore-style patterns).
118globset = "0.4"
119# Bounded, concurrent caches (attribute rule sets, highlight output, signatures).
120moka = { version = "0.12", features = ["sync"] }
121# Syntax highlighting over tree-sitter, with bundled grammars. Default features
122# carry all grammars plus the constants table we map onto our `hl-*` classes.
123inkjet = "0.11.1"
124# Snapshot testing.
125insta = "1"
126# Signature verification. Both are pure-Rust (ed25519-dalek/rsa/p256, no `ring`,
127# no OpenSSL) and MIT/Apache-2.0 — sequoia-openpgp is deliberately avoided as it
128# is LGPL, which our license gate forbids (see docs/decisions.md).
129ssh-key = { version = "0.6.7", features = ["ed25519", "rsa", "p256", "std"] }
130pgp = "0.20"
131# SMTP mail. native-tls (system OpenSSL, already a build input) rather than the
132# spec's rustls transport, which pulls `ring` and would fail the license gate —
133# see docs/decisions.md.
134lettre = { version = "0.11", default-features = false, features = [
135 "builder",
136 "smtp-transport",
137 "tokio1",
138 "tokio1-native-tls",
139 "hostname",
140] }
141# Web stack. No TLS/rustls features anywhere (TLS terminates at a reverse proxy),
142# so `ring` stays out of the graph. Versions are the current mutually-compatible
143# axum 0.8 set.
144axum = { version = "0.8", features = ["multipart"] }
145axum-extra = { version = "0.12", features = ["cookie"] }
146maud = { version = "0.27", features = ["axum"] }
147tower = { version = "0.5", features = ["util"] }
148tower-http = { version = "0.6", features = [
149 "compression-gzip",
150 "trace",
151 "request-id",
152 "timeout",
153] }
154rust-embed = "8"
155mime_guess = "2"
156time = "0.3"
157# Markdown rendering (GFM) + HTML sanitization for READMEs and comments.
158comrak = { version = "0.54", default-features = false }
159ammonia = "4"
160# Streaming the pack subprocess stdout into the HTTP response body; gunzip for
161# gzip-encoded upload-pack requests.
162tokio-util = { version = "0.7", features = ["io"] }
163# Stream adapters for LFS: turn the request body stream into an AsyncRead.
164futures-util = "0.3"
165flate2 = "1"
166# Content search (ripgrep's engine). Dual Unlicense/MIT — MIT satisfies the gate.
167grep = "0.3"
168# Outbound HTTP for optional captcha verification only. native-tls (system
169# OpenSSL, already a build input) not rustls, so `ring` stays out of the graph;
170# http2/charset off to keep the tree small. See docs/decisions.md.
171reqwest = { version = "0.12", default-features = false, features = [
172 "native-tls",
173 "json",
174] }
175# SSH server (default features: aws-lc-rs backend, no ring; re-exports ssh-key).
176russh = "0.62"
177# S3-compatible object storage for uploads (avatars, release assets, LFS).
178# aws-lc-rs crypto (already in the tree via russh) so `ring` stays out and the
179# license gate holds; see docs/decisions.md. Manual client config (static creds,
180# custom endpoint) avoids the heavier aws-config credential-provider chain.
181aws-sdk-s3 = { version = "1", default-features = false, features = [
182 "behavior-version-latest",
183 "rt-tokio",
184] }
185# The HTTP client is wired explicitly with the aws-lc-rs rustls provider so the
186# default (ring-based) client is never pulled in — keeping `ring` out of the tree.
187aws-smithy-http-client = { version = "1", default-features = false, features = [
188 "rustls-aws-lc",
189] }
190bytes = "1"
191tracing = "0.1"
192tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] }
193
194[workspace.lints.rust]
195unsafe_code = "forbid"
196missing_docs = "warn"
197
198[workspace.lints.clippy]
199all = { level = "deny", priority = -1 }
200pedantic = { level = "warn", priority = -1 }
201unwrap_used = "deny"
202expect_used = "warn"
203panic = "deny"
204todo = "deny"
205
206[profile.release]
207strip = true
208lto = false
209
210# The pinned nightly ICEs in LLVM codegen compiling tokio's runtime at any
211# optimization level (a known codegen bug, unrelated to our code, in the type
212# `Notified<Arc<multi_thread::Handle>>`). That type is compiled inside tokio's own
213# crate, so building just tokio unoptimized dodges the ICE while every other crate
214# stays optimized. Revisit when the fenix toolchain is bumped past the bug.
215[profile.release.package.tokio]
216opt-level = 0