From 7aaa8fb61e8acfd82ad8ac3ef7f96f7e6caa124b Mon Sep 17 00:00:00 2001 From: Matee Ullah Malik Date: Fri, 15 Aug 2025 18:16:15 +0500 Subject: [PATCH 1/4] Remove gateway port configuration and related code from supernode initialization --- supernode/cmd/init.go | 74 +++++----------------- supernode/cmd/start.go | 6 +- supernode/config.yml | 1 - supernode/config/config.go | 2 +- supernode/config/save.go | 9 ++- supernode/node/supernode/gateway/server.go | 9 +++ 6 files changed, 32 insertions(+), 69 deletions(-) diff --git a/supernode/cmd/init.go b/supernode/cmd/init.go index 67706e95..186f3be8 100644 --- a/supernode/cmd/init.go +++ b/supernode/cmd/init.go @@ -29,7 +29,6 @@ var ( mnemonicFlag string supernodeAddrFlag string supernodePortFlag int - gatewayPortFlag int lumeraGrpcFlag string chainIDFlag string passphrasePlain string @@ -43,7 +42,6 @@ const ( DefaultKeyName = "test-key" DefaultSupernodeAddr = "0.0.0.0" DefaultSupernodePort = 4444 - DefaultGatewayPort = 8002 DefaultLumeraGRPC = "localhost:9090" DefaultChainID = "testing" ) @@ -59,7 +57,6 @@ type InitInputs struct { Mnemonic string SupernodeAddr string SupernodePort int - GatewayPort int LumeraGRPC string ChainID string } @@ -125,7 +122,7 @@ Example: } // Update config with gathered settings and save - if err := updateAndSaveConfig(address, inputs.SupernodeAddr, inputs.SupernodePort, inputs.GatewayPort, inputs.LumeraGRPC, inputs.ChainID); err != nil { + if err := updateAndSaveConfig(address, inputs.SupernodeAddr, inputs.SupernodePort, inputs.LumeraGRPC, inputs.ChainID); err != nil { return err } @@ -201,7 +198,6 @@ func gatherUserInputs() (InitInputs, error) { keyringBackendFlag != "" && supernodeAddrFlag != "" && supernodePortFlag != 0 && - gatewayPortFlag != 0 && lumeraGrpcFlag != "" && chainIDFlag != "" && ((!shouldRecoverFlag && mnemonicFlag == "") || (shouldRecoverFlag && mnemonicFlag != "")) @@ -270,18 +266,7 @@ func gatherUserInputs() (InitInputs, error) { } } - // Step 5e: Set the HTTP gateway port for API access - gatewayPort := DefaultGatewayPort - if gatewayPortFlag != 0 { - gatewayPort = gatewayPortFlag - - // Validate gateway port is within valid range - if gatewayPort < 1 || gatewayPort > 65535 { - return InitInputs{}, fmt.Errorf("invalid gateway port: %d, must be between 1 and 65535", gatewayPort) - } - } - - // Step 5f: Configure connection to the Lumera blockchain node + // Step 5e: Configure connection to the Lumera blockchain node // This is the GRPC endpoint for blockchain interactions lumeraGRPC := DefaultLumeraGRPC if lumeraGrpcFlag != "" { @@ -293,20 +278,20 @@ func gatherUserInputs() (InitInputs, error) { } } - // Step 5g: Set the blockchain network identifier + // Step 5f: Set the blockchain network identifier // Must match the chain ID of the Lumera network you're connecting to chainID := DefaultChainID if chainIDFlag != "" { chainID = chainIDFlag } - // Step 5h: Final validation for key recovery mode + // Step 5g: Final validation for key recovery mode // Ensure mnemonic is provided when attempting to recover an existing key if shouldRecoverFlag && mnemonicFlag == "" { return InitInputs{}, fmt.Errorf("--mnemonic flag is required when --recover flag is set") } - // Step 5i: Return all collected configuration values + // Step 5h: Return all collected configuration values // These will be used to initialize the supernode return InitInputs{ KeyringBackend: backend, @@ -318,7 +303,6 @@ func gatherUserInputs() (InitInputs, error) { Mnemonic: mnemonicFlag, SupernodeAddr: supernodeAddr, SupernodePort: supernodePort, - GatewayPort: gatewayPort, LumeraGRPC: lumeraGRPC, ChainID: chainID, }, nil @@ -379,8 +363,8 @@ func gatherUserInputs() (InitInputs, error) { // Step 6d: Prompt for network configuration // Collect all network-related settings (ports, addresses, chain ID) - inputs.SupernodeAddr, inputs.SupernodePort, inputs.GatewayPort, inputs.LumeraGRPC, inputs.ChainID, err = - promptNetworkConfig(supernodeAddrFlag, supernodePortFlag, gatewayPortFlag, lumeraGrpcFlag, chainIDFlag) + inputs.SupernodeAddr, inputs.SupernodePort, inputs.LumeraGRPC, inputs.ChainID, err = + promptNetworkConfig(supernodeAddrFlag, supernodePortFlag, lumeraGrpcFlag, chainIDFlag) if err != nil { return InitInputs{}, fmt.Errorf("failed to configure network settings: %w", err) } @@ -477,12 +461,11 @@ func createNewKey(kr consmoskeyring.Keyring, keyName string) (string, string, er } // updateAndSaveConfig updates the configuration with network settings and saves it -func updateAndSaveConfig(address, supernodeAddr string, supernodePort int, gatewayPort int, lumeraGrpcAddr string, chainID string) error { +func updateAndSaveConfig(address, supernodeAddr string, supernodePort int, lumeraGrpcAddr string, chainID string) error { // Update config with address and network settings appConfig.SupernodeConfig.Identity = address appConfig.SupernodeConfig.Host = supernodeAddr appConfig.SupernodeConfig.Port = uint16(supernodePort) - appConfig.SupernodeConfig.GatewayPort = uint16(gatewayPort) appConfig.LumeraClientConfig.GRPCAddr = lumeraGrpcAddr appConfig.LumeraClientConfig.ChainID = chainID @@ -583,7 +566,7 @@ func promptKeyManagement(passedKeyName string, recover bool, passedMnemonic stri return keyName, shouldRecover, mnemonic, nil } -func promptNetworkConfig(passedAddrs string, passedPort int, passedGatewayPort int, passedGRPC, passedChainID string) (supernodeAddr string, supernodePort int, gatewayPort int, lumeraGrpcAddr string, chainID string, err error) { +func promptNetworkConfig(passedAddrs string, passedPort int, passedGRPC, passedChainID string) (supernodeAddr string, supernodePort int, lumeraGrpcAddr string, chainID string, err error) { if passedAddrs != "" { supernodeAddr = passedAddrs } else { @@ -596,12 +579,6 @@ func promptNetworkConfig(passedAddrs string, passedPort int, passedGatewayPort i port = fmt.Sprintf("%d", DefaultSupernodePort) } - var gPort string - if passedGatewayPort != 0 { - gPort = fmt.Sprintf("%d", passedGatewayPort) - } else { - gPort = fmt.Sprintf("%d", DefaultGatewayPort) - } if passedGRPC != "" { lumeraGrpcAddr = passedGRPC } else { @@ -621,12 +598,12 @@ func promptNetworkConfig(passedAddrs string, passedPort int, passedGatewayPort i } err = survey.AskOne(supernodePrompt, &supernodeAddr) if err != nil { - return "", 0, 0, "", "", err + return "", 0, "", "", err } // Validate IP address format if err := validateIPAddress(supernodeAddr); err != nil { - return "", 0, 0, "", "", err + return "", 0, "", "", err } // Supernode port @@ -638,30 +615,14 @@ func promptNetworkConfig(passedAddrs string, passedPort int, passedGatewayPort i } err = survey.AskOne(supernodePortPrompt, &portStr) if err != nil { - return "", 0, 0, "", "", err + return "", 0, "", "", err } supernodePort, err = strconv.Atoi(portStr) if err != nil || supernodePort < 1 || supernodePort > 65535 { - return "", 0, 0, "", "", fmt.Errorf("invalid supernode port: %s", portStr) + return "", 0, "", "", fmt.Errorf("invalid supernode port: %s", portStr) } - // Gateway port - var gatewayPortStr string - gatewayPortPrompt := &survey.Input{ - Message: "Enter HTTP gateway port:", - Default: gPort, - Help: "Port for the HTTP gateway to listen on (1-65535), Ctrl-C for exit", - } - err = survey.AskOne(gatewayPortPrompt, &gatewayPortStr) - if err != nil { - return "", 0, 0, "", "", err - } - - gatewayPort, err = strconv.Atoi(gatewayPortStr) - if err != nil || gatewayPort < 1 || gatewayPort > 65535 { - return "", 0, 0, "", "", fmt.Errorf("invalid gateway port: %s", gatewayPortStr) - } // Lumera GRPC address (full address with port) lumeraPrompt := &survey.Input{ @@ -671,12 +632,12 @@ func promptNetworkConfig(passedAddrs string, passedPort int, passedGatewayPort i } err = survey.AskOne(lumeraPrompt, &lumeraGrpcAddr) if err != nil { - return "", 0, 0, "", "", err + return "", 0, "", "", err } // Validate GRPC address format if err := validateGRPCAddress(lumeraGrpcAddr); err != nil { - return "", 0, 0, "", "", err + return "", 0, "", "", err } // Chain ID @@ -687,10 +648,10 @@ func promptNetworkConfig(passedAddrs string, passedPort int, passedGatewayPort i } err = survey.AskOne(chainPrompt, &chainID, survey.WithValidator(survey.Required)) if err != nil { - return "", 0, 0, "", "", err + return "", 0, "", "", err } - return supernodeAddr, supernodePort, gatewayPort, lumeraGrpcAddr, chainID, nil + return supernodeAddr, supernodePort, lumeraGrpcAddr, chainID, nil } // validateKeyringBackend checks if the provided keyring backend is valid @@ -796,7 +757,6 @@ func init() { initCmd.Flags().StringVar(&mnemonicFlag, "mnemonic", "", "Mnemonic phrase for key recovery (only used with --recover)") initCmd.Flags().StringVar(&supernodeAddrFlag, "supernode-addr", "", "IP address for the supernode to listen on") initCmd.Flags().IntVar(&supernodePortFlag, "supernode-port", 0, "Port for the supernode to listen on") - initCmd.Flags().IntVar(&gatewayPortFlag, "gateway-port", 0, "Port for the HTTP gateway to listen on") initCmd.Flags().StringVar(&lumeraGrpcFlag, "lumera-grpc", "", "GRPC address of the Lumera node (host:port)") initCmd.Flags().StringVar(&chainIDFlag, "chain-id", "", "Chain ID of the Lumera network") initCmd.Flags().StringVar(&passphrasePlain, "keyring-passphrase", "", "Keyring passphrase for non-interactive mode") diff --git a/supernode/cmd/start.go b/supernode/cmd/start.go index a92bfa3a..ae533231 100644 --- a/supernode/cmd/start.go +++ b/supernode/cmd/start.go @@ -134,11 +134,7 @@ The supernode will connect to the Lumera network and begin participating in the } // Create HTTP gateway server that directly calls the supernode server - gatewayPort := appConfig.SupernodeConfig.GatewayPort - if gatewayPort == 0 { - gatewayPort = 8002 // Default fallback - } - gatewayServer, err := gateway.NewServer(appConfig.SupernodeConfig.Host, int(gatewayPort), supernodeServer) + gatewayServer, err := gateway.NewServer(appConfig.SupernodeConfig.Host, int(appConfig.SupernodeConfig.GatewayPort), supernodeServer) if err != nil { return fmt.Errorf("failed to create gateway server: %w", err) } diff --git a/supernode/config.yml b/supernode/config.yml index 99883d59..3bbf8b7e 100644 --- a/supernode/config.yml +++ b/supernode/config.yml @@ -4,7 +4,6 @@ supernode: identity: "lumera1ccmw5plzuldntum2rz6kq6uq346vtrhrvwfzsa" # Identity of the supernode, lumera address host: "0.0.0.0" port: 4444 - gateway_port: 8002 # Port for the HTTP gateway # Keyring Configuration keyring: diff --git a/supernode/config/config.go b/supernode/config/config.go index 3b8a2a89..b8e73cd7 100644 --- a/supernode/config/config.go +++ b/supernode/config/config.go @@ -15,7 +15,7 @@ type SupernodeConfig struct { Identity string `yaml:"identity"` Host string `yaml:"host"` Port uint16 `yaml:"port"` - GatewayPort uint16 `yaml:"gateway_port"` + GatewayPort uint16 `yaml:"gateway_port,omitempty"` } type KeyringConfig struct { diff --git a/supernode/config/save.go b/supernode/config/save.go index 52cf5b04..5199fb81 100644 --- a/supernode/config/save.go +++ b/supernode/config/save.go @@ -42,11 +42,10 @@ func CreateDefaultConfig(keyName, identity, chainID string, keyringBackend, keyr return &Config{ SupernodeConfig: SupernodeConfig{ - KeyName: keyName, - Identity: identity, - Host: "0.0.0.0", - Port: 4444, - GatewayPort: 8002, + KeyName: keyName, + Identity: identity, + Host: "0.0.0.0", + Port: 4444, }, KeyringConfig: KeyringConfig{ Backend: keyringBackend, diff --git a/supernode/node/supernode/gateway/server.go b/supernode/node/supernode/gateway/server.go index e72effd5..44bbe41f 100644 --- a/supernode/node/supernode/gateway/server.go +++ b/supernode/node/supernode/gateway/server.go @@ -14,6 +14,9 @@ import ( "github.com/LumeraProtocol/supernode/pkg/logtrace" ) +// DefaultGatewayPort is an uncommon port for internal gateway use +const DefaultGatewayPort = 8092 + // Server represents the HTTP gateway server type Server struct { ipAddress string @@ -23,11 +26,17 @@ type Server struct { } // NewServer creates a new HTTP gateway server that directly calls the service +// If port is 0, it will use the default port func NewServer(ipAddress string, port int, supernodeServer pb.SupernodeServiceServer) (*Server, error) { if supernodeServer == nil { return nil, fmt.Errorf("supernode server is required") } + // Use default port if not specified + if port == 0 { + port = DefaultGatewayPort + } + return &Server{ ipAddress: ipAddress, port: port, From 75f842610fa601779b643c7b318a0d7b6e2e4357 Mon Sep 17 00:00:00 2001 From: Matee Ullah Malik Date: Fri, 15 Aug 2025 18:33:27 +0500 Subject: [PATCH 2/4] Add port availability checks to config verification --- supernode/services/verifier/verifier.go | 57 ++++++++++ supernode/services/verifier/verifier_test.go | 109 +++++++++++++++++++ 2 files changed, 166 insertions(+) diff --git a/supernode/services/verifier/verifier.go b/supernode/services/verifier/verifier.go index d8a39c87..84b5aeee 100644 --- a/supernode/services/verifier/verifier.go +++ b/supernode/services/verifier/verifier.go @@ -3,6 +3,7 @@ package verifier import ( "context" "fmt" + "net" "strings" "github.com/LumeraProtocol/supernode/pkg/lumera" @@ -81,6 +82,9 @@ func (cv *ConfigVerifier) VerifyConfig(ctx context.Context) (*VerificationResult // Check 7: Check host alignment with on-chain registration (warning only - may differ due to load balancer) cv.checkHostAlignment(result, supernode) + // Check 8: Verify all required ports are available + cv.checkPortsAvailable(result) + logtrace.Info(ctx, "Config verification completed", logtrace.Fields{ "valid": result.IsValid(), "errors": len(result.Errors), @@ -221,4 +225,57 @@ func (cv *ConfigVerifier) checkHostAlignment(result *VerificationResult, superno }) } } +} + +// checkPortsAvailable verifies that all required ports are available for binding +func (cv *ConfigVerifier) checkPortsAvailable(result *VerificationResult) { + // Check supernode port + if !cv.isPortAvailable(cv.config.SupernodeConfig.Host, int(cv.config.SupernodeConfig.Port)) { + result.Valid = false + result.Errors = append(result.Errors, ConfigError{ + Field: "supernode_port", + Actual: fmt.Sprintf("%d", cv.config.SupernodeConfig.Port), + Message: fmt.Sprintf("Port %d is already in use. Please stop the conflicting service or choose a different port", cv.config.SupernodeConfig.Port), + }) + } + + // Check P2P port + if !cv.isPortAvailable(cv.config.SupernodeConfig.Host, int(cv.config.P2PConfig.Port)) { + result.Valid = false + result.Errors = append(result.Errors, ConfigError{ + Field: "p2p_port", + Actual: fmt.Sprintf("%d", cv.config.P2PConfig.Port), + Message: fmt.Sprintf("Port %d is already in use. Please stop the conflicting service or choose a different port", cv.config.P2PConfig.Port), + }) + } + + // Check gateway port (use configured port or default port 8092) + gatewayPort := int(cv.config.SupernodeConfig.GatewayPort) + if gatewayPort == 0 { + gatewayPort = 8092 // Default gateway port (same as gateway.DefaultGatewayPort) + } + + if !cv.isPortAvailable(cv.config.SupernodeConfig.Host, gatewayPort) { + result.Valid = false + result.Errors = append(result.Errors, ConfigError{ + Field: "gateway_port", + Actual: fmt.Sprintf("%d", gatewayPort), + Message: fmt.Sprintf("Port %d is already in use. Please stop the conflicting service or choose a different port", gatewayPort), + }) + } +} + +// isPortAvailable checks if a port is available for binding +func (cv *ConfigVerifier) isPortAvailable(host string, port int) bool { + address := fmt.Sprintf("%s:%d", host, port) + + // Try to listen on the port + listener, err := net.Listen("tcp", address) + if err != nil { + return false // Port is not available + } + + // Close the listener immediately since we're just checking availability + listener.Close() + return true // Port is available } \ No newline at end of file diff --git a/supernode/services/verifier/verifier_test.go b/supernode/services/verifier/verifier_test.go index a0532efc..80fdc969 100644 --- a/supernode/services/verifier/verifier_test.go +++ b/supernode/services/verifier/verifier_test.go @@ -1,6 +1,8 @@ package verifier import ( + "net" + "strconv" "testing" "github.com/LumeraProtocol/supernode/supernode/config" @@ -149,4 +151,111 @@ func TestVerificationResult_Summary(t *testing.T) { } }) } +} + +func TestConfigVerifier_isPortAvailable(t *testing.T) { + cfg := &config.Config{ + SupernodeConfig: config.SupernodeConfig{ + Identity: "lumera1testaddress", + KeyName: "test-key", + Host: "127.0.0.1", + }, + } + + verifier := NewConfigVerifier(cfg, nil, nil).(*ConfigVerifier) + + // Test available port + available := verifier.isPortAvailable("127.0.0.1", 0) // Port 0 lets OS choose available port + assert.True(t, available) + + // Test unavailable port by creating a listener + listener, err := net.Listen("tcp", "127.0.0.1:0") + assert.NoError(t, err) + defer listener.Close() + + // Extract the port that was assigned + _, portStr, err := net.SplitHostPort(listener.Addr().String()) + assert.NoError(t, err) + port, err := strconv.Atoi(portStr) + assert.NoError(t, err) + + // Now test that this port is not available + available = verifier.isPortAvailable("127.0.0.1", port) + assert.False(t, available) +} + +func TestConfigVerifier_checkPortsAvailable(t *testing.T) { + // Create a listener to occupy a port + listener, err := net.Listen("tcp", "127.0.0.1:0") + assert.NoError(t, err) + defer listener.Close() + + // Extract the port that was assigned + _, portStr, err := net.SplitHostPort(listener.Addr().String()) + assert.NoError(t, err) + port, err := strconv.Atoi(portStr) + assert.NoError(t, err) + + cfg := &config.Config{ + SupernodeConfig: config.SupernodeConfig{ + Identity: "lumera1testaddress", + KeyName: "test-key", + Host: "127.0.0.1", + Port: uint16(port), // Use the occupied port + }, + P2PConfig: config.P2PConfig{ + Port: 0, // Available port + }, + } + + verifier := NewConfigVerifier(cfg, nil, nil).(*ConfigVerifier) + result := &VerificationResult{ + Valid: true, + Errors: []ConfigError{}, + Warnings: []ConfigError{}, + } + + verifier.checkPortsAvailable(result) + + // Should have error for supernode port being unavailable + assert.False(t, result.IsValid()) + assert.Len(t, result.Errors, 1) + assert.Equal(t, "supernode_port", result.Errors[0].Field) + assert.Contains(t, result.Errors[0].Message, "already in use") +} + +func TestConfigVerifier_checkPortsAvailable_DefaultGatewayPort(t *testing.T) { + // Create a listener to occupy the default gateway port 8092 + listener, err := net.Listen("tcp", "127.0.0.1:8092") + assert.NoError(t, err) + defer listener.Close() + + cfg := &config.Config{ + SupernodeConfig: config.SupernodeConfig{ + Identity: "lumera1testaddress", + KeyName: "test-key", + Host: "127.0.0.1", + Port: 4444, // Available port + GatewayPort: 0, // Not configured, should use default 8092 + }, + P2PConfig: config.P2PConfig{ + Port: 4445, // Available port + }, + } + + verifier := NewConfigVerifier(cfg, nil, nil).(*ConfigVerifier) + result := &VerificationResult{ + Valid: true, + Errors: []ConfigError{}, + Warnings: []ConfigError{}, + } + + verifier.checkPortsAvailable(result) + + // Should have error for default gateway port being unavailable + assert.False(t, result.IsValid()) + assert.Len(t, result.Errors, 1) + assert.Equal(t, "gateway_port", result.Errors[0].Field) + assert.Equal(t, "8092", result.Errors[0].Actual) + assert.Contains(t, result.Errors[0].Message, "Port 8092 is already in use") } \ No newline at end of file From 7857872646e4eb95034033d3d04fb78a01dbe7d5 Mon Sep 17 00:00:00 2001 From: Matee Ullah Malik Date: Fri, 15 Aug 2025 23:03:30 +0500 Subject: [PATCH 3/4] Add readme, other updates --- README.md | 15 +- sn-manager/README.md | 143 ++++++++++++++++++ sn-manager/cmd/init.go | 48 +++--- sn-manager/cmd/start.go | 18 ++- sn-manager/go.mod | 33 ++-- sn-manager/go.sum | 127 +++++++++++++++- sn-manager/internal/github/client.go | 12 +- sn-manager/internal/manager/manager.go | 34 ++--- sn-manager/internal/updater/updater.go | 68 +++++---- supernode/cmd/init.go | 7 +- .../services/common/supernode/service.go | 27 +--- supernode/services/verifier/verifier.go | 69 +-------- 12 files changed, 412 insertions(+), 189 deletions(-) create mode 100644 sn-manager/README.md diff --git a/README.md b/README.md index 583b57b1..c575ea55 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ enum SupernodeEventType { ## HTTP Gateway -The supernode provides an HTTP gateway that exposes the gRPC services via REST API. The gateway runs on a separate port (default: 8002) and provides: +The supernode provides an HTTP gateway that exposes the gRPC services via REST API. The gateway runs on a separate port 8092 ### Endpoints @@ -144,7 +144,7 @@ The supernode provides an HTTP gateway that exposes the gRPC services via REST A Returns the current supernode status including system resources (CPU, memory, storage) and service information. ```bash -curl http://localhost:8002/api/v1/status +curl http://localhost:8092/api/v1/status ``` Response: @@ -198,7 +198,7 @@ Response: Returns the list of available services on the supernode. ```bash -curl http://localhost:8002/api/v1/services +curl http://localhost:8092/api/v1/services ``` Response: @@ -214,8 +214,8 @@ The gateway automatically translates between HTTP/JSON and gRPC/protobuf formats The gateway provides interactive API documentation via Swagger UI: -- **Swagger UI**: http://localhost:8002/swagger-ui/ -- **OpenAPI Spec**: http://localhost:8002/swagger.json +- **Swagger UI**: http://localhost:8092/swagger-ui/ +- **OpenAPI Spec**: http://localhost:8092/swagger.json The Swagger UI provides an interactive interface to explore and test all available API endpoints. @@ -239,7 +239,6 @@ supernode init -y \ --mnemonic "word1 word2 word3 ... word24" \ --supernode-addr 0.0.0.0 \ --supernode-port 4444 \ - --gateway-port 8002 \ --lumera-grpc https://grpc.testnet.lumera.io \ --chain-id lumera-testnet-1 @@ -253,7 +252,6 @@ supernode init -y \ --mnemonic "word1 word2 word3 ... word24" \ --supernode-addr 0.0.0.0 \ --supernode-port 4444 \ - --gateway-port 8002 \ --lumera-grpc https://grpc.testnet.lumera.io \ --chain-id lumera-testnet-1 @@ -267,7 +265,6 @@ supernode init -y \ --mnemonic "word1 word2 word3 ... word24" \ --supernode-addr 0.0.0.0 \ --supernode-port 4444 \ - --gateway-port 8002 \ --lumera-grpc https://grpc.testnet.lumera.io \ --chain-id lumera-testnet-1 ``` @@ -281,7 +278,6 @@ supernode init -y \ - `--mnemonic` - Mnemonic phrase for recovery (use with --recover) - `--supernode-addr` - IP address for supernode service - `--supernode-port` - Port for supernode service -- `--gateway-port` - Port for the HTTP gateway to listen on - `--lumera-grpc` - Lumera gRPC address (host:port) - `--chain-id` - Lumera blockchain chain ID - `--keyring-passphrase` - Keyring passphrase for non-interactive mode (plain text) @@ -357,7 +353,6 @@ supernode: identity: "lumera15t2e8gjgmuqtj..." # Lumera address for this supernode host: "0.0.0.0" # Host address to bind the service (IP or hostname) port: 4444 # Port for the supernode service - gateway_port: 8002 # Port for the HTTP gateway service ``` ### Keyring Configuration diff --git a/sn-manager/README.md b/sn-manager/README.md new file mode 100644 index 00000000..f721f25b --- /dev/null +++ b/sn-manager/README.md @@ -0,0 +1,143 @@ +# SN-Manager + +SuperNode Process Manager with Automatic Updates + +## Installation + +Download and install sn-manager: +```bash +# Download and extract +curl -L https://github.com/LumeraProtocol/supernode/releases/latest/download/supernode-linux-amd64.tar.gz | tar -xz + +# Install sn-manager only (supernode will be managed automatically) +chmod +x sn-manager +sudo mv sn-manager /usr/local/bin/ + +# Verify +sn-manager version +``` + +Note: SuperNode binary will be automatically downloaded and managed by sn-manager during initialization. + +## Systemd Service Setup + +**Replace `` with your Linux username:** + +```bash +sudo tee /etc/systemd/system/sn-manager.service < +ExecStart=/usr/local/bin/sn-manager start +Restart=on-failure +RestartSec=10 +LimitNOFILE=65536 +Environment="HOME=/home/" +WorkingDirectory=/home/ + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now sn-manager +journalctl -u sn-manager -f +``` + +## Initialization + +### Interactive Mode +```bash +sn-manager init +``` + +### Non-Interactive Mode + +**Basic setup:** +```bash +sn-manager init -y +``` + +**Full example with all flags:** +```bash +export SUPERNODE_PASSPHRASE="your-secure-passphrase" + +sn-manager init -y \ + --auto-upgrade \ + --check-interval 3600 \ + --keyring-backend file \ + --keyring-passphrase-env SUPERNODE_PASSPHRASE \ + --key-name myvalidator \ + --supernode-addr 0.0.0.0 \ + --supernode-port 4444 \ + --lumera-grpc https://grpc.lumera.io:443 \ + --chain-id lumera-testnet-2 +``` + +**With key recovery:** +```bash +sn-manager init -y \ + --keyring-backend file \ + --keyring-passphrase "your-secure-passphrase" \ + --key-name myvalidator \ + --recover \ + --mnemonic "word1 word2 ... word24" \ + --supernode-addr 0.0.0.0 \ + --lumera-grpc https://grpc.lumera.io:443 \ + --chain-id lumera-testnet-2 +``` + +### Flags + +**SN-Manager flags:** +- `--force` - Override existing configuration +- `--auto-upgrade` - Enable automatic updates +- `--check-interval` - Update check interval in seconds + +**SuperNode flags (passed through):** +- `-y` - Skip prompts +- `--keyring-backend` - Backend type (os/file/test) +- `--keyring-passphrase` - Plain text passphrase +- `--keyring-passphrase-env` - Environment variable name +- `--keyring-passphrase-file` - File path +- `--key-name` - Key identifier +- `--recover` - Recover from mnemonic +- `--mnemonic` - Recovery phrase +- `--supernode-addr` - Bind address +- `--supernode-port` - Service port +- `--lumera-grpc` - gRPC endpoint +- `--chain-id` - Chain identifier + +## Commands + +- `init` - Initialize sn-manager and SuperNode +- `start` - Start SuperNode +- `stop` - Stop SuperNode +- `status` - Show status +- `version` - Show version +- `get ` - Download version +- `use ` - Switch version +- `ls` - List installed versions +- `ls-remote` - List available versions +- `check` - Check for updates + +## Configuration + +### SN-Manager (`~/.sn-manager/config.yml`) +```yaml +updates: + current_version: "v1.7.4" + auto_upgrade: true + check_interval: 3600 +``` + +**Reset:** +```bash +sudo systemctl stop sn-manager +rm -rf ~/.sn-manager/ ~/.supernode/ +sn-manager init +``` + diff --git a/sn-manager/cmd/init.go b/sn-manager/cmd/init.go index 65af1509..40c1dbf6 100644 --- a/sn-manager/cmd/init.go +++ b/sn-manager/cmd/init.go @@ -57,6 +57,8 @@ func parseInitFlags(args []string) *initFlags { flags.force = true case "-y", "--yes": flags.nonInteractive = true + // Pass through to supernode as well + flags.supernodeArgs = append(flags.supernodeArgs, args[i]) default: // Pass all other args to supernode @@ -126,10 +128,10 @@ func runInit(cmd *cobra.Command, args []string) error { return fmt.Errorf("already initialized at %s. Use --force to re-initialize", managerHome) } - // Force mode: remove existing directory - fmt.Printf("Removing existing directory at %s...\n", managerHome) - if err := os.RemoveAll(managerHome); err != nil { - return fmt.Errorf("failed to remove existing directory: %w", err) + // Force mode: remove existing config file only + fmt.Printf("Removing existing config file at %s...\n", configPath) + if err := os.Remove(configPath); err != nil { + return fmt.Errorf("failed to remove existing config: %w", err) } } @@ -138,7 +140,6 @@ func runInit(cmd *cobra.Command, args []string) error { managerHome, filepath.Join(managerHome, "binaries"), filepath.Join(managerHome, "downloads"), - filepath.Join(managerHome, "logs"), } for _, dir := range dirs { @@ -186,7 +187,9 @@ func runInit(cmd *cobra.Command, args []string) error { fmt.Printf("Latest version: %s\n", targetVersion) // Check if already installed - if !versionMgr.IsVersionInstalled(targetVersion) { + if versionMgr.IsVersionInstalled(targetVersion) { + fmt.Printf("āœ“ SuperNode %s already installed, skipping download\n", targetVersion) + } else { // Get download URL downloadURL, err := client.GetSupernodeDownloadURL(targetVersion) if err != nil { @@ -238,19 +241,26 @@ func runInit(cmd *cobra.Command, args []string) error { // Step 3: Initialize SuperNode fmt.Println("\nStep 3: Initializing SuperNode...") - // Get the managed supernode binary path - supernodeBinary := filepath.Join(managerHome, "current", "supernode") - - // Always include -y flag for non-interactive mode since sn-manager runs programmatically - supernodeArgs := append([]string{"init", "-y"}, flags.supernodeArgs...) - supernodeCmd := exec.Command(supernodeBinary, supernodeArgs...) - supernodeCmd.Stdout = os.Stdout - supernodeCmd.Stderr = os.Stderr - supernodeCmd.Stdin = os.Stdin - - // Run supernode init - if err := supernodeCmd.Run(); err != nil { - return fmt.Errorf("supernode init failed: %w", err) + // Check if SuperNode is already initialized + supernodeConfigPath := filepath.Join(os.Getenv("HOME"), ".supernode", "config.yml") + if _, err := os.Stat(supernodeConfigPath); err == nil { + fmt.Println("āœ“ SuperNode already initialized, skipping initialization") + } else { + // Get the managed supernode binary path + supernodeBinary := filepath.Join(managerHome, "current", "supernode") + + // Pass through user-provided arguments to supernode init + supernodeArgs := append([]string{"init"}, flags.supernodeArgs...) + + supernodeCmd := exec.Command(supernodeBinary, supernodeArgs...) + supernodeCmd.Stdout = os.Stdout + supernodeCmd.Stderr = os.Stderr + supernodeCmd.Stdin = os.Stdin + + // Run supernode init + if err := supernodeCmd.Run(); err != nil { + return fmt.Errorf("supernode init failed: %w", err) + } } fmt.Println("\nāœ… Complete! Both sn-manager and SuperNode have been initialized.") diff --git a/sn-manager/cmd/start.go b/sn-manager/cmd/start.go index 189eb892..babf68c1 100644 --- a/sn-manager/cmd/start.go +++ b/sn-manager/cmd/start.go @@ -80,7 +80,12 @@ func runStart(cmd *cobra.Command, args []string) error { autoUpdater.Start(ctx) } - fmt.Println("SuperNode started. Press Ctrl+C to stop.") + // Monitor SuperNode process exit + processExitCh := make(chan error, 1) + go func() { + err := mgr.Wait() + processExitCh <- err + }() // Main loop - monitor for updates if auto-upgrade is enabled ticker := time.NewTicker(5 * time.Second) @@ -88,6 +93,17 @@ func runStart(cmd *cobra.Command, args []string) error { for { select { + case err := <-processExitCh: + // SuperNode process exited + if autoUpdater != nil { + autoUpdater.Stop() + } + if err != nil { + return fmt.Errorf("supernode exited with error: %w", err) + } + fmt.Println("SuperNode exited") + return nil + case <-sigChan: fmt.Println("\nShutting down...") diff --git a/sn-manager/go.mod b/sn-manager/go.mod index d5c4758c..b1726c67 100644 --- a/sn-manager/go.mod +++ b/sn-manager/go.mod @@ -1,27 +1,38 @@ module github.com/LumeraProtocol/supernode/sn-manager -go 1.23.0 - -toolchain go1.24.1 +go 1.24.1 require ( github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/LumeraProtocol/supernode v0.0.0-00010101000000-000000000000 github.com/golang/mock v1.6.0 github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // 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.2 // indirect - github.com/mattn/go-isatty v0.0.8 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.4.0 // 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 v0.0.0-20240701130421-f6361c86f094 // 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 ) + +replace github.com/LumeraProtocol/supernode => ../ diff --git a/sn-manager/go.sum b/sn-manager/go.sum index b4748a8d..3225e6f0 100644 --- a/sn-manager/go.sum +++ b/sn-manager/go.sum @@ -1,79 +1,190 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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 v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 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= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +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-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= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 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.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= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= 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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 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/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 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 h1:6whtk83KtD3FkGrVb2hFXuQ+ZMbCNdakARIn/aHMmG8= +google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094/go.mod h1:Zs4wYw8z1zr6RNF4cwYb31mvN/EGaKAdQjNCF3DW6K4= +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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/sn-manager/internal/github/client.go b/sn-manager/internal/github/client.go index 2c39e450..1c26d614 100644 --- a/sn-manager/internal/github/client.go +++ b/sn-manager/internal/github/client.go @@ -43,8 +43,9 @@ type Asset struct { // Client handles GitHub API interactions type Client struct { - repo string - httpClient *http.Client + repo string + httpClient *http.Client + downloadClient *http.Client } // NewClient creates a new GitHub API client @@ -52,7 +53,10 @@ func NewClient(repo string) GithubClient { return &Client{ repo: repo, httpClient: &http.Client{ - Timeout: 5 * time.Minute, // Increased timeout for large binary downloads + Timeout: 30 * time.Second, // 30 second timeout for API calls + }, + downloadClient: &http.Client{ + Timeout: 5 * time.Minute, // 5 minute timeout for large binary downloads }, } } @@ -195,7 +199,7 @@ func (c *Client) DownloadBinary(url, destPath string, progress func(downloaded, defer os.Remove(tmpPath) // Download file - resp, err := c.httpClient.Get(url) + resp, err := c.downloadClient.Get(url) if err != nil { tmpFile.Close() return fmt.Errorf("failed to download: %w", err) diff --git a/sn-manager/internal/manager/manager.go b/sn-manager/internal/manager/manager.go index d963b68d..cafbc06a 100644 --- a/sn-manager/internal/manager/manager.go +++ b/sn-manager/internal/manager/manager.go @@ -21,7 +21,6 @@ type Manager struct { process *os.Process cmd *exec.Cmd mu sync.RWMutex - logFile *os.File startTime time.Time } @@ -66,28 +65,17 @@ func (m *Manager) Start(ctx context.Context) error { return fmt.Errorf("supernode is already running") } - // Open log file - logPath := filepath.Join(m.homeDir, "logs", "supernode.log") - logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("failed to open log file: %w", err) - } - m.logFile = logFile - // Prepare command binary := m.GetSupernodeBinary() // SuperNode will handle its own home directory and arguments args := []string{"start"} - log.Printf("Starting SuperNode: %s %v", binary, args) - m.cmd = exec.CommandContext(ctx, binary, args...) - m.cmd.Stdout = m.logFile - m.cmd.Stderr = m.logFile + m.cmd.Stdout = os.Stdout + m.cmd.Stderr = os.Stderr // Start the process if err := m.cmd.Start(); err != nil { - m.logFile.Close() return fmt.Errorf("failed to start supernode: %w", err) } @@ -161,16 +149,24 @@ func (m *Manager) IsRunning() bool { return err == nil } +// Wait waits for the SuperNode process to exit and returns any error +func (m *Manager) Wait() error { + m.mu.RLock() + cmd := m.cmd + m.mu.RUnlock() + + if cmd == nil { + return fmt.Errorf("no process running") + } + + return cmd.Wait() +} + // cleanup performs cleanup after process stops func (m *Manager) cleanup() { m.process = nil m.cmd = nil - if m.logFile != nil { - m.logFile.Close() - m.logFile = nil - } - // Remove PID file pidPath := filepath.Join(m.homeDir, "supernode.pid") os.Remove(pidPath) diff --git a/sn-manager/internal/updater/updater.go b/sn-manager/internal/updater/updater.go index fde52374..913fb180 100644 --- a/sn-manager/internal/updater/updater.go +++ b/sn-manager/internal/updater/updater.go @@ -15,6 +15,7 @@ import ( "github.com/LumeraProtocol/supernode/sn-manager/internal/github" "github.com/LumeraProtocol/supernode/sn-manager/internal/utils" "github.com/LumeraProtocol/supernode/sn-manager/internal/version" + "github.com/LumeraProtocol/supernode/supernode/node/supernode/gateway" ) type AutoUpdater struct { @@ -36,12 +37,15 @@ type StatusResponse struct { } func New(homeDir string, cfg *config.Config) *AutoUpdater { + // Use the correct gateway endpoint with imported constants + gatewayURL := fmt.Sprintf("http://localhost:%d/api/v1/status", gateway.DefaultGatewayPort) + return &AutoUpdater{ config: cfg, homeDir: homeDir, githubClient: github.NewClient(config.GitHubRepo), versionMgr: version.NewManager(homeDir), - gatewayURL: "http://localhost:8002/api/supernode/status", + gatewayURL: gatewayURL, stopCh: make(chan struct{}), } } @@ -55,8 +59,6 @@ func (u *AutoUpdater) Start(ctx context.Context) { interval := time.Duration(u.config.Updates.CheckInterval) * time.Second u.ticker = time.NewTicker(interval) - log.Printf("Starting auto-updater (checking every %v)", interval) - u.checkAndUpdate(ctx) go func() { @@ -82,7 +84,7 @@ func (u *AutoUpdater) Stop() { func (u *AutoUpdater) checkAndUpdate(ctx context.Context) { log.Println("Checking for updates...") - + release, err := u.githubClient.GetLatestRelease() if err != nil { log.Printf("Failed to check for updates: %v", err) @@ -92,49 +94,67 @@ func (u *AutoUpdater) checkAndUpdate(ctx context.Context) { latestVersion := release.TagName currentVersion := u.config.Updates.CurrentVersion + log.Printf("Version comparison: current=%s, latest=%s", currentVersion, latestVersion) + if !u.shouldUpdate(currentVersion, latestVersion) { log.Printf("Current version %s is up to date", currentVersion) return } - log.Printf("New version available: %s -> %s", currentVersion, latestVersion) + log.Printf("Update available: %s -> %s", currentVersion, latestVersion) if !u.isGatewayIdle() { - log.Println("Gateway has running tasks, skipping update") + log.Println("Gateway busy, skipping update") return } if err := u.performUpdate(latestVersion); err != nil { - log.Printf("Failed to perform update: %v", err) + log.Printf("Update failed: %v", err) return } - log.Printf("Successfully updated to version %s", latestVersion) + log.Printf("Updated to %s", latestVersion) } func (u *AutoUpdater) shouldUpdate(current, latest string) bool { current = strings.TrimPrefix(current, "v") latest = strings.TrimPrefix(latest, "v") - currentParts := strings.Split(current, ".") - latestParts := strings.Split(latest, ".") + // Skip pre-release versions (beta, alpha, rc, etc.) + if strings.Contains(latest, "-") { + log.Printf("Skipping pre-release version: %s", latest) + return false + } + + // Handle pre-release versions (e.g., "1.7.1-beta" -> "1.7.1") + currentBase := strings.Split(current, "-")[0] + latestBase := strings.Split(latest, "-")[0] + + log.Printf("Comparing base versions: current=%s, latest=%s", currentBase, latestBase) + + currentParts := strings.Split(currentBase, ".") + latestParts := strings.Split(latestBase, ".") if len(currentParts) < 3 || len(latestParts) < 3 { + log.Printf("Invalid version format: current=%s, latest=%s", currentBase, latestBase) return false } + // Only update within same major.minor version if currentParts[0] != latestParts[0] || currentParts[1] != latestParts[1] { + log.Printf("Major/minor version mismatch, skipping update: %s.%s vs %s.%s", + currentParts[0], currentParts[1], latestParts[0], latestParts[1]) return false } - if currentParts[2] != latestParts[2] { - cmp := utils.CompareVersions(current, latest) - if cmp < 0 { - log.Printf("Update available (%s -> %s)", current, latest) - return true - } + // Compare base versions (stable releases only) + cmp := utils.CompareVersions(currentBase, latestBase) + if cmp < 0 { + log.Printf("Update needed: %s < %s", currentBase, latestBase) + return true } + log.Printf("No update needed: %s >= %s", currentBase, latestBase) return false } @@ -165,7 +185,7 @@ func (u *AutoUpdater) isGatewayIdle() bool { } if totalTasks > 0 { - log.Printf("Gateway has %d running tasks", totalTasks) + log.Printf("Gateway busy: %d running tasks", totalTasks) return false } @@ -173,8 +193,6 @@ func (u *AutoUpdater) isGatewayIdle() bool { } func (u *AutoUpdater) performUpdate(targetVersion string) error { - log.Printf("Downloading version %s...", targetVersion) - downloadURL, err := u.githubClient.GetSupernodeDownloadURL(targetVersion) if err != nil { return fmt.Errorf("failed to get download URL: %w", err) @@ -182,16 +200,8 @@ func (u *AutoUpdater) performUpdate(targetVersion string) error { tempFile := filepath.Join(u.homeDir, "downloads", fmt.Sprintf("supernode-%s.tmp", targetVersion)) - progress := func(downloaded, total int64) { - if total > 0 { - percent := int(downloaded * 100 / total) - if percent%20 == 0 { - log.Printf("Download progress: %d%%", percent) - } - } - } - - if err := u.githubClient.DownloadBinary(downloadURL, tempFile, progress); err != nil { + // Silent download - no progress reporting + if err := u.githubClient.DownloadBinary(downloadURL, tempFile, nil); err != nil { return fmt.Errorf("failed to download binary: %w", err) } diff --git a/supernode/cmd/init.go b/supernode/cmd/init.go index 186f3be8..41f99a92 100644 --- a/supernode/cmd/init.go +++ b/supernode/cmd/init.go @@ -457,6 +457,7 @@ func createNewKey(kr consmoskeyring.Keyring, keyName string) (string, string, er } address := addr.String() + fmt.Printf("Key generated successfully! Name: %s, Address: %s\n", keyName, address) return address, keyMnemonic, nil } @@ -483,12 +484,6 @@ func updateAndSaveConfig(address, supernodeAddr string, supernodePort int, lumer func printSuccessMessage(mnemonic string) { fmt.Println("\nYour supernode has been initialized successfully!") - // If a mnemonic was generated, display it again - if mnemonic != "" { - fmt.Println("\nIMPORTANT: Make sure you have saved your mnemonic:") - fmt.Printf("Mnemonic: %s\n", mnemonic) - } - fmt.Println("\nYou can now start your supernode with:") fmt.Println(" supernode start") } diff --git a/supernode/services/common/supernode/service.go b/supernode/services/common/supernode/service.go index 74ec360c..47300243 100644 --- a/supernode/services/common/supernode/service.go +++ b/supernode/services/common/supernode/service.go @@ -57,7 +57,7 @@ func (s *SupernodeStatusService) GetStatus(ctx context.Context) (StatusResponse, var resp StatusResponse resp.Version = Version - + // Calculate uptime resp.UptimeSeconds = uint64(time.Since(s.startTime).Seconds()) @@ -67,7 +67,7 @@ func (s *SupernodeStatusService) GetStatus(ctx context.Context) (StatusResponse, return resp, err } resp.Resources.CPU.UsagePercent = cpuUsage - + // Get CPU cores cpuCores, err := s.metrics.GetCPUCores(ctx) if err != nil { @@ -82,14 +82,14 @@ func (s *SupernodeStatusService) GetStatus(ctx context.Context) (StatusResponse, if err != nil { return resp, err } - + // Convert to GB const bytesToGB = 1024 * 1024 * 1024 resp.Resources.Memory.TotalGB = float64(memTotal) / bytesToGB resp.Resources.Memory.UsedGB = float64(memUsed) / bytesToGB resp.Resources.Memory.AvailableGB = float64(memAvailable) / bytesToGB resp.Resources.Memory.UsagePercent = memUsedPerc - + // Generate hardware summary if cpuCores > 0 && resp.Resources.Memory.TotalGB > 0 { resp.Resources.HardwareSummary = fmt.Sprintf("%d cores / %.0fGB RAM", cpuCores, resp.Resources.Memory.TotalGB) @@ -123,7 +123,7 @@ func (s *SupernodeStatusService) GetStatus(ctx context.Context) (StatusResponse, PeersCount: 0, PeerAddresses: []string{}, } - + // Collect P2P network information if s.p2pService != nil { p2pStats, err := s.p2pService.Stats(ctx) @@ -135,7 +135,7 @@ func (s *SupernodeStatusService) GetStatus(ctx context.Context) (StatusResponse, if peersCount, ok := dhtStats["peers_count"].(int); ok { resp.Network.PeersCount = int32(peersCount) } - + // Extract peer addresses if peers, ok := dhtStats["peers"].([]*kademlia.Node); ok { resp.Network.PeerAddresses = make([]string, 0, len(peers)) @@ -187,20 +187,5 @@ func (s *SupernodeStatusService) GetStatus(ctx context.Context) (StatusResponse, totalTasks += int(service.TaskCount) } - logtrace.Info(ctx, "status data collected", logtrace.Fields{ - "cpu_usage%": cpuUsage, - "cpu_cores": cpuCores, - "mem_total_gb": resp.Resources.Memory.TotalGB, - "mem_used_gb": resp.Resources.Memory.UsedGB, - "mem_usage%": memUsedPerc, - "uptime_seconds": resp.UptimeSeconds, - "storage_volumes": len(resp.Resources.Storage), - "service_count": len(resp.RunningTasks), - "total_tasks": totalTasks, - "network_peers": resp.Network.PeersCount, - "rank": resp.Rank, - "ip_address": resp.IPAddress, - }) - return resp, nil } diff --git a/supernode/services/verifier/verifier.go b/supernode/services/verifier/verifier.go index 84b5aeee..b77f3123 100644 --- a/supernode/services/verifier/verifier.go +++ b/supernode/services/verifier/verifier.go @@ -4,14 +4,13 @@ import ( "context" "fmt" "net" - "strings" - "github.com/LumeraProtocol/supernode/pkg/lumera" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" "github.com/LumeraProtocol/supernode/pkg/logtrace" + "github.com/LumeraProtocol/supernode/pkg/lumera" "github.com/LumeraProtocol/supernode/supernode/config" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" - sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" ) // ConfigVerifier implements ConfigVerifierService @@ -76,13 +75,7 @@ func (cv *ConfigVerifier) VerifyConfig(ctx context.Context) (*VerificationResult // Check 5: Verify supernode state is active cv.checkSupernodeState(result, supernode) - // Check 6: Check supernode port alignment with on-chain registration - cv.checkSupernodePortAlignment(result, supernode) - - // Check 7: Check host alignment with on-chain registration (warning only - may differ due to load balancer) - cv.checkHostAlignment(result, supernode) - - // Check 8: Verify all required ports are available + // Check 6: Verify all required ports are available cv.checkPortsAvailable(result) logtrace.Info(ctx, "Config verification completed", logtrace.Fields{ @@ -153,7 +146,7 @@ func (cv *ConfigVerifier) checkSupernodeExists(ctx context.Context, result *Veri func (cv *ConfigVerifier) checkP2PPortMatches(result *VerificationResult, supernode *sntypes.SuperNode) { configPort := fmt.Sprintf("%d", cv.config.P2PConfig.Port) chainPort := supernode.P2PPort - + if chainPort != "" && chainPort != configPort { result.Valid = false result.Errors = append(result.Errors, ConfigError{ @@ -181,52 +174,6 @@ func (cv *ConfigVerifier) checkSupernodeState(result *VerificationResult, supern } } -// checkSupernodePortAlignment compares supernode port with on-chain registered port (error if mismatch) -func (cv *ConfigVerifier) checkSupernodePortAlignment(result *VerificationResult, supernode *sntypes.SuperNode) { - if len(supernode.PrevIpAddresses) > 0 { - chainAddress := supernode.PrevIpAddresses[len(supernode.PrevIpAddresses)-1].Address - - // Extract port from chain address - var chainPort string - if idx := strings.LastIndex(chainAddress, ":"); idx != -1 { - chainPort = chainAddress[idx+1:] - } - - configPort := fmt.Sprintf("%d", cv.config.SupernodeConfig.Port) - if chainPort != "" && chainPort != configPort { - result.Valid = false - result.Errors = append(result.Errors, ConfigError{ - Field: "supernode_port", - Expected: chainPort, - Actual: configPort, - Message: fmt.Sprintf("Supernode port mismatch: config=%s, chain=%s", configPort, chainPort), - }) - } - } -} - -// checkHostAlignment compares host with on-chain registered host (warning only - may differ due to load balancer) -func (cv *ConfigVerifier) checkHostAlignment(result *VerificationResult, supernode *sntypes.SuperNode) { - if len(supernode.PrevIpAddresses) > 0 { - chainAddress := supernode.PrevIpAddresses[len(supernode.PrevIpAddresses)-1].Address - - // Extract host from chain address - chainHost := chainAddress - if idx := strings.LastIndex(chainAddress, ":"); idx != -1 { - chainHost = chainAddress[:idx] - } - - if chainHost != cv.config.SupernodeConfig.Host { - result.Warnings = append(result.Warnings, ConfigError{ - Field: "host", - Expected: cv.config.SupernodeConfig.Host, - Actual: chainHost, - Message: fmt.Sprintf("Host mismatch: config=%s, chain=%s", cv.config.SupernodeConfig.Host, chainHost), - }) - } - } -} - // checkPortsAvailable verifies that all required ports are available for binding func (cv *ConfigVerifier) checkPortsAvailable(result *VerificationResult) { // Check supernode port @@ -254,7 +201,7 @@ func (cv *ConfigVerifier) checkPortsAvailable(result *VerificationResult) { if gatewayPort == 0 { gatewayPort = 8092 // Default gateway port (same as gateway.DefaultGatewayPort) } - + if !cv.isPortAvailable(cv.config.SupernodeConfig.Host, gatewayPort) { result.Valid = false result.Errors = append(result.Errors, ConfigError{ @@ -268,14 +215,14 @@ func (cv *ConfigVerifier) checkPortsAvailable(result *VerificationResult) { // isPortAvailable checks if a port is available for binding func (cv *ConfigVerifier) isPortAvailable(host string, port int) bool { address := fmt.Sprintf("%s:%d", host, port) - + // Try to listen on the port listener, err := net.Listen("tcp", address) if err != nil { return false // Port is not available } - + // Close the listener immediately since we're just checking availability listener.Close() return true // Port is available -} \ No newline at end of file +} From ee0fbe1386498f87b83fbd1cc7423aa085927fac Mon Sep 17 00:00:00 2001 From: Matee Ullah Malik Date: Fri, 15 Aug 2025 23:11:23 +0500 Subject: [PATCH 4/4] Add build target for sn-manager in Makefile --- Makefile | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 13466bf4..baa29bc0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build build-release build-sncli +.PHONY: build build-release build-sncli build-sn-manager .PHONY: install-lumera setup-supernodes system-test-setup .PHONY: gen-cascade gen-supernode .PHONY: test-e2e test-unit test-integration test-system @@ -13,6 +13,11 @@ LDFLAGS = -X github.com/LumeraProtocol/supernode/supernode/cmd.Version=$(VERSION -X github.com/LumeraProtocol/supernode/supernode/cmd.GitCommit=$(GIT_COMMIT) \ -X github.com/LumeraProtocol/supernode/supernode/cmd.BuildTime=$(BUILD_TIME) +# Linker flags for sn-manager +SN_MANAGER_LDFLAGS = -X main.Version=$(VERSION) \ + -X main.GitCommit=$(GIT_COMMIT) \ + -X main.BuildTime=$(BUILD_TIME) + build: @mkdir -p release CGO_ENABLED=1 \ @@ -46,6 +51,21 @@ release/sncli: $(SNCLI_SRC) cmd/sncli/go.mod cmd/sncli/go.sum SNCLI_SRC=$(shell find cmd/sncli -name "*.go") +build-sn-manager: + @mkdir -p release + @echo "Building sn-manager..." + @cd sn-manager && \ + CGO_ENABLED=0 \ + GOOS=linux \ + GOARCH=amd64 \ + go build \ + -trimpath \ + -ldflags="-s -w $(SN_MANAGER_LDFLAGS)" \ + -o ../release/sn-manager \ + . + @chmod +x release/sn-manager + @echo "sn-manager built successfully at release/sn-manager" + test-unit: go test -v ./...