forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

knotserver: implement internal endpoint to record git push

records git operations in an "oplog" table. this table can be routinely
cleaned up as necessary.

Signed-off-by: oppiliappan <me@oppi.li>

Changed files
+135
knotserver
+9
knotserver/db/init.go
···
id integer primary key autoincrement,
last_time_us integer not null
);
+
+
create table if not exists oplog (
+
tid text primary key,
+
did text not null,
+
repo text not null,
+
old_sha text not null,
+
new_sha text not null,
+
ref text not null
+
);
`)
if err != nil {
return nil, err
+63
knotserver/db/oplog.go
···
+
package db
+
+
import (
+
"fmt"
+
)
+
+
type Op struct {
+
Tid string // time based ID, easy to enumerate & monotonic
+
Did string // did of pusher
+
Repo string // <did/repo> fully qualified repo
+
OldSha string // old sha of reference being updated
+
NewSha string // new sha of reference being updated
+
Ref string // the reference being updated
+
}
+
+
func (d *DB) InsertOp(op Op) error {
+
_, err := d.db.Exec(
+
`insert into oplog (tid, did, repo, old_sha, new_sha, ref) values (?, ?, ?, ?, ?, ?)`,
+
op.Tid,
+
op.Did,
+
op.Repo,
+
op.OldSha,
+
op.NewSha,
+
op.Ref,
+
)
+
return err
+
}
+
+
func (d *DB) GetOps(cursor string) ([]Op, error) {
+
whereClause := ""
+
args := []any{}
+
if cursor != "" {
+
whereClause = "where tid > ?"
+
args = append(args, cursor)
+
}
+
+
query := fmt.Sprintf(`
+
select tid, did, repo, old_sha, new_sha, ref
+
from oplog
+
%s
+
order by tid asc
+
limit 100
+
`, whereClause)
+
+
rows, err := d.db.Query(query, args...)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
var ops []Op
+
for rows.Next() {
+
var op Op
+
rows.Scan(&op.Tid, &op.Did, &op.Repo, &op.OldSha, &op.NewSha, &op.Ref)
+
ops = append(ops, op)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return ops, nil
+
}
+56
knotserver/internal.go
···
package knotserver
import (
+
"bufio"
"context"
"log/slog"
"net/http"
+
"path/filepath"
+
"strings"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
···
return
}
+
func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) {
+
l := h.l.With("handler", "PostReceiveHook")
+
+
gitAbsoluteDir := r.Header.Get("X-Git-Dir")
+
gitRelativeDir, err := filepath.Rel(h.c.Repo.ScanPath, gitAbsoluteDir)
+
if err != nil {
+
l.Error("failed to calculate relative git dir", "scanPath", h.c.Repo.ScanPath, "gitAbsoluteDir", gitAbsoluteDir)
+
return
+
}
+
gitUserDid := r.Header.Get("X-Git-User-Did")
+
+
var ops []db.Op
+
scanner := bufio.NewScanner(r.Body)
+
for scanner.Scan() {
+
line := scanner.Text()
+
parts := strings.SplitN(line, " ", 3)
+
if len(parts) != 3 {
+
l.Error("invalid payload", "parts", parts)
+
continue
+
}
+
+
tid := TID()
+
oldSha := parts[0]
+
newSha := parts[1]
+
ref := parts[2]
+
op := db.Op{
+
Tid: tid,
+
Did: gitUserDid,
+
Repo: gitRelativeDir,
+
OldSha: oldSha,
+
NewSha: newSha,
+
Ref: ref,
+
}
+
ops = append(ops, op)
+
}
+
+
if err := scanner.Err(); err != nil {
+
l.Error("failed to read payload", "err", err)
+
return
+
}
+
+
for _, op := range ops {
+
err := h.db.InsertOp(op)
+
if err != nil {
+
l.Error("failed to insert op", "err", err, "op", op)
+
continue
+
}
+
}
+
+
return
+
}
+
func Internal(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger) http.Handler {
r := chi.NewRouter()
···
r.Get("/push-allowed", h.PushAllowed)
r.Get("/keys", h.InternalKeys)
+
r.Post("/hooks/post-receive", h.PostReceiveHook)
r.Mount("/debug", middleware.Profiler())
return r
+7
knotserver/util.go
···
"os"
"path/filepath"
+
"github.com/bluesky-social/indigo/atproto/syntax"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-chi/chi/v5"
"github.com/microcosm-cc/bluemonday"
···
func setMIME(w http.ResponseWriter, mime string) {
w.Header().Add("Content-Type", mime)
}
+
+
var TIDClock = syntax.NewTIDClock(0)
+
+
func TID() string {
+
return TIDClock.Next().String()
+
}