lotus/cmd/curio/cli.go
Andrew Jackson (Ajax) fd7f1a95e2
feat: curio: web based config edit (#11822)
* cfg edit 1

* jsonschema deps

* feat: lp mig - first few steps

* lp mig: default tasks

* code comments

* docs

* lp-mig-progress

* shared

* comments and todos

* fix: curio: rename lotus-provider to curio (#11645)

* rename provider to curio

* install gotext

* fix lint errors, mod tidy

* fix typo

* fix API_INFO and add gotext to circleCI

* add back gotext

* add gotext after remerge

* lp: channels doc

* finish easy-migration TODOs

* out generate

* merging and more renames

* avoid make-all

* minor doc stuff

* cu: make gen

* make gen fix

* make gen

* tryfix

* go mod tidy

* minor ez migration fixes

* ez setup - ui cleanups

* better error message

* guided setup colors

* better path to saveconfigtolayer

* loadconfigwithupgrades fix

* readMiner oops

* guided - homedir

* err if miner is running

* prompt error should exit

* process already running, miner_id sectors in migration

* dont prompt for language a second time

* check miner stopped

* unlock repo

* render and sql oops

* curio easyMig - some fixes

* easyMigration runs successfully

* lint

* part 2 of last

* message

* merge addtl

* fixing guided setup for myself

* warn-on-no-post

* EditorLoads

* cleanups and styles

* create info

* fix tests

* make gen

* change layout, add help button

* Duration custom json

* mjs naming

---------

Co-authored-by: LexLuthr <88259624+LexLuthr@users.noreply.github.com>
Co-authored-by: LexLuthr <lexluthr@protocol.ai>
Co-authored-by: LexLuthr <lexluthr@curiostorage.org>
2024-04-16 09:30:27 -05:00

250 lines
5.3 KiB
Go

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()
},
}