···703 return err
704 })
705706+ // repurpose the read-only column to "needs-upgrade"
707+ runMigration(conn, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {
708+ _, err := tx.Exec(`
709+ alter table registrations rename column read_only to needs_upgrade;
710+ `)
711+ return err
712+ })
713+714+ // require all knots to upgrade after the release of total xrpc
715+ runMigration(conn, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error {
716+ _, err := tx.Exec(`
717+ update registrations set needs_upgrade = 1;
718+ `)
719+ return err
720+ })
721+722+ // require all knots to upgrade after the release of total xrpc
723+ runMigration(conn, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error {
724+ _, err := tx.Exec(`
725+ alter table spindles add column needs_upgrade integer not null default 0;
726+ `)
727+ if err != nil {
728+ return err
729+ }
730+731+ _, err = tx.Exec(`
732+ update spindles set needs_upgrade = 1;
733+ `)
734+ return err
735+ })
736+737 return &DB{db}, nil
738}
739
+17-17
appview/db/registration.go
···10// Registration represents a knot registration. Knot would've been a better
11// name but we're stuck with this for historical reasons.
12type Registration struct {
13- Id int64
14- Domain string
15- ByDid string
16- Created *time.Time
17- Registered *time.Time
18- ReadOnly bool
19}
2021func (r *Registration) Status() Status {
22- if r.ReadOnly {
23- return ReadOnly
24 } else if r.Registered != nil {
25 return Registered
26 } else {
···32 return r.Status() == Registered
33}
3435-func (r *Registration) IsReadOnly() bool {
36- return r.Status() == ReadOnly
37}
3839func (r *Registration) IsPending() bool {
···45const (
46 Registered Status = iota
47 Pending
48- ReadOnly
49)
5051func GetRegistrations(e Execer, filters ...filter) ([]Registration, error) {
···64 }
6566 query := fmt.Sprintf(`
67- select id, domain, did, created, registered, read_only
68 from registrations
69 %s
70 order by created
···80 for rows.Next() {
81 var createdAt string
82 var registeredAt sql.Null[string]
83- var readOnly int
84 var reg Registration
8586- err = rows.Scan(®.Id, ®.Domain, ®.ByDid, &createdAt, ®isteredAt, &readOnly)
87 if err != nil {
88 return nil, err
89 }
···98 }
99 }
100101- if readOnly != 0 {
102- reg.ReadOnly = true
103 }
104105 registrations = append(registrations, reg)
···116 args = append(args, filter.Arg()...)
117 }
118119- query := "update registrations set registered = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), read_only = 0"
120 if len(conditions) > 0 {
121 query += " where " + strings.Join(conditions, " and ")
122 }
···10// Registration represents a knot registration. Knot would've been a better
11// name but we're stuck with this for historical reasons.
12type Registration struct {
13+ Id int64
14+ Domain string
15+ ByDid string
16+ Created *time.Time
17+ Registered *time.Time
18+ NeedsUpgrade bool
19}
2021func (r *Registration) Status() Status {
22+ if r.NeedsUpgrade {
23+ return NeedsUpgrade
24 } else if r.Registered != nil {
25 return Registered
26 } else {
···32 return r.Status() == Registered
33}
3435+func (r *Registration) IsNeedsUpgrade() bool {
36+ return r.Status() == NeedsUpgrade
37}
3839func (r *Registration) IsPending() bool {
···45const (
46 Registered Status = iota
47 Pending
48+ NeedsUpgrade
49)
5051func GetRegistrations(e Execer, filters ...filter) ([]Registration, error) {
···64 }
6566 query := fmt.Sprintf(`
67+ select id, domain, did, created, registered, needs_upgrade
68 from registrations
69 %s
70 order by created
···80 for rows.Next() {
81 var createdAt string
82 var registeredAt sql.Null[string]
83+ var needsUpgrade int
84 var reg Registration
8586+ err = rows.Scan(®.Id, ®.Domain, ®.ByDid, &createdAt, ®isteredAt, &needsUpgrade)
87 if err != nil {
88 return nil, err
89 }
···98 }
99 }
100101+ if needsUpgrade != 0 {
102+ reg.NeedsUpgrade = true
103 }
104105 registrations = append(registrations, reg)
···116 args = append(args, filter.Arg()...)
117 }
118119+ query := "update registrations set registered = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), needs_upgrade = 0"
120 if len(conditions) > 0 {
121 query += " where " + strings.Join(conditions, " and ")
122 }
+14-7
appview/db/spindle.go
···10)
1112type Spindle struct {
13- Id int
14- Owner syntax.DID
15- Instance string
16- Verified *time.Time
17- Created time.Time
018}
1920type SpindleMember struct {
···42 }
4344 query := fmt.Sprintf(
45- `select id, owner, instance, verified, created
46 from spindles
47 %s
48 order by created
···61 var spindle Spindle
62 var createdAt string
63 var verified sql.NullString
06465 if err := rows.Scan(
66 &spindle.Id,
···68 &spindle.Instance,
69 &verified,
70 &createdAt,
071 ); err != nil {
72 return nil, err
73 }
···86 spindle.Verified = &t
87 }
88000089 spindles = append(spindles, spindle)
90 }
91···115 whereClause = " where " + strings.Join(conditions, " and ")
116 }
117118- query := fmt.Sprintf(`update spindles set verified = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)
119120 res, err := e.Exec(query, args...)
121 if err != nil {
···10)
1112type Spindle struct {
13+ Id int
14+ Owner syntax.DID
15+ Instance string
16+ Verified *time.Time
17+ Created time.Time
18+ NeedsUpgrade bool
19}
2021type SpindleMember struct {
···43 }
4445 query := fmt.Sprintf(
46+ `select id, owner, instance, verified, created, needs_upgrade
47 from spindles
48 %s
49 order by created
···62 var spindle Spindle
63 var createdAt string
64 var verified sql.NullString
65+ var needsUpgrade int
6667 if err := rows.Scan(
68 &spindle.Id,
···70 &spindle.Instance,
71 &verified,
72 &createdAt,
73+ &needsUpgrade,
74 ); err != nil {
75 return nil, err
76 }
···89 spindle.Verified = &t
90 }
9192+ if needsUpgrade != 0 {
93+ spindle.NeedsUpgrade = true
94+ }
95+96 spindles = append(spindles, spindle)
97 }
98···122 whereClause = " where " + strings.Join(conditions, " and ")
123 }
124125+ query := fmt.Sprintf(`update spindles set verified = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now'), needs_upgrade = 0 %s`, whereClause)
126127 res, err := e.Exec(query, args...)
128 if err != nil {
+5-57
appview/knots/knots.go
···3import (
4 "errors"
5 "fmt"
6- "log"
7 "log/slog"
8 "net/http"
9 "slices"
···17 "tangled.sh/tangled.sh/core/appview/oauth"
18 "tangled.sh/tangled.sh/core/appview/pages"
19 "tangled.sh/tangled.sh/core/appview/serververify"
020 "tangled.sh/tangled.sh/core/eventconsumer"
21 "tangled.sh/tangled.sh/core/idresolver"
22 "tangled.sh/tangled.sh/core/rbac"
···49 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/retry", k.retry)
50 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/add", k.addMember)
51 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/remove", k.removeMember)
52-53- r.With(middleware.AuthMiddleware(k.OAuth)).Get("/upgradeBanner", k.banner)
5455 return r
56}
···399 if err != nil {
400 l.Error("verification failed", "err", err)
401402- if errors.Is(err, serververify.FetchError) {
403- k.Pages.Notice(w, noticeId, "Failed to verify knot, unable to fetch owner.")
404 return
405 }
406···420 return
421 }
422423- // if this knot was previously read-only, then emit a record too
424 //
425 // this is part of migrating from the old knot system to the new one
426- if registration.ReadOnly {
427 // re-announce by registering under same rkey
428 client, err := k.OAuth.AuthorizedClient(r)
429 if err != nil {
···484 return
485 }
486 updatedRegistration := registrations[0]
487-488- log.Println(updatedRegistration)
489490 w.Header().Set("HX-Reswap", "outerHTML")
491 k.Pages.KnotListing(w, pages.KnotListingParams{
···678 // ok
679 k.Pages.HxRefresh(w)
680}
681-682-func (k *Knots) banner(w http.ResponseWriter, r *http.Request) {
683- user := k.OAuth.GetUser(r)
684- l := k.Logger.With("handler", "banner")
685- l = l.With("did", user.Did)
686- l = l.With("handle", user.Handle)
687-688- allRegistrations, err := db.GetRegistrations(
689- k.Db,
690- db.FilterEq("did", user.Did),
691- )
692- if err != nil {
693- l.Error("non-fatal: failed to get registrations")
694- return
695- }
696-697- httpClient := &http.Client{Timeout: 5 * time.Second}
698- regs404 := []db.Registration{}
699- for _, reg := range allRegistrations {
700- healthURL := fmt.Sprintf("http://%s/xrpc/_health", reg.Domain)
701-702- fmt.Println(healthURL)
703-704- req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, healthURL, nil)
705- if err != nil {
706- l.Error("failed to create health check request", "domain", reg.Domain, "err", err)
707- continue
708- }
709-710- resp, err := httpClient.Do(req)
711- if err != nil {
712- l.Error("failed to make health check request", "domain", reg.Domain, "err", err)
713- continue
714- }
715- defer resp.Body.Close()
716-717- if resp.StatusCode == http.StatusNotFound {
718- regs404 = append(regs404, reg)
719- }
720- }
721- if len(regs404) == 0 {
722- return
723- }
724-725- k.Pages.KnotBanner(w, pages.KnotBannerParams{
726- Registrations: regs404,
727- })
728-}
···3import (
4 "errors"
5 "fmt"
06 "log/slog"
7 "net/http"
8 "slices"
···16 "tangled.sh/tangled.sh/core/appview/oauth"
17 "tangled.sh/tangled.sh/core/appview/pages"
18 "tangled.sh/tangled.sh/core/appview/serververify"
19+ "tangled.sh/tangled.sh/core/appview/xrpcclient"
20 "tangled.sh/tangled.sh/core/eventconsumer"
21 "tangled.sh/tangled.sh/core/idresolver"
22 "tangled.sh/tangled.sh/core/rbac"
···49 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/retry", k.retry)
50 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/add", k.addMember)
51 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/remove", k.removeMember)
005253 return r
54}
···397 if err != nil {
398 l.Error("verification failed", "err", err)
399400+ if errors.Is(err, xrpcclient.ErrXrpcUnsupported) {
401+ k.Pages.Notice(w, noticeId, "Failed to verify knot, XRPC queries are unsupported on this knot, consider upgrading!")
402 return
403 }
404···418 return
419 }
420421+ // if this knot requires upgrade, then emit a record too
422 //
423 // this is part of migrating from the old knot system to the new one
424+ if registration.NeedsUpgrade {
425 // re-announce by registering under same rkey
426 client, err := k.OAuth.AuthorizedClient(r)
427 if err != nil {
···482 return
483 }
484 updatedRegistration := registrations[0]
00485486 w.Header().Set("HX-Reswap", "outerHTML")
487 k.Pages.KnotListing(w, pages.KnotListingParams{
···674 // ok
675 k.Pages.HxRefresh(w)
676}
000000000000000000000000000000000000000000000000
···1+{{ define "banner" }}
2+<div class="px-6 py-2">
3+ The following services that you administer will have to be upgraded to be compatible with the latest version of Tangled:
4+ {{ if .Registrations }}
5+ <ul class="list-disc mx-12 my-2">
6+ {{range .Registrations}}
7+ <li>{{ .Domain }}</li>
8+ {{ end }}
9+ </ul>
10+ Repositories hosted on these knots may not be accessible until upgraded.
11+ {{ end }}
12+13+ {{ if .Spindles }}
14+ <ul class="list-disc mx-12 my-2">
15+ {{range .Spindles}}
16+ <li>{{ .Instance }}</li>
17+ {{ end }}
18+ </ul>
19+ Pipelines may not be executed on these spindles until upgraded.
20+ {{ end }}
21+ <a href="https://tangled.sh/@tangled.sh/core/tree/master/docs/migrations/">Click to read the upgrade guide</a>.
22+</div>
23+{{ end }}
···1-{{ define "knots/fragments/bannerReadOnly" }}
2-<div class="w-full px-6 py-2 -z-15 bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 border border-yellow-200 dark:border-yellow-800 rounded-b drop-shadow-sm">
3- A knot ({{range $i, $r := .Registrations}}{{if ne $i 0}}, {{end}}{{ $r.Domain }}{{ end }})
4- that you administer is presently read-only. Consider upgrading this knot to
5- continue creating repositories on it.
6- <a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/migrations/knot-1.7.0.md">Click to read the upgrade guide</a>.
7-</div>
8-{{ end }}
···1-{{ define "knots/fragments/bannerRequiresUpgrade" }}
2-<div class="px-6 py-2">
3- The following knots that you administer will have to be upgraded to be compatible with the latest version of Tangled:
4- <ul class="list-disc mx-12 my-2">
5- {{range $i, $r := .Registrations}}
6- <li>{{ $r.Domain }}</li>
7- {{ end }}
8- </ul>
9- Repositories hosted on these knots will not be accessible until upgraded.
10- <a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/migrations/knot-1.8.0.md">Click to read the upgrade guide</a>.
11-</div>
12-{{ end }}