backend / lorx
Lorx forge server
A single Rust process that hosts git repositories over smart HTTP and SSH and serves the REST API for change requests, review, merge and auto-merge, on SQLite or Postgres.
2026 · live · private repository
- [*]LanguagesRust · JavaScript · Shell
- [*]Frameworksaxum · tower-http · SeaORM · utoipa · russh · gix · tokio
- [*]StoresSQLite · Postgres · git bare repositories on local disk
- [*]AsyncTransactional outbox with durable consumer cursors · Server-Sent Events · Periodic sweeps (orphaned git processes, push reconciliation)
- [*]InfraGCP Compute Engine · Caddy · systemd · Cloudflare Workers (relay) · Cloudflare KV · Vercel (web app) · GitHub Actions
- [*]Third partiesGitHub OAuth and REST (sign-in, pull request import) · SMTP (lettre) · OpenTelemetry OTLP
lorx serve is an axum server that hosts ordinary bare git repositories. Clone, fetch and push work over smart HTTP (v0 and v2, streamed through git http-backend) and over an embedded russh SSH server that can only run git. The same process serves 108 REST operations across 92 paths under /api/v1, all described in a committed OpenAPI document that CI checks against the handlers and the generated TypeScript types.
Every push runs git's receive hooks, which call back into the server over a Unix socket. pre-receive runs while the pushed objects are still in git's quarantine and accepts or refuses the whole push. post-receive records what was applied and writes the events into a database outbox in the same transaction. Before a force-push or deletion moves a head, the old head is kept under refs/lorx/history/ so garbage collection cannot drop it.
Change requests merge with no working tree. git merge-tree writes into a throwaway object directory, and the base ref only moves through a compare-and-swap update-ref, so a push that lands at the same moment wins. A merge gate built from branch rules (CI passed on the head, the in-house review engine finding nothing at or above a set severity) blocks the merge endpoint. Auto-merge is armed per request and fires on the server, as the person who armed it, with their access checked at the moment it fires.
The workspace has 350 commits from 2026-09-22 to 2026-10-07 and 90,551 lines of Rust across 8 crates. Of those, 48,596 are in lorx-server and 12,724 in lorx-db (24 SeaORM migrations, 35 entities). It runs on one e2-small GCP VM behind Caddy, on SQLite. The live health endpoint reports backend sqlite.
How it holds up
- Auth
- Argon2 passwords with web sessions and CSRF tokens, scoped access tokens for git and the API (Basic or Bearer, never cookies on git routes), an OAuth-style device flow for the bit CLI, GitHub OAuth linking, step-up re-authentication for sensitive actions, signed known-device cookies that exempt a browser from account lockout, and a central permit() check that answers 401 or 404 so a private repository's existence is never revealed.
- Data
- 35 SeaORM entities over 24 migrations that run on both SQLite and Postgres: users, identities, sessions, tokens, device authorizations, repos, branch rules, ref updates, change requests with revisions, reviews, comments and events, a commit index and code cache, the events outbox with event_consumers cursors, and audit events.
- Consistency
- Events go into the outbox in the same transaction as the change they report, and outbox writers are serialised so seq commits in order. On SQLite every write transaction takes the writer lock with its first statement, so WAL snapshot conflicts wait out the busy timeout instead of failing. Merges move refs only by compare-and-swap. Auto-merge holds a per-request mutex from reading the arming to the end of the merge, and coalesces results that arrive while an attempt is running.
- Failure
- Every git child asks the kernel for SIGKILL when the server dies, and a sweep every minute kills descendants whose owning server (identified by pid, start time and pid namespace) is gone. Ref updates that post-receive never reported are reconciled against the repository's actual refs. API routes have a request timeout and a panic catcher that returns a JSON error. Auto-merge waits on conditions that can clear by themselves and disarms, with the reason on the timeline, on ones that cannot.
- Testing
- 33 integration test files in crates/lorx-server/tests (28 for the forge itself, 5 for CI) drive real git over HTTP and SSH, SMTP and the device flow. There are 624 #[test]/#[tokio::test] cases across the workspace, 74 of them in lorx-review with a flag/pass corpus. A migrations test proves every down migration works. GitHub Actions runs the whole suite on both SQLite and Postgres.
Techniques
- Transactional outbox with durable, resumable deliveryEvents are written in the same transaction as the change they describe, so a crash can neither lose one nor invent one. Each consumer keeps a durable cursor and acknowledges by seq. SSE clients resume from Last-Event-ID out of the same outbox, so they miss nothing and get nothing twice.
crates/lorx-server/src/events.rs:3crates/lorx-server/src/events.rs:51crates/lorx-db/src/store.rs:161crates/lorx-server/src/live.rs:3 - All-or-nothing push policy inside git's quarantinepre-receive judges the pushed objects before they enter the repository and accepts or refuses the whole push, giving a reason for each refused ref. post-receive applies the result. Heads displaced by a force-push or deletion are kept under refs/lorx/history/ so they can be restored, and pushes that were never reported are reconciled against the real refs.
crates/lorx-server/src/hooks.rs:10crates/lorx-server/src/hooks.rs:54crates/lorx-server/src/hooks.rs:595 - Worktree-free merges with a compare-and-swap ref moveMergeability and conflict checks run git merge-tree against a temporary object directory that uses the repository's objects as alternates, so the check leaves nothing behind. The base ref moves only through update-ref with the expected old value, so a push that races the merge wins.
crates/lorx-server/src/git.rs:168crates/lorx-server/src/git.rs:312crates/lorx-server/src/api/cr_merge.rs:182 - Merge gate and server-side auto-mergeBranch rules are combined into the strictest requirement: CI passed on the head, and the review engine finding nothing at or above a set severity. An armed change request merges itself on the server as the person who armed it, with their access checked when it fires. A per-request lock makes a disarm either land before the merge starts or find it already merged.
crates/lorx-server/src/merge_gate.rs:1crates/lorx-server/src/merge_gate.rs:46crates/lorx-server/src/auto_merge.rs:1crates/lorx-server/src/auto_merge.rs:37 - Reaping orphaned git processesEvery git child sets PR_SET_PDEATHSIG, which is the only unsafe block in the workspace. Each child also carries an owner marker (pid, start time, pid namespace) that its descendants inherit. A sweep every minute kills processes whose owner is gone and never touches another user's or another namespace's processes.
crates/lorx-server/src/git.rs:46crates/lorx-server/src/reaper.rs:1 - Rate limiting that a flood cannot evictFixed-window limits are kept in memory, one capped map per namespace. When a namespace fills, it evicts expired windows first and then the lowest-count ones, and it never evicts a window that has reached half its limit. That keeps a pending lockout alive through a key-flooding attack. Sign-in failures for unknown users verify against a dummy Argon2 hash to equalise timing, and known-device cookies stop an attacker from locking the real owner out.
crates/lorx-server/src/auth/ratelimit.rs:12crates/lorx-server/src/auth/ratelimit.rs:157crates/lorx-server/src/auth/password.rs:41crates/lorx-server/src/auth/device.rs:1
Counted
- 350Commits
git rev-list --count HEAD - 90,551Rust LOC (workspace)
git ls-files '*.rs' | xargs wc -l | tail -1 - 48,596lorx-server LOC
git ls-files crates/lorx-server | grep .rs$ | xargs cat | wc -l - 12,724lorx-db LOC
same, crates/lorx-db - 108 / 92REST operations / paths
crates/lorx-server/openapi.json, counted with python json - 24Migrations
ls crates/lorx-db/src/migration/m*.rs - 35Entities (tables)
ls crates/lorx-db/src/entity | grep -v mod.rs | wc -l - 33Server integration test files
git ls-files crates/lorx-server/tests | grep .rs$ - 624Test cases (workspace)
grep -cE '#\[(tokio::)?test' over git ls-files '*.rs', corpus excluded - 11Active days
git log --format=%ad --date=short | sort -u | wc -l