[package] name = "fabrica" description = "A self-hosted git server: a single binary serving repos over HTTPS and SSH with a themeable web UI." version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true publish.workspace = true [[bin]] name = "fabrica" path = "src/main.rs" [dependencies] # The binary wires the servers together: `cli` owns the command tree and hands # `serve` back to us so that no library crate has to depend on `web`. cli = { path = "crates/cli" } web = { path = "crates/web" } ssh = { path = "crates/ssh" } config = { path = "crates/config" } store = { path = "crates/store" } mirror = { path = "crates/mirror" } # Direct, optional dep only to expose git's `vendored` feature at the binary # (feature unification then applies it to the transitive `git` too). git = { path = "crates/git", optional = true } anyhow = { workspace = true } tokio = { workspace = true } [features] # Statically link libgit2 (for the Docker image, which has no system libgit2). vendored = ["dep:git", "git/vendored"] [lints] workspace = true [workspace] members = ["crates/*"] resolver = "3" [workspace.package] version = "0.1.0" edition = "2024" rust-version = "1.85" license = "MPL-2.0" authors = ["fabrica contributors"] repository = "https://git.example.com/fabrica" publish = false [workspace.dependencies] # In-tree crates. Dependency direction is strictly one-way and enforced: # model <- store / git / auth <- web / api / ssh / cli # No crate may depend on `web`; `model` depends on nothing in-tree. config = { path = "crates/config" } store = { path = "crates/store" } model = { path = "crates/model" } blob = { path = "crates/blob" } git = { path = "crates/git" } highlight = { path = "crates/highlight" } auth = { path = "crates/auth" } mail = { path = "crates/mail" } ssh = { path = "crates/ssh" } web = { path = "crates/web" } api = { path = "crates/api" } cli = { path = "crates/cli" } mirror = { path = "crates/mirror" } # External dependencies. Versions are centralised here; crates opt in with # ` = { workspace = true }` so the whole workspace stays on one version. figment = { version = "0.10", features = ["toml", "env"] } serde = { version = "1", features = ["derive"] } serde_json = "1" toml = "0.8" thiserror = "2" url = "2" clap = { version = "4", features = ["derive"] } anyhow = "1" # Database. `any` gives one code path over both backends; TLS is deliberately # omitted for now (Postgres-over-TLS is a phase-17 deployment concern) to keep # the dependency tree small and the license set clean — see docs/decisions.md. sqlx = { version = "0.9", default-features = false, features = [ "runtime-tokio", "any", "sqlite", "postgres", "migrate", "macros", ] } ulid = "1" tokio = { version = "1", features = [ "macros", "rt-multi-thread", "signal", "net", "time", "fs", "process", "io-util", ] } tempfile = "3" # Git object model (refs, trees, blobs, commits, diffs, signatures). Linked # against the system libgit2 (1.9) via pkg-config; the Nix build sets # LIBGIT2_NO_VENDOR=1. Client-only for transport — pack transport spawns `git`. git2 = { version = "0.20", default-features = false } # Authentication primitives. Argon2id password hashing; SHA-256 for session and # invite token digests; HMAC-SHA256 for hand-rolled HS256 JWTs (avoiding a `ring` # dependency, whose license set is not in our allow-list — see docs/decisions.md); # base64url for token encoding; OsRng for salts and session bytes. argon2 = { version = "0.5", features = ["std"] } sha2 = "0.10" hmac = "0.12" base64 = "0.22" rand_core = { version = "0.6", features = ["getrandom"] } # Interactive, non-echoing password entry for the CLI. rpassword = "7" # Glob matching for the `.gitattributes` resolver (gitignore-style patterns). globset = "0.4" # Bounded, concurrent caches (attribute rule sets, highlight output, signatures). moka = { version = "0.12", features = ["sync"] } # Syntax highlighting over tree-sitter, with bundled grammars. Default features # carry all grammars plus the constants table we map onto our `hl-*` classes. inkjet = "0.11.1" # Snapshot testing. insta = "1" # Signature verification. Both are pure-Rust (ed25519-dalek/rsa/p256, no `ring`, # no OpenSSL) and MIT/Apache-2.0 — sequoia-openpgp is deliberately avoided as it # is LGPL, which our license gate forbids (see docs/decisions.md). ssh-key = { version = "0.6.7", features = ["ed25519", "rsa", "p256", "std"] } pgp = "0.20" # SMTP mail. native-tls (system OpenSSL, already a build input) rather than the # spec's rustls transport, which pulls `ring` and would fail the license gate — # see docs/decisions.md. lettre = { version = "0.11", default-features = false, features = [ "builder", "smtp-transport", "tokio1", "tokio1-native-tls", "hostname", ] } # Web stack. No TLS/rustls features anywhere (TLS terminates at a reverse proxy), # so `ring` stays out of the graph. Versions are the current mutually-compatible # axum 0.8 set. axum = { version = "0.8", features = ["multipart"] } axum-extra = { version = "0.12", features = ["cookie"] } maud = { version = "0.27", features = ["axum"] } tower = { version = "0.5", features = ["util"] } tower-http = { version = "0.6", features = [ "compression-gzip", "trace", "request-id", "timeout", ] } rust-embed = "8" mime_guess = "2" time = "0.3" # Markdown rendering (GFM) + HTML sanitization for READMEs and comments. comrak = { version = "0.54", default-features = false } ammonia = "4" # Streaming the pack subprocess stdout into the HTTP response body; gunzip for # gzip-encoded upload-pack requests. tokio-util = { version = "0.7", features = ["io"] } # Stream adapters for LFS: turn the request body stream into an AsyncRead. futures-util = "0.3" flate2 = "1" # Content search (ripgrep's engine). Dual Unlicense/MIT — MIT satisfies the gate. grep = "0.3" # Outbound HTTP for optional captcha verification only. native-tls (system # OpenSSL, already a build input) not rustls, so `ring` stays out of the graph; # http2/charset off to keep the tree small. See docs/decisions.md. reqwest = { version = "0.12", default-features = false, features = [ "native-tls", "json", ] } # SSH server (default features: aws-lc-rs backend, no ring; re-exports ssh-key). russh = "0.62" # S3-compatible object storage for uploads (avatars, release assets, LFS). # aws-lc-rs crypto (already in the tree via russh) so `ring` stays out and the # license gate holds; see docs/decisions.md. Manual client config (static creds, # custom endpoint) avoids the heavier aws-config credential-provider chain. aws-sdk-s3 = { version = "1", default-features = false, features = [ "behavior-version-latest", "rt-tokio", ] } # The HTTP client is wired explicitly with the aws-lc-rs rustls provider so the # default (ring-based) client is never pulled in — keeping `ring` out of the tree. aws-smithy-http-client = { version = "1", default-features = false, features = [ "rustls-aws-lc", ] } bytes = "1" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] } [workspace.lints.rust] unsafe_code = "forbid" missing_docs = "warn" [workspace.lints.clippy] all = { level = "deny", priority = -1 } pedantic = { level = "warn", priority = -1 } unwrap_used = "deny" expect_used = "warn" panic = "deny" todo = "deny" [profile.release] strip = true lto = false # The pinned nightly ICEs in LLVM codegen compiling tokio's runtime at any # optimization level (a known codegen bug, unrelated to our code, in the type # `Notified>`). That type is compiled inside tokio's own # crate, so building just tokio unoptimized dodges the ICE while every other crate # stays optimized. Revisit when the fenix toolchain is bumped past the bug. [profile.release.package.tokio] opt-level = 0