2024-02-08 12:27:46 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2024-04-16 00:29:56 +00:00
|
|
|
"context"
|
2024-02-08 12:27:46 +00:00
|
|
|
"encoding/base64"
|
2024-04-16 00:29:56 +00:00
|
|
|
"errors"
|
2024-02-08 12:27:46 +00:00
|
|
|
"fmt"
|
2024-02-11 13:08:54 +00:00
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2024-02-08 12:27:46 +00:00
|
|
|
"github.com/BurntSushi/toml"
|
2024-02-11 13:08:54 +00:00
|
|
|
"github.com/gbrlsnchs/jwt/v3"
|
|
|
|
manet "github.com/multiformats/go-multiaddr/net"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
2024-02-08 12:27:46 +00:00
|
|
|
"github.com/filecoin-project/go-jsonrpc/auth"
|
2024-02-11 13:08:54 +00:00
|
|
|
|
2024-02-08 12:27:46 +00:00
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
lcli "github.com/filecoin-project/lotus/cli"
|
2024-03-15 21:38:13 +00:00
|
|
|
"github.com/filecoin-project/lotus/cmd/curio/deps"
|
|
|
|
"github.com/filecoin-project/lotus/cmd/curio/rpc"
|
2024-02-08 12:27:46 +00:00
|
|
|
)
|
|
|
|
|
2024-03-18 19:03:26 +00:00
|
|
|
const providerEnvVar = "CURIO_API_INFO"
|
2024-02-08 12:27:46 +00:00
|
|
|
|
|
|
|
var cliCmd = &cli.Command{
|
|
|
|
Name: "cli",
|
|
|
|
Usage: "Execute cli commands",
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "machine",
|
2024-04-04 12:06:21 +00:00
|
|
|
Usage: "machine host:port (curio run --listen address)",
|
2024-02-08 12:27:46 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Before: func(cctx *cli.Context) error {
|
|
|
|
if os.Getenv(providerEnvVar) != "" {
|
|
|
|
// set already
|
|
|
|
return nil
|
|
|
|
}
|
2024-04-16 14:30:27 +00:00
|
|
|
if os.Getenv("LOTUS_DOCS_GENERATION") == "1" {
|
|
|
|
return nil
|
|
|
|
}
|
2024-02-08 12:27:46 +00:00
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
2024-02-08 13:30:40 +00:00
|
|
|
|
|
|
|
machine = machines[selection-1].HostAndPort
|
2024-02-08 12:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-02-08 13:30:40 +00:00
|
|
|
token := fmt.Sprintf("%s:%s", string(apiToken), ma)
|
|
|
|
if err := os.Setenv(providerEnvVar, token); err != nil {
|
|
|
|
return xerrors.Errorf("setting env var: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2024-03-15 21:38:13 +00:00
|
|
|
api, closer, err := rpc.GetCurioAPI(cctx)
|
2024-02-08 13:30:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
v, err := api.Version(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("querying version: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-02-08 15:28:34 +00:00
|
|
|
fmt.Println("remote node version:", v.String())
|
2024-02-08 12:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
2024-02-08 15:28:34 +00:00
|
|
|
Subcommands: []*cli.Command{
|
|
|
|
storageCmd,
|
2024-04-16 00:29:56 +00:00
|
|
|
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()
|
2024-02-08 15:28:34 +00:00
|
|
|
},
|
2024-02-08 12:27:46 +00:00
|
|
|
}
|