{"slug": "gitlawb-node-federated-git-server-in-rust", "title": "Gitlawb Node, Federated Git server in Rust", "summary": "Gitlawb released Gitlawb Node, an open-source Rust workspace for a decentralized git infrastructure that lets users run self-hosted nodes, publish repositories under DID identities, and sign writes with Ed25519 HTTP signatures. The project, which includes four crates (gitlawb-node, gl CLI, git-remote-gitlawb, and gitlawb-core), aims to create a resilient app-delivery network where code and build assets are served closer to users, with known limitations including incomplete private repository enforcement and UCAN validation. The software is available via Docker Compose, npm, Homebrew, curl, and PowerShell.", "body_md": "**Decentralized git infrastructure for developers, AI agents, and app delivery.**\n\nGitlawb Node is the open-source node software behind the Gitlawb network. It lets anyone run a self-hosted node, publish repositories under a DID, sign writes with Ed25519 HTTP signatures, replicate git activity across peers, and move toward a resilient app-delivery network where code and build assets can be served closer to users.\n\nGitlawb is not trying to be only \"another git host.\" The long-term direction is:\n\n```\nDecentralized GitHub\n+ signed agent-native workflows\n+ resilient repo replication\n+ CDN-style app/code delivery\n```\n\nThe mission is simple: once code is pushed to the network, it should not disappear because one server went down.\n\nThis is a Rust workspace with four crates:\n\n| Crate | Purpose |\n|---|---|\n`gitlawb-node` |\nThe node daemon: Axum HTTP server, git smart-HTTP, Postgres metadata, libp2p gossip, optional S3/Tigris/IPFS/Arweave/Base PoS hooks. |\n`gl` |\nThe Gitlawb CLI for identity, repos, issues, PRs, bounties, tasks, peers, node status, MCP, and setup flows. |\n`git-remote-gitlawb` |\nGit remote helper for `gitlawb://` URLs, so normal `git clone` , `git fetch` , and `git push` can talk to Gitlawb nodes. |\n`gitlawb-core` |\nShared primitives: Ed25519 identities, `did:key` , CIDs, RFC 9421 HTTP signatures, certificates, and UCAN tokens. |\n\nMost git hosting today depends on a small number of centralized platforms. Gitlawb Nodes are designed for a different model:\n\n**Own your identity**: every user, agent, and node is an Ed25519 keypair represented as`did:key:z6Mk...`\n\n.**Signed writes by default**: write requests use RFC 9421 HTTP Signatures instead of passwords.** Git-native transport**: repositories are still real git repositories served over smart HTTP.** Agent-native workflows**: the`gl`\n\nCLI and MCP server expose repo, issue, task, PR, and UCAN flows to AI agents.**Peer-aware delivery**: nodes can announce, discover, gossip, and sync with each other.** App CDN direction**: the network can evolve from decentralized code storage into code + asset + app delivery.\n\nGitlawb Node is live early infrastructure. It is useful today, but some security and reliability features are intentionally staged for compatibility with existing nodes.\n\nGood today:\n\n- Local or Docker node startup.\n- Postgres-backed repo metadata.\n- Bare git repository storage.\n- Git smart-HTTP clone/fetch/push.\n- RFC 9421-signed writes.\n- DID identities.\n`gl`\n\nCLI workflows.- libp2p peer discovery/gossip foundation.\n- Optional Tigris/S3 storage.\n- Optional IPFS/Pinata and Arweave/Irys hooks.\n- Optional Base node-operator staking/heartbeat hooks.\n\nKnown limitations:\n\n- Private repository read enforcement is not wired yet. Treat public nodes as public infrastructure unless you restrict access at your proxy/firewall.\n- UCAN chain validation and revocation are not complete.\n- Repository write authorization is not capability-complete yet; HTTP signatures prove identity, not full authorization policy.\n- Peer writes are signed by upgraded nodes, but strict signed-peer enforcement is opt-in during rolling upgrades.\n- GraphQL mutations need mutation-aware auth before becoming a public write surface.\n\nSee:\n\nThe fastest path is Docker Compose. It starts a node and Postgres.\n\n```\ngit clone https://github.com/Gitlawb/node.git\ncd node\ncp .env.example .env\ndocker compose up -d\n```\n\nYour local node will serve:\n\n| Service | Default |\n|---|---|\n| HTTP API + git smart-HTTP | `http://localhost:7545` |\n| libp2p QUIC/UDP | `7546` |\n| Postgres | compose-managed |\n\nVerify:\n\n```\ncurl http://localhost:7545/health\ncurl http://localhost:7545/api/v1/stats\n```\n\nExpected health response:\n\n```\n{ \"status\": \"ok\" }\n```\n\nStop it:\n\n```\ndocker compose down\n# npm (macOS / Linux)\nnpm install -g @gitlawb/gl\n\n# Homebrew (macOS / Linux)\nbrew install gitlawb/tap/gl\n\n# curl (macOS / Linux)\ncurl -fsSL https://gitlawb.com/install.sh | sh\n\n# PowerShell (Windows)\nirm https://gitlawb.com/install.ps1 | iex\n```\n\nOr build from source:\n\n```\ncargo build --release -p gl -p git-remote-gitlawb -p gitlawb-node\n```\n\nPut these binaries on your `PATH`\n\n:\n\n```\ntarget/release/gl\ntarget/release/git-remote-gitlawb\ntarget/release/gitlawb-node\n```\n\nCheck your setup:\n\n```\ngl doctor\n```\n\nCreate an identity:\n\n```\ngl identity new\ngl identity show\n```\n\nRegister against your local node:\n\n```\ngl register --node http://localhost:7545\n```\n\nCreate a repo:\n\n```\ngl repo create my-repo --description \"My first Gitlawb repo\" --node http://localhost:7545\n```\n\nUse the git remote helper:\n\n```\nexport GITLAWB_NODE=http://localhost:7545\ngit clone gitlawb://did:key:z6Mk.../my-repo\n```\n\nFor public-network use, make sure `GITLAWB_NODE`\n\npoints to the node you want. The helper defaults to localhost for local development.\n\nPublic nodes (e.g. `node.gitlawb.com`\n\n) require two things on writes:\n\n**RFC 9421 HTTP Signatures**: every write is signed by your identity key.`gl`\n\nand the`git-remote-gitlawb`\n\nhelper do this automatically. An old/unsigned CLI fails with`401 not_an_agent`\n\n;`gl`\n\nwill tell you to upgrade and register.**An iCaptcha proof** on the spam-gated writes (**repo create, fork, register**).`gl`\n\nsolves this for you: on the node's`403 icaptcha_proof_required`\n\nit reads the`x-icaptcha-url`\n\n/`x-icaptcha-level`\n\nhints, requests a challenge, solves it locally (arithmetic / algebra / sequence), and**retries the same signed request** with the`x-icaptcha-proof`\n\nheader. No manual steps, no env vars.\n\n```\ngl identity new                                   # create did:key identity\ngl register      --node https://node.gitlawb.com  # signed + auto-solves iCaptcha\ngl repo create memlawb --node https://node.gitlawb.com   # signed + auto-solves iCaptcha\ngit push  origin2 main                            # origin2 = gitlawb://<your-did>/memlawb (signed)\ngit clone gitlawb://<your-did>/memlawb            # public read, no proof needed\ngl doctor                                         # preflight: identity, node, version, iCaptcha\n```\n\nNotes:\n\nThe proof's`requesterId`\n\nis always your DID.`sub`\n\nclaim must equal the authenticated signer;`gl`\n\n/helper set this automatically and the node enforces`sub == authenticated DID`\n\n(so a proof minted for another identity is rejected).**Proofs are short-lived (~5 min TTL) and single-use.** If one expires between solving and use, the client transparently solves a fresh one and retries.**What needs what:** create / fork / register are signed**and** iCaptcha-gated;`git push`\n\nis**signed-only**(owner signature is the gate, no per-push challenge); reads (clone / fetch /`repo info`\n\n) need no proof. A non-existent repo returns a clear`404`\n\n, never a placeholder.**API-key iCaptcha deployments:** set`GITLAWB_ICAPTCHA_URL`\n\nto your iCaptcha origin and`GITLAWB_ICAPTCHA_API_KEY`\n\nto its key. The client only talks to an`https`\n\norigin whose host is allowlisted (that URL or the public default), and sends the bearer token**only** to your configured origin, never to a URL a node advertises, so a hostile node can't capture the key or redirect the solve.\n\n```\n┌──────────────────────────┐\n│ gl CLI / git / AI agents │\n└────────────┬─────────────┘\n             │ signed HTTP writes / git smart-HTTP\n             ↓\n┌──────────────────────────┐\n│ gitlawb-node             │\n│ Axum API + git routes    │\n└────────────┬─────────────┘\n             │\n    ┌────────┴────────┐\n    ↓                 ↓\nPostgres        Bare git repos\nmetadata        local disk / optional S3\n    │                 │\n    └────────┬────────┘\n             ↓\n       libp2p peers\n  gossip + discovery + sync\n             ↓\n optional IPFS / Arweave / Base PoS\n```\n\n| Concept | Meaning |\n|---|---|\n| DID | A user, agent, or node identity derived from an Ed25519 public key. |\n| HTTP Signature | RFC 9421 signature proving control of the DID key for write requests. |\n| Ref certificate | Signed record of a ref update. Useful for audit and replication. |\n| UCAN | Delegation token for future capability-based workflows. |\n| Peer announce | Node-to-node HTTP announcement of DID + public URL. |\n| Gossipsub | libp2p topic for ref-update events. |\n| Smart HTTP | Standard git protocol over HTTP for clone/fetch/push. |\n\nThe node exposes both git smart-HTTP routes and JSON APIs.\n\nCommon public read routes:\n\n```\nGET /health\nGET /\nGET /api/v1/stats\nGET /api/v1/contracts\nGET /api/v1/repos\nGET /api/v1/repos/{owner}/{repo}\nGET /api/v1/repos/{owner}/{repo}/tree\nGET /api/v1/repos/{owner}/{repo}/blob/{path}\nGET /api/v1/repos/{owner}/{repo}/issues\nGET /api/v1/repos/{owner}/{repo}/pulls\nGET /api/v1/peers\nGET /{owner}/{repo}/info/refs\nPOST /{owner}/{repo}/git-upload-pack\n```\n\nSigned write routes include:\n\n```\nPOST /api/v1/repos\nPOST /api/register\nPOST /api/v1/repos/{owner}/{repo}/fork\nPOST /api/v1/repos/{owner}/{repo}/issues\nPOST /api/v1/repos/{owner}/{repo}/pulls\nPOST /api/v1/repos/{owner}/{repo}/pulls/{number}/merge\nPOST /api/v1/repos/{owner}/{repo}/hooks\nPOST /api/v1/bounties/{id}/...\nPOST /{owner}/{repo}/git-receive-pack\n```\n\nThese peer write routes support staged rollout:\n\n```\nPOST /api/v1/peers/announce\nPOST /api/v1/sync/notify\n```\n\nWhen `GITLAWB_REQUIRE_SIGNED_PEER_WRITES=false`\n\n, unsigned legacy peers are accepted on those two routes, but signed requests are verified when signature headers are present. Staged rollout relaxes who may announce, not who owns a peer row. An unsigned announce may register a previously unseen peer, and it may refresh an existing row whose `http_url`\n\nis unchanged, but changing an existing peer's `http_url`\n\nrequires an RFC 9421 signature from that peer's own DID and is refused with 403 otherwise. An unsigned announce can only register a `did:key`\n\n, since that is the only method whose verifying key can be resolved and therefore the only kind of row anyone could ever correct through the signed path; any other DID is refused with 400. Once all live peers upgrade, operators can set:\n\n```\nGITLAWB_REQUIRE_SIGNED_PEER_WRITES=true\n```\n\n`POST /api/v1/sync/trigger`\n\nis not part of the staged rollout: it always requires a signature in both config modes and returns 401 without one, because each call drives an O(peers) outbound fan-out.\n\nAll configuration is via environment variables. See [ .env.example](/Gitlawb/node/blob/main/.env.example) for the full reference.\n\nMinimum required for a persistent node:\n\n```\nDATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawb\n```\n\nImportant node settings:\n\n| Variable | Purpose |\n|---|---|\n`GITLAWB_HOST` / `GITLAWB_PORT` |\nHTTP bind address and port. |\n`GITLAWB_REPOS_DIR` |\nLocal bare repo storage directory. |\n`GITLAWB_PUBLIC_URL` |\nPublic HTTP URL announced to peers. |\n`GITLAWB_P2P_PORT` |\nlibp2p QUIC/UDP port. Use `0` to disable. |\n`GITLAWB_BOOTSTRAP_PEERS` |\nComma-separated HTTP peer URLs. |\n`GITLAWB_P2P_BOOTSTRAP` |\nComma-separated libp2p multiaddrs. |\n`GITLAWB_BOOTSTRAP_DISABLE_SEEDS` |\nDisable embedded seed peers for isolated dev/test networks. |\n`GITLAWB_REQUIRE_SIGNED_PEER_WRITES` |\nRequire signed peer announce/sync writes. |\n`GITLAWB_AUTO_SYNC` |\nEnable automatic sync from known peers. |\n`GITLAWB_MAX_PACK_BYTES` |\nMax git pack body size for smart-HTTP routes. |\n`GITLAWB_GIT_SERVICE_TIMEOUT_SECS` |\nMax seconds a served git upload-pack, receive-pack, or `info/refs` advertisement may run before it is aborted (504). Default 600. Also bounds the withheld-blob classification walk (on both the upload-pack serve and receive-pack replication paths) and the push-side pin-candidate discovery (`rev-list` / `cat-file` ), each reaped via process-group teardown at the deadline. On the path-scoped upload-pack path the classification walk and the pack serve share ONE deadline, so this value bounds their combined duration rather than granting each stage a full budget: a walk that consumes it leaves the serve nothing and the clone gets a 504. Serving large path-scoped repos may therefore need a higher value than they did when each stage was budgeted separately. Accepted range is 1 to 3153600000 (100 years), since the node derives deadlines from this value and a larger one cannot be represented. |\n`GITLAWB_GIT_ACQUIRE_TIMEOUT_SECS` |\nMax seconds the storage-acquisition phase (Tigris HEAD/GET, push advisory-lock) of a served git op may run before the request is shed with a 503, separate from the git-run timeout. The concurrency permit is released on expiry so a stalled backend cannot pin the pool. Default 30. |\n`GITLAWB_MAX_CONCURRENT_GIT_OPS` |\nMax concurrent served git READ ops (upload-pack and its `info/refs` advertisement) across all callers; over-cap sheds a 503 + Retry-After. Anonymous reads draw from this pool, so pair it with `GITLAWB_MAX_CONCURRENT_READS_PER_CALLER` . Pushes and the receive-pack advertisement have their own pools, so a read flood cannot shed an authenticated push. Default 128. |\n`GITLAWB_MAX_CONCURRENT_GIT_PUSHES` |\nMax concurrent `git-receive-pack` POST operations, in a pool separate from the read pool. The anon receive-pack `info/refs` advertisement runs in a third pool of the same size, disjoint from both, so an advertisement flood cannot shed a push either. Two per-source push caps are derived from this value (`/8` , floor 1) and have no env var of their own. Over-cap sheds a 503 + Retry-After. Default 32. |\n`GITLAWB_MAX_CONCURRENT_READS_PER_CALLER` |\nMax concurrent read ops a single caller may hold, so one caller cannot monopolize the read pool. Keyed on the resolved source IP, never the DID, and only as granular as `GITLAWB_TRUSTED_PROXY` : left unset, a node behind an edge or NAT keys every caller on the edge IP and this collapses to one global cap. Default 16. |\n`GITLAWB_MAX_CONCURRENT_PIN_TASKS` |\nMax post-push pin loops (IPFS + Pinata) running concurrently across all repos. This caps how many loops RUN at once, not how much object-id list memory the node retains: on the local IPFS path a loop parked waiting for a permit still holds its full list. Do not size memory from this knob alone. A loop over cap waits, never drops a pin. Default 8. |\n`GITLAWB_REPO_LEASE_MAX_WAITERS` |\nMax pushes parked at once waiting for the same repo's write lease. Each waiter pins its buffered pack body, so this bounds that memory for a hot repo; past the cap the newest push sheds a 503 + Retry-After instead of queueing. Pushes to other repos are unaffected, and the lease holder is not counted. Default 8. |\n`GITLAWB_MAX_CONCURRENT_IPFS_WALKS` |\nMax concurrent `GET /ipfs/{cid}` visibility walks across all callers (own pool, disjoint from the served-git pools); over-cap sheds 503. Default 32. |\n`GITLAWB_IPFS_WALK_PER_SOURCE` |\nMax concurrent `/ipfs` walks a single source IP may hold. Default 4. |\n`GITLAWB_IPFS_MAX_REPOS_WALKED` |\nMax expensive path-scope visibility walks per `/ipfs/{cid}` request; over-cap repos are skipped and the scan continues, shedding a retryable 503 (not a false 404) if the object is then found nowhere. Default 64. |\n`GITLAWB_IPFS_MAX_REPO_VISITS` |\nCeiling on repos one `/ipfs/{cid}` request may visit (acquire + probe) past the visibility gate. Also the worst-case per-request Tigris fetch count. On exhaustion the scan stops with a retryable 503. Default 1024. |\n`GITLAWB_IPFS_REQUEST_BUDGET_SECS` |\nAbsolute wall-clock budget for one admitted `/ipfs/{cid}` request's acquire+walk lifetime. Per-stage clamps bound the acquire and walk stages to the remaining budget, and no stage starts once it is exhausted; the scan then stops with a retryable 503. The object-type probe and content-read `cat-file` subprocesses are budget-checked before starting and each also run under their own deadline (the lesser of `GITLAWB_GIT_SERVICE_TIMEOUT_SECS` and the remaining budget), reaped via process-group teardown, so a hung `cat-file` cannot hold the request's walk slot past it. One hang path is still unbounded: the probe's object-store readability check is a plain filesystem sweep with nothing to reap, so a wedged filesystem can hold the slot past the deadline. Default 600. Accepted range is 1 to 3153600000 (100 years), since the node derives a deadline from this value and a larger one cannot be represented. |\n`GITLAWB_IPFS_RATE_LIMIT` |\nMax `/ipfs/{cid}` requests per client IP per hour (route flood brake). 0 disables. Default 600. |\n`GITLAWB_TIGRIS_BUCKET` |\nOptional S3/Tigris shared repo storage bucket. |\n`GITLAWB_PINATA_JWT` |\nOptional Pinata/IPFS warm-storage pinning. |\n`GITLAWB_IRYS_URL` |\nOptional Irys/Arweave permanent anchoring. |\n\nProduction note: change the default Postgres password before exposing a node publicly.\n\nGitlawb Node includes optional Base L2 node-operator hooks. Operators can register a node DID, stake `$GITLAWB`\n\n, and post heartbeats.\n\nPoS is disabled unless these are configured:\n\n```\nGITLAWB_CONTRACT_NODE_STAKING=0x...\nGITLAWB_OPERATOR_PRIVATE_KEY=0x...\nGITLAWB_CHAIN_RPC_URL=https://mainnet.base.org\n```\n\nRecommended for operators:\n\n```\nGITLAWB_OPERATOR_STRICT_MODE=true\nGITLAWB_HEARTBEAT_INTERVAL_HOURS=20\n```\n\nRead:\n\nUse a dedicated low-balance operator wallet. Do not use a treasury wallet as the heartbeat key.\n\nRequires Rust 1.91+.\n\n```\ncargo build --release -p gitlawb-node -p gl -p git-remote-gitlawb\n```\n\nRun tests:\n\n```\ncargo fmt --all -- --check\ncargo clippy --workspace --all-targets -- -D warnings\ncargo test --workspace\n```\n\nRun the node from source:\n\n```\nDATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawb \\\n  cargo run -p gitlawb-node --release\n```\n\nA native Swift/AppKit menu bar app is included for managing a local Docker Compose stack without living in the terminal.\n\nRequirements:\n\n- macOS 26+\n- Xcode Command Line Tools\n- Docker Desktop, OrbStack, or Colima\n\nBuild:\n\n```\n./scripts/build-macos-app.sh\n```\n\nOutput:\n\n```\ndist/Gitlawb Node.app\ndist/Gitlawb Node.dmg\n```\n\nFeatures:\n\n- Start/stop local node stack.\n- Status indicator.\n- Settings GUI.\n- Auto-start on login.\n- Docker runtime detection.\n\nUnsigned local build:\n\n```\nxattr -cr \"dist/Gitlawb Node.app\"\n```\n\nThe current maintainer focus is live-network stability first.\n\nShort-term priorities:\n\n- Keep CI green: fmt, clippy, tests, release build.\n- Add Docker and installer smoke tests.\n- Improve operator docs and\n`gl doctor`\n\nchecks. - Harden peer writes and publish the signed-peer rollout plan.\n- Implement repo write authorization: owner checks, protected branches, and UCAN capability checks.\n- Implement private-read enforcement or remove private repo affordances until it exists.\n- Add metrics for pushes, fetches, pack sizes, peer sync, failed auth, and webhooks.\n\nProduct direction:\n\n- Reliable repo replication.\n- Health-aware peer syncing.\n- CDN-style clone/fetch routing to healthy replicas.\n- App asset/build delivery from nodes.\n- Operator dashboard and desktop UX.\n\nRead the maintainer roadmap:\n\n```\ndocs/MAINTAINER-ROADMAP.md\n```\n\nStart here:\n\nGood first contribution areas:\n\n- docs and install polish\n- Docker smoke tests\n- CLI error messages\n`gl doctor`\n\nchecks- operator dashboard UX\n- test coverage for peer sync and signed writes\n\nSecurity issues should follow [ SECURITY.md](/Gitlawb/node/blob/main/SECURITY.md).\n\nLicensed under either of:\n\n- Apache License, Version 2.0 (\n[LICENSE-APACHE](/Gitlawb/node/blob/main/LICENSE-APACHE)or[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) - MIT license (\n[LICENSE-MIT](/Gitlawb/node/blob/main/LICENSE-MIT)or[http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))\n\nat your option.\n\nUnless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be dual licensed as above, without any additional terms or conditions.", "url": "https://wpnews.pro/news/gitlawb-node-federated-git-server-in-rust", "canonical_source": "https://github.com/Gitlawb/node", "published_at": "2026-08-14 08:28:16+00:00", "updated_at": "2026-08-14 08:41:04.477485+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-infrastructure"], "entities": ["Gitlawb", "Gitlawb Node", "Rust", "Axum", "Postgres", "libp2p", "Tigris", "IPFS"], "alternates": {"html": "https://wpnews.pro/news/gitlawb-node-federated-git-server-in-rust", "markdown": "https://wpnews.pro/news/gitlawb-node-federated-git-server-in-rust.md", "text": "https://wpnews.pro/news/gitlawb-node-federated-git-server-in-rust.txt", "jsonld": "https://wpnews.pro/news/gitlawb-node-federated-git-server-in-rust.jsonld"}}