···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
+40
appview/middleware/middleware.go
···275 }
276}
2770000000000000000000000000000000000000000278// this should serve the go-import meta tag even if the path is technically
279// a 404 like tangled.sh/oppi.li/go-git/v5
280func (mw Middleware) GoImport() middlewareFunc {
···275 }
276}
277278+// middleware that is tacked on top of /{user}/{repo}/issues/{issue}
279+func (mw Middleware) ResolveIssue() middlewareFunc {
280+ return func(next http.Handler) http.Handler {
281+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
282+ f, err := mw.repoResolver.Resolve(r)
283+ if err != nil {
284+ log.Println("failed to fully resolve repo", err)
285+ mw.pages.ErrorKnot404(w)
286+ return
287+ }
288+289+ issueIdStr := chi.URLParam(r, "issue")
290+ issueId, err := strconv.Atoi(issueIdStr)
291+ if err != nil {
292+ log.Println("failed to fully resolve issue ID", err)
293+ mw.pages.ErrorKnot404(w)
294+ return
295+ }
296+297+ issues, err := db.GetIssues(
298+ mw.db,
299+ db.FilterEq("repo_at", f.RepoAt()),
300+ db.FilterEq("issue_id", issueId),
301+ )
302+ if err != nil {
303+ log.Println("failed to get issues", "err", err)
304+ return
305+ }
306+ if len(issues) != 1 {
307+ log.Println("got incorrect number of issues", "len(issuse)", len(issues))
308+ return
309+ }
310+ issue := issues[0]
311+312+ ctx := context.WithValue(r.Context(), "issue", &issue)
313+ next.ServeHTTP(w, r.WithContext(ctx))
314+ })
315+ }
316+}
317+318// this should serve the go-import meta tag even if the path is technically
319// a 404 like tangled.sh/oppi.li/go-git/v5
320func (mw Middleware) GoImport() middlewareFunc {
···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="prose mx-auto w-96 px-6 py-2 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- The following knots that you administer require upgrade to be compatible with the latest version of Tangled:
4- <ul>
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 }}
···1-# Upgrading from v1.7.0
2-3-After v1.7.0, knot secrets have been deprecated. You no
4-longer need a secret from the appview to run a knot. All
5-authorized commands to knots are managed via [Inter-Service
6-Authentication](https://atproto.com/specs/xrpc#inter-service-authentication-jwt).
7-Knots will be read-only until upgraded.
8-9-Upgrading is quite easy, in essence:
10-11-- `KNOT_SERVER_SECRET` is no more, you can remove this
12- environment variable entirely
13-- `KNOT_SERVER_OWNER` is now required on boot, set this to
14- your DID. You can find your DID in the
15- [settings](https://tangled.sh/settings) page.
16-- Restart your knot once you have replaced the environment
17- variable
18-- Head to the [knot dashboard](https://tangled.sh/knots) and
19- hit the "retry" button to verify your knot. This simply
20- writes a `sh.tangled.knot` record to your PDS.
21-22-## Nix
23-24-If you use the nix module, simply bump the flake to the
25-latest revision, and change your config block like so:
26-27-```diff
28- services.tangled-knot = {
29- enable = true;
30- server = {
31-- secretFile = /path/to/secret;
32-+ owner = "did:plc:foo";
33- };
34- };
35-```
···1+# Migrations
2+3+This document is laid out in reverse-chronological order.
4+Newer migration guides are listed first, and older guides
5+are further down the page.
6+7+## Upgrading from v1.8.x
8+9+After v1.8.2, the HTTP API for knot and spindles have been
10+deprecated and replaced with XRPC. Repositories on outdated
11+knots will not be viewable from the appview. Upgrading is
12+straightforward however.
13+14+For knots:
15+16+- Upgrade to latest tag (v1.9.0 or above)
17+- Head to the [knot dashboard](https://tangled.sh/knots) and
18+ hit the "retry" button to verify your knot
19+20+For spindles:
21+22+- Upgrade to latest tag (v1.9.0 or above)
23+- Head to the [spindle
24+ dashboard](https://tangled.sh/spindles) and hit the
25+ "retry" button to verify your spindle
26+27+## Upgrading from v1.7.x
28+29+After v1.7.0, knot secrets have been deprecated. You no
30+longer need a secret from the appview to run a knot. All
31+authorized commands to knots are managed via [Inter-Service
32+Authentication](https://atproto.com/specs/xrpc#inter-service-authentication-jwt).
33+Knots will be read-only until upgraded.
34+35+Upgrading is quite easy, in essence:
36+37+- `KNOT_SERVER_SECRET` is no more, you can remove this
38+ environment variable entirely
39+- `KNOT_SERVER_OWNER` is now required on boot, set this to
40+ your DID. You can find your DID in the
41+ [settings](https://tangled.sh/settings) page.
42+- Restart your knot once you have replaced the environment
43+ variable
44+- Head to the [knot dashboard](https://tangled.sh/knots) and
45+ hit the "retry" button to verify your knot. This simply
46+ writes a `sh.tangled.knot` record to your PDS.
47+48+If you use the nix module, simply bump the flake to the
49+latest revision, and change your config block like so:
50+51+```diff
52+ services.tangled-knot = {
53+ enable = true;
54+ server = {
55+- secretFile = /path/to/secret;
56++ owner = "did:plc:foo";
57+ };
58+ };
59+```
60+