mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
376 lines
11 KiB
Go
376 lines
11 KiB
Go
// Copyright (C) 2022, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/luxfi/cli/v2/cmd/configcmd"
|
|
|
|
"github.com/luxfi/cli/v2/cmd/backendcmd"
|
|
"github.com/luxfi/cli/v2/cmd/blockchaincmd"
|
|
"github.com/luxfi/cli/v2/cmd/contractcmd"
|
|
"github.com/luxfi/cli/v2/cmd/databasecmd"
|
|
"github.com/luxfi/cli/v2/cmd/genesiscmd"
|
|
"github.com/luxfi/cli/v2/cmd/interchaincmd"
|
|
"github.com/luxfi/cli/v2/cmd/keycmd"
|
|
"github.com/luxfi/cli/v2/cmd/l1cmd"
|
|
"github.com/luxfi/cli/v2/cmd/l3cmd"
|
|
"github.com/luxfi/cli/v2/cmd/localcmd"
|
|
"github.com/luxfi/cli/v2/cmd/migratecmd"
|
|
"github.com/luxfi/cli/v2/cmd/networkcmd"
|
|
"github.com/luxfi/cli/v2/cmd/nodecmd"
|
|
"github.com/luxfi/cli/v2/cmd/primarycmd"
|
|
"github.com/luxfi/cli/v2/cmd/subnetcmd"
|
|
"github.com/luxfi/cli/v2/cmd/transactioncmd"
|
|
"github.com/luxfi/cli/v2/cmd/updatecmd"
|
|
"github.com/luxfi/cli/v2/cmd/validatorcmd"
|
|
"github.com/luxfi/cli/v2/internal/migrations"
|
|
"github.com/luxfi/cli/v2/pkg/lpmintegration"
|
|
"github.com/luxfi/cli/v2/pkg/application"
|
|
"github.com/luxfi/cli/v2/pkg/config"
|
|
"github.com/luxfi/cli/v2/pkg/constants"
|
|
"github.com/luxfi/cli/v2/pkg/prompts"
|
|
"github.com/luxfi/cli/v2/pkg/utils"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
luxlog "github.com/luxfi/log"
|
|
"github.com/luxfi/log/level"
|
|
"github.com/luxfi/node/v2/v2/utils/perms"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var (
|
|
app *application.Lux
|
|
|
|
logLevel string
|
|
Version = "1.9.0"
|
|
cfgFile string
|
|
skipCheck bool
|
|
)
|
|
|
|
func NewRootCmd() *cobra.Command {
|
|
// rootCmd represents the base command when called without any subcommands
|
|
rootCmd := &cobra.Command{
|
|
Use: "lux",
|
|
Long: `Lux CLI v2 - unified toolchain for sovereign L1s, based rollups, and L3s.
|
|
|
|
Architecture:
|
|
- L1: Sovereign chains with independent validation
|
|
- L2: Based rollups or OP Stack compatible (formerly subnets)
|
|
- L3: App-specific chains on L2s
|
|
|
|
Sequencing options:
|
|
- Lux: 100ms blocks, lowest cost (default)
|
|
- Ethereum: 12s blocks, highest security
|
|
- Lux: 2s blocks, fast finality
|
|
- OP: Optimism ecosystem compatible
|
|
|
|
Features:
|
|
- EIP-4844 blob support
|
|
- Pre-confirmations (<100ms ack)
|
|
- IBC/Teleport cross-chain messaging
|
|
- Corona post-quantum signatures
|
|
|
|
Quick start:
|
|
lux l1 create sovereign # Sovereign L1
|
|
lux l2 create rollup # L2 (based rollup)
|
|
lux l3 create app --l2 rollup # L3 (app chain)`,
|
|
PersistentPreRunE: createApp,
|
|
Version: Version,
|
|
PersistentPostRun: handleTracking,
|
|
}
|
|
|
|
// Disable printing the completion command
|
|
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
|
|
|
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.json)")
|
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "ERROR", "log level for the application")
|
|
rootCmd.PersistentFlags().BoolVar(&skipCheck, constants.SkipUpdateFlag, false, "skip check for new versions")
|
|
|
|
// add sub commands
|
|
rootCmd.AddCommand(blockchaincmd.NewCmd(app))
|
|
rootCmd.AddCommand(primarycmd.NewCmd(app))
|
|
rootCmd.AddCommand(l1cmd.NewCmd(app))
|
|
rootCmd.AddCommand(subnetcmd.NewCmd(app)) // l2 with subnet alias
|
|
rootCmd.AddCommand(l3cmd.NewCmd(app))
|
|
rootCmd.AddCommand(networkcmd.NewCmd(app))
|
|
rootCmd.AddCommand(nodecmd.NewCmd(app))
|
|
rootCmd.AddCommand(keycmd.NewCmd()) // Key management (from crypto package)
|
|
rootCmd.AddCommand(genesiscmd.NewCmd()) // Genesis integration
|
|
rootCmd.AddCommand(databasecmd.NewCmd()) // Database operations
|
|
|
|
// add transaction command
|
|
rootCmd.AddCommand(transactioncmd.NewCmd(app))
|
|
|
|
// add config command
|
|
rootCmd.AddCommand(configcmd.NewCmd(app))
|
|
|
|
// add update command
|
|
rootCmd.AddCommand(updatecmd.NewCmd(app, Version))
|
|
|
|
// add interchain command
|
|
rootCmd.AddCommand(interchaincmd.NewCmd(app))
|
|
|
|
// add contract command
|
|
rootCmd.AddCommand(contractcmd.NewCmd(app))
|
|
|
|
// add validator command
|
|
rootCmd.AddCommand(validatorcmd.NewCmd(app))
|
|
|
|
// add migrate command
|
|
rootCmd.AddCommand(migratecmd.NewCmd(app))
|
|
rootCmd.AddCommand(localcmd.NewCmd(app))
|
|
|
|
// add hidden backend command
|
|
rootCmd.AddCommand(backendcmd.NewCmd(app))
|
|
return rootCmd
|
|
}
|
|
|
|
func createApp(cmd *cobra.Command, _ []string) error {
|
|
baseDir, err := setupEnv()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log, err := setupLogging(baseDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cf := config.New()
|
|
app.Setup(baseDir, log, cf, prompts.NewPrompter(), application.NewDownloader())
|
|
|
|
// Setup LPM, skip if running a hidden command
|
|
if !cmd.Hidden {
|
|
usr, err := user.Current()
|
|
if err != nil {
|
|
app.Log.Error("unable to get system user")
|
|
return err
|
|
}
|
|
lpmBaseDir := filepath.Join(usr.HomeDir, constants.LPMDir)
|
|
if err = lpmintegration.SetupLpm(app, lpmBaseDir); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
initConfig()
|
|
|
|
if err := migrations.RunMigrations(app); err != nil {
|
|
return err
|
|
}
|
|
|
|
if os.Getenv("RUN_E2E") == "" && !app.ConfigFileExists() {
|
|
err = utils.HandleUserMetricsPreference(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := checkForUpdates(cmd, app); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkForUpdates evaluates first if the user is maybe wanting to skip the update check
|
|
// if there's no skip, it runs the update check
|
|
func checkForUpdates(cmd *cobra.Command, app *application.Lux) error {
|
|
var (
|
|
lastActs *application.LastActions
|
|
err error
|
|
)
|
|
// we store a timestamp of the last skip check in a file
|
|
lastActs, err = app.ReadLastActionsFile()
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
// if the file does not exist AND the user is requesting to skipCheck,
|
|
// we write the new file
|
|
if skipCheck {
|
|
lastActs := &application.LastActions{
|
|
LastSkipCheck: time.Now(),
|
|
}
|
|
app.WriteLastActionsFile(lastActs)
|
|
return nil
|
|
}
|
|
}
|
|
app.Log.Warn("failed to read last-actions file! This is non-critical but is logged", zap.Error(err))
|
|
lastActs = &application.LastActions{}
|
|
}
|
|
|
|
// if the user had requested to skipCheck less than 24 hrs ago, we skip in any case
|
|
if lastActs.LastSkipCheck != (time.Time{}) &&
|
|
time.Now().Before(lastActs.LastSkipCheck.Add(24*time.Hour)) {
|
|
app.Log.Debug("last checked %s, so less than 24 hrs earlier. Skipping to check for updates.",
|
|
zap.Time("lastSkipCheck", lastActs.LastSkipCheck))
|
|
return nil
|
|
}
|
|
|
|
// more than 24hrs ago or the user never asked to skip before
|
|
// we update the timestamp and write the file again
|
|
if skipCheck {
|
|
if lastActs == nil {
|
|
lastActs = &application.LastActions{}
|
|
}
|
|
lastActs.LastSkipCheck = time.Now()
|
|
app.WriteLastActionsFile(lastActs)
|
|
return nil
|
|
}
|
|
|
|
// at this point we want to run the check
|
|
isUserCalled := false
|
|
commandList := strings.Fields(cmd.CommandPath())
|
|
if !(len(commandList) > 1 && commandList[1] == "update") {
|
|
if err := updatecmd.Update(cmd, isUserCalled, Version); err != nil {
|
|
if errors.Is(err, updatecmd.ErrUserAbortedInstallation) {
|
|
return nil
|
|
}
|
|
if err == updatecmd.ErrNoVersion {
|
|
ux.Logger.PrintToUser(
|
|
"Attempted to check if a new version is available, but couldn't find the currently running version information")
|
|
ux.Logger.PrintToUser(
|
|
"Make sure to follow official instructions, or automatic updates won't be available for you")
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleTracking(cmd *cobra.Command, _ []string) {
|
|
utils.HandleTracking(cmd, app, nil)
|
|
}
|
|
|
|
func setupEnv() (string, error) {
|
|
// Set base dir
|
|
usr, err := user.Current()
|
|
if err != nil {
|
|
// no logger here yet
|
|
fmt.Printf("unable to get system user %s\n", err)
|
|
return "", err
|
|
}
|
|
baseDir := filepath.Join(usr.HomeDir, constants.BaseDirName)
|
|
|
|
// Create base dir if it doesn't exist
|
|
err = os.MkdirAll(baseDir, os.ModePerm)
|
|
if err != nil {
|
|
// no logger here yet
|
|
fmt.Printf("failed creating the basedir %s: %s\n", baseDir, err)
|
|
return "", err
|
|
}
|
|
|
|
// Create snapshots dir if it doesn't exist
|
|
snapshotsDir := filepath.Join(baseDir, constants.SnapshotsDirName)
|
|
if err = os.MkdirAll(snapshotsDir, os.ModePerm); err != nil {
|
|
fmt.Printf("failed creating the snapshots dir %s: %s\n", snapshotsDir, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Create key dir if it doesn't exist
|
|
keyDir := filepath.Join(baseDir, constants.KeyDir)
|
|
if err = os.MkdirAll(keyDir, os.ModePerm); err != nil {
|
|
fmt.Printf("failed creating the key dir %s: %s\n", keyDir, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Create custom vm dir if it doesn't exist
|
|
vmDir := filepath.Join(baseDir, constants.CustomVMDir)
|
|
if err = os.MkdirAll(vmDir, os.ModePerm); err != nil {
|
|
fmt.Printf("failed creating the vm dir %s: %s\n", vmDir, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Create subnet dir if it doesn't exist
|
|
subnetDir := filepath.Join(baseDir, constants.SubnetDir)
|
|
if err = os.MkdirAll(subnetDir, os.ModePerm); err != nil {
|
|
fmt.Printf("failed creating the subnet dir %s: %s\n", subnetDir, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Create repos dir if it doesn't exist
|
|
repoDir := filepath.Join(baseDir, constants.ReposDir)
|
|
if err = os.MkdirAll(repoDir, os.ModePerm); err != nil {
|
|
fmt.Printf("failed creating the repo dir %s: %s\n", repoDir, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
pluginDir := filepath.Join(baseDir, constants.PluginDir)
|
|
if err = os.MkdirAll(pluginDir, os.ModePerm); err != nil {
|
|
fmt.Printf("failed creating the plugin dir %s: %s\n", pluginDir, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
return baseDir, nil
|
|
}
|
|
|
|
func setupLogging(baseDir string) (luxlog.Logger, error) {
|
|
var err error
|
|
|
|
config := luxlog.Config{}
|
|
config.LogLevel = level.Info
|
|
config.DisplayLevel, err = luxlog.ToLevel(logLevel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid log level configured: %s", logLevel)
|
|
}
|
|
config.Directory = filepath.Join(baseDir, constants.LogDir)
|
|
if err := os.MkdirAll(config.Directory, perms.ReadWriteExecute); err != nil {
|
|
return nil, fmt.Errorf("failed creating log directory: %w", err)
|
|
}
|
|
|
|
// some logging config params
|
|
config.LogFormat = luxlog.Colors
|
|
config.MaxSize = constants.MaxLogFileSize
|
|
config.MaxFiles = constants.MaxNumOfLogFiles
|
|
config.MaxAge = constants.RetainOldFiles
|
|
|
|
factory := luxlog.NewFactoryWithConfig(config)
|
|
log, err := factory.Make("lux")
|
|
if err != nil {
|
|
factory.Close()
|
|
return nil, fmt.Errorf("failed setting up logging, exiting: %w", err)
|
|
}
|
|
// create the user facing logger as a global var
|
|
ux.NewUserLog(log, os.Stdout)
|
|
return log, nil
|
|
}
|
|
|
|
// initConfig reads in config file and ENV variables if set.
|
|
func initConfig() {
|
|
if cfgFile != "" {
|
|
// Use config file from the flag.
|
|
viper.SetConfigFile(cfgFile)
|
|
} else {
|
|
// Search for default config.
|
|
home, err := os.UserHomeDir()
|
|
cobra.CheckErr(err)
|
|
viper.AddConfigPath(home)
|
|
viper.SetConfigType(constants.DefaultConfigFileType)
|
|
viper.SetConfigName(constants.DefaultConfigFileName)
|
|
}
|
|
|
|
viper.AutomaticEnv() // read in environment variables that match
|
|
|
|
// If a config file is found, read it in.
|
|
if err := viper.ReadInConfig(); err == nil {
|
|
app.Log.Info("Using config file", zap.String("config-file", viper.ConfigFileUsed()))
|
|
} else {
|
|
app.Log.Info("No log file found")
|
|
}
|
|
}
|
|
|
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
func Execute() {
|
|
app = application.New()
|
|
rootCmd := NewRootCmd()
|
|
err := rootCmd.Execute()
|
|
if err != nil {
|
|
os.Exit(1)
|
|
}
|
|
} |