mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-03-02 23:07:33 +08:00
working node commands
This commit is contained in:
parent
a1a95d0d16
commit
7980450e2c
@ -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
10
client/cmd/node/clean.go
Normal 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
|
||||
43
client/cmd/node/config/assign-rewards.go
Normal file
43
client/cmd/node/config/assign-rewards.go
Normal 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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
107
client/cmd/node/config/create.go
Normal file
107
client/cmd/node/config/create.go
Normal 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")
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
82
client/cmd/node/config/switch.go
Normal file
82
client/cmd/node/config/switch.go
Normal 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)
|
||||
},
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
3
client/cmd/node/log/clean.go
Normal file
3
client/cmd/node/log/clean.go
Normal file
@ -0,0 +1,3 @@
|
||||
package log
|
||||
|
||||
// TODO: Implement a log subcommand to clean the logs
|
||||
3
client/cmd/node/log/log.go
Normal file
3
client/cmd/node/log/log.go
Normal file
@ -0,0 +1,3 @@
|
||||
package log
|
||||
|
||||
// TODO: Implement a command to view and manage the logs
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
10
client/cmd/node/uninstall.go
Normal file
10
client/cmd/node/uninstall.go
Normal 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)
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@ -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 (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user