···703703 return err704704 })705705706706+ // repurpose the read-only column to "needs-upgrade"707707+ runMigration(conn, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {708708+ _, err := tx.Exec(`709709+ alter table registrations rename column read_only to needs_upgrade;710710+ `)711711+ return err712712+ })713713+714714+ // require all knots to upgrade after the release of total xrpc715715+ runMigration(conn, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error {716716+ _, err := tx.Exec(`717717+ update registrations set needs_upgrade = 1;718718+ `)719719+ return err720720+ })721721+722722+ // require all knots to upgrade after the release of total xrpc723723+ runMigration(conn, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error {724724+ _, err := tx.Exec(`725725+ alter table spindles add column needs_upgrade integer not null default 0;726726+ `)727727+ if err != nil {728728+ return err729729+ }730730+731731+ _, err = tx.Exec(`732732+ update spindles set needs_upgrade = 1;733733+ `)734734+ return err735735+ })736736+706737 return &DB{db}, nil707738}708739
+17-17
appview/db/registration.go
···1010// Registration represents a knot registration. Knot would've been a better1111// name but we're stuck with this for historical reasons.1212type Registration struct {1313- Id int641414- Domain string1515- ByDid string1616- Created *time.Time1717- Registered *time.Time1818- ReadOnly bool1313+ Id int641414+ Domain string1515+ ByDid string1616+ Created *time.Time1717+ Registered *time.Time1818+ NeedsUpgrade bool1919}20202121func (r *Registration) Status() Status {2222- if r.ReadOnly {2323- return ReadOnly2222+ if r.NeedsUpgrade {2323+ return NeedsUpgrade2424 } else if r.Registered != nil {2525 return Registered2626 } else {···3232 return r.Status() == Registered3333}34343535-func (r *Registration) IsReadOnly() bool {3636- return r.Status() == ReadOnly3535+func (r *Registration) IsNeedsUpgrade() bool {3636+ return r.Status() == NeedsUpgrade3737}38383939func (r *Registration) IsPending() bool {···4545const (4646 Registered Status = iota4747 Pending4848- ReadOnly4848+ NeedsUpgrade4949)50505151func GetRegistrations(e Execer, filters ...filter) ([]Registration, error) {···6464 }65656666 query := fmt.Sprintf(`6767- select id, domain, did, created, registered, read_only6767+ select id, domain, did, created, registered, needs_upgrade6868 from registrations6969 %s7070 order by created···8080 for rows.Next() {8181 var createdAt string8282 var registeredAt sql.Null[string]8383- var readOnly int8383+ var needsUpgrade int8484 var reg Registration85858686- err = rows.Scan(®.Id, ®.Domain, ®.ByDid, &createdAt, ®isteredAt, &readOnly)8686+ err = rows.Scan(®.Id, ®.Domain, ®.ByDid, &createdAt, ®isteredAt, &needsUpgrade)8787 if err != nil {8888 return nil, err8989 }···9898 }9999 }100100101101- if readOnly != 0 {102102- reg.ReadOnly = true101101+ if needsUpgrade != 0 {102102+ reg.NeedsUpgrade = true103103 }104104105105 registrations = append(registrations, reg)···116116 args = append(args, filter.Arg()...)117117 }118118119119- query := "update registrations set registered = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), read_only = 0"119119+ query := "update registrations set registered = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), needs_upgrade = 0"120120 if len(conditions) > 0 {121121 query += " where " + strings.Join(conditions, " and ")122122 }
+14-7
appview/db/spindle.go
···1010)11111212type Spindle struct {1313- Id int1414- Owner syntax.DID1515- Instance string1616- Verified *time.Time1717- Created time.Time1313+ Id int1414+ Owner syntax.DID1515+ Instance string1616+ Verified *time.Time1717+ Created time.Time1818+ NeedsUpgrade bool1819}19202021type SpindleMember struct {···4342 }44434544 query := fmt.Sprintf(4646- `select id, owner, instance, verified, created4545+ `select id, owner, instance, verified, created, needs_upgrade4746 from spindles4847 %s4948 order by created···6261 var spindle Spindle6362 var createdAt string6463 var verified sql.NullString6464+ var needsUpgrade int65656666 if err := rows.Scan(6767 &spindle.Id,···7068 &spindle.Instance,7169 &verified,7270 &createdAt,7171+ &needsUpgrade,7372 ); err != nil {7473 return nil, err7574 }···8784 spindle.Verified = &now8885 }8986 spindle.Verified = &t8787+ }8888+8989+ if needsUpgrade != 0 {9090+ spindle.NeedsUpgrade = true9091 }91929293 spindles = append(spindles, spindle)···122115 whereClause = " where " + strings.Join(conditions, " and ")123116 }124117125125- query := fmt.Sprintf(`update spindles set verified = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)118118+ query := fmt.Sprintf(`update spindles set verified = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now'), needs_upgrade = 0 %s`, whereClause)126119127120 res, err := e.Exec(query, args...)128121 if err != nil {
+5-57
appview/knots/knots.go
···33import (44 "errors"55 "fmt"66- "log"76 "log/slog"87 "net/http"98 "slices"···1617 "tangled.sh/tangled.sh/core/appview/oauth"1718 "tangled.sh/tangled.sh/core/appview/pages"1819 "tangled.sh/tangled.sh/core/appview/serververify"2020+ "tangled.sh/tangled.sh/core/appview/xrpcclient"1921 "tangled.sh/tangled.sh/core/eventconsumer"2022 "tangled.sh/tangled.sh/core/idresolver"2123 "tangled.sh/tangled.sh/core/rbac"···4949 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/retry", k.retry)5050 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/add", k.addMember)5151 r.With(middleware.AuthMiddleware(k.OAuth)).Post("/{domain}/remove", k.removeMember)5252-5353- r.With(middleware.AuthMiddleware(k.OAuth)).Get("/upgradeBanner", k.banner)54525553 return r5654}···397399 if err != nil {398400 l.Error("verification failed", "err", err)399401400400- if errors.Is(err, serververify.FetchError) {401401- k.Pages.Notice(w, noticeId, "Failed to verify knot, unable to fetch owner.")402402+ if errors.Is(err, xrpcclient.ErrXrpcUnsupported) {403403+ k.Pages.Notice(w, noticeId, "Failed to verify knot, XRPC queries are unsupported on this knot, consider upgrading!")402404 return403405 }404406···418420 return419421 }420422421421- // if this knot was previously read-only, then emit a record too423423+ // if this knot requires upgrade, then emit a record too422424 //423425 // this is part of migrating from the old knot system to the new one424424- if registration.ReadOnly {426426+ if registration.NeedsUpgrade {425427 // re-announce by registering under same rkey426428 client, err := k.OAuth.AuthorizedClient(r)427429 if err != nil {···482484 return483485 }484486 updatedRegistration := registrations[0]485485-486486- log.Println(updatedRegistration)487487488488 w.Header().Set("HX-Reswap", "outerHTML")489489 k.Pages.KnotListing(w, pages.KnotListingParams{···673677674678 // ok675679 k.Pages.HxRefresh(w)676676-}677677-678678-func (k *Knots) banner(w http.ResponseWriter, r *http.Request) {679679- user := k.OAuth.GetUser(r)680680- l := k.Logger.With("handler", "banner")681681- l = l.With("did", user.Did)682682- l = l.With("handle", user.Handle)683683-684684- allRegistrations, err := db.GetRegistrations(685685- k.Db,686686- db.FilterEq("did", user.Did),687687- )688688- if err != nil {689689- l.Error("non-fatal: failed to get registrations")690690- return691691- }692692-693693- httpClient := &http.Client{Timeout: 5 * time.Second}694694- regs404 := []db.Registration{}695695- for _, reg := range allRegistrations {696696- healthURL := fmt.Sprintf("http://%s/xrpc/_health", reg.Domain)697697-698698- fmt.Println(healthURL)699699-700700- req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, healthURL, nil)701701- if err != nil {702702- l.Error("failed to create health check request", "domain", reg.Domain, "err", err)703703- continue704704- }705705-706706- resp, err := httpClient.Do(req)707707- if err != nil {708708- l.Error("failed to make health check request", "domain", reg.Domain, "err", err)709709- continue710710- }711711- defer resp.Body.Close()712712-713713- if resp.StatusCode == http.StatusNotFound {714714- regs404 = append(regs404, reg)715715- }716716- }717717- if len(regs404) == 0 {718718- return719719- }720720-721721- k.Pages.KnotBanner(w, pages.KnotBannerParams{722722- Registrations: regs404,723723- })724680}
···11+{{ define "banner" }}22+<div class="px-6 py-2">33+ The following services that you administer will have to be upgraded to be compatible with the latest version of Tangled:44+ {{ if .Registrations }}55+ <ul class="list-disc mx-12 my-2">66+ {{range .Registrations}}77+ <li>{{ .Domain }}</li>88+ {{ end }}99+ </ul>1010+ Repositories hosted on these knots may not be accessible until upgraded.1111+ {{ end }}1212+1313+ {{ if .Spindles }}1414+ <ul class="list-disc mx-12 my-2">1515+ {{range .Spindles}}1616+ <li>{{ .Instance }}</li>1717+ {{ end }}1818+ </ul>1919+ Pipelines may not be executed on these spindles until upgraded.2020+ {{ end }}2121+ <a href="https://tangled.sh/@tangled.sh/core/tree/master/docs/migrations/">Click to read the upgrade guide</a>.2222+</div>2323+{{ end }}
···11-{{ define "knots/fragments/bannerReadOnly" }}22-<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">33- A knot ({{range $i, $r := .Registrations}}{{if ne $i 0}}, {{end}}{{ $r.Domain }}{{ end }})44- that you administer is presently read-only. Consider upgrading this knot to55- continue creating repositories on it.66- <a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/migrations/knot-1.7.0.md">Click to read the upgrade guide</a>.77-</div>88-{{ end }}
···11-{{ define "knots/fragments/bannerRequiresUpgrade" }}22-<div class="px-6 py-2">33- The following knots that you administer will have to be upgraded to be compatible with the latest version of Tangled:44- <ul class="list-disc mx-12 my-2">55- {{range $i, $r := .Registrations}}66- <li>{{ $r.Domain }}</li>77- {{ end }}88- </ul>99- Repositories hosted on these knots will not be accessible until upgraded.1010- <a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/migrations/knot-1.8.0.md">Click to read the upgrade guide</a>.1111-</div>1212-{{ end }}