forked from tangled.org/core
this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+169 -272
appview
docs
knotserver
nix
+8 -161
appview/db/issues.go
···
repoMap[string(repos[i].RepoAt())] = &repos[i]
}
-
for issueAt := range issueMap {
-
i := issueMap[issueAt]
-
r := repoMap[string(i.RepoAt)]
-
i.Repo = r
}
// collect comments
···
return issues, nil
}
-
func GetIssuesWithLimit(e Execer, limit int, filters ...filter) ([]Issue, error) {
-
issues := make([]Issue, 0, limit)
-
-
var conditions []string
-
var args []any
-
for _, filter := range filters {
-
conditions = append(conditions, filter.Condition())
-
args = append(args, filter.Arg()...)
-
}
-
-
whereClause := ""
-
if conditions != nil {
-
whereClause = " where " + strings.Join(conditions, " and ")
-
}
-
limitClause := ""
-
if limit != 0 {
-
limitClause = fmt.Sprintf(" limit %d ", limit)
-
}
-
-
query := fmt.Sprintf(
-
`select
-
i.id,
-
i.owner_did,
-
i.repo_at,
-
i.issue_id,
-
i.created,
-
i.title,
-
i.body,
-
i.open
-
from
-
issues i
-
%s
-
order by
-
i.created desc
-
%s`,
-
whereClause, limitClause)
-
-
rows, err := e.Query(query, args...)
-
if err != nil {
-
return nil, err
-
}
-
defer rows.Close()
-
-
for rows.Next() {
-
var issue Issue
-
var issueCreatedAt string
-
err := rows.Scan(
-
&issue.Id,
-
&issue.Did,
-
&issue.RepoAt,
-
&issue.IssueId,
-
&issueCreatedAt,
-
&issue.Title,
-
&issue.Body,
-
&issue.Open,
-
)
-
if err != nil {
-
return nil, err
-
}
-
-
issueCreatedTime, err := time.Parse(time.RFC3339, issueCreatedAt)
-
if err != nil {
-
return nil, err
-
}
-
issue.Created = issueCreatedTime
-
-
issues = append(issues, issue)
-
}
-
-
if err := rows.Err(); err != nil {
-
return nil, err
-
}
-
-
return issues, nil
-
}
-
func GetIssues(e Execer, filters ...filter) ([]Issue, error) {
return GetIssuesPaginated(e, pagination.FirstPage(), filters...)
-
}
-
-
// timeframe here is directly passed into the sql query filter, and any
-
// timeframe in the past should be negative; e.g.: "-3 months"
-
func GetIssuesByOwnerDid(e Execer, ownerDid string, timeframe string) ([]Issue, error) {
-
var issues []Issue
-
-
rows, err := e.Query(
-
`select
-
i.id,
-
i.owner_did,
-
i.rkey,
-
i.repo_at,
-
i.issue_id,
-
i.created,
-
i.title,
-
i.body,
-
i.open,
-
r.did,
-
r.name,
-
r.knot,
-
r.rkey,
-
r.created
-
from
-
issues i
-
join
-
repos r on i.repo_at = r.at_uri
-
where
-
i.owner_did = ? and i.created >= date ('now', ?)
-
order by
-
i.created desc`,
-
ownerDid, timeframe)
-
if err != nil {
-
return nil, err
-
}
-
defer rows.Close()
-
-
for rows.Next() {
-
var issue Issue
-
var issueCreatedAt, repoCreatedAt string
-
var repo Repo
-
err := rows.Scan(
-
&issue.Id,
-
&issue.Did,
-
&issue.Rkey,
-
&issue.RepoAt,
-
&issue.IssueId,
-
&issueCreatedAt,
-
&issue.Title,
-
&issue.Body,
-
&issue.Open,
-
&repo.Did,
-
&repo.Name,
-
&repo.Knot,
-
&repo.Rkey,
-
&repoCreatedAt,
-
)
-
if err != nil {
-
return nil, err
-
}
-
-
issueCreatedTime, err := time.Parse(time.RFC3339, issueCreatedAt)
-
if err != nil {
-
return nil, err
-
}
-
issue.Created = issueCreatedTime
-
-
repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt)
-
if err != nil {
-
return nil, err
-
}
-
repo.Created = repoCreatedTime
-
-
issues = append(issues, issue)
-
}
-
-
if err := rows.Err(); err != nil {
-
return nil, err
-
}
-
-
return issues, nil
}
func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) {
···
repoMap[string(repos[i].RepoAt())] = &repos[i]
}
+
for issueAt, i := range issueMap {
+
if r, ok := repoMap[string(i.RepoAt)]; ok {
+
i.Repo = r
+
} else {
+
// do not show up the issue if the repo is deleted
+
// TODO: foreign key where?
+
delete(issueMap, issueAt)
+
}
}
// collect comments
···
return issues, nil
}
func GetIssues(e Execer, filters ...filter) ([]Issue, error) {
return GetIssuesPaginated(e, pagination.FirstPage(), filters...)
}
func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) {
+7 -3
appview/db/profile.go
···
*items = append(*items, &pull)
}
-
issues, err := GetIssuesByOwnerDid(e, forDid, timeframe)
if err != nil {
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
}
···
query = `select count(id) from pulls where owner_did = ? and state = ?`
args = append(args, did, PullOpen)
case VanityStatOpenIssueCount:
-
query = `select count(id) from issues where owner_did = ? and open = 1`
args = append(args, did)
case VanityStatClosedIssueCount:
-
query = `select count(id) from issues where owner_did = ? and open = 0`
args = append(args, did)
case VanityStatRepositoryCount:
query = `select count(id) from repos where did = ?`
···
*items = append(*items, &pull)
}
+
issues, err := GetIssues(
+
e,
+
FilterEq("did", forDid),
+
FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)),
+
)
if err != nil {
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
}
···
query = `select count(id) from pulls where owner_did = ? and state = ?`
args = append(args, did, PullOpen)
case VanityStatOpenIssueCount:
+
query = `select count(id) from issues where did = ? and open = 1`
args = append(args, did)
case VanityStatClosedIssueCount:
+
query = `select count(id) from issues where did = ? and open = 0`
args = append(args, did)
case VanityStatRepositoryCount:
query = `select count(id) from repos where did = ?`
+1 -1
appview/pages/markup/markdown.go
···
repoName := fmt.Sprintf("%s/%s", rctx.RepoInfo.OwnerDid, rctx.RepoInfo.Name)
query := fmt.Sprintf("repo=%s&ref=%s&path=%s&raw=true",
-
repoName, url.PathEscape(rctx.RepoInfo.Ref), actualPath)
parsedURL := &url.URL{
Scheme: scheme,
···
repoName := fmt.Sprintf("%s/%s", rctx.RepoInfo.OwnerDid, rctx.RepoInfo.Name)
query := fmt.Sprintf("repo=%s&ref=%s&path=%s&raw=true",
+
url.PathEscape(repoName), url.PathEscape(rctx.RepoInfo.Ref), actualPath)
parsedURL := &url.URL{
Scheme: scheme,
+1 -1
appview/pages/templates/banner.html
···
<div class="mx-6">
These services may not be fully accessible until upgraded.
<a class="underline text-red-800 dark:text-red-200"
-
href="https://tangled.sh/@tangled.sh/core/tree/master/docs/migrations/">
Click to read the upgrade guide</a>.
</div>
</details>
···
<div class="mx-6">
These services may not be fully accessible until upgraded.
<a class="underline text-red-800 dark:text-red-200"
+
href="https://tangled.sh/@tangled.sh/core/tree/master/docs/migrations.md">
Click to read the upgrade guide</a>.
</div>
</details>
+8
appview/pages/templates/fragments/logotype.html
···
···
+
{{ define "fragments/logotype" }}
+
<span class="flex items-center gap-2">
+
<span class="font-bold italic">tangled</span>
+
<span class="font-normal not-italic text-xs rounded bg-gray-100 dark:bg-gray-700 px-1">
+
alpha
+
</span>
+
<span>
+
{{ end }}
+3 -6
appview/pages/templates/knots/index.html
···
{{ define "title" }}knots{{ end }}
{{ define "content" }}
-
<div class="px-6 py-4 flex items-end justify-start gap-4 align-bottom">
<h1 class="text-xl font-bold dark:text-white">Knots</h1>
-
-
<span class="flex items-center gap-1 text-sm">
{{ i "book" "w-3 h-3" }}
-
<a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/knot-hosting.md">
-
docs
-
</a>
</span>
</div>
···
{{ define "title" }}knots{{ end }}
{{ define "content" }}
+
<div class="px-6 py-4 flex items-center justify-between gap-4 align-bottom">
<h1 class="text-xl font-bold dark:text-white">Knots</h1>
+
<span class="flex items-center gap-1">
{{ i "book" "w-3 h-3" }}
+
<a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/knot-hosting.md">docs</a>
</span>
</div>
+4 -4
appview/pages/templates/layouts/base.html
···
<title>{{ block "title" . }}{{ end }} ยท tangled</title>
{{ block "extrameta" . }}{{ end }}
</head>
-
<body class="min-h-screen grid grid-cols-1 grid-rows-[min-content_auto_min-content] md:grid-cols-12 gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">
{{ block "topbarLayout" . }}
-
<header class="px-1 col-span-1 md:col-start-3 md:col-span-8" style="z-index: 20;">
{{ if .LoggedInUser }}
<div id="upgrade-banner"
···
{{ end }}
{{ block "mainLayout" . }}
-
<div class="px-1 col-span-1 md:col-start-3 md:col-span-8 flex flex-col gap-4">
{{ block "contentLayout" . }}
<main class="col-span-1 md:col-span-8">
{{ block "content" . }}{{ end }}
···
{{ end }}
{{ block "footerLayout" . }}
-
<footer class="px-1 col-span-1 md:col-start-3 md:col-span-8 mt-12">
{{ template "layouts/fragments/footer" . }}
</footer>
{{ end }}
···
<title>{{ block "title" . }}{{ end }} ยท tangled</title>
{{ block "extrameta" . }}{{ end }}
</head>
+
<body class="min-h-screen grid grid-cols-1 grid-rows-[min-content_auto_min-content] md:grid-cols-10 lg:grid-cols-12 gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">
{{ block "topbarLayout" . }}
+
<header class="px-1 col-span-1 md:col-start-2 md:col-span-8 lg:col-start-3" style="z-index: 20;">
{{ if .LoggedInUser }}
<div id="upgrade-banner"
···
{{ end }}
{{ block "mainLayout" . }}
+
<div class="px-1 col-span-1 md:col-start-2 md:col-span-8 lg:col-start-3 flex flex-col gap-4">
{{ block "contentLayout" . }}
<main class="col-span-1 md:col-span-8">
{{ block "content" . }}{{ end }}
···
{{ end }}
{{ block "footerLayout" . }}
+
<footer class="px-1 col-span-1 md:col-start-2 md:col-span-8 lg:col-start-3 mt-12">
{{ template "layouts/fragments/footer" . }}
</footer>
{{ end }}
+1 -3
appview/pages/templates/layouts/fragments/topbar.html
···
<nav class="space-x-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">
<div class="flex justify-between p-0 items-center">
<div id="left-items">
-
<a href="/" hx-boost="true" class="flex gap-2 font-bold italic">
-
tangled<sub>alpha</sub>
-
</a>
</div>
<div id="right-items" class="flex items-center gap-2">
···
<nav class="space-x-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">
<div class="flex justify-between p-0 items-center">
<div id="left-items">
+
<a href="/" hx-boost="true" class="text-lg">{{ template "fragments/logotype" }}</a>
</div>
<div id="right-items" class="flex items-center gap-2">
+3 -7
appview/pages/templates/spindles/index.html
···
{{ define "title" }}spindles{{ end }}
{{ define "content" }}
-
<div class="px-6 py-4 flex items-end justify-start gap-4 align-bottom">
<h1 class="text-xl font-bold dark:text-white">Spindles</h1>
-
-
-
<span class="flex items-center gap-1 text-sm">
{{ i "book" "w-3 h-3" }}
-
<a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/spindle/hosting.md">
-
docs
-
</a>
</span>
</div>
···
{{ define "title" }}spindles{{ end }}
{{ define "content" }}
+
<div class="px-6 py-4 flex items-center justify-between gap-4 align-bottom">
<h1 class="text-xl font-bold dark:text-white">Spindles</h1>
+
<span class="flex items-center gap-1">
{{ i "book" "w-3 h-3" }}
+
<a href="https://tangled.sh/@tangled.sh/core/blob/master/docs/spindle/hosting.md">docs</a>
</span>
</div>
+1 -1
appview/pages/templates/timeline/fragments/hero.html
···
<figure class="w-full hidden md:block md:w-auto">
<a href="https://tangled.sh/@tangled.sh/core" class="block">
-
<img src="https://assets.tangled.network/hero-repo.png" alt="Screenshot of the Tangled monorepo." class="max-w-md mx-auto md:max-w-none w-full md:w-[30vw] h-auto shadow-sm rounded hover:shadow-md transition-shadow" />
</a>
<figcaption class="text-sm text-gray-600 dark:text-gray-400 mt-2 text-center">
Monorepo for Tangled, built in the open with the community.
···
<figure class="w-full hidden md:block md:w-auto">
<a href="https://tangled.sh/@tangled.sh/core" class="block">
+
<img src="https://assets.tangled.network/hero-repo.png" alt="Screenshot of the Tangled monorepo." class="max-w-md mx-auto md:max-w-none w-full md:w-[30vw] h-auto shadow-sm rounded" />
</a>
<figcaption class="text-sm text-gray-600 dark:text-gray-400 mt-2 text-center">
Monorepo for Tangled, built in the open with the community.
+3 -3
appview/pages/templates/timeline/home.html
···
{{ define "feature" }}
{{ $info := index . 0 }}
{{ $bullets := index . 1 }}
-
<div class="flex flex-col items-top gap-6 md:flex-row md:gap-12">
<div class="flex-1">
<h2 class="text-2xl font-bold text-black dark:text-white mb-6">{{ $info.title }}</h2>
<ul class="leading-normal">
···
</div>
<div class="flex-shrink-0 w-96 md:w-1/3">
<a href="{{ $info.image }}">
-
<img src="{{ $info.image }}" alt="{{ $info.alt }}" class="w-full h-auto rounded" />
</a>
</div>
</div>
{{ end }}
{{ define "features" }}
-
<div class="prose dark:text-gray-200 space-y-12 px-6 py-4">
{{ template "feature" (list
(dict
"title" "lightweight git repo hosting"
···
{{ define "feature" }}
{{ $info := index . 0 }}
{{ $bullets := index . 1 }}
+
<div class="flex flex-col items-center gap-6 md:flex-row md:items-top">
<div class="flex-1">
<h2 class="text-2xl font-bold text-black dark:text-white mb-6">{{ $info.title }}</h2>
<ul class="leading-normal">
···
</div>
<div class="flex-shrink-0 w-96 md:w-1/3">
<a href="{{ $info.image }}">
+
<img src="{{ $info.image }}" alt="{{ $info.alt }}" class="w-full h-auto rounded shadow-sm" />
</a>
</div>
</div>
{{ end }}
{{ define "features" }}
+
<div class="prose dark:text-gray-200 space-y-12 px-6 py-4 bg-white dark:bg-gray-800 rounded drop-shadow-sm">
{{ template "feature" (list
(dict
"title" "lightweight git repo hosting"
+2 -4
appview/pages/templates/user/completeSignup.html
···
</head>
<body class="flex items-center justify-center min-h-screen">
<main class="max-w-md px-6 -mt-4">
-
<h1
-
class="text-center text-2xl font-semibold italic dark:text-white"
-
>
-
tangled
</h1>
<h2 class="text-center text-xl italic dark:text-white">
tightly-knit social coding.
···
</head>
<body class="flex items-center justify-center min-h-screen">
<main class="max-w-md px-6 -mt-4">
+
<h1 class="flex place-content-center text-2xl font-semibold italic dark:text-white" >
+
{{ template "fragments/logotype" }}
</h1>
<h2 class="text-center text-xl italic dark:text-white">
tightly-knit social coding.
+2 -2
appview/pages/templates/user/login.html
···
</head>
<body class="flex items-center justify-center min-h-screen">
<main class="max-w-md px-6 -mt-4">
-
<h1 class="text-center text-2xl font-semibold italic dark:text-white" >
-
tangled
</h1>
<h2 class="text-center text-xl italic dark:text-white">
tightly-knit social coding.
···
</head>
<body class="flex items-center justify-center min-h-screen">
<main class="max-w-md px-6 -mt-4">
+
<h1 class="flex place-content-center text-2xl font-semibold italic dark:text-white" >
+
{{ template "fragments/logotype" }}
</h1>
<h2 class="text-center text-xl italic dark:text-white">
tightly-knit social coding.
+2 -2
appview/pages/templates/user/overview.html
···
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
{{ range $items }}
-
{{ $repoOwner := resolve .Metadata.Repo.Did }}
-
{{ $repoName := .Metadata.Repo.Name }}
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
···
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
{{ range $items }}
+
{{ $repoOwner := resolve .Repo.Did }}
+
{{ $repoName := .Repo.Name }}
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
+3 -1
appview/pages/templates/user/signup.html
···
</head>
<body class="flex items-center justify-center min-h-screen">
<main class="max-w-md px-6 -mt-4">
-
<h1 class="text-center text-2xl font-semibold italic dark:text-white" >tangled</h1>
<h2 class="text-center text-xl italic dark:text-white">tightly-knit social coding.</h2>
<form
class="mt-4 max-w-sm mx-auto"
···
</head>
<body class="flex items-center justify-center min-h-screen">
<main class="max-w-md px-6 -mt-4">
+
<h1 class="flex place-content-center text-2xl font-semibold italic dark:text-white" >
+
{{ template "fragments/logotype" }}
+
</h1>
<h2 class="text-center text-xl italic dark:text-white">tightly-knit social coding.</h2>
<form
class="mt-4 max-w-sm mx-auto"
+6 -1
appview/repo/feed.go
···
"time"
"tangled.sh/tangled.sh/core/appview/db"
"tangled.sh/tangled.sh/core/appview/reporesolver"
"github.com/bluesky-social/indigo/atproto/syntax"
···
return nil, err
}
-
issues, err := db.GetIssuesWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt()))
if err != nil {
return nil, err
}
···
"time"
"tangled.sh/tangled.sh/core/appview/db"
+
"tangled.sh/tangled.sh/core/appview/pagination"
"tangled.sh/tangled.sh/core/appview/reporesolver"
"github.com/bluesky-social/indigo/atproto/syntax"
···
return nil, err
}
+
issues, err := db.GetIssuesPaginated(
+
rp.db,
+
pagination.Page{Limit: feedLimitPerType},
+
db.FilterEq("repo_at", f.RepoAt()),
+
)
if err != nil {
return nil, err
}
+1 -2
appview/repo/repo.go
···
"log/slog"
"net/http"
"net/url"
-
"path"
"path/filepath"
"slices"
"strconv"
···
}
// fetch the raw binary content using sh.tangled.repo.blob xrpc
-
repoName := path.Join("%s/%s", f.OwnerDid(), f.Name)
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath))
···
"log/slog"
"net/http"
"net/url"
"path/filepath"
"slices"
"strconv"
···
}
// fetch the raw binary content using sh.tangled.repo.blob xrpc
+
repoName := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath))
+10 -9
appview/state/profile.go
···
"github.com/gorilla/feeds"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/appview/db"
-
// "tangled.sh/tangled.sh/core/appview/oauth"
"tangled.sh/tangled.sh/core/appview/pages"
)
···
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
loggedInUser := s.oauth.GetUser(r)
follows, err := fetchFollows(s.db, profile.UserDid)
if err != nil {
l.Error("failed to fetch follows", "err", err)
-
return nil, err
}
if len(follows) == 0 {
-
return nil, nil
}
followDids := make([]string, 0, len(follows))
···
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
if err != nil {
l.Error("failed to get profiles", "followDids", followDids, "err", err)
-
return nil, err
}
followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids)
···
following, err := db.GetFollowing(s.db, loggedInUser.Did)
if err != nil {
l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Did)
-
return nil, err
}
loggedInUserFollowing = make(map[string]struct{}, len(following))
for _, follow := range following {
···
}
}
-
return &FollowsPageParams{
-
Follows: followCards,
-
Card: profile,
-
}, nil
}
func (s *State) followersPage(w http.ResponseWriter, r *http.Request) {
···
"github.com/gorilla/feeds"
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/appview/db"
"tangled.sh/tangled.sh/core/appview/pages"
)
···
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
loggedInUser := s.oauth.GetUser(r)
+
params := FollowsPageParams{
+
Card: profile,
+
}
follows, err := fetchFollows(s.db, profile.UserDid)
if err != nil {
l.Error("failed to fetch follows", "err", err)
+
return &params, err
}
if len(follows) == 0 {
+
return &params, nil
}
followDids := make([]string, 0, len(follows))
···
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
if err != nil {
l.Error("failed to get profiles", "followDids", followDids, "err", err)
+
return &params, err
}
followStatsMap, err := db.GetFollowerFollowingCounts(s.db, followDids)
···
following, err := db.GetFollowing(s.db, loggedInUser.Did)
if err != nil {
l.Error("failed to get follow list", "err", err, "loggedInUser", loggedInUser.Did)
+
return &params, err
}
loggedInUserFollowing = make(map[string]struct{}, len(following))
for _, follow := range following {
···
}
}
+
params.Follows = followCards
+
+
return &params, nil
}
func (s *State) followersPage(w http.ResponseWriter, r *http.Request) {
-35
docs/migrations/knot-1.7.0.md
···
-
# Upgrading from v1.7.0
-
-
After v1.7.0, knot secrets have been deprecated. You no
-
longer need a secret from the appview to run a knot. All
-
authorized commands to knots are managed via [Inter-Service
-
Authentication](https://atproto.com/specs/xrpc#inter-service-authentication-jwt).
-
Knots will be read-only until upgraded.
-
-
Upgrading is quite easy, in essence:
-
-
- `KNOT_SERVER_SECRET` is no more, you can remove this
-
environment variable entirely
-
- `KNOT_SERVER_OWNER` is now required on boot, set this to
-
your DID. You can find your DID in the
-
[settings](https://tangled.sh/settings) page.
-
- Restart your knot once you have replaced the environment
-
variable
-
- Head to the [knot dashboard](https://tangled.sh/knots) and
-
hit the "retry" button to verify your knot. This simply
-
writes a `sh.tangled.knot` record to your PDS.
-
-
## Nix
-
-
If you use the nix module, simply bump the flake to the
-
latest revision, and change your config block like so:
-
-
```diff
-
services.tangled-knot = {
-
enable = true;
-
server = {
-
- secretFile = /path/to/secret;
-
+ owner = "did:plc:foo";
-
};
-
};
-
```
···
+60
docs/migrations.md
···
···
+
# Migrations
+
+
This document is laid out in reverse-chronological order.
+
Newer migration guides are listed first, and older guides
+
are further down the page.
+
+
## Upgrading from v1.8.x
+
+
After v1.8.2, the HTTP API for knot and spindles have been
+
deprecated and replaced with XRPC. Repositories on outdated
+
knots will not be viewable from the appview. Upgrading is
+
straightforward however.
+
+
For knots:
+
+
- Upgrade to latest tag (v1.9.0 or above)
+
- Head to the [knot dashboard](https://tangled.sh/knots) and
+
hit the "retry" button to verify your knot
+
+
For spindles:
+
+
- Upgrade to latest tag (v1.9.0 or above)
+
- Head to the [spindle
+
dashboard](https://tangled.sh/spindles) and hit the
+
"retry" button to verify your spindle
+
+
## Upgrading from v1.7.x
+
+
After v1.7.0, knot secrets have been deprecated. You no
+
longer need a secret from the appview to run a knot. All
+
authorized commands to knots are managed via [Inter-Service
+
Authentication](https://atproto.com/specs/xrpc#inter-service-authentication-jwt).
+
Knots will be read-only until upgraded.
+
+
Upgrading is quite easy, in essence:
+
+
- `KNOT_SERVER_SECRET` is no more, you can remove this
+
environment variable entirely
+
- `KNOT_SERVER_OWNER` is now required on boot, set this to
+
your DID. You can find your DID in the
+
[settings](https://tangled.sh/settings) page.
+
- Restart your knot once you have replaced the environment
+
variable
+
- Head to the [knot dashboard](https://tangled.sh/knots) and
+
hit the "retry" button to verify your knot. This simply
+
writes a `sh.tangled.knot` record to your PDS.
+
+
If you use the nix module, simply bump the flake to the
+
latest revision, and change your config block like so:
+
+
```diff
+
services.tangled-knot = {
+
enable = true;
+
server = {
+
- secretFile = /path/to/secret;
+
+ owner = "did:plc:foo";
+
};
+
};
+
```
+
+1
knotserver/xrpc/repo_blob.go
···
return
}
w.Header().Set("ETag", eTag)
case strings.HasPrefix(mimeType, "text/"):
w.Header().Set("Cache-Control", "public, no-cache")
···
return
}
w.Header().Set("ETag", eTag)
+
w.Header().Set("Content-Type", mimeType)
case strings.HasPrefix(mimeType, "text/"):
w.Header().Set("Cache-Control", "public, no-cache")
+8 -6
knotserver/xrpc/repo_branches.go
···
cursor := r.URL.Query().Get("cursor")
-
limit := 50 // default
-
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
-
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
-
limit = l
-
}
-
}
gr, err := git.PlainOpen(repoPath)
if err != nil {
···
cursor := r.URL.Query().Get("cursor")
+
// limit := 50 // default
+
// if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
+
// if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
+
// limit = l
+
// }
+
// }
+
+
limit := 500
gr, err := git.PlainOpen(repoPath)
if err != nil {
+11 -1
knotserver/xrpc/repo_log.go
···
return
}
// Create response using existing types.RepoLogResponse
response := types.RepoLogResponse{
Commits: commits,
Ref: ref,
Page: (offset / limit) + 1,
PerPage: limit,
-
Total: len(commits), // This is not accurate for pagination, but matches existing behavior
}
if path != "" {
···
return
}
+
total, err := gr.TotalCommits()
+
if err != nil {
+
x.Logger.Error("fetching total commits", "error", err.Error())
+
writeError(w, xrpcerr.NewXrpcError(
+
xrpcerr.WithTag("InternalServerError"),
+
xrpcerr.WithMessage("failed to fetch total commits"),
+
), http.StatusNotFound)
+
return
+
}
+
// Create response using existing types.RepoLogResponse
response := types.RepoLogResponse{
Commits: commits,
Ref: ref,
Page: (offset / limit) + 1,
PerPage: limit,
+
Total: total,
}
if path != "" {
+8 -2
nix/gomod2nix.toml
···
[mod."github.com/whyrusleeping/cbor-gen"]
version = "v0.3.1"
hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc="
[mod."github.com/yuin/goldmark"]
-
version = "v1.4.15"
-
hash = "sha256-MvSOT6dwf5hVYkIg4MnqMpsy5ZtWZ7amAE7Zo9HkEa0="
[mod."github.com/yuin/goldmark-highlighting/v2"]
version = "v2.0.0-20230729083705-37449abec8cc"
hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg="
···
[mod."github.com/whyrusleeping/cbor-gen"]
version = "v0.3.1"
hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc="
+
[mod."github.com/wyatt915/goldmark-treeblood"]
+
version = "v0.0.0-20250825231212-5dcbdb2f4b57"
+
hash = "sha256-IZEsUXTBTsNgWoD7vqRUc9aFCCHNjzk1IUmI9O+NCnM="
+
[mod."github.com/wyatt915/treeblood"]
+
version = "v0.1.15"
+
hash = "sha256-hb99exdkoY2Qv8WdDxhwgPXGbEYimUr6wFtPXEvcO9g="
[mod."github.com/yuin/goldmark"]
+
version = "v1.7.12"
+
hash = "sha256-thLYBS4woL2X5qRdo7vP+xCvjlGRDU0jXtDCUt6vvWM="
[mod."github.com/yuin/goldmark-highlighting/v2"]
version = "v2.0.0-20230729083705-37449abec8cc"
hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg="
+15 -17
nix/pkgs/knot-unwrapped.nix
···
modules,
sqlite-lib,
src,
-
}:
-
let
-
version = "1.8.1-alpha";
in
-
buildGoApplication {
-
pname = "knot";
-
version = "1.8.1";
-
inherit src modules;
-
doCheck = false;
-
subPackages = ["cmd/knot"];
-
tags = ["libsqlite3"];
-
ldflags = [
-
"-X tangled.sh/tangled.sh/core/knotserver/xrpc.version=${version}"
-
];
-
env.CGO_CFLAGS = "-I ${sqlite-lib}/include ";
-
env.CGO_LDFLAGS = "-L ${sqlite-lib}/lib";
-
CGO_ENABLED = 1;
-
}
···
modules,
sqlite-lib,
src,
+
}: let
+
version = "1.9.0-alpha";
in
+
buildGoApplication {
+
pname = "knot";
+
inherit src version modules;
+
doCheck = false;
+
subPackages = ["cmd/knot"];
+
tags = ["libsqlite3"];
+
ldflags = [
+
"-X tangled.sh/tangled.sh/core/knotserver/xrpc.version=${version}"
+
];
+
env.CGO_CFLAGS = "-I ${sqlite-lib}/include ";
+
env.CGO_LDFLAGS = "-L ${sqlite-lib}/lib";
+
CGO_ENABLED = 1;
+
}