Decentralized git infrastructure for developers, AI agents, and app delivery.
Gitlawb 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.
Gitlawb is not trying to be only "another git host." The long-term direction is:
Decentralized GitHub
+ signed agent-native workflows
+ resilient repo replication
+ CDN-style app/code deliveryThe mission is simple: once code is pushed to the network, it should not disappear because one server went down.
This is a Rust workspace with six crates:
| Crate | Purpose |
|---|---|
gitlawb-node |
The node daemon: Axum HTTP server, git smart-HTTP, Postgres metadata, libp2p gossip, optional S3/Tigris/IPFS/Arweave/Base PoS hooks. |
gl |
The Gitlawb CLI for identity, repos, issues, PRs, bounties, tasks, peers, node status, MCP, and setup flows. |
git-remote-gitlawb |
Git remote helper for gitlawb:// URLs, so normal git clone, git fetch, and git push can talk to Gitlawb nodes. |
gitlawb-core |
Shared primitives: Ed25519 identities, did:key, CIDs, RFC 9421 HTTP signatures, certificates, and UCAN tokens. |
gitlawb-attest |
Pluggable external provenance attestations for ref-update certificates. |
icaptcha-client |
Client for the iCaptcha proof-of-intelligence flow used to protect spam-prone writes. |
Most git hosting today depends on a small number of centralized platforms. Gitlawb Nodes are designed for a different model:
- Own your identity: every user, agent, and node is an Ed25519 keypair represented as
did:key:z6Mk.... - 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
glCLI 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.
Gitlawb Node is live early infrastructure. It is useful today, but some security and reliability features are intentionally staged for compatibility with existing nodes.
Good today:
- Local or Docker node startup.
- Postgres-backed repo metadata.
- Bare git repository storage.
- Git smart-HTTP clone/fetch/push.
- RFC 9421-signed writes.
- Repository and path-scoped visibility enforcement for repository and Git content reads, with 404-shaped repository denials.
- DID identities.
glCLI workflows.- libp2p peer discovery/gossip foundation.
- Optional Tigris/S3 storage.
- Optional IPFS/Pinata and Arweave/Irys hooks.
- Optional Base node-operator staking/heartbeat hooks.
Known limitations:
- Repository write authorization is not secure by default:
GITLAWB_ENFORCE_OWNER_PUSHdefaults tofalsefor compatibility, so a valid HTTP Signature identifies a pusher but does not enforce owner-only pushes. - UCAN proof chains are validated when supplied, but UCAN capabilities are not consulted by write authorization and the root issuer is not independently trust-anchored. UCANs therefore do not yet grant scoped collaborator access.
- Agent lifecycle revocation is not enforced by HTTP Signature authorization; do not rely on removing or revoking an agent record to block a compromised signer.
- Read visibility is not a blanket data-classification boundary: task, IPFS-pin, and Arweave-anchor listings are not repository-gated; withheld path names can be visible to a root reader; and later visibility changes cannot retract content already announced or externally anchored.
- Peer writes are signed by upgraded nodes, but strict signed-peer enforcement is opt-in during rolling upgrades.
- Current GraphQL mutations require an authenticated signer, but there is no mutation-specific guardrail that prevents a future mutation from omitting that check.
- Pull-request review comments do not yet have threaded line-level anchors, and merges do not enforce approval requirements.
See:
The fastest path is Docker Compose. It starts a node and Postgres.
git clone https://github.com/Gitlawb/node.git
cd node
cp .env.example .env
docker compose up -dYour local node will serve:
| Service | Default |
|---|---|
| HTTP API + git smart-HTTP | http://localhost:7545 |
| libp2p QUIC/UDP | 7546 |
| Postgres | compose-managed |
Verify:
curl http://localhost:7545/health
curl http://localhost:7545/api/v1/statsExpected health response:
{ "status": "ok" }Stop it:
docker compose down# npm (macOS / Linux)
npm install -g @gitlawb/gl
# Homebrew (macOS / Linux)
brew install gitlawb/tap/gl
# curl (macOS / Linux)
curl -fsSL https://gitlawb.com/install.sh | sh
# PowerShell (Windows)
irm https://gitlawb.com/install.ps1 | iexOr build from source:
cargo build --release -p gl -p git-remote-gitlawb -p gitlawb-nodePut these binaries on your PATH:
target/release/gl
target/release/git-remote-gitlawb
target/release/gitlawb-nodeCheck your setup:
gl doctorCreate an identity:
gl identity new
gl identity showRegister against your local node:
gl register --node http://localhost:7545Create a repo:
gl repo create my-repo --description "My first Gitlawb repo" --node http://localhost:7545Use the git remote helper:
export GITLAWB_NODE=http://localhost:7545
git clone gitlawb://did:key:z6Mk.../my-repoFor public-network use, make sure GITLAWB_NODE points to the node you want. The helper defaults to localhost for local development.
Public nodes (e.g. node.gitlawb.com) require two things on writes:
- RFC 9421 HTTP Signatures: every write is signed by your identity key.
gland thegit-remote-gitlawbhelper do this automatically. An old/unsigned CLI fails with401 not_an_agent;glwill tell you to upgrade and register. - An iCaptcha proof on the spam-gated writes (repo create, fork, register).
glsolves this for you: on the node's403 icaptcha_proof_requiredit reads thex-icaptcha-url/x-icaptcha-levelhints, requests a challenge, solves it locally (arithmetic / algebra / sequence), and retries the same signed request with thex-icaptcha-proofheader. No manual steps, no env vars.
gl identity new # create did:key identity
gl register --node https://node.gitlawb.com # signed + auto-solves iCaptcha
gl repo create memlawb --node https://node.gitlawb.com # signed + auto-solves iCaptcha
git push origin2 main # origin2 = gitlawb://<your-did>/memlawb (signed)
git clone gitlawb://<your-did>/memlawb # public read, no proof needed
gl doctor # preflight: identity, node, version, iCaptchaNotes:
requesterIdis always your DID. The proof'ssubclaim must equal the authenticated signer;gl/helper set this automatically and the node enforcessub == authenticated DID(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 pushis signed-only (owner signature is the gate, no per-push challenge); reads (clone / fetch /repo info) need no proof. A non-existent repo returns a clear404, never a placeholder. - API-key iCaptcha deployments: set
GITLAWB_ICAPTCHA_URLto your iCaptcha origin andGITLAWB_ICAPTCHA_API_KEYto its key. The client only talks to anhttpsorigin 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.
gl ipfs list # CIDs this node has pinned
gl ipfs get bafkrei... > object.bin # object bytes on stdoutObjects that were pinned before the node started recording which repo they came
from are found by scanning its repo inventory, and that scan stops at the
per-request ceilings in the Configuration table. A stopped scan
answers 503 with a resume token instead of a false "not found", and gl ipfs get
follows the token automatically: up to 8 resumes after the first request, so at
most 9 calls to the node, waiting between attempts for as long as the node's
Retry-After asks and never longer than 5 seconds.
The whole ladder runs under a 60 second wall-clock deadline. The deadline bounds the search, not the download: each attempt gets the time left on it to produce response headers, and once an object is found its bytes stream outside that deadline. They are not unbounded, though. The client's own 30 second HTTP timeout is a total request timeout, running from the start of a request until its body has finished, so a transfer still going 30 seconds after its request began is cut off. Waits between attempts never run past the deadline either, so a single run spends at most around 90 seconds on the network: the deadline plus the 30 second timeout covering the last attempt. Writing the object out sits outside both bounds, so piping into a reader that stops reading can hold the command open longer than that.
Two node-side brakes end a ladder early and are reported rather than retried around. A 429 is terminal, because the node's rate-limit window is an hour and that wait cannot be honored inside one invocation; a transient overload (a 503 carrying no incomplete-scan code) is retried on the token already held, under the same cap, clamp and deadline. The per-IP fanout brake can also stop a ladder well short of the 9 calls, so automatic resumption is not a guarantee of reaching the object.
When a bound stops the ladder with a usable token in hand, the command prints the token and the invocation that continues from it before exiting nonzero:
resume from where this stopped: gl ipfs get bafkrei... --scan <token>Run that to carry on from where the scan stopped. Re-running without --scan
restarts at the first row, reproduces the same truncation and spends the node's
per-IP budget again, so the token is the only thing that makes progress. Tokens
are valid for an hour.
┌──────────────────────────┐
│ gl CLI / git / AI agents │
└────────────┬─────────────┘
│ signed HTTP writes / git smart-HTTP
↓
┌──────────────────────────┐
│ gitlawb-node │
│ Axum API + git routes │
└────────────┬─────────────┘
│
┌────────┴────────┐
↓ ↓
Postgres Bare git repos
metadata local disk / optional S3
│ │
└────────┬────────┘
↓
libp2p peers
gossip + discovery + sync
↓
optional IPFS / Arweave / Base PoS| Concept | Meaning |
|---|---|
| DID | A user, agent, or node identity derived from an Ed25519 public key. |
| HTTP Signature | RFC 9421 signature proving control of the DID key for write requests. |
| Ref certificate | Signed record of a ref update. Useful for audit and replication. |
| UCAN | Delegation token for future capability-based workflows. |
| Peer announce | Node-to-node HTTP announcement of DID + public URL. |
| Gossipsub | libp2p topic for ref-update events. |
| Smart HTTP | Standard git protocol over HTTP for clone/fetch/push. |
The node exposes both git smart-HTTP routes and JSON APIs.
Common public read routes:
GET /health
GET /
GET /api/v1/stats
GET /api/v1/contracts
GET /api/v1/repos
GET /api/v1/repos/{owner}/{repo}
GET /api/v1/repos/{owner}/{repo}/tree
GET /api/v1/repos/{owner}/{repo}/blob/{path}
GET /api/v1/repos/{owner}/{repo}/issues
GET /api/v1/repos/{owner}/{repo}/pulls
GET /api/v1/peers
GET /{owner}/{repo}/info/refs
POST /{owner}/{repo}/git-upload-packSigned write routes include:
POST /api/v1/repos
POST /api/register
POST /api/v1/repos/{owner}/{repo}/fork
POST /api/v1/repos/{owner}/{repo}/issues
POST /api/v1/repos/{owner}/{repo}/pulls
POST /api/v1/repos/{owner}/{repo}/pulls/{number}/merge
POST /api/v1/repos/{owner}/{repo}/hooks
POST /api/v1/bounties/{id}/...
POST /{owner}/{repo}/git-receive-packThese peer write routes support staged rollout:
POST /api/v1/peers/announce
POST /api/v1/sync/notifyWhen GITLAWB_REQUIRE_SIGNED_PEER_WRITES=false, 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 is unchanged, but changing an existing peer's http_url requires 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, 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:
GITLAWB_REQUIRE_SIGNED_PEER_WRITES=truePOST /api/v1/sync/trigger is 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.
All configuration is via environment variables. See .env.example for the full reference.
Minimum required for a persistent node:
DATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawbImportant node settings:
Production note: change the default Postgres password before exposing a node publicly.
Legacy-pin window: releases before the CID-resolver work stored the provider CID (Kubo dag-pb / Pinata) as a pinned object's resolver key. The /ipfs/{cid} resolver now recomputes the raw-content CID from the object bytes and refuses to serve a key that does not match, so GET /api/v1/ipfs/pins can still advertise an unrepaired legacy CID that 404s. Such a row is repaired opportunistically the next time a push carries the object again (its key is rewritten to the raw CID, the old value kept in legacy_provider_cid), but git negotiation omits objects the node already has, so most legacy rows never re-enter a push delta. A deferred one-shot startup sweep, not this opportunistic path, is what fully retires the advertise-then-404 window. Rows whose object bytes are gone stay withheld.
Gitlawb Node includes optional Base L2 node-operator hooks. Operators can register a node DID, stake $GITLAWB, and post heartbeats.
PoS is disabled unless these are configured:
GITLAWB_CONTRACT_NODE_STAKING=0x...
GITLAWB_OPERATOR_PRIVATE_KEY=0x...
GITLAWB_CHAIN_RPC_URL=https://mainnet.base.orgRecommended for operators:
GITLAWB_OPERATOR_STRICT_MODE=true
GITLAWB_HEARTBEAT_INTERVAL_HOURS=20Read:
Use a dedicated low-balance operator wallet. Do not use a treasury wallet as the heartbeat key.
Requires Rust 1.91+.
cargo build --release -p gitlawb-node -p gl -p git-remote-gitlawbRun tests:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspaceRun the node from source:
DATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawb \
cargo run -p gitlawb-node --releaseA native Swift/AppKit menu bar app is included for managing a local Docker Compose stack without living in the terminal.
Requirements:
- macOS 26+
- Xcode Command Line Tools
- Docker Desktop, OrbStack, or Colima
Build:
./scripts/build-macos-app.shOutput:
dist/Gitlawb Node.app
dist/Gitlawb Node.dmgFeatures:
- Start/stop local node stack.
- Status indicator.
- Settings GUI.
- Auto-start on login.
- Docker runtime detection.
Unsigned local build:
xattr -cr "dist/Gitlawb Node.app"The current maintainer focus is live-network stability first.
Short-term priorities:
- Keep CI green: fmt, clippy, tests, release build.
- Add Docker and installer smoke tests.
- Improve operator docs and
gl doctorchecks. - Harden peer writes and publish the signed-peer rollout plan.
- Close default-open write authorization and wire trusted UCAN delegation into repository permissions.
- Add threaded, line-level pull-request discussions and enforce approval requirements on merges.
- Add metrics for pushes, fetches, pack sizes, peer sync, failed auth, and webhooks.
Product direction:
- Reliable repo replication.
- Health-aware peer syncing.
- CDN-style clone/fetch routing to healthy replicas.
- App asset/build delivery from nodes.
- Operator dashboard and desktop UX.
Read the maintainer roadmap:
docs/MAINTAINER-ROADMAP.mdStart here:
Good first contribution areas:
- docs and install polish
- Docker smoke tests
- CLI error messages
gl doctorchecks- operator dashboard UX
- test coverage for peer sync and signed writes
Security issues should follow SECURITY.md.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless 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.
