Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 \
Expand Down Expand Up @@ -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 ./...

Expand Down
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,15 @@ 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

#### GET /api/v1/status
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:
Expand Down Expand Up @@ -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:
Expand All @@ -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.

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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
```
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
143 changes: 143 additions & 0 deletions sn-manager/README.md
Original file line number Diff line number Diff line change
@@ -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 `<YOUR_USER>` with your Linux username:**

```bash
sudo tee /etc/systemd/system/sn-manager.service <<EOF
[Unit]
Description=Lumera SuperNode Manager
After=network-online.target

[Service]
User=<YOUR_USER>
ExecStart=/usr/local/bin/sn-manager start
Restart=on-failure
RestartSec=10
LimitNOFILE=65536
Environment="HOME=/home/<YOUR_USER>"
WorkingDirectory=/home/<YOUR_USER>

[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 <version>` - Download version
- `use <version>` - 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
```

48 changes: 29 additions & 19 deletions sn-manager/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.")
Expand Down
18 changes: 17 additions & 1 deletion sn-manager/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,30 @@ 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)
defer ticker.Stop()

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...")

Expand Down
Loading