# gitlawb node — environment variables # Copy to .env and fill in your values. # All variables are optional unless marked REQUIRED. # ── Node identity ───────────────────────────────────────────────────────── # Path to the node's Ed25519 keypair PEM file. # Generate with: gl identity new GITLAWB_KEY=/data/keys/identity.pem # Publicly reachable URL of this node (used in peer announcements) GITLAWB_PUBLIC_URL=https://your-node.example.com # ── Server ──────────────────────────────────────────────────────────────── GITLAWB_HOST=0.0.0.0 GITLAWB_PORT=7545 # ── Storage ─────────────────────────────────────────────────────────────── GITLAWB_REPOS_DIR=/data/repos # PostgreSQL connection URL. Required. # When using the bundled docker-compose, this is wired automatically. 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. 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 # generous enough for a peer instance to finish migrating (the migration # advisory lock is held across a deploy); on expiry the attempt is retried. GITLAWB_DB_CONNECT_TIMEOUT_SECS=60 # Startup retry backoff while the database is unavailable: first delay and # ceiling, in seconds. The node stays up serving 503s (with a truthful # /ready) instead of crash-looping. GITLAWB_DB_RETRY_INITIAL_SECS=5 GITLAWB_DB_RETRY_MAX_SECS=60 # ── IPFS pinning (Pinata) ───────────────────────────────────────────────── # Get a JWT at https://app.pinata.cloud/developers/api-keys GITLAWB_PINATA_JWT= GITLAWB_PINATA_UPLOAD_URL=https://uploads.pinata.cloud/v3/files # ── Arweave permanent anchoring (Irys devnet) ───────────────────────────── # Leave empty to disable Arweave anchoring. GITLAWB_IRYS_URL=https://devnet.irys.xyz # ── Base L2 smart contracts ─────────────────────────────────────────────── GITLAWB_CHAIN_RPC_URL=https://sepolia.base.org GITLAWB_CONTRACT_DID_REGISTRY=0x8046284116C5ac6724adbBf860feBeA85692d574 GITLAWB_CONTRACT_NAME_REGISTRY=0x73094B9DAb2421878A20Abed1497001fbD51302c # $GITLAWB ERC20 token (Base mainnet). GITLAWB_TOKEN=0x5F980Dcfc4c0fa3911554cf5ab288ed0eb13DBa3 # ── On-chain PoS (operators — required to earn rewards) ─────────────────── # Set both of the next two to enable Proof-of-Stake mode. With both set, the # node verifies its registration on startup and posts heartbeats automatically. # Leave empty to run without on-chain PoS (local dev / pre-launch). # Deployed GitlawbNodeStaking contract address. GITLAWB_CONTRACT_NODE_STAKING= # Operator wallet private key (0x-prefixed hex). Used to sign heartbeats. # Use a dedicated wallet with only enough ETH for gas — not your treasury. GITLAWB_OPERATOR_PRIVATE_KEY= # If true, the node refuses to start unless registered and currently active. # Leave false while bootstrapping; flip to true once your stake is confirmed. GITLAWB_OPERATOR_STRICT_MODE=false # Heartbeat cadence in hours. Must be < 24 with headroom. Default: 20. GITLAWB_HEARTBEAT_INTERVAL_HOURS=20 # ── docker-compose Postgres password ────────────────────────────────────── # Only used when running via `docker compose up` with the bundled Postgres. POSTGRES_PASSWORD=changeme # ── P2P networking ──────────────────────────────────────────────────────── GITLAWB_P2P_PORT=7546 # Comma-separated HTTP peer URLs to announce to on startup. # The binary also ships with bootstrap-peers.json; set # GITLAWB_BOOTSTRAP_DISABLE_SEEDS=true for isolated dev/test networks. GITLAWB_BOOTSTRAP_PEERS= GITLAWB_BOOTSTRAP_DISABLE_SEEDS=false # Require signatures on peer announce/sync write routes. Keep false during # rolling upgrades so existing live nodes can still communicate. GITLAWB_REQUIRE_SIGNED_PEER_WRITES=false # Require the authenticated pusher to be the repo owner on git-receive-pack. # A valid did:key signature is authentication, not authorization: anyone can # sign as their own DID. When true, pushes from a non-owner DID are rejected. # Keep false until the repo owner is ready for owner-only writes. GITLAWB_ENFORCE_OWNER_PUSH=false # Comma-separated libp2p multiaddrs. # Example: /ip4/1.2.3.4/udp/7546/quic-v1/p2p/12D3KooW... GITLAWB_P2P_BOOTSTRAP= # ── Access control ──────────────────────────────────────────────────────── # Reserved for private-read mode. Public/private repo read enforcement is not # wired in the current live release; do not rely on this for private repositories. GITLAWB_PUBLIC_READ=true # Maximum git smart-HTTP pack request size, in bytes. 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. 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. The per-repo encrypt-task set caps the pin-task COUNT; this caps how many # of those loops RUN CONCURRENTLY, and so how much MB-scale object-id list memory is # held WHILE BEING PINNED. It does not bound retained memory in general: on the local # IPFS path the caller builds its list before this pool is entered, so a loop parked # waiting for a permit still holds its full list, and how many park is capped only # per repo. Do not size memory from this knob alone. 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 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 # limit alone is bypassed by a did:key farm (one throwaway identity per repo), # which is how spam-repo floods slip past it and the iCaptcha gate; the per-IP # brake caps a single-source flood. Uses GITLAWB_TRUSTED_PROXY below to resolve # the client IP. 0 disables. Default 120. GITLAWB_CREATE_RATE_LIMIT=120 # ── Peer-sync rate limiting (per client IP, uses GITLAWB_TRUSTED_PROXY below) ─ # /api/v1/peers/announce and /api/v1/sync/notify accept unsigned requests from # known peers and run at higher frequency, so a generous bucket. Separate from # the trigger bucket so an unsigned notify flood can't drain trigger's quota. # 0 disables. Default 600. GITLAWB_PEER_WRITE_RATE_LIMIT=600 # /api/v1/sync/trigger requires a signature and fans out to every peer per call, # so it gets a tight bucket. 0 disables. Default 60. GITLAWB_SYNC_TRIGGER_RATE_LIMIT=60 # Which forwarded header the edge is trusted to set, used to resolve the real # client IP for the push limiter. One of: # (unset) — no trusted proxy: key on the socket peer address, ignore headers. # fly — behind Fly's edge (trust Fly-Client-IP). # x-forwarded-for — behind a single reverse proxy like Caddy/NGINX (trust the # rightmost X-Forwarded-For hop). # Only set this when a proxy you control actually fronts the node; trusting a # forwarded header on a directly-exposed node lets clients spoof the key. GITLAWB_TRUSTED_PROXY= # ── Sync ───────────────────────────────────────────────────────────────── # Enable automatic background sync from known peers GITLAWB_AUTO_SYNC=false # ── iCaptcha proof-of-intelligence gate ─────────────────────────────────── # Optional gate on create_repo + register: require callers to present an # iCaptcha proof (X-ICaptcha-Proof header) earned at icaptcha.gitlawb.com. # Default off = fully inert (no behavior change). # off - gate disabled (default) # shadow - verify + log would-be rejections, but always allow # enforce - reject requests without a valid, strong-enough proof ICAPTCHA_MODE=off # Optional base64url Ed25519 public key override; if unset it is fetched from # ICAPTCHA_URL/v1/pubkey at startup. Recommended in production: pinning the key # removes the startup dependency on the iCaptcha service being reachable and # avoids silently running inert if that one-time fetch fails. ICAPTCHA_PUBKEY= # Minimum proof difficulty level required to pass. ICAPTCHA_REQUIRED_LEVEL=3 # Base URL of the iCaptcha service. ICAPTCHA_URL=https://icaptcha.gitlawb.com