+2
-2
knotserver/events.go
+2
-2
knotserver/events.go
···
15
15
WriteBufferSize: 1024,
16
16
}
17
17
18
-
func (h *Handle) Events(w http.ResponseWriter, r *http.Request) {
18
+
func (h *Knot) Events(w http.ResponseWriter, r *http.Request) {
19
19
l := h.l.With("handler", "OpLog")
20
20
l.Debug("received new connection")
21
21
···
83
83
}
84
84
}
85
85
86
-
func (h *Handle) streamOps(conn *websocket.Conn, cursor *int64) error {
86
+
func (h *Knot) streamOps(conn *websocket.Conn, cursor *int64) error {
87
87
events, err := h.db.GetEvents(*cursor)
88
88
if err != nil {
89
89
h.l.Error("failed to fetch events from db", "err", err, "cursor", cursor)
-48
knotserver/file.go
-48
knotserver/file.go
···
1
-
package knotserver
2
-
3
-
import (
4
-
"bytes"
5
-
"io"
6
-
"log/slog"
7
-
"net/http"
8
-
"strings"
9
-
10
-
"tangled.sh/tangled.sh/core/types"
11
-
)
12
-
13
-
func countLines(r io.Reader) (int, error) {
14
-
buf := make([]byte, 32*1024)
15
-
bufLen := 0
16
-
count := 0
17
-
nl := []byte{'\n'}
18
-
19
-
for {
20
-
c, err := r.Read(buf)
21
-
if c > 0 {
22
-
bufLen += c
23
-
}
24
-
count += bytes.Count(buf[:c], nl)
25
-
26
-
switch {
27
-
case err == io.EOF:
28
-
/* handle last line not having a newline at the end */
29
-
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
30
-
count++
31
-
}
32
-
return count, nil
33
-
case err != nil:
34
-
return 0, err
35
-
}
36
-
}
37
-
}
38
-
39
-
func (h *Handle) showFile(resp types.RepoBlobResponse, w http.ResponseWriter, l *slog.Logger) {
40
-
lc, err := countLines(strings.NewReader(resp.Contents))
41
-
if err != nil {
42
-
// Non-fatal, we'll just skip showing line numbers in the template.
43
-
l.Warn("counting lines", "error", err)
44
-
}
45
-
46
-
resp.Lines = lc
47
-
writeJSON(w, resp)
48
-
}
+4
-4
knotserver/git.go
+4
-4
knotserver/git.go
···
13
13
"tangled.sh/tangled.sh/core/knotserver/git/service"
14
14
)
15
15
16
-
func (d *Handle) InfoRefs(w http.ResponseWriter, r *http.Request) {
16
+
func (d *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) {
17
17
did := chi.URLParam(r, "did")
18
18
name := chi.URLParam(r, "name")
19
19
repoName, err := securejoin.SecureJoin(did, name)
···
56
56
}
57
57
}
58
58
59
-
func (d *Handle) UploadPack(w http.ResponseWriter, r *http.Request) {
59
+
func (d *Knot) UploadPack(w http.ResponseWriter, r *http.Request) {
60
60
did := chi.URLParam(r, "did")
61
61
name := chi.URLParam(r, "name")
62
62
repo, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
···
105
105
}
106
106
}
107
107
108
-
func (d *Handle) ReceivePack(w http.ResponseWriter, r *http.Request) {
108
+
func (d *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) {
109
109
did := chi.URLParam(r, "did")
110
110
name := chi.URLParam(r, "name")
111
111
_, err := securejoin.SecureJoin(d.c.Repo.ScanPath, filepath.Join(did, name))
···
118
118
d.RejectPush(w, r, name)
119
119
}
120
120
121
-
func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
121
+
func (d *Knot) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
122
122
// A text/plain response will cause git to print each line of the body
123
123
// prefixed with "remote: ".
124
124
w.Header().Set("content-type", "text/plain; charset=UTF-8")
-1069
knotserver/handler.go
-1069
knotserver/handler.go
···
1
-
package knotserver
2
-
3
-
import (
4
-
"compress/gzip"
5
-
"context"
6
-
"crypto/sha256"
7
-
"encoding/json"
8
-
"errors"
9
-
"fmt"
10
-
"log"
11
-
"net/http"
12
-
"net/url"
13
-
"path/filepath"
14
-
"strconv"
15
-
"strings"
16
-
"sync"
17
-
"time"
18
-
19
-
securejoin "github.com/cyphar/filepath-securejoin"
20
-
"github.com/gliderlabs/ssh"
21
-
"github.com/go-chi/chi/v5"
22
-
"github.com/go-git/go-git/v5/plumbing"
23
-
"github.com/go-git/go-git/v5/plumbing/object"
24
-
"tangled.sh/tangled.sh/core/knotserver/db"
25
-
"tangled.sh/tangled.sh/core/knotserver/git"
26
-
"tangled.sh/tangled.sh/core/types"
27
-
)
28
-
29
-
func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
30
-
w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
31
-
}
32
-
33
-
func (h *Handle) Capabilities(w http.ResponseWriter, r *http.Request) {
34
-
w.Header().Set("Content-Type", "application/json")
35
-
36
-
capabilities := map[string]any{
37
-
"pull_requests": map[string]any{
38
-
"format_patch": true,
39
-
"patch_submissions": true,
40
-
"branch_submissions": true,
41
-
"fork_submissions": true,
42
-
},
43
-
"xrpc": true,
44
-
}
45
-
46
-
jsonData, err := json.Marshal(capabilities)
47
-
if err != nil {
48
-
http.Error(w, "Failed to serialize JSON", http.StatusInternalServerError)
49
-
return
50
-
}
51
-
52
-
w.Write(jsonData)
53
-
}
54
-
55
-
func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
56
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
57
-
l := h.l.With("path", path, "handler", "RepoIndex")
58
-
ref := chi.URLParam(r, "ref")
59
-
ref, _ = url.PathUnescape(ref)
60
-
61
-
gr, err := git.Open(path, ref)
62
-
if err != nil {
63
-
plain, err2 := git.PlainOpen(path)
64
-
if err2 != nil {
65
-
l.Error("opening repo", "error", err2.Error())
66
-
notFound(w)
67
-
return
68
-
}
69
-
branches, _ := plain.Branches()
70
-
71
-
log.Println(err)
72
-
73
-
if errors.Is(err, plumbing.ErrReferenceNotFound) {
74
-
resp := types.RepoIndexResponse{
75
-
IsEmpty: true,
76
-
Branches: branches,
77
-
}
78
-
writeJSON(w, resp)
79
-
return
80
-
} else {
81
-
l.Error("opening repo", "error", err.Error())
82
-
notFound(w)
83
-
return
84
-
}
85
-
}
86
-
87
-
var (
88
-
commits []*object.Commit
89
-
total int
90
-
branches []types.Branch
91
-
files []types.NiceTree
92
-
tags []object.Tag
93
-
)
94
-
95
-
var wg sync.WaitGroup
96
-
errorsCh := make(chan error, 5)
97
-
98
-
wg.Add(1)
99
-
go func() {
100
-
defer wg.Done()
101
-
cs, err := gr.Commits(0, 60)
102
-
if err != nil {
103
-
errorsCh <- fmt.Errorf("commits: %w", err)
104
-
return
105
-
}
106
-
commits = cs
107
-
}()
108
-
109
-
wg.Add(1)
110
-
go func() {
111
-
defer wg.Done()
112
-
t, err := gr.TotalCommits()
113
-
if err != nil {
114
-
errorsCh <- fmt.Errorf("calculating total: %w", err)
115
-
return
116
-
}
117
-
total = t
118
-
}()
119
-
120
-
wg.Add(1)
121
-
go func() {
122
-
defer wg.Done()
123
-
bs, err := gr.Branches()
124
-
if err != nil {
125
-
errorsCh <- fmt.Errorf("fetching branches: %w", err)
126
-
return
127
-
}
128
-
branches = bs
129
-
}()
130
-
131
-
wg.Add(1)
132
-
go func() {
133
-
defer wg.Done()
134
-
ts, err := gr.Tags()
135
-
if err != nil {
136
-
errorsCh <- fmt.Errorf("fetching tags: %w", err)
137
-
return
138
-
}
139
-
tags = ts
140
-
}()
141
-
142
-
wg.Add(1)
143
-
go func() {
144
-
defer wg.Done()
145
-
fs, err := gr.FileTree(r.Context(), "")
146
-
if err != nil {
147
-
errorsCh <- fmt.Errorf("fetching filetree: %w", err)
148
-
return
149
-
}
150
-
files = fs
151
-
}()
152
-
153
-
wg.Wait()
154
-
close(errorsCh)
155
-
156
-
// show any errors
157
-
for err := range errorsCh {
158
-
l.Error("loading repo", "error", err.Error())
159
-
writeError(w, err.Error(), http.StatusInternalServerError)
160
-
return
161
-
}
162
-
163
-
rtags := []*types.TagReference{}
164
-
for _, tag := range tags {
165
-
var target *object.Tag
166
-
if tag.Target != plumbing.ZeroHash {
167
-
target = &tag
168
-
}
169
-
tr := types.TagReference{
170
-
Tag: target,
171
-
}
172
-
173
-
tr.Reference = types.Reference{
174
-
Name: tag.Name,
175
-
Hash: tag.Hash.String(),
176
-
}
177
-
178
-
if tag.Message != "" {
179
-
tr.Message = tag.Message
180
-
}
181
-
182
-
rtags = append(rtags, &tr)
183
-
}
184
-
185
-
var readmeContent string
186
-
var readmeFile string
187
-
for _, readme := range h.c.Repo.Readme {
188
-
content, _ := gr.FileContent(readme)
189
-
if len(content) > 0 {
190
-
readmeContent = string(content)
191
-
readmeFile = readme
192
-
}
193
-
}
194
-
195
-
if ref == "" {
196
-
mainBranch, err := gr.FindMainBranch()
197
-
if err != nil {
198
-
writeError(w, err.Error(), http.StatusInternalServerError)
199
-
l.Error("finding main branch", "error", err.Error())
200
-
return
201
-
}
202
-
ref = mainBranch
203
-
}
204
-
205
-
resp := types.RepoIndexResponse{
206
-
IsEmpty: false,
207
-
Ref: ref,
208
-
Commits: commits,
209
-
Description: getDescription(path),
210
-
Readme: readmeContent,
211
-
ReadmeFileName: readmeFile,
212
-
Files: files,
213
-
Branches: branches,
214
-
Tags: rtags,
215
-
TotalCommits: total,
216
-
}
217
-
218
-
writeJSON(w, resp)
219
-
}
220
-
221
-
func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
222
-
treePath := chi.URLParam(r, "*")
223
-
ref := chi.URLParam(r, "ref")
224
-
ref, _ = url.PathUnescape(ref)
225
-
226
-
l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath)
227
-
228
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
229
-
gr, err := git.Open(path, ref)
230
-
if err != nil {
231
-
notFound(w)
232
-
return
233
-
}
234
-
235
-
files, err := gr.FileTree(r.Context(), treePath)
236
-
if err != nil {
237
-
writeError(w, err.Error(), http.StatusInternalServerError)
238
-
l.Error("file tree", "error", err.Error())
239
-
return
240
-
}
241
-
242
-
resp := types.RepoTreeResponse{
243
-
Ref: ref,
244
-
Parent: treePath,
245
-
Description: getDescription(path),
246
-
DotDot: filepath.Dir(treePath),
247
-
Files: files,
248
-
}
249
-
250
-
writeJSON(w, resp)
251
-
}
252
-
253
-
func (h *Handle) BlobRaw(w http.ResponseWriter, r *http.Request) {
254
-
treePath := chi.URLParam(r, "*")
255
-
ref := chi.URLParam(r, "ref")
256
-
ref, _ = url.PathUnescape(ref)
257
-
258
-
l := h.l.With("handler", "BlobRaw", "ref", ref, "treePath", treePath)
259
-
260
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
261
-
gr, err := git.Open(path, ref)
262
-
if err != nil {
263
-
notFound(w)
264
-
return
265
-
}
266
-
267
-
contents, err := gr.RawContent(treePath)
268
-
if err != nil {
269
-
writeError(w, err.Error(), http.StatusBadRequest)
270
-
l.Error("file content", "error", err.Error())
271
-
return
272
-
}
273
-
274
-
mimeType := http.DetectContentType(contents)
275
-
276
-
// exception for svg
277
-
if filepath.Ext(treePath) == ".svg" {
278
-
mimeType = "image/svg+xml"
279
-
}
280
-
281
-
contentHash := sha256.Sum256(contents)
282
-
eTag := fmt.Sprintf("\"%x\"", contentHash)
283
-
284
-
// allow image, video, and text/plain files to be served directly
285
-
switch {
286
-
case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"):
287
-
if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag {
288
-
w.WriteHeader(http.StatusNotModified)
289
-
return
290
-
}
291
-
w.Header().Set("ETag", eTag)
292
-
293
-
case strings.HasPrefix(mimeType, "text/plain"):
294
-
w.Header().Set("Cache-Control", "public, no-cache")
295
-
296
-
default:
297
-
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
298
-
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
299
-
return
300
-
}
301
-
302
-
w.Header().Set("Content-Type", mimeType)
303
-
w.Write(contents)
304
-
}
305
-
306
-
func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) {
307
-
treePath := chi.URLParam(r, "*")
308
-
ref := chi.URLParam(r, "ref")
309
-
ref, _ = url.PathUnescape(ref)
310
-
311
-
l := h.l.With("handler", "Blob", "ref", ref, "treePath", treePath)
312
-
313
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
314
-
gr, err := git.Open(path, ref)
315
-
if err != nil {
316
-
notFound(w)
317
-
return
318
-
}
319
-
320
-
var isBinaryFile bool = false
321
-
contents, err := gr.FileContent(treePath)
322
-
if errors.Is(err, git.ErrBinaryFile) {
323
-
isBinaryFile = true
324
-
} else if errors.Is(err, object.ErrFileNotFound) {
325
-
notFound(w)
326
-
return
327
-
} else if err != nil {
328
-
writeError(w, err.Error(), http.StatusInternalServerError)
329
-
return
330
-
}
331
-
332
-
bytes := []byte(contents)
333
-
// safe := string(sanitize(bytes))
334
-
sizeHint := len(bytes)
335
-
336
-
resp := types.RepoBlobResponse{
337
-
Ref: ref,
338
-
Contents: string(bytes),
339
-
Path: treePath,
340
-
IsBinary: isBinaryFile,
341
-
SizeHint: uint64(sizeHint),
342
-
}
343
-
344
-
h.showFile(resp, w, l)
345
-
}
346
-
347
-
func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
348
-
name := chi.URLParam(r, "name")
349
-
file := chi.URLParam(r, "file")
350
-
351
-
l := h.l.With("handler", "Archive", "name", name, "file", file)
352
-
353
-
// TODO: extend this to add more files compression (e.g.: xz)
354
-
if !strings.HasSuffix(file, ".tar.gz") {
355
-
notFound(w)
356
-
return
357
-
}
358
-
359
-
ref := strings.TrimSuffix(file, ".tar.gz")
360
-
361
-
unescapedRef, err := url.PathUnescape(ref)
362
-
if err != nil {
363
-
notFound(w)
364
-
return
365
-
}
366
-
367
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-")
368
-
369
-
// This allows the browser to use a proper name for the file when
370
-
// downloading
371
-
filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename)
372
-
setContentDisposition(w, filename)
373
-
setGZipMIME(w)
374
-
375
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
376
-
gr, err := git.Open(path, unescapedRef)
377
-
if err != nil {
378
-
notFound(w)
379
-
return
380
-
}
381
-
382
-
gw := gzip.NewWriter(w)
383
-
defer gw.Close()
384
-
385
-
prefix := fmt.Sprintf("%s-%s", name, safeRefFilename)
386
-
err = gr.WriteTar(gw, prefix)
387
-
if err != nil {
388
-
// once we start writing to the body we can't report error anymore
389
-
// so we are only left with printing the error.
390
-
l.Error("writing tar file", "error", err.Error())
391
-
return
392
-
}
393
-
394
-
err = gw.Flush()
395
-
if err != nil {
396
-
// once we start writing to the body we can't report error anymore
397
-
// so we are only left with printing the error.
398
-
l.Error("flushing?", "error", err.Error())
399
-
return
400
-
}
401
-
}
402
-
403
-
func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
404
-
ref := chi.URLParam(r, "ref")
405
-
ref, _ = url.PathUnescape(ref)
406
-
407
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
408
-
409
-
l := h.l.With("handler", "Log", "ref", ref, "path", path)
410
-
411
-
gr, err := git.Open(path, ref)
412
-
if err != nil {
413
-
notFound(w)
414
-
return
415
-
}
416
-
417
-
// Get page parameters
418
-
page := 1
419
-
pageSize := 30
420
-
421
-
if pageParam := r.URL.Query().Get("page"); pageParam != "" {
422
-
if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
423
-
page = p
424
-
}
425
-
}
426
-
427
-
if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" {
428
-
if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 {
429
-
pageSize = ps
430
-
}
431
-
}
432
-
433
-
// convert to offset/limit
434
-
offset := (page - 1) * pageSize
435
-
limit := pageSize
436
-
437
-
commits, err := gr.Commits(offset, limit)
438
-
if err != nil {
439
-
writeError(w, err.Error(), http.StatusInternalServerError)
440
-
l.Error("fetching commits", "error", err.Error())
441
-
return
442
-
}
443
-
444
-
total := len(commits)
445
-
446
-
resp := types.RepoLogResponse{
447
-
Commits: commits,
448
-
Ref: ref,
449
-
Description: getDescription(path),
450
-
Log: true,
451
-
Total: total,
452
-
Page: page,
453
-
PerPage: pageSize,
454
-
}
455
-
456
-
writeJSON(w, resp)
457
-
}
458
-
459
-
func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
460
-
ref := chi.URLParam(r, "ref")
461
-
ref, _ = url.PathUnescape(ref)
462
-
463
-
l := h.l.With("handler", "Diff", "ref", ref)
464
-
465
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
466
-
gr, err := git.Open(path, ref)
467
-
if err != nil {
468
-
notFound(w)
469
-
return
470
-
}
471
-
472
-
diff, err := gr.Diff()
473
-
if err != nil {
474
-
writeError(w, err.Error(), http.StatusInternalServerError)
475
-
l.Error("getting diff", "error", err.Error())
476
-
return
477
-
}
478
-
479
-
resp := types.RepoCommitResponse{
480
-
Ref: ref,
481
-
Diff: diff,
482
-
}
483
-
484
-
writeJSON(w, resp)
485
-
}
486
-
487
-
func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) {
488
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
489
-
l := h.l.With("handler", "Refs")
490
-
491
-
gr, err := git.Open(path, "")
492
-
if err != nil {
493
-
notFound(w)
494
-
return
495
-
}
496
-
497
-
tags, err := gr.Tags()
498
-
if err != nil {
499
-
// Non-fatal, we *should* have at least one branch to show.
500
-
l.Warn("getting tags", "error", err.Error())
501
-
}
502
-
503
-
rtags := []*types.TagReference{}
504
-
for _, tag := range tags {
505
-
var target *object.Tag
506
-
if tag.Target != plumbing.ZeroHash {
507
-
target = &tag
508
-
}
509
-
tr := types.TagReference{
510
-
Tag: target,
511
-
}
512
-
513
-
tr.Reference = types.Reference{
514
-
Name: tag.Name,
515
-
Hash: tag.Hash.String(),
516
-
}
517
-
518
-
if tag.Message != "" {
519
-
tr.Message = tag.Message
520
-
}
521
-
522
-
rtags = append(rtags, &tr)
523
-
}
524
-
525
-
resp := types.RepoTagsResponse{
526
-
Tags: rtags,
527
-
}
528
-
529
-
writeJSON(w, resp)
530
-
}
531
-
532
-
func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) {
533
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
534
-
535
-
gr, err := git.PlainOpen(path)
536
-
if err != nil {
537
-
notFound(w)
538
-
return
539
-
}
540
-
541
-
branches, _ := gr.Branches()
542
-
543
-
resp := types.RepoBranchesResponse{
544
-
Branches: branches,
545
-
}
546
-
547
-
writeJSON(w, resp)
548
-
}
549
-
550
-
func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) {
551
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
552
-
branchName := chi.URLParam(r, "branch")
553
-
branchName, _ = url.PathUnescape(branchName)
554
-
555
-
l := h.l.With("handler", "Branch")
556
-
557
-
gr, err := git.PlainOpen(path)
558
-
if err != nil {
559
-
notFound(w)
560
-
return
561
-
}
562
-
563
-
ref, err := gr.Branch(branchName)
564
-
if err != nil {
565
-
l.Error("getting branch", "error", err.Error())
566
-
writeError(w, err.Error(), http.StatusInternalServerError)
567
-
return
568
-
}
569
-
570
-
commit, err := gr.Commit(ref.Hash())
571
-
if err != nil {
572
-
l.Error("getting commit object", "error", err.Error())
573
-
writeError(w, err.Error(), http.StatusInternalServerError)
574
-
return
575
-
}
576
-
577
-
defaultBranch, err := gr.FindMainBranch()
578
-
isDefault := false
579
-
if err != nil {
580
-
l.Error("getting default branch", "error", err.Error())
581
-
// do not quit though
582
-
} else if defaultBranch == branchName {
583
-
isDefault = true
584
-
}
585
-
586
-
resp := types.RepoBranchResponse{
587
-
Branch: types.Branch{
588
-
Reference: types.Reference{
589
-
Name: ref.Name().Short(),
590
-
Hash: ref.Hash().String(),
591
-
},
592
-
Commit: commit,
593
-
IsDefault: isDefault,
594
-
},
595
-
}
596
-
597
-
writeJSON(w, resp)
598
-
}
599
-
600
-
func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
601
-
l := h.l.With("handler", "Keys")
602
-
603
-
switch r.Method {
604
-
case http.MethodGet:
605
-
keys, err := h.db.GetAllPublicKeys()
606
-
if err != nil {
607
-
writeError(w, err.Error(), http.StatusInternalServerError)
608
-
l.Error("getting public keys", "error", err.Error())
609
-
return
610
-
}
611
-
612
-
data := make([]map[string]any, 0)
613
-
for _, key := range keys {
614
-
j := key.JSON()
615
-
data = append(data, j)
616
-
}
617
-
writeJSON(w, data)
618
-
return
619
-
620
-
case http.MethodPut:
621
-
pk := db.PublicKey{}
622
-
if err := json.NewDecoder(r.Body).Decode(&pk); err != nil {
623
-
writeError(w, "invalid request body", http.StatusBadRequest)
624
-
return
625
-
}
626
-
627
-
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key))
628
-
if err != nil {
629
-
writeError(w, "invalid pubkey", http.StatusBadRequest)
630
-
}
631
-
632
-
if err := h.db.AddPublicKey(pk); err != nil {
633
-
writeError(w, err.Error(), http.StatusInternalServerError)
634
-
l.Error("adding public key", "error", err.Error())
635
-
return
636
-
}
637
-
638
-
w.WriteHeader(http.StatusNoContent)
639
-
return
640
-
}
641
-
}
642
-
643
-
// func (h *Handle) RepoForkAheadBehind(w http.ResponseWriter, r *http.Request) {
644
-
// l := h.l.With("handler", "RepoForkSync")
645
-
//
646
-
// data := struct {
647
-
// Did string `json:"did"`
648
-
// Source string `json:"source"`
649
-
// Name string `json:"name,omitempty"`
650
-
// HiddenRef string `json:"hiddenref"`
651
-
// }{}
652
-
//
653
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
654
-
// writeError(w, "invalid request body", http.StatusBadRequest)
655
-
// return
656
-
// }
657
-
//
658
-
// did := data.Did
659
-
// source := data.Source
660
-
//
661
-
// if did == "" || source == "" {
662
-
// l.Error("invalid request body, empty did or name")
663
-
// w.WriteHeader(http.StatusBadRequest)
664
-
// return
665
-
// }
666
-
//
667
-
// var name string
668
-
// if data.Name != "" {
669
-
// name = data.Name
670
-
// } else {
671
-
// name = filepath.Base(source)
672
-
// }
673
-
//
674
-
// branch := chi.URLParam(r, "branch")
675
-
// branch, _ = url.PathUnescape(branch)
676
-
//
677
-
// relativeRepoPath := filepath.Join(did, name)
678
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
679
-
//
680
-
// gr, err := git.PlainOpen(repoPath)
681
-
// if err != nil {
682
-
// log.Println(err)
683
-
// notFound(w)
684
-
// return
685
-
// }
686
-
//
687
-
// forkCommit, err := gr.ResolveRevision(branch)
688
-
// if err != nil {
689
-
// l.Error("error resolving ref revision", "msg", err.Error())
690
-
// writeError(w, fmt.Sprintf("error resolving revision %s", branch), http.StatusBadRequest)
691
-
// return
692
-
// }
693
-
//
694
-
// sourceCommit, err := gr.ResolveRevision(data.HiddenRef)
695
-
// if err != nil {
696
-
// l.Error("error resolving hidden ref revision", "msg", err.Error())
697
-
// writeError(w, fmt.Sprintf("error resolving revision %s", data.HiddenRef), http.StatusBadRequest)
698
-
// return
699
-
// }
700
-
//
701
-
// status := types.UpToDate
702
-
// if forkCommit.Hash.String() != sourceCommit.Hash.String() {
703
-
// isAncestor, err := forkCommit.IsAncestor(sourceCommit)
704
-
// if err != nil {
705
-
// log.Printf("error resolving whether %s is ancestor of %s: %s", branch, data.HiddenRef, err)
706
-
// return
707
-
// }
708
-
//
709
-
// if isAncestor {
710
-
// status = types.FastForwardable
711
-
// } else {
712
-
// status = types.Conflict
713
-
// }
714
-
// }
715
-
//
716
-
// w.Header().Set("Content-Type", "application/json")
717
-
// json.NewEncoder(w).Encode(types.AncestorCheckResponse{Status: status})
718
-
// }
719
-
720
-
func (h *Handle) RepoLanguages(w http.ResponseWriter, r *http.Request) {
721
-
repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
722
-
ref := chi.URLParam(r, "ref")
723
-
ref, _ = url.PathUnescape(ref)
724
-
725
-
l := h.l.With("handler", "RepoLanguages")
726
-
727
-
gr, err := git.Open(repoPath, ref)
728
-
if err != nil {
729
-
l.Error("opening repo", "error", err.Error())
730
-
notFound(w)
731
-
return
732
-
}
733
-
734
-
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
735
-
defer cancel()
736
-
737
-
sizes, err := gr.AnalyzeLanguages(ctx)
738
-
if err != nil {
739
-
l.Error("failed to analyze languages", "error", err.Error())
740
-
writeError(w, err.Error(), http.StatusNoContent)
741
-
return
742
-
}
743
-
744
-
resp := types.RepoLanguageResponse{Languages: sizes}
745
-
746
-
writeJSON(w, resp)
747
-
}
748
-
749
-
// func (h *Handle) RepoForkSync(w http.ResponseWriter, r *http.Request) {
750
-
// l := h.l.With("handler", "RepoForkSync")
751
-
//
752
-
// data := struct {
753
-
// Did string `json:"did"`
754
-
// Source string `json:"source"`
755
-
// Name string `json:"name,omitempty"`
756
-
// }{}
757
-
//
758
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
759
-
// writeError(w, "invalid request body", http.StatusBadRequest)
760
-
// return
761
-
// }
762
-
//
763
-
// did := data.Did
764
-
// source := data.Source
765
-
//
766
-
// if did == "" || source == "" {
767
-
// l.Error("invalid request body, empty did or name")
768
-
// w.WriteHeader(http.StatusBadRequest)
769
-
// return
770
-
// }
771
-
//
772
-
// var name string
773
-
// if data.Name != "" {
774
-
// name = data.Name
775
-
// } else {
776
-
// name = filepath.Base(source)
777
-
// }
778
-
//
779
-
// branch := chi.URLParam(r, "branch")
780
-
// branch, _ = url.PathUnescape(branch)
781
-
//
782
-
// relativeRepoPath := filepath.Join(did, name)
783
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
784
-
//
785
-
// gr, err := git.Open(repoPath, branch)
786
-
// if err != nil {
787
-
// log.Println(err)
788
-
// notFound(w)
789
-
// return
790
-
// }
791
-
//
792
-
// err = gr.Sync()
793
-
// if err != nil {
794
-
// l.Error("error syncing repo fork", "error", err.Error())
795
-
// writeError(w, err.Error(), http.StatusInternalServerError)
796
-
// return
797
-
// }
798
-
//
799
-
// w.WriteHeader(http.StatusNoContent)
800
-
// }
801
-
802
-
// func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) {
803
-
// l := h.l.With("handler", "RepoFork")
804
-
//
805
-
// data := struct {
806
-
// Did string `json:"did"`
807
-
// Source string `json:"source"`
808
-
// Name string `json:"name,omitempty"`
809
-
// }{}
810
-
//
811
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
812
-
// writeError(w, "invalid request body", http.StatusBadRequest)
813
-
// return
814
-
// }
815
-
//
816
-
// did := data.Did
817
-
// source := data.Source
818
-
//
819
-
// if did == "" || source == "" {
820
-
// l.Error("invalid request body, empty did or name")
821
-
// w.WriteHeader(http.StatusBadRequest)
822
-
// return
823
-
// }
824
-
//
825
-
// var name string
826
-
// if data.Name != "" {
827
-
// name = data.Name
828
-
// } else {
829
-
// name = filepath.Base(source)
830
-
// }
831
-
//
832
-
// relativeRepoPath := filepath.Join(did, name)
833
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
834
-
//
835
-
// err := git.Fork(repoPath, source)
836
-
// if err != nil {
837
-
// l.Error("forking repo", "error", err.Error())
838
-
// writeError(w, err.Error(), http.StatusInternalServerError)
839
-
// return
840
-
// }
841
-
//
842
-
// // add perms for this user to access the repo
843
-
// err = h.e.AddRepo(did, rbac.ThisServer, relativeRepoPath)
844
-
// if err != nil {
845
-
// l.Error("adding repo permissions", "error", err.Error())
846
-
// writeError(w, err.Error(), http.StatusInternalServerError)
847
-
// return
848
-
// }
849
-
//
850
-
// hook.SetupRepo(
851
-
// hook.Config(
852
-
// hook.WithScanPath(h.c.Repo.ScanPath),
853
-
// hook.WithInternalApi(h.c.Server.InternalListenAddr),
854
-
// ),
855
-
// repoPath,
856
-
// )
857
-
//
858
-
// w.WriteHeader(http.StatusNoContent)
859
-
// }
860
-
861
-
// func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
862
-
// l := h.l.With("handler", "RemoveRepo")
863
-
//
864
-
// data := struct {
865
-
// Did string `json:"did"`
866
-
// Name string `json:"name"`
867
-
// }{}
868
-
//
869
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
870
-
// writeError(w, "invalid request body", http.StatusBadRequest)
871
-
// return
872
-
// }
873
-
//
874
-
// did := data.Did
875
-
// name := data.Name
876
-
//
877
-
// if did == "" || name == "" {
878
-
// l.Error("invalid request body, empty did or name")
879
-
// w.WriteHeader(http.StatusBadRequest)
880
-
// return
881
-
// }
882
-
//
883
-
// relativeRepoPath := filepath.Join(did, name)
884
-
// repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
885
-
// err := os.RemoveAll(repoPath)
886
-
// if err != nil {
887
-
// l.Error("removing repo", "error", err.Error())
888
-
// writeError(w, err.Error(), http.StatusInternalServerError)
889
-
// return
890
-
// }
891
-
//
892
-
// w.WriteHeader(http.StatusNoContent)
893
-
//
894
-
// }
895
-
896
-
// func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
897
-
// path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
898
-
//
899
-
// data := types.MergeRequest{}
900
-
//
901
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
902
-
// writeError(w, err.Error(), http.StatusBadRequest)
903
-
// h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err)
904
-
// return
905
-
// }
906
-
//
907
-
// mo := &git.MergeOptions{
908
-
// AuthorName: data.AuthorName,
909
-
// AuthorEmail: data.AuthorEmail,
910
-
// CommitBody: data.CommitBody,
911
-
// CommitMessage: data.CommitMessage,
912
-
// }
913
-
//
914
-
// patch := data.Patch
915
-
// branch := data.Branch
916
-
// gr, err := git.Open(path, branch)
917
-
// if err != nil {
918
-
// notFound(w)
919
-
// return
920
-
// }
921
-
//
922
-
// mo.FormatPatch = patchutil.IsFormatPatch(patch)
923
-
//
924
-
// if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
925
-
// var mergeErr *git.ErrMerge
926
-
// if errors.As(err, &mergeErr) {
927
-
// conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
928
-
// for i, conflict := range mergeErr.Conflicts {
929
-
// conflicts[i] = types.ConflictInfo{
930
-
// Filename: conflict.Filename,
931
-
// Reason: conflict.Reason,
932
-
// }
933
-
// }
934
-
// response := types.MergeCheckResponse{
935
-
// IsConflicted: true,
936
-
// Conflicts: conflicts,
937
-
// Message: mergeErr.Message,
938
-
// }
939
-
// writeConflict(w, response)
940
-
// h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr)
941
-
// } else {
942
-
// writeError(w, err.Error(), http.StatusBadRequest)
943
-
// h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error())
944
-
// }
945
-
// return
946
-
// }
947
-
//
948
-
// w.WriteHeader(http.StatusOK)
949
-
// }
950
-
951
-
// func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) {
952
-
// path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
953
-
//
954
-
// var data struct {
955
-
// Patch string `json:"patch"`
956
-
// Branch string `json:"branch"`
957
-
// }
958
-
//
959
-
// if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
960
-
// writeError(w, err.Error(), http.StatusBadRequest)
961
-
// h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err)
962
-
// return
963
-
// }
964
-
//
965
-
// patch := data.Patch
966
-
// branch := data.Branch
967
-
// gr, err := git.Open(path, branch)
968
-
// if err != nil {
969
-
// notFound(w)
970
-
// return
971
-
// }
972
-
//
973
-
// err = gr.MergeCheck([]byte(patch), branch)
974
-
// if err == nil {
975
-
// response := types.MergeCheckResponse{
976
-
// IsConflicted: false,
977
-
// }
978
-
// writeJSON(w, response)
979
-
// return
980
-
// }
981
-
//
982
-
// var mergeErr *git.ErrMerge
983
-
// if errors.As(err, &mergeErr) {
984
-
// conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
985
-
// for i, conflict := range mergeErr.Conflicts {
986
-
// conflicts[i] = types.ConflictInfo{
987
-
// Filename: conflict.Filename,
988
-
// Reason: conflict.Reason,
989
-
// }
990
-
// }
991
-
// response := types.MergeCheckResponse{
992
-
// IsConflicted: true,
993
-
// Conflicts: conflicts,
994
-
// Message: mergeErr.Message,
995
-
// }
996
-
// writeConflict(w, response)
997
-
// h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error())
998
-
// return
999
-
// }
1000
-
// writeError(w, err.Error(), http.StatusInternalServerError)
1001
-
// h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
1002
-
// }
1003
-
1004
-
func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
1005
-
rev1 := chi.URLParam(r, "rev1")
1006
-
rev1, _ = url.PathUnescape(rev1)
1007
-
1008
-
rev2 := chi.URLParam(r, "rev2")
1009
-
rev2, _ = url.PathUnescape(rev2)
1010
-
1011
-
l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
1012
-
1013
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
1014
-
gr, err := git.PlainOpen(path)
1015
-
if err != nil {
1016
-
notFound(w)
1017
-
return
1018
-
}
1019
-
1020
-
commit1, err := gr.ResolveRevision(rev1)
1021
-
if err != nil {
1022
-
l.Error("error resolving revision 1", "msg", err.Error())
1023
-
writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest)
1024
-
return
1025
-
}
1026
-
1027
-
commit2, err := gr.ResolveRevision(rev2)
1028
-
if err != nil {
1029
-
l.Error("error resolving revision 2", "msg", err.Error())
1030
-
writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest)
1031
-
return
1032
-
}
1033
-
1034
-
rawPatch, formatPatch, err := gr.FormatPatch(commit1, commit2)
1035
-
if err != nil {
1036
-
l.Error("error comparing revisions", "msg", err.Error())
1037
-
writeError(w, "error comparing revisions", http.StatusBadRequest)
1038
-
return
1039
-
}
1040
-
1041
-
writeJSON(w, types.RepoFormatPatchResponse{
1042
-
Rev1: commit1.Hash.String(),
1043
-
Rev2: commit2.Hash.String(),
1044
-
FormatPatch: formatPatch,
1045
-
Patch: rawPatch,
1046
-
})
1047
-
}
1048
-
1049
-
func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
1050
-
l := h.l.With("handler", "DefaultBranch")
1051
-
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
1052
-
1053
-
gr, err := git.Open(path, "")
1054
-
if err != nil {
1055
-
notFound(w)
1056
-
return
1057
-
}
1058
-
1059
-
branch, err := gr.FindMainBranch()
1060
-
if err != nil {
1061
-
writeError(w, err.Error(), http.StatusInternalServerError)
1062
-
l.Error("getting default branch", "error", err.Error())
1063
-
return
1064
-
}
1065
-
1066
-
writeJSON(w, types.RepoDefaultBranchResponse{
1067
-
Branch: branch,
1068
-
})
1069
-
}
+6
-6
knotserver/ingester.go
+6
-6
knotserver/ingester.go
···
24
24
"tangled.sh/tangled.sh/core/workflow"
25
25
)
26
26
27
-
func (h *Handle) processPublicKey(ctx context.Context, event *models.Event) error {
27
+
func (h *Knot) processPublicKey(ctx context.Context, event *models.Event) error {
28
28
l := log.FromContext(ctx)
29
29
raw := json.RawMessage(event.Commit.Record)
30
30
did := event.Did
···
46
46
return nil
47
47
}
48
48
49
-
func (h *Handle) processKnotMember(ctx context.Context, event *models.Event) error {
49
+
func (h *Knot) processKnotMember(ctx context.Context, event *models.Event) error {
50
50
l := log.FromContext(ctx)
51
51
raw := json.RawMessage(event.Commit.Record)
52
52
did := event.Did
···
86
86
return nil
87
87
}
88
88
89
-
func (h *Handle) processPull(ctx context.Context, event *models.Event) error {
89
+
func (h *Knot) processPull(ctx context.Context, event *models.Event) error {
90
90
raw := json.RawMessage(event.Commit.Record)
91
91
did := event.Did
92
92
···
219
219
}
220
220
221
221
// duplicated from add collaborator
222
-
func (h *Handle) processCollaborator(ctx context.Context, event *models.Event) error {
222
+
func (h *Knot) processCollaborator(ctx context.Context, event *models.Event) error {
223
223
raw := json.RawMessage(event.Commit.Record)
224
224
did := event.Did
225
225
···
280
280
return h.fetchAndAddKeys(ctx, subjectId.DID.String())
281
281
}
282
282
283
-
func (h *Handle) fetchAndAddKeys(ctx context.Context, did string) error {
283
+
func (h *Knot) fetchAndAddKeys(ctx context.Context, did string) error {
284
284
l := log.FromContext(ctx)
285
285
286
286
keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did)
···
323
323
return nil
324
324
}
325
325
326
-
func (h *Handle) processMessages(ctx context.Context, event *models.Event) error {
326
+
func (h *Knot) processMessages(ctx context.Context, event *models.Event) error {
327
327
if event.Kind != models.EventKindCommit {
328
328
return nil
329
329
}
+20
-46
knotserver/routes.go
knotserver/router.go
+20
-46
knotserver/routes.go
knotserver/router.go
···
19
19
"tangled.sh/tangled.sh/core/xrpc/serviceauth"
20
20
)
21
21
22
-
type Handle struct {
22
+
type Knot struct {
23
23
c *config.Config
24
24
db *db.DB
25
25
jc *jetstream.JetstreamClient
···
32
32
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) {
33
33
r := chi.NewRouter()
34
34
35
-
h := Handle{
35
+
h := Knot{
36
36
c: c,
37
37
db: db,
38
38
e: e,
···
68
68
return nil, fmt.Errorf("failed to start jetstream: %w", err)
69
69
}
70
70
71
-
r.Get("/", h.Index)
72
-
r.Get("/capabilities", h.Capabilities)
73
-
r.Get("/version", h.Version)
74
-
r.Get("/owner", func(w http.ResponseWriter, r *http.Request) {
71
+
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
72
+
w.Write([]byte("This is a knot server. More info at https://tangled.sh"))
73
+
})
74
+
75
+
owner := func(w http.ResponseWriter, r *http.Request) {
75
76
w.Write([]byte(h.c.Server.Owner))
76
-
})
77
+
}
78
+
// Deprecated: the sh.tangled.knot.owner xrpc call should be used instead
79
+
r.Get("/owner", owner)
80
+
77
81
r.Route("/{did}", func(r chi.Router) {
78
-
// Repo routes
79
82
r.Route("/{name}", func(r chi.Router) {
80
-
81
-
r.Route("/languages", func(r chi.Router) {
82
-
r.Get("/", h.RepoLanguages)
83
-
r.Get("/{ref}", h.RepoLanguages)
84
-
})
85
-
86
-
r.Get("/", h.RepoIndex)
83
+
// routes for git operations
87
84
r.Get("/info/refs", h.InfoRefs)
88
85
r.Post("/git-upload-pack", h.UploadPack)
89
86
r.Post("/git-receive-pack", h.ReceivePack)
90
-
r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects
91
-
92
-
r.Route("/tree/{ref}", func(r chi.Router) {
93
-
r.Get("/", h.RepoIndex)
94
-
r.Get("/*", h.RepoTree)
95
-
})
96
-
97
-
r.Route("/blob/{ref}", func(r chi.Router) {
98
-
r.Get("/*", h.Blob)
99
-
})
100
-
101
-
r.Route("/raw/{ref}", func(r chi.Router) {
102
-
r.Get("/*", h.BlobRaw)
103
-
})
104
-
105
-
r.Get("/log/{ref}", h.Log)
106
-
r.Get("/archive/{file}", h.Archive)
107
-
r.Get("/commit/{ref}", h.Diff)
108
-
r.Get("/tags", h.Tags)
109
-
r.Route("/branches", func(r chi.Router) {
110
-
r.Get("/", h.Branches)
111
-
r.Get("/{branch}", h.Branch)
112
-
r.Get("/default", h.DefaultBranch)
113
-
})
114
87
})
115
88
})
116
89
117
90
// xrpc apis
118
-
r.Mount("/xrpc", h.XrpcRouter())
91
+
r.Route("/xrpc", func(r chi.Router) {
92
+
r.Get("/_health", h.Version)
93
+
r.Get("/sh.tangled.knot.owner", owner)
94
+
r.Mount("/", h.XrpcRouter())
95
+
})
119
96
120
97
// Socket that streams git oplogs
121
98
r.Get("/events", h.Events)
122
99
123
-
// All public keys on the knot.
124
-
r.Get("/keys", h.Keys)
125
-
126
100
return r, nil
127
101
}
128
102
129
-
func (h *Handle) XrpcRouter() http.Handler {
103
+
func (h *Knot) XrpcRouter() http.Handler {
130
104
logger := tlog.New("knots")
131
105
132
106
serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String())
···
147
121
// version is set during build time.
148
122
var version string
149
123
150
-
func (h *Handle) Version(w http.ResponseWriter, r *http.Request) {
124
+
func (h *Knot) Version(w http.ResponseWriter, r *http.Request) {
151
125
if version == "" {
152
126
info, ok := debug.ReadBuildInfo()
153
127
if !ok {
···
192
166
fmt.Fprintf(w, "knotserver/%s", version)
193
167
}
194
168
195
-
func (h *Handle) configureOwner() error {
169
+
func (h *Knot) configureOwner() error {
196
170
cfgOwner := h.c.Server.Owner
197
171
198
172
rbacDomain := "thisserver"