diff --git a/pkg/keyring/keyring.go b/pkg/keyring/keyring.go index b640eeb7..1a116041 100644 --- a/pkg/keyring/keyring.go +++ b/pkg/keyring/keyring.go @@ -42,6 +42,11 @@ func InitKeyring(cfg config.KeyringConfig) (sdkkeyring.Keyring, error) { if err != nil { return nil, err } + // Validate non-interactive passphrase sources early so we can emit + // clear errors for misconfigured environment variables or files. + if err := validatePassphraseConfig(cfg, backend); err != nil { + return nil, err + } // Use the directory as-is, it should already be resolved by the config dir := cfg.Dir @@ -112,6 +117,39 @@ func selectPassphrase(cfg config.KeyringConfig) string { return "" } +// validatePassphraseConfig ensures that configured non-interactive passphrase +// sources are usable. It does not enforce that a passphrase is provided at all +// (interactive mode is still allowed); it only catches obvious misconfigurations +// such as an empty environment variable or unreadable/empty file. +func validatePassphraseConfig(cfg config.KeyringConfig, backend string) error { + backend = strings.ToLower(backend) + // The "test" backend never uses a passphrase. + if backend == "test" { + return nil + } + + // If an environment variable is configured, it must be set and non-empty. + if cfg.PassEnv != "" { + val, ok := os.LookupEnv(cfg.PassEnv) + if !ok || strings.TrimSpace(val) == "" { + return fmt.Errorf("keyring passphrase environment variable %q is not set or is empty", cfg.PassEnv) + } + } + + // If a passphrase file is configured, it must be readable and non-empty. + if cfg.PassFile != "" { + b, err := os.ReadFile(cfg.PassFile) + if err != nil { + return fmt.Errorf("failed to read keyring passphrase file %q: %w", cfg.PassFile, err) + } + if strings.TrimSpace(string(b)) == "" { + return fmt.Errorf("keyring passphrase file %q is empty", cfg.PassFile) + } + } + + return nil +} + func normaliseBackend(b string) (string, error) { switch strings.ToLower(b) { case "file": diff --git a/sn-manager/README.md b/sn-manager/README.md index 5d24124c..ccb8a871 100644 --- a/sn-manager/README.md +++ b/sn-manager/README.md @@ -294,6 +294,22 @@ Auto-update checks run every 10 minutes when enabled. - `--lumera-grpc` - gRPC endpoint - `--chain-id` - Chain identifier +### Keyring configuration details + +All keyring-related flags are forwarded directly to `supernode init` and configure the underlying Cosmos SDK keyring used by SuperNode: + +- `--keyring-backend` controls where and how keys are stored: + - `file` – encrypted keyring files under `/.supernode/keys/keyring-file` (recommended for servers). + - `os` – use the operating system’s keyring (where supported). + - `test` – in-memory, unencrypted test keys (development only). +- Passphrase options configure how the keyring unlocks keys in non-interactive mode: + - `--keyring-passphrase` – passphrase provided directly on the command line (only for testing; avoid in production). + - `--keyring-passphrase-env` – name of an environment variable containing the passphrase. The variable must be set and non-empty; otherwise `supernode start` will fail with a clear error. + - `--keyring-passphrase-file` – path to a file containing the passphrase. The file must be readable and non-empty. +- If none of the passphrase flags are provided, the keyring will prompt interactively when needed. + +For more background and examples, see the `supernode init` section in the top-level `README.md`, which documents these flags in the context of SuperNode itself. + ## Commands - `init` - Initialize sn-manager and SuperNode @@ -327,7 +343,8 @@ The auto-updater follows stable-only, same-major update rules and defers updates Mechanics and notes: - Stable-only: auto-updater targets latest stable GitHub release (non-draft, non-prerelease). -- Same-major only: SuperNode and sn-manager auto-update only when the latest is the same major version (the number before the first dot). Example: 1.7 → 1.8 = allowed; 1.x → 2.0 = manual. +- Same-major only (periodic checks): during regular background checks, SuperNode and sn-manager auto-update only when the latest is the same major version (the number before the first dot). Example: 1.7 → 1.8 = allowed; 1.x → 2.0 = manual. +- Startup sync: when `sn-manager start` runs, it performs a one-time forced sync to the latest stable release for both sn-manager and SuperNode. This startup sync may update across major versions and does not wait for the gateway to be idle; failures are logged but do not block startup. - Gateway-aware: updates are applied only when the gateway reports no running tasks; otherwise they are deferred. - Gateway errors: repeated check failures over a 5-minute window request a clean SuperNode restart (no version change) to recover. - Combined tarball: when updating, sn-manager downloads a single tarball once, then updates itself first (if eligible), then installs/activates the new SuperNode version. diff --git a/sn-manager/cmd/helpers.go b/sn-manager/cmd/helpers.go index 860d16ba..3187e069 100644 --- a/sn-manager/cmd/helpers.go +++ b/sn-manager/cmd/helpers.go @@ -22,8 +22,11 @@ func checkInitialized() error { homeDir := config.GetManagerHome() configPath := filepath.Join(homeDir, "config.yml") - if _, err := os.Stat(configPath); os.IsNotExist(err) { - return fmt.Errorf("not initialized. Run: sn-manager init") + if _, err := os.Stat(configPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("not initialized. Run: sn-manager init") + } + return fmt.Errorf("failed to check manager config: %w", err) } return nil diff --git a/sn-manager/cmd/init.go b/sn-manager/cmd/init.go index 2eb2639c..cebbeb83 100644 --- a/sn-manager/cmd/init.go +++ b/sn-manager/cmd/init.go @@ -96,6 +96,14 @@ func promptForManagerConfig(flags *initFlags) error { } func runInit(cmd *cobra.Command, args []string) error { + // If the user explicitly asks for help on this subcommand, show usage + // without performing any initialization or forwarding flags to supernode. + for _, a := range args { + if a == "--help" || a == "-h" { + return cmd.Help() + } + } + // Parse flags flags := parseInitFlags(args) @@ -224,7 +232,11 @@ func runInit(cmd *cobra.Command, args []string) error { fmt.Println("\nStep 3: Initializing SuperNode...") // Check if SuperNode is already initialized - supernodeConfigPath := filepath.Join(os.Getenv("HOME"), ".supernode", "config.yml") + userHome, _ := os.UserHomeDir() + if userHome == "" { + userHome = os.Getenv("HOME") + } + supernodeConfigPath := filepath.Join(userHome, ".supernode", "config.yml") if _, err := os.Stat(supernodeConfigPath); err == nil { fmt.Println("✓ SuperNode already initialized, skipping initialization") } else { diff --git a/sn-manager/cmd/supernode_start.go b/sn-manager/cmd/supernode_start.go index f3727140..a8357af3 100644 --- a/sn-manager/cmd/supernode_start.go +++ b/sn-manager/cmd/supernode_start.go @@ -1,71 +1,76 @@ package cmd import ( - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "syscall" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" - "github.com/spf13/cobra" + "github.com/spf13/cobra" ) var supernodeStartCmd = &cobra.Command{ - Use: "start", - Short: "Start the managed SuperNode", - Long: `Signal the running sn-manager to start SuperNode. Requires sn-manager service to be running.`, - RunE: runSupernodeStart, + Use: "start", + Short: "Start the managed SuperNode", + Long: `Signal the running sn-manager to start SuperNode. Requires sn-manager service to be running.`, + RunE: runSupernodeStart, } func runSupernodeStart(cmd *cobra.Command, args []string) error { - // Ensure manager is initialized - if err := checkInitialized(); err != nil { - return err - } + // Ensure manager is initialized + if err := checkInitialized(); err != nil { + return err + } - home := getHomeDir() + home := getHomeDir() - // Check if sn-manager (service) is running via manager PID file - managerPidPath := filepath.Join(home, managerPIDFile) - data, err := os.ReadFile(managerPidPath) - if err != nil { - if os.IsNotExist(err) { - fmt.Println("sn-manager is not running. Start it with: sn-manager start") - return nil - } - return fmt.Errorf("failed to read sn-manager PID: %w", err) - } - pidStr := strings.TrimSpace(string(data)) - pid, _ := strconv.Atoi(pidStr) - proc, alive := getProcessIfAlive(pid) - if !alive { - // Stale PID file, clean up and instruct user - _ = os.Remove(managerPidPath) - fmt.Println("sn-manager is not running. Start it with: sn-manager start") - return nil - } - // Best-effort ping - _ = proc.Signal(syscall.Signal(0)) + // Check if sn-manager (service) is running via manager PID file + managerPidPath := filepath.Join(home, managerPIDFile) + data, err := os.ReadFile(managerPidPath) + if err != nil { + if os.IsNotExist(err) { + fmt.Println("sn-manager is not running. Start it with: sn-manager start") + return nil + } + return fmt.Errorf("failed to read sn-manager PID: %w", err) + } + pidStr := strings.TrimSpace(string(data)) + pid, err := strconv.Atoi(pidStr) + if err != nil || pid <= 0 { + // Invalid PID contents; treat as stale and ask user to restart manager + _ = os.Remove(managerPidPath) + fmt.Println("sn-manager is not running. Start it with: sn-manager start") + return nil + } + proc, alive := getProcessIfAlive(pid) + if !alive { + // Stale PID file, clean up and instruct user + _ = os.Remove(managerPidPath) + fmt.Println("sn-manager is not running. Start it with: sn-manager start") + return nil + } + // Best-effort ping + _ = proc.Signal(syscall.Signal(0)) - // Remove stop marker to allow the manager to start SuperNode - stopMarker := filepath.Join(home, stopMarkerFile) - if err := os.Remove(stopMarker); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to clear stop marker: %w", err) - } + // Remove stop marker to allow the manager to start SuperNode + stopMarker := filepath.Join(home, stopMarkerFile) + if err := os.Remove(stopMarker); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to clear stop marker: %w", err) + } - // If SuperNode already running, just inform - pidPath := filepath.Join(home, supernodePIDFile) - if p, err := readPIDFromFile(pidPath); err == nil { - if _, ok := getProcessIfAlive(p); ok { - fmt.Println("SuperNode is already running") - return nil - } - // stale pid file - _ = os.Remove(pidPath) - } + // If SuperNode already running, just inform + pidPath := filepath.Join(home, supernodePIDFile) + if p, err := readPIDFromFile(pidPath); err == nil { + if _, ok := getProcessIfAlive(p); ok { + fmt.Println("SuperNode is already running") + return nil + } + // stale pid file + _ = os.Remove(pidPath) + } - fmt.Println("Request sent. The running sn-manager will start SuperNode shortly.") - return nil + fmt.Println("Request sent. The running sn-manager will start SuperNode shortly.") + return nil } - diff --git a/sn-manager/go.mod b/sn-manager/go.mod index 8d29e8e6..f93f732e 100644 --- a/sn-manager/go.mod +++ b/sn-manager/go.mod @@ -1,32 +1,32 @@ module github.com/LumeraProtocol/supernode/v2/sn-manager -go 1.24.1 +go 1.25.1 require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/LumeraProtocol/supernode/v2 v2.0.0-00010101000000-000000000000 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.10.1 + google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.10 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/grpc v1.71.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect + google.golang.org/grpc v1.76.0 // indirect ) replace github.com/LumeraProtocol/supernode/v2 => ../ diff --git a/sn-manager/go.sum b/sn-manager/go.sum index 6413ef48..b1fdbcdf 100644 --- a/sn-manager/go.sum +++ b/sn-manager/go.sum @@ -2,15 +2,15 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -19,8 +19,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -32,10 +32,9 @@ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3x github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= @@ -46,27 +45,28 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -79,8 +79,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -89,32 +89,33 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= +google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/sn-manager/internal/updater/updater.go b/sn-manager/internal/updater/updater.go index 2e6f9d56..b15cad05 100644 --- a/sn-manager/internal/updater/updater.go +++ b/sn-manager/internal/updater/updater.go @@ -204,7 +204,7 @@ func (u *AutoUpdater) checkAndUpdateCombined(force bool) { return } - // If the latest release has been out for > 4 hours, elevate to force mode + // If the latest release has been out longer than forceUpdateAfter, elevate to force mode if !force { if !release.PublishedAt.IsZero() && time.Since(release.PublishedAt) > forceUpdateAfter { force = true diff --git a/supernode/verifier/verifier.go b/supernode/verifier/verifier.go index 3e77172e..caf53305 100644 --- a/supernode/verifier/verifier.go +++ b/supernode/verifier/verifier.go @@ -50,10 +50,28 @@ func (cv *ConfigVerifier) VerifyConfig(ctx context.Context) (*VerificationResult func (cv *ConfigVerifier) checkKeyExists(result *VerificationResult) error { _, err := cv.keyring.Key(cv.config.SupernodeConfig.KeyName) - if err != nil { - result.Valid = false - result.Errors = append(result.Errors, ConfigError{Field: "key_name", Actual: cv.config.SupernodeConfig.KeyName, Message: fmt.Sprintf("Key '%s' not found in keyring", cv.config.SupernodeConfig.KeyName)}) + if err == nil { + return nil } + + result.Valid = false + + // Provide a more actionable error message that includes keyring backend and + // directory, and preserve the original error for debugging. + msg := fmt.Sprintf( + "failed to load key %q from keyring (backend=%s, dir=%s): %v. "+ + "Ensure the key exists and that your keyring passphrase configuration is correct.", + cv.config.SupernodeConfig.KeyName, + cv.config.KeyringConfig.Backend, + cv.config.GetKeyringDir(), + err, + ) + + result.Errors = append(result.Errors, ConfigError{ + Field: "key_name", + Actual: cv.config.SupernodeConfig.KeyName, + Message: msg, + }) return nil } @@ -78,9 +96,35 @@ func (cv *ConfigVerifier) checkSupernodeExists(ctx context.Context, result *Veri sn, err := cv.lumeraClient.SuperNode().GetSupernodeWithLatestAddress(ctx, cv.config.SupernodeConfig.Identity) if err != nil { result.Valid = false - result.Errors = append(result.Errors, ConfigError{Field: "registration", Actual: "error", Message: err.Error()}) + + msg := fmt.Sprintf( + "failed to fetch supernode registration from chain (identity=%s, grpc=%s): %v", + cv.config.SupernodeConfig.Identity, + cv.config.LumeraClientConfig.GRPCAddr, + err, + ) + + result.Errors = append(result.Errors, ConfigError{ + Field: "registration", + Actual: "error", + Message: msg, + }) return nil, err } + if sn == nil { + result.Valid = false + msg := fmt.Sprintf( + "supernode identity %s is not registered on chain (grpc=%s)", + cv.config.SupernodeConfig.Identity, + cv.config.LumeraClientConfig.GRPCAddr, + ) + result.Errors = append(result.Errors, ConfigError{ + Field: "registration", + Actual: "not_found", + Message: msg, + }) + return nil, nil + } return sn, nil }