Browse Source
update API docs Nostr-Signature: 9d1fb9db75e26a5fcdcab54253ef1c6126ea1e01e98728554435e655354f6238 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 0cfde0ac8a7083479c982c7f212a91eee7e8ed33b43b30291677fe3a126638ae4fde03893aa5d492f8f6fc8e066b7c46bd6f07246376a5d9e56becc73e4e48d8main
41 changed files with 2404 additions and 5201 deletions
@ -1,196 +0,0 @@ |
|||||||
NIP-01 |
|
||||||
====== |
|
||||||
|
|
||||||
Basic protocol flow description |
|
||||||
------------------------------- |
|
||||||
|
|
||||||
`draft` `mandatory` `relay` |
|
||||||
|
|
||||||
This NIP defines the basic protocol that should be implemented by everybody. New NIPs may add new optional (or mandatory) fields and messages and features to the structures and flows described here. |
|
||||||
|
|
||||||
## Events and signatures |
|
||||||
|
|
||||||
Each user has a keypair. Signatures, public key, and encodings are done according to the [Schnorr signatures standard for the curve `secp256k1`](https://bips.xyz/340). |
|
||||||
|
|
||||||
The only object type that exists is the `event`, which has the following format on the wire: |
|
||||||
|
|
||||||
```yaml |
|
||||||
{ |
|
||||||
"id": <32-bytes lowercase hex-encoded sha256 of the serialized event data>, |
|
||||||
"pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, |
|
||||||
"created_at": <unix timestamp in seconds>, |
|
||||||
"kind": <integer between 0 and 65535>, |
|
||||||
"tags": [ |
|
||||||
[<arbitrary string>...], |
|
||||||
// ... |
|
||||||
], |
|
||||||
"content": <arbitrary string>, |
|
||||||
"sig": <64-bytes lowercase hex of the signature of the sha256 hash of the serialized event data, which is the same as the "id" field> |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
To obtain the `event.id`, we `sha256` the serialized event. The serialization is done over the UTF-8 JSON-serialized string (which is described below) of the following structure: |
|
||||||
|
|
||||||
``` |
|
||||||
[ |
|
||||||
0, |
|
||||||
<pubkey, as a lowercase hex string>, |
|
||||||
<created_at, as a number>, |
|
||||||
<kind, as a number>, |
|
||||||
<tags, as an array of arrays of non-null strings>, |
|
||||||
<content, as a string> |
|
||||||
] |
|
||||||
``` |
|
||||||
|
|
||||||
To prevent implementation differences from creating a different event ID for the same event, the following rules MUST be followed while serializing: |
|
||||||
- UTF-8 should be used for encoding. |
|
||||||
- Whitespace, line breaks or other unnecessary formatting should not be included in the output JSON. |
|
||||||
- The following characters in the content field must be escaped as shown, and all other characters must be included verbatim: |
|
||||||
- A line break (`0x0A`), use `\n` |
|
||||||
- A double quote (`0x22`), use `\"` |
|
||||||
- A backslash (`0x5C`), use `\\` |
|
||||||
- A carriage return (`0x0D`), use `\r` |
|
||||||
- A tab character (`0x09`), use `\t` |
|
||||||
- A backspace, (`0x08`), use `\b` |
|
||||||
- A form feed, (`0x0C`), use `\f` |
|
||||||
|
|
||||||
### Tags |
|
||||||
|
|
||||||
Each tag is an array of one or more strings, with some conventions around them. Take a look at the example below: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"tags": [ |
|
||||||
["e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com"], |
|
||||||
["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca"], |
|
||||||
["a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com"], |
|
||||||
["alt", "reply"], |
|
||||||
// ... |
|
||||||
], |
|
||||||
// ... |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The first element of the tag array is referred to as the tag _name_ or _key_ and the second as the tag _value_. So we can safely say that the event above has an `e` tag set to `"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"`, an `alt` tag set to `"reply"` and so on. All elements after the second do not have a conventional name. |
|
||||||
|
|
||||||
This NIP defines 3 standard tags that can be used across all event kinds with the same meaning. They are as follows: |
|
||||||
|
|
||||||
- The `e` tag, used to refer to an event: `["e", <32-bytes lowercase hex of the id of another event>, <recommended relay URL, optional>, <32-bytes lowercase hex of the author's pubkey, optional>]` |
|
||||||
- The `p` tag, used to refer to another user: `["p", <32-bytes lowercase hex of a pubkey>, <recommended relay URL, optional>]` |
|
||||||
- The `a` tag, used to refer to an addressable or replaceable event |
|
||||||
- for an addressable event: `["a", "<kind integer>:<32-bytes lowercase hex of a pubkey>:<d tag value>", <recommended relay URL, optional>]` |
|
||||||
- for a normal replaceable event: `["a", "<kind integer>:<32-bytes lowercase hex of a pubkey>:", <recommended relay URL, optional>]` (note: include the trailing colon) |
|
||||||
|
|
||||||
As a convention, all single-letter (only english alphabet letters: a-z, A-Z) key tags are expected to be indexed by relays, such that it is possible, for example, to query or subscribe to events that reference the event `"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"` by using the `{"#e": ["5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"]}` filter. Only the first value in any given tag is indexed. |
|
||||||
|
|
||||||
### Kinds |
|
||||||
|
|
||||||
Kinds specify how clients should interpret the meaning of each event and the other fields of each event (e.g. an `"r"` tag may have a meaning in an event of kind 1 and an entirely different meaning in an event of kind 10002). Each NIP may define the meaning of a set of kinds that weren't defined elsewhere. [NIP-10](10.md), for instance, specifies the `kind:1` text note for social media applications. |
|
||||||
|
|
||||||
This NIP defines one basic kind: |
|
||||||
|
|
||||||
- `0`: **user metadata**: the `content` is set to a stringified JSON object `{name: <nickname or full name>, about: <short bio>, picture: <url of the image>}` describing the user who created the event. [Extra metadata fields](24.md#kind-0) may be set. A relay may delete older events once it gets a new one for the same pubkey. |
|
||||||
|
|
||||||
And also a convention for kind ranges that allow for easier experimentation and flexibility of relay implementation: |
|
||||||
|
|
||||||
- for kind `n` such that `1000 <= n < 10000 || 4 <= n < 45 || n == 1 || n == 2`, events are **regular**, which means they're all expected to be stored by relays. |
|
||||||
- for kind `n` such that `10000 <= n < 20000 || n == 0 || n == 3`, events are **replaceable**, which means that, for each combination of `pubkey` and `kind`, only the latest event MUST be stored by relays, older versions MAY be discarded. |
|
||||||
- for kind `n` such that `20000 <= n < 30000`, events are **ephemeral**, which means they are not expected to be stored by relays. |
|
||||||
- for kind `n` such that `30000 <= n < 40000`, events are **addressable** by their `kind`, `pubkey` and `d` tag value -- which means that, for each combination of `kind`, `pubkey` and the `d` tag value, only the latest event MUST be stored by relays, older versions MAY be discarded. |
|
||||||
|
|
||||||
In case of replaceable events with the same timestamp, the event with the lowest id (first in lexical order) should be retained, and the other discarded. |
|
||||||
|
|
||||||
When answering to `REQ` messages for replaceable events such as `{"kinds":[0],"authors":[<hex-key>]}`, even if the relay has more than one version stored, it SHOULD return just the latest one. |
|
||||||
|
|
||||||
These are just conventions and relay implementations may differ. |
|
||||||
|
|
||||||
## Communication between clients and relays |
|
||||||
|
|
||||||
Relays expose a websocket endpoint to which clients can connect. Clients SHOULD open a single websocket connection to each relay and use it for all their subscriptions. Relays MAY limit number of connections from specific IP/client/etc. |
|
||||||
|
|
||||||
### From client to relay: sending events and creating subscriptions |
|
||||||
|
|
||||||
Clients can send 3 types of messages, which must be JSON arrays, according to the following patterns: |
|
||||||
|
|
||||||
* `["EVENT", <event JSON as defined above>]`, used to publish events. |
|
||||||
* `["REQ", <subscription_id>, <filters1>, <filters2>, ...]`, used to request events and subscribe to new updates. |
|
||||||
* `["CLOSE", <subscription_id>]`, used to stop previous subscriptions. |
|
||||||
|
|
||||||
`<subscription_id>` is an arbitrary, non-empty string of max length 64 chars. It represents a subscription per connection. Relays MUST manage `<subscription_id>`s independently for each WebSocket connection. `<subscription_id>`s are not guaranteed to be globally unique. |
|
||||||
|
|
||||||
`<filtersX>` is a JSON object that determines what events will be sent in that subscription, it can have the following attributes: |
|
||||||
|
|
||||||
```yaml |
|
||||||
{ |
|
||||||
"ids": <a list of event ids>, |
|
||||||
"authors": <a list of lowercase pubkeys, the pubkey of an event must be one of these>, |
|
||||||
"kinds": <a list of a kind numbers>, |
|
||||||
"#<single-letter (a-zA-Z)>": <a list of tag values, for #e — a list of event ids, for #p — a list of pubkeys, etc.>, |
|
||||||
"since": <an integer unix timestamp in seconds. Events must have a created_at >= to this to pass>, |
|
||||||
"until": <an integer unix timestamp in seconds. Events must have a created_at <= to this to pass>, |
|
||||||
"limit": <maximum number of events relays SHOULD return in the initial query> |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Upon receiving a `REQ` message, the relay SHOULD return events that match the filter. Any new events it receives SHOULD be sent to that same websocket until the connection is closed, a `CLOSE` event is received with the same `<subscription_id>`, or a new `REQ` is sent using the same `<subscription_id>` (in which case a new subscription is created, replacing the old one). |
|
||||||
|
|
||||||
Filter attributes containing lists (`ids`, `authors`, `kinds` and tag filters like `#e`) are JSON arrays with one or more values. At least one of the arrays' values must match the relevant field in an event for the condition to be considered a match. For scalar event attributes such as `authors` and `kind`, the attribute from the event must be contained in the filter list. In the case of tag attributes such as `#e`, for which an event may have multiple values, the event and filter condition values must have at least one item in common. |
|
||||||
|
|
||||||
The `ids`, `authors`, `#e` and `#p` filter lists MUST contain exact 64-character lowercase hex values. |
|
||||||
|
|
||||||
The `since` and `until` properties can be used to specify the time range of events returned in the subscription. If a filter includes the `since` property, events with `created_at` greater than or equal to `since` are considered to match the filter. The `until` property is similar except that `created_at` must be less than or equal to `until`. In short, an event matches a filter if `since <= created_at <= until` holds. |
|
||||||
|
|
||||||
All conditions of a filter that are specified must match for an event for it to pass the filter, i.e., multiple conditions are interpreted as `&&` conditions. |
|
||||||
|
|
||||||
A `REQ` message may contain multiple filters. In this case, events that match any of the filters are to be returned, i.e., multiple filters are to be interpreted as `||` conditions. |
|
||||||
|
|
||||||
The `limit` property of a filter is only valid for the initial query and MUST be ignored afterwards. When `limit: n` is present it is assumed that the events returned in the initial query will be the last `n` events ordered by the `created_at`. Newer events should appear first, and in the case of ties the event with the lowest id (first in lexical order) should be first. Relays SHOULD use the `limit` value to guide how many events are returned in the initial response. Returning fewer events is acceptable, but returning (much) more should be avoided to prevent overwhelming clients. |
|
||||||
|
|
||||||
### From relay to client: sending events and notices |
|
||||||
|
|
||||||
Relays can send 5 types of messages, which must also be JSON arrays, according to the following patterns: |
|
||||||
|
|
||||||
* `["EVENT", <subscription_id>, <event JSON as defined above>]`, used to send events requested by clients. |
|
||||||
* `["OK", <event_id>, <true|false>, <message>]`, used to indicate acceptance or denial of an `EVENT` message. |
|
||||||
* `["EOSE", <subscription_id>]`, used to indicate the _end of stored events_ and the beginning of events newly received in real-time. |
|
||||||
* `["CLOSED", <subscription_id>, <message>]`, used to indicate that a subscription was ended on the server side. |
|
||||||
* `["NOTICE", <message>]`, used to send human-readable error messages or other things to clients. |
|
||||||
|
|
||||||
This NIP defines no rules for how `NOTICE` messages should be sent or treated. |
|
||||||
|
|
||||||
- `EVENT` messages MUST be sent only with a subscription ID related to a subscription previously initiated by the client (using the `REQ` message above). |
|
||||||
- `OK` messages MUST be sent in response to `EVENT` messages received from clients, they must have the 3rd parameter set to `true` when an event has been accepted by the relay, `false` otherwise. The 4th parameter MUST always be present, but MAY be an empty string when the 3rd is `true`, otherwise it MUST be a string formed by a machine-readable single-word prefix followed by a `:` and then a human-readable message. Some examples: |
|
||||||
* `["OK", "b1a649ebe8...", true, ""]` |
|
||||||
* `["OK", "b1a649ebe8...", true, "pow: difficulty 25>=24"]` |
|
||||||
* `["OK", "b1a649ebe8...", true, "duplicate: already have this event"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "blocked: you are banned from posting here"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "blocked: please register your pubkey at https://my-expensive-relay.example.com"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "rate-limited: slow down there chief"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "invalid: event creation date is too far off from the current time"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "restricted: not allowed to write."]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "error: could not connect to the database"]` |
|
||||||
* `["OK", "b1a649ebe8...", false, "mute: no one was listening to your ephemeral event and it wasn't handled in any way, it was ignored"]` |
|
||||||
- `CLOSED` messages MUST be sent in response to a `REQ` when the relay refuses to fulfill it. It can also be sent when a relay decides to kill a subscription on its side before a client has disconnected or sent a `CLOSE`. This message uses the same pattern of `OK` messages with the machine-readable prefix and human-readable message. Some examples: |
|
||||||
* `["CLOSED", "sub1", "unsupported: filter contains unknown elements"]` |
|
||||||
* `["CLOSED", "sub1", "error: could not connect to the database"]` |
|
||||||
* `["CLOSED", "sub1", "error: shutting down idle subscription"]` |
|
||||||
- The standardized machine-readable prefixes for `OK` and `CLOSED` are: `duplicate`, `pow`, `blocked`, `rate-limited`, `invalid`, `restricted`, `mute` and `error` for when none of that fits. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic uses NIP-01 as the foundation for all Nostr event handling. All events follow the standard event structure defined in NIP-01. |
|
||||||
|
|
||||||
### Event Structure |
|
||||||
|
|
||||||
All repository-related events (announcements, PRs, issues, patches, etc.) follow the NIP-01 event format: |
|
||||||
- Events are serialized according to NIP-01 rules (UTF-8, no whitespace, proper escaping) |
|
||||||
- Event IDs are computed as SHA256 of the serialized event |
|
||||||
- Signatures use Schnorr signatures on secp256k1 |
|
||||||
- The `nostr-tools` library is used for event serialization, ID computation, and signature verification |
|
||||||
|
|
||||||
### Kind 24 (Public Message) Usage for Relay Write Proof |
|
||||||
|
|
||||||
GitRepublic uses kind 24 (public message) events for relay write proofs when NIP-98 authentication events are not available. This ensures git operations can still be authenticated even if the NIP-98 flow fails. |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/nostr-client.ts`, `src/lib/types/nostr.ts` |
|
||||||
@ -1,173 +0,0 @@ |
|||||||
NIP-02 |
|
||||||
====== |
|
||||||
|
|
||||||
Follow List |
|
||||||
----------- |
|
||||||
|
|
||||||
`final` `optional` |
|
||||||
|
|
||||||
A special event with kind `3`, meaning "follow list" is defined as having a list of `p` tags, one for each of the followed/known profiles one is following. |
|
||||||
|
|
||||||
Each tag entry should contain the key for the profile, a relay URL where events from that key can be found (can be set to an empty string if not needed), and a local name (or "petname") for that profile (can also be set to an empty string or not provided), i.e., `["p", <32-bytes hex key>, <main relay URL>, <petname>]`. |
|
||||||
|
|
||||||
The `.content` is not used. |
|
||||||
|
|
||||||
For example: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 3, |
|
||||||
"tags": [ |
|
||||||
["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"], |
|
||||||
["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"], |
|
||||||
["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"] |
|
||||||
], |
|
||||||
"content": "", |
|
||||||
// other fields... |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Every new following list that gets published overwrites the past ones, so it should contain all entries. Relays and clients SHOULD delete past following lists as soon as they receive a new one. |
|
||||||
|
|
||||||
Whenever new follows are added to an existing list, clients SHOULD append them to the end of the list, so they are stored in chronological order. |
|
||||||
|
|
||||||
## Uses |
|
||||||
|
|
||||||
### Follow list backup |
|
||||||
|
|
||||||
If one believes a relay will store their events for sufficient time, they can use this kind-3 event to backup their following list and recover on a different device. |
|
||||||
|
|
||||||
### Profile discovery and context augmentation |
|
||||||
|
|
||||||
A client may rely on the kind-3 event to display a list of followed people by profiles one is browsing; make lists of suggestions on who to follow based on the follow lists of other people one might be following or browsing; or show the data in other contexts. |
|
||||||
|
|
||||||
### Relay sharing |
|
||||||
|
|
||||||
A client may publish a follow list with good relays for each of their follows so other clients may use these to update their internal relay lists if needed, increasing censorship-resistance. |
|
||||||
|
|
||||||
### Petname scheme |
|
||||||
|
|
||||||
The data from these follow lists can be used by clients to construct local ["petname"](http://www.skyhunter.com/marcs/petnames/IntroPetNames.html) tables derived from other people's follow lists. This alleviates the need for global human-readable names. For example: |
|
||||||
|
|
||||||
A user has an internal follow list that says |
|
||||||
|
|
||||||
```json |
|
||||||
[ |
|
||||||
["p", "21df6d143fb96c2ec9d63726bf9edc71", "", "erin"] |
|
||||||
] |
|
||||||
``` |
|
||||||
|
|
||||||
And receives two follow lists, one from `21df6d143fb96c2ec9d63726bf9edc71` that says |
|
||||||
|
|
||||||
```json |
|
||||||
[ |
|
||||||
["p", "a8bb3d884d5d90b413d9891fe4c4e46d", "", "david"] |
|
||||||
] |
|
||||||
``` |
|
||||||
|
|
||||||
and another from `a8bb3d884d5d90b413d9891fe4c4e46d` that says |
|
||||||
|
|
||||||
```json |
|
||||||
[ |
|
||||||
["p", "f57f54057d2a7af0efecc8b0b66f5708", "", "frank"] |
|
||||||
] |
|
||||||
``` |
|
||||||
|
|
||||||
When the user sees `21df6d143fb96c2ec9d63726bf9edc71` the client can show _erin_ instead; |
|
||||||
When the user sees `a8bb3d884d5d90b413d9891fe4c4e46d` the client can show _david.erin_ instead; |
|
||||||
When the user sees `f57f54057d2a7af0efecc8b0b66f5708` the client can show _frank.david.erin_ instead. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic uses KIND 3 (Contact List) events to enable repository filtering based on social connections. |
|
||||||
|
|
||||||
### Repository Filtering |
|
||||||
|
|
||||||
When a user enables the "Show only my repos and those of my contacts" filter on the landing page, GitRepublic: |
|
||||||
|
|
||||||
1. **Fetches the user's contact list**: Retrieves the latest KIND 3 event published by the logged-in user |
|
||||||
2. **Extracts contact pubkeys**: Parses all `p` tags from the contact list event to build a set of followed pubkeys |
|
||||||
3. **Includes the user's own pubkey**: Automatically adds the logged-in user's pubkey to the filter set |
|
||||||
4. **Filters repositories**: Shows only repositories where: |
|
||||||
- The repository owner (event `pubkey`) is in the contact set, OR |
|
||||||
- Any maintainer (from `maintainers` tags) is in the contact set |
|
||||||
|
|
||||||
### Implementation Details |
|
||||||
|
|
||||||
- **Pubkey normalization**: GitRepublic handles both hex-encoded pubkeys and bech32-encoded npubs in contact lists |
|
||||||
- **Maintainer matching**: The filter checks all maintainers listed in repository announcement `maintainers` tags |
|
||||||
- **Real-time updates**: The contact list is fetched when the user logs in and can be refreshed by reloading the page |
|
||||||
|
|
||||||
### Example Use Case |
|
||||||
|
|
||||||
A user follows several developers on Nostr. By enabling the contact filter: |
|
||||||
|
|
||||||
- They see repositories owned by people they follow |
|
||||||
- They see repositories where their contacts are maintainers |
|
||||||
- They see their own repositories |
|
||||||
- They don't see repositories from people they don't follow |
|
||||||
|
|
||||||
This creates a personalized, social discovery experience for finding relevant code repositories based on trust relationships established through Nostr's follow mechanism. |
|
||||||
|
|
||||||
### Technical Implementation |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Fetch user's kind 3 contact list |
|
||||||
const contactEvents = await nostrClient.fetchEvents([ |
|
||||||
{ |
|
||||||
kinds: [KIND.CONTACT_LIST], // KIND 3 |
|
||||||
authors: [userPubkey], |
|
||||||
limit: 1 |
|
||||||
} |
|
||||||
]); |
|
||||||
|
|
||||||
// Extract pubkeys from 'p' tags |
|
||||||
const contactPubkeys = new Set<string>(); |
|
||||||
contactPubkeys.add(userPubkey); // Include user's own repos |
|
||||||
|
|
||||||
if (contactEvents.length > 0) { |
|
||||||
const contactEvent = contactEvents[0]; |
|
||||||
for (const tag of contactEvent.tags) { |
|
||||||
if (tag[0] === 'p' && tag[1]) { |
|
||||||
let pubkey = tag[1]; |
|
||||||
// Decode npub if needed |
|
||||||
try { |
|
||||||
const decoded = nip19.decode(pubkey); |
|
||||||
if (decoded.type === 'npub') { |
|
||||||
pubkey = decoded.data as string; |
|
||||||
} |
|
||||||
} catch { |
|
||||||
// Assume it's already a hex pubkey |
|
||||||
} |
|
||||||
contactPubkeys.add(pubkey); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Filter repositories |
|
||||||
const filteredRepos = allRepos.filter(event => { |
|
||||||
// Check if owner is in contacts |
|
||||||
if (contactPubkeys.has(event.pubkey)) return true; |
|
||||||
|
|
||||||
// Check if any maintainer is in contacts |
|
||||||
const maintainerTags = event.tags.filter(t => t[0] === 'maintainers'); |
|
||||||
for (const tag of maintainerTags) { |
|
||||||
for (let i = 1; i < tag.length; i++) { |
|
||||||
let maintainerPubkey = tag[i]; |
|
||||||
// Decode npub if needed |
|
||||||
try { |
|
||||||
const decoded = nip19.decode(maintainerPubkey); |
|
||||||
if (decoded.type === 'npub') { |
|
||||||
maintainerPubkey = decoded.data as string; |
|
||||||
} |
|
||||||
} catch { |
|
||||||
// Assume it's already a hex pubkey |
|
||||||
} |
|
||||||
if (contactPubkeys.has(maintainerPubkey)) return true; |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
}); |
|
||||||
``` |
|
||||||
|
|
||||||
This implementation provides a seamless way for users to discover repositories from their social network while maintaining the decentralized, trustless nature of Nostr. |
|
||||||
@ -1,58 +0,0 @@ |
|||||||
NIP-07 |
|
||||||
====== |
|
||||||
|
|
||||||
`window.nostr` capability for web browsers |
|
||||||
------------------------------------------ |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
The `window.nostr` object may be made available by web browsers or extensions and websites or web-apps may make use of it after checking its availability. |
|
||||||
|
|
||||||
That object must define the following methods: |
|
||||||
|
|
||||||
``` |
|
||||||
async window.nostr.getPublicKey(): string // returns a public key as hex |
|
||||||
async window.nostr.signEvent(event: { created_at: number, kind: number, tags: string[][], content: string }): Event // takes an event object, adds `id`, `pubkey` and `sig` and returns it |
|
||||||
``` |
|
||||||
|
|
||||||
Aside from these two basic above, the following functions can also be implemented optionally: |
|
||||||
``` |
|
||||||
async window.nostr.nip04.encrypt(pubkey, plaintext): string // returns ciphertext and iv as specified in nip-04 (deprecated) |
|
||||||
async window.nostr.nip04.decrypt(pubkey, ciphertext): string // takes ciphertext and iv as specified in nip-04 (deprecated) |
|
||||||
async window.nostr.nip44.encrypt(pubkey, plaintext): string // returns ciphertext as specified in nip-44 |
|
||||||
async window.nostr.nip44.decrypt(pubkey, ciphertext): string // takes ciphertext as specified in nip-44 |
|
||||||
``` |
|
||||||
|
|
||||||
### Recommendation to Extension Authors |
|
||||||
To make sure that the `window.nostr` is available to nostr clients on page load, the authors who create Chromium and Firefox extensions should load their scripts by specifying `"run_at": "document_end"` in the extension's manifest. |
|
||||||
|
|
||||||
|
|
||||||
### Implementation |
|
||||||
|
|
||||||
See https://github.com/aljazceru/awesome-nostr#nip-07-browser-extensions. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
NIP-07 is the primary authentication method for GitRepublic. All user interactions that require signing events use the NIP-07 browser extension interface. |
|
||||||
|
|
||||||
### Authentication Flow |
|
||||||
|
|
||||||
1. **Availability Check**: GitRepublic checks for `window.nostr` availability on page load |
|
||||||
2. **Public Key Retrieval**: When users need to authenticate, `getPublicKey()` is called to get their pubkey |
|
||||||
3. **Event Signing**: All repository announcements, PRs, issues, and other events are signed using `signEvent()` |
|
||||||
|
|
||||||
### Key Features |
|
||||||
|
|
||||||
- **Repository Creation**: Users sign repository announcement events (kind 30617) using NIP-07 |
|
||||||
- **Repository Updates**: Settings changes, maintainer additions, and other updates are signed via NIP-07 |
|
||||||
- **Pull Requests**: PR creation and updates are signed by the PR author |
|
||||||
- **Issues**: Issue creation and comments are signed by the author |
|
||||||
- **Commit Signatures**: Git commits can be signed using NIP-07 (client-side only, keys never leave browser) |
|
||||||
|
|
||||||
### Security |
|
||||||
|
|
||||||
- Keys never leave the browser - all signing happens client-side |
|
||||||
- No server-side key storage required |
|
||||||
- Users maintain full control of their private keys |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/nip07-signer.ts` |
|
||||||
@ -1,76 +0,0 @@ |
|||||||
NIP-09 |
|
||||||
====== |
|
||||||
|
|
||||||
Event Deletion Request |
|
||||||
---------------------- |
|
||||||
|
|
||||||
`draft` `optional` `relay` |
|
||||||
|
|
||||||
A special event with kind `5`, meaning "deletion request" is defined as having a list of one or more `e` or `a` tags, each referencing an event the author is requesting to be deleted. Deletion requests SHOULD include a `k` tag for the kind of each event being requested for deletion. |
|
||||||
|
|
||||||
The event's `content` field MAY contain a text note describing the reason for the deletion request. |
|
||||||
|
|
||||||
For example: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 5, |
|
||||||
"pubkey": <32-bytes hex-encoded public key of the event creator>, |
|
||||||
"tags": [ |
|
||||||
["e", "dcd59..464a2"], |
|
||||||
["e", "968c5..ad7a4"], |
|
||||||
["a", "<kind>:<pubkey>:<d-identifier>"], |
|
||||||
["k", "1"], |
|
||||||
["k", "30023"] |
|
||||||
], |
|
||||||
"content": "these posts were published by accident", |
|
||||||
// other fields... |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Relays SHOULD delete or stop publishing any referenced events that have an identical `pubkey` as the deletion request. Clients SHOULD hide or otherwise indicate a deletion request status for referenced events. |
|
||||||
|
|
||||||
Relays SHOULD continue to publish/share the deletion request events indefinitely, as clients may already have the event that's intended to be deleted. Additionally, clients SHOULD broadcast deletion request events to other relays which don't have it. |
|
||||||
|
|
||||||
When an `a` tag is used, relays SHOULD delete all versions of the replaceable event up to the `created_at` timestamp of the deletion request event. |
|
||||||
|
|
||||||
## Client Usage |
|
||||||
|
|
||||||
Clients MAY choose to fully hide any events that are referenced by valid deletion request events. This includes text notes, direct messages, or other yet-to-be defined event kinds. Alternatively, they MAY show the event along with an icon or other indication that the author has "disowned" the event. The `content` field MAY also be used to replace the deleted events' own content, although a user interface should clearly indicate that this is a deletion request reason, not the original content. |
|
||||||
|
|
||||||
A client MUST validate that each event `pubkey` referenced in the `e` tag of the deletion request is identical to the deletion request `pubkey`, before hiding or deleting any event. Relays can not, in general, perform this validation and should not be treated as authoritative. |
|
||||||
|
|
||||||
Clients display the deletion request event itself in any way they choose, e.g., not at all, or with a prominent notice. |
|
||||||
|
|
||||||
Clients MAY choose to inform the user that their request for deletion does not guarantee deletion because it is impossible to delete events from all relays and clients. |
|
||||||
|
|
||||||
## Relay Usage |
|
||||||
|
|
||||||
Relays MAY validate that a deletion request event only references events that have the same `pubkey` as the deletion request itself, however this is not required since relays may not have knowledge of all referenced events. |
|
||||||
|
|
||||||
## Deletion Request of a Deletion Request |
|
||||||
|
|
||||||
Publishing a deletion request event against a deletion request has no effect. Clients and relays are not obliged to support "unrequest deletion" functionality. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic uses NIP-09 deletion requests for repository fork failures and other error scenarios where an event needs to be removed. |
|
||||||
|
|
||||||
### Fork Failure Deletion |
|
||||||
|
|
||||||
When a repository fork operation fails (e.g., ownership transfer event cannot be published), GitRepublic creates a deletion request to remove the failed fork announcement: |
|
||||||
|
|
||||||
```typescript |
|
||||||
{ |
|
||||||
kind: 5, |
|
||||||
tags: [ |
|
||||||
['a', `30617:${userPubkeyHex}:${forkRepoName}`], |
|
||||||
['k', KIND.REPO_ANNOUNCEMENT.toString()] |
|
||||||
], |
|
||||||
content: 'Fork failed: ownership transfer event could not be published...' |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
This ensures that failed fork attempts don't leave orphaned repository announcements in the system. |
|
||||||
|
|
||||||
**Implementation**: `src/routes/api/repos/[npub]/[repo]/fork/+server.ts` |
|
||||||
@ -1,107 +0,0 @@ |
|||||||
NIP-10 |
|
||||||
====== |
|
||||||
|
|
||||||
Text Notes and Threads |
|
||||||
---------------------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
This NIP defines `kind:1` as a simple plaintext note. |
|
||||||
|
|
||||||
## Abstract |
|
||||||
|
|
||||||
The `.content` property contains some human-readable text. |
|
||||||
|
|
||||||
`e` tags can be used to define note thread roots and replies. They SHOULD be sorted by the reply stack from root to the direct parent. |
|
||||||
|
|
||||||
`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md). |
|
||||||
|
|
||||||
```json |
|
||||||
["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"] |
|
||||||
``` |
|
||||||
|
|
||||||
Authors of the `e` and `q` tags SHOULD be added as `p` tags to notify of a new reply or quote. |
|
||||||
|
|
||||||
Markup languages such as markdown and HTML SHOULD NOT be used. |
|
||||||
|
|
||||||
## Marked "e" tags (PREFERRED) |
|
||||||
|
|
||||||
Kind 1 events with `e` tags are replies to other kind 1 events. Kind 1 replies MUST NOT be used to reply to other kinds, use [NIP-22](22.md) instead. |
|
||||||
|
|
||||||
`["e", <event-id>, <relay-url>, <marker>, <pubkey>]` |
|
||||||
|
|
||||||
Where: |
|
||||||
|
|
||||||
* `<event-id>` is the id of the event being referenced. |
|
||||||
* `<relay-url>` is the URL of a recommended relay associated with the reference. Clients SHOULD add a valid `<relay-url>` field, but may instead leave it as `""`. |
|
||||||
* `<marker>` is optional and if present is one of `"reply"`, `"root"`. |
|
||||||
* `<pubkey>` is optional, SHOULD be the pubkey of the author of the referenced event |
|
||||||
|
|
||||||
Those marked with `"reply"` denote the id of the reply event being responded to. Those marked with `"root"` denote the root id of the reply thread being responded to. For top level replies (those replying directly to the root event), only the `"root"` marker should be used. |
|
||||||
|
|
||||||
A direct reply to the root of a thread should have a single marked "e" tag of type "root". |
|
||||||
|
|
||||||
>This scheme is preferred because it allows events to mention others without confusing them with `<reply-id>` or `<root-id>`. |
|
||||||
|
|
||||||
`<pubkey>` SHOULD be the pubkey of the author of the `e` tagged event, this is used in the outbox model to search for that event from the authors write relays where relay hints did not resolve the event. |
|
||||||
|
|
||||||
## The "p" tag |
|
||||||
Used in a text event contains a list of pubkeys used to record who is involved in a reply thread. |
|
||||||
|
|
||||||
When replying to a text event E the reply event's "p" tags should contain all of E's "p" tags as well as the `"pubkey"` of the event being replied to. |
|
||||||
|
|
||||||
Example: Given a text event authored by `a1` with "p" tags [`p1`, `p2`, `p3`] then the "p" tags of the reply should be [`a1`, `p1`, `p2`, `p3`] |
|
||||||
in no particular order. |
|
||||||
|
|
||||||
## Deprecated Positional "e" tags |
|
||||||
|
|
||||||
This scheme is not in common use anymore and is here just to keep backward compatibility with older events on the network. |
|
||||||
|
|
||||||
Positional `e` tags are deprecated because they create ambiguities that are difficult, or impossible to resolve when an event references another but is not a reply. |
|
||||||
|
|
||||||
They use simple `e` tags without any marker. |
|
||||||
|
|
||||||
`["e", <event-id>, <relay-url>]` as per NIP-01. |
|
||||||
|
|
||||||
Where: |
|
||||||
|
|
||||||
* `<event-id>` is the id of the event being referenced. |
|
||||||
* `<relay-url>` is the URL of a recommended relay associated with the reference. Many clients treat this field as optional. |
|
||||||
|
|
||||||
**The positions of the "e" tags within the event denote specific meanings as follows**: |
|
||||||
|
|
||||||
* No "e" tag: <br> |
|
||||||
This event is not a reply to, nor does it refer to, any other event. |
|
||||||
|
|
||||||
* One "e" tag: <br> |
|
||||||
`["e", <id>]`: The id of the event to which this event is a reply. |
|
||||||
|
|
||||||
* Two "e" tags: `["e", <root-id>]`, `["e", <reply-id>]` <br> |
|
||||||
`<root-id>` is the id of the event at the root of the reply chain. `<reply-id>` is the id of the article to which this event is a reply. |
|
||||||
|
|
||||||
* Many "e" tags: `["e", <root-id>]` `["e", <mention-id>]`, ..., `["e", <reply-id>]`<br> |
|
||||||
There may be any number of `<mention-ids>`. These are the ids of events which may, or may not be in the reply chain. |
|
||||||
They are citing from this event. `root-id` and `reply-id` are as above. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic uses NIP-10 event references primarily in patch events (kind 1617) to create patch series. Patches in a patch set use `e` tags with `reply` markers to link to previous patches in the series. |
|
||||||
|
|
||||||
### Patch Series Threading |
|
||||||
|
|
||||||
When multiple patches are sent as part of a patch series, each patch after the first includes an `e` tag with a `reply` marker pointing to the previous patch: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1617, |
|
||||||
"tags": [ |
|
||||||
["a", "30617:owner:repo"], |
|
||||||
["e", "previous_patch_event_id", "wss://relay.example.com", "reply"], |
|
||||||
// ... other tags |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
This creates a threaded chain of patches that can be applied in sequence. |
|
||||||
|
|
||||||
**Implementation**: Used in patch creation and processing throughout the codebase |
|
||||||
@ -1,95 +0,0 @@ |
|||||||
NIP-19 |
|
||||||
====== |
|
||||||
|
|
||||||
bech32-encoded entities |
|
||||||
----------------------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
This NIP standardizes bech32-formatted strings that can be used to display keys, ids and other information in clients. These formats are not meant to be used anywhere in the core protocol, they are only meant for displaying to users, copy-pasting, sharing, rendering QR codes and inputting data. |
|
||||||
|
|
||||||
It is recommended that ids and keys are stored in either hex or binary format, since these formats are closer to what must actually be used the core protocol. |
|
||||||
|
|
||||||
## Bare keys and ids |
|
||||||
|
|
||||||
To prevent confusion and mixing between private keys, public keys and event ids, which are all 32 byte strings. bech32-(not-m) encoding with different prefixes can be used for each of these entities. |
|
||||||
|
|
||||||
These are the possible bech32 prefixes: |
|
||||||
|
|
||||||
- `npub`: public keys |
|
||||||
- `nsec`: private keys |
|
||||||
- `note`: note ids |
|
||||||
|
|
||||||
Example: the hex public key `3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d` translates to `npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6`. |
|
||||||
|
|
||||||
The bech32 encodings of keys and ids are not meant to be used inside the standard NIP-01 event formats or inside the filters, they're meant for human-friendlier display and input only. Clients should still accept keys in both hex and npub format for now, and convert internally. |
|
||||||
|
|
||||||
## Shareable identifiers with extra metadata |
|
||||||
|
|
||||||
When sharing a profile or an event, an app may decide to include relay information and other metadata such that other apps can locate and display these entities more easily. |
|
||||||
|
|
||||||
For these events, the contents are a binary-encoded list of `TLV` (type-length-value), with `T` and `L` being 1 byte each (`uint8`, i.e. a number in the range of 0-255), and `V` being a sequence of bytes of the size indicated by `L`. |
|
||||||
|
|
||||||
These are the possible bech32 prefixes with `TLV`: |
|
||||||
|
|
||||||
- `nprofile`: a nostr profile |
|
||||||
- `nevent`: a nostr event |
|
||||||
- `naddr`: a nostr _addressable event_ coordinate |
|
||||||
- `nrelay`: a nostr relay (deprecated) |
|
||||||
|
|
||||||
These possible standardized `TLV` types are indicated here: |
|
||||||
|
|
||||||
- `0`: `special` |
|
||||||
- depends on the bech32 prefix: |
|
||||||
- for `nprofile` it will be the 32 bytes of the profile public key |
|
||||||
- for `nevent` it will be the 32 bytes of the event id |
|
||||||
- for `naddr`, it is the identifier (the `"d"` tag) of the event being referenced. For normal replaceable events use an empty string. |
|
||||||
- `1`: `relay` |
|
||||||
- for `nprofile`, `nevent` and `naddr`, _optionally_, a relay in which the entity (profile or event) is more likely to be found, encoded as ascii |
|
||||||
- this may be included multiple times |
|
||||||
- `2`: `author` |
|
||||||
- for `naddr`, the 32 bytes of the pubkey of the event |
|
||||||
- for `nevent`, _optionally_, the 32 bytes of the pubkey of the event |
|
||||||
- `3`: `kind` |
|
||||||
- for `naddr`, the 32-bit unsigned integer of the kind, big-endian |
|
||||||
- for `nevent`, _optionally_, the 32-bit unsigned integer of the kind, big-endian |
|
||||||
|
|
||||||
## Examples |
|
||||||
|
|
||||||
- `npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg` should decode into the public key hex `7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e` and vice-versa |
|
||||||
- `nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5` should decode into the private key hex `67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa` and vice-versa |
|
||||||
- `nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p` should decode into a profile with the following TLV items: |
|
||||||
- pubkey: `3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d` |
|
||||||
- relay: `wss://r.x.com` |
|
||||||
- relay: `wss://djbas.sadkb.com` |
|
||||||
|
|
||||||
## Notes |
|
||||||
|
|
||||||
- `npub` keys MUST NOT be used in NIP-01 events or in NIP-05 JSON responses, only the hex format is supported there. |
|
||||||
- When decoding a bech32-formatted string, TLVs that are not recognized or supported should be ignored, rather than causing an error. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic extensively uses NIP-19 bech32 encoding for user-friendly display and input of Nostr entities throughout the application. |
|
||||||
|
|
||||||
### User Interface |
|
||||||
|
|
||||||
- **Repository URLs**: Repositories are accessed via URLs like `/repos/{npub}/{repo-name}` where `npub` is the bech32-encoded public key |
|
||||||
- **User Profiles**: User profile pages use npub in URLs: `/users/{npub}` |
|
||||||
- **Search**: Users can search for repositories using npub, naddr, or nevent formats |
|
||||||
- **Repository References**: The "Load Existing Repository" feature accepts hex event IDs, nevent, or naddr formats |
|
||||||
|
|
||||||
### Internal Handling |
|
||||||
|
|
||||||
- **Conversion**: All npub values are converted to hex format internally for event creation and filtering |
|
||||||
- **Maintainers**: Maintainer lists accept both npub and hex formats, converting as needed |
|
||||||
- **Contact Lists**: When processing kind 3 contact lists, GitRepublic handles both npub and hex pubkey formats |
|
||||||
|
|
||||||
### Search Support |
|
||||||
|
|
||||||
The repository search feature can decode and search by: |
|
||||||
- **npub**: User public keys (converted to hex for filtering) |
|
||||||
- **naddr**: Repository announcement addresses (decoded to extract pubkey, kind, and d-tag) |
|
||||||
- **nevent**: Event references (decoded to extract event ID) |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/nip19-utils.ts`, used throughout the codebase for encoding/decoding |
|
||||||
@ -1,237 +0,0 @@ |
|||||||
NIP-22 |
|
||||||
====== |
|
||||||
|
|
||||||
Comment |
|
||||||
------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
A comment is a threading note always scoped to a root event or an [`I`-tag](73.md). |
|
||||||
|
|
||||||
It uses `kind:1111` with plaintext `.content` (no HTML, Markdown, or other formatting). |
|
||||||
|
|
||||||
Comments MUST point to the root scope using uppercase tag names (e.g. `K`, `E`, `A` or `I`) |
|
||||||
and MUST point to the parent item with lowercase ones (e.g. `k`, `e`, `a` or `i`). |
|
||||||
|
|
||||||
Comments MUST point to the authors when one is available (i.e. tagging a nostr event). `P` for the root scope |
|
||||||
and `p` for the author of the parent item. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "<comment>", |
|
||||||
"tags": [ |
|
||||||
// root scope: event addresses, event ids, or I-tags. |
|
||||||
["<A, E, I>", "<address, id or I-value>", "<relay or web page hint>", "<root event's pubkey, if an E tag>"], |
|
||||||
// the root item kind |
|
||||||
["K", "<root kind>"], |
|
||||||
|
|
||||||
// pubkey of the author of the root scope event |
|
||||||
["P", "<root-pubkey>", "relay-url-hint"], |
|
||||||
|
|
||||||
// parent item: event addresses, event ids, or i-tags. |
|
||||||
["<a, e, i>", "<address, id or i-value>", "<relay or web page hint>", "<parent event's pubkey, if an e tag>"], |
|
||||||
// parent item kind |
|
||||||
["k", "<parent comment kind>"], |
|
||||||
|
|
||||||
// parent item pubkey |
|
||||||
["p", "<parent-pubkey>", "relay-url-hint"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Tags `K` and `k` MUST be present to define the event kind of the root and the parent items. |
|
||||||
|
|
||||||
`I` and `i` tags create scopes for hashtags, geohashes, URLs, and other external identifiers. |
|
||||||
|
|
||||||
The possible values for `i` tags – and `k` tags, when related to an external identity – are listed on [NIP-73](73.md). |
|
||||||
Their uppercase versions use the same type of values but relate to the root item instead of the parent one. |
|
||||||
|
|
||||||
`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md). |
|
||||||
|
|
||||||
```json |
|
||||||
["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"] |
|
||||||
``` |
|
||||||
|
|
||||||
`p` tags SHOULD be used when mentioning pubkeys in the `.content` with [NIP-21](21.md). |
|
||||||
|
|
||||||
Comments MUST NOT be used to reply to kind 1 notes. [NIP-10](10.md) should instead be followed. |
|
||||||
|
|
||||||
## Examples |
|
||||||
|
|
||||||
A comment on a blog post looks like this: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "Great blog post!", |
|
||||||
"tags": [ |
|
||||||
// top-level comments scope to event addresses or ids |
|
||||||
["A", "30023:3c9849383bdea883b0bd16fece1ed36d37e37cdde3ce43b17ea4e9192ec11289:f9347ca7", "wss://example.relay"], |
|
||||||
// the root kind |
|
||||||
["K", "30023"], |
|
||||||
// author of root event |
|
||||||
["P", "3c9849383bdea883b0bd16fece1ed36d37e37cdde3ce43b17ea4e9192ec11289", "wss://example.relay"] |
|
||||||
|
|
||||||
// the parent event address (same as root for top-level comments) |
|
||||||
["a", "30023:3c9849383bdea883b0bd16fece1ed36d37e37cdde3ce43b17ea4e9192ec11289:f9347ca7", "wss://example.relay"], |
|
||||||
// when the parent event is replaceable or addressable, also include an `e` tag referencing its id |
|
||||||
["e", "5b4fc7fed15672fefe65d2426f67197b71ccc82aa0cc8a9e94f683eb78e07651", "wss://example.relay"], |
|
||||||
// the parent event kind |
|
||||||
["k", "30023"], |
|
||||||
// author of the parent event |
|
||||||
["p", "3c9849383bdea883b0bd16fece1ed36d37e37cdde3ce43b17ea4e9192ec11289", "wss://example.relay"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
A comment on a [NIP-94](94.md) file looks like this: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "Great file!", |
|
||||||
"tags": [ |
|
||||||
// top-level comments have the same scope and reply to addresses or ids |
|
||||||
["E", "768ac8720cdeb59227cf95e98b66560ef03d8bc9a90d721779e76e68fb42f5e6", "wss://example.relay", "3721e07b079525289877c366ccab47112bdff3d1b44758ca333feb2dbbbbe5bb"], |
|
||||||
// the root kind |
|
||||||
["K", "1063"], |
|
||||||
// author of the root event |
|
||||||
["P", "3721e07b079525289877c366ccab47112bdff3d1b44758ca333feb2dbbbbe5bb"], |
|
||||||
|
|
||||||
// the parent event id (same as root for top-level comments) |
|
||||||
["e", "768ac8720cdeb59227cf95e98b66560ef03d8bc9a90d721779e76e68fb42f5e6", "wss://example.relay", "3721e07b079525289877c366ccab47112bdff3d1b44758ca333feb2dbbbbe5bb"], |
|
||||||
// the parent kind |
|
||||||
["k", "1063"], |
|
||||||
["p", "3721e07b079525289877c366ccab47112bdff3d1b44758ca333feb2dbbbbe5bb"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
A reply to a comment looks like this: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "This is a reply to \"Great file!\"", |
|
||||||
"tags": [ |
|
||||||
// nip-94 file event id |
|
||||||
["E", "768ac8720cdeb59227cf95e98b66560ef03d8bc9a90d721779e76e68fb42f5e6", "wss://example.relay", "fd913cd6fa9edb8405750cd02a8bbe16e158b8676c0e69fdc27436cc4a54cc9a"], |
|
||||||
// the root kind |
|
||||||
["K", "1063"], |
|
||||||
["P", "fd913cd6fa9edb8405750cd02a8bbe16e158b8676c0e69fdc27436cc4a54cc9a"], |
|
||||||
|
|
||||||
// the parent event |
|
||||||
["e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://example.relay", "93ef2ebaaf9554661f33e79949007900bbc535d239a4c801c33a4d67d3e7f546"], |
|
||||||
// the parent kind |
|
||||||
["k", "1111"], |
|
||||||
["p", "93ef2ebaaf9554661f33e79949007900bbc535d239a4c801c33a4d67d3e7f546"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
A comment on a website's url looks like this: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "Nice article!", |
|
||||||
"tags": [ |
|
||||||
// referencing the root url |
|
||||||
["I", "https://abc.com/articles/1"], |
|
||||||
// the root "kind": for an url |
|
||||||
["K", "web"], |
|
||||||
|
|
||||||
// the parent reference (same as root for top-level comments) |
|
||||||
["i", "https://abc.com/articles/1"], |
|
||||||
// the parent "kind": for an url |
|
||||||
["k", "web"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
A podcast comment example: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"id": "80c48d992a38f9c445b943a9c9f1010b396676013443765750431a9004bdac05", |
|
||||||
"pubkey": "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111", |
|
||||||
"kind": 1111, |
|
||||||
"content": "This was a great episode!", |
|
||||||
"tags": [ |
|
||||||
// podcast episode reference |
|
||||||
["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f", "https://fountain.fm/episode/z1y9TMQRuqXl2awyrQxg"], |
|
||||||
// podcast episode type |
|
||||||
["K", "podcast:item:guid"], |
|
||||||
|
|
||||||
// same value as "I" tag above, because it is a top-level comment (not a reply to a comment) |
|
||||||
["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f", "https://fountain.fm/episode/z1y9TMQRuqXl2awyrQxg"], |
|
||||||
["k", "podcast:item:guid"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
A reply to a podcast comment: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "I'm replying to the above comment.", |
|
||||||
"tags": [ |
|
||||||
// podcast episode reference |
|
||||||
["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f", "https://fountain.fm/episode/z1y9TMQRuqXl2awyrQxg"], |
|
||||||
// podcast episode type |
|
||||||
["K", "podcast:item:guid"], |
|
||||||
|
|
||||||
// this is a reference to the above comment |
|
||||||
["e", "80c48d992a38f9c445b943a9c9f1010b396676013443765750431a9004bdac05", "wss://example.relay", "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111"], |
|
||||||
// the parent comment kind |
|
||||||
["k", "1111"] |
|
||||||
["p", "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111"] |
|
||||||
] |
|
||||||
// other fields |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic uses NIP-22 comments for threaded discussions on pull requests, issues, and patches. Comments enable collaborative code review and issue discussion. |
|
||||||
|
|
||||||
### Comment Threading |
|
||||||
|
|
||||||
Comments on PRs, issues, and patches use NIP-22's uppercase/lowercase tag convention: |
|
||||||
- **Uppercase tags** (`E`, `K`, `P`, `A`) reference the root event (PR, issue, or patch) |
|
||||||
- **Lowercase tags** (`e`, `k`, `p`, `a`) reference the parent comment (for reply threads) |
|
||||||
|
|
||||||
### Implementation Details |
|
||||||
|
|
||||||
- **Root Comments**: Comments directly on a PR/issue use uppercase tags pointing to the root event |
|
||||||
- **Reply Comments**: Replies to other comments use lowercase tags for the parent comment |
|
||||||
- **Relay Hints**: Relay URLs are included in `E` and `e` tags to help clients locate events |
|
||||||
- **Plaintext Content**: Comment content is plaintext (no HTML/Markdown) as per NIP-22 spec |
|
||||||
|
|
||||||
### Example: Comment on Pull Request |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1111, |
|
||||||
"content": "This looks good, but consider adding error handling here.", |
|
||||||
"tags": [ |
|
||||||
["E", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], |
|
||||||
["K", "1618"], |
|
||||||
["P", "pr_author_pubkey", "wss://relay.example.com"], |
|
||||||
["e", "pr_event_id", "wss://relay.example.com", "pr_author_pubkey"], |
|
||||||
["k", "1618"], |
|
||||||
["p", "pr_author_pubkey", "wss://relay.example.com"] |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/highlights-service.ts` (comment creation), used throughout PR and issue discussion features |
|
||||||
``` |
|
||||||
@ -1,345 +0,0 @@ |
|||||||
NIP-34 |
|
||||||
====== |
|
||||||
|
|
||||||
`git` stuff |
|
||||||
----------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
This NIP defines all the ways code collaboration using and adjacent to [`git`](https://git-scm.com/) can be done using Nostr. |
|
||||||
|
|
||||||
## Repository announcements |
|
||||||
|
|
||||||
Git repositories are hosted in Git-enabled servers, but their existence can be announced using Nostr events. By doing so the author asserts themselves as a maintainer and expresses a willingness to receive patches, bug reports and comments in general, unless `t` tag `personal-fork` is included. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 30617, |
|
||||||
"content": "", |
|
||||||
"tags": [ |
|
||||||
["d", "<repo-id>"], // usually kebab-case short name |
|
||||||
["name", "<human-readable project name>"], |
|
||||||
["description", "brief human-readable project description>"], |
|
||||||
["web", "<url for browsing>", ...], // a webpage url, if the git server being used provides such a thing |
|
||||||
["clone", "<url for git-cloning>", ...], // a url to be given to `git clone` so anyone can clone it |
|
||||||
["relays", "<relay-url>", ...], // relays that this repository will monitor for patches and issues |
|
||||||
["r", "<earliest-unique-commit-id>", "euc"], |
|
||||||
["maintainers", "<other-recognized-maintainer>", ...], |
|
||||||
["t","personal-fork"], // optionally indicate author isn't a maintainer |
|
||||||
["t", "<arbitrary string>"], // hashtags labelling the repository |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The tags `web`, `clone`, `relays`, `maintainers` can have multiple values. |
|
||||||
|
|
||||||
The `r` tag annotated with the `"euc"` marker should be the commit ID of the earliest unique commit of this repo, made to identify it among forks and group it with other repositories hosted elsewhere that may represent essentially the same project. In most cases it will be the root commit of a repository. In case of a permanent fork between two projects, then the first commit after the fork should be used. |
|
||||||
|
|
||||||
Except `d`, all tags are optional. |
|
||||||
|
|
||||||
## Repository state announcements |
|
||||||
|
|
||||||
An optional source of truth for the state of branches and tags in a repository. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 30618, |
|
||||||
"content": "", |
|
||||||
"tags": [ |
|
||||||
["d", "<repo-id>"], // matches the identifier in the corresponding repository announcement |
|
||||||
["refs/<heads|tags>/<branch-or-tag-name>","<commit-id>"] |
|
||||||
["HEAD", "ref: refs/heads/<branch-name>"] |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The `refs` tag may appear multiple times, or none. |
|
||||||
|
|
||||||
If no `refs` tags are present, the author is no longer tracking repository state using this event. This approach enables the author to restart tracking state at a later time unlike [NIP-09](09.md) deletion requests. |
|
||||||
|
|
||||||
The `refs` tag can be optionally extended to enable clients to identify how many commits ahead a ref is: |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"tags": [ |
|
||||||
["refs/<heads|tags>/<branch-or-tag-name>", "<commit-id>", "<shorthand-parent-commit-id>", "<shorthand-grandparent>", ...], |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Patches and Pull Requests (PRs) |
|
||||||
|
|
||||||
Patches and PRs can be sent by anyone to any repository. Patches and PRs to a specific repository SHOULD be sent to the relays specified in that repository's announcement event's `"relays"` tag. Patch and PR events SHOULD include an `a` tag pointing to that repository's announcement address. |
|
||||||
|
|
||||||
### When to Use Patches vs. Pull Requests |
|
||||||
|
|
||||||
**Patches SHOULD be used if each event is under 60kb, otherwise PRs SHOULD be used.** |
|
||||||
|
|
||||||
However, the choice between patches and pull requests isn't just about size. Here are the key differences: |
|
||||||
|
|
||||||
#### Patches (Kind 1617) |
|
||||||
- **Content**: Patch content is embedded directly in the Nostr event content field |
|
||||||
- **Size**: Each patch event must be under 60KB |
|
||||||
- **Workflow**: Event-based, sequential patch series linked via NIP-10 reply tags |
|
||||||
- **Use Cases**: |
|
||||||
- Small, self-contained changes |
|
||||||
- Bug fixes and typo corrections |
|
||||||
- Simple feature additions |
|
||||||
- When you want to send code changes as self-contained events without maintaining a fork |
|
||||||
- **Series**: Patches in a series are linked via NIP-10 reply tags |
|
||||||
- **Format**: Patch content can be in any format (git format-patch, unified diff, or plain text description) |
|
||||||
- **Discovery**: Patches are discoverable as Nostr events on relays, independent of git repository access |
|
||||||
|
|
||||||
#### Pull Requests (Kind 1618) |
|
||||||
- **Content**: Markdown description with references to commits via clone URLs |
|
||||||
- **Size**: No strict limit (commits are stored in git repositories, not in the event) |
|
||||||
- **Workflow**: Branch-based, iterative collaboration |
|
||||||
- **Use Cases**: |
|
||||||
- Large, complex changes |
|
||||||
- Multi-file refactoring |
|
||||||
- Features requiring discussion and iteration |
|
||||||
- When working with forks and branches |
|
||||||
- **Updates**: PRs can be updated (kind 1619) to change the tip commit |
|
||||||
- **Format**: References commits that must be accessible via clone URLs |
|
||||||
|
|
||||||
#### Key Differences Summary |
|
||||||
|
|
||||||
| Aspect | Patches | Pull Requests | |
|
||||||
|--------|---------|---------------| |
|
||||||
| **Event Size** | Under 60KB | No limit (commits stored separately) | |
|
||||||
| **Content Location** | In event content | In referenced git repository | |
|
||||||
| **Workflow Style** | Email-style, sequential | Branch-based, iterative | |
|
||||||
| **Best For** | Small, self-contained changes | Large, complex changes | |
|
||||||
| **Maintenance** | Static (new patch for revisions) | Dynamic (can update tip commit) | |
|
||||||
| **Application** | Extract from event, then `git am` (if applicable) | `git merge` or `git cherry-pick` | |
|
||||||
| **Discussion** | Via NIP-22 comments | Via NIP-22 comments + inline code comments | |
|
||||||
|
|
||||||
### Patches |
|
||||||
|
|
||||||
wPatches are created as Nostr events (kind 1617) with the patch content embedded in the event's content field. This makes patches self-contained and discoverable on Nostr relays without requiring access to git repositories. |
|
||||||
|
|
||||||
Patches in a patch set SHOULD include a [NIP-10](10.md) `e` `reply` tag pointing to the previous patch. |
|
||||||
|
|
||||||
The first patch revision in a patch revision SHOULD include a [NIP-10](10.md) `e` `reply` to the original root patch. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1617, |
|
||||||
"content": "<patch-content>", // Patch content in any format (git format-patch, unified diff, or plain text) |
|
||||||
"tags": [ |
|
||||||
["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], |
|
||||||
["r", "<earliest-unique-commit-id-of-repo>"], // so clients can subscribe to all patches sent to a local git repo |
|
||||||
["p", "<repository-owner>"], |
|
||||||
["p", "<other-user>"], // optionally send the patch to another user to bring it to their attention |
|
||||||
|
|
||||||
["t", "root"], // omitted for additional patches in a series |
|
||||||
// for the first patch in a revision |
|
||||||
["t", "root-revision"], |
|
||||||
|
|
||||||
// optional tags for when it is desirable that the merged patch has a stable commit id |
|
||||||
// these fields are necessary for ensuring that the commit resulting from applying a patch |
|
||||||
// has the same id as it had in the proposer's machine -- all these tags can be omitted |
|
||||||
// if the maintainer doesn't care about these things |
|
||||||
["commit", "<current-commit-id>"], |
|
||||||
["r", "<current-commit-id>"], // so clients can find existing patches for a specific commit |
|
||||||
["parent-commit", "<parent-commit-id>"], |
|
||||||
["commit-pgp-sig", "-----BEGIN PGP SIGNATURE-----..."], // empty string for unsigned commit |
|
||||||
["committer", "<name>", "<email>", "<timestamp>", "<timezone offset in minutes>"], |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The patch content can be in any format that describes code changes. Common formats include git format-patch output, unified diff format, or plain text descriptions. The first patch in a series MAY be a cover letter describing the patch series. |
|
||||||
|
|
||||||
### Pull Requests |
|
||||||
|
|
||||||
The PR or PR update tip SHOULD be successfully pushed to `refs/nostr/<[PR|PR-Update]-event-id>` in all repositories listed in its `clone` tag before the event is signed. |
|
||||||
|
|
||||||
An attempt SHOULD be made to push this ref to all repositories listed in the repository's announcement event's `"clone"` tag, for which their is reason to believe the user might have write access. This includes each [grasp server](https://njump.me/naddr1qvzqqqrhnypzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqy28wumn8ghj7un9d3shjtnwva5hgtnyv4mqqpt8wfshxuqlnvh8x) which can be identified using this method: `clone` tag includes `[http|https]://<grasp-path>/<valid-npub>/<string>.git` and `relays` tag includes `[ws/wss]://<grasp-path>`. |
|
||||||
|
|
||||||
Clients MAY fallback to creating a 'personal-fork' `repository announcement` listing other grasp servers, e.g. from the `User grasp list`, for the purpose of serving the specified commit(s). |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1618, |
|
||||||
"content": "<markdown text>", |
|
||||||
"tags": [ |
|
||||||
["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], |
|
||||||
["r", "<earliest-unique-commit-id-of-repo>"] // so clients can subscribe to all PRs sent to a local git repo |
|
||||||
["p", "<repository-owner>"], |
|
||||||
["p", "<other-user>"], // optionally send the PR to another user to bring it to their attention |
|
||||||
|
|
||||||
["subject", "<PR-subject>"], |
|
||||||
["t", "<PR-label>"], // optional |
|
||||||
["t", "<another-PR-label>"], // optional |
|
||||||
|
|
||||||
["c", "<current-commit-id>"], // tip of the PR branch |
|
||||||
["clone", "<clone-url>", ...], // at least one git clone url where commit can be downloaded |
|
||||||
["branch-name", "<branch-name>"], // optional recommended branch name |
|
||||||
|
|
||||||
["e", "<root-patch-event-id>"], // optionally indicate PR is a revision of an existing patch, which should be closed |
|
||||||
["merge-base", "<commit-id>"], // optional: the most recent common ancestor with the target branch |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### Pull Request Updates |
|
||||||
|
|
||||||
A PR Update changes the tip of a referenced PR event. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1619, |
|
||||||
"content": "", |
|
||||||
"tags": [ |
|
||||||
["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], |
|
||||||
["r", "<earliest-unique-commit-id-of-repo>"] // so clients can subscribe to all PRs sent to a local git repo |
|
||||||
["p", "<repository-owner>"], |
|
||||||
["p", "<other-user>"], // optionally send the PR to another user to bring it to their attention |
|
||||||
|
|
||||||
// NIP-22 tags |
|
||||||
["E", "<pull-request-event-id>"], |
|
||||||
["P", "<pull-request-author>"], |
|
||||||
|
|
||||||
["c", "<current-commit-id>"], // updated tip of PR |
|
||||||
["clone", "<clone-url>", ...], // at least one git clone url where commit can be downloaded |
|
||||||
["merge-base", "<commit-id>"], // optional: the most recent common ancestor with the target branch |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Issues |
|
||||||
|
|
||||||
Issues are Markdown text that is just human-readable conversational threads related to the repository: bug reports, feature requests, questions or comments of any kind. Like patches, these SHOULD be sent to the relays specified in that repository's announcement event's `"relays"` tag. |
|
||||||
|
|
||||||
Issues may have a `subject` tag, which clients can utilize to display a header. Additionally, one or more `t` tags may be included to provide labels for the issue. |
|
||||||
|
|
||||||
```json |
|
||||||
{ |
|
||||||
"kind": 1621, |
|
||||||
"content": "<markdown text>", |
|
||||||
"tags": [ |
|
||||||
["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"], |
|
||||||
["p", "<repository-owner>"] |
|
||||||
["subject", "<issue-subject>"] |
|
||||||
["t", "<issue-label>"] |
|
||||||
["t", "<another-issue-label>"] |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Replies |
|
||||||
|
|
||||||
Replies to either a `kind:1621` (_issue_), `kind:1617` (_patch_) or `kind:1618` (_pull request_) event should follow [NIP-22 comment](22.md). |
|
||||||
|
|
||||||
## Status |
|
||||||
|
|
||||||
Root Patches, PRs and Issues have a Status that defaults to 'Open' and can be set by issuing Status events. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 1630, // Open |
|
||||||
"kind": 1631, // Applied / Merged for Patches; Resolved for Issues |
|
||||||
"kind": 1632, // Closed |
|
||||||
"kind": 1633, // Draft |
|
||||||
"content": "<markdown text>", |
|
||||||
"tags": [ |
|
||||||
["e", "<issue-or-PR-or-original-root-patch-id-hex>", "", "root"], |
|
||||||
["e", "<accepted-revision-root-id-hex>", "", "reply"], // for when revisions applied |
|
||||||
["p", "<repository-owner>"], |
|
||||||
["p", "<root-event-author>"], |
|
||||||
["p", "<revision-author>"], |
|
||||||
|
|
||||||
// optional for improved subscription filter efficiency |
|
||||||
["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>", "<relay-url>"], |
|
||||||
["r", "<earliest-unique-commit-id-of-repo>"] |
|
||||||
|
|
||||||
// optional for `1631` status |
|
||||||
["q", "<applied-or-merged-patch-event-id>", "<relay-url>", "<pubkey>"], // for each |
|
||||||
// when merged |
|
||||||
["merge-commit", "<merge-commit-id>"] |
|
||||||
["r", "<merge-commit-id>"] |
|
||||||
// when applied |
|
||||||
["applied-as-commits", "<commit-id-in-master-branch>", ...] |
|
||||||
["r", "<applied-commit-id>"] // for each |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
The most recent Status event (by `created_at` date) from either the issue/patch author or a maintainer is considered valid. |
|
||||||
|
|
||||||
The Status of a patch-revision is to either that of the root-patch, or `1632` (_Closed_) if the root-patch's Status is `1631` (_Applied/Merged_) and the patch-revision isn't tagged in the `1631` (_Applied/Merged_) event. |
|
||||||
|
|
||||||
## User grasp list |
|
||||||
|
|
||||||
List of [grasp servers](https://njump.me/naddr1qvzqqqrhnypzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqy28wumn8ghj7un9d3shjtnwva5hgtnyv4mqqpt8wfshxuqlnvh8x) the user generally wishes to use for NIP-34 related activity. It is similar in function to the NIP-65 relay list and NIP-B7 blossom list. |
|
||||||
|
|
||||||
The event SHOULD include a list of `g` tags with grasp service websocket URLs in order of preference. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 10317, |
|
||||||
"content": "", |
|
||||||
"tags": [ |
|
||||||
["g", "<grasp-service-websocket-url>"], // zero or more grasp sever urls |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Possible things to be added later |
|
||||||
|
|
||||||
- inline file comments kind (we probably need one for patches and a different one for merged files) |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
NIP-34 is the core NIP that GitRepublic implements. All repository functionality is built on top of NIP-34 event kinds. |
|
||||||
|
|
||||||
### Repository Announcements (Kind 30617) |
|
||||||
|
|
||||||
GitRepublic uses repository announcements to: |
|
||||||
- **Discover Repositories**: The landing page fetches all kind 30617 events to display available repositories |
|
||||||
- **Repository Creation**: Users create repository announcements via the signup page |
|
||||||
- **Repository Updates**: Repository information updates are done via the signup page, which updates the repository announcement event |
|
||||||
- **Fork Detection**: Forks are identified by `a` tags pointing to the original repository |
|
||||||
- **Offline Papertrail**: Repository announcements are saved to `nostr/repo-events.jsonl` in the repository for offline verification |
|
||||||
|
|
||||||
### Pull Requests (Kind 1618) |
|
||||||
|
|
||||||
- **PR Creation**: Users create PRs by pushing commits and creating kind 1618 events |
|
||||||
- **PR Updates**: PR updates (kind 1619) change the tip commit of existing PRs |
|
||||||
- **PR Display**: PRs are displayed with their full markdown content and metadata |
|
||||||
- **PR Merging**: Merging a PR creates a status event (kind 1631) with merge commit information |
|
||||||
|
|
||||||
### Issues (Kind 1621) |
|
||||||
|
|
||||||
- **Issue Creation**: Users create issues for bug reports, feature requests, and questions |
|
||||||
- **Issue Threading**: Issues use NIP-22 comments for discussion |
|
||||||
- **Issue Status**: Status events (kinds 1630-1633) track issue state (open, resolved, closed, draft) |
|
||||||
|
|
||||||
### Status Events (Kinds 1630-1633) |
|
||||||
|
|
||||||
- **Status Tracking**: PRs and issues use status events to track their state |
|
||||||
- **Most Recent Wins**: Only the most recent status event from the author or a maintainer is considered valid |
|
||||||
- **Merge Tracking**: When a PR is merged (status 1631), the merge commit ID is included in tags |
|
||||||
|
|
||||||
### Repository State (Kind 30618) |
|
||||||
|
|
||||||
- **Branch Tracking**: Optional repository state events track branch and tag positions |
|
||||||
- **State Updates**: Repository state can be updated to reflect current branch/tag positions |
|
||||||
|
|
||||||
### Patches (Kind 1617) |
|
||||||
|
|
||||||
GitRepublic supports event-based patches for small, self-contained code changes: |
|
||||||
|
|
||||||
- **Patch Creation**: Users create patches as Nostr events (kind 1617) via the web interface |
|
||||||
- **Patch Content**: Patch content is embedded directly in the event's content field (can be in any format: git format-patch, unified diff, or plain text) |
|
||||||
- **Event-Based**: Patches are self-contained Nostr events, discoverable on relays without requiring git repository access |
|
||||||
- **Patch Series**: Multiple related patches can be linked using NIP-10 reply tags |
|
||||||
- **Patch Status**: Patches use status events (kinds 1630-1633) to track state (open, applied, closed, draft) |
|
||||||
- **Patch Application**: Maintainers extract patch content from events and apply them, then mark as applied with status events |
|
||||||
|
|
||||||
**Implementation**: |
|
||||||
- Repository announcements: `src/routes/signup/+page.svelte`, `src/lib/services/nostr/repo-polling.ts` |
|
||||||
- Pull requests: `src/lib/services/nostr/prs-service.ts` |
|
||||||
- Issues: `src/lib/services/nostr/issues-service.ts` |
|
||||||
- Status events: Used throughout PR, patch, and issue management |
|
||||||
@ -1,73 +0,0 @@ |
|||||||
NIP-65 |
|
||||||
====== |
|
||||||
|
|
||||||
Relay List Metadata |
|
||||||
------------------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
Defines a replaceable event using `kind:10002` to advertise relays where the user generally **writes** to and relays where the user generally **reads** mentions. |
|
||||||
|
|
||||||
The event MUST include a list of `r` tags with relay URLs as value and an optional `read` or `write` marker. If the marker is omitted, the relay is both **read** and **write**. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 10002, |
|
||||||
"tags": [ |
|
||||||
["r", "wss://alicerelay.example.com"], |
|
||||||
["r", "wss://brando-relay.com"], |
|
||||||
["r", "wss://expensive-relay.example2.com", "write"], |
|
||||||
["r", "wss://nostr-relay.example.com", "read"] |
|
||||||
], |
|
||||||
"content": "", |
|
||||||
// other fields... |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
When downloading events **from** a user, clients SHOULD use the **write** relays of that user. |
|
||||||
|
|
||||||
When downloading events **about** a user, where the user was tagged (mentioned), clients SHOULD use the user's **read** relays. |
|
||||||
|
|
||||||
When publishing an event, clients SHOULD: |
|
||||||
|
|
||||||
- Send the event to the **write** relays of the author |
|
||||||
- Send the event to all **read** relays of each tagged user |
|
||||||
- Send the author's `kind:10002` event to all relays the event was published to |
|
||||||
|
|
||||||
### Size |
|
||||||
|
|
||||||
Clients SHOULD guide users to keep `kind:10002` lists small (2-4 relays of each category). |
|
||||||
|
|
||||||
### Discoverability |
|
||||||
|
|
||||||
Clients SHOULD spread an author's `kind:10002` event to as many relays as viable, paying attention to relays that, at any moment, serve naturally as well-known public indexers for these relay lists (where most other clients and users are connecting to in order to publish and fetch those). |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic uses NIP-65 relay lists to discover user's preferred relays for publishing events. This ensures events are published to relays the user trusts and uses. |
|
||||||
|
|
||||||
### Relay Discovery |
|
||||||
|
|
||||||
When publishing repository announcements, PRs, issues, or other events: |
|
||||||
|
|
||||||
1. **Fetch User's Relay List**: GitRepublic fetches the user's kind 10002 event |
|
||||||
2. **Extract Write Relays**: Parses `r` tags with `write` marker or no marker (both read and write) |
|
||||||
3. **Combine with Defaults**: Combines user's outbox relays with default relays for redundancy |
|
||||||
4. **Publish**: Sends events to the combined relay list |
|
||||||
|
|
||||||
### Fallback to Kind 3 |
|
||||||
|
|
||||||
If no kind 10002 event is found, GitRepublic falls back to kind 3 (contact list) for relay discovery, maintaining compatibility with older clients. |
|
||||||
|
|
||||||
### Implementation |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Fetch user's relay preferences |
|
||||||
const { inbox, outbox } = await getUserRelays(pubkey, nostrClient); |
|
||||||
const userRelays = combineRelays(outbox); |
|
||||||
|
|
||||||
// Publish to user's preferred relays |
|
||||||
await nostrClient.publishEvent(signedEvent, userRelays); |
|
||||||
``` |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/user-relays.ts`, used when publishing all repository-related events |
|
||||||
@ -1,95 +0,0 @@ |
|||||||
NIP-84 |
|
||||||
====== |
|
||||||
|
|
||||||
Highlights |
|
||||||
---------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
This NIP defines `kind:9802`, a "highlight" event, to signal content a user finds valuable. |
|
||||||
|
|
||||||
## Format |
|
||||||
The `.content` of these events is the highlighted portion of the text. |
|
||||||
|
|
||||||
`.content` might be empty for highlights of non-text based media (e.g. NIP-94 audio/video). |
|
||||||
|
|
||||||
### References |
|
||||||
Events SHOULD tag the source of the highlight, whether nostr-native or not. |
|
||||||
`a` or `e` tags should be used for nostr events and `r` tags for URLs. |
|
||||||
|
|
||||||
When tagging a URL, clients generating these events SHOULD do a best effort of cleaning the URL from trackers |
|
||||||
or obvious non-useful information from the query string. |
|
||||||
|
|
||||||
### Attribution |
|
||||||
Clients MAY include one or more `p` tags, tagging the original authors of the material being highlighted; this is particularly |
|
||||||
useful when highlighting non-nostr content for which the client might be able to get a nostr pubkey somehow |
|
||||||
(e.g. prompting the user or reading a `<link rel="me" href="nostr:nprofile1..." />` tag on the document). A role MAY be included as the |
|
||||||
last value of the tag. |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"tags": [ |
|
||||||
["p", "<pubkey-hex>", "<relay-url>", "author"], |
|
||||||
["p", "<pubkey-hex>", "<relay-url>", "author"], |
|
||||||
["p", "<pubkey-hex>", "<relay-url>", "editor"] |
|
||||||
], |
|
||||||
// other fields... |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### Context |
|
||||||
Clients MAY include a `context` tag, useful when the highlight is a subset of a paragraph and displaying the |
|
||||||
surrounding content might be beneficial to give context to the highlight. |
|
||||||
|
|
||||||
## Quote Highlights |
|
||||||
A `comment` tag may be added to create a quote highlight. This MUST be rendered like a quote repost with the highlight as the quoted note. |
|
||||||
|
|
||||||
This is to prevent the creation and multiple notes (highlight + kind 1) for a single highlight action, which looks bad in micro-blogging clients where these notes may appear in succession. |
|
||||||
|
|
||||||
p-tag mentions MUST have a `mention` attribute to distinguish it from authors and editors. |
|
||||||
|
|
||||||
r-tag urls from the comment MUST have a `mention` attribute to distinguish from the highlighted source url. The source url MUST have the `source` attribute. |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
GitRepublic extends NIP-84 highlights for code review and code selection features. Highlights enable users to select and comment on specific code sections in pull requests. |
|
||||||
|
|
||||||
### Code Highlighting |
|
||||||
|
|
||||||
When users select code in a PR for review: |
|
||||||
|
|
||||||
1. **Highlight Creation**: A kind 9802 event is created with the selected code in the `content` field |
|
||||||
2. **File Context**: Custom tags (`file`, `line-start`, `line-end`) specify which file and lines are highlighted |
|
||||||
3. **PR Reference**: The highlight references the PR using `a` and `e` tags |
|
||||||
4. **Attribution**: `P` and `p` tags reference the PR author and highlight creator |
|
||||||
|
|
||||||
### Extended Tags |
|
||||||
|
|
||||||
GitRepublic extends NIP-84 with file-specific tags for code context: |
|
||||||
- `file`: File path being highlighted |
|
||||||
- `line-start`: Starting line number |
|
||||||
- `line-end`: Ending line number |
|
||||||
- `context`: Optional surrounding context |
|
||||||
- `comment`: Optional comment text for quote highlights |
|
||||||
|
|
||||||
### Example: Code Highlight on PR |
|
||||||
|
|
||||||
```jsonc |
|
||||||
{ |
|
||||||
"kind": 9802, |
|
||||||
"content": "const result = await fetch(url);", |
|
||||||
"tags": [ |
|
||||||
["a", "1618:pr_author_pubkey.../repo-name"], |
|
||||||
["e", "pr_event_id"], |
|
||||||
["P", "pr_author_pubkey"], |
|
||||||
["K", "1618"], |
|
||||||
["file", "src/main.ts"], |
|
||||||
["line-start", "42"], |
|
||||||
["line-end", "45"], |
|
||||||
["context", "// Fetch data from API"], |
|
||||||
["p", "pr_author_pubkey", "wss://relay.example.com", "author"] |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/highlights-service.ts` |
|
||||||
@ -1,107 +0,0 @@ |
|||||||
NIP-98 |
|
||||||
====== |
|
||||||
|
|
||||||
HTTP Auth |
|
||||||
--------- |
|
||||||
|
|
||||||
`draft` `optional` |
|
||||||
|
|
||||||
This NIP defines an ephemeral event used to authorize requests to HTTP servers using nostr events. |
|
||||||
|
|
||||||
This is useful for HTTP services which are built for Nostr and deal with Nostr user accounts. |
|
||||||
|
|
||||||
## Nostr event |
|
||||||
|
|
||||||
A `kind 27235` (In reference to [RFC 7235](https://www.rfc-editor.org/rfc/rfc7235)) event is used. |
|
||||||
|
|
||||||
The `content` SHOULD be empty. |
|
||||||
|
|
||||||
The following tags MUST be included. |
|
||||||
|
|
||||||
* `u` - absolute URL |
|
||||||
* `method` - HTTP Request Method |
|
||||||
|
|
||||||
Example event: |
|
||||||
```json |
|
||||||
{ |
|
||||||
"id": "fe964e758903360f28d8424d092da8494ed207cba823110be3a57dfe4b578734", |
|
||||||
"pubkey": "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", |
|
||||||
"content": "", |
|
||||||
"kind": 27235, |
|
||||||
"created_at": 1682327852, |
|
||||||
"tags": [ |
|
||||||
["u", "https://api.snort.social/api/v1/n5sp/list"], |
|
||||||
["method", "GET"] |
|
||||||
], |
|
||||||
"sig": "5ed9d8ec958bc854f997bdc24ac337d005af372324747efe4a00e24f4c30437ff4dd8308684bed467d9d6be3e5a517bb43b1732cc7d33949a3aaf86705c22184" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Servers MUST perform the following checks in order to validate the event: |
|
||||||
1. The `kind` MUST be `27235`. |
|
||||||
2. The `created_at` timestamp MUST be within a reasonable time window (suggestion 60 seconds). |
|
||||||
3. The `u` tag MUST be exactly the same as the absolute request URL (including query parameters). |
|
||||||
4. The `method` tag MUST be the same HTTP method used for the requested resource. |
|
||||||
|
|
||||||
When the request contains a body (as in POST/PUT/PATCH methods) clients SHOULD include a SHA256 hash of the request body in a `payload` tag as hex (`["payload", "<sha256-hex>"]`), servers MAY check this to validate that the requested payload is authorized. |
|
||||||
|
|
||||||
If one of the checks was to fail the server SHOULD respond with a 401 Unauthorized response code. |
|
||||||
|
|
||||||
Servers MAY perform additional implementation-specific validation checks. |
|
||||||
|
|
||||||
## Request Flow |
|
||||||
|
|
||||||
Using the `Authorization` HTTP header, the `kind 27235` event MUST be `base64` encoded and use the Authorization scheme `Nostr` |
|
||||||
|
|
||||||
Example HTTP Authorization header: |
|
||||||
``` |
|
||||||
Authorization: Nostr |
|
||||||
eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIzMTEwYmUzYTU3ZGZlNGI1Nzg3MzQiLCJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRkMDllMThiZmQ3NmFhYmMyNGEwMDgxY2UyODU2ZjMzMDUwNGVkIiwiY29udGVudCI6IiIsImtpbmQiOjI3MjM1LCJjcmVhdGVkX2F0IjoxNjgyMzI3ODUyLCJ0YWdzIjpbWyJ1IiwiaHR0cHM6Ly9hcGkuc25vcnQuc29jaWFsL2FwaS92MS9uNXNwL2xpc3QiXSxbIm1ldGhvZCIsIkdFVCJdXSwic2lnIjoiNWVkOWQ4ZWM5NThiYzg1NGY5OTdiZGMyNGFjMzM3ZDAwNWFmMzcyMzI0NzQ3ZWZlNGEwMGUyNGY0YzMwNDM3ZmY0ZGQ4MzA4Njg0YmVkNDY3ZDlkNmJlM2U1YTUxN2JiNDNiMTczMmNjN2QzMzk0OWEzYWFmODY3MDVjMjIxODQifQ |
|
||||||
``` |
|
||||||
|
|
||||||
## Reference Implementations |
|
||||||
- C# ASP.NET `AuthenticationHandler` [NostrAuth.cs](https://gist.github.com/v0l/74346ae530896115bfe2504c8cd018d3) |
|
||||||
|
|
||||||
## GitRepublic Usage |
|
||||||
|
|
||||||
NIP-98 is used extensively in GitRepublic for authenticating git operations (clone, push, pull) and API requests. This enables secure git operations without requiring traditional username/password authentication. |
|
||||||
|
|
||||||
### Git Operations Authentication |
|
||||||
|
|
||||||
All git operations (push, pull, clone) use NIP-98 authentication: |
|
||||||
|
|
||||||
1. **Client Creates Auth Event**: User's browser extension creates a kind 27235 event with: |
|
||||||
- `u` tag: Absolute URL of the git endpoint |
|
||||||
- `method` tag: HTTP method (POST for push, GET for pull/clone) |
|
||||||
- `payload` tag: SHA256 hash of request body (for POST requests) |
|
||||||
|
|
||||||
2. **Base64 Encoding**: The event is base64-encoded and sent in `Authorization: Nostr {base64_event}` header |
|
||||||
|
|
||||||
3. **Server Verification**: GitRepublic verifies: |
|
||||||
- Event kind is 27235 |
|
||||||
- Timestamp is within 60 seconds |
|
||||||
- URL matches exactly (normalized, trailing slashes removed) |
|
||||||
- HTTP method matches |
|
||||||
- Payload hash matches (for POST requests) |
|
||||||
- Event signature is valid |
|
||||||
|
|
||||||
### API Endpoint Authentication |
|
||||||
|
|
||||||
API endpoints that modify repository state also use NIP-98: |
|
||||||
- File creation/editing |
|
||||||
- Repository settings updates |
|
||||||
- PR/issue creation |
|
||||||
- Comment posting |
|
||||||
|
|
||||||
### URL Normalization |
|
||||||
|
|
||||||
GitRepublic normalizes URLs before comparison to handle trailing slashes and ensure consistent matching: |
|
||||||
- Removes trailing slashes |
|
||||||
- Preserves query parameters |
|
||||||
- Handles both HTTP and HTTPS |
|
||||||
|
|
||||||
### Fallback to Kind 24 |
|
||||||
|
|
||||||
If NIP-98 authentication fails, GitRepublic can fall back to kind 24 (public message) events for relay write proofs. |
|
||||||
|
|
||||||
**Implementation**: `src/lib/services/nostr/nip98-auth.ts`, used in all git operation endpoints and API routes |
|
||||||
@ -1,352 +0,0 @@ |
|||||||
# Architecture FAQ |
|
||||||
|
|
||||||
Answers to common questions about gitrepublic-web's architecture and design decisions. |
|
||||||
|
|
||||||
## 1. Session State |
|
||||||
|
|
||||||
### Where does session state live? |
|
||||||
|
|
||||||
**Answer**: Session state lives entirely on the client (browser). There is **no server-side session storage**. |
|
||||||
|
|
||||||
- **Client-side**: User's public key (`userPubkey`) is stored in Svelte component state (`$state`) |
|
||||||
- **No server storage**: The server does not maintain session cookies, tokens, or any session database |
|
||||||
- **Stateless authentication**: Each request is authenticated independently using: |
|
||||||
- **NIP-07**: Browser extension (Alby, nos2x) for web UI operations |
|
||||||
- **NIP-98**: HTTP authentication events for git operations |
|
||||||
|
|
||||||
### Implementation Details |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Client-side state (src/routes/+page.svelte) |
|
||||||
let userPubkey = $state<string | null>(null); |
|
||||||
|
|
||||||
// Login: Get pubkey from NIP-07 extension |
|
||||||
async function login() { |
|
||||||
userPubkey = await getPublicKeyWithNIP07(); |
|
||||||
} |
|
||||||
|
|
||||||
// Logout: Clear client state |
|
||||||
function logout() { |
|
||||||
userPubkey = null; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Why stateless?** |
|
||||||
- Decentralized design: No central session authority |
|
||||||
- Scalability: No session database to manage |
|
||||||
- Privacy: Server doesn't track user sessions |
|
||||||
- Nostr-native: Uses Nostr's cryptographic authentication |
|
||||||
|
|
||||||
## 2. Session Scope |
|
||||||
|
|
||||||
### When does a session begin and end? |
|
||||||
|
|
||||||
**Answer**: Since there's no server-side session, the "session" is really just client-side authentication state: |
|
||||||
|
|
||||||
- **Begins**: When user connects their NIP-07 extension and calls `login()` |
|
||||||
- The extension provides the user's public key |
|
||||||
- This is stored in component state for the current page load |
|
||||||
|
|
||||||
- **Ends**: |
|
||||||
- When user calls `logout()` (sets `userPubkey = null`) |
|
||||||
- When browser tab/window is closed (state is lost) |
|
||||||
- When page is refreshed (state is lost unless persisted) |
|
||||||
|
|
||||||
**Note**: There's currently **no persistence** of login state across page refreshes. Users need to reconnect their NIP-07 extension on each page load. |
|
||||||
|
|
||||||
**Potential Enhancement**: Could add localStorage to persist `userPubkey` across sessions, but this is a design decision - some prefer explicit re-authentication for security. |
|
||||||
|
|
||||||
## 3. Repository Settings Storage |
|
||||||
|
|
||||||
### Where are repo settings stored? |
|
||||||
|
|
||||||
**Answer**: Repository settings are stored **entirely in Nostr events** (kind 30617, NIP-34 repo announcements). **No database is required**. |
|
||||||
|
|
||||||
### Storage Location |
|
||||||
|
|
||||||
- **Nostr Events**: All settings are stored as tags in the repository announcement event: |
|
||||||
- `name`: Repository name |
|
||||||
- `description`: Repository description |
|
||||||
- `clone`: Clone URLs (array) |
|
||||||
- `maintainers`: List of maintainer pubkeys |
|
||||||
- `private`: Privacy flag (`true`/`false`) |
|
||||||
- `relays`: Nostr relays to publish to |
|
||||||
|
|
||||||
### How It Works |
|
||||||
|
|
||||||
1. **Reading Settings**: |
|
||||||
```typescript |
|
||||||
// Fetch from Nostr relays |
|
||||||
const events = await nostrClient.fetchEvents([{ |
|
||||||
kinds: [KIND.REPO_ANNOUNCEMENT], |
|
||||||
authors: [ownerPubkey], |
|
||||||
'#d': [repoName], |
|
||||||
limit: 1 |
|
||||||
}]); |
|
||||||
|
|
||||||
// Extract settings from event tags |
|
||||||
const name = event.tags.find(t => t[0] === 'name')?.[1]; |
|
||||||
const maintainers = event.tags.filter(t => t[0] === 'maintainers').map(t => t[1]); |
|
||||||
``` |
|
||||||
|
|
||||||
2. **Updating Settings**: |
|
||||||
```typescript |
|
||||||
// Create new announcement event with updated tags |
|
||||||
const updatedEvent = { |
|
||||||
kind: KIND.REPO_ANNOUNCEMENT, |
|
||||||
pubkey: ownerPubkey, |
|
||||||
tags: [ |
|
||||||
['d', repoName], |
|
||||||
['name', newName], |
|
||||||
['maintainers', maintainer1], |
|
||||||
['maintainers', maintainer2], |
|
||||||
['private', 'true'] |
|
||||||
] |
|
||||||
}; |
|
||||||
|
|
||||||
// Sign with NIP-07 and publish to relays |
|
||||||
const signed = await signEventWithNIP07(updatedEvent); |
|
||||||
await nostrClient.publishEvent(signed, relays); |
|
||||||
``` |
|
||||||
|
|
||||||
### Benefits |
|
||||||
|
|
||||||
- **Decentralized**: Settings live on Nostr relays, not a central database |
|
||||||
- **Verifiable**: Cryptographically signed by repository owner |
|
||||||
- **Resilient**: Multiple relays store copies |
|
||||||
- **No database needed**: Simplifies deployment |
|
||||||
|
|
||||||
### Limitations |
|
||||||
|
|
||||||
- **Event replaceability**: NIP-34 announcements are replaceable (same `d` tag), so latest event wins |
|
||||||
- **Relay dependency**: Settings are only as available as the relays |
|
||||||
- **No complex queries**: Can't do complex database-style queries |
|
||||||
|
|
||||||
## 4. NIP-98 Authorization Requirements |
|
||||||
|
|
||||||
### What actions require NIP-98 authorization? |
|
||||||
|
|
||||||
**Answer**: NIP-98 is required for **git operations** (clone, push, pull) and **optional for web UI file operations**. |
|
||||||
|
|
||||||
### Required NIP-98 Operations |
|
||||||
|
|
||||||
1. **Git Push Operations** (`POST /api/git/{npub}/{repo}.git/git-receive-pack`) |
|
||||||
- **Always required** for push operations |
|
||||||
- Verifies user is repository owner or maintainer |
|
||||||
- Validates event signature, timestamp, URL, and method |
|
||||||
|
|
||||||
2. **Private Repository Clone/Fetch** (`GET /api/git/{npub}/{repo}.git/info/refs?service=git-upload-pack`) |
|
||||||
- **Required** if repository is marked as private |
|
||||||
- Verifies user has view access (owner or maintainer) |
|
||||||
- Public repos don't require authentication |
|
||||||
|
|
||||||
3. **Private Repository Fetch** (`POST /api/git/{npub}/{repo}.git/git-upload-pack`) |
|
||||||
- **Required** if repository is private |
|
||||||
- Same authentication as clone |
|
||||||
|
|
||||||
### Optional NIP-98 Operations |
|
||||||
|
|
||||||
4. **File Write Operations** (`POST /api/repos/{npub}/{repo}/file`) |
|
||||||
- **Optional**: Can use NIP-07 (browser extension) or NIP-98 |
|
||||||
- NIP-98 is useful for automated scripts or git operations |
|
||||||
- NIP-07 is more convenient for web UI |
|
||||||
|
|
||||||
### NIP-98 Verification Process |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Server verifies: |
|
||||||
1. Event signature (cryptographic verification) |
|
||||||
2. Event timestamp (within 60 seconds) |
|
||||||
3. URL matches request URL exactly |
|
||||||
4. HTTP method matches |
|
||||||
5. Payload hash matches request body (for POST) |
|
||||||
6. Pubkey is repository owner or maintainer |
|
||||||
``` |
|
||||||
|
|
||||||
### API Endpoints Summary |
|
||||||
|
|
||||||
| Endpoint | NIP-98 Required? | Notes | |
|
||||||
|----------|------------------|-------| |
|
||||||
| `GET /api/git/.../info/refs` | Only for private repos | Public repos: no auth needed | |
|
||||||
| `POST /api/git/.../git-upload-pack` | Only for private repos | Public repos: no auth needed | |
|
||||||
| `POST /api/git/.../git-receive-pack` | **Always required** | All push operations | |
|
||||||
| `POST /api/repos/.../file` | Optional | Can use NIP-07 instead | |
|
||||||
| `GET /api/repos/.../file` | No | Uses query param `userPubkey` | |
|
||||||
| `POST /api/repos/.../settings` | No | Uses NIP-07 (browser extension) | |
|
||||||
|
|
||||||
## 5. Repository Announcement Polling |
|
||||||
|
|
||||||
### Why is the server polling instead of using subscriptions? |
|
||||||
|
|
||||||
**Answer**: The server uses **polling** (every 60 seconds) instead of persistent WebSocket subscriptions for simplicity and reliability. |
|
||||||
|
|
||||||
### Current Implementation |
|
||||||
|
|
||||||
```typescript |
|
||||||
// src/lib/services/nostr/repo-polling.ts |
|
||||||
constructor( |
|
||||||
pollingInterval: number = 60000 // 1 minute default |
|
||||||
) { |
|
||||||
// Poll immediately, then every interval |
|
||||||
this.intervalId = setInterval(() => { |
|
||||||
this.poll(); |
|
||||||
}, this.pollingInterval); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Polling Schedule**: |
|
||||||
- **Frequency**: Every 60 seconds (1 minute) |
|
||||||
- **Type**: Long-running background process |
|
||||||
- **Location**: Started in `hooks.server.ts` when server starts |
|
||||||
- **Not a cron job**: Runs continuously in the Node.js process |
|
||||||
|
|
||||||
### Why Polling Instead of Subscriptions? |
|
||||||
|
|
||||||
**Advantages of Polling**: |
|
||||||
1. **Simplicity**: No need to maintain persistent WebSocket connections |
|
||||||
2. **Reliability**: If a connection drops, polling automatically retries |
|
||||||
3. **Resource efficiency**: Only connects when fetching, not maintaining long-lived connections |
|
||||||
4. **Easier error handling**: Each poll is independent |
|
||||||
|
|
||||||
**Disadvantages of Polling**: |
|
||||||
1. **Latency**: Up to 60 seconds delay before new repos are discovered |
|
||||||
2. **Relay load**: More frequent queries to relays |
|
||||||
3. **Less real-time**: Not immediate notification of new repos |
|
||||||
|
|
||||||
### Could We Use Subscriptions? |
|
||||||
|
|
||||||
**Yes, but with trade-offs**: |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Potential subscription implementation |
|
||||||
const ws = new WebSocket(relay); |
|
||||||
ws.send(JSON.stringify(['REQ', 'sub-id', { |
|
||||||
kinds: [KIND.REPO_ANNOUNCEMENT], |
|
||||||
'#clone': [domain] |
|
||||||
}])); |
|
||||||
|
|
||||||
ws.on('message', (event) => { |
|
||||||
// Handle new repo announcement immediately |
|
||||||
}); |
|
||||||
``` |
|
||||||
|
|
||||||
**Challenges**: |
|
||||||
- Need to maintain WebSocket connections to multiple relays |
|
||||||
- Handle connection drops and reconnections |
|
||||||
- More complex error handling |
|
||||||
- Higher memory usage for long-lived connections |
|
||||||
|
|
||||||
### Recommendation |
|
||||||
|
|
||||||
For most use cases, **60-second polling is acceptable**: |
|
||||||
- New repos don't need to be discovered instantly |
|
||||||
- Reduces complexity |
|
||||||
- More reliable for production |
|
||||||
|
|
||||||
For real-time requirements, subscriptions could be added as an enhancement, but polling is a solid default. |
|
||||||
|
|
||||||
## 6. Branch Protection |
|
||||||
|
|
||||||
### What is the scheme for branch protection? |
|
||||||
|
|
||||||
**Answer**: **Branch protection is not currently implemented**. This is a missing feature. |
|
||||||
|
|
||||||
### Current State |
|
||||||
|
|
||||||
**What Exists**: |
|
||||||
- Maintainers can create branches (`POST /api/repos/{npub}/{repo}/branches`) |
|
||||||
- Only maintainers can create branches (not regular users) |
|
||||||
- No protection for `main`/`master` branch |
|
||||||
|
|
||||||
**What's Missing**: |
|
||||||
- ❌ No branch protection rules |
|
||||||
- ❌ No restriction on pushing to `main`/`master` |
|
||||||
- ❌ No required pull request reviews |
|
||||||
- ❌ No required status checks |
|
||||||
- ❌ No force push restrictions |
|
||||||
|
|
||||||
### Current Authorization |
|
||||||
|
|
||||||
```typescript |
|
||||||
// src/routes/api/repos/[npub]/[repo]/branches/+server.ts |
|
||||||
const isMaintainer = await maintainerService.isMaintainer(userPubkeyHex, repoOwnerPubkey, repo); |
|
||||||
if (!isMaintainer) { |
|
||||||
return error(403, 'Only repository maintainers can create branches'); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Authorized Users**: |
|
||||||
- **Repository Owner**: Can do everything |
|
||||||
- **Maintainers**: Listed in repo announcement `maintainers` tags |
|
||||||
- Can create branches |
|
||||||
- Can push to any branch (including main) |
|
||||||
- Can write files |
|
||||||
|
|
||||||
### Proposed Branch Protection Implementation |
|
||||||
|
|
||||||
**Option 1: Nostr Events (Recommended)** |
|
||||||
- Create new event kind (e.g., 30620) for branch protection rules |
|
||||||
- Store rules in Nostr events: |
|
||||||
```json |
|
||||||
{ |
|
||||||
"kind": 30620, |
|
||||||
"tags": [ |
|
||||||
["d", "repo-name"], |
|
||||||
["branch", "main", "protected"], |
|
||||||
["branch", "main", "require-pr"], |
|
||||||
["branch", "main", "require-reviewers", "pubkey1", "pubkey2"] |
|
||||||
] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Option 2: In-Repo Configuration** |
|
||||||
- Store `.gitrepublic/branch-protection.json` in repository |
|
||||||
- Git-based, version-controlled |
|
||||||
- Requires pull request to change rules |
|
||||||
|
|
||||||
**Option 3: Server Configuration** |
|
||||||
- Store in server database (conflicts with decentralized design) |
|
||||||
- Not recommended for this architecture |
|
||||||
|
|
||||||
### Recommended Approach |
|
||||||
|
|
||||||
**Hybrid: Nostr Events + In-Repo Config** |
|
||||||
|
|
||||||
1. **Default rules**: Stored in Nostr events (kind 30620) |
|
||||||
2. **Override rules**: Can be stored in `.gitrepublic/branch-protection.json` in repo |
|
||||||
3. **Enforcement**: Server checks rules before allowing push to protected branches |
|
||||||
|
|
||||||
**Example Rules**: |
|
||||||
```json |
|
||||||
{ |
|
||||||
"protectedBranches": ["main", "master"], |
|
||||||
"requirePullRequest": true, |
|
||||||
"requireReviewers": ["pubkey1", "pubkey2"], |
|
||||||
"allowForcePush": false, |
|
||||||
"requireStatusChecks": ["ci", "lint"] |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### Implementation Priority |
|
||||||
|
|
||||||
This is a **medium-priority feature** that would enhance security and workflow, but the current system works for basic use cases where: |
|
||||||
- Owners trust their maintainers |
|
||||||
- Repositories are small teams |
|
||||||
- Formal review processes aren't needed |
|
||||||
|
|
||||||
For enterprise use cases, branch protection would be highly recommended. |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Summary |
|
||||||
|
|
||||||
| Question | Answer | |
|
||||||
|----------|--------| |
|
||||||
| **Session State** | Client-side only, no server storage | |
|
||||||
| **Session Scope** | Begins on NIP-07 login, ends on logout or page close | |
|
||||||
| **Repo Settings** | Stored in Nostr events (kind 30617), no database needed | |
|
||||||
| **NIP-98 Required** | Git push (always), private repo clone/fetch (conditional) | |
|
||||||
| **Polling Schedule** | Every 60 seconds, long-running background process | |
|
||||||
| **Branch Protection** | ✅ **Implemented** - Stored in Nostr events (kind 30620) | |
|
||||||
|
|
||||||
--- |
|
||||||
@ -1,139 +0,0 @@ |
|||||||
# Implementation Guide for git-http-backend Integration |
|
||||||
|
|
||||||
## Overview |
|
||||||
|
|
||||||
The git-http-backend integration needs to be implemented in `/src/routes/api/git/[...path]/+server.ts`. This route will handle all git HTTP operations (clone, push, pull). |
|
||||||
|
|
||||||
## URL Structure |
|
||||||
|
|
||||||
All git requests will follow this pattern: |
|
||||||
- `GET /api/git/{npub}/{repo-name}.git/info/refs?service=git-upload-pack` (clone/fetch) |
|
||||||
- `GET /api/git/{npub}/{repo-name}.git/info/refs?service=git-receive-pack` (push capability check) |
|
||||||
- `POST /api/git/{npub}/{repo-name}.git/git-upload-pack` (fetch) |
|
||||||
- `POST /api/git/{npub}/{repo-name}.git/git-receive-pack` (push) |
|
||||||
|
|
||||||
## Implementation Steps |
|
||||||
|
|
||||||
### 1. Parse Request Path |
|
||||||
|
|
||||||
Extract `npub` and `repo-name` from the path parameter: |
|
||||||
```typescript |
|
||||||
const match = params.path.match(/^([^\/]+)\/([^\/]+)\.git\/(.+)$/); |
|
||||||
if (!match) return new Response('Invalid path', { status: 400 }); |
|
||||||
const [, npub, repoName, gitPath] = match; |
|
||||||
``` |
|
||||||
|
|
||||||
### 2. Authenticate with NIP-98 |
|
||||||
|
|
||||||
For push operations, verify NIP-98 authentication: |
|
||||||
```typescript |
|
||||||
import { verifyEvent } from 'nostr-tools'; |
|
||||||
|
|
||||||
const authHeader = request.headers.get('Authorization'); |
|
||||||
if (!authHeader?.startsWith('Nostr ')) { |
|
||||||
return new Response('Unauthorized', { status: 401 }); |
|
||||||
} |
|
||||||
|
|
||||||
const nostrEvent = JSON.parse(authHeader.slice(7)); |
|
||||||
if (!verifyEvent(nostrEvent)) { |
|
||||||
return new Response('Invalid signature', { status: 401 }); |
|
||||||
} |
|
||||||
|
|
||||||
// Verify pubkey matches repo owner |
|
||||||
if (nostrEvent.pubkey !== expectedPubkey) { |
|
||||||
return new Response('Unauthorized', { status: 403 }); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### 3. Map to Git Repository Path |
|
||||||
|
|
||||||
Use `RepoManager` to get the full path: |
|
||||||
```typescript |
|
||||||
import { RepoManager } from '$lib/services/git/repo-manager.js'; |
|
||||||
|
|
||||||
const repoManager = new RepoManager(process.env.GIT_REPO_ROOT || '/repos'); |
|
||||||
const repoPath = join(repoManager.repoRoot, npub, `${repoName}.git`); |
|
||||||
|
|
||||||
if (!repoManager.repoExists(repoPath)) { |
|
||||||
return new Response('Repository not found', { status: 404 }); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### 4. Proxy to git-http-backend |
|
||||||
|
|
||||||
Execute git-http-backend as a subprocess: |
|
||||||
```typescript |
|
||||||
import { spawn } from 'child_process'; |
|
||||||
import { env } from '$env/dynamic/private'; |
|
||||||
|
|
||||||
const gitHttpBackend = '/usr/lib/git-core/git-http-backend'; // or wherever it's installed |
|
||||||
|
|
||||||
const envVars = { |
|
||||||
...process.env, |
|
||||||
GIT_PROJECT_ROOT: repoManager.repoRoot, |
|
||||||
GIT_HTTP_EXPORT_ALL: '1', |
|
||||||
REQUEST_METHOD: request.method, |
|
||||||
PATH_INFO: `/${npub}/${repoName}.git/${gitPath}`, |
|
||||||
QUERY_STRING: url.searchParams.toString(), |
|
||||||
CONTENT_TYPE: request.headers.get('Content-Type') || '', |
|
||||||
CONTENT_LENGTH: request.headers.get('Content-Length') || '0', |
|
||||||
}; |
|
||||||
|
|
||||||
const gitProcess = spawn(gitHttpBackend, [], { |
|
||||||
env: envVars, |
|
||||||
stdio: ['pipe', 'pipe', 'pipe'] |
|
||||||
}); |
|
||||||
|
|
||||||
// Pipe request body to git-http-backend |
|
||||||
if (request.body) { |
|
||||||
request.body.pipeTo(gitProcess.stdin); |
|
||||||
} |
|
||||||
|
|
||||||
// Return git-http-backend response |
|
||||||
return new Response(gitProcess.stdout, { |
|
||||||
headers: { |
|
||||||
'Content-Type': 'application/x-git-upload-pack-result', |
|
||||||
// or 'application/x-git-receive-pack-result' for push |
|
||||||
} |
|
||||||
}); |
|
||||||
``` |
|
||||||
|
|
||||||
### 5. Post-Receive Hook |
|
||||||
|
|
||||||
After successful push, sync to other remotes: |
|
||||||
```typescript |
|
||||||
// After successful git-receive-pack |
|
||||||
if (gitPath === 'git-receive-pack' && request.method === 'POST') { |
|
||||||
// Fetch NIP-34 announcement for this repo |
|
||||||
const announcement = await fetchRepoAnnouncement(npub, repoName); |
|
||||||
if (announcement) { |
|
||||||
const cloneUrls = extractCloneUrls(announcement); |
|
||||||
const otherUrls = cloneUrls.filter(url => !url.includes('git.imwald.eu')); |
|
||||||
await repoManager.syncToRemotes(repoPath, otherUrls); |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Alternative: Use a Git Server Library |
|
||||||
|
|
||||||
Instead of calling git-http-backend directly, you could use a Node.js git server library: |
|
||||||
|
|
||||||
- `isomorphic-git` with `@isomorphic-git/http-server` |
|
||||||
- `node-git-server` |
|
||||||
- Custom implementation using `dugite` or `simple-git` |
|
||||||
|
|
||||||
## Testing |
|
||||||
|
|
||||||
Test with: |
|
||||||
```bash |
|
||||||
# Clone |
|
||||||
git clone https://git.imwald.eu/{npub}/{repo-name}.git |
|
||||||
|
|
||||||
# Push (requires NIP-98 auth) |
|
||||||
git push origin main |
|
||||||
``` |
|
||||||
|
|
||||||
For NIP-98 authentication, you'll need a git credential helper that: |
|
||||||
1. Intercepts git HTTP requests |
|
||||||
2. Signs a Nostr event with the user's key |
|
||||||
3. Adds `Authorization: Nostr {event}` header |
|
||||||
@ -1,123 +0,0 @@ |
|||||||
# Logging Strategy |
|
||||||
|
|
||||||
## Current State |
|
||||||
|
|
||||||
The application currently uses `console.log`, `console.error`, and `console.warn` for logging, with: |
|
||||||
- Context prefixes (e.g., `[Fork] [repo1 → repo2]`) |
|
||||||
- Security sanitization (truncated pubkeys, redacted private keys) |
|
||||||
- Structured audit logging via `AuditLogger` service |
|
||||||
|
|
||||||
## Recommendation: Use Pino for Production |
|
||||||
|
|
||||||
### Why Pino? |
|
||||||
|
|
||||||
1. **Performance**: Extremely fast (async logging, minimal overhead) |
|
||||||
2. **Structured Logging**: JSON output perfect for ELK/Kibana/Logstash |
|
||||||
3. **Log Levels**: Built-in severity levels (trace, debug, info, warn, error, fatal) |
|
||||||
4. **Child Loggers**: Context propagation (request IDs, user IDs, etc.) |
|
||||||
5. **Ecosystem**: Excellent Kubernetes/Docker support |
|
||||||
6. **Small Bundle**: ~4KB minified |
|
||||||
|
|
||||||
### Implementation Plan |
|
||||||
|
|
||||||
#### 1. Install Pino |
|
||||||
|
|
||||||
```bash |
|
||||||
npm install pino pino-pretty |
|
||||||
``` |
|
||||||
|
|
||||||
#### 2. Create Logger Service |
|
||||||
|
|
||||||
```typescript |
|
||||||
// src/lib/services/logger.ts |
|
||||||
import pino from 'pino'; |
|
||||||
|
|
||||||
const logger = pino({ |
|
||||||
level: process.env.LOG_LEVEL || 'info', |
|
||||||
...(process.env.NODE_ENV === 'development' && { |
|
||||||
transport: { |
|
||||||
target: 'pino-pretty', |
|
||||||
options: { |
|
||||||
colorize: true, |
|
||||||
translateTime: 'HH:MM:ss Z', |
|
||||||
ignore: 'pid,hostname' |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
}); |
|
||||||
|
|
||||||
export default logger; |
|
||||||
``` |
|
||||||
|
|
||||||
#### 3. Replace Console Logs |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Before |
|
||||||
console.log(`[Fork] ${context} Starting fork process`); |
|
||||||
|
|
||||||
// After |
|
||||||
import logger from '$lib/services/logger.js'; |
|
||||||
logger.info({ context, repo: `${npub}/${repo}` }, 'Starting fork process'); |
|
||||||
``` |
|
||||||
|
|
||||||
#### 4. Structured Context |
|
||||||
|
|
||||||
```typescript |
|
||||||
// Create child logger with context |
|
||||||
const forkLogger = logger.child({ |
|
||||||
operation: 'fork', |
|
||||||
originalRepo: `${npub}/${repo}`, |
|
||||||
forkRepo: `${userNpub}/${forkRepoName}` |
|
||||||
}); |
|
||||||
|
|
||||||
forkLogger.info('Starting fork process'); |
|
||||||
forkLogger.info({ relayCount: combinedRelays.length }, 'Using relays'); |
|
||||||
forkLogger.error({ error: sanitizedError }, 'Fork failed'); |
|
||||||
``` |
|
||||||
|
|
||||||
#### 5. ELK/Kibana Integration |
|
||||||
|
|
||||||
Pino outputs JSON by default, which works perfectly with: |
|
||||||
- **Filebeat**: Collect logs from files |
|
||||||
- **Logstash**: Parse and enrich logs |
|
||||||
- **Elasticsearch**: Store and index logs |
|
||||||
- **Kibana**: Visualize and search logs |
|
||||||
|
|
||||||
Example log output: |
|
||||||
```json |
|
||||||
{ |
|
||||||
"level": 30, |
|
||||||
"time": 1703123456789, |
|
||||||
"pid": 12345, |
|
||||||
"hostname": "gitrepublic-1", |
|
||||||
"operation": "fork", |
|
||||||
"originalRepo": "npub1.../repo1", |
|
||||||
"forkRepo": "npub2.../repo2", |
|
||||||
"msg": "Starting fork process" |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### Migration Strategy |
|
||||||
|
|
||||||
1. **Phase 1**: Install Pino, create logger service |
|
||||||
2. **Phase 2**: Replace console logs in critical paths (fork, file operations, git operations) |
|
||||||
3. **Phase 3**: Replace remaining console logs |
|
||||||
4. **Phase 4**: Add request ID middleware for request tracing |
|
||||||
5. **Phase 5**: Configure log aggregation (Filebeat → ELK) |
|
||||||
|
|
||||||
### Benefits |
|
||||||
|
|
||||||
- **Searchability**: Query logs by operation, user, repo, etc. |
|
||||||
- **Alerting**: Set up alerts for error rates, failed operations |
|
||||||
- **Performance Monitoring**: Track operation durations |
|
||||||
- **Security Auditing**: Enhanced audit trail with structured data |
|
||||||
- **Debugging**: Easier to trace requests across services |
|
||||||
|
|
||||||
### Alternative: Winston |
|
||||||
|
|
||||||
Winston is also popular but: |
|
||||||
- Slower than Pino |
|
||||||
- More configuration overhead |
|
||||||
- Better for complex transports (multiple outputs) |
|
||||||
|
|
||||||
**Recommendation**: Use Pino for this project. |
|
||||||
@ -1,402 +0,0 @@ |
|||||||
# Messaging Forwarding Feature |
|
||||||
|
|
||||||
This feature allows users with **unlimited access** to forward Nostr events to messaging platforms (Telegram, SimpleX, Email) and Git hosting platforms (GitHub, GitLab, Gitea, Codeberg, Forgejo) when they publish events. |
|
||||||
|
|
||||||
## Security Architecture |
|
||||||
|
|
||||||
### Multi-Layer Security |
|
||||||
|
|
||||||
1. **Encrypted Salt Storage**: Each user's salt is encrypted with a separate key (`MESSAGING_SALT_ENCRYPTION_KEY`) |
|
||||||
2. **HMAC Lookup Keys**: User pubkeys are hashed with HMAC before being used as database keys |
|
||||||
3. **Rate Limiting**: Decryption attempts are rate-limited (10 attempts per 15 minutes) |
|
||||||
4. **Per-User Key Derivation**: Encryption keys derived from master key + pubkey + salt |
|
||||||
5. **AES-256-GCM**: Authenticated encryption with random IV per encryption |
|
||||||
|
|
||||||
### Threat Mitigation |
|
||||||
|
|
||||||
- **Database Compromise**: All data encrypted at rest with per-user keys |
|
||||||
- **Key Leakage**: Per-user key derivation means master key leak doesn't expose all users |
|
||||||
- **Brute Force**: Rate limiting prevents rapid decryption attempts |
|
||||||
- **Lookup Attacks**: HMAC hashing prevents pubkey enumeration |
|
||||||
|
|
||||||
## Environment Variables |
|
||||||
|
|
||||||
### Required for Encryption |
|
||||||
|
|
||||||
```bash |
|
||||||
# Master encryption key (256-bit hex, generate with: openssl rand -hex 32) |
|
||||||
MESSAGING_PREFS_ENCRYPTION_KEY=<256-bit-hex> |
|
||||||
|
|
||||||
# Salt encryption key (256-bit hex, generate with: openssl rand -hex 32) |
|
||||||
MESSAGING_SALT_ENCRYPTION_KEY=<256-bit-hex> |
|
||||||
|
|
||||||
# HMAC secret for lookup key hashing (256-bit hex, generate with: openssl rand -hex 32) |
|
||||||
MESSAGING_LOOKUP_SECRET=<256-bit-hex> |
|
||||||
``` |
|
||||||
|
|
||||||
### Optional for Messaging Platforms |
|
||||||
|
|
||||||
```bash |
|
||||||
# Telegram Bot Configuration |
|
||||||
TELEGRAM_BOT_TOKEN=<bot-token> |
|
||||||
TELEGRAM_ENABLED=true |
|
||||||
|
|
||||||
# Email SMTP Configuration |
|
||||||
SMTP_HOST=<smtp-host> |
|
||||||
SMTP_PORT=587 |
|
||||||
SMTP_USER=<smtp-username> |
|
||||||
SMTP_PASSWORD=<smtp-password> |
|
||||||
SMTP_FROM_ADDRESS=<from-email> |
|
||||||
SMTP_FROM_NAME=GitRepublic |
|
||||||
EMAIL_ENABLED=true |
|
||||||
|
|
||||||
# OR use SMTP API (alternative to direct SMTP) |
|
||||||
SMTP_API_URL=<smtp-api-url> |
|
||||||
SMTP_API_KEY=<api-key> |
|
||||||
|
|
||||||
# SimpleX API Configuration |
|
||||||
SIMPLEX_API_URL=<simplex-api-url> |
|
||||||
SIMPLEX_API_KEY=<api-key> |
|
||||||
SIMPLEX_ENABLED=true |
|
||||||
|
|
||||||
# Git Platforms Forwarding Configuration |
|
||||||
GIT_PLATFORMS_ENABLED=true |
|
||||||
``` |
|
||||||
|
|
||||||
## Setup |
|
||||||
|
|
||||||
### 1. Generate Encryption Keys |
|
||||||
|
|
||||||
```bash |
|
||||||
# Generate all three required keys |
|
||||||
openssl rand -hex 32 # For MESSAGING_PREFS_ENCRYPTION_KEY |
|
||||||
openssl rand -hex 32 # For MESSAGING_SALT_ENCRYPTION_KEY |
|
||||||
openssl rand -hex 32 # For MESSAGING_LOOKUP_SECRET |
|
||||||
``` |
|
||||||
|
|
||||||
### 2. Store Keys Securely |
|
||||||
|
|
||||||
**Development:** |
|
||||||
- Store in `.env` file (never commit to git) |
|
||||||
|
|
||||||
**Production:** |
|
||||||
- Use secret management service (AWS Secrets Manager, HashiCorp Vault, etc.) |
|
||||||
- Or environment variables in deployment platform |
|
||||||
- Consider using Hardware Security Modules (HSM) for maximum security |
|
||||||
|
|
||||||
### 3. Configure Messaging Platforms |
|
||||||
|
|
||||||
#### Telegram |
|
||||||
1. Create a bot with [@BotFather](https://t.me/botfather) |
|
||||||
2. Get bot token |
|
||||||
3. Users will provide their chat ID when configuring preferences |
|
||||||
|
|
||||||
#### Email |
|
||||||
1. Configure SMTP settings (host, port, credentials) |
|
||||||
2. Or use an SMTP API service |
|
||||||
3. Users will provide their email addresses (to/cc) |
|
||||||
|
|
||||||
#### SimpleX |
|
||||||
1. Set up SimpleX Chat API |
|
||||||
2. Configure API URL and key |
|
||||||
3. Users will provide their contact ID |
|
||||||
|
|
||||||
#### Git Platforms (GitHub, GitLab, Gitea, Codeberg, Forgejo) |
|
||||||
1. Users create Personal Access Tokens with appropriate scopes |
|
||||||
2. Users provide platform, username/org, repository name, and token |
|
||||||
3. Events will be forwarded as issues or PRs on the selected platform |
|
||||||
4. Supports self-hosted instances via custom API URL |
|
||||||
|
|
||||||
## User Flow |
|
||||||
|
|
||||||
### 1. User Saves Preferences (Client-Side) |
|
||||||
|
|
||||||
```typescript |
|
||||||
// User encrypts preferences to self on Nostr (kind 30078) |
|
||||||
const encryptedContent = await window.nostr.nip44.encrypt( |
|
||||||
userPubkey, |
|
||||||
JSON.stringify(preferences) |
|
||||||
); |
|
||||||
|
|
||||||
// Publish to Nostr (backup/sync) |
|
||||||
const event = { |
|
||||||
kind: 30078, |
|
||||||
pubkey: userPubkey, |
|
||||||
tags: [['d', 'gitrepublic-messaging'], ['enabled', 'true']], |
|
||||||
content: encryptedContent |
|
||||||
}; |
|
||||||
|
|
||||||
// Sign and publish |
|
||||||
const signedEvent = await signEventWithNIP07(event); |
|
||||||
await nostrClient.publishEvent(signedEvent); |
|
||||||
|
|
||||||
// Send decrypted copy to server (over HTTPS) |
|
||||||
await fetch('/api/user/messaging-preferences', { |
|
||||||
method: 'POST', |
|
||||||
body: JSON.stringify({ |
|
||||||
preferences, |
|
||||||
proofEvent: signedEvent |
|
||||||
}) |
|
||||||
}); |
|
||||||
``` |
|
||||||
|
|
||||||
### 2. Server Stores Securely |
|
||||||
|
|
||||||
- Verifies proof event signature |
|
||||||
- Checks user has unlimited access |
|
||||||
- Generates random salt |
|
||||||
- Encrypts salt with `MESSAGING_SALT_ENCRYPTION_KEY` |
|
||||||
- Derives per-user encryption key |
|
||||||
- Encrypts preferences with AES-256-GCM |
|
||||||
- Stores using HMAC lookup key |
|
||||||
|
|
||||||
### 3. Event Forwarding |
|
||||||
|
|
||||||
When user publishes an event (issue, PR, highlight): |
|
||||||
1. Server checks user has unlimited access |
|
||||||
2. Retrieves encrypted preferences |
|
||||||
3. Decrypts (with rate limiting) |
|
||||||
4. Checks if forwarding enabled and event kind matches |
|
||||||
5. Forwards to configured platforms |
|
||||||
|
|
||||||
## API Endpoints |
|
||||||
|
|
||||||
### POST `/api/user/messaging-preferences` |
|
||||||
|
|
||||||
Save messaging preferences. |
|
||||||
|
|
||||||
**Request:** |
|
||||||
```json |
|
||||||
{ |
|
||||||
"preferences": { |
|
||||||
"telegram": "@username", |
|
||||||
"simplex": "contact-id", |
|
||||||
"email": { |
|
||||||
"to": ["user@example.com"], |
|
||||||
"cc": ["cc@example.com"] |
|
||||||
}, |
|
||||||
"gitPlatforms": [ |
|
||||||
{ |
|
||||||
"platform": "github", |
|
||||||
"owner": "username", |
|
||||||
"repo": "repository-name", |
|
||||||
"token": "ghp_xxxxxxxxxxxx" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"platform": "gitlab", |
|
||||||
"owner": "username", |
|
||||||
"repo": "repository-name", |
|
||||||
"token": "glpat-xxxxxxxxxxxx" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"platform": "codeberg", |
|
||||||
"owner": "username", |
|
||||||
"repo": "repository-name", |
|
||||||
"token": "xxxxxxxxxxxx" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"platform": "onedev", |
|
||||||
"owner": "project-path", |
|
||||||
"repo": "repository-name", |
|
||||||
"token": "xxxxxxxxxxxx", |
|
||||||
"apiUrl": "https://your-onedev-instance.com" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"platform": "custom", |
|
||||||
"owner": "username", |
|
||||||
"repo": "repository-name", |
|
||||||
"token": "xxxxxxxxxxxx", |
|
||||||
"apiUrl": "https://your-git-instance.com/api/v1" |
|
||||||
} |
|
||||||
], |
|
||||||
"enabled": true, |
|
||||||
"notifyOn": ["1621", "1618"] |
|
||||||
}, |
|
||||||
"proofEvent": { /* Signed Nostr event (kind 30078) */ } |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Response:** |
|
||||||
```json |
|
||||||
{ |
|
||||||
"success": true |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### GET `/api/user/messaging-preferences` |
|
||||||
|
|
||||||
Get preferences status (without decrypting). |
|
||||||
|
|
||||||
**Response:** |
|
||||||
```json |
|
||||||
{ |
|
||||||
"configured": true, |
|
||||||
"rateLimit": { |
|
||||||
"remaining": 10, |
|
||||||
"resetAt": null |
|
||||||
} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
### DELETE `/api/user/messaging-preferences` |
|
||||||
|
|
||||||
Delete messaging preferences. |
|
||||||
|
|
||||||
**Response:** |
|
||||||
```json |
|
||||||
{ |
|
||||||
"success": true |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Security Best Practices |
|
||||||
|
|
||||||
1. **Key Management** |
|
||||||
- Never commit keys to git |
|
||||||
- Rotate keys periodically |
|
||||||
- Use secret management services in production |
|
||||||
- Consider HSM for maximum security |
|
||||||
|
|
||||||
2. **Monitoring** |
|
||||||
- Monitor rate limit violations |
|
||||||
- Alert on decryption failures |
|
||||||
- Audit log all preference changes |
|
||||||
|
|
||||||
3. **Access Control** |
|
||||||
- Only users with unlimited access can use this feature |
|
||||||
- Requires valid signed Nostr event proof |
|
||||||
- Server verifies all inputs |
|
||||||
|
|
||||||
4. **Data Protection** |
|
||||||
- All data encrypted at rest |
|
||||||
- Per-user key derivation |
|
||||||
- HMAC lookup keys |
|
||||||
- Rate limiting on decryption |
|
||||||
|
|
||||||
## Troubleshooting |
|
||||||
|
|
||||||
### "Decryption rate limit exceeded" |
|
||||||
|
|
||||||
User has exceeded 10 decryption attempts in 15 minutes. Wait for the window to reset. |
|
||||||
|
|
||||||
### "Messaging forwarding requires unlimited access" |
|
||||||
|
|
||||||
User must have relay write access (unlimited level) to use this feature. |
|
||||||
|
|
||||||
### "Failed to forward event" |
|
||||||
|
|
||||||
Check: |
|
||||||
- Messaging platform API credentials |
|
||||||
- Network connectivity |
|
||||||
- Platform-specific error logs |
|
||||||
|
|
||||||
## Email Setup |
|
||||||
|
|
||||||
### Option 1: Direct SMTP |
|
||||||
|
|
||||||
Install nodemailer: |
|
||||||
```bash |
|
||||||
npm install nodemailer |
|
||||||
``` |
|
||||||
|
|
||||||
Configure environment variables: |
|
||||||
```bash |
|
||||||
SMTP_HOST=smtp.gmail.com |
|
||||||
SMTP_PORT=587 |
|
||||||
SMTP_USER=your-email@gmail.com |
|
||||||
SMTP_PASSWORD=your-app-password |
|
||||||
SMTP_FROM_ADDRESS=noreply@yourdomain.com |
|
||||||
SMTP_FROM_NAME=GitRepublic |
|
||||||
EMAIL_ENABLED=true |
|
||||||
``` |
|
||||||
|
|
||||||
### Option 2: SMTP API |
|
||||||
|
|
||||||
Use an SMTP API service (e.g., SendGrid, Mailgun, AWS SES): |
|
||||||
```bash |
|
||||||
SMTP_API_URL=https://api.sendgrid.com/v3/mail/send |
|
||||||
SMTP_API_KEY=your-api-key |
|
||||||
EMAIL_ENABLED=true |
|
||||||
``` |
|
||||||
|
|
||||||
## Git Platforms Setup |
|
||||||
|
|
||||||
### Supported Platforms |
|
||||||
|
|
||||||
- **GitHub** (`github`) - github.com |
|
||||||
- **GitLab** (`gitlab`) - gitlab.com (also supports self-hosted with apiUrl) |
|
||||||
- **Gitea** (`gitea`) - Self-hosted instances (defaults to codeberg.org if apiUrl not provided) |
|
||||||
- **Codeberg** (`codeberg`) - codeberg.org (uses Gitea API) |
|
||||||
- **Forgejo** (`forgejo`) - Self-hosted instances (defaults to forgejo.org if apiUrl not provided) |
|
||||||
- **OneDev** (`onedev`) - Self-hosted instances (requires apiUrl) |
|
||||||
- **Custom** (`custom`) - Any Gitea-compatible API with custom URL (requires apiUrl) |
|
||||||
|
|
||||||
### Creating Personal Access Tokens |
|
||||||
|
|
||||||
#### GitHub |
|
||||||
1. Go to Settings → Developer settings → Personal access tokens → Tokens (classic) |
|
||||||
2. Click "Generate new token (classic)" |
|
||||||
3. Select `repo` scope |
|
||||||
4. Generate and copy token |
|
||||||
|
|
||||||
#### GitLab |
|
||||||
1. Go to Settings → Access Tokens |
|
||||||
2. Create token with `api` scope |
|
||||||
3. Generate and copy token |
|
||||||
|
|
||||||
#### Gitea/Codeberg/Forgejo |
|
||||||
1. Go to Settings → Applications → Generate New Token |
|
||||||
2. Select `repo` scope |
|
||||||
3. Generate and copy token |
|
||||||
|
|
||||||
#### OneDev |
|
||||||
1. Go to User Settings → Access Tokens |
|
||||||
2. Create a new access token |
|
||||||
3. Select appropriate scopes (typically `write:issue` and `write:pull-request`) |
|
||||||
4. Generate and copy token |
|
||||||
5. **Note**: OneDev is self-hosted, so you must provide the `apiUrl` (e.g., `https://your-onedev-instance.com`) |
|
||||||
|
|
||||||
### User Configuration |
|
||||||
|
|
||||||
Users provide: |
|
||||||
- **Platform**: One of `github`, `gitlab`, `gitea`, `codeberg`, `forgejo`, `onedev`, or `custom` |
|
||||||
- **Owner**: Username or organization name (project path for OneDev) |
|
||||||
- **Repo**: Repository name (project name for OneDev) |
|
||||||
- **Token**: Personal access token (stored encrypted) |
|
||||||
- **API URL**: |
|
||||||
- **Required** for: `onedev`, `custom` |
|
||||||
- **Optional** for: `gitea`, `forgejo`, `gitlab` (use for self-hosted instances) |
|
||||||
- **Not used** for: `github`, `codeberg` (always use hosted instances) |
|
||||||
- Format: Base URL of the instance (e.g., `https://your-gitea-instance.com/api/v1` or `https://your-onedev-instance.com`) |
|
||||||
|
|
||||||
### Event Mapping |
|
||||||
|
|
||||||
- **Nostr Issues (kind 1621)** → Platform Issues |
|
||||||
- **Nostr PRs (kind 1618)** → Platform Pull Requests/Merge Requests (if branch info available) or Issues with PR label |
|
||||||
- **Other events** → Platform Issues with event kind label |
|
||||||
|
|
||||||
### Platform-Specific Notes |
|
||||||
|
|
||||||
- **GitHub**: Uses `body` field and `head`/`base` for PRs. Always uses `https://api.github.com` |
|
||||||
- **GitLab**: Uses `description` field instead of `body`, and `source_branch`/`target_branch` for PRs. Defaults to `https://gitlab.com/api/v4`, but supports self-hosted with `apiUrl` |
|
||||||
- **Gitea**: Compatible with GitHub API format. Defaults to `https://codeberg.org/api/v1` (Codeberg), but supports self-hosted instances with `apiUrl` (e.g., `https://your-gitea-instance.com/api/v1`) |
|
||||||
- **Codeberg**: Uses Gitea API format. Always uses `https://codeberg.org/api/v1` |
|
||||||
- **Forgejo**: Compatible with GitHub API format. Defaults to `https://forgejo.org/api/v1`, but supports self-hosted instances with `apiUrl` (e.g., `https://your-forgejo-instance.com/api/v1`) |
|
||||||
- **OneDev**: Uses `description` field and `source_branch`/`target_branch` for PRs. **Requires** `apiUrl` (self-hosted only). API endpoints: `/api/projects/{owner}/{repo}/issues` and `/api/projects/{owner}/{repo}/pull-requests` |
|
||||||
- **Custom**: Must provide `apiUrl` pointing to Gitea-compatible API (assumes GitHub/Gitea API format) |
|
||||||
|
|
||||||
### Security Note |
|
||||||
|
|
||||||
All tokens are stored encrypted in the database. Users should: |
|
||||||
- Use tokens with minimal required scopes |
|
||||||
- Rotate tokens periodically |
|
||||||
- Revoke tokens if compromised |
|
||||||
|
|
||||||
## Future Enhancements |
|
||||||
|
|
||||||
- [ ] Support for more messaging platforms |
|
||||||
- [ ] User-configurable message templates |
|
||||||
- [ ] Webhook support |
|
||||||
- [ ] Encrypted preferences sync across devices |
|
||||||
- [ ] Per-repository forwarding rules |
|
||||||
- [ ] HTML email templates |
|
||||||
@ -1,117 +0,0 @@ |
|||||||
# NIP Compliance and Documentation Index |
|
||||||
|
|
||||||
This document serves as an index to all Nostr Improvement Proposals (NIPs) used by GitRepublic and their implementation details. |
|
||||||
|
|
||||||
## Standard NIPs |
|
||||||
|
|
||||||
GitRepublic implements the following standard NIPs: |
|
||||||
|
|
||||||
### Core Protocol |
|
||||||
|
|
||||||
- **[NIP-01: Basic Protocol Flow](01.md)** - Event structure, signatures, and client-relay communication |
|
||||||
- Foundation for all Nostr events |
|
||||||
|
|
||||||
### Authentication & Identity |
|
||||||
|
|
||||||
- **[NIP-02: Contact List](02.md)** - Contact list (kind 3) |
|
||||||
- Used for repository filtering ("Show only my repos and those of my contacts") |
|
||||||
- Fallback for relay discovery |
|
||||||
|
|
||||||
- **[NIP-07: Browser Extension Authentication](07.md)** - `window.nostr` capability |
|
||||||
- Primary authentication method for GitRepublic |
|
||||||
- Used for signing all repository-related events |
|
||||||
|
|
||||||
- **[NIP-19: bech32-encoded Entities](19.md)** - bech32 encoding (npub, nsec, note, nevent, naddr) |
|
||||||
- User-friendly display of pubkeys and event references |
|
||||||
- Used throughout the UI for repository URLs and search |
|
||||||
|
|
||||||
- **[NIP-98: HTTP Authentication](98.md)** - HTTP auth events (kind 27235) |
|
||||||
- Authenticates git operations (push, pull, clone) |
|
||||||
- Authenticates API requests |
|
||||||
|
|
||||||
### Event Management |
|
||||||
|
|
||||||
- **[NIP-09: Event Deletion](09.md)** - Deletion requests (kind 5) |
|
||||||
- Used for cleaning up failed fork attempts |
|
||||||
|
|
||||||
- **[NIP-10: Event References](10.md)** - Event references (e, p tags) |
|
||||||
- Used in patch series for threading patches |
|
||||||
|
|
||||||
- **[NIP-22: Comments](22.md)** - Comment events (kind 1111) |
|
||||||
- Threaded discussions on PRs, issues, and patches |
|
||||||
|
|
||||||
### Git Collaboration |
|
||||||
|
|
||||||
- **[NIP-34: Git Repository Announcements](34.md)** - Git collaboration on Nostr |
|
||||||
- **30617**: Repository announcements |
|
||||||
- **30618**: Repository state |
|
||||||
- **1617**: Patches |
|
||||||
- **1618**: Pull requests |
|
||||||
- **1619**: Pull request updates |
|
||||||
- **1621**: Issues |
|
||||||
- **1630-1633**: Status events (Open, Applied/Merged, Closed, Draft) |
|
||||||
|
|
||||||
### Relay & Discovery |
|
||||||
|
|
||||||
- **[NIP-65: Relay List Metadata](65.md)** - Relay list (kind 10002) |
|
||||||
- Discovers user's preferred relays for publishing events |
|
||||||
|
|
||||||
### Content Features |
|
||||||
|
|
||||||
- **[NIP-84: Highlights](84.md)** - Highlight events (kind 9802) |
|
||||||
- Code selection and review features |
|
||||||
- Extended with file/line tags for code context |
|
||||||
|
|
||||||
## Custom Event Kinds |
|
||||||
|
|
||||||
GitRepublic uses custom event kinds not defined in any standard NIP: |
|
||||||
|
|
||||||
- **[Custom Event Kinds](CustomKinds.md)** |
|
||||||
- **1640**: Commit Signature - Cryptographically sign git commits |
|
||||||
- **1641**: Ownership Transfer - Transfer repository ownership (immutable chain) |
|
||||||
- **30620**: Branch Protection - Enforce branch protection rules |
|
||||||
|
|
||||||
## Quick Reference |
|
||||||
|
|
||||||
### Event Kinds Used |
|
||||||
|
|
||||||
| Kind | Name | NIP | Replaceable | Documentation | |
|
||||||
|------|------|-----|-------------|---------------| |
|
||||||
| 1 | Text Note | NIP-01 | No | [01.md](01.md) | |
|
||||||
| 3 | Contact List | NIP-02 | Yes | [02.md](02.md) | |
|
||||||
| 5 | Deletion Request | NIP-09 | No | [09.md](09.md) | |
|
||||||
| 1111 | Comment | NIP-22 | No | [22.md](22.md) | |
|
||||||
| 1617 | Patch | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1618 | Pull Request | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1619 | Pull Request Update | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1621 | Issue | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1630 | Status Open | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1631 | Status Applied | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1632 | Status Closed | NIP-34 | No | [34.md](34.md) | |
|
||||||
| 1633 | Status Draft | NIP-34 | No | [34.md](34.md) | |
|
||||||
| **1640** | **Commit Signature** | **Custom** | **No** | **[CustomKinds.md](CustomKinds.md)** | |
|
||||||
| **1641** | **Ownership Transfer** | **Custom** | **No** | **[CustomKinds.md](CustomKinds.md)** | |
|
||||||
| 30617 | Repo Announcement | NIP-34 | Yes | [34.md](34.md) | |
|
||||||
| 30618 | Repo State | NIP-34 | Yes | [34.md](34.md) | |
|
||||||
| **30620** | **Branch Protection** | **Custom** | **Yes** | **[CustomKinds.md](CustomKinds.md)** | |
|
||||||
| 9802 | Highlight | NIP-84 | No | [84.md](84.md) | |
|
||||||
| 10002 | Relay List | NIP-65 | Yes | [65.md](65.md) | |
|
||||||
| 27235 | HTTP Auth | NIP-98 | No | [98.md](98.md) | |
|
||||||
|
|
||||||
## Implementation Status |
|
||||||
|
|
||||||
All listed NIPs are **fully implemented** and compliant with their specifications. Each NIP document includes a "GitRepublic Usage" section describing how the NIP is used in this application. |
|
||||||
|
|
||||||
## Compliance Verification |
|
||||||
|
|
||||||
For detailed compliance verification and implementation notes, see the individual NIP documents linked above. Each document includes: |
|
||||||
- The original NIP specification |
|
||||||
- GitRepublic-specific usage documentation |
|
||||||
- Implementation details and code references |
|
||||||
|
|
||||||
## See Also |
|
||||||
|
|
||||||
- [NIP-34 Documentation](34.md) - Core git collaboration features |
|
||||||
- [Custom Event Kinds](CustomKinds.md) - GitRepublic-specific event kinds |
|
||||||
- [Architecture FAQ](ARCHITECTURE_FAQ.md) - System architecture overview |
|
||||||
- [Implementation Details](IMPLEMENTATION.md) - Technical implementation notes |
|
||||||
@ -1,206 +0,0 @@ |
|||||||
# Publishing GitRepublic CLI to npm |
|
||||||
|
|
||||||
## Prerequisites |
|
||||||
|
|
||||||
1. **Create npm account** (if you don't have one): |
|
||||||
- Visit https://www.npmjs.com/signup |
|
||||||
- Or run: `npm adduser` |
|
||||||
|
|
||||||
2. **Enable Two-Factor Authentication (2FA) or Create Access Token**: |
|
||||||
|
|
||||||
npm requires either TOTP/SMS 2FA or a granular access token to publish packages. Biometric authentication (fingerprint) alone is not sufficient for CLI publishing. |
|
||||||
|
|
||||||
**Option A: Enable TOTP/SMS 2FA** (Recommended for regular use): |
|
||||||
- Go to https://www.npmjs.com/settings/[your-username]/security |
|
||||||
- Look for "Two-factor authentication" section |
|
||||||
- If you only see biometric options, you may need to: |
|
||||||
1. Check if there's an "Advanced" or "More options" link |
|
||||||
2. Look for "Authenticator app" or "SMS" options |
|
||||||
3. Some accounts may need to disable biometric first to see other options |
|
||||||
- **If using TOTP app** (recommended): |
|
||||||
- You'll see a QR code on your computer screen |
|
||||||
- Scan it with your phone's authenticator app (Google Authenticator, Authy, 1Password, etc.) |
|
||||||
- The app will generate 6-digit codes that you'll use when logging in |
|
||||||
- **If using SMS**: |
|
||||||
- Enter your phone number |
|
||||||
- You'll receive codes via text message |
|
||||||
- Follow the setup instructions to complete the setup |
|
||||||
|
|
||||||
**Option B: Create Granular Access Token** (Alternative if 2FA setup is difficult): |
|
||||||
- Go to https://www.npmjs.com/settings/[your-username]/tokens |
|
||||||
- Click "Generate New Token" |
|
||||||
- Choose "Granular Access Token" |
|
||||||
- Set permissions: Select "Publish" for the package(s) you want to publish |
|
||||||
- Enable "Bypass 2FA" option (this is required for publishing) |
|
||||||
- Copy the token (you'll only see it once!) |
|
||||||
- Use it for authentication: |
|
||||||
```bash |
|
||||||
npm config set //registry.npmjs.org/:_authToken YOUR_TOKEN_HERE |
|
||||||
``` |
|
||||||
- Or set it as an environment variable: |
|
||||||
```bash |
|
||||||
export NPM_TOKEN=YOUR_TOKEN_HERE |
|
||||||
``` |
|
||||||
|
|
||||||
3. **Login to npm from your computer** (if using Option A): |
|
||||||
```bash |
|
||||||
npm logout # Log out first if already logged in |
|
||||||
npm login |
|
||||||
``` |
|
||||||
- Enter your username, password, and email |
|
||||||
- If 2FA is enabled, you'll be prompted for the authentication code |
|
||||||
- **Get the code from your phone's authenticator app** (if using TOTP) or check your SMS (if using SMS) |
|
||||||
- Enter the 6-digit code when prompted |
|
||||||
|
|
||||||
3. **Check if package name is available**: |
|
||||||
```bash |
|
||||||
npm view gitrepublic-cli |
|
||||||
``` |
|
||||||
If it returns 404, the name is available. If it shows package info, the name is taken. |
|
||||||
|
|
||||||
## Publishing Steps |
|
||||||
|
|
||||||
### 1. Update version (if needed) |
|
||||||
|
|
||||||
```bash |
|
||||||
# Patch version (1.0.0 -> 1.0.1) |
|
||||||
npm version patch |
|
||||||
|
|
||||||
# Minor version (1.0.0 -> 1.1.0) |
|
||||||
npm version minor |
|
||||||
|
|
||||||
# Major version (1.0.0 -> 2.0.0) |
|
||||||
npm version major |
|
||||||
``` |
|
||||||
|
|
||||||
Or manually edit `package.json` and update the version field. |
|
||||||
|
|
||||||
### 2. Verify package contents |
|
||||||
|
|
||||||
```bash |
|
||||||
# See what will be published |
|
||||||
npm pack --dry-run |
|
||||||
``` |
|
||||||
|
|
||||||
This shows the files that will be included (based on `files` field in package.json). |
|
||||||
|
|
||||||
### 3. Test the package locally |
|
||||||
|
|
||||||
```bash |
|
||||||
# Pack the package |
|
||||||
npm pack |
|
||||||
|
|
||||||
# Install it locally to test |
|
||||||
npm install -g ./gitrepublic-cli-1.0.0.tgz |
|
||||||
|
|
||||||
# Test the commands |
|
||||||
gitrepublic-path --credential |
|
||||||
gitrepublic-path --hook |
|
||||||
``` |
|
||||||
|
|
||||||
### 4. Publish to npm |
|
||||||
|
|
||||||
```bash |
|
||||||
cd gitrepublic-cli |
|
||||||
npm publish |
|
||||||
``` |
|
||||||
|
|
||||||
For scoped packages (if you want `@your-org/gitrepublic-cli`): |
|
||||||
```bash |
|
||||||
npm publish --access public |
|
||||||
``` |
|
||||||
|
|
||||||
### 5. Verify publication |
|
||||||
|
|
||||||
```bash |
|
||||||
# Check on npm website |
|
||||||
# Visit: https://www.npmjs.com/package/gitrepublic-cli |
|
||||||
|
|
||||||
# Or via command line |
|
||||||
npm view gitrepublic-cli |
|
||||||
``` |
|
||||||
|
|
||||||
## After Publishing |
|
||||||
|
|
||||||
Users can now install via: |
|
||||||
```bash |
|
||||||
npm install -g gitrepublic-cli |
|
||||||
``` |
|
||||||
|
|
||||||
## Updating the Package |
|
||||||
|
|
||||||
1. Make your changes |
|
||||||
2. Update version: `npm version patch` (or minor/major) |
|
||||||
3. Publish: `npm publish` |
|
||||||
|
|
||||||
## Important Notes |
|
||||||
|
|
||||||
- **Package name**: `gitrepublic-cli` must be unique on npm. If taken, use a scoped name like `@your-org/gitrepublic-cli` |
|
||||||
- **Version**: Follow semantic versioning (semver) |
|
||||||
- **Files**: Only files listed in `files` array (or not in `.npmignore`) will be published |
|
||||||
- **Unpublishing**: You can unpublish within 72 hours, but it's discouraged. Use deprecation instead: |
|
||||||
```bash |
|
||||||
npm deprecate gitrepublic-cli@1.0.0 "Use version 1.0.1 instead" |
|
||||||
``` |
|
||||||
|
|
||||||
## Troubleshooting |
|
||||||
|
|
||||||
### "Access token expired or revoked" |
|
||||||
- Your npm login session has expired |
|
||||||
- Solution: Run `npm login` again to authenticate |
|
||||||
- Verify you're logged in: `npm whoami` |
|
||||||
|
|
||||||
### "403 Forbidden - Two-factor authentication or granular access token with bypass 2fa enabled is required" |
|
||||||
- npm requires 2FA (TOTP/SMS) or a granular access token to publish packages |
|
||||||
- Biometric authentication (fingerprint) alone is not sufficient for CLI publishing |
|
||||||
- **Solution Option 1: Enable TOTP/SMS 2FA** |
|
||||||
1. Visit: https://www.npmjs.com/settings/[your-username]/security |
|
||||||
2. Look for "Two-factor authentication" section |
|
||||||
3. If you only see biometric options: |
|
||||||
- Check for "Advanced" or "More options" links |
|
||||||
- Look for "Authenticator app" or "SMS" options |
|
||||||
- You may need to disable biometric first to see other options |
|
||||||
4. Enable TOTP app (recommended) or SMS |
|
||||||
5. Follow setup instructions |
|
||||||
6. After enabling, log out and log back in: `npm logout` then `npm login` |
|
||||||
- **Solution Option 2: Use Granular Access Token** (if 2FA setup is difficult) |
|
||||||
1. Visit: https://www.npmjs.com/settings/[your-username]/tokens |
|
||||||
2. Click "Generate New Token" → "Granular Access Token" |
|
||||||
3. Set permissions: Select "Publish" for your package(s) |
|
||||||
4. **Important**: Enable "Bypass 2FA" option |
|
||||||
5. Copy the token (save it securely - you'll only see it once!) |
|
||||||
6. Use it for authentication: |
|
||||||
```bash |
|
||||||
npm config set //registry.npmjs.org/:_authToken YOUR_TOKEN_HERE |
|
||||||
``` |
|
||||||
7. Or set as environment variable: |
|
||||||
```bash |
|
||||||
export NPM_TOKEN=YOUR_TOKEN_HERE |
|
||||||
``` |
|
||||||
8. Now you can publish: `npm publish` |
|
||||||
|
|
||||||
### "404 Not Found - PUT https://registry.npmjs.org/gitrepublic-cli" |
|
||||||
- This is normal for a first publish (package doesn't exist yet) |
|
||||||
- Make sure you're logged in: `npm login` |
|
||||||
- Check if package name is available: `npm view gitrepublic-cli` (should return 404) |
|
||||||
|
|
||||||
### "Package name already exists" |
|
||||||
- The name `gitrepublic-cli` is taken |
|
||||||
- Options: |
|
||||||
1. Use a scoped package: Change name to `@your-org/gitrepublic-cli` in package.json |
|
||||||
2. Choose a different name |
|
||||||
3. Contact the owner of the existing package |
|
||||||
|
|
||||||
### "You do not have permission" |
|
||||||
- Make sure you're logged in: `npm whoami` |
|
||||||
- If using scoped package, add `--access public` flag |
|
||||||
|
|
||||||
### "Invalid package name" |
|
||||||
- Package names must be lowercase |
|
||||||
- Can contain hyphens and underscores |
|
||||||
- Cannot start with dot or underscore |
|
||||||
- Max 214 characters |
|
||||||
|
|
||||||
### npm warnings about package.json |
|
||||||
- If you see warnings about `bin` script names being "cleaned", this is usually fine - npm normalizes them |
|
||||||
- If you see warnings about `repositories` field, remove it and use only the `repository` field (single object, not array) |
|
||||||
@ -1,268 +0,0 @@ |
|||||||
# Security Implementation Plan |
|
||||||
|
|
||||||
This document outlines the implementation of security improvements in two tiers: |
|
||||||
1. **Lightweight** - Single container, application-level improvements |
|
||||||
2. **Enterprise** - Multi-container/Kubernetes with process isolation |
|
||||||
|
|
||||||
## Architecture Overview |
|
||||||
|
|
||||||
### Lightweight (Single Container) |
|
||||||
- Application-level security controls |
|
||||||
- Resource limits enforced in code |
|
||||||
- Rate limiting in application |
|
||||||
- Audit logging |
|
||||||
- Works with current Docker setup |
|
||||||
|
|
||||||
### Enterprise (Kubernetes) |
|
||||||
- Process isolation per tenant |
|
||||||
- Network policies |
|
||||||
- Resource quotas per namespace |
|
||||||
- Separate volumes per tenant |
|
||||||
- Scales horizontally |
|
||||||
|
|
||||||
## Implementation Plan |
|
||||||
|
|
||||||
### Phase 1: Lightweight Improvements (Single Container) |
|
||||||
|
|
||||||
These improvements work in the current single-container setup and provide immediate security benefits. |
|
||||||
|
|
||||||
#### 1.1 Resource Limits Per User |
|
||||||
|
|
||||||
**Implementation**: Application-level tracking and enforcement |
|
||||||
|
|
||||||
**Files to create/modify**: |
|
||||||
- `src/lib/services/security/resource-limits.ts` - Track and enforce limits |
|
||||||
- `src/routes/api/repos/[npub]/[repo]/+server.ts` - Check limits before operations |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- Per-user repository count limit (configurable, default: 100) |
|
||||||
- Per-user disk quota (configurable, default: 10GB) |
|
||||||
- Per-repository size limit (already exists: 2GB) |
|
||||||
- Per-file size limit (already exists: 500MB) |
|
||||||
|
|
||||||
**Configuration**: |
|
||||||
```typescript |
|
||||||
// Environment variables |
|
||||||
MAX_REPOS_PER_USER=100 |
|
||||||
MAX_DISK_QUOTA_PER_USER=10737418240 // 10GB in bytes |
|
||||||
``` |
|
||||||
|
|
||||||
#### 1.2 Rate Limiting |
|
||||||
|
|
||||||
**Implementation**: In-memory or Redis-based rate limiting |
|
||||||
|
|
||||||
**Files to create/modify**: |
|
||||||
- `src/lib/services/security/rate-limiter.ts` - Rate limiting logic |
|
||||||
- `src/hooks.server.ts` - Apply rate limits to requests |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- Per-IP rate limiting (requests per minute) |
|
||||||
- Per-user rate limiting (operations per minute) |
|
||||||
- Different limits for different operations: |
|
||||||
- Git operations (clone/push): 60/min |
|
||||||
- File operations: 30/min |
|
||||||
- API requests: 120/min |
|
||||||
|
|
||||||
**Configuration**: |
|
||||||
```typescript |
|
||||||
// Environment variables |
|
||||||
RATE_LIMIT_ENABLED=true |
|
||||||
RATE_LIMIT_WINDOW_MS=60000 // 1 minute |
|
||||||
RATE_LIMIT_MAX_REQUESTS=120 |
|
||||||
``` |
|
||||||
|
|
||||||
#### 1.3 Audit Logging |
|
||||||
|
|
||||||
**Implementation**: Structured logging to files/console |
|
||||||
|
|
||||||
**Files to create/modify**: |
|
||||||
- `src/lib/services/security/audit-logger.ts` - Audit logging service |
|
||||||
- All API endpoints - Add audit log entries |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- Log all repository access attempts |
|
||||||
- Log all file operations (read/write/delete) |
|
||||||
- Log authentication attempts (success/failure) |
|
||||||
- Log ownership transfers |
|
||||||
- Include: timestamp, user pubkey, IP, action, result |
|
||||||
|
|
||||||
**Log Format**: |
|
||||||
```json |
|
||||||
{ |
|
||||||
"timestamp": "2024-01-01T12:00:00Z", |
|
||||||
"user": "abc123...", |
|
||||||
"ip": "192.168.1.1", |
|
||||||
"action": "repo.clone", |
|
||||||
"repo": "npub1.../myrepo", |
|
||||||
"result": "success", |
|
||||||
"metadata": {} |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
**Storage**: |
|
||||||
- **Console**: Always logs to stdout (JSON format, prefixed with `[AUDIT]`) |
|
||||||
- **File**: Optional file logging (if `AUDIT_LOG_FILE` is set) |
|
||||||
- Daily rotation: Creates new file each day (e.g., `audit-2024-01-01.log`) |
|
||||||
- Location: Configurable via `AUDIT_LOG_FILE` environment variable |
|
||||||
- Default location: Console only (no file logging by default) |
|
||||||
|
|
||||||
**Retention**: |
|
||||||
- **Default**: 90 days (configurable via `AUDIT_LOG_RETENTION_DAYS`) |
|
||||||
- **Automatic cleanup**: Old log files are automatically deleted |
|
||||||
- **Rotation**: Logs rotate daily at midnight (based on date change) |
|
||||||
- **Set to 0**: Disables automatic cleanup (manual cleanup required) |
|
||||||
|
|
||||||
**Example Configuration**: |
|
||||||
```bash |
|
||||||
# Log to /var/log/gitrepublic/audit.log (with daily rotation) |
|
||||||
AUDIT_LOG_FILE=/var/log/gitrepublic/audit.log |
|
||||||
AUDIT_LOG_RETENTION_DAYS=90 |
|
||||||
|
|
||||||
# Or use Docker volume |
|
||||||
AUDIT_LOG_FILE=/app/logs/audit.log |
|
||||||
AUDIT_LOG_RETENTION_DAYS=30 |
|
||||||
``` |
|
||||||
|
|
||||||
#### 1.4 Enhanced git-http-backend Hardening |
|
||||||
|
|
||||||
**Implementation**: Additional security measures for git-http-backend |
|
||||||
|
|
||||||
**Files to modify**: |
|
||||||
- `src/routes/api/git/[...path]/+server.ts` - Add security measures |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- Validate PATH_INFO to prevent manipulation |
|
||||||
- Set restrictive environment variables |
|
||||||
- Timeout for git operations |
|
||||||
- Resource limits for spawned processes |
|
||||||
|
|
||||||
### Phase 2: Enterprise Improvements (Kubernetes) |
|
||||||
|
|
||||||
These require multi-container architecture and Kubernetes. |
|
||||||
|
|
||||||
#### 2.1 Container-per-Tenant Architecture |
|
||||||
|
|
||||||
**Architecture**: |
|
||||||
- Each user (npub) gets their own namespace |
|
||||||
- Each namespace has: |
|
||||||
- Application pod (gitrepublic instance) |
|
||||||
- Persistent volume for repositories |
|
||||||
- Service for networking |
|
||||||
- Resource quotas |
|
||||||
|
|
||||||
**Kubernetes Resources**: |
|
||||||
- `k8s/namespace-template.yaml` - Namespace per tenant |
|
||||||
- `k8s/deployment-template.yaml` - Application deployment |
|
||||||
- `k8s/service-template.yaml` - Service definition |
|
||||||
- `k8s/pvc-template.yaml` - Persistent volume claim |
|
||||||
- `k8s/resource-quota.yaml` - Resource limits |
|
||||||
|
|
||||||
#### 2.2 Network Isolation |
|
||||||
|
|
||||||
**Implementation**: Kubernetes Network Policies |
|
||||||
|
|
||||||
**Files to create**: |
|
||||||
- `k8s/network-policy.yaml` - Network isolation rules |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- Namespace-level network isolation |
|
||||||
- Only allow traffic from ingress controller |
|
||||||
- Block inter-namespace communication |
|
||||||
- Allow egress to Nostr relays only |
|
||||||
|
|
||||||
#### 2.3 Resource Quotas |
|
||||||
|
|
||||||
**Implementation**: Kubernetes ResourceQuota |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- CPU limits per tenant |
|
||||||
- Memory limits per tenant |
|
||||||
- Storage limits per tenant |
|
||||||
- Pod count limits |
|
||||||
|
|
||||||
#### 2.4 Separate Volumes Per Tenant |
|
||||||
|
|
||||||
**Implementation**: Kubernetes PersistentVolumeClaims |
|
||||||
|
|
||||||
**Features**: |
|
||||||
- Each tenant gets their own volume |
|
||||||
- Volume size limits |
|
||||||
- Backup/restore per tenant |
|
||||||
- Snapshot support |
|
||||||
|
|
||||||
## Hybrid Approach (Recommended) |
|
||||||
|
|
||||||
The hybrid approach implements lightweight improvements first, then provides a migration path to enterprise architecture. |
|
||||||
|
|
||||||
### Benefits: |
|
||||||
1. **Immediate security improvements** - Lightweight features work now |
|
||||||
2. **Scalable architecture** - Can migrate to Kubernetes when needed |
|
||||||
3. **Cost-effective** - Start simple, scale as needed |
|
||||||
4. **Flexible deployment** - Works in both scenarios |
|
||||||
|
|
||||||
### Implementation Strategy: |
|
||||||
|
|
||||||
1. **Start with lightweight** - Implement Phase 1 features |
|
||||||
2. **Design for scale** - Code structure supports multi-container |
|
||||||
3. **Add Kubernetes support** - Phase 2 when needed |
|
||||||
4. **Gradual migration** - Move tenants to K8s as needed |
|
||||||
|
|
||||||
## File Structure |
|
||||||
|
|
||||||
``` |
|
||||||
src/lib/services/security/ |
|
||||||
├── resource-limits.ts # Resource limit tracking |
|
||||||
├── rate-limiter.ts # Rate limiting |
|
||||||
├── audit-logger.ts # Audit logging |
|
||||||
└── quota-manager.ts # Disk quota management |
|
||||||
|
|
||||||
k8s/ |
|
||||||
├── base/ |
|
||||||
│ ├── namespace.yaml |
|
||||||
│ ├── deployment.yaml |
|
||||||
│ ├── service.yaml |
|
||||||
│ └── pvc.yaml |
|
||||||
├── overlays/ |
|
||||||
│ ├── single-container/ # Single container setup |
|
||||||
│ └── multi-tenant/ # Kubernetes setup |
|
||||||
└── helm-chart/ # Optional Helm chart |
|
||||||
``` |
|
||||||
|
|
||||||
## Configuration |
|
||||||
|
|
||||||
### Lightweight Mode (Single Container) |
|
||||||
```yaml |
|
||||||
# docker-compose.yml or .env |
|
||||||
SECURITY_MODE=lightweight |
|
||||||
MAX_REPOS_PER_USER=100 |
|
||||||
MAX_DISK_QUOTA_PER_USER=10737418240 |
|
||||||
RATE_LIMIT_ENABLED=true |
|
||||||
AUDIT_LOGGING_ENABLED=true |
|
||||||
``` |
|
||||||
|
|
||||||
### Enterprise Mode (Kubernetes) |
|
||||||
```yaml |
|
||||||
# Kubernetes ConfigMap |
|
||||||
security: |
|
||||||
mode: enterprise |
|
||||||
isolation: container-per-tenant |
|
||||||
networkPolicy: enabled |
|
||||||
resourceQuotas: enabled |
|
||||||
``` |
|
||||||
|
|
||||||
## Migration Path |
|
||||||
|
|
||||||
### From Lightweight to Enterprise: |
|
||||||
|
|
||||||
1. **Phase 1**: Deploy lightweight improvements (no architecture change) |
|
||||||
2. **Phase 2**: Add Kubernetes support alongside single container |
|
||||||
3. **Phase 3**: Migrate high-value tenants to Kubernetes |
|
||||||
4. **Phase 4**: Full Kubernetes deployment (optional) |
|
||||||
|
|
||||||
## Priority Implementation Order |
|
||||||
|
|
||||||
1. ✅ **Audit Logging** - Easy, high value, works everywhere |
|
||||||
2. ✅ **Rate Limiting** - Prevents abuse, works in single container |
|
||||||
3. ✅ **Resource Limits** - Prevents resource exhaustion |
|
||||||
4. ⏳ **Enhanced git-http-backend** - Additional hardening |
|
||||||
5. ⏳ **Kubernetes Support** - When scaling needed |
|
||||||
@ -0,0 +1,76 @@ |
|||||||
|
# About GitRepublic |
||||||
|
|
||||||
|
GitRepublic is a decentralized git hosting platform built on Nostr. Unlike traditional git hosting services, GitRepublic provides truly decentralized repository hosting where repositories are announced on Nostr relays, giving you full control and ownership. |
||||||
|
|
||||||
|
## Key Features |
||||||
|
|
||||||
|
- **Decentralized**: Repositories are announced on Nostr relays, no central authority |
||||||
|
- **Nostr-based Authentication**: Uses NIP-07 (browser extensions) and NIP-98 (HTTP authentication) |
||||||
|
- **Full Control**: Own your repositories, transfer ownership, manage maintainers, control access |
||||||
|
- **Open Collaboration**: Create pull requests, issues, and collaborate using Nostr events |
||||||
|
- **Multi-remote Sync**: Automatically syncs to multiple remotes when you push |
||||||
|
- **GRASP Compatible**: Works seamlessly with GRASP servers |
||||||
|
|
||||||
|
## Documentation Topics |
||||||
|
|
||||||
|
### Core Concepts |
||||||
|
|
||||||
|
- [How this git server integrates Nostr](./nostr-integration.md) - Understanding the Nostr-based architecture |
||||||
|
|
||||||
|
### Repository Operations |
||||||
|
|
||||||
|
- [Creating, forking, transferring, or cloning a repo](./repo-operations.md) - Getting started with repositories |
||||||
|
|
||||||
|
- [Editing a repo](./editing-repos.md) - Branch management, file management, auto-provisioning, file-editing and event-creation permissions |
||||||
|
|
||||||
|
- [Managing a repo](./managing-repos.md) - Complete guide to repository management |
||||||
|
- [Repo Header](./managing-repos.md#repo-header) |
||||||
|
- [Clone Section](./managing-repos.md#clone-section) |
||||||
|
- [File Tab](./managing-repos.md#file-tab) |
||||||
|
- [Commit Tab](./managing-repos.md#commit-tab) |
||||||
|
- [PRs Tab](./managing-repos.md#prs-tab) |
||||||
|
- [Issues Tab](./managing-repos.md#issues-tab) |
||||||
|
- [Patches Tab](./managing-repos.md#patches-tab) |
||||||
|
- [Discussions Tab](./managing-repos.md#discussions-tab) |
||||||
|
- [Docs Tab](./managing-repos.md#docs-tab) |
||||||
|
- [History Tab](./managing-repos.md#history-tab) |
||||||
|
- [Tags Tab](./managing-repos.md#tags-tab) |
||||||
|
|
||||||
|
### Discovery and Access |
||||||
|
|
||||||
|
- [Search and viewing external git repos](./search-and-external-repos.md) - Finding and viewing repositories |
||||||
|
|
||||||
|
### API and CLI |
||||||
|
|
||||||
|
- [REST API and CLI](./api-and-cli.md) - Programmatic access and command-line tools |
||||||
|
|
||||||
|
### User Features |
||||||
|
|
||||||
|
- [Profile pages](./profile-pages.md) - User profiles and payment targets |
||||||
|
|
||||||
|
- [Settings and Dashboard](./settings-and-dashboard.md) - User account management |
||||||
|
|
||||||
|
### Infrastructure |
||||||
|
|
||||||
|
- [Working with GRASP servers](./grasp-servers.md) - GRASP protocol compatibility |
||||||
|
|
||||||
|
- [Lightweight versus Enterprise modes](./deployment-modes.md) - Deployment options and security |
||||||
|
|
||||||
|
### Technical Details |
||||||
|
|
||||||
|
- [Tech stack used](./tech-stack.md) - Code editor, syntax highlighter, programming languages, logger, etc. |
||||||
|
|
||||||
|
- [Specs used](./specs.md) - Links to NIPs and GRASP docs, and list of custom events |
||||||
|
|
||||||
|
## Getting Started |
||||||
|
|
||||||
|
1. **Install a NIP-07 browser extension** (Alby, nos2x, etc.) |
||||||
|
2. **Visit a GitRepublic instance** and connect with your extension |
||||||
|
3. **Create your first repository** via the signup page |
||||||
|
4. **Start collaborating** using pull requests, issues, and patches |
||||||
|
|
||||||
|
For detailed instructions, see the topic pages above. |
||||||
|
|
||||||
|
## Additional Resources |
||||||
|
|
||||||
|
- [API Documentation](../src/routes/api/openapi.json/openapi.json) - OpenAPI specification |
||||||
@ -0,0 +1,250 @@ |
|||||||
|
# REST API and CLI |
||||||
|
|
||||||
|
Complete guide to programmatic access via REST API and command-line interface. |
||||||
|
|
||||||
|
## REST API |
||||||
|
|
||||||
|
GitRepublic provides a comprehensive REST API for all operations. |
||||||
|
|
||||||
|
### Authentication |
||||||
|
|
||||||
|
All write operations require **NIP-98 HTTP Authentication**: |
||||||
|
|
||||||
|
``` |
||||||
|
Authorization: Nostr <base64-encoded-event-json> |
||||||
|
``` |
||||||
|
|
||||||
|
The event must be: |
||||||
|
- Kind 27235 (NIP-98 authentication event) |
||||||
|
- Include `u` tag with request URL |
||||||
|
- Include `method` tag with HTTP method |
||||||
|
- Include `payload` tag with SHA256 hash of request body (for POST/PUT) |
||||||
|
- Signed with your Nostr private key |
||||||
|
|
||||||
|
### API Documentation |
||||||
|
|
||||||
|
Full API documentation is available in OpenAPI format: |
||||||
|
|
||||||
|
- **Development**: `http://localhost:5173/api/openapi.json` |
||||||
|
- **Production**: `https://your-domain.com/api/openapi.json` |
||||||
|
|
||||||
|
View interactive documentation at `/api/openapi.json` or use any OpenAPI viewer. |
||||||
|
|
||||||
|
### Endpoints Overview |
||||||
|
|
||||||
|
#### Repository Management |
||||||
|
|
||||||
|
- `GET /api/repos/list` - List all repositories |
||||||
|
- `GET /api/repos/local` - List local repositories |
||||||
|
- `GET /api/repos/{npub}/{repo}/settings` - Get repository settings |
||||||
|
- `POST /api/repos/{npub}/{repo}/settings` - Update repository settings |
||||||
|
- `GET /api/repos/{npub}/{repo}/maintainers` - Get maintainers |
||||||
|
- `POST /api/repos/{npub}/{repo}/maintainers` - Add maintainer |
||||||
|
- `DELETE /api/repos/{npub}/{repo}/maintainers` - Remove maintainer |
||||||
|
- `POST /api/repos/{npub}/{repo}/fork` - Fork repository |
||||||
|
- `DELETE /api/repos/{npub}/{repo}/delete` - Delete repository |
||||||
|
- `POST /api/repos/{npub}/{repo}/transfer` - Transfer ownership |
||||||
|
- `POST /api/repos/{npub}/{repo}/clone` - Clone to server |
||||||
|
|
||||||
|
#### File Operations |
||||||
|
|
||||||
|
- `GET /api/repos/{npub}/{repo}/file` - Get file content |
||||||
|
- `POST /api/repos/{npub}/{repo}/file` - Create/update/delete file |
||||||
|
- `GET /api/repos/{npub}/{repo}/tree` - List files and directories |
||||||
|
- `GET /api/repos/{npub}/{repo}/raw` - Get raw file content |
||||||
|
- `GET /api/repos/{npub}/{repo}/readme` - Get README content |
||||||
|
|
||||||
|
#### Git Operations |
||||||
|
|
||||||
|
- `GET /api/repos/{npub}/{repo}/branches` - List branches |
||||||
|
- `POST /api/repos/{npub}/{repo}/branches` - Create branch |
||||||
|
- `GET /api/repos/{npub}/{repo}/tags` - List tags |
||||||
|
- `POST /api/repos/{npub}/{repo}/tags` - Create tag |
||||||
|
- `GET /api/repos/{npub}/{repo}/commits` - List commits |
||||||
|
- `GET /api/repos/{npub}/{repo}/commits/{hash}/verify` - Verify commit signature |
||||||
|
- `GET /api/repos/{npub}/{repo}/diff` - Get diff between commits |
||||||
|
- `GET /api/repos/{npub}/{repo}/default-branch` - Get default branch |
||||||
|
- `POST /api/repos/{npub}/{repo}/default-branch` - Set default branch |
||||||
|
|
||||||
|
#### Collaboration |
||||||
|
|
||||||
|
- `GET /api/repos/{npub}/{repo}/prs` - List pull requests |
||||||
|
- `POST /api/repos/{npub}/{repo}/prs` - Create pull request |
||||||
|
- `PATCH /api/repos/{npub}/{repo}/prs` - Update PR status |
||||||
|
- `POST /api/repos/{npub}/{repo}/prs/{prId}/merge` - Merge PR |
||||||
|
- `GET /api/repos/{npub}/{repo}/issues` - List issues |
||||||
|
- `POST /api/repos/{npub}/{repo}/issues` - Create issue |
||||||
|
- `PATCH /api/repos/{npub}/{repo}/issues` - Update issue status |
||||||
|
- `GET /api/repos/{npub}/{repo}/patches` - List patches |
||||||
|
- `POST /api/repos/{npub}/{repo}/patches` - Create patch |
||||||
|
- `PATCH /api/repos/{npub}/{repo}/patches` - Update patch status |
||||||
|
- `POST /api/repos/{npub}/{repo}/patches/{patchId}/apply` - Apply patch |
||||||
|
- `GET /api/repos/{npub}/{repo}/highlights` - List highlights/comments |
||||||
|
- `POST /api/repos/{npub}/{repo}/highlights` - Create highlight/comment |
||||||
|
|
||||||
|
#### Search and Discovery |
||||||
|
|
||||||
|
- `GET /api/search` - Search repositories |
||||||
|
- `GET /api/repos/{npub}/{repo}/code-search` - Search code in repository |
||||||
|
- `GET /api/code-search` - Global code search |
||||||
|
- `GET /api/repos/{npub}/{repo}/clone-urls/reachability` - Check clone URL reachability |
||||||
|
|
||||||
|
#### User Operations |
||||||
|
|
||||||
|
- `GET /api/users/{npub}/profile` - Get user profile |
||||||
|
- `GET /api/users/{npub}/repos` - Get user's repositories |
||||||
|
- `GET /api/user/level` - Get user access level |
||||||
|
- `GET /api/user/git-dashboard` - Get git dashboard |
||||||
|
- `GET /api/user/messaging-preferences` - Get messaging preferences |
||||||
|
- `POST /api/user/messaging-preferences` - Update messaging preferences |
||||||
|
|
||||||
|
#### Infrastructure |
||||||
|
|
||||||
|
- `GET /api/config` - Get server configuration |
||||||
|
- `GET /api/tor/onion` - Get Tor .onion address |
||||||
|
- `GET /api/transfers/pending` - Get pending ownership transfers |
||||||
|
|
||||||
|
#### Git HTTP Backend |
||||||
|
|
||||||
|
- `GET /api/git/{npub}/{repo}.git/{path}` - Git smart HTTP operations |
||||||
|
- Supports: `info/refs`, `git-upload-pack`, `git-receive-pack` |
||||||
|
- Handles: clone, fetch, push operations |
||||||
|
|
||||||
|
### Example API Usage |
||||||
|
|
||||||
|
```bash |
||||||
|
# List repositories |
||||||
|
curl https://your-domain.com/api/repos/list |
||||||
|
|
||||||
|
# Get repository settings |
||||||
|
curl https://your-domain.com/api/repos/{npub}/{repo}/settings |
||||||
|
|
||||||
|
# Create file (requires NIP-98 auth) |
||||||
|
curl -X POST https://your-domain.com/api/repos/{npub}/{repo}/file \ |
||||||
|
-H "Authorization: Nostr <base64-event>" \ |
||||||
|
-H "Content-Type: application/json" \ |
||||||
|
-d '{"path": "test.txt", "content": "Hello", "commitMessage": "Add file", "branch": "main", "action": "write"}' |
||||||
|
``` |
||||||
|
|
||||||
|
## Command Line Interface (CLI) |
||||||
|
|
||||||
|
The GitRepublic CLI provides full access to all features from the command line. |
||||||
|
|
||||||
|
### Installation |
||||||
|
|
||||||
|
```bash |
||||||
|
npm install -g gitrepublic-cli |
||||||
|
``` |
||||||
|
|
||||||
|
### Setup |
||||||
|
|
||||||
|
```bash |
||||||
|
# Set your Nostr private key |
||||||
|
export NOSTRGIT_SECRET_KEY="nsec1..." |
||||||
|
|
||||||
|
# Configure credential helper and commit hook |
||||||
|
gitrep-setup |
||||||
|
``` |
||||||
|
|
||||||
|
### Getting Help |
||||||
|
|
||||||
|
For complete CLI documentation, run: |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep --help |
||||||
|
``` |
||||||
|
|
||||||
|
This shows: |
||||||
|
- Initial setup instructions |
||||||
|
- All git commands |
||||||
|
- All API commands |
||||||
|
- Repository management |
||||||
|
- Publishing Nostr events |
||||||
|
- Environment variables |
||||||
|
- And much more |
||||||
|
|
||||||
|
### Common CLI Operations |
||||||
|
|
||||||
|
#### Git Operations |
||||||
|
|
||||||
|
```bash |
||||||
|
# Clone repository |
||||||
|
gitrep clone https://domain.com/api/git/npub1.../repo.git |
||||||
|
|
||||||
|
# Push changes |
||||||
|
gitrep push origin main |
||||||
|
|
||||||
|
# Pull changes |
||||||
|
gitrep pull origin main |
||||||
|
``` |
||||||
|
|
||||||
|
#### Repository Management |
||||||
|
|
||||||
|
```bash |
||||||
|
# List repositories |
||||||
|
gitrep repos list |
||||||
|
|
||||||
|
# Get repository info |
||||||
|
gitrep repos get <npub> <repo> |
||||||
|
|
||||||
|
# Update settings |
||||||
|
gitrep repos settings <npub> <repo> --visibility public |
||||||
|
|
||||||
|
# Manage maintainers |
||||||
|
gitrep repos maintainers <npub> <repo> add <maintainer-npub> |
||||||
|
``` |
||||||
|
|
||||||
|
#### Publishing Events |
||||||
|
|
||||||
|
```bash |
||||||
|
# Publish repository announcement |
||||||
|
gitrep publish repo-announcement myrepo --description "My repo" |
||||||
|
|
||||||
|
# Create pull request |
||||||
|
gitrep publish pr <owner> <repo> "PR Title" --content "Description" |
||||||
|
|
||||||
|
# Create issue |
||||||
|
gitrep publish issue <owner> <repo> "Issue Title" --content "Description" |
||||||
|
``` |
||||||
|
|
||||||
|
#### Multi-Remote Operations |
||||||
|
|
||||||
|
```bash |
||||||
|
# Push to all remotes |
||||||
|
gitrep push-all main |
||||||
|
|
||||||
|
# Pull from all remotes and merge |
||||||
|
gitrep pull-all --merge |
||||||
|
``` |
||||||
|
|
||||||
|
### CLI Features |
||||||
|
|
||||||
|
- **Git Wrapper**: Enhanced error messages for GitRepublic operations |
||||||
|
- **Credential Helper**: Automatic NIP-98 authentication |
||||||
|
- **Commit Signing**: Automatic commit signatures via hook |
||||||
|
- **API Access**: Full command-line access to all APIs |
||||||
|
- **Multi-Remote Sync**: Push/pull to/from all remotes |
||||||
|
|
||||||
|
## Authentication |
||||||
|
|
||||||
|
### NIP-98 for API |
||||||
|
|
||||||
|
For API access, create NIP-98 authentication events: |
||||||
|
|
||||||
|
1. Create ephemeral event (kind 27235) |
||||||
|
2. Add tags: `u` (URL), `method` (HTTP method), `payload` (body hash) |
||||||
|
3. Sign with your Nostr private key |
||||||
|
4. Base64 encode the event JSON |
||||||
|
5. Include in `Authorization: Nostr <base64-event>` header |
||||||
|
|
||||||
|
### CLI Authentication |
||||||
|
|
||||||
|
The CLI handles authentication automatically: |
||||||
|
- **Credential Helper**: Generates NIP-98 events for git operations |
||||||
|
- **API Commands**: Uses NIP-98 for all API calls |
||||||
|
- **No manual setup**: Just set `NOSTRGIT_SECRET_KEY` and run `gitrep-setup` |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Tech stack used](./tech-stack.md) - Technical implementation details |
||||||
|
- [Specs used](./specs.md) - NIPs and GRASP documentation |
||||||
@ -0,0 +1,128 @@ |
|||||||
|
# Lightweight versus Enterprise Modes |
||||||
|
|
||||||
|
GitRepublic supports two deployment modes with different security and isolation characteristics. |
||||||
|
|
||||||
|
## Lightweight Mode (Single Container) - Default |
||||||
|
|
||||||
|
Lightweight mode is the default deployment option, suitable for most use cases. |
||||||
|
|
||||||
|
### Characteristics |
||||||
|
|
||||||
|
- **Single container**: All users share the same container |
||||||
|
- **Shared filesystem**: Repositories stored in shared directory structure |
||||||
|
- **Process isolation**: Uses git-http-backend with process isolation |
||||||
|
- **Resource limits**: Per-user repository count and disk quota limits |
||||||
|
- **Rate limiting**: Per-IP and per-user rate limiting |
||||||
|
|
||||||
|
### Security Features |
||||||
|
|
||||||
|
- **Resource Limits**: |
||||||
|
- Maximum repositories per user (default: 100) |
||||||
|
- Maximum disk quota per user (default: 10 GB) |
||||||
|
- Maximum file size (500 MB) |
||||||
|
- Maximum repository size (2 GB) |
||||||
|
|
||||||
|
- **Rate Limiting**: |
||||||
|
- Per-IP rate limiting for all operations |
||||||
|
- Per-user rate limiting for authenticated operations |
||||||
|
- Configurable rate limit windows |
||||||
|
|
||||||
|
- **Path Validation**: |
||||||
|
- Strict path validation to prevent traversal attacks |
||||||
|
- All file operations validated against repository root |
||||||
|
- Git operations scoped to repository directories |
||||||
|
|
||||||
|
- **Audit Logging**: |
||||||
|
- Comprehensive logging of all security-relevant events |
||||||
|
- User actions, access attempts, permission checks |
||||||
|
- Repository operations and ownership transfers |
||||||
|
|
||||||
|
- **git-http-backend Hardening**: |
||||||
|
- Timeouts for git operations |
||||||
|
- Process isolation |
||||||
|
- Scoped access to repository directories |
||||||
|
- Input validation and sanitization |
||||||
|
|
||||||
|
### Configuration |
||||||
|
|
||||||
|
Set `ENTERPRISE_MODE=false` or leave unset (default). |
||||||
|
|
||||||
|
### Use Cases |
||||||
|
|
||||||
|
- Personal git hosting |
||||||
|
- Small to medium teams |
||||||
|
- Development environments |
||||||
|
- Single-tenant deployments |
||||||
|
|
||||||
|
## Enterprise Mode (Kubernetes) |
||||||
|
|
||||||
|
Enterprise mode provides maximum isolation and security for multi-tenant deployments. |
||||||
|
|
||||||
|
### Characteristics |
||||||
|
|
||||||
|
- **Container-per-tenant**: Each tenant has their own container |
||||||
|
- **Network isolation**: Kubernetes Network Policies |
||||||
|
- **Resource quotas**: Per-tenant CPU, memory, and storage limits |
||||||
|
- **Separate volumes**: Each tenant has their own PersistentVolume |
||||||
|
- **Complete isolation**: Tenants cannot access each other's resources |
||||||
|
|
||||||
|
### Security Features |
||||||
|
|
||||||
|
All lightweight mode features, plus: |
||||||
|
|
||||||
|
- **Process Isolation**: |
||||||
|
- Each tenant runs in separate container |
||||||
|
- No shared processes or memory |
||||||
|
- Complete process isolation |
||||||
|
|
||||||
|
- **Network Isolation**: |
||||||
|
- Kubernetes Network Policies |
||||||
|
- Tenant-to-tenant communication blocked |
||||||
|
- Only necessary network access allowed |
||||||
|
|
||||||
|
- **Resource Quotas**: |
||||||
|
- Per-tenant CPU limits |
||||||
|
- Per-tenant memory limits |
||||||
|
- Per-tenant storage limits |
||||||
|
- Enforced by Kubernetes |
||||||
|
|
||||||
|
- **Volume Isolation**: |
||||||
|
- Each tenant has dedicated PersistentVolume |
||||||
|
- No shared storage access |
||||||
|
- Complete filesystem isolation |
||||||
|
|
||||||
|
### Configuration |
||||||
|
|
||||||
|
Set `ENTERPRISE_MODE=true` environment variable. |
||||||
|
|
||||||
|
### Deployment |
||||||
|
|
||||||
|
See `k8s/ENTERPRISE_MODE.md` for complete setup instructions. |
||||||
|
|
||||||
|
### Use Cases |
||||||
|
|
||||||
|
- Multi-tenant SaaS deployments |
||||||
|
- Enterprise customers |
||||||
|
- High-security requirements |
||||||
|
- Regulatory compliance needs |
||||||
|
|
||||||
|
## Security Comparison |
||||||
|
|
||||||
|
| Feature | Lightweight Mode | Enterprise Mode | |
||||||
|
|---------|-----------------|-----------------| |
||||||
|
| Process Isolation | git-http-backend only | Container-per-tenant | |
||||||
|
| Filesystem Isolation | Directory-based | Volume-per-tenant | |
||||||
|
| Network Isolation | None | Kubernetes Policies | |
||||||
|
| Resource Limits | Per-user quotas | Per-tenant Kubernetes quotas | |
||||||
|
| Audit Logging | ✅ | ✅ | |
||||||
|
| Path Validation | ✅ | ✅ | |
||||||
|
| Rate Limiting | ✅ | ✅ | |
||||||
|
|
||||||
|
## Security Documentation |
||||||
|
|
||||||
|
Security features are implemented in both lightweight and enterprise modes. See the codebase for detailed implementation. |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Tech stack used](./tech-stack.md) - Technical implementation |
||||||
|
- [Specs used](./specs.md) - Security-related specifications |
||||||
@ -0,0 +1,190 @@ |
|||||||
|
# Editing a Repo |
||||||
|
|
||||||
|
This page covers all aspects of editing repositories: branch management, file management, auto-provisioning, file-editing permissions, and event-creation permissions. |
||||||
|
|
||||||
|
## Auto-Provisioning |
||||||
|
|
||||||
|
When you create a repository announcement, GitRepublic automatically: |
||||||
|
|
||||||
|
1. **Polls Nostr relays** for new announcements |
||||||
|
2. **Creates a bare git repository** at `/repos/{npub}/{repo-name}.git` |
||||||
|
3. **Fetches self-transfer event** for ownership verification |
||||||
|
4. **Creates initial commit** with README.md (if provided) |
||||||
|
5. **Saves announcement and transfer events** to `nostr/repo-events.jsonl` |
||||||
|
6. **Syncs from other remotes** if clone URLs are configured |
||||||
|
|
||||||
|
The repository is ready to use immediately after announcement. |
||||||
|
|
||||||
|
## Branch Management |
||||||
|
|
||||||
|
### Creating Branches |
||||||
|
|
||||||
|
#### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to your repository |
||||||
|
2. Click **Create Branch** button |
||||||
|
3. Enter branch name |
||||||
|
4. Select source branch |
||||||
|
5. Click **Create** |
||||||
|
|
||||||
|
#### Via Git |
||||||
|
|
||||||
|
```bash |
||||||
|
git checkout -b feature/new-feature |
||||||
|
git push origin feature/new-feature |
||||||
|
``` |
||||||
|
|
||||||
|
### Viewing Branches |
||||||
|
|
||||||
|
- **Web Interface**: View all branches on the repository page |
||||||
|
- **API**: `GET /api/repos/{npub}/{repo}/branches` |
||||||
|
- **CLI**: `gitrep repos branches <npub> <repo>` |
||||||
|
|
||||||
|
### Default Branch |
||||||
|
|
||||||
|
The default branch (usually `main`) can be changed via: |
||||||
|
- **Web Interface**: Repository settings |
||||||
|
- **API**: `POST /api/repos/{npub}/{repo}/default-branch` |
||||||
|
|
||||||
|
## File Management |
||||||
|
|
||||||
|
### Reading Files |
||||||
|
|
||||||
|
#### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to repository |
||||||
|
2. Click **File** tab |
||||||
|
3. Browse directory structure |
||||||
|
4. Click files to view content |
||||||
|
|
||||||
|
#### Via API |
||||||
|
|
||||||
|
```bash |
||||||
|
GET /api/repos/{npub}/{repo}/file?path={file-path}&branch={branch} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep file get <npub> <repo> <path> [branch] |
||||||
|
``` |
||||||
|
|
||||||
|
### Creating/Updating Files |
||||||
|
|
||||||
|
#### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to repository |
||||||
|
2. Click **File** tab |
||||||
|
3. Click **Edit** button on a file (or create new) |
||||||
|
4. Use the code editor to make changes |
||||||
|
5. Enter commit message |
||||||
|
6. Select branch |
||||||
|
7. Click **Save** |
||||||
|
|
||||||
|
#### Via API |
||||||
|
|
||||||
|
```bash |
||||||
|
POST /api/repos/{npub}/{repo}/file |
||||||
|
{ |
||||||
|
"path": "file.txt", |
||||||
|
"content": "File content", |
||||||
|
"commitMessage": "Add file", |
||||||
|
"branch": "main", |
||||||
|
"action": "write" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep file put <npub> <repo> <path> [file] [message] [branch] |
||||||
|
``` |
||||||
|
|
||||||
|
### Deleting Files |
||||||
|
|
||||||
|
#### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to file |
||||||
|
2. Click **Delete** button |
||||||
|
3. Enter commit message |
||||||
|
4. Click **Delete** |
||||||
|
|
||||||
|
#### Via API |
||||||
|
|
||||||
|
```bash |
||||||
|
POST /api/repos/{npub}/{repo}/file |
||||||
|
{ |
||||||
|
"path": "file.txt", |
||||||
|
"commitMessage": "Remove file", |
||||||
|
"branch": "main", |
||||||
|
"action": "delete" |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep file delete <npub> <repo> <path> [message] [branch] |
||||||
|
``` |
||||||
|
|
||||||
|
## Permissions |
||||||
|
|
||||||
|
### File Editing Permissions |
||||||
|
|
||||||
|
- **Repository Owner**: Can edit all files |
||||||
|
- **Maintainers**: Can edit all files |
||||||
|
- **Other Users**: Cannot edit files (read-only) |
||||||
|
|
||||||
|
### Event Creation Permissions |
||||||
|
|
||||||
|
Different events have different permission requirements: |
||||||
|
|
||||||
|
- **Pull Requests**: Anyone can create |
||||||
|
- **Issues**: Anyone can create |
||||||
|
- **Patches**: Anyone can create |
||||||
|
- **Comments**: Anyone can comment |
||||||
|
- **Status Updates**: Only owners/maintainers can update PR/issue status |
||||||
|
- **Repository Settings**: Only owners/maintainers can update |
||||||
|
|
||||||
|
### Branch Protection |
||||||
|
|
||||||
|
Repository owners can protect branches: |
||||||
|
|
||||||
|
- **Require Pull Requests**: Direct pushes blocked, must use PRs |
||||||
|
- **Require Reviews**: PRs need approval before merging |
||||||
|
- **Require Status Checks**: Custom checks must pass |
||||||
|
|
||||||
|
Configure via: |
||||||
|
- **Web Interface**: Repository settings → Branch Protection |
||||||
|
- **API**: `POST /api/repos/{npub}/{repo}/branch-protection` |
||||||
|
|
||||||
|
## Code Editor |
||||||
|
|
||||||
|
The web interface includes a full-featured code editor with: |
||||||
|
|
||||||
|
- **Syntax Highlighting**: Supports many languages |
||||||
|
- **Line Numbers**: Easy navigation |
||||||
|
- **Word Wrap**: Toggle for long lines |
||||||
|
- **Search/Replace**: Find and replace functionality |
||||||
|
- **File Browser**: Navigate directory structure |
||||||
|
|
||||||
|
## Commit Signing |
||||||
|
|
||||||
|
Commits can be automatically signed using Nostr keys: |
||||||
|
|
||||||
|
- **CLI**: Automatic signing via commit hook (if configured) |
||||||
|
- **Web Interface**: Commits are signed by the server using your NIP-07 key |
||||||
|
|
||||||
|
Commit signatures are stored as Nostr events (kind 1640) and can be verified. |
||||||
|
|
||||||
|
## File Size Limits |
||||||
|
|
||||||
|
- **Maximum file size**: 500 MB per file |
||||||
|
- **Maximum repository size**: 2 GB total |
||||||
|
|
||||||
|
These limits prevent abuse and ensure reasonable resource usage. |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Managing a repo](./managing-repos.md) - Complete repository management guide |
||||||
|
- [REST API and CLI](./api-and-cli.md) - Programmatic access |
||||||
@ -0,0 +1,108 @@ |
|||||||
|
# Working with GRASP Servers |
||||||
|
|
||||||
|
GitRepublic provides minimal GRASP (Git Repository Announcement and Synchronization Protocol) interoperability for seamless compatibility with GRASP servers. |
||||||
|
|
||||||
|
## What is GRASP? |
||||||
|
|
||||||
|
GRASP is a protocol specification for decentralized git hosting that combines git smart HTTP with Nostr relays. GRASP servers provide git repository hosting with Nostr-based announcements and state management. |
||||||
|
|
||||||
|
## GitRepublic GRASP Support |
||||||
|
|
||||||
|
### What We Support |
||||||
|
|
||||||
|
1. **GRASP Server Detection** |
||||||
|
- Automatically identifies GRASP servers from repository announcements |
||||||
|
- Uses GRASP-01 identification (clone URL pattern + matching `relays` tag) |
||||||
|
- Displays GRASP server status in clone URL reachability |
||||||
|
|
||||||
|
2. **Clone URL Reachability** |
||||||
|
- Tests and displays reachability status for all clone URLs |
||||||
|
- Shows which remotes (including GRASP servers) are accessible |
||||||
|
- Indicates server type (Git, GRASP) |
||||||
|
|
||||||
|
3. **Multi-Remote Synchronization** |
||||||
|
- When you push, automatically syncs to all remotes listed in announcement |
||||||
|
- Includes GRASP servers in sync operations |
||||||
|
- Handles sync failures gracefully |
||||||
|
|
||||||
|
4. **Local Pull Command** |
||||||
|
- Use `gitrep pull-all --merge` to fetch and merge from all remotes |
||||||
|
- Checks reachability first, only pulls from accessible remotes |
||||||
|
- Detects conflicts before merging (aborts unless `--allow-conflicts`) |
||||||
|
- Works with GRASP servers seamlessly |
||||||
|
|
||||||
|
5. **Standard Git Operations** |
||||||
|
- Full compatibility with GRASP servers for clone, push, pull |
||||||
|
- Uses standard git smart HTTP protocol |
||||||
|
- No special configuration needed |
||||||
|
|
||||||
|
### What We Don't Support (By Design) |
||||||
|
|
||||||
|
- **Full GRASP-01 server compliance**: We're not a full GRASP server |
||||||
|
- **GRASP-02 proactive sync**: No server-side hourly pulls (user-controlled via CLI) |
||||||
|
- **GRASP-05 archive mode**: Not implemented |
||||||
|
|
||||||
|
## Using GRASP Servers |
||||||
|
|
||||||
|
### Cloning from GRASP Servers |
||||||
|
|
||||||
|
```bash |
||||||
|
# Clone from a GRASP server (works just like any git server) |
||||||
|
gitrep clone https://grasp.example.com/npub1.../repo.git |
||||||
|
``` |
||||||
|
|
||||||
|
### Pushing to Multiple Remotes |
||||||
|
|
||||||
|
When your repository has both GitRepublic and GRASP servers in clone URLs: |
||||||
|
|
||||||
|
```bash |
||||||
|
# Push to your repo (automatically syncs to all remotes including GRASP) |
||||||
|
gitrep push origin main |
||||||
|
``` |
||||||
|
|
||||||
|
GitRepublic will: |
||||||
|
1. Push to the primary server |
||||||
|
2. Automatically sync to all other remotes (including GRASP servers) |
||||||
|
3. Handle sync failures gracefully |
||||||
|
|
||||||
|
### Pulling from Multiple Remotes |
||||||
|
|
||||||
|
```bash |
||||||
|
# Pull from all remotes including GRASP servers |
||||||
|
gitrep pull-all --merge |
||||||
|
``` |
||||||
|
|
||||||
|
This command: |
||||||
|
- Checks reachability of all remotes |
||||||
|
- Fetches from accessible remotes |
||||||
|
- Merges changes into your current branch |
||||||
|
- Detects conflicts before merging |
||||||
|
|
||||||
|
### Clone URL Reachability |
||||||
|
|
||||||
|
View which remotes (including GRASP servers) are accessible: |
||||||
|
|
||||||
|
- **Web Interface**: Repository page shows reachability status |
||||||
|
- **API**: `GET /api/repos/{npub}/{repo}/clone-urls/reachability` |
||||||
|
- **CLI**: `gitrep repos get <npub> <repo>` shows reachability |
||||||
|
|
||||||
|
## GRASP Server Identification |
||||||
|
|
||||||
|
GRASP servers are identified by: |
||||||
|
- Clone URL pattern matching GRASP conventions |
||||||
|
- Matching `relays` tag in repository announcement |
||||||
|
- Server response indicating GRASP support |
||||||
|
|
||||||
|
GitRepublic automatically detects GRASP servers and displays them appropriately. |
||||||
|
|
||||||
|
## Best Practices |
||||||
|
|
||||||
|
1. **Add GRASP servers to clone URLs**: Include GRASP server URLs in your repository announcement |
||||||
|
2. **Test reachability**: Check that all remotes (including GRASP) are reachable |
||||||
|
3. **Use pull-all**: Regularly pull from all remotes to stay in sync |
||||||
|
4. **Handle conflicts**: Use `--allow-conflicts` if you need to proceed despite conflicts |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [REST API and CLI](./api-and-cli.md) - Multi-remote operations |
||||||
|
- [Specs used](./specs.md) - GRASP documentation links |
||||||
@ -0,0 +1,239 @@ |
|||||||
|
# Managing a Repo |
||||||
|
|
||||||
|
Complete guide to managing repositories in GitRepublic, including all tabs and sections of the repository interface. |
||||||
|
|
||||||
|
## Repository Header |
||||||
|
|
||||||
|
The repository header displays: |
||||||
|
|
||||||
|
- **Repository name and description** |
||||||
|
- **Owner information** (with link to profile) |
||||||
|
- **Visibility badge** (public, unlisted, restricted, private) |
||||||
|
- **Action menu** (three dots): |
||||||
|
- Clone to server (if not already cloned) |
||||||
|
- Fork repository |
||||||
|
- Transfer ownership (owners only) |
||||||
|
- Delete repository (owners only) |
||||||
|
|
||||||
|
## Clone Section |
||||||
|
|
||||||
|
The clone section shows: |
||||||
|
|
||||||
|
- **All clone URLs** for the repository |
||||||
|
- **Reachability status** for each URL (✅ reachable, ❌ unreachable) |
||||||
|
- **Server type** indicators (Git, GRASP) |
||||||
|
- **Copy buttons** for easy URL copying |
||||||
|
- **Tor .onion URL** (if configured) |
||||||
|
|
||||||
|
Clone URLs are extracted from the repository announcement and can include: |
||||||
|
- This server's URL |
||||||
|
- Other GitRepublic instances |
||||||
|
- GRASP servers |
||||||
|
- Other git hosts (GitHub, GitLab, etc.) |
||||||
|
|
||||||
|
## File Tab |
||||||
|
|
||||||
|
Browse and edit files in the repository. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Directory navigation**: Click folders to browse |
||||||
|
- **File viewing**: Click files to view content |
||||||
|
- **File editing**: Click "Edit" to modify files |
||||||
|
- **File creation**: Click "New File" to create files |
||||||
|
- **File deletion**: Click "Delete" to remove files |
||||||
|
- **Raw file access**: Click "Raw" for direct file content |
||||||
|
- **Syntax highlighting**: Automatic for code files |
||||||
|
- **Line numbers**: Easy reference |
||||||
|
- **Search**: Find text in files |
||||||
|
|
||||||
|
### Permissions |
||||||
|
|
||||||
|
- **Read**: Anyone can view public repositories |
||||||
|
- **Write**: Only owners and maintainers can edit |
||||||
|
|
||||||
|
## Commit Tab |
||||||
|
|
||||||
|
View commit history and details. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Commit list**: Chronological list of all commits |
||||||
|
- **Commit details**: Click commits to view: |
||||||
|
- Commit message |
||||||
|
- Author information |
||||||
|
- Files changed |
||||||
|
- Diff view |
||||||
|
- **Commit verification**: Verify commit signatures (Nostr events) |
||||||
|
- **Branch filter**: View commits for specific branches |
||||||
|
- **Diff view**: See what changed in each commit |
||||||
|
|
||||||
|
### Commit Verification |
||||||
|
|
||||||
|
Commits can be verified if they have Nostr signatures (kind 1640): |
||||||
|
- Shows signature validity |
||||||
|
- Displays signer information |
||||||
|
- Links to signature event |
||||||
|
|
||||||
|
## PRs Tab |
||||||
|
|
||||||
|
Manage pull requests. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **PR list**: View all pull requests |
||||||
|
- **PR status**: Open, merged, closed, draft |
||||||
|
- **Create PR**: Propose changes from forks |
||||||
|
- **PR details**: View PR description, diff, comments |
||||||
|
- **Merge PR**: Merge PRs into target branch (maintainers only) |
||||||
|
- **Update PR status**: Close, reopen, mark as draft |
||||||
|
|
||||||
|
### Creating Pull Requests |
||||||
|
|
||||||
|
1. Fork the repository (if needed) |
||||||
|
2. Make changes in your fork |
||||||
|
3. Push changes to a branch |
||||||
|
4. Navigate to original repository |
||||||
|
5. Click "Create Pull Request" |
||||||
|
6. Fill in details and submit |
||||||
|
|
||||||
|
### PR Status Management |
||||||
|
|
||||||
|
- **Open**: Active PR ready for review |
||||||
|
- **Merged**: PR has been merged |
||||||
|
- **Closed**: PR closed without merging |
||||||
|
- **Draft**: Work in progress |
||||||
|
|
||||||
|
## Issues Tab |
||||||
|
|
||||||
|
Track bugs, feature requests, and tasks. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Issue list**: View all issues |
||||||
|
- **Issue status**: Open, resolved, closed, draft |
||||||
|
- **Create issue**: Report bugs or request features |
||||||
|
- **Issue details**: View description, comments, status |
||||||
|
- **Update status**: Resolve, close, reopen issues |
||||||
|
|
||||||
|
### Issue Status |
||||||
|
|
||||||
|
- **Open**: Active issue needing attention |
||||||
|
- **Resolved**: Issue has been fixed |
||||||
|
- **Closed**: Issue closed (duplicate, won't fix, etc.) |
||||||
|
- **Draft**: Issue still being written |
||||||
|
|
||||||
|
## Patches Tab |
||||||
|
|
||||||
|
View and manage patches (kind 1617). |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Patch list**: View all patches |
||||||
|
- **Patch status**: Open, applied, closed, draft |
||||||
|
- **Create patch**: Submit patch content directly |
||||||
|
- **Apply patch**: Apply patches to repository (maintainers only) |
||||||
|
- **Patch series**: View linked patch series |
||||||
|
|
||||||
|
### Patches vs Pull Requests |
||||||
|
|
||||||
|
- **Patches**: For small changes (< 60KB), email-style workflow |
||||||
|
- **Pull Requests**: For large changes, branch-based workflow |
||||||
|
|
||||||
|
## Discussions Tab |
||||||
|
|
||||||
|
View discussions and comments. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Discussion list**: View all discussions |
||||||
|
- **Comments**: Threaded comments on PRs, issues, highlights |
||||||
|
- **Code highlights**: Highlighted code sections with comments |
||||||
|
- **Reply**: Reply to comments and highlights |
||||||
|
|
||||||
|
### Discussion Types |
||||||
|
|
||||||
|
- **PR Comments**: Comments on pull requests |
||||||
|
- **Issue Comments**: Comments on issues |
||||||
|
- **Code Highlights**: Comments on specific code sections |
||||||
|
- **General Discussions**: Standalone discussion threads |
||||||
|
|
||||||
|
## Docs Tab |
||||||
|
|
||||||
|
View documentation files. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Documentation list**: View all documentation files |
||||||
|
- **Markdown rendering**: Automatic markdown rendering |
||||||
|
- **Documentation addresses**: Links to external documentation (naddr format) |
||||||
|
|
||||||
|
Documentation can be: |
||||||
|
- **README files**: Automatically rendered |
||||||
|
- **Documentation files**: Listed in repository announcement |
||||||
|
- **External docs**: Referenced via naddr |
||||||
|
|
||||||
|
## History Tab |
||||||
|
|
||||||
|
View repository history. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Commit timeline**: Visual timeline of commits |
||||||
|
- **Branch visualization**: See branch structure |
||||||
|
- **Tag markers**: See where tags were created |
||||||
|
- **Filter by branch**: View history for specific branches |
||||||
|
|
||||||
|
## Tags Tab |
||||||
|
|
||||||
|
Manage git tags. |
||||||
|
|
||||||
|
### Features |
||||||
|
|
||||||
|
- **Tag list**: View all tags |
||||||
|
- **Tag details**: View tag name, commit, message |
||||||
|
- **Create tag**: Create new tags |
||||||
|
- **Tag releases**: Use tags for version releases |
||||||
|
|
||||||
|
### Creating Tags |
||||||
|
|
||||||
|
#### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to Tags tab |
||||||
|
2. Click "Create Tag" |
||||||
|
3. Enter tag name |
||||||
|
4. Select commit |
||||||
|
5. Add message (optional) |
||||||
|
6. Click "Create" |
||||||
|
|
||||||
|
#### Via Git |
||||||
|
|
||||||
|
```bash |
||||||
|
git tag -a v1.0.0 -m "Release version 1.0.0" |
||||||
|
git push origin v1.0.0 |
||||||
|
``` |
||||||
|
|
||||||
|
## Repository Settings |
||||||
|
|
||||||
|
Access repository settings via the repository menu or Settings link. |
||||||
|
|
||||||
|
### Available Settings |
||||||
|
|
||||||
|
- **Description**: Update repository description |
||||||
|
- **Visibility**: Change visibility level (public, unlisted, restricted, private) |
||||||
|
- **Project Relays**: Configure project relays for event publishing |
||||||
|
- **Maintainers**: Add/remove maintainers |
||||||
|
- **Clone URLs**: Add/remove clone URLs |
||||||
|
- **Ownership Transfer**: Transfer repository to another user |
||||||
|
|
||||||
|
### Visibility Levels |
||||||
|
|
||||||
|
- **Public**: Repository and events published to all relays |
||||||
|
- **Unlisted**: Repository public, events only to project relay |
||||||
|
- **Restricted**: Repository private, events only to project relay |
||||||
|
- **Private**: Repository private, no relay publishing |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Search and viewing external git repos](./search-and-external-repos.md) - Finding repositories |
||||||
|
- [REST API and CLI](./api-and-cli.md) - Programmatic management |
||||||
@ -0,0 +1,86 @@ |
|||||||
|
# How GitRepublic Integrates Nostr |
||||||
|
|
||||||
|
GitRepublic is built entirely on Nostr, using Nostr events and relays for repository announcements, authentication, and collaboration. This page explains how the git server integrates with Nostr. |
||||||
|
|
||||||
|
## Core Integration Points |
||||||
|
|
||||||
|
### Repository Announcements (NIP-34) |
||||||
|
|
||||||
|
Repositories are announced via Nostr events (kind 30617) that are published to Nostr relays. These announcements contain: |
||||||
|
|
||||||
|
- Repository metadata (name, description, clone URLs) |
||||||
|
- Visibility settings (public, unlisted, restricted, private) |
||||||
|
- Project relays for event publishing |
||||||
|
- Maintainer information |
||||||
|
- Clone URLs pointing to git servers |
||||||
|
|
||||||
|
The server automatically polls Nostr relays for new repository announcements and provisions git repositories when found. |
||||||
|
|
||||||
|
### Authentication |
||||||
|
|
||||||
|
GitRepublic uses two Nostr authentication methods: |
||||||
|
|
||||||
|
1. **NIP-07** - Browser extension authentication for web interface |
||||||
|
- Users connect via browser extensions (Alby, nos2x, etc.) |
||||||
|
- No private keys stored on server |
||||||
|
- Secure, user-controlled authentication |
||||||
|
|
||||||
|
2. **NIP-98** - HTTP authentication for git operations |
||||||
|
- Ephemeral events (kind 27235) for each git operation |
||||||
|
- Includes request URL, method, and payload hash |
||||||
|
- Verified by server before allowing operations |
||||||
|
- Used for clone, push, pull operations |
||||||
|
|
||||||
|
### Event Publishing |
||||||
|
|
||||||
|
All repository-related events are published to Nostr relays based on repository visibility: |
||||||
|
|
||||||
|
- **Public**: Published to all default relays + project relays |
||||||
|
- **Unlisted**: Published only to project relays |
||||||
|
- **Restricted**: Published only to project relays |
||||||
|
- **Private**: Not published to relays (git-only, stored locally) |
||||||
|
|
||||||
|
Events are always saved to the repository's `nostr/repo-events.jsonl` file for offline papertrail, regardless of visibility. |
||||||
|
|
||||||
|
### Ownership and Permissions |
||||||
|
|
||||||
|
Repository ownership is tracked via Nostr events: |
||||||
|
|
||||||
|
- **Self-transfer events** (kind 1641) prove initial ownership |
||||||
|
- **Ownership transfer events** (kind 1641) create a chain of ownership |
||||||
|
- **Maintainer information** stored in repository announcements |
||||||
|
- All ownership events saved to repository for verification |
||||||
|
|
||||||
|
### Collaboration Events |
||||||
|
|
||||||
|
All collaboration features use Nostr events: |
||||||
|
|
||||||
|
- **Pull Requests** (kind 1618) |
||||||
|
- **Pull Request Updates** (kind 1619) |
||||||
|
- **Issues** (kind 1621) |
||||||
|
- **Status Events** (kinds 1630-1633) |
||||||
|
- **Patches** (kind 1617) |
||||||
|
- **Highlights** (kind 9802, NIP-84) |
||||||
|
- **Comments** (kind 1111, NIP-22) |
||||||
|
|
||||||
|
These events are published to relays (based on visibility) and stored in the repository. |
||||||
|
|
||||||
|
## Event Flow |
||||||
|
|
||||||
|
1. **User creates repository announcement** → Published to Nostr relays |
||||||
|
2. **Server polls relays** → Finds announcement → Provisions git repository |
||||||
|
3. **User pushes changes** → Server verifies NIP-98 auth → Processes push |
||||||
|
4. **Collaboration events created** → Published to relays → Stored in repo |
||||||
|
5. **Other users discover events** → Via relays or repository storage |
||||||
|
|
||||||
|
## Benefits of Nostr Integration |
||||||
|
|
||||||
|
- **Decentralized**: No single point of failure |
||||||
|
- **Censorship Resistant**: Events stored on multiple relays |
||||||
|
- **User Controlled**: Users own their keys and data |
||||||
|
- **Interoperable**: Works with any Nostr client |
||||||
|
- **Offline Papertrail**: All events saved to repository |
||||||
|
|
||||||
|
## Technical Details |
||||||
|
|
||||||
|
For complete technical details on event kinds, tags, and structure, see [Specs Used](./specs.md). |
||||||
@ -0,0 +1,111 @@ |
|||||||
|
# Profile Pages |
||||||
|
|
||||||
|
User profiles in GitRepublic display comprehensive user information, repositories, and payment targets. |
||||||
|
|
||||||
|
## Viewing Profiles |
||||||
|
|
||||||
|
Navigate to a user's profile page: |
||||||
|
``` |
||||||
|
https://{domain}/users/{npub} |
||||||
|
``` |
||||||
|
|
||||||
|
## Profile Information |
||||||
|
|
||||||
|
Profiles display: |
||||||
|
|
||||||
|
- **User name**: From Nostr profile event (kind 0) |
||||||
|
- **About/Bio**: User description |
||||||
|
- **Profile picture**: Avatar image |
||||||
|
- **Banner image**: Profile banner (if available) |
||||||
|
- **Payment targets**: Lightning addresses and payment information (NIP-A3) |
||||||
|
- **Repositories**: List of user's repositories |
||||||
|
- **Activity**: Recent activity and contributions |
||||||
|
|
||||||
|
## Payment Targets (NIP-A3) |
||||||
|
|
||||||
|
GitRepublic supports payment targets using NIP-A3 (kind 10133) and merges payment information from multiple sources. |
||||||
|
|
||||||
|
### Supported Payment Types |
||||||
|
|
||||||
|
- **Lightning**: Lightning Network addresses (e.g., `user@wallet.example.com`) |
||||||
|
- **Bitcoin**: Bitcoin addresses |
||||||
|
- **Ethereum**: Ethereum addresses |
||||||
|
- **Nano**: Nano addresses |
||||||
|
- **Monero**: Monero addresses |
||||||
|
- And more (see [NIP-A3 documentation](./NIP-A3.md)) |
||||||
|
|
||||||
|
### Payment Target Sources |
||||||
|
|
||||||
|
GitRepublic merges payment information from: |
||||||
|
|
||||||
|
1. **NIP-01 (kind 0)**: Lightning addresses from `lud16` tags or JSON `lud16` field |
||||||
|
2. **NIP-A3 (kind 10133)**: All payment targets from `payto` tags |
||||||
|
|
||||||
|
The system: |
||||||
|
- Normalizes addresses (lowercase) for deduplication |
||||||
|
- Merges lightning addresses from both sources |
||||||
|
- Displays all payment targets with `payto://` URIs |
||||||
|
- Provides copy buttons for easy sharing |
||||||
|
|
||||||
|
### Creating Payment Target Events |
||||||
|
|
||||||
|
To add payment targets to your profile, publish a kind 10133 event: |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"kind": 10133, |
||||||
|
"content": "", |
||||||
|
"tags": [ |
||||||
|
["payto", "lightning", "user@wallet.example.com"], |
||||||
|
["payto", "bitcoin", "bc1qxq66e0t8d7ugdecwnmv58e90tpry23nc84pg9k"] |
||||||
|
], |
||||||
|
"created_at": 1234567890 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Repository Listings |
||||||
|
|
||||||
|
User profiles show: |
||||||
|
|
||||||
|
- **All repositories**: Public and user's own repositories |
||||||
|
- **Repository status**: Registered, local, verified |
||||||
|
- **Repository descriptions**: Quick overview |
||||||
|
- **Clone URLs**: Direct access to repositories |
||||||
|
|
||||||
|
## API Access |
||||||
|
|
||||||
|
Fetch user profiles via API: |
||||||
|
|
||||||
|
```bash |
||||||
|
GET /api/users/{npub}/profile |
||||||
|
``` |
||||||
|
|
||||||
|
Response includes: |
||||||
|
- Full profile event (kind 0) |
||||||
|
- Payment targets array with `payto://` URIs |
||||||
|
- Payment event (kind 10133) if available |
||||||
|
- Merged lightning addresses |
||||||
|
|
||||||
|
## CLI Access |
||||||
|
|
||||||
|
The GitRepublic CLI can fetch profiles: |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep profile fetch <npub> |
||||||
|
``` |
||||||
|
|
||||||
|
Automatically merges payment targets and returns `payto://` URIs. |
||||||
|
|
||||||
|
## Profile Event Format |
||||||
|
|
||||||
|
GitRepublic supports both profile event formats: |
||||||
|
|
||||||
|
- **Old format**: JSON in `content` field |
||||||
|
- **New format**: Tags-based format (recommended) |
||||||
|
|
||||||
|
Both formats are supported for backward compatibility. |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Settings and Dashboard](./settings-and-dashboard.md) - User account management |
||||||
|
- [NIP-A3 documentation](./NIP-A3.md) - Complete payment target details |
||||||
@ -0,0 +1,153 @@ |
|||||||
|
# Creating, Forking, Transferring, or Cloning a Repo |
||||||
|
|
||||||
|
This page covers all operations for creating, forking, transferring ownership, and cloning repositories in GitRepublic. |
||||||
|
|
||||||
|
## Creating a Repository |
||||||
|
|
||||||
|
### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to the **Sign Up** page (`/signup`) |
||||||
|
2. Connect your NIP-07 browser extension |
||||||
|
3. Fill in repository details: |
||||||
|
- **Repository Name**: Choose a unique name |
||||||
|
- **Description**: Describe your repository |
||||||
|
- **Visibility**: Select visibility level (public, unlisted, restricted, private) |
||||||
|
- **Project Relays**: Add project relays (required for unlisted/restricted) |
||||||
|
- **Clone URLs**: Add existing clone URLs if migrating |
||||||
|
- **Maintainers**: Add maintainer npubs (optional) |
||||||
|
4. Click **Publish Repository Announcement** |
||||||
|
|
||||||
|
The server will automatically: |
||||||
|
- Create a Nostr event announcing your repository |
||||||
|
- Provision a bare git repository |
||||||
|
- Create a self-transfer event for ownership proof |
||||||
|
- Publish events to relays (based on visibility) |
||||||
|
- Save events to the repository |
||||||
|
|
||||||
|
### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep publish repo-announcement <repo-name> \ |
||||||
|
--description "My repository" \ |
||||||
|
--clone-url "https://domain.com/api/git/npub1.../repo.git" \ |
||||||
|
--visibility public |
||||||
|
``` |
||||||
|
|
||||||
|
See `gitrep --help` for complete CLI documentation. |
||||||
|
|
||||||
|
### Repository URL Structure |
||||||
|
|
||||||
|
Your repository will be accessible at: |
||||||
|
- Web: `https://{domain}/repos/{your-npub}/{repository-name}` |
||||||
|
- Git: `https://{domain}/api/git/{your-npub}/{repository-name}.git` (recommended) |
||||||
|
|
||||||
|
## Forking a Repository |
||||||
|
|
||||||
|
Forking creates your own copy of a repository that you can modify independently. |
||||||
|
|
||||||
|
### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to the repository you want to fork |
||||||
|
2. Click the **Fork** button |
||||||
|
3. Enter a name for your fork |
||||||
|
4. Click **Fork** |
||||||
|
|
||||||
|
GitRepublic will: |
||||||
|
- Clone the repository to your account |
||||||
|
- Create a new repository announcement for your fork |
||||||
|
- Set you as the owner |
||||||
|
- Preserve visibility and project-relay settings from original |
||||||
|
- Add a reference to the original repository |
||||||
|
|
||||||
|
### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep repos fork <owner-npub> <repo-name> |
||||||
|
``` |
||||||
|
|
||||||
|
### Working with Forks |
||||||
|
|
||||||
|
After forking: |
||||||
|
- Clone your fork: `git clone https://{domain}/api/git/{your-npub}/{fork-name}.git` |
||||||
|
- Make changes and push to your fork |
||||||
|
- Create a pull request back to the original repository |
||||||
|
|
||||||
|
## Transferring Ownership |
||||||
|
|
||||||
|
Transfer repository ownership to another user. |
||||||
|
|
||||||
|
### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to your repository |
||||||
|
2. Click **Transfer Ownership** (in repository menu) |
||||||
|
3. Enter the new owner's npub |
||||||
|
4. Confirm the transfer |
||||||
|
|
||||||
|
The transfer process: |
||||||
|
1. Creates an ownership transfer event (kind 1641) |
||||||
|
2. Publishes to Nostr relays |
||||||
|
3. Saves to repository for verification |
||||||
|
4. Notifies new owner when they log in |
||||||
|
5. New owner completes transfer by publishing new announcement |
||||||
|
|
||||||
|
### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep repos transfer <npub> <repo> <new-owner-npub> |
||||||
|
``` |
||||||
|
|
||||||
|
**Important**: Ownership transfers are permanent and create a chain of ownership events. The new owner will have full control. |
||||||
|
|
||||||
|
## Cloning a Repository |
||||||
|
|
||||||
|
### Public Repositories |
||||||
|
|
||||||
|
Anyone can clone public repositories: |
||||||
|
|
||||||
|
```bash |
||||||
|
git clone https://{domain}/api/git/{owner-npub}/{repo-name}.git |
||||||
|
``` |
||||||
|
|
||||||
|
### Private/Restricted Repositories |
||||||
|
|
||||||
|
Private and restricted repositories require authentication: |
||||||
|
|
||||||
|
1. **Install GitRepublic CLI**: |
||||||
|
```bash |
||||||
|
npm install -g gitrepublic-cli |
||||||
|
``` |
||||||
|
|
||||||
|
2. **Set your Nostr private key**: |
||||||
|
```bash |
||||||
|
export NOSTRGIT_SECRET_KEY="nsec1..." |
||||||
|
``` |
||||||
|
|
||||||
|
3. **Run setup**: |
||||||
|
```bash |
||||||
|
gitrep-setup |
||||||
|
``` |
||||||
|
|
||||||
|
4. **Clone the repository**: |
||||||
|
```bash |
||||||
|
git clone https://{domain}/api/git/{owner-npub}/{repo-name}.git |
||||||
|
``` |
||||||
|
|
||||||
|
The credential helper will automatically generate NIP-98 authentication tokens. |
||||||
|
|
||||||
|
### Clone URL Paths |
||||||
|
|
||||||
|
GitRepublic supports multiple clone URL paths: |
||||||
|
- `/api/git/{npub}/{repo}.git` - Recommended (best compatibility) |
||||||
|
- `/repos/{npub}/{repo}.git` - Alternative path |
||||||
|
- `/{npub}/{repo}.git` - Root path |
||||||
|
|
||||||
|
All paths work, but `/api/git/` is recommended for best compatibility with commit signing hooks. |
||||||
|
|
||||||
|
## Multi-Remote Synchronization |
||||||
|
|
||||||
|
If a repository has multiple clone URLs configured, GitRepublic automatically syncs changes to all remotes when you push. You can see all clone URLs on the repository page. |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Editing a repo](./editing-repos.md) - Learn about branch management and file operations |
||||||
|
- [Managing a repo](./managing-repos.md) - Complete repository management guide |
||||||
@ -0,0 +1,110 @@ |
|||||||
|
# Search and Viewing External Git Repos |
||||||
|
|
||||||
|
This page covers searching for repositories and viewing external git repositories as read-only. |
||||||
|
|
||||||
|
## Searching Repositories |
||||||
|
|
||||||
|
### Via Web Interface |
||||||
|
|
||||||
|
1. Navigate to the **Search** page (`/search`) |
||||||
|
2. Enter search query |
||||||
|
3. View results with: |
||||||
|
- Repository name and description |
||||||
|
- Owner information |
||||||
|
- Clone URLs |
||||||
|
- Visibility status |
||||||
|
|
||||||
|
### Via API |
||||||
|
|
||||||
|
```bash |
||||||
|
GET /api/search?q={query} |
||||||
|
``` |
||||||
|
|
||||||
|
### Via CLI |
||||||
|
|
||||||
|
```bash |
||||||
|
gitrep search <query> |
||||||
|
``` |
||||||
|
|
||||||
|
### Search Features |
||||||
|
|
||||||
|
- **Name search**: Find repositories by name |
||||||
|
- **Description search**: Find by description content |
||||||
|
- **Owner search**: Find repositories by owner |
||||||
|
- **Tag search**: Find by repository tags |
||||||
|
|
||||||
|
Search queries repositories from Nostr relays and returns matching results. |
||||||
|
|
||||||
|
## Viewing External Git Repos |
||||||
|
|
||||||
|
GitRepublic can display external git repositories as read-only, even if they're not announced on Nostr. |
||||||
|
|
||||||
|
### How It Works |
||||||
|
|
||||||
|
1. **Repository Detection**: If a repository has clone URLs pointing to external git hosts (GitHub, GitLab, etc.) |
||||||
|
2. **API Fallback**: GitRepublic attempts to fetch repository data via the external host's API |
||||||
|
3. **Read-Only Display**: Repository is displayed with limited functionality: |
||||||
|
- View files and directories |
||||||
|
- View commit history |
||||||
|
- View branches and tags |
||||||
|
- **Cannot**: Edit files, create PRs, or push changes |
||||||
|
|
||||||
|
### Supported External Hosts |
||||||
|
|
||||||
|
GitRepublic supports API fallback for: |
||||||
|
- **GitHub**: Via GitHub API |
||||||
|
- **GitLab**: Via GitLab API |
||||||
|
- **Other git hosts**: If they provide compatible APIs |
||||||
|
|
||||||
|
### Limitations |
||||||
|
|
||||||
|
When viewing external repos: |
||||||
|
- **Read-only**: No editing or pushing |
||||||
|
- **Limited features**: Some features may not be available |
||||||
|
- **API dependent**: Requires external host's API to be accessible |
||||||
|
- **No local clone**: Repository is not cloned to server |
||||||
|
|
||||||
|
### Clone URLs |
||||||
|
|
||||||
|
External repositories are identified by their clone URLs. If a repository announcement includes clone URLs pointing to external hosts, GitRepublic will attempt to display them. |
||||||
|
|
||||||
|
## Repository Discovery |
||||||
|
|
||||||
|
### Public Repositories |
||||||
|
|
||||||
|
Public repositories are discoverable via: |
||||||
|
- **Search**: Search across Nostr relays |
||||||
|
- **User profiles**: Browse user's repositories |
||||||
|
- **Repository listings**: View all public repositories |
||||||
|
|
||||||
|
### Unlisted Repositories |
||||||
|
|
||||||
|
Unlisted repositories: |
||||||
|
- **Not in search results**: Won't appear in general search |
||||||
|
- **Accessible if you know the URL**: Can be accessed directly |
||||||
|
- **Events only to project relay**: Collaboration events only published to project relay |
||||||
|
|
||||||
|
### Restricted/Private Repositories |
||||||
|
|
||||||
|
Restricted and private repositories: |
||||||
|
- **Not discoverable**: Won't appear in search |
||||||
|
- **Require authentication**: Must be owner or maintainer to access |
||||||
|
- **Limited event publishing**: Events only to project relay (restricted) or not at all (private) |
||||||
|
|
||||||
|
## Clone URL Reachability |
||||||
|
|
||||||
|
GitRepublic tests clone URL reachability and displays status: |
||||||
|
|
||||||
|
- **✅ Reachable**: Server responds and is accessible |
||||||
|
- **❌ Unreachable**: Server not accessible or returns error |
||||||
|
- **Server type**: Indicates if it's a Git server or GRASP server |
||||||
|
|
||||||
|
View reachability via: |
||||||
|
- **Web Interface**: Repository page shows reachability for all clone URLs |
||||||
|
- **API**: `GET /api/repos/{npub}/{repo}/clone-urls/reachability` |
||||||
|
- **CLI**: `gitrep repos get <npub> <repo>` shows reachability |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [REST API and CLI](./api-and-cli.md) - Programmatic search and access |
||||||
|
- [Profile pages](./profile-pages.md) - Browse user repositories |
||||||
@ -0,0 +1,90 @@ |
|||||||
|
# Settings and Dashboard |
||||||
|
|
||||||
|
User account settings and dashboard features in GitRepublic. |
||||||
|
|
||||||
|
## User Dashboard |
||||||
|
|
||||||
|
Access your dashboard at `/user/dashboard` or via the user menu. |
||||||
|
|
||||||
|
### Dashboard Features |
||||||
|
|
||||||
|
- **Repository overview**: Quick view of your repositories |
||||||
|
- **Activity summary**: Recent activity and contributions |
||||||
|
- **Pending transfers**: Ownership transfers waiting for your approval |
||||||
|
- **Access level**: Your current access level (unlimited, standard, etc.) |
||||||
|
- **Quick actions**: Common operations |
||||||
|
|
||||||
|
## User Settings |
||||||
|
|
||||||
|
### Access Level |
||||||
|
|
||||||
|
View your access level: |
||||||
|
- **Unlimited**: Full access to all features |
||||||
|
- **Standard**: Standard user access |
||||||
|
- **Limited**: Restricted access |
||||||
|
|
||||||
|
Access level determines: |
||||||
|
- Repository creation limits |
||||||
|
- API rate limits |
||||||
|
- Feature availability |
||||||
|
|
||||||
|
### Messaging Preferences |
||||||
|
|
||||||
|
Configure messaging and notification preferences: |
||||||
|
|
||||||
|
- **Enable/disable messaging**: Control message forwarding |
||||||
|
- **Relay preferences**: Configure preferred relays for messages |
||||||
|
- **Notification settings**: Control what you're notified about |
||||||
|
|
||||||
|
Access via: |
||||||
|
- **Web Interface**: User settings menu |
||||||
|
- **API**: `GET /api/user/messaging-preferences` |
||||||
|
- **CLI**: Via API commands |
||||||
|
|
||||||
|
### Git Dashboard |
||||||
|
|
||||||
|
View git-specific information: |
||||||
|
|
||||||
|
- **Repository statistics**: Count, size, activity |
||||||
|
- **Clone operations**: Recent clone activity |
||||||
|
- **Push operations**: Recent push activity |
||||||
|
|
||||||
|
Access via: |
||||||
|
- **Web Interface**: User dashboard |
||||||
|
- **API**: `GET /api/user/git-dashboard` |
||||||
|
|
||||||
|
## Repository Settings |
||||||
|
|
||||||
|
Repository-specific settings are managed on the repository page (see [Managing a Repo](./managing-repos.md)). |
||||||
|
|
||||||
|
### Available Settings |
||||||
|
|
||||||
|
- **Description**: Update repository description |
||||||
|
- **Visibility**: Change visibility level |
||||||
|
- **Project Relays**: Configure project relays |
||||||
|
- **Maintainers**: Add/remove maintainers |
||||||
|
- **Clone URLs**: Manage clone URLs |
||||||
|
- **Branch Protection**: Configure branch protection rules |
||||||
|
- **Ownership Transfer**: Transfer repository ownership |
||||||
|
|
||||||
|
## SSH Keys |
||||||
|
|
||||||
|
GitRepublic supports SSH key verification for git operations (optional). |
||||||
|
|
||||||
|
### Verifying SSH Keys |
||||||
|
|
||||||
|
- **API**: `GET /api/user/ssh-keys/verify` |
||||||
|
- **Purpose**: Verify SSH keys for git operations |
||||||
|
|
||||||
|
## Environment Variables |
||||||
|
|
||||||
|
For CLI operations, configure these environment variables: |
||||||
|
|
||||||
|
- `NOSTRGIT_SECRET_KEY`: Your Nostr private key (required for CLI) |
||||||
|
- `GITREPUBLIC_SERVER`: Default server URL (optional) |
||||||
|
- `NOSTR_RELAYS`: Comma-separated relay URLs (optional) |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Managing a Repo](./managing-repos.md) - Repository-specific settings |
||||||
|
- [REST API and CLI](./api-and-cli.md) - Programmatic access to settings |
||||||
@ -0,0 +1,186 @@ |
|||||||
|
# Specs Used |
||||||
|
|
||||||
|
Complete list of Nostr Improvement Proposals (NIPs), GRASP specifications, and custom event kinds used by GitRepublic. |
||||||
|
|
||||||
|
## Standard NIPs |
||||||
|
|
||||||
|
### Core Protocol |
||||||
|
|
||||||
|
- **[NIP-01: Basic Protocol Flow](https://github.com/nostr-protocol/nips/blob/master/01.md)** |
||||||
|
- Event structure, signatures, and client-relay communication |
||||||
|
- Foundation for all Nostr events |
||||||
|
|
||||||
|
### Authentication & Identity |
||||||
|
|
||||||
|
- **[NIP-07: Browser Extension Authentication](https://github.com/nostr-protocol/nips/blob/master/07.md)** |
||||||
|
- `window.nostr` capability for browser extensions |
||||||
|
- Primary authentication method for GitRepublic web interface |
||||||
|
- Used for signing all repository-related events |
||||||
|
|
||||||
|
- **[NIP-19: bech32-encoded Entities](https://github.com/nostr-protocol/nips/blob/master/19.md)** |
||||||
|
- bech32 encoding (npub, nsec, note, nevent, naddr) |
||||||
|
- User-friendly display of pubkeys and event references |
||||||
|
- Used throughout the UI for repository URLs and search |
||||||
|
|
||||||
|
- **[NIP-98: HTTP Authentication](https://github.com/nostr-protocol/nips/blob/master/98.md)** |
||||||
|
- HTTP auth events (kind 27235) |
||||||
|
- Authenticates git operations (push, pull, clone) |
||||||
|
- Authenticates API requests |
||||||
|
- Ephemeral events for each request |
||||||
|
|
||||||
|
### Event Management |
||||||
|
|
||||||
|
- **[NIP-09: Event Deletion](https://github.com/nostr-protocol/nips/blob/master/09.md)** |
||||||
|
- Deletion requests (kind 5) |
||||||
|
- Used for cleaning up failed operations |
||||||
|
|
||||||
|
- **[NIP-10: Event References](https://github.com/nostr-protocol/nips/blob/master/10.md)** |
||||||
|
- Event references (e, p tags) |
||||||
|
- Used in patch series for threading patches |
||||||
|
- Used in PR updates and comments |
||||||
|
|
||||||
|
- **[NIP-22: Comments](https://github.com/nostr-protocol/nips/blob/master/22.md)** |
||||||
|
- Comment events (kind 1111) |
||||||
|
- Threaded discussions on PRs, issues, and patches |
||||||
|
- NIP-10 reply threading support |
||||||
|
|
||||||
|
### Git Collaboration |
||||||
|
|
||||||
|
- **[NIP-34: Git Repository Announcements](https://github.com/nostr-protocol/nips/blob/master/34.md)** |
||||||
|
- Complete git collaboration specification |
||||||
|
- **30617**: Repository announcements (replaceable) |
||||||
|
- **30618**: Repository state (replaceable, optional) |
||||||
|
- **1617**: Patches |
||||||
|
- **1618**: Pull requests |
||||||
|
- **1619**: Pull request updates |
||||||
|
- **1621**: Issues |
||||||
|
- **1630**: Status Open |
||||||
|
- **1631**: Status Applied/Merged |
||||||
|
- **1632**: Status Closed |
||||||
|
- **1633**: Status Draft |
||||||
|
|
||||||
|
### Relay & Discovery |
||||||
|
|
||||||
|
- **[NIP-02: Contact List](https://github.com/nostr-protocol/nips/blob/master/02.md)** |
||||||
|
- Contact list (kind 3) |
||||||
|
- Used for repository filtering |
||||||
|
- Fallback for relay discovery |
||||||
|
|
||||||
|
- **[NIP-65: Relay List Metadata](https://github.com/nostr-protocol/nips/blob/master/65.md)** |
||||||
|
- Relay list (kind 10002) |
||||||
|
- Discovers user's preferred relays for publishing events |
||||||
|
- Used to determine which relays to publish to |
||||||
|
|
||||||
|
### Content Features |
||||||
|
|
||||||
|
- **[NIP-84: Highlights](https://github.com/nostr-protocol/nips/blob/master/84.md)** |
||||||
|
- Highlight events (kind 9802) |
||||||
|
- Code selection and review features |
||||||
|
- Extended with file/line tags for code context |
||||||
|
|
||||||
|
### Payment Targets |
||||||
|
|
||||||
|
- **[NIP-A3: Payment Targets](https://github.com/nostr-protocol/nips/blob/master/A3.md)** |
||||||
|
- Payment target events (kind 10133) |
||||||
|
- `payto://` URI scheme (RFC-8905) |
||||||
|
- Supports multiple payment types (Lightning, Bitcoin, Ethereum, etc.) |
||||||
|
|
||||||
|
See [NIP-A3 documentation](./NIP-A3.md) for complete details. |
||||||
|
|
||||||
|
## GRASP Specifications |
||||||
|
|
||||||
|
GitRepublic provides minimal GRASP interoperability: |
||||||
|
|
||||||
|
- **GRASP-01**: Server identification and clone URL patterns |
||||||
|
- Detects GRASP servers from repository announcements |
||||||
|
- Identifies GRASP servers by URL pattern and relay tags |
||||||
|
|
||||||
|
- **GRASP-02**: Proactive synchronization (not implemented) |
||||||
|
- We don't do server-side hourly pulls |
||||||
|
- User-controlled via CLI `pull-all` command |
||||||
|
|
||||||
|
- **GRASP-05**: Archive mode (not implemented) |
||||||
|
|
||||||
|
For GRASP documentation, see the GRASP specification repository. |
||||||
|
|
||||||
|
## Custom Event Kinds |
||||||
|
|
||||||
|
GitRepublic uses custom event kinds not defined in any standard NIP: |
||||||
|
|
||||||
|
### Kind 1640: Commit Signature |
||||||
|
|
||||||
|
Cryptographically sign git commits using Nostr keys. |
||||||
|
|
||||||
|
**Tags**: |
||||||
|
- `commit`: Final commit hash |
||||||
|
- `author`: Author name (first occurrence) |
||||||
|
- `author`: Author email (second occurrence) |
||||||
|
- `message`: Commit message |
||||||
|
- `e`: Optional reference to NIP-98 auth event |
||||||
|
|
||||||
|
**Status**: Custom implementation (may be proposed as NIP in future) |
||||||
|
|
||||||
|
See [Custom Event Kinds](./CustomKinds.md#kind-1640-commit-signature) for complete documentation. |
||||||
|
|
||||||
|
### Kind 1641: Ownership Transfer |
||||||
|
|
||||||
|
Transfer repository ownership from one pubkey to another. |
||||||
|
|
||||||
|
**Tags**: |
||||||
|
- `a`: Repository address (30617:pubkey:repo) |
||||||
|
- `p`: New owner pubkey |
||||||
|
- `d`: Repository name |
||||||
|
- `t`: "self-transfer" marker (for initial ownership proof) |
||||||
|
|
||||||
|
**Status**: Custom implementation (non-replaceable to maintain ownership chain) |
||||||
|
|
||||||
|
See [Custom Event Kinds](./CustomKinds.md#kind-1641-ownership-transfer) for complete documentation. |
||||||
|
|
||||||
|
### Kind 30620: Branch Protection |
||||||
|
|
||||||
|
Enforce branch protection rules (require PRs, reviews, status checks). |
||||||
|
|
||||||
|
**Tags**: |
||||||
|
- `d`: Repository name |
||||||
|
- `a`: Repository identifier |
||||||
|
- `branch`: Branch name and protection settings |
||||||
|
|
||||||
|
**Status**: Custom implementation (replaceable) |
||||||
|
|
||||||
|
See [Custom Event Kinds](./CustomKinds.md#30620---branch_protection) for complete documentation. |
||||||
|
|
||||||
|
## Event Kind Reference |
||||||
|
|
||||||
|
| Kind | Name | NIP | Replaceable | Documentation | |
||||||
|
|------|------|-----|-------------|---------------| |
||||||
|
| 1 | Text Note | NIP-01 | No | [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) | |
||||||
|
| 3 | Contact List | NIP-02 | Yes | [NIP-02](https://github.com/nostr-protocol/nips/blob/master/02.md) | |
||||||
|
| 5 | Deletion Request | NIP-09 | No | [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md) | |
||||||
|
| 1111 | Comment | NIP-22 | No | [NIP-22](https://github.com/nostr-protocol/nips/blob/master/22.md) | |
||||||
|
| 1617 | Patch | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1618 | Pull Request | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1619 | Pull Request Update | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1621 | Issue | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1630 | Status Open | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1631 | Status Applied | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1632 | Status Closed | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 1633 | Status Draft | NIP-34 | No | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| **1640** | **Commit Signature** | **Custom** | **No** | **[CustomKinds.md](./CustomKinds.md)** | |
||||||
|
| **1641** | **Ownership Transfer** | **Custom** | **No** | **[CustomKinds.md](./CustomKinds.md)** | |
||||||
|
| 30617 | Repo Announcement | NIP-34 | Yes | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| 30618 | Repo State | NIP-34 | Yes | [NIP-34](https://github.com/nostr-protocol/nips/blob/master/34.md) | |
||||||
|
| **30620** | **Branch Protection** | **Custom** | **Yes** | **[CustomKinds.md](./CustomKinds.md)** | |
||||||
|
| 9802 | Highlight | NIP-84 | No | [NIP-84](https://github.com/nostr-protocol/nips/blob/master/84.md) | |
||||||
|
| 10002 | Relay List | NIP-65 | Yes | [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md) | |
||||||
|
| 10133 | Payment Targets | NIP-A3 | Yes | [NIP-A3](https://github.com/nostr-protocol/nips/blob/master/A3.md) | |
||||||
|
| 27235 | HTTP Auth | NIP-98 | No | [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) | |
||||||
|
|
||||||
|
## Complete Event Documentation |
||||||
|
|
||||||
|
For complete event structure documentation with JSON examples, see: |
||||||
|
- [Custom Event Kinds](./CustomKinds.md) - Custom event documentation |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [How this git server integrates Nostr](./nostr-integration.md) - Understanding the integration |
||||||
|
- [Tech stack used](./tech-stack.md) - Technical implementation |
||||||
@ -0,0 +1,159 @@ |
|||||||
|
# Tech Stack Used |
||||||
|
|
||||||
|
Technical implementation details of GitRepublic, including programming languages, frameworks, libraries, and tools. |
||||||
|
|
||||||
|
## Frontend |
||||||
|
|
||||||
|
### Framework |
||||||
|
- **SvelteKit**: Full-stack framework with SSR support |
||||||
|
- **TypeScript**: Type-safe JavaScript |
||||||
|
- **Svelte 5**: Reactive UI framework |
||||||
|
|
||||||
|
### Code Editor |
||||||
|
- **CodeMirror 6**: Full-featured code editor |
||||||
|
- `@codemirror/autocomplete`: Code autocompletion |
||||||
|
- `@codemirror/basic-setup`: Basic editor setup |
||||||
|
- `@codemirror/commands`: Editor commands |
||||||
|
- `@codemirror/lang-markdown`: Markdown language support |
||||||
|
- `@codemirror/language`: Language support infrastructure |
||||||
|
- `@codemirror/search`: Search and replace functionality |
||||||
|
- `@codemirror/state`: Editor state management |
||||||
|
- `@codemirror/view`: Editor view components |
||||||
|
|
||||||
|
### Syntax Highlighting |
||||||
|
- **highlight.js**: Syntax highlighting for code blocks |
||||||
|
- **CodeMirror language modes**: Language-specific highlighting in editor |
||||||
|
|
||||||
|
### Markdown Processing |
||||||
|
- **markdown-it**: Markdown parser and renderer |
||||||
|
- **asciidoctor**: AsciiDoc support (via codemirror-asciidoc) |
||||||
|
|
||||||
|
### UI Components |
||||||
|
- Custom Svelte components for: |
||||||
|
- Repository browser |
||||||
|
- Code editor |
||||||
|
- Pull request detail view |
||||||
|
- Issue management |
||||||
|
- File tree navigation |
||||||
|
|
||||||
|
## Backend |
||||||
|
|
||||||
|
### Runtime |
||||||
|
- **Node.js**: JavaScript runtime |
||||||
|
- **SvelteKit**: Server-side rendering and API routes |
||||||
|
|
||||||
|
### Git Operations |
||||||
|
- **git-http-backend**: Git smart HTTP protocol handler |
||||||
|
- **simple-git**: Node.js git wrapper for repository operations |
||||||
|
|
||||||
|
### Web Server |
||||||
|
- **SvelteKit adapter-node**: Node.js adapter for production |
||||||
|
- **Vite**: Build tool and dev server |
||||||
|
|
||||||
|
## Nostr Integration |
||||||
|
|
||||||
|
### Nostr Libraries |
||||||
|
- **nostr-tools**: Core Nostr functionality |
||||||
|
- Event creation and signing |
||||||
|
- NIP-19 encoding/decoding (npub, nsec, naddr, etc.) |
||||||
|
- Event verification |
||||||
|
- Relay communication utilities |
||||||
|
|
||||||
|
### WebSocket Client |
||||||
|
- **ws**: WebSocket client for Nostr relay connections |
||||||
|
- Custom **NostrClient**: WebSocket client wrapper for relay operations |
||||||
|
|
||||||
|
### Authentication |
||||||
|
- **NIP-07**: Browser extension integration (client-side) |
||||||
|
- **NIP-98**: HTTP authentication implementation (server-side) |
||||||
|
|
||||||
|
## Logging |
||||||
|
|
||||||
|
### Logger |
||||||
|
- **pino**: Fast, structured JSON logger |
||||||
|
- **pino-pretty**: Human-readable log formatting for development |
||||||
|
|
||||||
|
### Logging Features |
||||||
|
- Structured logging with context |
||||||
|
- Log levels (debug, info, warn, error) |
||||||
|
- Audit logging for security events |
||||||
|
- Performance logging |
||||||
|
|
||||||
|
Logging is configured via environment variables and uses structured JSON logging with pino. |
||||||
|
|
||||||
|
## Networking |
||||||
|
|
||||||
|
### Tor Support |
||||||
|
- **socks**: SOCKS proxy support for Tor connections |
||||||
|
- Tor .onion address support for repositories |
||||||
|
- Tor routing for Nostr relay connections |
||||||
|
|
||||||
|
### Email |
||||||
|
- **nodemailer**: Email sending for notifications (optional) |
||||||
|
|
||||||
|
## Development Tools |
||||||
|
|
||||||
|
### Type Checking |
||||||
|
- **TypeScript**: Static type checking |
||||||
|
- **svelte-check**: Svelte-specific type checking |
||||||
|
|
||||||
|
### Linting and Formatting |
||||||
|
- **ESLint**: JavaScript/TypeScript linting |
||||||
|
- **Prettier**: Code formatting |
||||||
|
- **@typescript-eslint**: TypeScript-specific ESLint rules |
||||||
|
|
||||||
|
### Build Tools |
||||||
|
- **Vite**: Fast build tool and dev server |
||||||
|
- **TypeScript Compiler**: Type checking and compilation |
||||||
|
|
||||||
|
## Programming Languages |
||||||
|
|
||||||
|
- **TypeScript**: Primary language for all code |
||||||
|
- **JavaScript**: Runtime language (compiled from TypeScript) |
||||||
|
- **Svelte**: Component framework (compiled to JavaScript) |
||||||
|
|
||||||
|
## Database/Storage |
||||||
|
|
||||||
|
- **File System**: Git repositories stored on filesystem |
||||||
|
- **JSONL Files**: Event storage in `nostr/repo-events.jsonl` format |
||||||
|
- **No SQL Database**: Uses filesystem and Nostr relays for data storage |
||||||
|
|
||||||
|
## Security |
||||||
|
|
||||||
|
### Authentication Libraries |
||||||
|
- **nostr-tools**: Cryptographic signing and verification |
||||||
|
- Custom NIP-98 implementation for HTTP authentication |
||||||
|
|
||||||
|
### Security Features |
||||||
|
- Path validation |
||||||
|
- Input sanitization |
||||||
|
- Rate limiting |
||||||
|
- Audit logging |
||||||
|
- Process isolation (git-http-backend) |
||||||
|
|
||||||
|
## Deployment |
||||||
|
|
||||||
|
### Containerization |
||||||
|
- **Docker**: Container support |
||||||
|
- **Docker Compose**: Multi-container orchestration |
||||||
|
- **Kubernetes**: Enterprise mode deployment (optional) |
||||||
|
|
||||||
|
### Adapters |
||||||
|
- **@sveltejs/adapter-node**: Node.js production adapter |
||||||
|
|
||||||
|
## CLI Tools |
||||||
|
|
||||||
|
The CLI (`gitrepublic-cli`) uses: |
||||||
|
- **nostr-tools**: Nostr event handling |
||||||
|
- **Node.js**: Runtime environment |
||||||
|
- Native **git**: Git operations via git commands |
||||||
|
|
||||||
|
## Version Requirements |
||||||
|
|
||||||
|
- **Node.js**: 18+ (for both web app and CLI) |
||||||
|
- **Git**: Required for git operations |
||||||
|
- **npm**: Package management |
||||||
|
|
||||||
|
## Next Steps |
||||||
|
|
||||||
|
- [Specs used](./specs.md) - NIPs and GRASP documentation |
||||||
@ -1,907 +0,0 @@ |
|||||||
# GitRepublic Tutorial & Walkthrough |
|
||||||
|
|
||||||
Welcome to GitRepublic! This comprehensive guide will walk you through everything you need to know to get started with decentralized git hosting on Nostr. |
|
||||||
|
|
||||||
## Table of Contents |
|
||||||
|
|
||||||
1. [What is GitRepublic?](#what-is-gitrepublic) |
|
||||||
2. [Getting Started](#getting-started) |
|
||||||
3. [Creating Your First Repository](#creating-your-first-repository) |
|
||||||
4. [Cloning Repositories](#cloning-repositories) |
|
||||||
5. [Making Changes and Pushing](#making-changes-and-pushing) |
|
||||||
6. [Pull Requests](#pull-requests) |
|
||||||
7. [Issues](#issues) |
|
||||||
8. [Forking Repositories](#forking-repositories) |
|
||||||
9. [Updating Repository Information](#updating-repository-information) |
|
||||||
10. [Collaboration Features](#collaboration-features) |
|
||||||
11. [Best Practices](#best-practices) |
|
||||||
12. [Troubleshooting](#troubleshooting) |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## What is GitRepublic? |
|
||||||
|
|
||||||
GitRepublic is a decentralized git hosting platform built on Nostr. Unlike traditional git hosting services, GitRepublic: |
|
||||||
|
|
||||||
- **No central authority**: Your repositories are announced on Nostr relays, making them truly decentralized |
|
||||||
- **Nostr-based authentication**: Uses NIP-07 (browser extensions) and NIP-98 (HTTP authentication) for secure access |
|
||||||
- **Full control**: You own your repositories and can transfer ownership, manage maintainers, and control access |
|
||||||
- **Open collaboration**: Create pull requests, issues, and collaborate with others using Nostr events |
|
||||||
|
|
||||||
### Key Concepts |
|
||||||
|
|
||||||
- **NIP-34**: The Nostr Improvement Proposal that defines how repositories are announced and managed |
|
||||||
- **NIP-07**: Browser extension authentication (like Alby or nos2x) |
|
||||||
- **NIP-98**: HTTP authentication for git operations (clone, push, pull) |
|
||||||
- **Repository Announcements**: Nostr events (kind 30617) that announce your repository to the network |
|
||||||
- **Ownership Transfer**: Chain of ownership events (kind 1641) that prove repository ownership |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Getting Started |
|
||||||
|
|
||||||
### Prerequisites |
|
||||||
|
|
||||||
Before you begin, you'll need: |
|
||||||
|
|
||||||
1. **A Nostr key pair**: You can generate one using a NIP-07 browser extension |
|
||||||
2. **NIP-07 Extension**: Install [Alby](https://getalby.com/) or [nos2x](https://github.com/fiatjaf/nos2x) in your browser |
|
||||||
3. **Git installed**: Make sure you have git installed on your system |
|
||||||
4. **Access to GitRepublic**: Visit the GitRepublic instance you want to use |
|
||||||
|
|
||||||
### Step 1: Install NIP-07 Extension |
|
||||||
|
|
||||||
1. Install a Nostr browser extension: |
|
||||||
- **Alby**: [getalby.com](https://getalby.com/) |
|
||||||
- **nos2x**: Available for Chrome/Firefox |
|
||||||
|
|
||||||
2. Create or import your Nostr key pair in the extension |
|
||||||
|
|
||||||
3. Make sure the extension is active and unlocked |
|
||||||
|
|
||||||
### Step 2: Connect to GitRepublic |
|
||||||
|
|
||||||
1. Visit the GitRepublic homepage |
|
||||||
2. Click the **Login** button in the top right |
|
||||||
3. Approve the connection request in your NIP-07 extension |
|
||||||
4. You should now see your user badge in the header |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Creating Your First Repository |
|
||||||
|
|
||||||
### Using the Web Interface |
|
||||||
|
|
||||||
1. **Navigate to Sign Up**: Click "Sign Up" in the navigation menu |
|
||||||
|
|
||||||
2. **Fill in Repository Details**: |
|
||||||
- **Repository Name**: Choose a unique name (e.g., `my-awesome-project`) |
|
||||||
- **Description**: Add a description of what your repository does |
|
||||||
- **Clone URLs** (optional): If you're migrating from another git host, add the clone URLs here |
|
||||||
- **Private** (optional): Check this if you want a private repository |
|
||||||
|
|
||||||
3. **Create Repository**: Click the create button |
|
||||||
|
|
||||||
4. **What Happens Next**: |
|
||||||
- GitRepublic creates a Nostr event announcing your repository |
|
||||||
- A bare git repository is automatically provisioned |
|
||||||
- You'll be redirected to your new repository page |
|
||||||
|
|
||||||
### Repository URL Structure |
|
||||||
|
|
||||||
Your repository will be accessible at: |
|
||||||
``` |
|
||||||
https://{domain}/repos/{your-npub}/{repository-name} |
|
||||||
``` |
|
||||||
|
|
||||||
For git operations, you can use any of these paths: |
|
||||||
``` |
|
||||||
https://{domain}/api/git/{your-npub}/{repository-name}.git # Recommended |
|
||||||
https://{domain}/repos/{your-npub}/{repository-name}.git |
|
||||||
https://{domain}/{your-npub}/{repository-name}.git |
|
||||||
``` |
|
||||||
|
|
||||||
**Note**: The `/api/git/` path is recommended for best compatibility with commit signing hooks. |
|
||||||
|
|
||||||
### Initial Setup |
|
||||||
|
|
||||||
After creating your repository, you can: |
|
||||||
|
|
||||||
1. **Clone it locally**: |
|
||||||
```bash |
|
||||||
git clone https://{domain}/api/git/{your-npub}/{repository-name}.git |
|
||||||
cd {repository-name} |
|
||||||
``` |
|
||||||
|
|
||||||
2. **Add your first files**: |
|
||||||
```bash |
|
||||||
echo "# My Awesome Project" > README.md |
|
||||||
git add README.md |
|
||||||
git commit -m "Initial commit" |
|
||||||
git push origin main |
|
||||||
``` |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Cloning Repositories |
|
||||||
|
|
||||||
### Public Repositories |
|
||||||
|
|
||||||
Anyone can clone public repositories without authentication: |
|
||||||
|
|
||||||
```bash |
|
||||||
git clone https://{domain}/api/git/{owner-npub}/{repository-name}.git |
|
||||||
cd {repository-name} |
|
||||||
``` |
|
||||||
|
|
||||||
### Private Repositories |
|
||||||
|
|
||||||
Private repositories require authentication. You'll need to set up NIP-98 authentication. |
|
||||||
|
|
||||||
#### Setting Up NIP-98 Authentication |
|
||||||
|
|
||||||
For command-line git operations, you need to install the [GitRepublic CLI](https://github.com/silberengel/gitrepublic-cli) which provides: |
|
||||||
|
|
||||||
1. **Credential Helper**: Automatically generates NIP-98 authentication tokens for git operations |
|
||||||
2. **Commit Signing Hook**: Automatically signs commits for GitRepublic repositories |
|
||||||
|
|
||||||
**Quick Setup**: |
|
||||||
|
|
||||||
1. Install the CLI: |
|
||||||
```bash |
|
||||||
npm install -g gitrepublic-cli |
|
||||||
``` |
|
||||||
|
|
||||||
2. Set your Nostr private key: |
|
||||||
```bash |
|
||||||
export NOSTRGIT_SECRET_KEY="nsec1..." |
|
||||||
``` |
|
||||||
|
|
||||||
3. Run the setup script: |
|
||||||
```bash |
|
||||||
gitrepublic-setup |
|
||||||
``` |
|
||||||
|
|
||||||
This automatically configures the credential helper and commit signing hook. See the README for complete setup instructions. |
|
||||||
|
|
||||||
3. **Clone the private repository**: |
|
||||||
```bash |
|
||||||
git clone https://{domain}/api/git/{owner-npub}/{repository-name}.git |
|
||||||
``` |
|
||||||
|
|
||||||
When prompted, the credential helper will automatically generate and use a NIP-98 authentication token. |
|
||||||
|
|
||||||
**Note**: For command-line git operations, you'll need to install the [GitRepublic CLI](https://github.com/silberengel/gitrepublic-cli) via `npm install -g gitrepublic-cli` and set up the credential helper. See the README for complete setup instructions. |
|
||||||
|
|
||||||
### Cloning from Multiple Remotes |
|
||||||
|
|
||||||
If a repository has multiple clone URLs configured, GitRepublic will automatically sync changes to all remotes when you push. You can see all clone URLs on the repository page. |
|
||||||
|
|
||||||
For information about GRASP (Git Repository Announcement and Synchronization Protocol) support, including how to work with GRASP servers, see [GRASP.md](./GRASP.md). |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Making Changes and Pushing |
|
||||||
|
|
||||||
### Basic Workflow |
|
||||||
|
|
||||||
1. **Make changes to your files**: |
|
||||||
```bash |
|
||||||
echo "New feature" >> README.md |
|
||||||
``` |
|
||||||
|
|
||||||
2. **Stage your changes**: |
|
||||||
```bash |
|
||||||
git add README.md |
|
||||||
``` |
|
||||||
|
|
||||||
3. **Commit your changes**: |
|
||||||
```bash |
|
||||||
git commit -m "Add new feature" |
|
||||||
``` |
|
||||||
|
|
||||||
4. **Push to GitRepublic**: |
|
||||||
```bash |
|
||||||
git push origin main |
|
||||||
``` |
|
||||||
|
|
||||||
### Authentication for Push |
|
||||||
|
|
||||||
When you push, GitRepublic will: |
|
||||||
|
|
||||||
1. Verify your NIP-98 authentication token |
|
||||||
2. Check that you're the repository owner or a maintainer |
|
||||||
3. Verify the repository size limit (2 GB maximum) |
|
||||||
4. Process your push |
|
||||||
5. Automatically sync to other remotes (if configured) |
|
||||||
|
|
||||||
### Branch Management |
|
||||||
|
|
||||||
#### Creating a New Branch |
|
||||||
|
|
||||||
```bash |
|
||||||
git checkout -b feature/new-feature |
|
||||||
# Make changes |
|
||||||
git add . |
|
||||||
git commit -m "Add new feature" |
|
||||||
git push origin feature/new-feature |
|
||||||
``` |
|
||||||
|
|
||||||
#### Viewing Branches |
|
||||||
|
|
||||||
You can view all branches: |
|
||||||
- On the repository web page |
|
||||||
- Using the API: `GET /api/repos/{npub}/{repo}/branches` |
|
||||||
|
|
||||||
#### Creating Branches via Web UI |
|
||||||
|
|
||||||
1. Navigate to your repository |
|
||||||
2. Click "Create Branch" |
|
||||||
3. Enter branch name and select the source branch |
|
||||||
4. Click "Create" |
|
||||||
|
|
||||||
### Tags |
|
||||||
|
|
||||||
#### Creating a Tag |
|
||||||
|
|
||||||
```bash |
|
||||||
git tag -a v1.0.0 -m "Release version 1.0.0" |
|
||||||
git push origin v1.0.0 |
|
||||||
``` |
|
||||||
|
|
||||||
#### Creating Tags via Web UI |
|
||||||
|
|
||||||
1. Navigate to your repository |
|
||||||
2. Click "Create Tag" |
|
||||||
3. Enter tag name, select commit, and add a message |
|
||||||
4. Click "Create" |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Pull Requests |
|
||||||
|
|
||||||
Pull requests (PRs) allow you to propose changes to a repository. They're created as Nostr events (kind 1618) and can be reviewed, commented on, and merged. |
|
||||||
|
|
||||||
### Creating a Pull Request |
|
||||||
|
|
||||||
#### Method 1: Via Web Interface |
|
||||||
|
|
||||||
1. **Fork the repository** (if you don't have write access) |
|
||||||
2. **Make your changes** in your fork |
|
||||||
3. **Push your changes** to a branch |
|
||||||
4. **Navigate to the original repository** |
|
||||||
5. **Click "Create Pull Request"** |
|
||||||
6. **Fill in the details**: |
|
||||||
- Source repository and branch |
|
||||||
- Target repository and branch |
|
||||||
- Title and description |
|
||||||
7. **Submit the PR** |
|
||||||
|
|
||||||
#### Method 2: Using Git and Nostr |
|
||||||
|
|
||||||
1. **Fork and clone the repository**: |
|
||||||
```bash |
|
||||||
git clone https://{domain}/api/git/{owner-npub}/{repo}.git |
|
||||||
cd {repo} |
|
||||||
``` |
|
||||||
|
|
||||||
2. **Create a feature branch**: |
|
||||||
```bash |
|
||||||
git checkout -b feature/my-feature |
|
||||||
``` |
|
||||||
|
|
||||||
3. **Make your changes and push**: |
|
||||||
```bash |
|
||||||
# Make changes |
|
||||||
git add . |
|
||||||
git commit -m "Add new feature" |
|
||||||
git push origin feature/my-feature |
|
||||||
``` |
|
||||||
|
|
||||||
4. **Create a PR event** using a Nostr client or the web interface |
|
||||||
|
|
||||||
### Reviewing Pull Requests |
|
||||||
|
|
||||||
1. **Navigate to the repository** |
|
||||||
2. **Click on "Pull Requests"** |
|
||||||
3. **Select a PR to review** |
|
||||||
4. **Review the changes** in the diff view |
|
||||||
5. **Add comments** on specific lines or sections |
|
||||||
6. **Approve or request changes** |
|
||||||
|
|
||||||
### PR Status |
|
||||||
|
|
||||||
Pull requests can have the following statuses: |
|
||||||
|
|
||||||
- **Open**: The PR is active and ready for review |
|
||||||
- **Applied/Merged**: The PR has been merged into the target branch |
|
||||||
- **Closed**: The PR was closed without merging |
|
||||||
- **Draft**: The PR is still a work in progress |
|
||||||
|
|
||||||
### Merging Pull Requests |
|
||||||
|
|
||||||
Only repository owners and maintainers can merge PRs: |
|
||||||
|
|
||||||
1. **Open the PR detail view** by clicking on a PR |
|
||||||
2. **Review the changes** in the diff view |
|
||||||
3. **Click "Merge"** button |
|
||||||
4. **Select target branch** (default: main) |
|
||||||
5. **Optionally add a merge message** |
|
||||||
6. **Click "Merge"** to: |
|
||||||
- Create a merge commit in the target branch |
|
||||||
- Update PR status to "merged" (kind 1631) |
|
||||||
- Include merge commit ID in the status event |
|
||||||
|
|
||||||
### Managing PR Status |
|
||||||
|
|
||||||
Repository owners and maintainers can manage PR status: |
|
||||||
|
|
||||||
- **Close PR**: Click "Close" button to mark PR as closed (kind 1632) |
|
||||||
- **Reopen PR**: Click "Reopen" button on a closed PR to reopen it (kind 1630) |
|
||||||
- **Mark as Draft**: Click "Mark as Draft" to mark PR as draft (kind 1633) |
|
||||||
|
|
||||||
### PR Updates (Kind 1619) |
|
||||||
|
|
||||||
Only the PR author can update their PR: |
|
||||||
|
|
||||||
- PR updates change the tip commit of the PR |
|
||||||
- Use this when you've pushed new commits to your branch |
|
||||||
- The update creates a kind 1619 event with the new commit ID |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Patches |
|
||||||
|
|
||||||
Patches (kind 1617) are an alternative to pull requests for proposing changes to a repository. They're particularly useful for smaller changes or when you want to send code changes directly without creating a full pull request. |
|
||||||
|
|
||||||
### Patches vs. Pull Requests |
|
||||||
|
|
||||||
**When to use Patches:** |
|
||||||
- **Small changes**: Each patch event should be under 60KB (per NIP-34 specification) |
|
||||||
- **Simple fixes**: Bug fixes, typo corrections, or small feature additions |
|
||||||
- **Direct code submission**: When you want to send code changes directly without maintaining a fork |
|
||||||
- **Email-style workflow**: Similar to traditional git email-based patch workflows |
|
||||||
|
|
||||||
**When to use Pull Requests:** |
|
||||||
- **Large changes**: Changes that exceed 60KB per event |
|
||||||
- **Complex features**: Multi-file changes that need discussion and review |
|
||||||
- **Ongoing collaboration**: When you need to iterate on changes with maintainers |
|
||||||
- **Branch-based workflow**: When you're working with forks and branches |
|
||||||
|
|
||||||
### Key Differences |
|
||||||
|
|
||||||
| Feature | Patches | Pull Requests | |
|
||||||
|---------|---------|---------------| |
|
||||||
| **Size Limit** | Under 60KB per event | No strict limit | |
|
||||||
| **Format** | Git format-patch output | Markdown description + commit reference | |
|
||||||
| **Workflow** | Email-style, sequential | Branch-based, iterative | |
|
||||||
| **Series Support** | Yes (linked via NIP-10 reply tags) | Yes (via PR updates) | |
|
||||||
| **Content** | Full patch content in event | Reference to commits in clone URL | |
|
||||||
| **Use Case** | Small, self-contained changes | Large, complex changes | |
|
||||||
|
|
||||||
### Creating a Patch |
|
||||||
|
|
||||||
wPatches are created as Nostr events (kind 1617). The patch content is embedded directly in the event, making it easy to share and review without requiring access to a git repository. |
|
||||||
|
|
||||||
#### Via Web Interface |
|
||||||
|
|
||||||
1. **Navigate to the repository** you want to contribute to |
|
||||||
2. **Click the repository menu** (three dots) and select "Create Patch" |
|
||||||
3. **Fill in the patch details**: |
|
||||||
- **Subject** (optional): A brief title for your patch |
|
||||||
- **Patch Content**: Enter your patch content (can be in git format-patch format, but any patch format is acceptable) |
|
||||||
4. **Submit the patch** - it will be published as a Nostr event (kind 1617) to the repository's relays |
|
||||||
|
|
||||||
#### Patch Content Format |
|
||||||
|
|
||||||
The patch content can be in any format that describes code changes. Common formats include: |
|
||||||
- **Git format-patch output**: Standard git patch format |
|
||||||
- **Unified diff format**: `git diff` output |
|
||||||
- **Plain text description**: For simple changes, you can describe the changes in plain text |
|
||||||
|
|
||||||
The key advantage of event-based patches is that they're self-contained Nostr events that can be discovered, shared, and reviewed without requiring access to the original git repository. |
|
||||||
|
|
||||||
### Patch Series |
|
||||||
|
|
||||||
For multiple related patches, you can create a patch series: |
|
||||||
|
|
||||||
1. **First patch**: Marked with `t` tag `"root"` |
|
||||||
2. **Subsequent patches**: Include NIP-10 `e` reply tags pointing to the previous patch |
|
||||||
3. **Patch revisions**: If you need to revise a patch, mark the first revision with `t` tag `"root-revision"` and link to the original root patch |
|
||||||
|
|
||||||
### Patch Status |
|
||||||
|
|
||||||
Like pull requests, patches can have status events: |
|
||||||
- **Open**: The patch is active and ready for review |
|
||||||
- **Applied/Merged**: The patch has been applied to the repository |
|
||||||
- **Closed**: The patch was closed without applying |
|
||||||
- **Draft**: The patch is still a work in progress |
|
||||||
|
|
||||||
### Applying Patches |
|
||||||
|
|
||||||
Repository owners and maintainers can apply patches from Nostr events: |
|
||||||
|
|
||||||
1. **Review the patch event** - patches are stored as Nostr events (kind 1617) and can be found on relays |
|
||||||
2. **Extract the patch content** from the event content field |
|
||||||
3. **Apply the patch** using git (if in git format): |
|
||||||
```bash |
|
||||||
# If the patch content is in git format-patch format, save it to a file and apply: |
|
||||||
echo "<patch-content-from-event>" > patch.patch |
|
||||||
git am patch.patch |
|
||||||
``` |
|
||||||
Or manually apply the changes if the patch is in a different format |
|
||||||
4. **Create a status event** (kind 1631) marking the patch as applied, referencing the patch event ID |
|
||||||
5. **Push the changes** to the repository |
|
||||||
|
|
||||||
The status event creates a permanent record that the patch was applied, linking the patch event to the resulting commits. |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Issues |
|
||||||
|
|
||||||
Issues (kind 1621) allow you to track bugs, feature requests, and other tasks related to your repository. |
|
||||||
|
|
||||||
### Creating an Issue |
|
||||||
|
|
||||||
1. **Navigate to your repository** |
|
||||||
2. **Click "Issues"** in the repository menu |
|
||||||
3. **Click "Create Issue"** |
|
||||||
4. **Fill in the details**: |
|
||||||
- Title |
|
||||||
- Description |
|
||||||
- Labels (optional) |
|
||||||
5. **Submit the issue** |
|
||||||
|
|
||||||
### Issue Status |
|
||||||
|
|
||||||
Issues can have the following statuses: |
|
||||||
|
|
||||||
- **Open**: The issue is active and needs attention |
|
||||||
- **Resolved**: The issue has been fixed or addressed |
|
||||||
- **Closed**: The issue was closed (e.g., duplicate, won't fix) |
|
||||||
- **Draft**: The issue is still being written |
|
||||||
|
|
||||||
### Managing Issue Status |
|
||||||
|
|
||||||
Repository owners, maintainers, and issue authors can update issue status: |
|
||||||
|
|
||||||
- **Close Issue**: Click "Close" button to mark issue as closed (kind 1632) |
|
||||||
- **Resolve Issue**: Click "Resolve" button to mark issue as resolved (kind 1631) |
|
||||||
- **Reopen Issue**: Click "Reopen" button on a closed or resolved issue to reopen it (kind 1630) |
|
||||||
- **Mark as Draft**: Mark issue as draft (kind 1633) |
|
||||||
|
|
||||||
### Managing Issues |
|
||||||
|
|
||||||
- **Assign issues** to maintainers |
|
||||||
- **Add comments** to discuss solutions |
|
||||||
- **Link issues to PRs** by referencing them in PR descriptions |
|
||||||
- **Close issues** when they're resolved |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Forking Repositories |
|
||||||
|
|
||||||
Forking creates your own copy of a repository that you can modify independently. |
|
||||||
|
|
||||||
### How to Fork |
|
||||||
|
|
||||||
1. **Navigate to the repository** you want to fork |
|
||||||
2. **Click the "Fork" button** |
|
||||||
3. **GitRepublic will**: |
|
||||||
- Create a copy of the repository under your account |
|
||||||
- Create a new NIP-34 announcement for your fork |
|
||||||
- Set you as the owner of the fork |
|
||||||
- Add a reference to the original repository |
|
||||||
|
|
||||||
### Working with Forks |
|
||||||
|
|
||||||
After forking: |
|
||||||
|
|
||||||
1. **Clone your fork**: |
|
||||||
```bash |
|
||||||
git clone https://{domain}/api/git/{your-npub}/{fork-name}.git |
|
||||||
``` |
|
||||||
|
|
||||||
2. **Make changes** in your fork |
|
||||||
|
|
||||||
3. **Push changes**: |
|
||||||
```bash |
|
||||||
git push origin main |
|
||||||
``` |
|
||||||
|
|
||||||
4. **Create a pull request** back to the original repository (if you want to contribute) |
|
||||||
|
|
||||||
### Syncing with Upstream |
|
||||||
|
|
||||||
To keep your fork up to date with the original repository: |
|
||||||
|
|
||||||
1. **Add the original as a remote**: |
|
||||||
```bash |
|
||||||
git remote add upstream https://{domain}/api/git/{original-npub}/{original-repo}.git |
|
||||||
``` |
|
||||||
|
|
||||||
2. **Fetch and merge**: |
|
||||||
```bash |
|
||||||
git fetch upstream |
|
||||||
git merge upstream/main |
|
||||||
git push origin main |
|
||||||
``` |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Updating Repository Information |
|
||||||
|
|
||||||
To update your repository information, navigate to the signup page with your repository details pre-filled. |
|
||||||
|
|
||||||
### Accessing the Update Form |
|
||||||
|
|
||||||
1. Navigate to your repository page |
|
||||||
2. Click "Settings" (if you're the owner or a maintainer) |
|
||||||
3. You'll be redirected to the signup page with all current repository information pre-filled |
|
||||||
|
|
||||||
### Privacy Settings |
|
||||||
|
|
||||||
- **Public**: Anyone can view and clone your repository |
|
||||||
- **Private**: Only owners and maintainers can access |
|
||||||
|
|
||||||
You can change the privacy setting by checking or unchecking the "Private Repository" option in the update form. |
|
||||||
|
|
||||||
### Maintainer Management |
|
||||||
|
|
||||||
Add maintainers who can: |
|
||||||
- Push to the repository |
|
||||||
- Merge pull requests |
|
||||||
- Manage issues |
|
||||||
- Update repository information |
|
||||||
|
|
||||||
**To add a maintainer**: |
|
||||||
1. Access the update form (via Settings link) |
|
||||||
2. Enter the maintainer's npub in the maintainers field |
|
||||||
3. Click "Add Maintainer" to add additional maintainer fields |
|
||||||
4. Save changes |
|
||||||
|
|
||||||
**To remove a maintainer**: |
|
||||||
1. Access the update form |
|
||||||
2. Remove the maintainer's npub from the maintainers field |
|
||||||
3. Save changes |
|
||||||
|
|
||||||
### Repository Description |
|
||||||
|
|
||||||
Update your repository description: |
|
||||||
1. Access the update form |
|
||||||
2. Edit the description field |
|
||||||
3. Save changes |
|
||||||
|
|
||||||
### Clone URLs |
|
||||||
|
|
||||||
Add multiple clone URLs to sync your repository to other git hosts: |
|
||||||
1. Access the update form |
|
||||||
2. Add clone URLs (one per field) |
|
||||||
3. Save changes |
|
||||||
|
|
||||||
When you push, GitRepublic will automatically sync to all configured remotes. |
|
||||||
|
|
||||||
### Ownership Transfer |
|
||||||
|
|
||||||
Transfer repository ownership to another user using the transfer workflow: |
|
||||||
|
|
||||||
1. **Initiate Transfer**: On your repository page, click "Transfer Ownership" |
|
||||||
2. **Enter New Owner**: Provide the new owner's npub |
|
||||||
3. **Sign and Publish**: The transfer event (kind 1641) is signed and published to Nostr relays |
|
||||||
4. **Save to Repository**: The transfer event is saved to `nostr/repo-events.jsonl` in your repository for offline papertrail |
|
||||||
5. **New Owner Notification**: The new owner will be notified when they log into GitRepublic web |
|
||||||
6. **Complete Transfer**: The new owner completes the transfer by publishing a new repository announcement (kind 30617) |
|
||||||
7. **Verification**: The new announcement is saved to the repository, and the transfer is complete |
|
||||||
|
|
||||||
**Important**: Ownership transfers are permanent and create a chain of ownership events. The new owner will have full control. Both the transfer event and the new repository announcement are published to relays and saved to `nostr/repo-events.jsonl` in the repository for both online and offline papertrail. |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Collaboration Features |
|
||||||
|
|
||||||
### Code Highlights |
|
||||||
|
|
||||||
Highlight specific code sections in pull requests: |
|
||||||
|
|
||||||
1. **Select code** in the PR diff view |
|
||||||
2. **Click "Highlight"** |
|
||||||
3. **Add a comment** explaining the highlight |
|
||||||
4. **Others can comment** on your highlights |
|
||||||
|
|
||||||
### Comments |
|
||||||
|
|
||||||
Comment on: |
|
||||||
- Pull requests |
|
||||||
- Issues |
|
||||||
- Code highlights |
|
||||||
- Specific lines in diffs |
|
||||||
|
|
||||||
Comments are threaded and use Nostr events (kind 1111) for persistence. |
|
||||||
|
|
||||||
### Notifications |
|
||||||
|
|
||||||
GitRepublic uses Nostr events for notifications. You can: |
|
||||||
- Subscribe to repository events |
|
||||||
- Get notified of new PRs, issues, and comments |
|
||||||
- Track changes using your Nostr client |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Best Practices |
|
||||||
|
|
||||||
### Repository Organization |
|
||||||
|
|
||||||
1. **Use descriptive names**: Choose clear, descriptive repository names |
|
||||||
2. **Write good READMEs**: Include installation instructions, usage examples, and contribution guidelines |
|
||||||
3. **Use tags for releases**: Tag important versions (e.g., `v1.0.0`) |
|
||||||
4. **Keep repositories focused**: One repository per project or component |
|
||||||
|
|
||||||
### Commit Messages |
|
||||||
|
|
||||||
Write clear, descriptive commit messages: |
|
||||||
|
|
||||||
```bash |
|
||||||
# Good |
|
||||||
git commit -m "Add user authentication feature" |
|
||||||
|
|
||||||
# Better |
|
||||||
git commit -m "Add user authentication with NIP-07 support |
|
||||||
|
|
||||||
- Implement NIP-07 browser extension authentication |
|
||||||
- Add login/logout functionality |
|
||||||
- Update UI to show user badge when logged in" |
|
||||||
``` |
|
||||||
|
|
||||||
### Branch Strategy |
|
||||||
|
|
||||||
- **main/master**: Production-ready code |
|
||||||
- **feature/**: New features |
|
||||||
- **bugfix/**: Bug fixes |
|
||||||
- **hotfix/**: Urgent production fixes |
|
||||||
|
|
||||||
### Pull Request Guidelines |
|
||||||
|
|
||||||
1. **Keep PRs focused**: One feature or fix per PR |
|
||||||
2. **Write clear descriptions**: Explain what and why, not just what |
|
||||||
3. **Link related issues**: Reference issues in PR descriptions |
|
||||||
4. **Request reviews**: Ask maintainers to review your PRs |
|
||||||
5. **Respond to feedback**: Address review comments promptly |
|
||||||
|
|
||||||
### Security |
|
||||||
|
|
||||||
1. **Keep your keys secure**: Never share your nsec (private key) |
|
||||||
2. **Use NIP-07 extensions**: Don't enter keys directly in web forms |
|
||||||
3. **Review maintainers**: Only add trusted users as maintainers |
|
||||||
4. **Monitor your repositories**: Check for unexpected changes |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Troubleshooting |
|
||||||
|
|
||||||
### Authentication Issues |
|
||||||
|
|
||||||
**Problem**: Can't push to repository |
|
||||||
|
|
||||||
**Solutions**: |
|
||||||
- Verify you're logged in with NIP-07 |
|
||||||
- Check that you're the owner or a maintainer |
|
||||||
- Ensure your NIP-98 authentication is configured correctly |
|
||||||
- Check repository privacy settings |
|
||||||
|
|
||||||
### Clone Fails |
|
||||||
|
|
||||||
**Problem**: Can't clone a repository |
|
||||||
|
|
||||||
**Solutions**: |
|
||||||
- Verify the repository URL is correct |
|
||||||
- Check if the repository is private (requires authentication) |
|
||||||
- Ensure you have network access to the GitRepublic instance |
|
||||||
- Try cloning with verbose output: `git clone -v {url}` |
|
||||||
|
|
||||||
### Push Fails |
|
||||||
|
|
||||||
**Problem**: Push is rejected |
|
||||||
|
|
||||||
**Solutions**: |
|
||||||
- Check repository size limit (2 GB maximum) |
|
||||||
- Verify you have write permissions |
|
||||||
- Ensure your branch is up to date: `git pull origin main` |
|
||||||
- Check for branch protection rules |
|
||||||
|
|
||||||
### Repository Not Found |
|
||||||
|
|
||||||
**Problem**: Repository doesn't appear after creation |
|
||||||
|
|
||||||
**Solutions**: |
|
||||||
- Wait a few moments for auto-provisioning |
|
||||||
- Refresh the page |
|
||||||
- Check that the NIP-34 announcement was published |
|
||||||
- Verify you're looking at the correct domain |
|
||||||
|
|
||||||
### Sync Issues |
|
||||||
|
|
||||||
**Problem**: Changes not syncing to other remotes |
|
||||||
|
|
||||||
**Solutions**: |
|
||||||
- Verify clone URLs are correct in repository settings |
|
||||||
- Check network connectivity to remote git hosts |
|
||||||
- Review server logs for sync errors |
|
||||||
- Manually push to remotes if needed |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## User Profiles and Payment Targets |
|
||||||
|
|
||||||
GitRepublic displays full user profiles with support for payment targets using NIP-A3 (kind 10133). |
|
||||||
|
|
||||||
### Viewing User Profiles |
|
||||||
|
|
||||||
1. Navigate to a user's profile page: `/users/{npub}` |
|
||||||
2. View their repositories, profile information, and payment targets |
|
||||||
3. Profiles support both old JSON format (in content) and new tags format |
|
||||||
|
|
||||||
### Payment Targets (NIP-A3) |
|
||||||
|
|
||||||
Payment targets allow users to specify how they want to receive payments using the `payto://` URI scheme (RFC-8905). |
|
||||||
|
|
||||||
#### Supported Payment Types |
|
||||||
|
|
||||||
- **Lightning**: Lightning Network addresses (e.g., `user@wallet.example.com`) |
|
||||||
- **Bitcoin**: Bitcoin addresses |
|
||||||
- **Ethereum**: Ethereum addresses |
|
||||||
- **Nano**: Nano addresses |
|
||||||
- **Monero**: Monero addresses |
|
||||||
- And more (see [NIP-A3 documentation](./NIP-A3.md)) |
|
||||||
|
|
||||||
#### How Payment Targets Work |
|
||||||
|
|
||||||
GitRepublic merges payment information from multiple sources: |
|
||||||
|
|
||||||
1. **NIP-01 (kind 0)**: Lightning addresses from `lud16` tags or JSON `lud16` field |
|
||||||
2. **NIP-A3 (kind 10133)**: All payment targets from `payto` tags |
|
||||||
|
|
||||||
The system: |
|
||||||
- Normalizes addresses (lowercase) for deduplication |
|
||||||
- Merges lightning addresses from both sources |
|
||||||
- Displays all payment targets with `payto://` URIs |
|
||||||
- Provides copy buttons for easy sharing |
|
||||||
|
|
||||||
#### Creating Payment Target Events |
|
||||||
|
|
||||||
To add payment targets to your profile, publish a kind 10133 event: |
|
||||||
|
|
||||||
```json |
|
||||||
{ |
|
||||||
"kind": 10133, |
|
||||||
"content": "", |
|
||||||
"tags": [ |
|
||||||
["payto", "lightning", "user@wallet.example.com"], |
|
||||||
["payto", "bitcoin", "bc1qxq66e0t8d7ugdecwnmv58e90tpry23nc84pg9k"] |
|
||||||
], |
|
||||||
"created_at": 1234567890 |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
#### API Access |
|
||||||
|
|
||||||
Fetch user profiles with payment targets via the API: |
|
||||||
|
|
||||||
```bash |
|
||||||
GET /api/users/{npub}/profile |
|
||||||
``` |
|
||||||
|
|
||||||
Response includes: |
|
||||||
- Full profile event (kind 0) |
|
||||||
- Payment targets array with `payto://` URIs |
|
||||||
- Payment event (kind 10133) if available |
|
||||||
|
|
||||||
#### CLI Access |
|
||||||
|
|
||||||
The GitRepublic CLI automatically fetches payment targets when fetching profiles: |
|
||||||
|
|
||||||
```bash |
|
||||||
gitrep profile fetch npub1... |
|
||||||
``` |
|
||||||
|
|
||||||
See [NIP-A3 documentation](./NIP-A3.md) for complete details. |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Advanced Topics |
|
||||||
|
|
||||||
### NIP-34 Specification |
|
||||||
|
|
||||||
GitRepublic implements NIP-34 for repository announcements. Key event types: |
|
||||||
|
|
||||||
- **Kind 30617**: Repository announcement |
|
||||||
- **Kind 30618**: Repository state |
|
||||||
- **Kind 1617**: Git patch |
|
||||||
- **Kind 1618**: Pull request |
|
||||||
- **Kind 1619**: Pull request update |
|
||||||
- **Kind 1621**: Issue |
|
||||||
- **Kind 1630-1633**: Status events (open, applied/merged, closed, draft) |
|
||||||
- **Kind 1641**: Ownership transfer |
|
||||||
|
|
||||||
See the [NIP-34 documentation](/docs/nip34) for full details. |
|
||||||
|
|
||||||
### GRASP Protocol Support |
|
||||||
|
|
||||||
GitRepublic provides **minimal GRASP (Git Repository Announcement and Synchronization Protocol) interoperability** for seamless compatibility with GRASP servers. |
|
||||||
|
|
||||||
**What is GRASP?** |
|
||||||
|
|
||||||
GRASP is a protocol specification for decentralized git hosting that combines git smart HTTP with Nostr relays. GRASP servers provide git repository hosting with Nostr-based announcements and state management. |
|
||||||
|
|
||||||
**What GitRepublic Supports:** |
|
||||||
|
|
||||||
1. **GRASP Server Detection**: Automatically identifies GRASP servers from repository announcements using GRASP-01 identification (clone URL pattern + matching `relays` tag) |
|
||||||
|
|
||||||
2. **Clone URL Reachability**: Tests and displays reachability status for all clone URLs, showing which remotes (including GRASP servers) are accessible |
|
||||||
|
|
||||||
3. **Multi-Remote Synchronization**: When you push, automatically syncs to all remotes listed in your announcement, including GRASP servers |
|
||||||
|
|
||||||
4. **Local Pull Command**: Use `gitrep pull-all --merge` to fetch and merge from all remotes (including GRASP servers) |
|
||||||
- Checks reachability first, only pulls from accessible remotes |
|
||||||
- Detects conflicts before merging (aborts unless `--allow-conflicts`) |
|
||||||
|
|
||||||
5. **Standard Git Operations**: Full compatibility with GRASP servers for clone, push, pull using standard git smart HTTP protocol |
|
||||||
|
|
||||||
**What We Don't Support (By Design):** |
|
||||||
|
|
||||||
- Full GRASP-01 server compliance (we're not a full GRASP server) |
|
||||||
- GRASP-02 proactive sync (no server-side hourly pulls - user-controlled via CLI) |
|
||||||
- GRASP-05 archive mode |
|
||||||
|
|
||||||
**Example: Working with GRASP Servers** |
|
||||||
|
|
||||||
```bash |
|
||||||
# Clone from a GRASP server (works just like any git server) |
|
||||||
gitrep clone https://grasp.example.com/npub1.../repo.git |
|
||||||
|
|
||||||
# Push to your repo (automatically syncs to all remotes including GRASP) |
|
||||||
gitrep push origin main |
|
||||||
|
|
||||||
# Pull from all remotes including GRASP servers |
|
||||||
gitrep pull-all --merge |
|
||||||
``` |
|
||||||
|
|
||||||
### NIP-98 HTTP Authentication |
|
||||||
|
|
||||||
Git operations use NIP-98 for authentication: |
|
||||||
|
|
||||||
1. Client creates an ephemeral event (kind 27235) |
|
||||||
2. Event includes request URL, method, and payload hash |
|
||||||
3. Client signs event and includes in `Authorization` header |
|
||||||
4. Server verifies signature and permissions |
|
||||||
|
|
||||||
### Relay Configuration |
|
||||||
|
|
||||||
GitRepublic uses Nostr relays to: |
|
||||||
- Publish repository announcements |
|
||||||
- Fetch repository metadata |
|
||||||
- Sync pull requests and issues |
|
||||||
- Track ownership transfers |
|
||||||
|
|
||||||
Default relays are configured, but you can use custom relays if needed. |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Getting Help |
|
||||||
|
|
||||||
- **Documentation**: Check this tutorial and the NIP-34 specification |
|
||||||
- **Issues**: Report bugs or request features via GitHub issues (if the instance has a GitHub repo) |
|
||||||
- **Community**: Join Nostr communities to discuss GitRepublic |
|
||||||
- **Support**: Contact the GitRepublic instance administrator |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## Conclusion |
|
||||||
|
|
||||||
Congratulations! You now know how to use GitRepublic for decentralized git hosting. Remember: |
|
||||||
|
|
||||||
- GitRepublic is built on Nostr, making it truly decentralized |
|
||||||
- You have full control over your repositories |
|
||||||
- Collaboration happens through Nostr events |
|
||||||
- Security is handled via NIP-07 and NIP-98 |
|
||||||
|
|
||||||
Happy coding! |
|
||||||
@ -0,0 +1,94 @@ |
|||||||
|
/** |
||||||
|
* Server-side loader for GitRepublic documentation |
||||||
|
* Dynamic route that serves any markdown file from the docs directory |
||||||
|
*/ |
||||||
|
|
||||||
|
import { readFile } from 'fs/promises'; |
||||||
|
import { join } from 'path'; |
||||||
|
import { fileURLToPath } from 'url'; |
||||||
|
import { existsSync } from 'fs'; |
||||||
|
import { error } from '@sveltejs/kit'; |
||||||
|
// @ts-ignore - SvelteKit generates this type
|
||||||
|
import type { PageServerLoad } from './$types'; |
||||||
|
import logger from '$lib/services/logger.js'; |
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ params }: { params: { slug: string } }) => { |
||||||
|
let slug = params.slug; |
||||||
|
|
||||||
|
// Security: Only allow alphanumeric, hyphens, underscores, and dots
|
||||||
|
if (!/^[a-zA-Z0-9._-]+$/.test(slug)) { |
||||||
|
throw error(400, 'Invalid documentation path'); |
||||||
|
} |
||||||
|
|
||||||
|
// Prevent path traversal
|
||||||
|
if (slug.includes('..') || slug.includes('/')) { |
||||||
|
throw error(400, 'Invalid documentation path'); |
||||||
|
} |
||||||
|
|
||||||
|
const attemptedPaths: string[] = []; |
||||||
|
let lastError: Error | null = null; |
||||||
|
|
||||||
|
// Helper function to try both exact case and lowercase versions
|
||||||
|
const tryPaths = (basePath: string) => { |
||||||
|
const paths: string[] = []; |
||||||
|
// Try exact case first
|
||||||
|
paths.push(join(basePath, `${slug}.md`)); |
||||||
|
// Try lowercase version (for case-insensitive filesystems)
|
||||||
|
if (slug !== slug.toLowerCase()) { |
||||||
|
paths.push(join(basePath, `${slug.toLowerCase()}.md`)); |
||||||
|
} |
||||||
|
return paths; |
||||||
|
}; |
||||||
|
|
||||||
|
// List of base paths to try
|
||||||
|
const basePathsToTry = [ |
||||||
|
// Method 1: process.cwd() (works in most cases)
|
||||||
|
() => join(process.cwd(), 'docs'), |
||||||
|
// Method 2: process.cwd() from build directory
|
||||||
|
() => join(process.cwd(), '..', 'docs'), |
||||||
|
// Method 3: import.meta.url - go up from route file to project root
|
||||||
|
() => { |
||||||
|
const __filename = fileURLToPath(import.meta.url); |
||||||
|
return join(__filename, '..', '..', '..', '..', '..', 'docs'); |
||||||
|
}, |
||||||
|
// Method 4: import.meta.url - alternative path calculation
|
||||||
|
() => { |
||||||
|
const __filename = fileURLToPath(import.meta.url); |
||||||
|
return join(__filename, '..', '..', '..', '..', '..', '..', 'docs'); |
||||||
|
}, |
||||||
|
// Method 5: Check if running from build directory
|
||||||
|
() => join(process.cwd(), 'build', 'docs'), |
||||||
|
]; |
||||||
|
|
||||||
|
// Try all combinations of base paths and file name variations
|
||||||
|
for (const getBasePath of basePathsToTry) { |
||||||
|
try { |
||||||
|
const basePath = getBasePath(); |
||||||
|
const paths = tryPaths(basePath); |
||||||
|
|
||||||
|
for (const filePath of paths) { |
||||||
|
attemptedPaths.push(filePath); |
||||||
|
|
||||||
|
if (existsSync(filePath)) { |
||||||
|
logger.info({ filePath, slug }, 'Found documentation file'); |
||||||
|
const content = await readFile(filePath, 'utf-8'); |
||||||
|
return { content, slug }; |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
lastError = err instanceof Error ? err : new Error(String(err)); |
||||||
|
// Continue to next path
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// All paths failed
|
||||||
|
logger.error({
|
||||||
|
error: lastError,
|
||||||
|
attemptedPaths, |
||||||
|
slug, |
||||||
|
cwd: process.cwd(), |
||||||
|
importMetaUrl: import.meta.url |
||||||
|
}, 'Error loading documentation - all paths failed'); |
||||||
|
|
||||||
|
throw error(404, `Documentation file "${slug}.md" not found`); |
||||||
|
}; |
||||||
@ -0,0 +1,226 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
import { page } from '$app/stores'; |
||||||
|
import { onMount } from 'svelte'; |
||||||
|
|
||||||
|
let content = $state(''); |
||||||
|
let loading = $state(true); |
||||||
|
let error = $state<string | null>(null); |
||||||
|
|
||||||
|
onMount(async () => { |
||||||
|
try { |
||||||
|
const docContent = $page.data.content; |
||||||
|
if (docContent) { |
||||||
|
const MarkdownIt = (await import('markdown-it')).default; |
||||||
|
const hljsModule = await import('highlight.js'); |
||||||
|
const hljs = hljsModule.default || hljsModule; |
||||||
|
|
||||||
|
const md = new MarkdownIt({ |
||||||
|
highlight: function (str: string, lang: string): string { |
||||||
|
if (lang && hljs.getLanguage(lang)) { |
||||||
|
try { |
||||||
|
return '<pre class="hljs"><code>' + |
||||||
|
hljs.highlight(str, { language: lang }).value + |
||||||
|
'</code></pre>'; |
||||||
|
} catch (err) { |
||||||
|
// Fallback to escaped HTML if highlighting fails |
||||||
|
} |
||||||
|
} |
||||||
|
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
let rendered = md.render(docContent); |
||||||
|
|
||||||
|
// Add IDs to headings for anchor links |
||||||
|
rendered = rendered.replace(/<h([1-6])>(.*?)<\/h[1-6]>/g, (match, level, text) => { |
||||||
|
const textContent = text.replace(/<[^>]*>/g, '').trim(); |
||||||
|
const slug = textContent |
||||||
|
.toLowerCase() |
||||||
|
.replace(/[^\w\s-]/g, '') |
||||||
|
.replace(/\s+/g, '-') |
||||||
|
.replace(/-+/g, '-') |
||||||
|
.replace(/^-|-$/g, ''); |
||||||
|
|
||||||
|
return `<h${level} id="${slug}">${text}</h${level}>`; |
||||||
|
}); |
||||||
|
|
||||||
|
// Convert relative markdown links to docs routes |
||||||
|
rendered = rendered.replace(/<a href="\.\/([^"]+\.md)"/g, (match, file) => { |
||||||
|
const slug = file.replace('.md', ''); |
||||||
|
return `<a href="/docs/${slug}"`; |
||||||
|
}); |
||||||
|
|
||||||
|
content = rendered; |
||||||
|
|
||||||
|
// Handle anchor links after content is rendered |
||||||
|
setTimeout(() => { |
||||||
|
if (window.location.hash) { |
||||||
|
const id = window.location.hash.substring(1); |
||||||
|
const element = document.getElementById(id); |
||||||
|
if (element) { |
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const markdownContent = document.querySelector('.markdown-content'); |
||||||
|
if (markdownContent) { |
||||||
|
markdownContent.addEventListener('click', (e) => { |
||||||
|
const target = e.target as HTMLElement; |
||||||
|
if (target.tagName === 'A' && target.getAttribute('href')?.startsWith('#')) { |
||||||
|
const id = target.getAttribute('href')?.substring(1); |
||||||
|
if (id) { |
||||||
|
const element = document.getElementById(id); |
||||||
|
if (element) { |
||||||
|
e.preventDefault(); |
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
||||||
|
window.history.pushState(null, '', `#${id}`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}, 100); |
||||||
|
} else { |
||||||
|
error = $page.data.error || 'Failed to load documentation'; |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
error = err instanceof Error ? err.message : 'Failed to load documentation'; |
||||||
|
console.error('Error parsing documentation:', err); |
||||||
|
} finally { |
||||||
|
loading = false; |
||||||
|
} |
||||||
|
}); |
||||||
|
</script> |
||||||
|
|
||||||
|
<div class="container"> |
||||||
|
<header> |
||||||
|
<h1>Documentation</h1> |
||||||
|
</header> |
||||||
|
|
||||||
|
<main class="docs-content"> |
||||||
|
{#if loading} |
||||||
|
<div class="loading">Loading documentation...</div> |
||||||
|
{:else if error} |
||||||
|
<div class="error">{error}</div> |
||||||
|
{:else} |
||||||
|
<div class="markdown-content"> |
||||||
|
{@html content} |
||||||
|
</div> |
||||||
|
{/if} |
||||||
|
</main> |
||||||
|
</div> |
||||||
|
|
||||||
|
<style> |
||||||
|
.docs-content { |
||||||
|
background: var(--card-bg); |
||||||
|
padding: 2rem; |
||||||
|
border-radius: 0.5rem; |
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
||||||
|
border: 1px solid var(--border-color); |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content) { |
||||||
|
line-height: 1.6; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content h1) { |
||||||
|
font-size: 2rem; |
||||||
|
margin-top: 2rem; |
||||||
|
margin-bottom: 1rem; |
||||||
|
border-bottom: 2px solid var(--border-color); |
||||||
|
padding-bottom: 0.5rem; |
||||||
|
color: var(--text-primary); |
||||||
|
scroll-margin-top: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content h2) { |
||||||
|
font-size: 1.5rem; |
||||||
|
margin-top: 1.5rem; |
||||||
|
margin-bottom: 0.75rem; |
||||||
|
color: var(--text-primary); |
||||||
|
scroll-margin-top: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content h3) { |
||||||
|
font-size: 1.25rem; |
||||||
|
margin-top: 1.25rem; |
||||||
|
margin-bottom: 0.5rem; |
||||||
|
color: var(--text-primary); |
||||||
|
scroll-margin-top: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content h4, .markdown-content h5, .markdown-content h6) { |
||||||
|
scroll-margin-top: 1rem; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content) { |
||||||
|
scroll-behavior: smooth; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content code) { |
||||||
|
background: var(--bg-secondary); |
||||||
|
padding: 0.125rem 0.25rem; |
||||||
|
border-radius: 0.25rem; |
||||||
|
font-family: 'IBM Plex Mono', monospace; |
||||||
|
font-size: 0.875em; |
||||||
|
color: var(--text-primary); |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content pre) { |
||||||
|
background: var(--bg-tertiary); |
||||||
|
color: var(--text-primary); |
||||||
|
padding: 1rem; |
||||||
|
border-radius: 0.5rem; |
||||||
|
overflow-x: auto; |
||||||
|
margin: 1rem 0; |
||||||
|
border: 1px solid var(--border-color); |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content pre code) { |
||||||
|
background: transparent; |
||||||
|
padding: 0; |
||||||
|
color: inherit; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content p) { |
||||||
|
margin: 1rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content ul, .markdown-content ol) { |
||||||
|
margin: 1rem 0; |
||||||
|
padding-left: 2rem; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content li) { |
||||||
|
margin: 0.5rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content blockquote) { |
||||||
|
border-left: 4px solid var(--accent); |
||||||
|
padding-left: 1rem; |
||||||
|
margin: 1rem 0; |
||||||
|
color: var(--text-secondary); |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content table) { |
||||||
|
width: 100%; |
||||||
|
border-collapse: collapse; |
||||||
|
margin: 1rem 0; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content th, .markdown-content td) { |
||||||
|
border: 1px solid var(--border-color); |
||||||
|
padding: 0.5rem; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content th) { |
||||||
|
background: var(--bg-secondary); |
||||||
|
font-weight: 600; |
||||||
|
color: var(--text-primary); |
||||||
|
} |
||||||
|
|
||||||
|
:global(.markdown-content td) { |
||||||
|
color: var(--text-primary); |
||||||
|
} |
||||||
|
</style> |
||||||
Loading…
Reference in new issue