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