tinyrelayfile
tinyrelay/tinygit/gitrelay.go/

tinygit/gitrelay.go raw
1package tinygit
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net/http"
8 "os"
9 "strings"
10 "sync"
11
12 event "github.com/FelineStateMachine/tinyrelay/protocol/nostr"
13)
14
15// Repository is the resolved authority and Git state for one hosted project.
16type Repository struct {
17 Owner string
18 Identifier string
19 EventID string
20 Private bool
21 Clone []string
22 Relays []string
23 Maintainers []string
24 Refs map[string]string
25 Head string
26 Alternative bool
27}
28
29// Maintainer is one key that may sign repository state, push refs and change
30// status. Role is "owner", "maintainer" for a key in the announcement's
31// maintainers tag, or "agent" for a key the host vouches for under an agent
32// grant; Name carries the grant's label when it has one.
33type Maintainer struct {
34 PubKey string `json:"pubkey"`
35 Role string `json:"role"`
36 Name string `json:"name,omitempty"`
37}
38
39// MaintainerSource answers whether a key maintains a repository beyond the
40// owner and the announcement's maintainers tag. The host relay supplies it so
41// authority it records elsewhere, such as an agent grant, counts in every
42// place this package checks maintainers.
43type MaintainerSource interface {
44 IsMaintainer(ctx context.Context, r Repository, pubkey string) bool
45}
46
47// Config supplies the event store, Git root, authorization and transport hooks.
48type Config struct {
49 Store Store
50 Root string
51 Policy func() Policy
52 Authorize func(context.Context, Event, Repository) error
53 // Maintainers extends maintainer checks with host-side authority. It is
54 // consulted only after the owner and the maintainers tag.
55 Maintainers MaintainerSource
56 // AuthorizeHTTP is called for private repositories and for operators that
57 // want signed/NIP-98 authorization at the Git HTTP boundary.
58 AuthorizeHTTP func(context.Context, *http.Request, Repository) error
59 // ServiceURL is used to reject recursive GRASP sources and to construct
60 // advertised clone URLs. It is optional for an embedded relay.
61 ServiceURL string
62 // PublicURL is the canonical HTTPS URL used in repository advertisements;
63 // ServiceURL is used when PublicURL is empty.
64 PublicURL string
65 // EnableGRASP06 enables the alternative PR repository surface. It is kept
66 // opt-in because publishing a capability creates an interoperability
67 // promise beyond the base GRASP-01 HTTP endpoint.
68 EnableGRASP06 bool
69 // AllowMissingObjects is ignored.
70 // Missing objects enter the durable pending path and stay hidden until
71 // receive-pack or repair supplies every advertised tip.
72 AllowMissingObjects bool
73 // AllowPrivateRelays permits operator-controlled private DNS targets for
74 // self-hosted networks. Public deployments should leave it disabled.
75 AllowPrivateRelays bool
76 // PrivatePeers are operator-configured GRASP-08 origins. Only these
77 // sources may receive signed outbound Git requests.
78 PrivatePeers []string
79 // HTTPAuth signs requests to configured private peers. It is never used for
80 // sources that are merely present in a repository announcement.
81 HTTPAuth HTTPAuthSigner
82 // GitSync and EventSync are operator-owned transport callbacks. Keeping
83 // transport outside this package allows native websocket/NIP-77, HTTP, or
84 // an internal queue without making GRASP depend on one client library.
85 GitSync func(context.Context, Repository) error
86 EventSync func(context.Context, Repository) error
87 // OnPromote is called after a pending state becomes object-complete and
88 // visible. The host relay uses it to release event visibility/live fanout.
89 OnPromote func(context.Context, string, Repository) error
90}
91
92// GitRelay coordinates signed repository events with native bare repositories.
93type GitRelay struct {
94 store Store
95 root string
96 policy func() Policy
97 authorize func(context.Context, event.Event, Repository) error
98 authorizeHTTP func(context.Context, *http.Request, Repository) error
99 maintainers MaintainerSource
100 serviceURL string
101 gitSync func(context.Context, Repository) error
102 eventSync func(context.Context, Repository) error
103 onPromote func(context.Context, string, Repository) error
104 grasp06 bool
105 allowMissing bool
106 allowPrivate bool
107 privatePeers []string
108 httpAuth HTTPAuthSigner
109 mu sync.RWMutex
110 repos map[string]Repository
111 pending map[string]struct{}
112 commitMu sync.Mutex
113 tickMu sync.Mutex
114 repoConfigMu sync.Mutex
115 configured map[string]struct{}
116}
117
118func New(cfg Config) (*GitRelay, error) {
119 if cfg.Store == nil {
120 return nil, errors.New("git relay: store is required")
121 }
122 store := cfg.Store
123 if cfg.Root == "" {
124 return nil, errors.New("git relay: root is required")
125 }
126 if err := os.MkdirAll(cfg.Root, 0700); err != nil {
127 return nil, fmt.Errorf("git relay root: %w", err)
128 }
129 p := cfg.Policy
130 if p == nil {
131 p = func() Policy { return DefaultPolicy("") }
132 }
133 serviceURL := cfg.PublicURL
134 if serviceURL == "" {
135 serviceURL = cfg.ServiceURL
136 }
137 g := &GitRelay{store: store, root: cfg.Root, policy: p, authorize: cfg.Authorize, authorizeHTTP: cfg.AuthorizeHTTP, maintainers: cfg.Maintainers, serviceURL: strings.TrimRight(serviceURL, "/"), grasp06: cfg.EnableGRASP06, allowMissing: cfg.AllowMissingObjects, allowPrivate: cfg.AllowPrivateRelays, privatePeers: append([]string(nil), cfg.PrivatePeers...), httpAuth: cfg.HTTPAuth, gitSync: cfg.GitSync, eventSync: cfg.EventSync, onPromote: cfg.OnPromote, repos: make(map[string]Repository), pending: make(map[string]struct{})}
138 if err := g.recoverJournals(); err != nil {
139 return nil, err
140 }
141 if err := g.Reload(context.Background()); err != nil {
142 return nil, err
143 }
144 return g, nil
145}
146
147func key(owner, identifier string) string { return owner + "\x00" + identifier }
148
149// IsMaintainer reports whether pubkey may sign state and push refs for a
150// repository: the owner, a key in the announcement's maintainers tag, or a key
151// the configured MaintainerSource vouches for. Every maintainer check in this
152// package goes through here so the host's answer applies uniformly.
153func (g *GitRelay) IsMaintainer(ctx context.Context, r Repository, pubkey string) bool {
154 if pubkey == "" {
155 return false
156 }
157 if pubkey == r.Owner || contains(r.Maintainers, pubkey) {
158 return true
159 }
160 return g.maintainers != nil && g.maintainers.IsMaintainer(ctx, r, pubkey)
161}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs