Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
65ba137
feat(node,git): cap concurrent served git ops with a 503 load-shed (#62)
Jul 10, 2026
db4de84
feat(node,config): add write-pool and per-caller read-cap knobs (#62)
Jul 10, 2026
2434dde
fix(node,git): isolate authed pushes in a dedicated write pool (#62)
Jul 10, 2026
fc4c206
fix(node,git): per-caller concurrency sub-cap on the read pool (#62)
Jul 10, 2026
2c264e0
fix(node,git): bound and reap the info/refs advertisement (#62)
Jul 10, 2026
57c6e01
fix(node,git): bound and reap the withheld-blob pack build (#62)
Jul 10, 2026
6336657
docs(node,config): reconcile the concurrency-cap comments with the fi…
Jul 10, 2026
5069cd1
fix(node,concurrency): resolve #174 code-review findings
Jul 10, 2026
3b1fa54
docs(config): add GITLAWB_MAX_CONCURRENT_GIT_OPS to .env.example (#174)
Jul 10, 2026
362ee34
fix(node,git): bound the rev-list stage under drive_git_child so a hu…
Jul 11, 2026
4983b85
fix(node): key the per-caller read cap on source IP, not the signed D…
Jul 12, 2026
e444bc0
fix(node): draw the receive-pack advertisement from the write pool (#…
Jul 12, 2026
71389e6
fix(node): bound the withheld-blob walk at the shared blob_paths seam…
Jul 12, 2026
ab0ea24
docs(node): correct the git-service-timeout coverage note (#174)
Jul 12, 2026
87a7e40
fix(review): close the withheld-walk watchdog reused-pgid race (#174)
Jul 12, 2026
e750c30
fix(review): cap the receive-pack advertisement per source so anon ca…
Jul 12, 2026
c35452e
test(node): vet the withheld-walk bound end to end; use the configure…
Jul 12, 2026
454e59b
docs(node): the withheld-walk is bounded by the service timeout, not …
Jul 12, 2026
79b9ee5
fix(node): shed the served-git pool before the DB and cap per-source …
Jul 12, 2026
f19f281
fix(node): give the receive-pack advert its own pool so it can't shed…
Jul 13, 2026
473924d
fix(node): compile the bounded visibility git runner on non-Unix targ…
Jul 13, 2026
7a6bd35
fix(node): SIGKILL a SIGTERM-ignoring member of the visibility-walk p…
Jul 13, 2026
9412aa9
fix(node): bound and reap push-candidate git children under the write…
Jul 13, 2026
4410b83
docs(node): correct concurrency + timeout docs to match the final git…
Jul 13, 2026
cf97405
test(node): execution-cover the delta-path push-scan timeout wiring (…
Jul 13, 2026
a030eef
test(node): retry the ETXTBSY exec race in build_filtered_pack timeou…
Jul 13, 2026
3cc0189
fix(node): keep the visibility-walk watchdog armed until the child is…
Jul 15, 2026
ac59bd7
fix(node): reap the served-git process group on disconnect, not just …
Jul 15, 2026
be0cdd6
fix(node): hold upload-pack admission through the walk on disconnect …
Jul 15, 2026
1264357
fix(node): cap concurrent receive-pack pushes per source IP (#174)
Jul 15, 2026
2a54c15
fix(node): bound concurrent post-push encryption walks with an admiss…
Jul 15, 2026
5749df6
test(node): INV-22 completeness guard for the served-git concurrency …
Jul 15, 2026
72e899d
fix(node): hold served-git admission until the process group is reape…
Jul 15, 2026
8d17038
fix(node): bound the served-git storage-acquisition phase with a rele…
Jul 15, 2026
f424ccb
fix(node): gate the /ipfs walk with bounded concurrency admission + p…
Jul 15, 2026
3947687
fix(node): bound the post-push encryption task set by per-repo coales…
Jul 15, 2026
413d6cf
fix(node): reject unsupported git service before the read slot; docum…
Jul 15, 2026
433c891
feat(node): integrate the #173 IPFS CID tree-gate onto the #174 walk-…
Jul 17, 2026
8afc9ea
fix(node): close grok P1 findings on the #173/#174 IPFS integration
Jul 17, 2026
c767658
fix(node): bound the /ipfs cat-file probes under the walk permit; bac…
Jul 17, 2026
2eea354
fix(node): tie the /ipfs walk ceiling to MAX_PIN_SOURCES + 1
Jul 17, 2026
94ca744
fix(node): align ipfs_pin return with pinata; add Retry-After to Sear…
Jul 17, 2026
d26cf6d
fix(node): close the /ipfs pin-source griefing hole via a bounded sca…
Jul 17, 2026
3af50a5
fix(node): hold filtered-pack admission through disconnect teardown (…
Jul 17, 2026
138b482
fix(node): make /ipfs scan verdicts honest: walk-scoped cap, visit ce…
Jul 17, 2026
39d30dc
fix(node): bound an admitted /ipfs request's total lifetime with a sh…
Jul 17, 2026
daf9195
fix(node): admit the post-receive git scans to the encrypt pool, defe…
Jul 17, 2026
8c95d05
fix(node): requeue coalesced post-push work via an in-lock pending sl…
Jul 18, 2026
48ed0a7
fix(review): close the round's review findings on the #174 fix series
Jul 18, 2026
b6d835d
fix(review): state the probe/read stages' unbounded-hang residual hon…
Jul 18, 2026
8d79692
fix(node): make write-acquire cancellation-safe and connection-affine…
Jul 18, 2026
65d7af2
fix(node): own the post-receive replication tail in a detached task (…
Jul 18, 2026
759c9b6
fix(node): bound the /ipfs cat-file probe and content read (#174 F3)
Jul 18, 2026
6dc2b2e
fix(node): share one whole-scan deadline across both full-scan phases…
Jul 18, 2026
d39fc8d
fix(node): disambiguate absence from an unreadable object store in th…
Jul 18, 2026
448d384
fix(node): bound concurrent pin-loop memory; document the per-repo ta…
Jul 18, 2026
93cdf30
fix(node): make RepoWriteGuard::release cancellation-safe (#174 F4)
Jul 20, 2026
e0e207e
fix(node): classify /ipfs object probe with a version-stable signal (…
Jul 20, 2026
9180467
fix(node): clamp the initial /ipfs metadata queries to the request bu…
Jul 20, 2026
a2be8ba
fix(node): bound post-receive Pinata replication memory (#174 F2)
Jul 20, 2026
a333ddc
fix(node): serialize a disconnected push's repo until its group is re…
Jul 20, 2026
f02c5e1
fix(node): take the write permits after the per-repo lease, not befor…
Jul 20, 2026
e2be6f3
fix(node): don't 500 an /ipfs miss when a transient taint co-occurs (…
Jul 20, 2026
e6f1fec
docs(node): correct three lease/replication comments from code review…
Jul 20, 2026
8682804
fix(node): hold read admission through the filtered-pack reaper
Jul 20, 2026
757edf5
fix(node): own /ipfs admission for the life of the walk
Jul 20, 2026
3351e09
fix(node): requeue coalesced pushes instead of dropping them
Jul 21, 2026
c2fce67
fix(node): wire GITLAWB_IPFS_MAX_REPOS_WALKED to the legacy-probe budget
Jul 21, 2026
0a1618f
fix(node): split /ipfs work accounting off the route rate limiter
Jul 21, 2026
b774c43
fix(node): retry pin-source and pinned-CID recording on transient errors
Jul 21, 2026
45e269b
fix(node): opportunistically repair legacy provider-CID pins
Jul 21, 2026
fba3c6f
fix(review): share one deadline across the /ipfs size+content reads
Jul 21, 2026
62a9927
fix(node): bound the post-push pin and repair reads so a wedge cannot…
Jul 21, 2026
9a70d75
fix(node): own the repo write lock on one connection so cancellation …
beardthelion Jul 27, 2026
8e6e2ad
fix(node): retry the requeue re-read so a coalesced push is not silen…
beardthelion Jul 27, 2026
fccdfc1
fix(node): record a failed pin-source write so the resolver keeps its…
beardthelion Jul 27, 2026
73429c7
fix(node): sweep legacy provider-CID pins instead of waiting for a re…
beardthelion Jul 27, 2026
1c82060
fix(node): stop the lock pool from being occupied by spinners and rac…
beardthelion Jul 27, 2026
c519d54
fix(node): keep the incompleteness marker until a source is really re…
beardthelion Jul 27, 2026
03aee26
fix(node): shed 503 from the issue and PR write paths when the lock p…
beardthelion Jul 28, 2026
370a364
test(node): cover the sweep's boot wiring and prove an interrupted pu…
beardthelion Jul 28, 2026
34a92c7
fix(node): take the per-source write permit before parking on the rep…
beardthelion Jul 28, 2026
92a7e58
fix(node): end the session when the advisory unlock errors, instead o…
beardthelion Jul 28, 2026
316bdc0
fix(node): coalesce a repeat push before it parks on the encrypt pool…
beardthelion Jul 28, 2026
f96fa7f
Merge remote-tracking branch 'origin/main' into fix/served-git-concur…
beardthelion Jul 28, 2026
b71201e
test(node): observe the write-cap shed as state, not as a wall-clock …
beardthelion Jul 28, 2026
8f0e910
fix(node): bound lease waiters per repo, and stop spending cross-repo…
beardthelion Jul 28, 2026
d753198
fix(node): stop a failed walk from taking a pin permit, and bound the…
beardthelion Jul 28, 2026
b9566b7
fix(node): sweep the /ipfs rate limiter and document the lease waiter…
beardthelion Jul 28, 2026
1564302
fix(node): write coalesced-drain pin metadata under the re-fetched re…
beardthelion Jul 29, 2026
19eaf9f
fix(node): key the write lease and encrypt coalescing on the stable d…
beardthelion Jul 29, 2026
38bce70
fix(node): hold /ipfs walk admission through the blocking walk, not t…
beardthelion Jul 29, 2026
43d35eb
fix(node): hold the encrypt-scan permit in its closure and run info/r…
beardthelion Jul 29, 2026
9fc68e2
fix(node): spawn the replication tail at the durability boundary
beardthelion Jul 29, 2026
a496002
style: rustfmt the repo_identity_key tests
beardthelion Jul 29, 2026
62ec882
fix(node): keep the repo identity key printable
beardthelion Jul 29, 2026
9292565
fix(node): spawn the replication tail above guard.release() (#174)
beardthelion Jul 29, 2026
13af73f
test(node): record why the handler path cannot probe the stranded-loc…
beardthelion Jul 29, 2026
a9d8906
docs(node): record the Tigris ordering residual the tail reorder intr…
beardthelion Jul 29, 2026
4f3278f
fix(node): share one deadline across the withheld walk and the pack s…
beardthelion Jul 30, 2026
6b76600
docs(node): correct stale admission-bound comments and document deriv…
beardthelion Jul 30, 2026
701a5e3
docs: normalize README punctuation and record the two admission doc c…
beardthelion Jul 30, 2026
28a6ca4
fix(node): charge blocking-queue delay against the shared upload-pack…
beardthelion Jul 30, 2026
4b69c45
fix(node): skip an /ipfs absence re-probe the budget cannot pay for (…
beardthelion Jul 30, 2026
39bd4d8
Merge branch 'main' into fix/served-git-concurrency-cap
Jul 30, 2026
28cbb50
fix(node): bound the IPFS pin batch and let a slow healthy upload fin…
Jul 30, 2026
17cbdf7
fix(node): charge the /ipfs walk budget for blocking-queue delay (#174)
Jul 30, 2026
2413a71
test(node): bind the repo-key boundary test to the separator it names…
Jul 30, 2026
a56f094
test(node): bind the per-source cap test to the production derivation…
Jul 30, 2026
15aceb4
docs(node): drop a backstop claim with no backstop behind it (#174)
Jul 30, 2026
43d2dd7
docs(node): stop the per-source cap comments claiming a bound the key…
Jul 30, 2026
b9d901f
fix(node): dispose the write-guard connection when the Drop unlock er…
Jul 30, 2026
fa9bf08
fix(node): saturate the derived lease steal bound instead of overflow…
Jul 30, 2026
88f5e93
fix(node): bound git_service_timeout_secs to a range its deadlines ca…
Jul 30, 2026
578ae5b
fix(node): set the git_service_timeout ceiling by representability, n…
Jul 30, 2026
44efb99
fix(node): bound GITLAWB_IPFS_REQUEST_BUDGET_SECS by representability
Jul 31, 2026
87bdc0c
docs(node): state what the pin-admission pool actually bounds
Jul 31, 2026
41325c3
docs(node): state what the permit-less load-shed peek actually delivers
Jul 31, 2026
807bc93
fix(review): correct the budget-knob claims and mirror the range in o…
Jul 31, 2026
b048248
docs(node): correct the ceiling margin, it is 5.85x not an order of m…
Jul 31, 2026
7da7e13
Merge #174 into the CID tree-gate branch
Aug 2, 2026
b4deb21
test(node): restore three guard tests the #174 merge dropped
Aug 2, 2026
3354c08
test(node): restore the four u3 drain tests onto the coalescer
Aug 2, 2026
8c1b800
test(node): add a test-only fault seam for the drain's two re-reads
Aug 2, 2026
5b15817
fix(node): retry the coalesced drain's re-read instead of losing the …
Aug 3, 2026
8f9ed07
test(node): stop the repos.rs structural gates splitting at the wrong…
Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 128 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ DATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawb
# ── Database pool & startup resilience ────────────────────────────────────
# Maximum connections in the PostgreSQL pool. A cap, not a floor —
# connections open lazily. Size against the DB server's max_connections,
# remembering admin tooling opens its own pool.
GITLAWB_DB_MAX_CONNECTIONS=20
# remembering admin tooling opens its own pool. Each concurrent write pins one
# connection for its whole duration (the connection-affine advisory lock), so the
# node REJECTS at boot any value below GITLAWB_MAX_CONCURRENT_GIT_PUSHES + 8
# headroom — keep this comfortably above that (default 48 for pushes 32).
GITLAWB_DB_MAX_CONNECTIONS=48
# Seconds a request waits for a pool connection before failing with 503.
GITLAWB_DB_ACQUIRE_TIMEOUT_SECS=5
# Upper bound on each startup connect+migrate attempt, in seconds. Keep it
Expand Down Expand Up @@ -109,15 +112,136 @@ GITLAWB_MAX_PACK_BYTES=2147483648

# Max seconds a served git upload-pack / receive-pack (clone / push) may run
# before it is aborted with a 504. Bounds a hung git that would otherwise pin a
# worker and, on push, the repo write lock. Does NOT cover the info/refs
# advertisement or the withheld-blob path, which remain unbounded. Default 600.
# worker and, on push, the repo write lock. Also bounds both info/refs
# advertisements, the withheld-blob pack build, and the push-side candidate
# discovery (rev-list / cat-file), all reaped via process-group teardown (#174).
# On the path-scoped upload-pack path the withheld-blob classification walk and
# the pack serve share ONE deadline, so this value bounds their COMBINED duration
# rather than giving 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
# need a higher value here than when each stage was budgeted separately.
# Must be 1..=3153600000 (100 years): the node derives deadlines from this value,
# and a larger one cannot be represented. Default 600.
GITLAWB_GIT_SERVICE_TIMEOUT_SECS=600

# Max seconds the storage-ACQUISITION phase of a served git op may run before the
# request is shed with a 503, separate from the git-run timeout above. A
# concurrency permit is taken before this phase and GITLAWB_GIT_SERVICE_TIMEOUT_SECS
# only starts once git spawns, so without this a stalled backend (a hung Tigris
# HEAD/GET, or a hung pg advisory-lock iteration on push) pins the permit and drains
# the pool until every later request 503s. On expiry the permit is released
# (fail-closed). Kept separate because acquisition and git execution are distinct
# cost centers. Must be positive; set very large to effectively disable. Default 30.
GITLAWB_GIT_ACQUIRE_TIMEOUT_SECS=30

# Max concurrent git READ ops (upload-pack + the upload-pack info/refs
# advertisement) served at once, a global pool separate from the push pool below.
# The anon receive-pack info/refs advertisement has its OWN pool (see below), not
# this one. Over-cap sheds a clean 503 + Retry-After. Anonymous reads draw from
# here, so pair it with GITLAWB_MAX_CONCURRENT_READS_PER_CALLER (below) so one
# caller cannot monopolize the pool. Default 128.
GITLAWB_MAX_CONCURRENT_GIT_OPS=128

# Max concurrent git-receive-pack (push) POST operations, in a pool separate from
# the read pool (GITLAWB_MAX_CONCURRENT_GIT_OPS) so anonymous reads cannot shed an
# authenticated push at admission. The anon receive-pack info/refs advertisement
# runs in a SEPARATE pool of the same size (disjoint from this one), so an
# advertisement flood cannot shed a push either. Over-cap sheds a 503 +
# Retry-After. Default 32.
GITLAWB_MAX_CONCURRENT_GIT_PUSHES=32

# Max pushes PARKED at once waiting for the per-repo write lease of the SAME repo.
# A parked push holds its already-buffered pack body in memory, so this bounds that
# memory for a hot repo. Past the cap the newest push sheds a clean 503 +
# Retry-After ("repo is busy with another push") instead of joining the queue; a
# push to a DIFFERENT repo is never affected. The lease holder is not counted.
# Raising GITLAWB_MAX_CONCURRENT_GIT_PUSHES does not raise this; set it explicitly.
# Default 8.
GITLAWB_REPO_LEASE_MAX_WAITERS=8

# Max concurrent post-push pin loops (IPFS + Pinata pin_new_objects) across all
# repos. Each loop holds a full per-push object-id list while pinning, so this
# bounds that MB-scale memory even though the per-repo encrypt-task set already
# caps the task COUNT. A loop DEFERS (waits) when the pool is full, never drops a
# pin. Default 8.
GITLAWB_MAX_CONCURRENT_PIN_TASKS=8

# Max concurrent read ops (upload-pack + the upload-pack info/refs advertisement)
# a single caller may hold, so one caller cannot monopolize the read pool. Keyed
# on the resolved SOURCE IP, never the DID: a signature does not move a caller off
# this cap. The source-IP key is only as granular as GITLAWB_TRUSTED_PROXY below:
# left unset, a node behind an edge/NAT keys all callers on the edge IP and this
# collapses to one global cap. Set GITLAWB_TRUSTED_PROXY for per-client keying; a
# high-fanout caller (CI behind one NAT) then needs the operator to raise this.
# Default 16.
GITLAWB_MAX_CONCURRENT_READS_PER_CALLER=16

# Two further per-source concurrency caps exist on the PUSH side but have NO
# environment variable of their own: both are DERIVED as
# GITLAWB_MAX_CONCURRENT_GIT_PUSHES / 8, with a floor of 1. They are not settable
# independently, so raising the push pool raises both proportionally, and the
# default push pool of 32 gives each a cap of 4.
# * The anon receive-pack info/refs advertisement cap: one source IP may hold at
# most this many slots in the dedicated advert pool, so saturating that pool
# takes ~8 distinct source IPs.
# * The authenticated receive-pack POST cap: one source IP may hold at most this
# many WRITE-pool slots. This one is load-bearing for push availability, since
# it is acquired before the global write permit: without it, one host minting
# disposable did:key identities could open enough slow pushes to monopolize the
# write pool and 503 every other source (owner enforcement defaults off, and
# the push rate limiter caps arrival rate, not in-flight concurrency).
# Keyed on the resolved source IP, never the DID, so a DID farm does not defeat
# them; keying granularity follows GITLAWB_TRUSTED_PROXY like the read cap above.

# ── Push rate limiting (git-receive-pack flood brake) ─────────────────────
# Max receive-pack requests (info/refs advertisement + push POST) per client
# IP per hour. 0 disables. Default 600.
GITLAWB_PUSH_RATE_LIMIT=600

# ── /ipfs/{cid} visibility-walk admission (#174) ──────────────────────────
# GET /ipfs/{cid} runs a per-repo full-history git walk in a blocking thread to
# decide whether the caller may read a path-scoped blob. It is publicly reachable,
# so it is bounded to keep a permissionless caller from fanning out unbounded
# concurrent walks and exhausting blocking-pool threads + PIDs.
# Max concurrent /ipfs walks across all callers (a pool of its own, disjoint from
# the served-git pools). Over-cap sheds a 503. Default 32.
GITLAWB_MAX_CONCURRENT_IPFS_WALKS=32
# Max concurrent /ipfs walks a single SOURCE IP may hold (keyed like the git
# per-caller caps via GITLAWB_TRUSTED_PROXY; reject-before-insert bounded map).
# Default 4.
GITLAWB_IPFS_WALK_PER_SOURCE=4
# Max legacy (NULL-provenance) repos probed per single /ipfs request, bounding the
# scan-fallback fan-out (git cat-file per candidate repo) for an anonymous caller. A
# truncated scan sheds a retryable 503, never a false 404. Default 256.
GITLAWB_IPFS_MAX_LEGACY_PROBES=256
# Max EXPENSIVE path-scope visibility walks per single /ipfs request (only a
# blob in a path-scoped repo costs a full-history walk). Over-cap repos are
# skipped without a verdict and the scan continues; if the object is then found
# nowhere the request sheds a retryable 503 instead of a false 404. Default 64.
GITLAWB_IPFS_MAX_REPOS_WALKED=64
# Ceiling on repos one /ipfs request may VISIT past the visibility gate. Each
# visit costs a repo acquire — on a Tigris cache miss a full archive download,
# so this is also the worst-case object-store fetch count per request — plus a
# cat-file probe. On exhaustion the scan stops and sheds a retryable 503.
# Default 1024.
GITLAWB_IPFS_MAX_REPO_VISITS=1024
# Absolute wall-clock budget for one admitted /ipfs request's acquire+walk
# lifetime (all stages of the whole scan). Per-stage clamps bound the acquire
# and walk stages to the remaining budget, and no stage starts once it is
# exhausted (the scan then sheds a retryable 503). The object-type probe and
# content-read cat-file subprocesses are budget-checked before starting AND each
# 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.
# Must be 1..=3153600000 (100 years): the node derives an Instant deadline from
# this value, and a larger one cannot be represented. Default 600.
GITLAWB_IPFS_REQUEST_BUDGET_SECS=600
# Max /ipfs/{cid} requests per client IP per hour (route flood brake, distinct
# from the concurrency caps above). 0 disables. Default 600.
GITLAWB_IPFS_RATE_LIMIT=600

# ── Creation rate limiting (repo/agent/issue/PR flood brake) ──────────────
# Max creation requests (POST /api/v1/repos, /api/register, fork, issues,
# pulls) per client IP per hour, in addition to the per-DID limit. The per-DID
Expand Down
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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:
Gitlawb is not trying to be only "another git host." The long-term direction is:

```txt
Decentralized GitHub
Expand Down Expand Up @@ -34,12 +34,12 @@ This is a Rust workspace with four crates:

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 `gl` CLI 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.
- **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 `gl` CLI 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.

---

Expand Down Expand Up @@ -189,14 +189,14 @@ For public-network use, make sure `GITLAWB_NODE` points to the node you want. Th

Public nodes (e.g. `node.gitlawb.com`) require two things on writes:

1. **RFC 9421 HTTP Signatures** every write is signed by your identity key. `gl`
1. **RFC 9421 HTTP Signatures**: every write is signed by your identity key. `gl`
and the `git-remote-gitlawb` helper do this automatically. An old/unsigned CLI
fails with `401 not_an_agent`; `gl` will tell you to upgrade and register.
2. **An iCaptcha proof** on the spam-gated writes (**repo create, fork, register**).
`gl` solves this for you: on the node's `403 icaptcha_proof_required` it reads the
`x-icaptcha-url` / `x-icaptcha-level` hints, requests a challenge, solves it
locally (arithmetic / algebra / sequence), and **retries the same signed request**
with the `x-icaptcha-proof` header — no manual steps, no env vars.
with the `x-icaptcha-proof` header. No manual steps, no env vars.

```bash
gl identity new # create did:key identity
Expand All @@ -215,14 +215,14 @@ Notes:
- **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` is **signed-only** (owner signature is the gate no per-push challenge);
`git push` is **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
clear `404`, never a placeholder.
- **API-key iCaptcha deployments:** set `GITLAWB_ICAPTCHA_URL` to your iCaptcha
origin and `GITLAWB_ICAPTCHA_API_KEY` to its key. The client only talks to an
`https` origin 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.
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.

---

Expand Down Expand Up @@ -343,13 +343,24 @@ Important node settings:
| `GITLAWB_REQUIRE_SIGNED_PEER_WRITES` | Require signed peer announce/sync writes. |
| `GITLAWB_AUTO_SYNC` | Enable automatic sync from known peers. |
| `GITLAWB_MAX_PACK_BYTES` | Max git pack body size for smart-HTTP routes. |
| `GITLAWB_GIT_SERVICE_TIMEOUT_SECS` | Max seconds a served git upload-pack/receive-pack may run before it is aborted (504). Default 600. Does not bound `info/refs` or the withheld-blob path. |
| `GITLAWB_GIT_SERVICE_TIMEOUT_SECS` | Max 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. |
| `GITLAWB_GIT_ACQUIRE_TIMEOUT_SECS` | Max 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. |
| `GITLAWB_REPO_LEASE_MAX_WAITERS` | Max 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. |
| `GITLAWB_MAX_CONCURRENT_IPFS_WALKS` | Max concurrent `GET /ipfs/{cid}` visibility walks across all callers (own pool, disjoint from the served-git pools); over-cap sheds 503. Default 32. |
| `GITLAWB_IPFS_WALK_PER_SOURCE` | Max concurrent `/ipfs` walks a single source IP may hold. Default 4. |
| `GITLAWB_IPFS_MAX_LEGACY_PROBES` | Max legacy (NULL-provenance) repos probed per `/ipfs/{cid}` request, bounding the scan-fallback fan-out. A truncated scan returns a retryable 503, not a false 404. Default 256. |
| `GITLAWB_IPFS_MAX_REPOS_WALKED` | Max 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. Raised to `MAX_PIN_SOURCES + 1` if set below it, so a provenanced request is never truncated before its full source set is tried. Default 64. |
| `GITLAWB_IPFS_MAX_REPO_VISITS` | Ceiling 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. |
| `GITLAWB_IPFS_REQUEST_BUDGET_SECS` | Absolute 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. |
| `GITLAWB_IPFS_RATE_LIMIT` | Max `/ipfs/{cid}` requests per client IP per hour (route flood brake). 0 disables. Default 600. |
| `GITLAWB_TIGRIS_BUCKET` | Optional S3/Tigris shared repo storage bucket. |
| `GITLAWB_PINATA_JWT` | Optional Pinata/IPFS warm-storage pinning. |
| `GITLAWB_IRYS_URL` | Optional Irys/Arweave permanent anchoring. |

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.

---

## Optional node staking
Expand Down
Loading
Loading