package main import ( "bufio" "context" "encoding/base64" "errors" "fmt" "net" "os" "time" "github.com/BurntSushi/toml" "github.com/gbrlsnchs/jwt/v3" manet "github.com/multiformats/go-multiaddr/net" "github.com/urfave/cli/v2" "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc/auth" "github.com/filecoin-project/lotus/api" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/cmd/curio/deps" "github.com/filecoin-project/lotus/cmd/curio/rpc" ) const providerEnvVar = "CURIO_API_INFO" var cliCmd = &cli.Command{ Name: "cli", Usage: "Execute cli commands", Flags: []cli.Flag{ &cli.StringFlag{ Name: "machine", Usage: "machine host:port (curio run --listen address)", }, }, Before: func(cctx *cli.Context) error { if os.Getenv(providerEnvVar) != "" { // set already return nil } if os.Getenv("LOTUS_DOCS_GENERATION") == "1" { return nil } db, err := deps.MakeDB(cctx) if err != nil { return err } ctx := lcli.ReqContext(cctx) machine := cctx.String("machine") if machine == "" { // interactive picker var machines []struct { HostAndPort string `db:"host_and_port"` LastContact time.Time `db:"last_contact"` } err := db.Select(ctx, &machines, "select host_and_port, last_contact from harmony_machines") if err != nil { return xerrors.Errorf("getting machine list: %w", err) } now := time.Now() fmt.Println("Available machines:") for i, m := range machines { // A machine is healthy if contacted not longer than 2 minutes ago healthStatus := "unhealthy" if now.Sub(m.LastContact) <= 2*time.Minute { healthStatus = "healthy" } fmt.Printf("%d. %s %s\n", i+1, m.HostAndPort, healthStatus) } fmt.Print("Select: ") reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n') if err != nil { return xerrors.Errorf("reading selection: %w", err) } var selection int _, err = fmt.Sscanf(input, "%d", &selection) if err != nil { return xerrors.Errorf("parsing selection: %w", err) } if selection < 1 || selection > len(machines) { return xerrors.New("invalid selection") } machine = machines[selection-1].HostAndPort } var apiKeys []string { var dbconfigs []struct { Config string `db:"config"` Title string `db:"title"` } err := db.Select(ctx, &dbconfigs, "select config from harmony_config") if err != nil { return xerrors.Errorf("getting configs: %w", err) } var seen = make(map[string]struct{}) for _, config := range dbconfigs { var layer struct { Apis struct { StorageRPCSecret string } } if _, err := toml.Decode(config.Config, &layer); err != nil { return xerrors.Errorf("decode config layer %s: %w", config.Title, err) } if layer.Apis.StorageRPCSecret != "" { if _, ok := seen[layer.Apis.StorageRPCSecret]; ok { continue } seen[layer.Apis.StorageRPCSecret] = struct{}{} apiKeys = append(apiKeys, layer.Apis.StorageRPCSecret) } } } if len(apiKeys) == 0 { return xerrors.New("no api keys found in the database") } if len(apiKeys) > 1 { return xerrors.Errorf("multiple api keys found in the database, not supported yet") } var apiToken []byte { type jwtPayload struct { Allow []auth.Permission } p := jwtPayload{ Allow: api.AllPermissions, } sk, err := base64.StdEncoding.DecodeString(apiKeys[0]) if err != nil { return xerrors.Errorf("decode secret: %w", err) } apiToken, err = jwt.Sign(&p, jwt.NewHS256(sk)) if err != nil { return xerrors.Errorf("signing token: %w", err) } } { laddr, err := net.ResolveTCPAddr("tcp", machine) if err != nil { return xerrors.Errorf("net resolve: %w", err) } if len(laddr.IP) == 0 { // set localhost laddr.IP = net.IPv4(127, 0, 0, 1) } ma, err := manet.FromNetAddr(laddr) if err != nil { return xerrors.Errorf("net from addr (%v): %w", laddr, err) } token := fmt.Sprintf("%s:%s", string(apiToken), ma) if err := os.Setenv(providerEnvVar, token); err != nil { return xerrors.Errorf("setting env var: %w", err) } } { api, closer, err := rpc.GetCurioAPI(cctx) if err != nil { return err } defer closer() v, err := api.Version(ctx) if err != nil { return xerrors.Errorf("querying version: %w", err) } fmt.Println("remote node version:", v.String()) } return nil }, Subcommands: []*cli.Command{ storageCmd, logCmd, waitApiCmd, }, } var waitApiCmd = &cli.Command{ Name: "wait-api", Usage: "Wait for Curio api to come online", Flags: []cli.Flag{ &cli.DurationFlag{ Name: "timeout", Usage: "duration to wait till fail", Value: time.Second * 30, }, }, Action: func(cctx *cli.Context) error { ctx := lcli.ReqContext(cctx) ctx, cancel := context.WithTimeout(ctx, cctx.Duration("timeout")) defer cancel() for { if ctx.Err() != nil { break } api, closer, err := rpc.GetCurioAPI(cctx) if err != nil { fmt.Printf("Not online yet... (%s)\n", err) time.Sleep(time.Second) continue } defer closer() _, err = api.Version(ctx) if err != nil { return err } return nil } if errors.Is(ctx.Err(), context.DeadlineExceeded) { return fmt.Errorf("timed out waiting for api to come online") } return ctx.Err() }, }