porting all github actions from bluesky-social/indigo to tangled CI
at main 9.7 kB view raw
1package main 2 3import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log/slog" 10 "net/http" 11 "net/url" 12 13 "github.com/urfave/cli/v2" 14) 15 16var cmdRelayAdmin = &cli.Command{ 17 Name: "admin", 18 Usage: "sub-comands for relay administration", 19 Flags: []cli.Flag{ 20 &cli.StringFlag{ 21 Name: "admin-password", 22 Usage: "relay admin password (for Basic admin auth)", 23 EnvVars: []string{"RELAY_ADMIN_PASSWORD", "ATP_AUTH_ADMIN_PASSWORD"}, 24 }, 25 &cli.StringFlag{ 26 Name: "admin-bearer-token", 27 Usage: "relay admin auth token (for Bearer auth)", 28 EnvVars: []string{"RELAY_ADMIN_BEARER_TOKEN"}, 29 }, 30 }, 31 Subcommands: []*cli.Command{ 32 &cli.Command{ 33 Name: "account", 34 Usage: "sub-commands for managing accounts", 35 Subcommands: []*cli.Command{ 36 &cli.Command{ 37 Name: "takedown", 38 Usage: "takedown a single account on relay", 39 Flags: []cli.Flag{ 40 &cli.StringFlag{ 41 Name: "collection", 42 Aliases: []string{"c"}, 43 Usage: "collection (NSID) to match", 44 }, 45 &cli.BoolFlag{ 46 Name: "reverse", 47 Usage: "un-takedown", 48 }, 49 }, 50 Action: runRelayAdminAccountTakedown, 51 }, 52 &cli.Command{ 53 Name: "list", 54 Aliases: []string{"ls"}, 55 Usage: "enumerate accounts (eg, takendown)", 56 Action: runRelayAdminAccountList, 57 }, 58 }, 59 }, 60 &cli.Command{ 61 Name: "host", 62 Usage: "sub-commands for upstream hosts (eg, PDS)", 63 Subcommands: []*cli.Command{ 64 &cli.Command{ 65 Name: "add", 66 Usage: "request crawl of upstream host (eg, PDS)", 67 ArgsUsage: `<hostname>`, 68 Action: runRelayAdminHostAdd, 69 }, 70 &cli.Command{ 71 Name: "block", 72 Usage: "request crawl of upstream host (eg, PDS)", 73 ArgsUsage: `<hostname>`, 74 Flags: []cli.Flag{ 75 &cli.BoolFlag{ 76 Name: "reverse", 77 Usage: "un-takedown", 78 }, 79 }, 80 Action: runRelayAdminHostBlock, 81 }, 82 &cli.Command{ 83 Name: "list", 84 Aliases: []string{"ls"}, 85 Usage: "enumerate hosts crawled by relay", 86 Action: runRelayAdminHostList, 87 }, 88 &cli.Command{ 89 Name: "config", 90 Usage: "update rate-limits per host", 91 ArgsUsage: `<hostname>`, 92 Flags: []cli.Flag{ 93 &cli.IntFlag{ 94 Name: "account-limit", 95 }, 96 }, 97 Action: runRelayAdminHostConfig, 98 }, 99 }, 100 }, 101 &cli.Command{ 102 Name: "domain", 103 Usage: "sub-commands for domain-level config", 104 Subcommands: []*cli.Command{ 105 &cli.Command{ 106 Name: "ban", 107 Usage: "ban an entire domain name from being crawled", 108 ArgsUsage: `<domain>`, 109 Flags: []cli.Flag{ 110 &cli.BoolFlag{ 111 Name: "reverse", 112 Usage: "un-takedown", 113 }, 114 }, 115 Action: runRelayAdminDomainBan, 116 }, 117 &cli.Command{ 118 Name: "list", 119 Aliases: []string{"ls"}, 120 Usage: "enumerate domains with configs (eg, bans)", 121 Action: runRelayAdminDomainList, 122 }, 123 }, 124 }, 125 &cli.Command{ 126 Name: "consumer", 127 Usage: "sub-commands for consumers", 128 Subcommands: []*cli.Command{ 129 &cli.Command{ 130 Name: "list", 131 Aliases: []string{"ls"}, 132 Usage: "enumerate consumers", 133 Action: runRelayAdminConsumerList, 134 }, 135 }, 136 }, 137 }, 138} 139 140type RelayAdminClient struct { 141 Host string 142 Password string 143 BearerToken string 144} 145 146func (c *RelayAdminClient) Do(method, path string, params map[string]string, body map[string]any) ([]byte, error) { 147 u, err := url.Parse(c.Host) 148 if err != nil { 149 return nil, err 150 } 151 u.Path = path 152 q := u.Query() 153 for k, v := range params { 154 q.Add(k, v) 155 } 156 u.RawQuery = q.Encode() 157 158 var buf *bytes.Buffer 159 if body != nil { 160 b, err := json.Marshal(body) 161 if err != nil { 162 return nil, err 163 } 164 buf = bytes.NewBuffer(b) 165 } 166 167 var req *http.Request 168 if buf != nil { 169 req, err = http.NewRequest(method, u.String(), buf) 170 } else { 171 req, err = http.NewRequest(method, u.String(), nil) 172 } 173 if err != nil { 174 return nil, err 175 } 176 if c.Password != "" { 177 req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:"+c.Password))) 178 } else if c.BearerToken != "" { 179 req.Header.Set("Authorization", "Bearer "+c.BearerToken) 180 } 181 req.Header.Set("User-Agent", *userAgent()) 182 if buf != nil { 183 req.Header.Set("Content-Type", "application/json") 184 } 185 186 resp, err := http.DefaultClient.Do(req) 187 if err != nil { 188 return nil, err 189 } 190 191 defer resp.Body.Close() 192 respBytes, err := io.ReadAll(resp.Body) 193 if err != nil { 194 return nil, err 195 } 196 if resp.StatusCode != http.StatusOK { 197 slog.Warn("relay HTTP error", "statusCode", resp.StatusCode, "body", string(respBytes)) 198 return nil, fmt.Errorf("relay HTTP request failed: %d", resp.StatusCode) 199 } 200 return respBytes, nil 201} 202 203func NewRelayAdminClient(cctx *cli.Context) (*RelayAdminClient, error) { 204 client := RelayAdminClient{ 205 Host: cctx.String("relay-host"), 206 Password: cctx.String("admin-password"), 207 BearerToken: cctx.String("admin-bearer-token"), 208 } 209 if client.Password == "" && client.BearerToken == "" { 210 return nil, fmt.Errorf("either admin password or admin bearer token must be provided") 211 } 212 return &client, nil 213} 214 215func runRelayAdminAccountTakedown(cctx *cli.Context) error { 216 ctx := cctx.Context 217 218 username := cctx.Args().First() 219 if username == "" { 220 return fmt.Errorf("need to provide username as an argument") 221 } 222 ident, err := resolveIdent(ctx, username) 223 if err != nil { 224 return err 225 } 226 227 client, err := NewRelayAdminClient(cctx) 228 if err != nil { 229 return err 230 } 231 232 path := "/admin/repo/takeDown" 233 if cctx.Bool("reverse") { 234 path = "/admin/repo/reverseTakedown" 235 } 236 237 body := map[string]any{ 238 "did": ident.DID.String(), 239 } 240 _, err = client.Do("POST", path, nil, body) 241 if err != nil { 242 return err 243 } 244 return nil 245} 246 247func runRelayAdminAccountList(cctx *cli.Context) error { 248 client, err := NewRelayAdminClient(cctx) 249 if err != nil { 250 return err 251 } 252 path := "/admin/repo/takedowns" 253 params := map[string]string{ 254 "cursor": "", 255 "size": "500", 256 } 257 for { 258 respBytes, err := client.Do("GET", path, params, nil) 259 if err != nil { 260 return err 261 } 262 var resp map[string]any 263 if err := json.Unmarshal(respBytes, &resp); err != nil { 264 return err 265 } 266 for _, d := range resp["dids"].([]any) { 267 fmt.Println(d) 268 } 269 cursor, ok := resp["cursor"] 270 if !ok || cursor == "" { 271 break 272 } 273 params["cursor"] = cursor.(string) 274 } 275 return nil 276} 277 278func runRelayAdminHostAdd(cctx *cli.Context) error { 279 280 hostname := cctx.Args().First() 281 if hostname == "" { 282 return fmt.Errorf("need to provide hostname as an argument") 283 } 284 285 client, err := NewRelayAdminClient(cctx) 286 if err != nil { 287 return err 288 } 289 path := "/admin/pds/requestCrawl" 290 body := map[string]any{ 291 "hostname": hostname, 292 } 293 _, err = client.Do("POST", path, nil, body) 294 if err != nil { 295 return err 296 } 297 return nil 298} 299 300func runRelayAdminHostBlock(cctx *cli.Context) error { 301 302 hostname := cctx.Args().First() 303 if hostname == "" { 304 return fmt.Errorf("need to provide hostname as an argument") 305 } 306 307 client, err := NewRelayAdminClient(cctx) 308 if err != nil { 309 return err 310 } 311 312 path := "/admin/pds/block" 313 if cctx.Bool("reverse") { 314 path = "/admin/pds/unblock" 315 } 316 317 params := map[string]string{ 318 "host": hostname, 319 } 320 _, err = client.Do("POST", path, params, nil) 321 if err != nil { 322 return err 323 } 324 return nil 325} 326 327func runRelayAdminHostList(cctx *cli.Context) error { 328 client, err := NewRelayAdminClient(cctx) 329 if err != nil { 330 return err 331 } 332 path := "/admin/pds/list" 333 334 respBytes, err := client.Do("GET", path, nil, nil) 335 if err != nil { 336 return err 337 } 338 var rows []map[string]any 339 if err := json.Unmarshal(respBytes, &rows); err != nil { 340 return err 341 } 342 for _, r := range rows { 343 b, err := json.Marshal(r) 344 if err != nil { 345 return nil 346 } 347 fmt.Println(string(b)) 348 } 349 return nil 350} 351 352func runRelayAdminHostConfig(cctx *cli.Context) error { 353 354 hostname := cctx.Args().First() 355 if hostname == "" { 356 return fmt.Errorf("need to provide hostname as an argument") 357 } 358 359 client, err := NewRelayAdminClient(cctx) 360 if err != nil { 361 return err 362 } 363 364 path := "/admin/pds/changeLimits" 365 366 body := map[string]any{ 367 "host": hostname, 368 } 369 if cctx.IsSet("account-limit") { 370 body["repo_limit"] = cctx.Int("account-limit") 371 } 372 373 _, err = client.Do("POST", path, nil, body) 374 if err != nil { 375 return err 376 } 377 return nil 378} 379 380func runRelayAdminDomainBan(cctx *cli.Context) error { 381 382 domain := cctx.Args().First() 383 if domain == "" { 384 return fmt.Errorf("need to provide domain as an argument") 385 } 386 387 client, err := NewRelayAdminClient(cctx) 388 if err != nil { 389 return err 390 } 391 392 path := "/admin/subs/banDomain" 393 if cctx.Bool("reverse") { 394 path = "/admin/subs/unbanDomain" 395 } 396 397 body := map[string]any{ 398 "domain": domain, 399 } 400 _, err = client.Do("POST", path, nil, body) 401 if err != nil { 402 return err 403 } 404 return nil 405} 406 407func runRelayAdminDomainList(cctx *cli.Context) error { 408 client, err := NewRelayAdminClient(cctx) 409 if err != nil { 410 return err 411 } 412 path := "/admin/subs/listDomainBans" 413 414 respBytes, err := client.Do("GET", path, nil, nil) 415 if err != nil { 416 return err 417 } 418 var resp map[string]any 419 if err := json.Unmarshal(respBytes, &resp); err != nil { 420 return err 421 } 422 for _, d := range resp["banned_domains"].([]any) { 423 fmt.Println(d) 424 } 425 return nil 426} 427 428func runRelayAdminConsumerList(cctx *cli.Context) error { 429 client, err := NewRelayAdminClient(cctx) 430 if err != nil { 431 return err 432 } 433 path := "/admin/consumers/list" 434 435 respBytes, err := client.Do("GET", path, nil, nil) 436 if err != nil { 437 return err 438 } 439 var rows []map[string]any 440 if err := json.Unmarshal(respBytes, &rows); err != nil { 441 return err 442 } 443 for _, r := range rows { 444 b, err := json.Marshal(r) 445 if err != nil { 446 return nil 447 } 448 fmt.Println(string(b)) 449 } 450 return nil 451}