working node commands

This commit is contained in:
Tyler Sturos 2025-04-10 14:35:03 -08:00
parent a1a95d0d16
commit 7980450e2c
25 changed files with 579 additions and 545 deletions

View File

@ -13,11 +13,11 @@ import (
// autoUpdateCmd represents the command to setup automatic updates
var autoUpdateCmd = &cobra.Command{
Use: "auto-update [enable|disable]",
Use: "auto-update [enable|disable|status]",
Short: "Setup automatic update checks",
Long: `Setup or remove a cron job to automatically check for Quilibrium node updates every 10 minutes.
Long: `Setup, remove, or check status of a cron job to automatically check for Quilibrium node updates every 10 minutes.
This command will create or remove a cron entry that runs 'qclient node update' every 10 minutes
This command will create, remove, or check a cron entry that runs 'qclient node update' every 10 minutes
to check for and apply any available updates.
Example:
@ -25,18 +25,23 @@ Example:
qclient node auto-update enable
# Remove automatic update checks
qclient node auto-update disable`,
qclient node auto-update disable
# Check if automatic update is enabled
qclient node auto-update status`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 || (args[0] != "enable" && args[0] != "disable") {
fmt.Fprintf(os.Stderr, "Error: must specify either 'enable' or 'disable'\n")
if len(args) != 1 || (args[0] != "enable" && args[0] != "disable" && args[0] != "status") {
fmt.Fprintf(os.Stderr, "Error: must specify 'enable', 'disable', or 'status'\n")
cmd.Help()
return
}
if args[0] == "enable" {
setupCronJob()
} else {
} else if args[0] == "disable" {
removeCronJob()
} else if args[0] == "status" {
checkAutoUpdateStatus()
}
},
}
@ -137,39 +142,19 @@ func setupUnixCron(qclientPath string) {
}
// Check if our update command is already in crontab
if strings.Contains(currentCrontab, "### qclient managed cron tasks") &&
strings.Contains(currentCrontab, "#### node auto-update") {
if strings.Contains(currentCrontab, "### qclient-auto-update") {
fmt.Fprintf(os.Stdout, "Automatic update check is already configured in crontab\n")
return
}
// Add new cron entry with indicators
var newCrontab string
// If qclient section exists but node auto-update doesn't, we need to add it
if strings.Contains(currentCrontab, "### qclient managed cron tasks") &&
strings.Contains(currentCrontab, "### end qclient managed cron tasks") {
// Insert node auto-update section before the end marker
parts := strings.Split(currentCrontab, "### end qclient managed cron tasks")
if len(parts) >= 2 {
newCrontab = parts[0] +
"#### node auto-update\n" +
cronExpression + "\n" +
"#### end node auto-update (DO NOT DELETE)\n\n" +
"### end qclient managed cron tasks" + parts[1]
}
} else {
// Add the entire section with markers
newCrontab = currentCrontab
if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
newCrontab += "\n"
}
newCrontab += "\n### qclient managed cron tasks\n" +
"#### node auto-update\n" +
cronExpression + "\n" +
"#### end node auto-update (DO NOT DELETE)\n\n" +
"### end qclient managed cron tasks (DO NOT DELETE)\n"
newCrontab := currentCrontab
if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
newCrontab += "\n"
}
newCrontab += "### qclient-auto-update\n" +
cronExpression + "\n" +
"### end-qclient-auto-update\n"
// Write to temporary file
tempFile, err := os.CreateTemp("", "qclient-crontab")
@ -216,46 +201,25 @@ func removeUnixCron() {
currentCrontab := string(checkOutput)
// No crontab or doesn't contain our section
if currentCrontab == "" ||
!strings.Contains(currentCrontab, "### qclient managed cron tasks") ||
!strings.Contains(currentCrontab, "#### node auto-update") {
if currentCrontab == "" || !strings.Contains(currentCrontab, "### qclient-auto-update") {
fmt.Fprintf(os.Stdout, "No auto-update job found in crontab\n")
return
}
// Remove our section
startMarker := "### qclient-auto-update"
endMarker := "### end-qclient-auto-update"
startIdx := strings.Index(currentCrontab, startMarker)
endIdx := strings.Index(currentCrontab, endMarker)
var newCrontab string
// If only node auto-update section exists, remove the whole qclient section
if !strings.Contains(currentCrontab, "#### end node auto-update") ||
!strings.Contains(strings.Split(currentCrontab, "### end qclient managed cron tasks")[0], "####") ||
strings.Count(strings.Split(currentCrontab, "### end qclient managed cron tasks")[0], "####") <= 2 {
// Remove entire qclient section
parts := strings.Split(currentCrontab, "### qclient managed cron tasks")
if len(parts) >= 2 {
endParts := strings.Split(parts[1], "### end qclient managed cron tasks")
if len(endParts) >= 2 {
newCrontab = parts[0] + endParts[1]
} else {
newCrontab = parts[0]
}
} else {
newCrontab = currentCrontab
}
if startIdx >= 0 && endIdx >= 0 {
endIdx += len(endMarker)
// Remove the section including markers
newCrontab = currentCrontab[:startIdx] + currentCrontab[endIdx:]
} else {
// Remove just the auto-update section
startMarker := "#### node auto-update"
endMarker := "#### end node auto-update (DO NOT DELETE)"
startIdx := strings.Index(currentCrontab, startMarker)
endIdx := strings.Index(currentCrontab, endMarker)
if startIdx >= 0 && endIdx >= 0 {
endIdx += len(endMarker)
// Remove the section including markers
newCrontab = currentCrontab[:startIdx] + currentCrontab[endIdx:]
} else {
newCrontab = currentCrontab
}
newCrontab = currentCrontab
}
// Clean up any leftover double newlines
@ -287,3 +251,46 @@ func removeUnixCron() {
fmt.Fprintf(os.Stdout, "Successfully removed auto-update cron job\n")
}
func checkAutoUpdateStatus() {
if !isCrontabInstalled() {
fmt.Fprintf(os.Stderr, "Error: crontab command not found\n")
fmt.Fprintf(os.Stdout, "Auto-update is not enabled (crontab not installed)\n")
return
}
// Check existing crontab
checkCmd := exec.Command("crontab", "-l")
checkOutput, err := checkCmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stdout, "Auto-update is not enabled (no crontab found)\n")
return
}
currentCrontab := string(checkOutput)
if strings.Contains(currentCrontab, "### qclient-auto-update") {
// Extract the cron expression
startMarker := "### qclient-auto-update"
endMarker := "### end-qclient-auto-update"
startIdx := strings.Index(currentCrontab, startMarker) + len(startMarker)
endIdx := strings.Index(currentCrontab, endMarker)
if startIdx >= 0 && endIdx >= 0 {
cronSection := currentCrontab[startIdx:endIdx]
cronLines := strings.Split(strings.TrimSpace(cronSection), "\n")
if len(cronLines) > 0 {
fmt.Fprintf(os.Stdout, "Auto-update is enabled.")
fmt.Fprintf(os.Stdout, "The installed schedule is: %s\n", strings.TrimSpace(cronLines[0]))
} else {
fmt.Fprintf(os.Stdout, "Auto-update is enabled\n")
}
} else {
fmt.Fprintf(os.Stdout, "Auto-update is enabled\n")
}
} else {
fmt.Fprintf(os.Stdout, "Auto-update is not enabled\n")
}
}

10
client/cmd/node/clean.go Normal file
View File

@ -0,0 +1,10 @@
// TODO: Implement a clean command that will remove old versions of the node,
// signatures, and logs
// qlient node clean
// qlient node clean --all (all logs, old node binaries and signatures)
// qlient node clean --logs (remove all logs)
// qlient node clean --node (remove all old node binary versions, including signatures)
// to remove even current versions, they must run 'qclient node uninstall'
package node

View File

@ -0,0 +1,43 @@
package config
// TODO: Implement a command to assign a config's rewards to a config's rewards address
// Should be able to assign to a specific address or by name of a config directory
// e.g. qclient node config assign-rewards my-config my-throwaway-config
// this finds the address from my-throwaway-config and assigns it to my-config
// qlient node config assign-rewards my-config --reset will reset the rewards address to the default address
// or
// qclient node config assign-rewards my-config --address 0x1234567890abcdef1234567890abcdef1234567890abcdef
//
// If no address is provided, the command will prompt for an address
// the prompt should prompt clearly the user for each part, asking if
// the user wants to use one of the config files as the source of the address
// or if they want to enter a new address manually
// if no configs are found locally, it should prompt the user to create a new config
// or import one
// i.e. Which config do you want to re-assign rewards?
// (1) my-config
// (2) my-other-config
// (3) my-throwaway-config
//
// Enter the number of the config you want to re-assign rewards: 1
// Finding address from my-config...
// Successfully found address 0x1234567890abcdef1234567890abcdef1234567890abcdef
// Which reward address do you want to assign to my-config?
// (1) my-other-config
// (2) my-throwaway-config
// (3) Enter a new address manually
// (4) Reset to default address
//
// Enter the number of the reward address you want to assign: 2
//
// Finding address from my-throwaway-config...
// Successfully found address 0x1234567890abcdef1234567890abcdef1234567890abcdef
// Assigning rewards from my-config to my-throwaway-config
// Successfully assigned rewards.
//
// Summary:
// Node address: 0x1234567890abcdef1234567890abcdef1234567890abcdef
// Rewards address: 0x1234567890abcdef1234567890abcdef1234567890abcdef
//
// Successfully updated my-config

View File

@ -1,11 +1,21 @@
package config
import (
"fmt"
"os"
"os/user"
"path/filepath"
"github.com/spf13/cobra"
clientNode "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
var (
NodeUser *user.User
ConfigDirs string
NodeConfigToRun string
)
// ConfigCmd represents the node config command
var ConfigCmd = &cobra.Command{
Use: "config",
@ -20,8 +30,15 @@ This command provides utilities for configuring your Quilibrium node, such as:
`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Check if the config directory exists
if utils.FileExists(clientNode.ConfigDirs) {
utils.ValidateAndCreateDir(clientNode.ConfigDirs, clientNode.NodeUser)
user, err := utils.GetCurrentSudoUser()
if err != nil {
fmt.Println("Error getting current user:", err)
os.Exit(1)
}
ConfigDirs = filepath.Join(user.HomeDir, ".quilibrium", "configs")
NodeConfigToRun = filepath.Join(user.HomeDir, ".quilibrium", "configs", "default")
if utils.FileExists(ConfigDirs) {
utils.ValidateAndCreateDir(ConfigDirs, user)
}
},
Run: func(cmd *cobra.Command, args []string) {
@ -29,36 +46,8 @@ This command provides utilities for configuring your Quilibrium node, such as:
},
}
// GetConfigSubCommands returns all the configuration subcommands
func GetConfigSubCommands() []*cobra.Command {
// This function can be used by other packages to get all config subcommands
// It can be expanded in the future to return additional subcommands as they are added
return []*cobra.Command{
importCmd,
setCmd,
setDefaultCmd,
createDefaultCmd,
}
}
func init() {
// Add subcommands to the config command
// These subcommands will register themselves in their own init() functions
// Register the config command to the node command
clientNode.NodeCmd.AddCommand(ConfigCmd)
}
// GetRootConfigCmd returns the root config command
// This is a utility function that can be used by other packages to get the config command
// and its subcommands
func GetRootConfigCmd() *cobra.Command {
// Return the config command that is defined in import.go
return ConfigCmd
}
// RegisterConfigCommand registers a subcommand to the root config command
func RegisterConfigCommand(cmd *cobra.Command) {
ConfigCmd.AddCommand(cmd)
ConfigCmd.AddCommand(importCmd)
ConfigCmd.AddCommand(SwitchConfigCmd)
ConfigCmd.AddCommand(createCmd)
}

View File

@ -1,91 +0,0 @@
package config
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
clientNode "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
var createDefaultCmd = &cobra.Command{
Use: "create-default [name]",
Short: "Create a default configuration",
Long: `Create a default configuration by running quilibrium-node with --peer-id and
--config flags, then symlink it to the default configuration.
Example:
qclient node config create-default
qclient node config create-default myconfig
The first example will create a new configuration at ConfigsDir/default-config and symlink it to ConfigsDir/default.
The second example will create a new configuration at ConfigsDir/myconfig and symlink it to ConfigsDir/default.`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// Check if running as root
if os.Geteuid() != 0 {
fmt.Println("Error: This command requires root privileges.")
fmt.Println("Please run with sudo or as root.")
os.Exit(1)
}
// Determine the config name (default-config or user-provided)
configName := "default-config"
if len(args) > 0 {
configName = args[0]
// Check if trying to use "default" which is reserved for the symlink
if configName == "default" {
fmt.Println("Error: 'default' is reserved for the symlink. Please use a different name.")
os.Exit(1)
}
}
// Construct the configuration directory path
configDir := filepath.Join(clientNode.ConfigDirs, configName)
// Create directory if it doesn't exist
if err := os.MkdirAll(configDir, 0755); err != nil {
fmt.Printf("Failed to create config directory: %s\n", err)
os.Exit(1)
}
// Run quilibrium-node command to generate config
// this is a hack to get the config files to be created
// TODO: fix this
// to fix this, we need to extrapolate the Node's config and keystore construction
// and reuse it for this command
nodeCmd := exec.Command("sudo", "quilibrium-node", "--peer-id", "--config", configDir)
nodeCmd.Stdout = os.Stdout
nodeCmd.Stderr = os.Stderr
fmt.Printf("Running quilibrium-node to generate configuration in %s...\n", configName)
if err := nodeCmd.Run(); err != nil {
fmt.Printf("Failed to run quilibrium-node: %s\n", err)
os.Exit(1)
}
// Check if the configuration was created successfully
if !HasConfigFiles(configDir) {
fmt.Printf("Failed to generate configuration files in: %s\n", configDir)
os.Exit(1)
}
// Construct the default directory path
defaultDir := filepath.Join(clientNode.ConfigDirs, "default")
// Create the symlink
if err := utils.CreateSymlink(configDir, defaultDir); err != nil {
fmt.Printf("Failed to create symlink: %s\n", err)
os.Exit(1)
}
fmt.Printf("Successfully created %s configuration and symlinked to default\n", configName)
},
}
func init() {
ConfigCmd.AddCommand(createDefaultCmd)
}

View File

@ -0,0 +1,107 @@
package config
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
var setDefault bool
var createCmd = &cobra.Command{
Use: "create [name]",
Short: "Create a default configuration file set for a node",
Long: fmt.Sprintf(`Create a default configuration by running quilibrium-node with --peer-id and --config flags, then symlink it to the default configuration.
Example:
qclient node config create
qclient node config create myconfig
qclient node config create myconfig --default
The first example will create a new configuration at %s/default-config.
The second example will create a new configuration at %s/myconfig.
The third example will create a new configuration at %s/myconfig and symlink it so the node will use it.`,
ConfigDirs, ConfigDirs, ConfigDirs),
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// Determine the config name (default-config or user-provided)
var configName string
if len(args) > 0 {
configName = args[0]
} else {
// Prompt for a name if none provided
fmt.Print("Enter a name for the configuration (cannot be 'default'): ")
fmt.Scanln(&configName)
if configName == "" {
configName = "default-config"
}
}
// Check if trying to use "default" which is reserved for the symlink
if configName == "default" {
fmt.Println("Error: 'default' is reserved for the symlink. Please use a different name.")
os.Exit(1)
}
// Construct the configuration directory path
configDir := filepath.Join(ConfigDirs, configName)
// Create directory if it doesn't exist
if err := utils.ValidateAndCreateDir(configDir, NodeUser); err != nil {
fmt.Printf("Failed to create config directory: %s\n", err)
os.Exit(1)
}
// Run quilibrium-node command to generate config
// this is a hack to get the config files to be created
// TODO: fix this
// to fix this, we need to extrapolate the Node's config and keystore construction
// and reuse it for this command
nodeCmd := exec.Command("quilibrium-node", "--peer-id", "--config", configDir)
nodeCmd.Stdout = os.Stdout
nodeCmd.Stderr = os.Stderr
fmt.Printf("Running quilibrium-node to generate configuration in %s...\n", configName)
if err := nodeCmd.Run(); err != nil {
// Check if the error is due to the command not being found
if exitErr, ok := err.(*exec.ExitError); ok {
fmt.Printf("Error running quilibrium-node: %s\n", exitErr)
} else if _, ok := err.(*exec.Error); ok {
fmt.Printf("Error: quilibrium-node command not found. Please ensure it is installed and in your PATH.\n")
} else {
fmt.Printf("Error: %s\n", err)
}
os.RemoveAll(configDir)
os.Exit(1)
}
// Check if the configuration was created successfully
if !HasConfigFiles(configDir) {
fmt.Printf("Failed to generate configuration files in: %s\n", configDir)
os.Exit(1)
}
if setDefault {
// Create the symlink
if err := utils.CreateSymlink(configDir, NodeConfigToRun); err != nil {
fmt.Printf("Failed to create symlink: %s\n", err)
os.Exit(1)
}
fmt.Printf("Successfully created %s configuration and symlinked to default\n", configName)
} else {
fmt.Printf("Successfully created %s configuration\n", configName)
}
fmt.Println("The keys.yml file will only contain 'null:' until the node is started.")
},
}
func init() {
createCmd.Flags().BoolVarP(&setDefault, "default", "d", false, "Select this config as the default")
}

View File

@ -6,7 +6,6 @@ import (
"path/filepath"
"github.com/spf13/cobra"
clientNode "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
@ -36,8 +35,8 @@ This will copy config.yml and keys.yml from /path/to/source to /home/quilibrium/
}
// Create target directory in the standard location
targetDir := filepath.Join(clientNode.ConfigDirs, name)
if err := os.MkdirAll(targetDir, 0755); err != nil {
targetDir := filepath.Join(ConfigDirs, name)
if err := utils.ValidateAndCreateDir(targetDir, NodeUser); err != nil {
fmt.Printf("Failed to create target directory: %s\n", err)
os.Exit(1)
}
@ -63,8 +62,3 @@ This will copy config.yml and keys.yml from /path/to/source to /home/quilibrium/
fmt.Printf("Successfully imported config files to %s\n", targetDir)
},
}
func init() {
// Add the import command to the config command
ConfigCmd.AddCommand(importCmd)
}

View File

@ -1,56 +0,0 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
clientNode "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
var setDefaultCmd = &cobra.Command{
Use: "set-default [name]",
Short: "Set a configuration as the default",
Long: `Set a configuration as the default by creating a symlink.
Example:
qclient node config set-default mynode
This will symlink /home/quilibrium/configs/mynode to /home/quilibrium/configs/default`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
// Construct the source directory path
sourceDir := filepath.Join(clientNode.ConfigDirs, name)
// Check if source directory exists
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
fmt.Printf("Config directory does not exist: %s\n", sourceDir)
os.Exit(1)
}
// Check if the source directory has both config.yml and keys.yml files
if !HasConfigFiles(sourceDir) {
fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
os.Exit(1)
}
// Construct the default directory path
defaultDir := filepath.Join(clientNode.ConfigDirs, "default")
// Create the symlink
if err := utils.CreateSymlink(sourceDir, defaultDir); err != nil {
fmt.Printf("Failed to create symlink: %s\n", err)
os.Exit(1)
}
fmt.Printf("Successfully set %s as the default configuration\n", name)
},
}
func init() {
ConfigCmd.AddCommand(setDefaultCmd)
}

View File

@ -6,7 +6,6 @@ import (
"path/filepath"
"github.com/spf13/cobra"
clientNode "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
"source.quilibrium.com/quilibrium/monorepo/node/config"
)
@ -25,7 +24,7 @@ Example:
value := args[2]
// Construct the config directory path
configDir := filepath.Join(clientNode.ConfigDirs, name)
configDir := filepath.Join(ConfigDirs, name)
configFile := filepath.Join(configDir, "config.yml")
// Check if config directory exists
@ -41,7 +40,7 @@ Example:
}
// Load the config
cfg, err := config.LoadConfig(configFile, "", false)
cfg, err := config.LoadConfig(configDir, "", false)
if err != nil {
fmt.Printf("Failed to load config: %s\n", err)
os.Exit(1)
@ -73,7 +72,7 @@ Example:
}
// Save the updated config
if err := config.SaveConfig(configFile, cfg); err != nil {
if err := config.SaveConfig(configDir, cfg); err != nil {
fmt.Printf("Failed to save config: %s\n", err)
os.Exit(1)
}

View File

@ -0,0 +1,82 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
var SwitchConfigCmd = &cobra.Command{
Use: "switch [name]",
Short: "Switch the config to be run by the node",
Long: fmt.Sprintf(`Switch the configuration to be run by the node by creating a symlink.
Example:
qclient node config switch mynode
This will symlink %s/mynode to %s`, ConfigDirs, NodeConfigToRun),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var name string
if len(args) > 0 {
name = args[0]
} else {
// List available configurations
configs, err := ListConfigurations()
if err != nil {
fmt.Printf("Error listing configurations: %s\n", err)
os.Exit(1)
}
if len(configs) == 0 {
fmt.Println("No configurations found. Create one with 'qclient node config create'")
os.Exit(1)
}
fmt.Println("Available configurations:")
for i, config := range configs {
fmt.Printf("%d. %s\n", i+1, config)
}
// Prompt for choice
var choice int
fmt.Print("Enter the number of the configuration to set as default: ")
_, err = fmt.Scanf("%d", &choice)
if err != nil || choice < 1 || choice > len(configs) {
fmt.Println("Invalid choice. Please enter a valid number.")
os.Exit(1)
}
name = configs[choice-1]
}
// Construct the source directory path
sourceDir := filepath.Join(ConfigDirs, name)
// Check if source directory exists
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
fmt.Printf("Config directory does not exist: %s\n", sourceDir)
os.Exit(1)
}
// Check if the source directory has both config.yml and keys.yml files
if !HasConfigFiles(sourceDir) {
fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
os.Exit(1)
}
// Construct the default directory path
defaultDir := filepath.Join(ConfigDirs, "default")
// Create the symlink
if err := utils.CreateSymlink(sourceDir, defaultDir); err != nil {
fmt.Printf("Failed to create symlink: %s\n", err)
os.Exit(1)
}
fmt.Printf("Successfully set %s as the default configuration\n", name)
},
}

View File

@ -24,3 +24,19 @@ func HasConfigFiles(dirPath string) bool {
return configExists && keysExists
}
func ListConfigurations() ([]string, error) {
files, err := os.ReadDir(ConfigDirs)
if err != nil {
return nil, err
}
configs := make([]string, 0)
for _, file := range files {
if file.IsDir() {
configs = append(configs, file.Name())
}
}
return configs, nil
}

View File

@ -3,7 +3,6 @@ package node
import (
"fmt"
"os"
"os/user"
"path/filepath"
"github.com/spf13/cobra"
@ -14,19 +13,87 @@ import (
var installCmd = &cobra.Command{
Use: "install [version]",
Short: "Install Quilibrium node",
Long: `Install Quilibrium node binary.
If no version is specified, the latest version will be installed.
Long: `Install Quilibrium node binary and create a service to run it.
## Service Management
You can start, stop, and restart the service with:
qclient node service start
qclient node service stop
qclient node service restart
qclient node service status
qclient node service enable
qclient node service disable
### Mac OS Notes
On Mac OS, the service is managed by launchd (installed by default)
### Linux Notes
On Linux, the service is managed by systemd (will be installed by this command).
## Config Management
A config directory will be created in the user's home directory at .quilibrium/configs/.
To create a default config, run the following command:
qclient node config create-default [name-for-config]
or, you can import existing configs one at a timefrom a directory:
qclient node config import [name-for-config] /path/to/src/config/dir/
You can then select the config to use when running the node with:
qclient node set-default [name-for-config]
## Binary Management
Binaries and signatures are installed to /var/quilibrium/bin/node/[version]/
You can update the node binary with:
qclient node update [version]
### Auto-update
You can enable auto-update with:
qclient node auto-update enable
You can disable auto-update with:
qclient node auto-update disable
You can check the auto-update status with:
qclient node auto-update status
## Log Management
Logging uses system logging with logrotate installed by default.
Logs are installed to /var/log/quilibrium
The logrotate config is installed to /etc/logrotate.d/quilibrium
You can view the logs with:
qclient node logs [version]
When installing with this command, if no version is specified, the latest version will be installed.
Sudo is required to install the node to install the node binary, logging,systemd (on Linux), and create the config directory.
Examples:
# Install the latest version
qclient node install
# Install a specific version
qclient node install 2.1.0
# Install the latest version
qclient node install
# Install without creating a dedicated user
qclient node install --no-create-user`,
# Install a specific version
qclient node install 2.1.0
`,
Args: cobra.RangeArgs(0, 1),
Run: func(cmd *cobra.Command, args []string) {
// Get system information and validate
@ -38,12 +105,34 @@ Examples:
if !utils.IsSudo() {
fmt.Println("This command must be run with sudo: sudo qclient node install")
fmt.Println("Sudo is required to install the node binary, logging, systemd (on Linux) service, and create the config directory.")
os.Exit(1)
}
// Determine version to install
version := determineVersion(args)
// Download and install the node
if version == "latest" {
latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
return
}
version = latestVersion
fmt.Fprintf(os.Stdout, "Found latest version: %s\n", version)
}
// do a pre-flight check to ensure the release file exists
fileName := fmt.Sprintf("%s-%s-%s-%s", utils.ReleaseTypeNode, version, osType, arch)
url := fmt.Sprintf("%s/%s", utils.BaseReleaseURL, fileName)
if !utils.DoesRemoteFileExist(url) {
fmt.Printf("The release file %s does not exist on the release server\n", fileName)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "Installing Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
// Install the node
@ -64,18 +153,6 @@ func installNode(version string) {
return
}
// Download and install the node
if version == "latest" {
latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
return
}
version = latestVersion
fmt.Fprintf(os.Stdout, "Found latest version: %s\n", version)
}
if IsExistingNodeVersion(version) {
fmt.Fprintf(os.Stderr, "Error: Node version %s already exists\n", version)
os.Exit(1)
@ -93,13 +170,9 @@ func installNode(version string) {
// installByVersion installs a specific version of the Quilibrium node
func installByVersion(version string) error {
// Create version-specific directory
user, err := user.Lookup(utils.DefaultNodeUser)
if err != nil {
os.Exit(1)
}
versionDir := filepath.Join(utils.NodeDataPath, version)
if err := utils.ValidateAndCreateDir(versionDir, user); err != nil {
if err := utils.ValidateAndCreateDir(versionDir, NodeUser); err != nil {
return fmt.Errorf("failed to create version directory: %w", err)
}

View File

@ -0,0 +1,3 @@
package log
// TODO: Implement a log subcommand to clean the logs

View File

@ -0,0 +1,3 @@
package log
// TODO: Implement a command to view and manage the logs

View File

@ -4,8 +4,10 @@ import (
"fmt"
"os"
"os/user"
"path/filepath"
"github.com/spf13/cobra"
clientNodeConfig "source.quilibrium.com/quilibrium/monorepo/client/cmd/node/config"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/config"
)
@ -18,23 +20,17 @@ var (
defaultSymlinkPath = "/usr/local/bin/quilibrium-node"
logPath = "/var/log/quilibrium"
// Default user to run the node
nodeUser = "quilibrium"
ServiceName = "quilibrium-node"
ConfigDirs = "/home/quilibrium/configs"
NodeConfigToRun = "/home/quilibrium/configs/default"
// Default config file name
defaultConfigFileName = "node.yaml"
osType string
arch string
OsType string
Arch string
configDirectory string
NodeConfig *config.Config
NodeUser *user.User
ConfigDirs string
NodeConfigToRun string
)
// NodeCmd represents the node command
@ -43,24 +39,26 @@ var NodeCmd = &cobra.Command{
Short: "Quilibrium node commands",
Long: `Run Quilibrium node commands.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Store reference to parent's PersistentPreRun to call it first
parent := cmd.Parent()
if parent != nil && parent.PersistentPreRun != nil {
parent.PersistentPreRun(parent, args)
}
// Then run the node-specific initialization
var userLookup *user.User
var err error
userLookup, err = user.Lookup(nodeUser)
userLookup, err = utils.GetCurrentSudoUser()
if err != nil {
if !utils.IsSudo() {
fmt.Printf("need to be sudo to install quilibrium user, retry with sudo: %s\n", err)
os.Exit(1)
}
userLookup, err = InstallQuilibriumUser()
if err != nil {
fmt.Printf("error installing quilibrium user: %s\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "Error getting current user: %v\n", err)
os.Exit(1)
}
NodeUser = userLookup
ConfigDirs = filepath.Join(userLookup.HomeDir, ".quilibrium", "configs")
NodeConfigToRun = filepath.Join(userLookup.HomeDir, ".quilibrium", "configs", "default")
},
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
@ -69,14 +67,16 @@ func init() {
// Add subcommands
NodeCmd.AddCommand(installCmd)
NodeCmd.AddCommand(clientNodeConfig.ConfigCmd)
NodeCmd.AddCommand(updateNodeCmd)
NodeCmd.AddCommand(nodeServiceCmd)
localOsType, localArch, err := utils.GetSystemInfo()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return
}
osType = localOsType
arch = localArch
OsType = localOsType
Arch = localArch
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"text/template"
@ -71,12 +70,12 @@ func installService() {
return
}
fmt.Fprintf(os.Stdout, "Installing Quilibrium node service for %s...\n", osType)
fmt.Fprintf(os.Stdout, "Installing Quilibrium node service for %s...\n", OsType)
if osType == "darwin" {
if OsType == "darwin" {
// launchctl is already installed on macOS by default, so no need to check for it
installMacOSService()
} else if osType == "linux" {
} else if OsType == "linux" {
// systemd is not installed on linux by default, so we need to check for it
if !CheckForSystemd() {
// install systemd if not found
@ -87,7 +86,7 @@ func installService() {
return
}
} else {
fmt.Fprintf(os.Stderr, "Error: Unsupported operating system: %s\n", osType)
fmt.Fprintf(os.Stderr, "Error: Unsupported operating system: %s\n", OsType)
return
}
@ -120,7 +119,7 @@ func startService() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command
cmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", ServiceName))
if err := cmd.Run(); err != nil {
@ -146,7 +145,7 @@ func stopService() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command
cmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", ServiceName))
if err := cmd.Run(); err != nil {
@ -172,7 +171,7 @@ func restartService() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command - stop then start
stopCmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", ServiceName))
if err := stopCmd.Run(); err != nil {
@ -204,7 +203,7 @@ func reloadService() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command - unload then load
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
unloadCmd := exec.Command("sudo", "launchctl", "unload", plistPath)
@ -239,7 +238,7 @@ func checkServiceStatus() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command
cmd := exec.Command("sudo", "launchctl", "list", fmt.Sprintf("com.quilibrium.%s", ServiceName))
cmd.Stdout = os.Stdout
@ -265,7 +264,7 @@ func enableService() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command - load with -w flag to enable at boot
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
cmd := exec.Command("sudo", "launchctl", "load", "-w", plistPath)
@ -292,7 +291,7 @@ func disableService() {
return
}
if osType == "darwin" {
if OsType == "darwin" {
// MacOS launchd command - unload with -w flag to disable at boot
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
cmd := exec.Command("sudo", "launchctl", "unload", "-w", plistPath)
@ -314,14 +313,14 @@ func disableService() {
func createService() {
// Create systemd service file
if osType == "linux" {
if OsType == "linux" {
if err := createSystemdServiceFile(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: Failed to create systemd service file: %v\n", err)
}
} else if osType == "darwin" {
} else if OsType == "darwin" {
installMacOSService()
} else {
fmt.Fprintf(os.Stderr, "Warning: Background service file creation not supported on %s\n", osType)
fmt.Fprintf(os.Stderr, "Warning: Background service file creation not supported on %s\n", OsType)
return
}
}
@ -337,11 +336,6 @@ func createSystemdServiceFile() error {
return fmt.Errorf("failed to get sudo privileges: %w", err)
}
userLookup, err := user.Lookup(nodeUser)
if err != nil {
return fmt.Errorf("failed to lookup user: %w", err)
}
// Create environment file content
envContent := `# Quilibrium Node Environment`
@ -352,7 +346,7 @@ func createSystemdServiceFile() error {
}
// Set ownership of environment file
chownCmd := utils.ChownPath(envPath, userLookup, false)
chownCmd := utils.ChownPath(envPath, NodeUser, false)
if chownCmd != nil {
return fmt.Errorf("failed to set environment file ownership: %w", chownCmd)
}

View File

@ -83,7 +83,7 @@ func setupLogRotation() error {
func finishInstallation(version string) {
setOwnership()
normalizedBinaryName := "node-" + version + "-" + osType + "-" + arch
normalizedBinaryName := "node-" + version + "-" + OsType + "-" + Arch
// Finish installation
nodeBinaryPath := filepath.Join(utils.NodeDataPath, version, normalizedBinaryName)
@ -118,16 +118,33 @@ func finishInstallation(version string) {
// printSuccessMessage prints a success message after installation
func printSuccessMessage(version string) {
fmt.Fprintf(os.Stdout, "\nSuccessfully installed Quilibrium node %s\n", version)
fmt.Fprintf(os.Stdout, "Binary download directory: %s\n", utils.NodeDataPath+"/"+version)
fmt.Fprintf(os.Stdout, "Binary download directory: %s\n", filepath.Join(utils.NodeDataPath, version))
fmt.Fprintf(os.Stdout, "Binary symlinked to %s\n", defaultSymlinkPath)
fmt.Fprintf(os.Stdout, "Log directory: %s\n", logPath)
fmt.Fprintf(os.Stdout, "Environment file: /etc/default/quilibrium-node\n")
fmt.Fprintf(os.Stdout, "Service file: /etc/systemd/system/quilibrium-node.service\n")
fmt.Fprintf(os.Stdout, "\nTo start the node, you can run:\n")
fmt.Fprintf(os.Stdout, " %s --config %s/config/config.yaml\n",
fmt.Fprintf(os.Stdout, "\nConfiguration:\n")
fmt.Fprintf(os.Stdout, " To create a new configuration:\n")
fmt.Fprintf(os.Stdout, " qclient node config create [name] --default\n")
fmt.Fprintf(os.Stdout, " quilibrium-node --peer-id %s/default-config\n", ConfigDirs)
fmt.Fprintf(os.Stdout, "\n To use an existing configuration:\n")
fmt.Fprintf(os.Stdout, " cp -r /path/to/your/existing/config %s/default-config\n", ConfigDirs)
fmt.Fprintf(os.Stdout, " # Or modify the service file to point to your existing config:\n")
fmt.Fprintf(os.Stdout, " sudo nano /etc/systemd/system/quilibrium-node.service\n")
fmt.Fprintf(os.Stdout, " # Then reload systemd:\n")
fmt.Fprintf(os.Stdout, " sudo systemctl daemon-reload\n")
fmt.Fprintf(os.Stdout, "\nTo select a configuration:\n")
fmt.Fprintf(os.Stdout, " qclient node config switch <config-name>\n")
fmt.Fprintf(os.Stdout, " # Or use the --default flag when creating a config to automatically select it:\n")
fmt.Fprintf(os.Stdout, " qclient node config create --default\n")
fmt.Fprintf(os.Stdout, "\nTo manually start the node (must create a config first), you can run:\n")
fmt.Fprintf(os.Stdout, " %s --config %s/myconfig/\n",
ServiceName, ConfigDirs)
fmt.Fprintf(os.Stdout, " # Or use systemd service:\n")
fmt.Fprintf(os.Stdout, " # Or use systemd service using the default config:\n")
fmt.Fprintf(os.Stdout, " sudo systemctl start quilibrium-node\n")
fmt.Fprintf(os.Stdout, "\nFor more options, run:\n")

View File

@ -0,0 +1,10 @@
package node
// TODO: Implement a command to uninstall the current and previous versions
// of the node and all files, not including user data
// this should NEVER delete the user data, only the node files and logs
// prompt the user for confirmation or a --force flag to skip the confirmation
// qlient node uninstall
// qlient node uninstall --force (skip the confirmation)

View File

@ -30,7 +30,7 @@ Examples:
// Determine version to install
version := determineVersion(args)
fmt.Fprintf(os.Stdout, "Updating Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
fmt.Fprintf(os.Stdout, "Updating Quilibrium node for %s-%s, version: %s\n", OsType, Arch, version)
// Update the node
updateNode(version)
@ -69,7 +69,7 @@ func updateNode(version string) {
err := utils.DownloadRelease(utils.ReleaseTypeNode, versionWithoutV)
if err != nil {
fmt.Fprintf(os.Stderr, "Error downloading version %s: %v\n", version, err)
fmt.Fprintf(os.Stderr, "The specified version %s does not exist for %s-%s\n", version, osType, arch)
fmt.Fprintf(os.Stderr, "The specified version %s does not exist for %s-%s\n", version, OsType, Arch)
// Clean up the created directories since installation failed
os.RemoveAll(versionDataDir)
return

View File

@ -1,112 +0,0 @@
package node
import (
"fmt"
"os"
"os/exec"
"os/user"
"strings"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
)
// createNodeUser creates a dedicated user for running the node
func createNodeUser(nodeUser string) error {
fmt.Fprintf(os.Stdout, "Creating dedicated user '%s' for running the node...\n", nodeUser)
// Check for sudo privileges
if err := utils.CheckAndRequestSudo("Creating system user requires root privileges"); err != nil {
return fmt.Errorf("failed to get sudo privileges: %w", err)
}
if osType == "linux" {
return createLinuxNodeUser(nodeUser)
} else if osType == "darwin" {
return createMacNodeUser(nodeUser)
} else {
return fmt.Errorf("User creation not supported on %s", osType)
}
}
func createLinuxNodeUser(username string) error {
var cmd *exec.Cmd
// Check if user already exists
_, err := user.Lookup(username)
if err == nil {
fmt.Fprintf(os.Stdout, "User '%s' already exists\n", username)
return nil
}
// Create user on Linux
cmd = exec.Command("useradd", "-r", "-s", "/bin/false", "-m", "-c", "Quilibrium Node User", username)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to create user: %w", err)
}
fmt.Fprintf(os.Stdout, "User '%s' created successfully\n", username)
return nil
}
func createMacNodeUser(username string) error {
var cmd *exec.Cmd
// Check if user already exists
_, err := user.Lookup(username)
if err == nil {
fmt.Fprintf(os.Stdout, "User '%s' already exists\n", username)
return nil
}
// Create user on macOS
// Get next available user ID
uidCmd := exec.Command("dscl", ".", "-list", "/Users", "UniqueID")
uidOutput, err := uidCmd.Output()
if err != nil {
return fmt.Errorf("failed to get user IDs: %v", err)
}
// Find the highest UID and add 1
var maxUID int = 500 // Start with a reasonable system UID
for _, line := range strings.Split(string(uidOutput), "\n") {
fields := strings.Fields(line)
if len(fields) >= 2 {
var uid int
fmt.Sscanf(fields[len(fields)-1], "%d", &uid)
if uid > maxUID && uid < 65000 { // Avoid system UIDs
maxUID = uid
}
}
}
nextUID := maxUID + 1
// Create the user
cmd = exec.Command("dscl", ".", "-create", "/Users/"+nodeUser)
if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to create user: %v", err)
}
// Set the user's properties
commands := [][]string{
{"-create", "/Users/" + nodeUser, "UniqueID", fmt.Sprintf("%d", nextUID)},
{"-create", "/Users/" + nodeUser, "PrimaryGroupID", "20"}, // staff group
{"-create", "/Users/" + nodeUser, "UserShell", "/bin/false"},
{"-create", "/Users/" + nodeUser, "NFSHomeDirectory", "/var/empty"},
{"-create", "/Users/" + nodeUser, "RealName", "Quilibrium Node User"},
}
for _, args := range commands {
cmd = exec.Command("dscl", append([]string{"."}, args...)...)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to set user property %s: %v", args[1], err)
}
}
// Disable the user account
cmd = exec.Command("dscl", ".", "-create", "/Users/"+nodeUser, "Password", "*")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to disable user account: %v", err)
}
return nil
}

View File

@ -2,10 +2,7 @@ package node
import (
"encoding/hex"
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"github.com/pkg/errors"
@ -55,26 +52,3 @@ func CheckForSystemd() bool {
_, err := exec.LookPath("systemctl")
return err == nil
}
func checkForQuilibriumUser() (*user.User, error) {
user, err := user.Lookup(nodeUser)
if err != nil {
return nil, err
}
return user, nil
}
func InstallQuilibriumUser() (*user.User, error) {
var user *user.User
var err error
user, err = checkForQuilibriumUser()
if err != nil {
if err := createNodeUser(nodeUser); err != nil {
fmt.Fprintf(os.Stderr, "Error creating user: %v\n", err)
os.Exit(1)
}
user, err = checkForQuilibriumUser()
}
return user, err
}

View File

@ -23,6 +23,7 @@ import (
var configDirectory string
var signatureCheck bool = true
var byPassSignatureCheck bool = false
var NodeConfig *config.Config
var simulateFail bool
var DryRun bool = false
@ -52,7 +53,7 @@ It provides commands for installing, updating, and managing Quilibrium nodes.`,
os.Exit(1)
}
if !clientConfig.SignatureCheck {
if !clientConfig.SignatureCheck || byPassSignatureCheck {
signatureCheck = false
}
@ -90,7 +91,7 @@ It provides commands for installing, updating, and managing Quilibrium nodes.`,
reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n')
response = strings.TrimSpace(strings.ToLower(response))
response = strings.ToLower(strings.TrimSpace(response))
if response != "y" && response != "yes" {
fmt.Println("Exiting due to missing digest file")
@ -199,27 +200,14 @@ func init() {
"bypass signature check (not recommended for binaries) (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
)
rootCmd.PersistentFlags().BoolP(
rootCmd.PersistentFlags().BoolVarP(
&byPassSignatureCheck,
"yes",
"y",
false,
"auto-approve and bypass signature check (sets signature-check=false)",
)
// Store original PersistentPreRun
originalPersistentPreRun := rootCmd.PersistentPreRun
// Override PersistentPreRun to check for -y flag first
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
// Check if -y flag is set and update signatureCheck
if yes, _ := cmd.Flags().GetBool("yes"); yes {
signatureCheck = false
}
// Call the original PersistentPreRun
originalPersistentPreRun(cmd, args)
}
// Add the node command
rootCmd.AddCommand(node.NodeCmd)
rootCmd.AddCommand(clientConfig.ConfigCmd)

View File

@ -15,6 +15,15 @@ var BaseReleaseURL = "https://releases.quilibrium.com"
// DownloadRelease downloads a specific release file
func DownloadRelease(releaseType ReleaseType, version string) error {
fileName := fmt.Sprintf("%s-%s-%s-%s", releaseType, version, osType, arch)
fmt.Printf("Getting binary %s...\n", fileName)
fmt.Println("Will save to", filepath.Join(BinaryPath, string(releaseType), version))
url := fmt.Sprintf("%s/%s", BaseReleaseURL, fileName)
if !DoesRemoteFileExist(url) {
fmt.Printf("the release file %s does not exist on the release server\n", fileName)
os.Exit(1)
}
return DownloadReleaseFile(releaseType, fileName, version, true)
}
@ -25,6 +34,7 @@ func GetLatestVersion(releaseType ReleaseType) (string, error) {
if releaseType == ReleaseTypeQClient {
releaseURL = fmt.Sprintf("%s/qclient-release", BaseReleaseURL)
}
resp, err := http.Get(releaseURL)
if err != nil {
return "", fmt.Errorf("failed to fetch latest version: %w", err)
@ -57,7 +67,7 @@ func DownloadReleaseFile(releaseType ReleaseType, fileName string, version strin
os.MkdirAll(destDir, 0755)
destPath := filepath.Join(destDir, fileName)
fmt.Printf("Downloading %s to %s\n", url, destPath)
fmt.Printf("Downloading %s...", fileName)
resp, err := http.Get(url)
if err != nil {
@ -83,7 +93,7 @@ func DownloadReleaseFile(releaseType ReleaseType, fileName string, version strin
if err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Downloaded %s to %s\n", fileName, destPath)
fmt.Print(" done\n")
return nil
}
@ -91,7 +101,8 @@ func DownloadReleaseFile(releaseType ReleaseType, fileName string, version strin
func DownloadReleaseSignatures(releaseType ReleaseType, version string) error {
var files []string
baseName := fmt.Sprintf("%s-%s-%s-%s", releaseType, version, osType, arch)
fmt.Printf("Downloading signatures for %s\n", baseName)
fmt.Printf("Searching for signatures for %s from %s\n", baseName, BaseReleaseURL)
fmt.Println("Will save to", filepath.Join(BinaryPath, string(releaseType), version))
// Add digest file URL
files = append(files, baseName+".dgst")
@ -103,19 +114,11 @@ func DownloadReleaseSignatures(releaseType ReleaseType, version string) error {
sigFile := fmt.Sprintf("%s.dgst.sig.%d", baseName, num)
remoteURL := fmt.Sprintf("%s/%s", BaseReleaseURL, sigFile)
// Make a HEAD request to check if the file exists
resp, err := http.Head(remoteURL)
if err != nil || resp.StatusCode != http.StatusOK {
// Skip this file if it doesn't exist on the server
fmt.Printf("Signature file %s not found on server, skipping\n", sigFile)
if !DoesRemoteFileExist(remoteURL) {
continue
} else {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
fmt.Printf("Signature file %s found on server, adding to download list\n", sigFile)
files = append(files, fmt.Sprintf("%s.dgst.sig.%d", baseName, num))
}
fmt.Printf("Found signature file %s\n", sigFile)
files = append(files, fmt.Sprintf("%s.dgst.sig.%d", baseName, num))
}
if len(files) == 0 {
@ -192,3 +195,14 @@ func DownloadAllReleaseFiles(releaseType ReleaseType, fileNames []string, instal
}
return true
}
func DoesRemoteFileExist(url string) bool {
resp, err := http.Head(url)
if err != nil || resp.StatusCode != http.StatusOK {
return false
}
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
return true
}

View File

@ -1,34 +0,0 @@
package utils
func OfferSignatureDownload(ClientConfig *ClientConfig, version string) {
// varDataPath := filepath.Join(ClientConfig.DataDir, version)
// digestPath := filepath.Join(varDataPath, StandardizedQClientFileName+".dgst")
// if FileExists(digestPath) {
// // Fall back to checking next to executable
// digestPath = ClientDataPath + "/" + version + "/" + StandardizedQClientFileName + ".dgst"
// if !FileExists(digestPath) {
// fmt.Println("Signature file not found. Would you like to download it? (y/n)")
// reader := bufio.NewReader(os.Stdin)
// response, _ := reader.ReadString('\n')
// response = strings.TrimSpace(strings.ToLower(response))
// if response == "y" || response == "yes" {
// fmt.Println("Downloading signature files...")
// if version == "" {
// fmt.Println("Could not determine version from executable name")
// return fmt.Errorf("could not determine version from executable name")
// }
// // Download signature files
// if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeQClient, version); err != nil {
// fmt.Printf("Error downloading signature files: %v\n", err)
// return fmt.Errorf("failed to download signature files: %v", err)
// }
// fmt.Println("Successfully downloaded signature files")
// } else {
// fmt.Println("Continuing without signature verification")
// signatureCheck = false
// }
// }
// }
}

View File

@ -8,10 +8,14 @@ type ClientConfig struct {
type NodeConfig struct {
ClientConfig
DataDir string
User string
RewardsAddress string `yaml:"rewardsAddress"`
AutoUpdateInterval string `yaml:"autoUpdateInterval"`
}
const (
DefaultAutoUpdateInterval = "*/10 * * * *"
)
type ReleaseType string
const (