clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

main
bindws / docs / 22-grasp-01-git-hosting.md
22-grasp-01-git-hosting.md 16.8K source

title: GRASP-01 Git hosting audience: user

GRASP-01 Git hosting

GRASP-01 gives a relay an optional Git Smart HTTP door. A repository owner publishes the NIP-34 repository announcement and its current state on the relay; the relay serves the Git objects that belong to that accepted state. The Nostr relay remains the source of authorization and discussion events.

This document tracks the GRASP-01 specification at commit f35b4f9a4ed2f0aaaf46926e4b1733c79e21b377, checked on 2026-09-04. The implementation is bounded to the core service. It The later extensions are independently opt-in: grasp02, grasp03, grasp05 and grasp06. GRASP-02 and GRASP-06 require grasp; GRASP-03 and GRASP-05 require GRASP-02. These switches default off, so enabling grasp alone preserves GRASP-01 behavior. This document describes the host implementation and does not claim an upstream audit or complete conformance.

Turn it on

The grasp feature is off unless the owner enables it. The Git repositories preset turns on open reads and the GRASP Git door. The later flags are separately opt-in and the dependency rules above apply. The repository still applies its ordinary fuel, storage and event rules, and the information document lists the exact acceptance criteria. The relay advertises GRASP-01 and, when validated and enabled, GRASP-06 in supported_grasps. GRASP-02/03/05 remain bounded opt-in previews pending interoperability and conformance review.

The relay templates include a GRASP template for a relay whose public purpose is hosting NIP-34 repositories. A template does not add members, bans or addresses. The owner supplies those separately when the relay's event policy requires them.

Announce a repository

The repository owner publishes a kind 30617 event with a d identifier, clone URLs and relays URLs. An announcement that hosts a repository here must list this service in both lists. A companion announcement from an already recognized maintainer may omit those service tags because the repository is already hosted here. The service URL is the relay's HTTPS Git endpoint and its WebSocket relay URL. The identifier is percent-encoded in the Git path:

/<npub>/<percent-encoded-identifier>.git

The relay accepts the announcement only after its normal signature, ban, fuel, size and event policy checks. A malformed identifier, URL or maintainer key is refused. A repository's private tag does not make a private Git door; private repository hosting is GRASP-08 and remains outside this feature.

The accepted announcement's address is 30617:<owner-pubkey>:<identifier>. NIP-34 issues, patches, pull requests and updates that refer to an accepted address can be stored by the normal relay rules. GRASP-06 pull requests may arrive before the announcement: they must still name a syntactically valid repository coordinate and remain in purgatory until their matching Git data arrives.

Publish a state

