clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

document files, private repositories and Git collaboration

Dami committed ee42dfd
commit
ee42dfd3d47999d1d66cc9fa1776b1cb5458ceea
parent
09a911f984e7b58f67f2fc07570259acb3f40d27
author
Dami <dami.etoile@gmail.com>
7 files changed, +125 -3
MREADME.md4++
Adocs/files-and-private-repositories.md77++++++++++++++++++++++++++++++
Mdocs/fixi.md2+-
Adocs/git-collaboration.md31++++++++++++
Mdocs/testing.md10++++-
Mdocs/webmcp.md2+-
Mscripts/conformance/README.md2+
M README.md +4 -0
A docs/files-and-private-repositories.md +77 -0
@@ -0,0 +1,77 @@
1+# Files and private repositories2+3+Use **Files** to search your uploads, import a public HTTPS URL or Blossom URI, inspect a file, and download it. Owners and moderators can browse the tenant's full inventory. Other users see the files they have uploaded or claimed.4+5+## Encrypted files6+7+Choose **Encrypt and upload** to encrypt a file in the browser before sending it to the relay. The default uses a fresh AES-256-GCM key and nonce for each upload. The resulting share link keeps the key, nonce, original name and file type in its URL fragment. Keep that link: the relay cannot recover the key. Anyone who has the complete link and permission to download the blob can decrypt it.8+9+The optional **Deduplicated key** mode follows the [BUD-15 proposal](https://github.com/hzrd149/blossom/pull/104). It derives the encryption key from the file's content, allowing identical files to share storage. This also reveals when files are identical and permits guesses about predictable content. Use the default random key for sensitive or predictable files. Both modes verify the ciphertext hash before decrypting; the deduplicated mode also verifies the plaintext hash.10+11+Encrypted files remain subject to the tenant's download policy. Sending an encrypted link does not grant membership in a private tenant. Removing a file or revoking membership cannot recall copies someone has already downloaded.12+13+Random-key files can be shared through [NIP-17 file messages](https://github.com/nostr-protocol/nips/blob/master/17.md). Connect a signer with NIP-44 support and enter the recipient's public key. Both parties need signed kind 10050 inbox relay lists available to this relay. The browser sends encrypted gift wraps to those inbox relays, including a copy for the sender. It refuses delivery when the required lists are missing.14+15+## Folders and large files16+17+Under **Files > Folders and large files**, choose a folder or a file to split into chunks. Files, chunks and all parent manifests use deduplicated keys. Names, child keys and file metadata appear only inside encrypted manifests. Keep the complete share link to browse the folder or download individual files.18+19+Folders use [draft BUD-16](https://github.com/hzrd149/blossom/pull/105). Large files use [draft BUD-17](https://github.com/hzrd149/blossom/pull/106), with 2 MiB plaintext chunks and up to 174 links per manifest. Larger directories use nested manifests that appear as one directory in the browser. Every retrieved object is checked against its hash before decryption, and file downloads verify their declared sizes.20+21+The browser workspace accepts up to 256 MiB per file, 1 GiB per folder, 10,000 files and 32 path levels. Browser folder selection includes files and their paths; empty folders are omitted. These browser limits apply in addition to the tenant's storage allowances.22+23+Cancel stops the current upload. Retry reuses the selected files while the page stays open. Completed chunks may remain in the Files inventory after an interrupted folder upload. Removing a root manifest does not remove its child blobs, which may be shared by other folders.24+25+## Resumable uploads26+27+Encrypted file uploads use [draft BUD-14](https://github.com/hzrd149/blossom/pull/102) when the server advertises it. Cancel and retry keep the encrypted bytes and key in memory while the page stays open. Closing or reloading the page loses that local state. The uploader falls back to BUD-13 when multipart support is unavailable.28+29+Other clients can send binary chunks to `PATCH /<sha256>` with `Upload-Type`, `Upload-Length`, `Upload-Offset` and `Content-Length`. Chunks may overlap or arrive out of order. A partial upload stays unavailable for download until its complete hash is verified. Blossom proofs name the final hash; NIP-98 proofs bind each chunk's request body.30+31+Partial uploads reserve the complete file size against the uploader's allowance. Reservations expire after 60 seconds of inactivity. Incomplete uploads can survive a server restart within that window. The draft provides no server endpoint for discovering received offsets, so clients must track their own progress.32+33+Temporary storage accepts chunks up to 8 MiB. A tenant can hold 1 GiB across 64 partial uploads, with up to 16 partial uploads per uploader and 32 chunk requests in flight. These bounds apply even when durable storage allowances are unlimited.34+35+## Upload limits36+37+Set byte allowances with an owner-authorized `setpolicy` patch:38+39+```json40+{41+ "fileLimits": {42+ "maxFileBytes": 104857600,43+ "userStorageBytes": 107374182444+ }45+}46+```47+48+This example limits each stored blob to 100 MiB and allows 1 GiB of claimed storage per uploader. Encrypted chunks and manifests each count toward storage. The per-blob limit applies to each chunk, rather than the reassembled size of a chunked file. Zero means unlimited. Changes apply to subsequent uploads; lowering an allowance does not delete existing files.49+50+Each uploader pays the full size of every file they claim, even when another uploader already stores identical bytes. Repeated uploads by the same user count once. Removing an uploader's claim releases their allowance; shared content remains while another claim exists. An owner can remove the stored file for everyone.51+52+Clients can check an upload with [BUD-06](https://github.com/hzrd149/blossom/blob/master/buds/06.md) `HEAD /upload`. The result is advisory; the upload itself checks the current limits. File descriptors include the optional Nostr metadata field defined by [BUD-08](https://github.com/hzrd149/blossom/blob/master/buds/08.md). A [BUD-13](https://github.com/hzrd149/blossom/pull/100) remote upload may name up to eight `url` sources.53+54+## Private Git hosting55+56+Private repositories require a dedicated [GRASP-08 private tenant](https://ngit.dev/protocol/grasp/specification/08). Enable `features.grasp` and `features.grasp08`, and set `reads` to `members` before publishing a private repository announcement. Members share access to the tenant's repository data. This is access control: the server still holds readable Git objects.57+58+Git clients authenticate with a signed kind 27235 event whose URL is the repository root ending in `.git` and whose method is `GET`. The proof can be reused across that repository's Git HTTP requests while its timestamp is within a minute of the server's clock. Relay events require NIP-42 authentication and current membership.59+60+Open tenants reject private repository announcements. A tenant that already holds a private repository announcement stays private, including after that announcement is removed. Policy changes and configuration imports cannot reopen a private tenant. Moving data to a public tenant requires a deliberate migration.61+62+Under **Manage > Connect**, edit your encrypted kind 10318 private service list. URLs and other private entries are encrypted to your own signer with NIP-44. The editor saves the event on the current relay; clients must be able to find it there.63+64+## Blossom drafts65+66+These formats may change before adoption.67+68+| Proposal | Support | What it adds |69+| --- | --- | --- |70+| [BUD-13](https://github.com/hzrd149/blossom/pull/100) | Server | Uploads to `/<sha256>` and imports from remote `url` sources |71+| [BUD-14](https://github.com/hzrd149/blossom/pull/102) | Server and browser | Resumable uploads with multipart `PATCH` |72+| [BUD-15](https://github.com/hzrd149/blossom/pull/104) | Browser | Optional deduplicated encryption |73+| [BUD-16](https://github.com/hzrd149/blossom/pull/105) | Browser | Encrypted folder manifests and browsing |74+| [BUD-17](https://github.com/hzrd149/blossom/pull/106) | Browser | Chunked files and nested directory manifests |75+| [BUD-18](https://github.com/hzrd149/blossom/pull/107) | Not yet | Addressing files within immutable or named trees |76+77+[Hashtree](https://github.com/mmalmi/hashtree) provides implementation references and test vectors for the encryption, manifest and tree formats.
M docs/fixi.md +1 -1
@@ -10,7 +10,7 @@ b70458c212d0409f8a6319c286351865d92587292ad4f9bc0dfd1c46ffcad855
1010 1111 `/fixi.js` is served locally with no CDN dependency. The page loads it before the existing signer bundle. The Sync page uses Fixi's `fx-action` request for the job status stream, which renders server-escaped table HTML; `/connect.json` remains a JSON API for the configured connections. Mutation forms remain handled by the existing signer bridge because every request body must be serialized once, hashed exactly, signed as NIP-98, and submitted once. Fixi is not allowed to issue those signed mutations or retry them.1212 13-The signer bridge handles NIP-07, NIP-46, Nostr Connect, Amber links, session login and management actions. Fixi updates page links and GET forms within the current tenant without a full reload. Filters, repository refs, browser history and page titles remain available. Downloads, external links and modified clicks retain normal browser behavior. A failed request displays an error in the footer and leaves the current page usable. Navigation and content also work without JavaScript, including on phones.13+The signer bridge handles NIP-07, NIP-46, Nostr Connect, Amber links, session login and management actions. Encrypted uploads, NIP-34 publishing and NIP-17 file messages sign through the same bridge and never retry a signed request on their own. Fixi updates page links and GET forms within the current tenant without a full reload. Filters, repository refs, browser history and page titles remain available. Downloads, external links and modified clicks retain normal browser behavior. A failed request displays an error in the footer and leaves the current page usable. Navigation and content also work without JavaScript, including on phones.1414 1515 Signed actions require JavaScript and submit once while their controls are disabled. Connect previews update in place. Job status uses an authenticated stream that sends updates when the results change.1616
A docs/git-collaboration.md +31 -0
@@ -0,0 +1,31 @@
1+# Git collaboration2+3+Open a repository and choose **Issues** or **Pull requests**. Search titles and descriptions, filter by status or label, and follow a conversation from its detail page. Reading works without JavaScript. Publishing requires JavaScript and a connected NIP-07 or NIP-46 signer.4+5+## Issues and pull requests6+7+Create an issue with a title, description and optional comma-separated labels. A pull request also needs the full Git commit ID and an HTTP or HTTPS clone URL. The optional merge base identifies the common ancestor with the target branch.8+9+Replies can address the original post or an individual comment. The author, repository owner and current maintainers can change status. Issues support open, resolved, closed and draft states; pull requests use merged in place of resolved. Marking a pull request merged records its status; merge and push the Git changes with your Git client.10+11+Pull request diffs use commits available in the hosted repository. Missing commits produce an availability message. Diff previews are limited to 512 KiB, with further display limits for long output. Clone the repository to review the complete change.12+13+Events follow [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md), with replies using [NIP-22](https://github.com/nostr-protocol/nips/blob/master/22.md). Markdown is supported in issues and pull request descriptions; comments are plain text. Status reflects the newest visible event signed by an authorized author or maintainer.14+15+The browser tools page exposes the same lists and detail pages to browser agents. See [Browser tools](webmcp.md).16+17+## Synchronization18+19+Enable `features.grasp` and `features.grasp02` to synchronize repository announcements, state and conversations from the repository's announced relays. History synchronization prefers NIP-77 reconciliation and falls back to paginated relay queries. Live subscriptions follow new events as they arrive. Missing Git objects for signed branch state and pull request commits are fetched from the announced sources. Pull request commits stay available in the hosted repository under `refs/nostr/<event-id>`.20+21+Enable `features.grasp03` as well to discover participant outboxes from their NIP-65 relay lists and kind 10317 GRASP lists. Profile and relay-list events are cached locally. New discussion participants are included in subsequent discovery passes.22+23+Synchronization runs on a schedule. A repository that synchronizes successfully is checked again after 55 minutes. One that fails is retried after five minutes. A pass that runs out of time keeps its progress and continues from the next relay or participant on the following pass. Live subscriptions and outbox discovery are limited so a busy repository does not open unbounded connections.24+25+History responses are limited to 10,000 events per filter. A history that reaches that limit is reported as incomplete instead of silently claiming success, and larger histories are not yet guaranteed to converge.26+27+## Private repositories28+29+Private synchronization requires a GRASP-08 tenant and operator-configured private peers. Relay discovery cannot authorize a new private destination. Both event queries and Git object repair stay within the configured peer list, and private relay connections authenticate with NIP-42. Members still need permission to read the local tenant.30+31+See [Files and private repositories](files-and-private-repositories.md) for private hosting setup.
M docs/testing.md +9 -1
@@ -5,6 +5,8 @@ The normal development loop is local:
55 ```sh66 make test-internal-race77 make test8+npm run test:files9+npm run test:collaboration810 ```911 1012 The repository also contains a multistage Docker build. `make docker-test` runs `TEST_PACKAGES` (default `./...`) under Go 1.27.1 on Debian Bookworm with the race detector. `make docker-build` builds the self-hosted runtime image.
@@ -78,8 +80,14 @@ node scripts/git-performance.mjs --relay ws://127.0.0.1:17447 \
7880 7981 Use `--only strudel,atlas` to select fixtures. Each run creates distinct signed repository identities, checks all transferred refs and HEAD, runs `git fsck`, and measures relay EVENT/REQ traffic throughout the Git phases. The incremental push adds a commit with an unchanged tree; it is a small metadata update, not a new binary upload.8082 81-Add `--private` to test private repository announcements and NIP-98 Git authorization. The harness authenticates its control WebSocket with the repository signer and starts a loopback signing proxy for native Git. That proxy hashes each request through a temporary file and signs the exact URL, method and body; its overhead is included in private timings. Private server authorization also spools before invoking Git, so malformed payloads cannot mutate the repository and upload size does not determine Go heap size. Do not interpret private timings as a like-for-like unauthenticated native Git comparison.83+Add `--private` to both commands to test a GRASP-08 private tenant. The private fixture uses the documented benchmark owner key, so it is already a tenant member. The harness authenticates its control WebSocket with the repository signer and starts a loopback signing proxy for native Git. The proxy adds a repository-root proof with the literal `GET` method to each Git request, and its temporary file handling is included in private timings. Signed repository state authorizes each pushed ref. Use a fresh data directory for each comparison. Do not interpret private timings as a like-for-like unauthenticated native Git comparison.8284 8385 For native Git comparisons, `go run ./scripts/git-http-baseline --root /path/to/fresh-bare-repos --listen 127.0.0.1:17449` exposes a benchmark-only HTTP server. Initialize one empty bare `<name>.git` per fixture, set its symbolic HEAD to the corpus manifest's `head_branch`, and enable `http.receivepack`. Add `--baseline http://127.0.0.1:17449` to the workload. Use fresh baseline repositories for each run so initial pushes transfer the complete corpus.8486 8587 The raw JSON records failures as failures; interrupted or absent phases are not successful samples. The observer separates Go process memory from container memory, which also includes native Git and file cache. Run performance measurements separately from compilation and test suites on the same host.88+89+## Git collaboration checks90+91+Run `npm run test:collaboration` to check signed issue, reply, status and pull request events. Run `go test ./internal/daemon ./internal/gitrelay ./internal/replication ./internal/webui` for collaboration queries, access controls, synchronization, Git repair and page rendering.92+93+The UX fixture includes an issue, a threaded comment, a status change and a pull request with a real Git diff. Use a disposable daemon when testing signed browser publishing.
M docs/webmcp.md +1 -1
M scripts/conformance/README.md +2 -0