···2626 pragma auto_vacuum = incremental;
2727 pragma busy_timeout = 5000;
28282929+ create table if not exists owner (
3030+ id integer primary key check (id = 0),
3131+ did text not null,
3232+ createdAt text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
3333+ );
3434+2935 create table if not exists known_dids (
3036 did text primary key
3137 );
+18
knotserver/db/owner.go
···11+package db
22+33+func (d *DB) SetOwner(did string) error {
44+ query := `insert into owner (id, did) values (?, ?)`
55+ _, err := d.db.Exec(query, 0, did)
66+ return err
77+}
88+99+func (d *DB) Owner() (string, error) {
1010+ query := `select did from owner`
1111+1212+ var did string
1313+ err := d.db.QueryRow(query).Scan(&did)
1414+ if err != nil {
1515+ return "", err
1616+ }
1717+ return did, nil
1818+}
+100-27
knotserver/handler.go
···66 "log/slog"
77 "net/http"
88 "runtime/debug"
99+ "time"
9101111+ "github.com/bluesky-social/indigo/api/atproto"
1212+ "github.com/bluesky-social/indigo/atproto/syntax"
1313+ lexutil "github.com/bluesky-social/indigo/lex/util"
1414+ "github.com/bluesky-social/indigo/xrpc"
1015 "github.com/go-chi/chi/v5"
1616+ "tangled.sh/tangled.sh/core/api/tangled"
1117 "tangled.sh/tangled.sh/core/jetstream"
1218 "tangled.sh/tangled.sh/core/knotserver/config"
1319 "tangled.sh/tangled.sh/core/knotserver/db"
1420 "tangled.sh/tangled.sh/core/rbac"
2121+ "tangled.sh/tangled.sh/core/resolver"
1522)
16231724const (
···1926)
20272128type Handle struct {
2222- c *config.Config
2323- db *db.DB
2424- jc *jetstream.JetstreamClient
2525- e *rbac.Enforcer
2626- l *slog.Logger
2727-2828- // init is a channel that is closed when the knot has been initailized
2929- // i.e. when the first user (knot owner) has been added.
3030- init chan struct{}
3131- knotInitialized bool
2929+ c *config.Config
3030+ db *db.DB
3131+ jc *jetstream.JetstreamClient
3232+ e *rbac.Enforcer
3333+ l *slog.Logger
3434+ clock syntax.TIDClock
3235}
33363437func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger) (http.Handler, error) {
3535- r := chi.NewRouter()
3636-3738 h := Handle{
3838- c: c,
3939- db: db,
4040- e: e,
4141- l: l,
4242- jc: jc,
4343- init: make(chan struct{}),
3939+ c: c,
4040+ db: db,
4141+ e: e,
4242+ l: l,
4343+ jc: jc,
4444 }
45454646 err := e.AddDomain(ThisServer)
···4848 return nil, fmt.Errorf("failed to setup enforcer: %w", err)
4949 }
50505151+ // if this knot does not already have an owner, publish it
5252+ if _, err := h.db.Owner(); err != nil {
5353+ l.Info("publishing this knot ...", "owner", h.c.Owner.Did)
5454+ err = h.Publish()
5555+ if err != nil {
5656+ return nil, fmt.Errorf("failed to announce knot: %w", err)
5757+ }
5858+ }
5959+6060+ l.Info("this knot has been published", "owner", h.c.Owner.Did)
6161+5162 err = h.jc.StartJetstream(ctx, h.processMessages)
5263 if err != nil {
5364 return nil, fmt.Errorf("failed to start jetstream: %w", err)
5465 }
55665656- // Check if the knot knows about any Dids;
5757- // if it does, it is already initialized and we can repopulate the
5858- // Jetstream subscriptions.
5967 dids, err := db.GetAllDids()
6068 if err != nil {
6169 return nil, fmt.Errorf("failed to get all Dids: %w", err)
6270 }
63716464- if len(dids) > 0 {
6565- h.knotInitialized = true
6666- close(h.init)
6767- for _, d := range dids {
6868- h.jc.AddDid(d)
6969- }
7272+ for _, d := range dids {
7373+ h.jc.AddDid(d)
7074 }
7575+7676+ r := chi.NewRouter()
71777278 r.Get("/", h.Index)
7379 r.Get("/capabilities", h.Capabilities)
···184190 w.Header().Set("Content-Type", "text/plain")
185191 fmt.Fprintf(w, "knotserver/%s", version)
186192}
193193+194194+func (h *Handle) Publish() error {
195195+ ownerDid := h.c.Owner.Did
196196+ appPassword := h.c.Owner.AppPassword
197197+198198+ res := resolver.DefaultResolver()
199199+ ident, err := res.ResolveIdent(context.Background(), ownerDid)
200200+ if err != nil {
201201+ return err
202202+ }
203203+204204+ client := xrpc.Client{
205205+ Host: ident.PDSEndpoint(),
206206+ }
207207+208208+ resp, err := atproto.ServerCreateSession(context.Background(), &client, &atproto.ServerCreateSession_Input{
209209+ Identifier: ownerDid,
210210+ Password: appPassword,
211211+ })
212212+ if err != nil {
213213+ return err
214214+ }
215215+216216+ authClient := xrpc.Client{
217217+ Host: ident.PDSEndpoint(),
218218+ Auth: &xrpc.AuthInfo{
219219+ AccessJwt: resp.AccessJwt,
220220+ RefreshJwt: resp.RefreshJwt,
221221+ Handle: resp.Handle,
222222+ Did: resp.Did,
223223+ },
224224+ }
225225+226226+ rkey := h.TID()
227227+228228+ // write a "knot" record to the owners's pds
229229+ _, err = atproto.RepoPutRecord(context.Background(), &authClient, &atproto.RepoPutRecord_Input{
230230+ Collection: tangled.KnotNSID,
231231+ Repo: ownerDid,
232232+ Rkey: rkey,
233233+ Record: &lexutil.LexiconTypeDecoder{
234234+ Val: &tangled.Knot{
235235+ CreatedAt: time.Now().Format(time.RFC3339),
236236+ Host: h.c.Server.Hostname,
237237+ },
238238+ },
239239+ })
240240+ if err != nil {
241241+ return err
242242+ }
243243+244244+ err = h.db.SetOwner(ownerDid)
245245+ if err != nil {
246246+ return err
247247+ }
248248+249249+ err = h.db.AddDid(ownerDid)
250250+ if err != nil {
251251+ return err
252252+ }
253253+254254+ return nil
255255+}
256256+257257+func (h *Handle) TID() string {
258258+ return h.clock.Next().String()
259259+}