265 lines
8.4 KiB
Go
265 lines
8.4 KiB
Go
|
package guidedsetup
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/BurntSushi/toml"
|
||
|
"github.com/ipfs/go-datastore"
|
||
|
"github.com/samber/lo"
|
||
|
"golang.org/x/xerrors"
|
||
|
|
||
|
"github.com/filecoin-project/go-address"
|
||
|
|
||
|
"github.com/filecoin-project/lotus/cmd/curio/deps"
|
||
|
"github.com/filecoin-project/lotus/lib/harmony/harmonydb"
|
||
|
"github.com/filecoin-project/lotus/node/config"
|
||
|
"github.com/filecoin-project/lotus/node/modules"
|
||
|
"github.com/filecoin-project/lotus/node/repo"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
FlagMinerRepo = "miner-repo"
|
||
|
)
|
||
|
|
||
|
const FlagMinerRepoDeprecation = "storagerepo"
|
||
|
|
||
|
func SaveConfigToLayer(minerRepoPath, layerName string, overwrite bool, chainApiInfo string) (minerAddress address.Address, err error) {
|
||
|
_, say := SetupLanguage()
|
||
|
ctx := context.Background()
|
||
|
|
||
|
r, err := repo.NewFS(minerRepoPath)
|
||
|
if err != nil {
|
||
|
return minerAddress, err
|
||
|
}
|
||
|
|
||
|
ok, err := r.Exists()
|
||
|
if err != nil {
|
||
|
return minerAddress, err
|
||
|
}
|
||
|
|
||
|
if !ok {
|
||
|
return minerAddress, fmt.Errorf("repo not initialized at: %s", minerRepoPath)
|
||
|
}
|
||
|
|
||
|
lr, err := r.LockRO(repo.StorageMiner)
|
||
|
if err != nil {
|
||
|
return minerAddress, fmt.Errorf("locking repo: %w", err)
|
||
|
}
|
||
|
defer func() {
|
||
|
err = lr.Close()
|
||
|
if err != nil {
|
||
|
fmt.Println("error closing repo: ", err)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
cfgNode, err := lr.Config()
|
||
|
if err != nil {
|
||
|
return minerAddress, fmt.Errorf("getting node config: %w", err)
|
||
|
}
|
||
|
smCfg := cfgNode.(*config.StorageMiner)
|
||
|
|
||
|
db, err := harmonydb.NewFromConfig(smCfg.HarmonyDB)
|
||
|
if err != nil {
|
||
|
return minerAddress, fmt.Errorf("could not reach the database. Ensure the Miner config toml's HarmonyDB entry"+
|
||
|
" is setup to reach Yugabyte correctly: %w", err)
|
||
|
}
|
||
|
|
||
|
var titles []string
|
||
|
err = db.Select(ctx, &titles, `SELECT title FROM harmony_config WHERE LENGTH(config) > 0`)
|
||
|
if err != nil {
|
||
|
return minerAddress, fmt.Errorf("miner cannot reach the db. Ensure the config toml's HarmonyDB entry"+
|
||
|
" is setup to reach Yugabyte correctly: %s", err.Error())
|
||
|
}
|
||
|
|
||
|
// Copy over identical settings:
|
||
|
|
||
|
buf, err := os.ReadFile(path.Join(lr.Path(), "config.toml"))
|
||
|
if err != nil {
|
||
|
return minerAddress, fmt.Errorf("could not read config.toml: %w", err)
|
||
|
}
|
||
|
curioCfg := config.DefaultCurioConfig()
|
||
|
|
||
|
ensureEmptyArrays(curioCfg)
|
||
|
_, err = deps.LoadConfigWithUpgrades(string(buf), curioCfg)
|
||
|
|
||
|
if err != nil {
|
||
|
return minerAddress, fmt.Errorf("could not decode toml: %w", err)
|
||
|
}
|
||
|
|
||
|
// Populate Miner Address
|
||
|
mmeta, err := lr.Datastore(ctx, "/metadata")
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("opening miner metadata datastore: %w", err)
|
||
|
}
|
||
|
defer func() {
|
||
|
// _ = mmeta.Close()
|
||
|
}()
|
||
|
|
||
|
maddrBytes, err := mmeta.Get(ctx, datastore.NewKey("miner-address"))
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("getting miner address datastore entry: %w", err)
|
||
|
}
|
||
|
|
||
|
addr, err := address.NewFromBytes(maddrBytes)
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("parsing miner actor address: %w", err)
|
||
|
}
|
||
|
|
||
|
minerAddress = addr
|
||
|
|
||
|
curioCfg.Addresses = []config.CurioAddresses{{
|
||
|
MinerAddresses: []string{addr.String()},
|
||
|
PreCommitControl: smCfg.Addresses.PreCommitControl,
|
||
|
CommitControl: smCfg.Addresses.CommitControl,
|
||
|
TerminateControl: smCfg.Addresses.TerminateControl,
|
||
|
DisableOwnerFallback: smCfg.Addresses.DisableOwnerFallback,
|
||
|
DisableWorkerFallback: smCfg.Addresses.DisableWorkerFallback,
|
||
|
}}
|
||
|
|
||
|
ks, err := lr.KeyStore()
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("keystore err: %w", err)
|
||
|
}
|
||
|
js, err := ks.Get(modules.JWTSecretName)
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("error getting JWTSecretName: %w", err)
|
||
|
}
|
||
|
|
||
|
curioCfg.Apis.StorageRPCSecret = base64.StdEncoding.EncodeToString(js.PrivateKey)
|
||
|
|
||
|
curioCfg.Apis.ChainApiInfo = append(curioCfg.Apis.ChainApiInfo, chainApiInfo)
|
||
|
// Express as configTOML
|
||
|
configTOML := &bytes.Buffer{}
|
||
|
if err = toml.NewEncoder(configTOML).Encode(curioCfg); err != nil {
|
||
|
return minerAddress, err
|
||
|
}
|
||
|
|
||
|
if lo.Contains(titles, "base") {
|
||
|
// append addresses
|
||
|
var baseCfg = config.DefaultCurioConfig()
|
||
|
var baseText string
|
||
|
err = db.QueryRow(ctx, "SELECT config FROM harmony_config WHERE title='base'").Scan(&baseText)
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("Cannot load base config: %w", err)
|
||
|
}
|
||
|
ensureEmptyArrays(baseCfg)
|
||
|
_, err := deps.LoadConfigWithUpgrades(baseText, baseCfg)
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("Cannot load base config: %w", err)
|
||
|
}
|
||
|
for _, addr := range baseCfg.Addresses {
|
||
|
if lo.Contains(addr.MinerAddresses, curioCfg.Addresses[0].MinerAddresses[0]) {
|
||
|
goto skipWritingToBase
|
||
|
}
|
||
|
}
|
||
|
// write to base
|
||
|
{
|
||
|
baseCfg.Addresses = append(baseCfg.Addresses, curioCfg.Addresses[0])
|
||
|
baseCfg.Addresses = lo.Filter(baseCfg.Addresses, func(a config.CurioAddresses, _ int) bool {
|
||
|
return len(a.MinerAddresses) > 0
|
||
|
})
|
||
|
|
||
|
cb, err := config.ConfigUpdate(baseCfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv())
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("cannot interpret config: %w", err)
|
||
|
}
|
||
|
_, err = db.Exec(ctx, "UPDATE harmony_config SET config=$1 WHERE title='base'", string(cb))
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("cannot update base config: %w", err)
|
||
|
}
|
||
|
say(plain, "Configuration 'base' was updated to include this miner's address and its wallet setup.")
|
||
|
}
|
||
|
say(plain, "Compare the configurations %s to %s. Changes between the miner IDs other than wallet addreses should be a new, minimal layer for runners that need it.", "base", "mig-"+curioCfg.Addresses[0].MinerAddresses[0])
|
||
|
skipWritingToBase:
|
||
|
} else if layerName == "" {
|
||
|
cfg, err := deps.GetDefaultConfig(true)
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("Cannot get default config: %w", err)
|
||
|
}
|
||
|
_, err = db.Exec(ctx, `INSERT INTO harmony_config (title, config) VALUES ('base', $1)
|
||
|
ON CONFLICT(title) DO UPDATE SET config=EXCLUDED.config`, cfg)
|
||
|
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("Cannot insert base config: %w", err)
|
||
|
}
|
||
|
say(notice, "Configuration 'base' was created to include this miner's address and its wallet setup.")
|
||
|
}
|
||
|
|
||
|
if layerName == "" { // only make mig if base exists and we are different. // compare to base.
|
||
|
layerName = fmt.Sprintf("mig-%s", curioCfg.Addresses[0].MinerAddresses[0])
|
||
|
overwrite = true
|
||
|
} else {
|
||
|
if lo.Contains(titles, layerName) && !overwrite {
|
||
|
return minerAddress, errors.New("the overwrite flag is needed to replace existing layer: " + layerName)
|
||
|
}
|
||
|
}
|
||
|
if overwrite {
|
||
|
_, err := db.Exec(ctx, "DELETE FROM harmony_config WHERE title=$1", layerName)
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("Cannot delete existing layer: %w", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_, err = db.Exec(ctx, "INSERT INTO harmony_config (title, config) VALUES ($1, $2)", layerName, configTOML.String())
|
||
|
if err != nil {
|
||
|
return minerAddress, xerrors.Errorf("Cannot insert layer after layer created message: %w", err)
|
||
|
}
|
||
|
say(plain, "Layer %s created. ", layerName)
|
||
|
|
||
|
dbSettings := getDBSettings(*smCfg)
|
||
|
say(plain, "To work with the config: ")
|
||
|
fmt.Println(code.Render(`curio ` + dbSettings + ` config edit base`))
|
||
|
say(plain, `To run Curio: With machine or cgroup isolation, use the command (with example layer selection):`)
|
||
|
fmt.Println(code.Render(`curio ` + dbSettings + ` run --layer=post`))
|
||
|
return minerAddress, nil
|
||
|
}
|
||
|
|
||
|
func getDBSettings(smCfg config.StorageMiner) string {
|
||
|
dbSettings := ""
|
||
|
def := config.DefaultStorageMiner().HarmonyDB
|
||
|
if def.Hosts[0] != smCfg.HarmonyDB.Hosts[0] {
|
||
|
dbSettings += ` --db-host="` + strings.Join(smCfg.HarmonyDB.Hosts, ",") + `"`
|
||
|
}
|
||
|
if def.Port != smCfg.HarmonyDB.Port {
|
||
|
dbSettings += " --db-port=" + smCfg.HarmonyDB.Port
|
||
|
}
|
||
|
if def.Username != smCfg.HarmonyDB.Username {
|
||
|
dbSettings += ` --db-user="` + smCfg.HarmonyDB.Username + `"`
|
||
|
}
|
||
|
if def.Password != smCfg.HarmonyDB.Password {
|
||
|
dbSettings += ` --db-password="` + smCfg.HarmonyDB.Password + `"`
|
||
|
}
|
||
|
if def.Database != smCfg.HarmonyDB.Database {
|
||
|
dbSettings += ` --db-name="` + smCfg.HarmonyDB.Database + `"`
|
||
|
}
|
||
|
return dbSettings
|
||
|
}
|
||
|
|
||
|
func ensureEmptyArrays(cfg *config.CurioConfig) {
|
||
|
if cfg.Addresses == nil {
|
||
|
cfg.Addresses = []config.CurioAddresses{}
|
||
|
} else {
|
||
|
for i := range cfg.Addresses {
|
||
|
if cfg.Addresses[i].PreCommitControl == nil {
|
||
|
cfg.Addresses[i].PreCommitControl = []string{}
|
||
|
}
|
||
|
if cfg.Addresses[i].CommitControl == nil {
|
||
|
cfg.Addresses[i].CommitControl = []string{}
|
||
|
}
|
||
|
if cfg.Addresses[i].TerminateControl == nil {
|
||
|
cfg.Addresses[i].TerminateControl = []string{}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if cfg.Apis.ChainApiInfo == nil {
|
||
|
cfg.Apis.ChainApiInfo = []string{}
|
||
|
}
|
||
|
}
|