This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

1// similar to zoekt-dynamic-indexserver, but targetting Tangled repos 2package main 3 4import ( 5 "bytes" 6 "context" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "log" 11 "net" 12 "net/http" 13 "os" 14 "os/exec" 15 "os/signal" 16 "strconv" 17 "syscall" 18 "time" 19 20 "github.com/bluesky-social/indigo/atproto/identity" 21 "github.com/bluesky-social/indigo/atproto/syntax" 22 "github.com/carlmjohnson/versioninfo" 23 "github.com/samber/lo" 24 "github.com/sourcegraph/zoekt" 25 "github.com/sourcegraph/zoekt/gitindex" 26 "github.com/sourcegraph/zoekt/index" 27 "github.com/urfave/cli/v3" 28 "tangled.org/core/repoident" 29) 30 31func loggedRun(cmd *exec.Cmd) error { 32 outBuf := &bytes.Buffer{} 33 errBuf := &bytes.Buffer{} 34 cmd.Stdout = outBuf 35 cmd.Stderr = errBuf 36 37 log.Printf("run %v", cmd.Args) 38 if err := cmd.Run(); err != nil { 39 log.Printf("command %s failed: %v\nOUT: %s\nERR: %s", 40 cmd.Args, err, outBuf.String(), errBuf.String()) 41 return fmt.Errorf("command %s failed: %v", cmd.Args, err) 42 } 43 44 return nil 45} 46 47// This function is declared as var so that we can stub it in test 48var executeCmd = func(ctx context.Context, name string, arg ...string) error { 49 cmd := exec.CommandContext(ctx, name, arg...) 50 cmd.Stdin = &bytes.Buffer{} 51 err := loggedRun(cmd) 52 53 return err 54} 55 56func main() { 57 if err := run(os.Args); err != nil { 58 log.Fatal(err) 59 } 60} 61 62func run(args []string) error { 63 ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 64 defer cancel() 65 66 app := cli.Command{ 67 Name: "zoekt-tngl-indexserver", 68 Usage: "tangled zoekt index server", 69 Version: versioninfo.Short(), 70 } 71 app.Flags = []cli.Flag{ 72 &cli.StringFlag{ 73 Name: "index-dir", 74 Usage: "directory holding index shards.", 75 Required: true, 76 Sources: cli.EnvVars("TANGLED_ZOEKT_INDEX_DIR"), 77 }, 78 &cli.StringFlag{ 79 Name: "appview-url", 80 Usage: "appview url. used when debugging", 81 Value: "https://tangled.org", 82 Sources: cli.EnvVars("TANGLED_ZOEKT_APPVIEW_URL"), 83 }, 84 } 85 app.Commands = []*cli.Command{ 86 { 87 Name: "serve", 88 Usage: "run the index server daemon", 89 Action: runIndexServer, 90 Flags: []cli.Flag{ 91 &cli.StringFlag{ 92 Name: "plc-url", 93 Usage: "atproto PLC directory.", 94 Value: "https://plc.directory", 95 Sources: cli.EnvVars("TANGLED_ZOEKT_PLC_URL", "TANGLED_PLC_URL", "ATP_PLC_HOST"), 96 }, 97 &cli.DurationFlag{ 98 Name: "index-timeout", 99 Usage: "kill index job after this much time.", 100 Value: time.Hour, 101 Sources: cli.EnvVars("TANGLED_ZOEKT_INDEX_TIMEOUT"), 102 }, 103 &cli.IntFlag{ 104 Name: "index-concurrency", 105 Usage: "number of repositories to index concurrently.", 106 Value: 4, 107 Sources: cli.EnvVars("TANGLED_ZOEKT_INDEX_CONCURRENCY"), 108 }, 109 &cli.IntFlag{ 110 Name: "index-queue-size", 111 Usage: "max index queue size", 112 Value: 100, 113 Sources: cli.EnvVars("TANGLED_ZOEKT_INDEX_QUEUE_SIZE"), 114 }, 115 &cli.StringFlag{ 116 Name: "listen", 117 Usage: "listen on this address", 118 Value: ":6060", 119 Sources: cli.EnvVars("TANGLED_ZOEKT_SERVER_LISTEN"), 120 }, 121 &cli.BoolFlag{ 122 Name: "allow-http", 123 Usage: "accept repo DIDs whose knot service endpoint is plaintext http.", 124 Sources: cli.EnvVars("TANGLED_ZOEKT_ALLOW_HTTP"), 125 }, 126 }, 127 }, 128 { 129 Name: "index", 130 Usage: "manually index a git repository", 131 Action: runIndex, 132 Arguments: []cli.Argument{ 133 &cli.StringArg{ 134 Name: "git-dir", 135 UsageText: "path to fetched git repository.", 136 }, 137 &cli.StringArg{ 138 Name: "repo", 139 UsageText: "json-encoded repository information", 140 }, 141 }, 142 }, 143 } 144 145 return app.Run(ctx, args) 146} 147 148type Config struct { 149 // IndexDir is the index directory to use. 150 IndexDir string 151 152 IndexTimeout time.Duration 153 154 // IndexConcurrency is the number of repositories we index at once. 155 IndexConcurrency int 156 IndexQueueSize int 157 158 PlcUrl string 159 AppviewUrl string 160 Listen string 161 162 KnotScheme repoident.SchemePolicy 163} 164 165func createMissingDirectories(cfg *Config) { 166 for _, s := range []string{cfg.IndexDir} { 167 if err := os.MkdirAll(s, 0o755); err != nil { 168 log.Fatalf("MkdirAll %s: %v", s, err) 169 } 170 } 171} 172 173type Repo struct { 174 Did repoident.RepoDid 175 Owner repoident.OwnerDid 176 Slug syntax.RecordKey 177 Knot repoident.KnotURL 178 Branches []zoekt.RepositoryBranch 179} 180 181func (r *Repo) CloneURL() string { 182 return r.Knot.JoinPath(r.Did.String()) 183} 184 185func runIndexServer(ctx context.Context, cmd *cli.Command) error { 186 cfg := &Config{ 187 IndexDir: cmd.String("index-dir"), 188 IndexTimeout: cmd.Duration("index-timeout"), 189 IndexConcurrency: cmd.Int("index-concurrency"), 190 IndexQueueSize: cmd.Int("index-queue-size"), 191 PlcUrl: cmd.String("plc-url"), 192 AppviewUrl: cmd.String("appview-url"), 193 Listen: cmd.String("listen"), 194 KnotScheme: repoident.SchemeFor(cmd.Bool("allow-http")), 195 } 196 createMissingDirectories(cfg) 197 198 server := NewIndexServer(cfg) 199 go server.Run(ctx) 200 201 <-ctx.Done() 202 return ctx.Err() 203} 204 205// sub-process to index a fetched repository 206func runIndex(ctx context.Context, cmd *cli.Command) error { 207 var ( 208 indexDir = cmd.String("index-dir") 209 appviewUrl = cmd.String("appview-url") 210 ) 211 212 gitDir := cmd.StringArg("git-dir") 213 if gitDir == "" { 214 return errors.New("git-dir is required.") 215 } 216 217 repoRaw := cmd.StringArg("repo") 218 if repoRaw == "" { 219 return errors.New("repo is required.") 220 } 221 222 var repo Repo 223 if err := json.Unmarshal([]byte(repoRaw), &repo); err != nil { 224 return fmt.Errorf("invalid repo: %w", err) 225 } 226 if repo.Did == "" || repo.Owner == "" || repo.Knot.IsZero() { 227 return fmt.Errorf("repo is missing did, owner, or knot: %q", repoRaw) 228 } 229 230 branches := lo.Map(repo.Branches, func(b zoekt.RepositoryBranch, _ int) string { return b.Name }) 231 232 buildOpts := index.Options{} 233 buildOpts.SetDefaults() 234 235 buildOpts.IndexDir = indexDir 236 237 buildOpts.ShardPrefixOverride = repo.Did.String() 238 239 // Tangled templates 240 webUrl := fmt.Sprintf("%s/%s", appviewUrl, repo.Did) 241 buildOpts.RepositoryDescription.CommitURLTemplate = fmt.Sprintf("%s/commit/{{.Version}}", webUrl) 242 buildOpts.RepositoryDescription.FileURLTemplate = fmt.Sprintf("%s/blob/{{.Version}}/{{.Path}}", webUrl) 243 buildOpts.RepositoryDescription.LineFragmentTemplate = "#L{{.LineNumber}}" 244 245 buildOpts.RepositoryDescription.Name = repo.Slug.String() 246 buildOpts.RepositoryDescription.URL = webUrl 247 buildOpts.RepositoryDescription.Metadata = map[string]string{ 248 "foo": "bar", // for testing 249 "did": repo.Did.String(), 250 "owner": repo.Owner.String(), 251 "knot": repo.Knot.String(), 252 } 253 // buildOpts.RepositoryDescription.Source = gitDir // configured later in IndexGitRepo 254 buildOpts.RepositoryDescription.Branches = nil 255 buildOpts.RepositoryDescription.SubRepoMap = nil 256 buildOpts.RepositoryDescription.RawConfig = map[string]string{ 257 "priority": strconv.FormatFloat(0.0, 'g', -1, 64), 258 "public": marshalBool(true), 259 "fork": marshalBool(false), 260 // Calculate repo rank based on the latest commit date. 261 "latestCommitDate": marshalBool(true), 262 } 263 buildOpts.RepositoryDescription.Rank = 0 264 // buildOpts.RepositoryDescription.IndexOptions = "" // configured later in IndexGitRepo 265 // buildOpts.RepositoryDescription.LatestCommitDate = _ // configured later in IndexGitRepo 266 buildOpts.RepositoryDescription.FileTombstones = nil 267 268 gitOpts := gitindex.Options{ 269 RepoDir: gitDir, 270 Submodules: false, 271 Incremental: true, 272 AllowMissingBranch: false, 273 RepoCacheDir: "", 274 BuildOptions: buildOpts, 275 BranchPrefix: "refs/heads/", 276 Branches: branches, 277 DeltaShardNumberFallbackThreshold: 0, 278 } 279 if _, err := gitindex.IndexGitRepo(gitOpts); err != nil { 280 return err 281 } 282 return nil 283} 284 285func marshalBool(b bool) string { 286 if b { 287 return "1" 288 } 289 return "0" 290} 291 292func baseDir(plc string) identity.Directory { 293 base := identity.BaseDirectory{ 294 PLCURL: plc, 295 HTTPClient: http.Client{ 296 Timeout: time.Second * 10, 297 Transport: &http.Transport{ 298 Proxy: http.ProxyFromEnvironment, 299 // would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad. 300 IdleConnTimeout: time.Millisecond * 1000, 301 MaxIdleConns: 100, 302 }, 303 }, 304 Resolver: net.Resolver{ 305 Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 306 d := net.Dialer{Timeout: time.Second * 3} 307 return d.DialContext(ctx, network, address) 308 }, 309 }, 310 TryAuthoritativeDNS: true, 311 // primary Bluesky PDS instance only supports HTTP resolution method 312 SkipDNSDomainSuffixes: []string{".bsky.social"}, 313 UserAgent: "indigo-identity/" + versioninfo.Short(), 314 } 315 return identity.NewCacheDirectory(&base, 250_000, time.Hour*24, time.Minute*2, time.Minute*5) 316}