The repository maintainer publishes a kind 30618 event with the same d identifier, a HEAD tag and the complete refs/heads/* and refs/tags/* map. The NIP-34 HEAD tag uses ["HEAD", "ref: refs/heads/main"]; a bare branch path is refused. The latest valid state is selected by NIP-34 addressable-event ordering among the owner and the recursive maintainer set. The recursive set starts at the owner and follows the maintainer announcements for that repository, with bounded depth and membership limits.

The Git door accepts a normal branch or tag update only when each changed ref and the requested HEAD agree with the accepted state. Unchanged refs remain untouched, so a push does not need to repeat the entire state map. The state event therefore authorizes the bytes a push makes visible; a maintainer signature alone does not authorize an arbitrary branch. A state with no HEAD represents an unborn repository. A pending state keeps the previously materialized branch and HEAD in the Git repository, so a stock clone still checks out the prior branch. The repository moves to the new HEAD when the new tip's objects arrive.

Pull requests and purgatory

The alternative PR endpoint is /prs/<signer-npub>/<percent-encoded-identifier>.git. It is a public, empty repository namespace with at most 16 hosted namespaces and 1,024 refs per namespace. Only refs/nostr/<event-id> refs are accepted; ordinary branches, tags and client deletions are refused. A pre-announcement PR must carry the exact signer PR clone URL and a valid repository coordinate with the same identifier. The signed PR or update and its c tip authorize the ref. Unknown events may upload first, but remain hidden until the event is accepted; their deadline is fixed at 20 minutes. Expiry removes refs, while immutable objects and orphaned chunks remain retained and charged because the service has no garbage collector.

Promotion checks for relevant pending events before loading Git packs. An empty queue or another repository's pending work does not trigger that read. A retried Git write still completes any pending promotion left by an earlier successful publication; expiry and authority are checked again after reads.

A pull request or update can name a Git ref as refs/nostr/<event-id>. The event ID is checked as a 64-character lowercase hex value. The relay can hold the event and the Git data while it waits for the other half of the pair. Until both are present, the event is not served from the normal read path and the response explains purgatory: won't be served until git data arrives. Unresolved entries expire after 30 minutes.

The relay accepts every syntactically valid refs/nostr/<event-id> push, including one whose Nostr event has not arrived yet. It rejects a ref whose known event names a different tip. A pending PR ref that has no accepted PR or PR update with a matching c tag is hidden and eligible for cleanup after 20 minutes. The cleanup window limits abandoned uploads and does not implement the broader archive or sync behavior of later GRASP documents.

Git HTTP

Your relay's Connect section includes a copyable git clone example with its own hostname, the repository owner's npub and repository name. Open in app opens this relay's repositories in GitWorkshop, alongside a copyable relay URL. Both Git connections stay hidden when Git hosting is unavailable.

The Git door is unauthenticated when the relay's reads are open. It serves Git Smart HTTP at the repository path and returns a repository page or a 404 for a path that is not hosted. Upload-pack advertises and serves allow-reachable-sha1-in-want, allow-tip-sha1-in-want and filter, including blob:none and tree:0 requests. Receive-pack applies the accepted state, maintainer and refs/nostr rules before storing anything.

The relay root, Git repository page and protocol responses include the GRASP CORS headers. OPTIONS returns 204. GET and POST are the allowed methods; Content-Type, Authorization, Git-Protocol and X-Git-Request-Id are allowed request headers. The NIP-5A site origins remain isolated from relay events and repository data.

The relay Durable Object's SQLite database stores compressed Git object chunks, refs and receipts. The backend removes the former R2 per-push root and transaction ceiling while preserving accepted-state authority, retry receipts and purgatory behavior. It keeps bounded object, byte, ref and receipt limits, and does not promise lower cost for every repository. SQLite storage is charged through the existing events allowance. The Worker depends on the ntig package, whose source and package artifact are recorded in Vendored dependencies. Its repository contract validates pack structure, deltas, object dependencies, trees and commits before publication. The owner can tune the Git capacity in the console's Rules → Git capacity panel, or in an exported configuration under policy.git. Values are bytes unless noted otherwise; omitted fields keep the current values. The editor validates against the same maximums used by the runtime. For example, a larger personal relay can set a 2 GiB retained Git allowance and 512 repositories:

{
  "format": "bind.ws/relay-config/2",
  "policy": {
    "git": {
      "maxRepositories": 512,
      "maxRelayBytes": 2147483648,
      "maxCompressedBytes": 1073741824,
      "maxPackBytes": 99614720,
      "maxObjectBytes": 67108864,
      "maxTransactionRawBytes": 67108864
    }
  }
}

The defaults are:

Scope Limit What it controls
Relay 128 repositories, 2 GiB Git storage accepted repositories and retained compressed Git objects plus metadata across the relay
Repository 16 MiB per pack or object one upload and one stored object
Repository 4 GiB raw, 1 GiB compressed and 128 MiB metadata retained object payloads and their SQL records
Repository 4,096 refs, 100,000 objects and 1,000,000 graph edges ref maps and verified object graphs
Repository receipt metadata within the metadata limit retry records without a separate transaction-count ceiling

The configurable maximums are 4,096 repositories, 6 GiB relay storage, 95 MiB packs, 64 MiB objects, 64 GiB raw data, 6 GiB compressed data and 1 GiB metadata per repository. Request limits are separately capped at 64 MiB and 100,000 objects per transaction, with 6 GiB fetch responses. These ceilings protect the Worker and SQLite database; raising a relay's settings does not remove storage, CPU, bandwidth or fuel costs. The per-relay physical SQLite ceiling and backup/archive limits remain separate operational constraints.

The SQL object store reads and writes chunks, refs and receipts in native SQL transactions. Clone and fetch stream selected objects from the index. Pushes remain bounded by the per-request pack and decoded-byte budgets; a large initial history may need staged pushes. Fetch currently sends complete requested reachable history, so incremental fetches may transfer objects the client already has. Compaction, orphan collection and production capacity measurements remain follow-up work. Large repositories should use ordinary client-side git clone --mirror or git bundle recovery; the portable relay backup is intentionally bounded and may omit Git payloads. Expired unknown PR refs stay hidden when cleanup cannot commit, while immutable object history keeps its bytes until the relay is torn down.

Git HTTP walks indexed object metadata to check reachability and filters before loading selected object bodies. Every write rechecks authority inside the SQL transaction, which publishes objects, refs, receipts and pending-event visibility together. The relay admits one repository operation at a time.

The owner can request gitstorage <repository-owner-hex> <identifier> through the management API to inspect the repository's object, ref and receipt totals. The report includes compressed payload, raw object and metadata bytes plus the relay's complete SQLite size. The diagnostic does not change stored objects, refs or receipts.

Reports read indexed SQL totals with a 60-second cooldown per live relay instance. A restart resets that cooldown. Git requests, event writes and configuration changes receive a retry response while a scan owns the relay. See HTTP reference for the method and Costs and margins for its host costs and limits.

Portable relay backups include compressed Git objects, refs and retry receipts within the archive's 8 MiB and 12,000-entry limits. The owner can request a configuration/events/media backup with Git omitted when repositories are too large for this bounded format. Restore verifies repository identity, object hashes, dependency graphs and the receipt chain before writing the fresh target. See Data and names.

Proactive synchronization and archive

With grasp02, the relay performs repository-scoped historic and bounded live event synchronization from accepted announcement relay lists. Missing Git data is queued from signed state and accepted PR/update clone sources with an hourly target subject to queued work, fuel and size limits. At most 16 Git sources are considered; each connected round is bounded to 10 seconds. Failures retry after five minutes before returning to the hourly schedule. Packs use policy.git.maxPackBytes (16 MiB by default), with 1 MiB of additional wire response allowance. The same configured object and transaction bounds apply to remote verification. Remote advertisements never authorize refs; signed state and accepted events are rechecked under the repository fence. PR objects already uploaded to this relay through GRASP-06 are copied locally into the target repository, without a self-fetch. Multiple clone URLs in a single tag are considered.

History uses durable cursors with inclusive timestamp overlap and a 256-event page cap. A source page cap is surfaced as partial history rather than silently reported complete. Up to 512 durable sync jobs and 128 history windows are retained. Restarts resume unfinished history before following the bounded 1.5 second live tail.

With grasp03, accepted issue, patch and PR threads are expanded through the authors' kind 10002 write relays. Replies may use #e or #E and do not need a new repository coordinate; the accepted parent thread remains the scope boundary. Only write relays from kind 10002 outbox lists are used. Metadata refresh is limited to kinds 0, 10002 and 10317, with the same bounded source and job limits.

With grasp05, the relay may admit announcements that omit its own clone and relay URLs. Archive admission still consumes the normal repository quota and storage accounting, and depends on GRASP-02. Immutable packs remain retained and charged; expiry does not provide garbage collection.

The owner can inspect synchronization through storagestats. Its graspSync result records whether synchronization is enabled, whether source or scope caps made the result partial, event and Git job counts, durable error counts, unfinished history windows, and the earliest due Unix timestamp (or null). Error details remain in durable SQL rows. Backups preserve optional state.prRefs rows, including until: 0 tombstones for accepted or deleted events; backups made before this field was added remain readable.

Information document and limits

When GRASP is on and reads are open, NIP-11 includes:

Field Meaning
supported_grasps Lists only locally validated and enabled profiles; dependencies still apply.
repo_acceptance_criteria who may announce repositories under the current write and kind rules, guest exceptions, required relay listing and storage limits
curation omitted unless the owner applies curation beyond generic spam, bans or fuel rules

GitWorkshop classifies the acceptance description as public or approval required. Open writes allow anyone to announce a repository; allowlist, owner-only and web-of-trust writes describe their eligible authors. A guest exception for kind 30617 opens announcements, while state and collaboration events still follow their own rules. A blocked announcement kind preserves the owner's existing kind-rule exemption. Changing a rule changes the description without granting any new permission.

NIP-11 never claims GRASP-08. A relay's regular read rule still governs the Nostr side, and turning GRASP off removes its Git door and advertisement without changing the NIP-5A site origins.

Limits under load

One Git operation runs at a time for a relay. The operation fence also blocks relay-side event and control mutations while a Git transaction reads or writes its object graph. This bounds Worker memory and keeps the SQL object snapshot and the relay's authority view from changing underneath one operation.

Git requests consume the relay's normal address rate limits. A busy fence or rate limit answers 429 with a retry reason. Fuel exhaustion, an unclaimed relay, a restricted read rule or a repository limit answers a restricted: response. A caller retries after the stated condition clears; it does not assume that a rejected receive-pack changed refs.

What this does not promise

The preview extensions do not promise complete history, uninterrupted live connections, unconditional hourly Git work, private repositories, or an upstream conformance result. Work is bounded by queued jobs, fuel, source, window, byte and storage limits. The repository's Git object store is an implementation detail; clients rely on the NIP-34 events and GRASP HTTP contract, not on a particular backend.

See Your relay on the web for the user-facing site and domain doors, Architecture for the Worker and Durable Object boundaries, and HTTP reference for the exact request paths and statuses.