mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-23 11:27:25 +08:00
update install and node commands
This commit is contained in:
parent
27ed7a775d
commit
df47bcc2d2
58
client/cmd/config/signatureCheck.go
Normal file
58
client/cmd/config/signatureCheck.go
Normal file
@ -0,0 +1,58 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var signatureCheckCmd = &cobra.Command{
|
||||
Use: "signature-check [true|false]",
|
||||
Short: "Set signature check setting",
|
||||
Long: `Set the signature check setting in the client configuration.
|
||||
When disabled, signature verification will be bypassed (not recommended for production use).
|
||||
Use 'true' to enable or 'false' to disable. If no argument is provided, it toggles the current setting.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config, err := utils.LoadClientConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
// Set the signature check based on the provided argument
|
||||
value := strings.ToLower(args[0])
|
||||
if value == "true" {
|
||||
config.SignatureCheck = true
|
||||
} else if value == "false" {
|
||||
config.SignatureCheck = false
|
||||
} else {
|
||||
// If the value is not true or false, print error message and exit
|
||||
fmt.Printf("Error: Invalid value '%s'. Please use 'true' or 'false'.\n", value)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// Toggle the signature check setting if no arguments are provided
|
||||
config.SignatureCheck = !config.SignatureCheck
|
||||
}
|
||||
|
||||
// Save the updated config
|
||||
if err := utils.SaveClientConfig(config); err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
status := "enabled"
|
||||
if !config.SignatureCheck {
|
||||
status = "disabled"
|
||||
}
|
||||
fmt.Printf("Signature check has been set to %s and will be persisted across future commands unless reset.\n", status)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigCmd.AddCommand(signatureCheckCmd)
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var toggleSignatureCheckCmd = &cobra.Command{
|
||||
Use: "toggle-signature-check",
|
||||
Short: "Toggle signature check setting",
|
||||
Long: `Toggle the signature check setting in the client configuration.
|
||||
When disabled, signature verification will be bypassed (not recommended for production use).`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config, err := utils.LoadClientConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Toggle the signature check setting
|
||||
config.SignatureCheck = !config.SignatureCheck
|
||||
|
||||
// Save the updated config
|
||||
if err := utils.SaveClientConfig(config); err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
status := "enabled"
|
||||
if !config.SignatureCheck {
|
||||
status = "disabled"
|
||||
}
|
||||
fmt.Printf("Signature check has been %s\n", status)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigCmd.AddCommand(toggleSignatureCheckCmd)
|
||||
}
|
||||
@ -3,25 +3,19 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var symlinkPath string
|
||||
var symlinkPath = "/usr/local/bin/qclient"
|
||||
|
||||
var linkCmd = &cobra.Command{
|
||||
Use: "link",
|
||||
Short: "Create a symlink to qclient in PATH",
|
||||
Long: fmt.Sprintf(`Create a symlink to the qclient binary in a suitable directory in your PATH.
|
||||
This allows you to run qclient from anywhere without specifying the full path.
|
||||
Long: `Create a symlink to the qclient binary in the directory /usr/local/bin/.
|
||||
|
||||
By default it will create the symlink in the current directory /usr/local/bin.
|
||||
You can also specify a custom directory using the --path flag.
|
||||
|
||||
Example: qclient link --path /usr/local/bin`),
|
||||
Example: qclient link`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Get the path to the current executable
|
||||
execPath, err := os.Executable()
|
||||
@ -29,102 +23,24 @@ Example: qclient link --path /usr/local/bin`),
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// Determine the target directory and path for the symlink
|
||||
targetDir, targetPath, err := determineSymlinkLocation()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If operation was cancelled by the user
|
||||
if targetDir == "" && targetPath == "" {
|
||||
return nil
|
||||
IsSudo := utils.IsSudo()
|
||||
if IsSudo {
|
||||
fmt.Println("Running as sudo, creating symlink at /usr/local/bin/qclient")
|
||||
} else {
|
||||
fmt.Println("Cannot create symlink at /usr/local/bin/qclient, please run this command with sudo")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create the symlink (handles existing symlinks)
|
||||
if err := utils.CreateSymlink(execPath, targetPath); err != nil {
|
||||
if err := utils.CreateSymlink(execPath, symlinkPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Symlink created at %s\n", targetPath)
|
||||
fmt.Printf("Symlink created at %s\n", symlinkPath)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// determineSymlinkLocation finds the appropriate location for the symlink
|
||||
// Returns the target directory, the full path for the symlink, and any error
|
||||
func determineSymlinkLocation() (string, string, error) {
|
||||
// If user provided a custom path
|
||||
if symlinkPath != "" {
|
||||
return validateUserProvidedPath(symlinkPath)
|
||||
}
|
||||
|
||||
// Otherwise, find a suitable directory in PATH
|
||||
return utils.DefaultSymlinkDir, utils.DefaultQClientSymlinkPath, nil
|
||||
}
|
||||
|
||||
// isDirectoryInPath checks if a directory is in the PATH environment variable
|
||||
func isDirectoryInPath(dir string) bool {
|
||||
pathEnv := os.Getenv("PATH")
|
||||
pathDirs := strings.Split(pathEnv, string(os.PathListSeparator))
|
||||
|
||||
// Normalize paths for comparison
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, pathDir := range pathDirs {
|
||||
absPathDir, err := filepath.Abs(pathDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if absDir == absPathDir {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// validateUserProvidedPath checks if the provided path is a valid directory
|
||||
func validateUserProvidedPath(path string) (string, string, error) {
|
||||
// Check if the provided path is a directory
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", fmt.Errorf("directory does not exist: %s", path)
|
||||
}
|
||||
return "", "", fmt.Errorf("error checking directory: %w", err)
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return "", "", fmt.Errorf("the specified path is not a directory: %s", path)
|
||||
}
|
||||
|
||||
// Check if the directory is in PATH
|
||||
if !isDirectoryInPath(path) {
|
||||
// Ask user for confirmation to proceed with a directory not in PATH
|
||||
fmt.Printf("Warning: The directory '%s' is not in your PATH environment variable.\n", path)
|
||||
fmt.Println("The symlink will be created, but you may not be able to run 'qclient' from anywhere.")
|
||||
fmt.Print("Do you want to continue? [y/N]: ")
|
||||
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
if strings.ToLower(response) != "y" {
|
||||
fmt.Println("Operation cancelled.")
|
||||
return "", "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Use the provided directory
|
||||
targetDir := path
|
||||
targetPath := filepath.Join(targetDir, "qclient")
|
||||
|
||||
return targetDir, targetPath, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(linkCmd)
|
||||
linkCmd.Flags().StringVar(&symlinkPath, "path", "", "Specify a custom directory for the symlink")
|
||||
}
|
||||
|
||||
141
client/cmd/node/autoUpdate.go
Normal file
141
client/cmd/node/autoUpdate.go
Normal file
@ -0,0 +1,141 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// autoUpdateCmd represents the command to setup automatic updates
|
||||
var autoUpdateCmd = &cobra.Command{
|
||||
Use: "auto-update",
|
||||
Short: "Setup automatic update checks",
|
||||
Long: `Setup a cron job to automatically check for Quilibrium node updates every 10 minutes.
|
||||
|
||||
This command will create a cron entry that runs 'qclient node update' every 10 minutes
|
||||
to check for and apply any available updates.
|
||||
|
||||
Example:
|
||||
# Setup automatic update checks
|
||||
qclient node auto-update`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
setupCronJob()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
NodeCmd.AddCommand(autoUpdateCmd)
|
||||
}
|
||||
|
||||
func setupCronJob() {
|
||||
// Get full path to qclient executable
|
||||
qclientPath, err := exec.LookPath("qclient")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: qclient executable not found in PATH: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "Please ensure qclient is properly installed and in your PATH (use 'sudo qclient link')\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Absolute path for qclient
|
||||
qclientAbsPath, err := filepath.Abs(qclientPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting absolute path for qclient: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// OS-specific setup
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
|
||||
setupUnixCron(qclientAbsPath)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: auto-update is only supported on macOS and Linux\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func isCrontabInstalled() bool {
|
||||
// Check if crontab is installed
|
||||
_, err := exec.LookPath("crontab")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func installCrontab() {
|
||||
fmt.Fprintf(os.Stdout, "Installing cron package...\n")
|
||||
// Install crontab
|
||||
installCmd := exec.Command("sudo", "apt-get", "update", "&&", "sudo", "apt-get", "install", "-y", "cron")
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing cron package: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if isCrontabInstalled() {
|
||||
fmt.Fprintf(os.Stdout, "Cron package installed\n")
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error: cron package not installed\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func setupUnixCron(qclientPath string) {
|
||||
if !isCrontabInstalled() {
|
||||
fmt.Fprintf(os.Stdout, "Crontab command not found, attempting to install cron package...\n")
|
||||
installCrontab()
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Setting up cron job...\n")
|
||||
// Create cron expression: run every 10 minutes
|
||||
cronExpression := fmt.Sprintf("*/10 * * * * %s node update > /dev/null 2>&1", qclientPath)
|
||||
|
||||
// Check existing crontab
|
||||
checkCmd := exec.Command("crontab", "-l")
|
||||
checkOutput, err := checkCmd.CombinedOutput()
|
||||
|
||||
var currentCrontab string
|
||||
if err != nil {
|
||||
// If there's no crontab yet, this is fine, start with empty crontab
|
||||
currentCrontab = ""
|
||||
} else {
|
||||
currentCrontab = string(checkOutput)
|
||||
}
|
||||
|
||||
// Check if our update command is already in crontab
|
||||
if strings.Contains(currentCrontab, "qclient node update") {
|
||||
fmt.Fprintf(os.Stdout, "Automatic update check is already configured in crontab\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Add new cron entry
|
||||
newCrontab := currentCrontab
|
||||
if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
|
||||
newCrontab += "\n"
|
||||
}
|
||||
newCrontab += cronExpression + "\n"
|
||||
|
||||
// Write to temporary file
|
||||
tempFile, err := os.CreateTemp("", "qclient-crontab")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
if _, err := tempFile.WriteString(newCrontab); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err)
|
||||
return
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Install new crontab
|
||||
installCmd := exec.Command("crontab", tempFile.Name())
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing crontab: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Successfully configured cron job to check for updates every 10 minutes\n")
|
||||
fmt.Fprintf(os.Stdout, "Added: %s\n", cronExpression)
|
||||
}
|
||||
64
client/cmd/node/config/config.go
Normal file
64
client/cmd/node/config/config.go
Normal file
@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
clientNode "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
// ConfigCmd represents the node config command
|
||||
var ConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage node configuration",
|
||||
Long: `Manage Quilibrium node configuration.
|
||||
|
||||
This command provides utilities for configuring your Quilibrium node, such as:
|
||||
- Setting configuration values
|
||||
- Setting default configuration
|
||||
- Creating default configuration
|
||||
- Importing configuration
|
||||
`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
// Check if the config directory exists
|
||||
if utils.FileExists(clientNode.ConfigDirs) {
|
||||
utils.ValidateAndCreateDir(clientNode.ConfigDirs, clientNode.NodeUser)
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
91
client/cmd/node/config/create-default.go
Normal file
91
client/cmd/node/config/create-default.go
Normal file
@ -0,0 +1,91 @@
|
||||
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)
|
||||
}
|
||||
70
client/cmd/node/config/import.go
Normal file
70
client/cmd/node/config/import.go
Normal file
@ -0,0 +1,70 @@
|
||||
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 importCmd = &cobra.Command{
|
||||
Use: "import [name] [source_directory]",
|
||||
Short: "Import config.yml and keys.yml from a source directory",
|
||||
Long: `Import config.yml and keys.yml from a source directory to the QuilibriumRoot config folder.
|
||||
|
||||
Example:
|
||||
qclient node config import mynode /path/to/source
|
||||
|
||||
This will copy config.yml and keys.yml from /path/to/source to /home/quilibrium/configs/mynode/`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
sourceDir := args[1]
|
||||
|
||||
// Check if source directory exists
|
||||
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
|
||||
fmt.Printf("Source directory does not exist: %s\n", sourceDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !HasConfigFiles(sourceDir) {
|
||||
fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create target directory in the standard location
|
||||
targetDir := filepath.Join(clientNode.ConfigDirs, name)
|
||||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||
fmt.Printf("Failed to create target directory: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Define source file paths
|
||||
sourceConfigPath := filepath.Join(sourceDir, "config.yml")
|
||||
sourceKeysPath := filepath.Join(sourceDir, "keys.yml")
|
||||
|
||||
// Copy config.yml
|
||||
targetConfigPath := filepath.Join(targetDir, "config.yml")
|
||||
if err := utils.CopyFile(sourceConfigPath, targetConfigPath); err != nil {
|
||||
fmt.Printf("Failed to copy config.yml: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Copy keys.yml
|
||||
targetKeysPath := filepath.Join(targetDir, "keys.yml")
|
||||
if err := utils.CopyFile(sourceKeysPath, targetKeysPath); err != nil {
|
||||
fmt.Printf("Failed to copy keys.yml: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully imported config files to %s\n", targetDir)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add the import command to the config command
|
||||
ConfigCmd.AddCommand(importCmd)
|
||||
}
|
||||
56
client/cmd/node/config/set-default.go
Normal file
56
client/cmd/node/config/set-default.go
Normal file
@ -0,0 +1,56 @@
|
||||
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)
|
||||
}
|
||||
87
client/cmd/node/config/set.go
Normal file
87
client/cmd/node/config/set.go
Normal file
@ -0,0 +1,87 @@
|
||||
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/node/config"
|
||||
)
|
||||
|
||||
var setCmd = &cobra.Command{
|
||||
Use: "set [name] [key] [value]",
|
||||
Short: "Set a configuration value",
|
||||
Long: `Set a configuration value in the node config.yml file.
|
||||
|
||||
Example:
|
||||
qclient node config set mynode engine.statsMultiaddr /dns/stats.quilibrium.com/tcp/443
|
||||
`,
|
||||
Args: cobra.ExactArgs(3),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
key := args[1]
|
||||
value := args[2]
|
||||
|
||||
// Construct the config directory path
|
||||
configDir := filepath.Join(clientNode.ConfigDirs, name)
|
||||
configFile := filepath.Join(configDir, "config.yml")
|
||||
|
||||
// Check if config directory exists
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
fmt.Printf("Config directory does not exist: %s\n", configDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check if config file exists
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
fmt.Printf("Config file does not exist: %s\n", configFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load the config
|
||||
cfg, err := config.LoadConfig(configFile, "", false)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to load config: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Update the config based on the key
|
||||
switch key {
|
||||
case "engine.statsMultiaddr":
|
||||
cfg.Engine.StatsMultiaddr = value
|
||||
case "p2p.listenMultiaddr":
|
||||
cfg.P2P.ListenMultiaddr = value
|
||||
case "listenGrpcMultiaddr":
|
||||
cfg.ListenGRPCMultiaddr = value
|
||||
case "listenRestMultiaddr":
|
||||
cfg.ListenRestMultiaddr = value
|
||||
case "engine.autoMergeCoins":
|
||||
if value == "true" {
|
||||
cfg.Engine.AutoMergeCoins = true
|
||||
} else if value == "false" {
|
||||
cfg.Engine.AutoMergeCoins = false
|
||||
} else {
|
||||
fmt.Printf("Invalid value for %s: must be 'true' or 'false'\n", key)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
fmt.Printf("Unsupported configuration key: %s\n", key)
|
||||
fmt.Println("Supported keys: engine.statsMultiaddr, p2p.listenMultiaddr, listenGrpcMultiaddr, listenRestMultiaddr, engine.autoMergeCoins")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Save the updated config
|
||||
if err := config.SaveConfig(configFile, cfg); err != nil {
|
||||
fmt.Printf("Failed to save config: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully updated %s to %s in %s\n", key, value, configFile)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigCmd.AddCommand(setCmd)
|
||||
}
|
||||
26
client/cmd/node/config/utils.go
Normal file
26
client/cmd/node/config/utils.go
Normal file
@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// HasConfigFiles checks if a directory contains both config.yml and keys.yml files
|
||||
func HasConfigFiles(dirPath string) bool {
|
||||
configPath := filepath.Join(dirPath, "config.yml")
|
||||
keysPath := filepath.Join(dirPath, "keys.yml")
|
||||
|
||||
// Check if both files exist
|
||||
configExists := false
|
||||
keysExists := false
|
||||
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
configExists = true
|
||||
}
|
||||
|
||||
if _, err := os.Stat(keysPath); !os.IsNotExist(err) {
|
||||
keysExists = true
|
||||
}
|
||||
|
||||
return configExists && keysExists
|
||||
}
|
||||
@ -3,6 +3,7 @@ package node
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -35,18 +36,16 @@ Examples:
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.IsSudo() {
|
||||
fmt.Println("This command must be run with sudo: sudo qclient node install")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Determine version to install
|
||||
version := determineVersion(args)
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Installing Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
|
||||
|
||||
// Check if we need to create a dedicated user (opt-out)
|
||||
|
||||
if err := createNodeUser(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating user: %v\n", err)
|
||||
fmt.Fprintf(os.Stdout, "Continuing with installation as current user...\n")
|
||||
}
|
||||
|
||||
// Install the node
|
||||
installNode(version)
|
||||
},
|
||||
@ -60,17 +59,11 @@ func init() {
|
||||
// installNode installs the Quilibrium node
|
||||
func installNode(version string) {
|
||||
// Create installation directory
|
||||
if err := utils.ValidateAndCreateDir(installPath); err != nil {
|
||||
if err := utils.ValidateAndCreateDir(utils.NodeDataPath, NodeUser); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create data directory
|
||||
if err := utils.ValidateAndCreateDir(dataPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Download and install the node
|
||||
if version == "latest" {
|
||||
latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
|
||||
@ -80,7 +73,12 @@ func installNode(version string) {
|
||||
}
|
||||
|
||||
version = latestVersion
|
||||
fmt.Fprintf(os.Stdout, "Installing latest version: %s\n", version)
|
||||
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)
|
||||
}
|
||||
|
||||
if err := installByVersion(version); err != nil {
|
||||
@ -88,16 +86,20 @@ func installNode(version string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Finish installation
|
||||
nodeBinaryPath := filepath.Join(installPath, string(utils.ReleaseTypeNode), version)
|
||||
finishInstallation(nodeBinaryPath, version)
|
||||
createService()
|
||||
|
||||
finishInstallation(version)
|
||||
}
|
||||
|
||||
// installByVersion installs a specific version of the Quilibrium node
|
||||
func installByVersion(version string) error {
|
||||
// Create version-specific directory
|
||||
versionDir := filepath.Join(installPath, version)
|
||||
if err := utils.ValidateAndCreateDir(versionDir); err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("failed to create version directory: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package node
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
@ -15,19 +16,15 @@ var (
|
||||
|
||||
// Default symlink path for the node binary
|
||||
defaultSymlinkPath = "/usr/local/bin/quilibrium-node"
|
||||
|
||||
// Default installation directory base path
|
||||
installPath = "/opt/quilibrium"
|
||||
|
||||
// Default data directory paths
|
||||
dataPath = "/var/lib/quilibrium"
|
||||
|
||||
logPath = "/var/log/quilibrium"
|
||||
logPath = "/var/log/quilibrium"
|
||||
|
||||
// Default user to run the node
|
||||
nodeUser = "quilibrium"
|
||||
|
||||
serviceName = "quilibrium-node"
|
||||
ServiceName = "quilibrium-node"
|
||||
|
||||
ConfigDirs = "/home/quilibrium/configs"
|
||||
NodeConfigToRun = "/home/quilibrium/configs/default"
|
||||
|
||||
// Default config file name
|
||||
defaultConfigFileName = "node.yaml"
|
||||
@ -37,8 +34,7 @@ var (
|
||||
|
||||
configDirectory string
|
||||
NodeConfig *config.Config
|
||||
publicRPC bool = false
|
||||
LightNode bool = false
|
||||
NodeUser *user.User
|
||||
)
|
||||
|
||||
// NodeCmd represents the node command
|
||||
@ -46,6 +42,19 @@ var NodeCmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Quilibrium node commands",
|
||||
Long: `Run Quilibrium node commands.`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
var userLookup *user.User
|
||||
var err error
|
||||
userLookup, err = user.Lookup(nodeUser)
|
||||
if err != nil {
|
||||
userLookup, err = InstallQuilibriumUser()
|
||||
if err != nil {
|
||||
fmt.Printf("error installing quilibrium user: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
NodeUser = userLookup
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// These commands handle their own configuration
|
||||
_, err := os.Stat(configDirectory)
|
||||
@ -59,22 +68,11 @@ var NodeCmd = &cobra.Command{
|
||||
fmt.Printf("invalid config directory: %s\n", configDirectory)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if publicRPC {
|
||||
fmt.Println("Public RPC enabled, using light node")
|
||||
LightNode = true
|
||||
}
|
||||
|
||||
if !LightNode && NodeConfig.ListenGRPCMultiaddr == "" {
|
||||
fmt.Println("No ListenGRPCMultiaddr found in config, using light node")
|
||||
LightNode = true
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
NodeCmd.PersistentFlags().StringVar(&configDirectory, "config", ".config", "config directory (default is .config/)")
|
||||
NodeCmd.PersistentFlags().BoolVar(&publicRPC, "public-rpc", false, "Use public RPC for node operations")
|
||||
|
||||
// Add subcommands
|
||||
NodeCmd.AddCommand(installCmd)
|
||||
|
||||
@ -4,6 +4,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
@ -62,6 +65,7 @@ Examples:
|
||||
|
||||
// installService installs the appropriate service configuration for the current OS
|
||||
func installService() {
|
||||
|
||||
if err := utils.CheckAndRequestSudo("Installing service requires root privileges"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
@ -70,8 +74,14 @@ func installService() {
|
||||
fmt.Fprintf(os.Stdout, "Installing Quilibrium node service for %s...\n", osType)
|
||||
|
||||
if osType == "darwin" {
|
||||
// launchctl is already installed on macOS by default, so no need to check for it
|
||||
installMacOSService()
|
||||
} 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
|
||||
installSystemd()
|
||||
}
|
||||
if err := createSystemdServiceFile(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating systemd service file: %v\n", err)
|
||||
return
|
||||
@ -84,6 +94,25 @@ func installService() {
|
||||
fmt.Fprintf(os.Stdout, "Quilibrium node service installed successfully\n")
|
||||
}
|
||||
|
||||
func installSystemd() {
|
||||
fmt.Fprintf(os.Stdout, "Installing systemd...\n")
|
||||
updateCmd := exec.Command("sudo", "apt-get", "update")
|
||||
updateCmd.Stdout = nil
|
||||
updateCmd.Stderr = nil
|
||||
if err := updateCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error updating package lists: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
installCmd := exec.Command("sudo", "apt-get", "install", "-y", "systemd")
|
||||
installCmd.Stdout = nil
|
||||
installCmd.Stderr = nil
|
||||
if err := installCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error installing systemd: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// startService starts the Quilibrium node service
|
||||
func startService() {
|
||||
if err := utils.CheckAndRequestSudo("Starting service requires root privileges"); err != nil {
|
||||
@ -93,14 +122,14 @@ func startService() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command
|
||||
cmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", serviceName))
|
||||
cmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "start", serviceName)
|
||||
cmd := exec.Command("sudo", "systemctl", "start", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
|
||||
return
|
||||
@ -119,14 +148,14 @@ func stopService() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command
|
||||
cmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", serviceName))
|
||||
cmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "stop", serviceName)
|
||||
cmd := exec.Command("sudo", "systemctl", "stop", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
|
||||
return
|
||||
@ -145,20 +174,20 @@ func restartService() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command - stop then start
|
||||
stopCmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", serviceName))
|
||||
stopCmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := stopCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
startCmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", serviceName))
|
||||
startCmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
if err := startCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "restart", serviceName)
|
||||
cmd := exec.Command("sudo", "systemctl", "restart", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err)
|
||||
return
|
||||
@ -177,7 +206,7 @@ func reloadService() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command - unload then load
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", serviceName)
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
|
||||
unloadCmd := exec.Command("sudo", "launchctl", "unload", plistPath)
|
||||
if err := unloadCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error unloading service: %v\n", err)
|
||||
@ -212,7 +241,7 @@ func checkServiceStatus() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command
|
||||
cmd := exec.Command("sudo", "launchctl", "list", fmt.Sprintf("com.quilibrium.%s", serviceName))
|
||||
cmd := exec.Command("sudo", "launchctl", "list", fmt.Sprintf("com.quilibrium.%s", ServiceName))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
@ -220,7 +249,7 @@ func checkServiceStatus() {
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "status", serviceName)
|
||||
cmd := exec.Command("sudo", "systemctl", "status", ServiceName)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
@ -238,7 +267,7 @@ func enableService() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command - load with -w flag to enable at boot
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", serviceName)
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
|
||||
cmd := exec.Command("sudo", "launchctl", "load", "-w", plistPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error enabling service: %v\n", err)
|
||||
@ -246,7 +275,7 @@ func enableService() {
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "enable", serviceName)
|
||||
cmd := exec.Command("sudo", "systemctl", "enable", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error enabling service: %v\n", err)
|
||||
return
|
||||
@ -265,7 +294,7 @@ func disableService() {
|
||||
|
||||
if osType == "darwin" {
|
||||
// MacOS launchd command - unload with -w flag to disable at boot
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", serviceName)
|
||||
plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", ServiceName)
|
||||
cmd := exec.Command("sudo", "launchctl", "unload", "-w", plistPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error disabling service: %v\n", err)
|
||||
@ -273,7 +302,7 @@ func disableService() {
|
||||
}
|
||||
} else {
|
||||
// Linux systemd command
|
||||
cmd := exec.Command("sudo", "systemctl", "disable", serviceName)
|
||||
cmd := exec.Command("sudo", "systemctl", "disable", ServiceName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error disabling service: %v\n", err)
|
||||
return
|
||||
@ -282,3 +311,189 @@ func disableService() {
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Disabled Quilibrium node service from starting on boot\n")
|
||||
}
|
||||
|
||||
func createService() {
|
||||
// Create systemd service file
|
||||
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" {
|
||||
installMacOSService()
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Background service file creation not supported on %s\n", osType)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// createSystemdServiceFile creates the systemd service file with environment file support
|
||||
func createSystemdServiceFile() error {
|
||||
if !CheckForSystemd() {
|
||||
installSystemd()
|
||||
}
|
||||
|
||||
// Check if we need sudo privileges
|
||||
if err := utils.CheckAndRequestSudo("Creating systemd service file requires root privileges"); err != nil {
|
||||
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`
|
||||
|
||||
// Write environment file
|
||||
envPath := filepath.Join(utils.RootQuilibriumPath, "quilibrium.env")
|
||||
if err := os.WriteFile(envPath, []byte(envContent), 0640); err != nil {
|
||||
return fmt.Errorf("failed to create environment file: %w", err)
|
||||
}
|
||||
|
||||
// Set ownership of environment file
|
||||
chownCmd := utils.ChownPath(envPath, userLookup, false)
|
||||
if chownCmd != nil {
|
||||
return fmt.Errorf("failed to set environment file ownership: %w", chownCmd)
|
||||
}
|
||||
|
||||
// Create systemd service file content
|
||||
serviceContent := fmt.Sprintf(`[Unit]
|
||||
Description=Quilibrium Node Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=quilibrium
|
||||
EnvironmentFile=/opt/quilibrium/config/quilibrium.env
|
||||
ExecStart=/usr/local/bin/quilibrium-node --config %s
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, ConfigDirs+"/default")
|
||||
|
||||
// Write service file
|
||||
servicePath := "/etc/systemd/system/quilibrium-node.service"
|
||||
if err := utils.WriteFileAuto(servicePath, serviceContent); err != nil {
|
||||
return fmt.Errorf("failed to create service file: %w", err)
|
||||
}
|
||||
|
||||
// Reload systemd daemon
|
||||
reloadCmd := exec.Command("sudo", "systemctl", "daemon-reload")
|
||||
if err := reloadCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to reload systemd daemon: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Created systemd service file at %s\n", servicePath)
|
||||
fmt.Fprintf(os.Stdout, "Created environment file at %s\n", envPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installMacOSService installs a launchd service on macOS
|
||||
func installMacOSService() {
|
||||
fmt.Println("Installing launchd service for Quilibrium node...")
|
||||
|
||||
// Create plist file content
|
||||
plistTemplate := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>{{.Label}}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/quilibrium-node</string>
|
||||
<string>--config</string>
|
||||
<string>/opt/quilibrium/config/</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>QUILIBRIUM_DATA_DIR</key>
|
||||
<string>{{.DataPath}}</string>
|
||||
<key>QUILIBRIUM_LOG_LEVEL</key>
|
||||
<string>info</string>
|
||||
<key>QUILIBRIUM_LISTEN_GRPC_MULTIADDR</key>
|
||||
<string>/ip4/127.0.0.1/tcp/8337</string>
|
||||
<key>QUILIBRIUM_LISTEN_REST_MULTIADDR</key>
|
||||
<string>/ip4/127.0.0.1/tcp/8338</string>
|
||||
<key>QUILIBRIUM_STATS_MULTIADDR</key>
|
||||
<string>/dns/stats.quilibrium.com/tcp/443</string>
|
||||
<key>QUILIBRIUM_NETWORK_ID</key>
|
||||
<string>0</string>
|
||||
<key>QUILIBRIUM_DEBUG</key>
|
||||
<string>false</string>
|
||||
<key>QUILIBRIUM_SIGNATURE_CHECK</key>
|
||||
<string>true</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{.LogPath}}/node.err</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{.LogPath}}/node.log</string>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
||||
// Prepare template data
|
||||
data := struct {
|
||||
Label string
|
||||
DataPath string
|
||||
ServiceName string
|
||||
LogPath string
|
||||
}{
|
||||
Label: fmt.Sprintf("com.quilibrium.node"),
|
||||
DataPath: utils.NodeDataPath,
|
||||
ServiceName: "node",
|
||||
LogPath: logPath,
|
||||
}
|
||||
|
||||
// Parse and execute template
|
||||
tmpl, err := template.New("plist").Parse(plistTemplate)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating plist template: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine plist file path
|
||||
var plistPath = fmt.Sprintf("/Library/LaunchDaemons/%s.plist", data.Label)
|
||||
|
||||
// Write plist file
|
||||
file, err := os.Create(plistPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating plist file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := tmpl.Execute(file, data); err != nil {
|
||||
fmt.Printf("Error writing plist file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set correct permissions
|
||||
chownCmd := exec.Command("chown", "root:wheel", plistPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
fmt.Printf("Warning: Failed to change ownership of plist file: %v\n", err)
|
||||
}
|
||||
|
||||
// Load the service
|
||||
var loadCmd = exec.Command("launchctl", "load", "-w", plistPath)
|
||||
|
||||
if err := loadCmd.Run(); err != nil {
|
||||
fmt.Printf("Error loading service: %v\n", err)
|
||||
fmt.Println("You may need to load the service manually.")
|
||||
}
|
||||
|
||||
fmt.Printf("Launchd service installed successfully as %s\n", plistPath)
|
||||
fmt.Println("\nTo start the service:")
|
||||
fmt.Printf(" sudo launchctl start %s\n", data.Label)
|
||||
fmt.Println("\nTo stop the service:")
|
||||
fmt.Printf(" sudo launchctl stop %s\n", data.Label)
|
||||
fmt.Println("\nTo view service logs:")
|
||||
fmt.Printf(" cat %s/%s.log\n", data.LogPath, data.ServiceName)
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
@ -21,122 +18,22 @@ func determineVersion(args []string) string {
|
||||
}
|
||||
|
||||
// confirmPaths asks the user to confirm the installation and data paths
|
||||
func confirmPaths(installPath, dataPath string) bool {
|
||||
fmt.Print("Do you want to continue with these paths? [Y/n]: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
// func confirmPaths(installPath, dataPath string) bool {
|
||||
// fmt.Print("Do you want to continue with these paths? [Y/n]: ")
|
||||
// reader := bufio.NewReader(os.Stdin)
|
||||
// response, _ := reader.ReadString('\n')
|
||||
// response = strings.TrimSpace(strings.ToLower(response))
|
||||
|
||||
return response == "" || response == "y" || response == "yes"
|
||||
}
|
||||
|
||||
// createNodeUser creates a dedicated user for running the node
|
||||
func createNodeUser() 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)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if osType == "linux" {
|
||||
// Check if user already exists
|
||||
checkCmd := exec.Command("id", nodeUser)
|
||||
if checkCmd.Run() == nil {
|
||||
fmt.Fprintf(os.Stdout, "User '%s' already exists\n", nodeUser)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create user on Linux
|
||||
cmd = exec.Command("useradd", "-r", "-s", "/bin/false", "-m", "-c", "Quilibrium Node User", nodeUser)
|
||||
} else if osType == "darwin" {
|
||||
// Check if user already exists on macOS
|
||||
checkCmd := exec.Command("dscl", ".", "-read", "/Users/"+nodeUser)
|
||||
if checkCmd.Run() == nil {
|
||||
fmt.Fprintf(os.Stdout, "User '%s' already exists\n", nodeUser)
|
||||
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
|
||||
} else {
|
||||
return fmt.Errorf("user creation not supported on %s", osType)
|
||||
}
|
||||
|
||||
// Run the command
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to create user: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "User '%s' created successfully\n", nodeUser)
|
||||
return nil
|
||||
}
|
||||
// return response == "" || response == "y" || response == "yes"
|
||||
// }
|
||||
|
||||
// setOwnership sets the ownership of directories to the node user
|
||||
func setOwnership() {
|
||||
fmt.Fprintf(os.Stdout, "Setting ownership of %s and %s to %s...\n", installPath, dataPath, nodeUser)
|
||||
|
||||
// Change ownership of installation directory
|
||||
chownCmd := exec.Command("chown", "-R", nodeUser+":"+nodeUser, installPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", installPath, err)
|
||||
}
|
||||
|
||||
// Change ownership of data directory
|
||||
chownCmd = exec.Command("chown", "-R", nodeUser+":"+nodeUser, dataPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", dataPath, err)
|
||||
err := utils.ChownPath(utils.NodeDataPath, NodeUser, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", utils.NodeDataPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,22 +56,22 @@ func setupLogRotation() error {
|
||||
postrotate
|
||||
systemctl reload quilibrium-node >/dev/null 2>&1 || true
|
||||
endscript
|
||||
}`, logPath, nodeUser, nodeUser)
|
||||
}`, logPath, NodeUser.Username, NodeUser.Username)
|
||||
|
||||
// Write the configuration file
|
||||
configPath := "/etc/logrotate.d/quilibrium-node"
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
|
||||
if err := utils.WriteFile(configPath, configContent); err != nil {
|
||||
return fmt.Errorf("failed to create logrotate configuration: %w", err)
|
||||
}
|
||||
|
||||
// Create log directory with proper permissions
|
||||
if err := os.MkdirAll(logPath, 0750); err != nil {
|
||||
if err := utils.ValidateAndCreateDir(logPath, NodeUser); err != nil {
|
||||
return fmt.Errorf("failed to create log directory: %w", err)
|
||||
}
|
||||
|
||||
// Set ownership of log directory
|
||||
chownCmd := exec.Command("chown", nodeUser+":"+nodeUser, logPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
err := utils.ChownPath(logPath, NodeUser, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set log directory ownership: %w", err)
|
||||
}
|
||||
|
||||
@ -183,14 +80,17 @@ func setupLogRotation() error {
|
||||
}
|
||||
|
||||
// finishInstallation completes the installation process by making the binary executable and creating a symlink
|
||||
func finishInstallation(nodeBinaryPath string, version string) {
|
||||
|
||||
func finishInstallation(version string) {
|
||||
setOwnership()
|
||||
|
||||
normalizedBinaryName := "node-" + version + "-" + osType + "-" + arch
|
||||
|
||||
// Finish installation
|
||||
nodeBinaryPath := filepath.Join(utils.NodeDataPath, version, normalizedBinaryName)
|
||||
fmt.Printf("Making binary executable: %s\n", nodeBinaryPath)
|
||||
// Make the binary executable
|
||||
if err := os.Chmod(nodeBinaryPath, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error making binary executable: %v\n", err)
|
||||
return
|
||||
if err := utils.ChmodPath(nodeBinaryPath, 0755, "executable"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to make binary executable: %v\n", err)
|
||||
}
|
||||
|
||||
// Check if we need sudo privileges for creating symlink in system directory
|
||||
@ -211,18 +111,6 @@ func finishInstallation(nodeBinaryPath string, version string) {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to set up log rotation: %v\n", err)
|
||||
}
|
||||
|
||||
// Create systemd service file
|
||||
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" {
|
||||
installMacOSService()
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Background service file creation not supported on %s\n", osType)
|
||||
return
|
||||
}
|
||||
|
||||
// Print success message
|
||||
printSuccessMessage(version)
|
||||
}
|
||||
@ -230,8 +118,7 @@ func finishInstallation(nodeBinaryPath string, 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, "Installation directory: %s\n", installPath)
|
||||
fmt.Fprintf(os.Stdout, "Data directory: %s\n", dataPath)
|
||||
fmt.Fprintf(os.Stdout, "Binary download directory: %s\n", 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")
|
||||
@ -239,173 +126,10 @@ func printSuccessMessage(version string) {
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\nTo start the node, you can run:\n")
|
||||
fmt.Fprintf(os.Stdout, " %s --config %s/config/config.yaml\n",
|
||||
defaultSymlinkPath, dataPath)
|
||||
ServiceName, ConfigDirs)
|
||||
fmt.Fprintf(os.Stdout, " # Or use systemd service:\n")
|
||||
fmt.Fprintf(os.Stdout, " sudo systemctl start quilibrium-node\n")
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\nFor more options, run:\n")
|
||||
fmt.Fprintf(os.Stdout, " quilibrium-node --help\n")
|
||||
}
|
||||
|
||||
// createSystemdServiceFile creates the systemd service file with environment file support
|
||||
func createSystemdServiceFile() error {
|
||||
// Check if we need sudo privileges
|
||||
if err := utils.CheckAndRequestSudo("Creating systemd service file requires root privileges"); err != nil {
|
||||
return fmt.Errorf("failed to get sudo privileges: %w", err)
|
||||
}
|
||||
|
||||
// Create environment file content
|
||||
envContent := fmt.Sprintf(`# Quilibrium Node Environment`, dataPath)
|
||||
|
||||
// Write environment file
|
||||
envPath := filepath.Join(dataPath, "config", "quilibrium.env")
|
||||
if err := os.WriteFile(envPath, []byte(envContent), 0640); err != nil {
|
||||
return fmt.Errorf("failed to create environment file: %w", err)
|
||||
}
|
||||
|
||||
// Set ownership of environment file
|
||||
chownCmd := exec.Command("chown", nodeUser+":"+nodeUser, envPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to set environment file ownership: %w", err)
|
||||
}
|
||||
|
||||
// Create systemd service file content
|
||||
serviceContent := fmt.Sprintf(`[Unit]
|
||||
Description=Quilibrium Node Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=quilibrium
|
||||
EnvironmentFile=/opt/quilibrium/config/quilibrium.env
|
||||
ExecStart=/usr/local/bin/quilibrium-node --config /opt/quilibrium/config
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, nodeUser, defaultSymlinkPath, dataPath)
|
||||
|
||||
// Write service file
|
||||
servicePath := "/etc/systemd/system/quilibrium-node.service"
|
||||
if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil {
|
||||
return fmt.Errorf("failed to create service file: %w", err)
|
||||
}
|
||||
|
||||
// Reload systemd daemon
|
||||
reloadCmd := exec.Command("systemctl", "daemon-reload")
|
||||
if err := reloadCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to reload systemd daemon: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Created systemd service file at %s\n", servicePath)
|
||||
fmt.Fprintf(os.Stdout, "Created environment file at %s\n", envPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// installMacOSService installs a launchd service on macOS
|
||||
func installMacOSService() {
|
||||
fmt.Println("Installing launchd service for Quilibrium node...")
|
||||
|
||||
// Create plist file content
|
||||
plistTemplate := `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>{{.Label}}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/quilibrium-node</string>
|
||||
<string>--config</string>
|
||||
<string>/opt/quilibrium/config/</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>QUILIBRIUM_DATA_DIR</key>
|
||||
<string>{{.DataPath}}</string>
|
||||
<key>QUILIBRIUM_LOG_LEVEL</key>
|
||||
<string>info</string>
|
||||
<key>QUILIBRIUM_LISTEN_GRPC_MULTIADDR</key>
|
||||
<string>/ip4/127.0.0.1/tcp/8337</string>
|
||||
<key>QUILIBRIUM_LISTEN_REST_MULTIADDR</key>
|
||||
<string>/ip4/127.0.0.1/tcp/8338</string>
|
||||
<key>QUILIBRIUM_STATS_MULTIADDR</key>
|
||||
<string>/dns/stats.quilibrium.com/tcp/443</string>
|
||||
<key>QUILIBRIUM_NETWORK_ID</key>
|
||||
<string>0</string>
|
||||
<key>QUILIBRIUM_DEBUG</key>
|
||||
<string>false</string>
|
||||
<key>QUILIBRIUM_SIGNATURE_CHECK</key>
|
||||
<string>true</string>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{.LogPath}}/node.err</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{.LogPath}}/node.log</string>
|
||||
</dict>
|
||||
</plist>`
|
||||
|
||||
// Prepare template data
|
||||
data := struct {
|
||||
Label string
|
||||
DataPath string
|
||||
ServiceName string
|
||||
LogPath string
|
||||
}{
|
||||
Label: fmt.Sprintf("com.quilibrium.node"),
|
||||
DataPath: dataPath,
|
||||
ServiceName: "node",
|
||||
LogPath: logPath,
|
||||
}
|
||||
|
||||
// Parse and execute template
|
||||
tmpl, err := template.New("plist").Parse(plistTemplate)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating plist template: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine plist file path
|
||||
var plistPath = fmt.Sprintf("/Library/LaunchDaemons/%s.plist", data.Label)
|
||||
|
||||
// Write plist file
|
||||
file, err := os.Create(plistPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating plist file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := tmpl.Execute(file, data); err != nil {
|
||||
fmt.Printf("Error writing plist file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set correct permissions
|
||||
chownCmd := exec.Command("chown", "root:wheel", plistPath)
|
||||
if err := chownCmd.Run(); err != nil {
|
||||
fmt.Printf("Warning: Failed to change ownership of plist file: %v\n", err)
|
||||
}
|
||||
|
||||
// Load the service
|
||||
var loadCmd = exec.Command("launchctl", "load", "-w", plistPath)
|
||||
|
||||
if err := loadCmd.Run(); err != nil {
|
||||
fmt.Printf("Error loading service: %v\n", err)
|
||||
fmt.Println("You may need to load the service manually.")
|
||||
}
|
||||
|
||||
fmt.Printf("Launchd service installed successfully as %s\n", plistPath)
|
||||
fmt.Println("\nTo start the service:")
|
||||
fmt.Printf(" sudo launchctl start %s\n", data.Label)
|
||||
fmt.Println("\nTo stop the service:")
|
||||
fmt.Printf(" sudo launchctl stop %s\n", data.Label)
|
||||
fmt.Println("\nTo view service logs:")
|
||||
fmt.Printf(" cat %s/%s.log\n", data.LogPath, data.ServiceName)
|
||||
}
|
||||
|
||||
@ -44,21 +44,14 @@ func init() {
|
||||
// updateNode handles the node update process
|
||||
func updateNode(version string) {
|
||||
// Check if we need sudo privileges
|
||||
if err := utils.CheckAndRequestSudo(fmt.Sprintf("Updating node at %s requires root privileges", installPath)); err != nil {
|
||||
if err := utils.CheckAndRequestSudo(fmt.Sprintf("Updating node at %s requires root privileges", utils.NodeDataPath)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create version-specific installation directory
|
||||
versionDir := filepath.Join(installPath, "node", version)
|
||||
if err := os.MkdirAll(versionDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create data directory
|
||||
versionDataDir := filepath.Join(dataPath, "node", version)
|
||||
if err := os.MkdirAll(versionDataDir, 0755); err != nil {
|
||||
// Create new binary version directory
|
||||
versionDataDir := filepath.Join(utils.NodeDataPath, version)
|
||||
if err := utils.ValidateAndCreateDir(versionDataDir, nil); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
@ -66,16 +59,18 @@ func updateNode(version string) {
|
||||
// Construct the expected filename for the specified version
|
||||
// Remove 'v' prefix if present for filename construction
|
||||
versionWithoutV := strings.TrimPrefix(version, "v")
|
||||
fileName := fmt.Sprintf("node-%s-%s-%s", versionWithoutV, osType, arch)
|
||||
|
||||
if IsExistingNodeVersion(versionWithoutV) {
|
||||
fmt.Fprintf(os.Stderr, "Error: Node version %s already exists\n", versionWithoutV)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Download the release directly
|
||||
nodeBinaryPath := filepath.Join(dataPath, version, fileName)
|
||||
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)
|
||||
// Clean up the created directories since installation failed
|
||||
os.RemoveAll(versionDir)
|
||||
os.RemoveAll(versionDataDir)
|
||||
return
|
||||
}
|
||||
@ -92,5 +87,5 @@ func updateNode(version string) {
|
||||
}
|
||||
|
||||
// Successfully downloaded the specific version
|
||||
finishInstallation(nodeBinaryPath, version)
|
||||
finishInstallation(version)
|
||||
}
|
||||
|
||||
112
client/cmd/node/user.go
Normal file
112
client/cmd/node/user.go
Normal file
@ -0,0 +1,112 @@
|
||||
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,11 +2,17 @@ package node
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||||
)
|
||||
|
||||
@ -39,3 +45,36 @@ func GetPrivKeyFromConfig(cfg *config.Config) (crypto.PrivKey, error) {
|
||||
privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
||||
return privKey, err
|
||||
}
|
||||
|
||||
func IsExistingNodeVersion(version string) bool {
|
||||
return utils.FileExists(filepath.Join(utils.NodeDataPath, version))
|
||||
}
|
||||
|
||||
func CheckForSystemd() bool {
|
||||
// Check if systemctl command exists
|
||||
_, 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
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ It provides commands for installing, updating, and managing Quilibrium nodes.`,
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.FileExists(utils.ClientConfigPath) {
|
||||
if !utils.FileExists(utils.GetConfigPath()) {
|
||||
fmt.Println("Client config not found, creating default config")
|
||||
utils.CreateDefaultConfig()
|
||||
}
|
||||
@ -85,9 +85,8 @@ It provides commands for installing, updating, and managing Quilibrium nodes.`,
|
||||
// Fall back to checking next to executable
|
||||
digest, err = os.ReadFile(ex + ".dgst")
|
||||
if err != nil {
|
||||
fmt.Println("")
|
||||
fmt.Println("The digest file was not found. Do you want to continue without signature verification? (y/n)")
|
||||
fmt.Println("The signature files (if they exist)can be downloaded with the 'qclient download-signatures' command")
|
||||
fmt.Println("You can also use --signature-check=false in your command to skip this prompt")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, _ := reader.ReadString('\n')
|
||||
@ -95,10 +94,19 @@ It provides commands for installing, updating, and managing Quilibrium nodes.`,
|
||||
|
||||
if response != "y" && response != "yes" {
|
||||
fmt.Println("Exiting due to missing digest file")
|
||||
fmt.Println("The signature files (if they exist) can be downloaded with the 'qclient download-signatures' command")
|
||||
fmt.Println("You can also skip this prompt manually by using the --signature-check=false flag or to permanently disable signature checks running 'qclient config signature-check false'")
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
// Check if the user is trying to run the config command to disable/enable signature checks
|
||||
if len(os.Args) >= 3 && os.Args[1] == "config" && os.Args[2] != "signature-check" {
|
||||
fmt.Println("The signature files (if they exist) can be downloaded with the 'qclient download-signatures' command")
|
||||
fmt.Println("You can also skip this prompt manually by using the --signature-check=false flag or to permanently disable signature checks running 'qclient config signature-check false'")
|
||||
}
|
||||
|
||||
fmt.Println("Continuing without signature verification")
|
||||
|
||||
signatureCheck = false
|
||||
}
|
||||
}
|
||||
@ -184,12 +192,6 @@ func signatureCheckDefault() bool {
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().BoolVar(
|
||||
&DryRun,
|
||||
"dry-run",
|
||||
false,
|
||||
"runs the command (if applicable) without actually mutating state (printing effect output)",
|
||||
)
|
||||
rootCmd.PersistentFlags().BoolVar(
|
||||
&signatureCheck,
|
||||
"signature-check",
|
||||
@ -197,6 +199,27 @@ func init() {
|
||||
"bypass signature check (not recommended for binaries) (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().BoolP(
|
||||
"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)
|
||||
|
||||
@ -146,6 +146,15 @@ sudo task build_qclient_arm64_linux
|
||||
# for mac, you will need to build on a mac
|
||||
```
|
||||
|
||||
## Run a test container
|
||||
|
||||
```
|
||||
sudo docker run -it \
|
||||
-v "/home/user/ceremonyclient/client/test/:/app" \
|
||||
-v "/home/user/ceremenoyclient/client/build/amd64_linux/qclient:/opt/quilibrium/bin/qclient" \
|
||||
quil-test /bin/bash
|
||||
|
||||
|
||||
This command builds the Docker image with the qclient binary according to the specifications in `Dockerfile.source`. The resulting image will be tagged as `qclient`.
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
dataDir: /var/quilibrium/data/qclient
|
||||
symlinkPath: /usr/local/bin/qclient
|
||||
signatureCheck: true
|
||||
@ -20,6 +20,19 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test: Link the qclient binary to the system PATH
|
||||
echo "Testing qclient link command..."
|
||||
run_test_with_format "sudo /opt/quilibrium/bin/qclient link"
|
||||
|
||||
# Verify qclient is now in PATH
|
||||
echo "Verifying qclient is in PATH after link command..."
|
||||
run_test_with_format "which qclient" | grep -q "/usr/local/bin/qclient" && echo "SUCCESS: qclient found in PATH" || echo "FAILURE: qclient not found in PATH"
|
||||
|
||||
# Test qclient can be executed directly
|
||||
echo "Testing qclient can be executed directly..."
|
||||
run_test_with_format "qclient --help" | grep -q "Usage:" && echo "SUCCESS: qclient executable works" || echo "FAILURE: qclient executable not working properly"
|
||||
|
||||
|
||||
# Test: Ensure no config file exists initially
|
||||
echo "Testing no config file exists initially..."
|
||||
run_test_with_format "test ! -f /etc/quilibrium/config/qclient.yaml"
|
||||
|
||||
@ -8,25 +8,34 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var ClientConfigDir = filepath.Join("/etc/quilibrium/", "config")
|
||||
var ClientConfigFile = string(ReleaseTypeQClient) + ".yaml"
|
||||
var ClientConfigDir = filepath.Join(os.Getenv("HOME"), ".quilibrium")
|
||||
var ClientConfigFile = string(ReleaseTypeQClient) + "-config.yaml"
|
||||
var ClientConfigPath = filepath.Join(ClientConfigDir, ClientConfigFile)
|
||||
|
||||
// var clientConfig = &ClientConfig{}
|
||||
|
||||
func CreateDefaultConfig() {
|
||||
fmt.Printf("Creating default config: %s\n", ClientConfigPath)
|
||||
configPath := GetConfigPath()
|
||||
|
||||
fmt.Printf("Creating default config: %s\n", configPath)
|
||||
SaveClientConfig(&ClientConfig{
|
||||
DataDir: ClientDataPath,
|
||||
SymlinkPath: DefaultQClientSymlinkPath,
|
||||
SignatureCheck: true,
|
||||
})
|
||||
|
||||
sudoUser, err := GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting current sudo user")
|
||||
os.Exit(1)
|
||||
}
|
||||
ChownPath(GetUserQuilibriumDir(), sudoUser, true)
|
||||
}
|
||||
|
||||
// LoadClientConfig loads the client configuration from the config file
|
||||
func LoadClientConfig() (*ClientConfig, error) {
|
||||
configPath := GetConfigPath()
|
||||
|
||||
// Create default config if it doesn't exist
|
||||
if _, err := os.Stat(ClientConfigPath); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
config := &ClientConfig{
|
||||
DataDir: ClientDataPath,
|
||||
SymlinkPath: filepath.Join(ClientDataPath, "current"),
|
||||
@ -39,7 +48,7 @@ func LoadClientConfig() (*ClientConfig, error) {
|
||||
}
|
||||
|
||||
// Read existing config
|
||||
data, err := os.ReadFile(ClientConfigPath)
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -60,16 +69,20 @@ func SaveClientConfig(config *ClientConfig) error {
|
||||
}
|
||||
|
||||
// Ensure the config directory exists
|
||||
if err := os.MkdirAll(ClientConfigDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(GetConfigDir(), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
|
||||
return os.WriteFile(ClientConfigPath, data, 0644)
|
||||
return os.WriteFile(GetConfigPath(), data, 0644)
|
||||
}
|
||||
|
||||
// GetConfigPath returns the path to the client configuration file
|
||||
func GetConfigPath() string {
|
||||
return ClientConfigPath
|
||||
return filepath.Join(GetConfigDir(), ClientConfigFile)
|
||||
}
|
||||
|
||||
func GetConfigDir() string {
|
||||
return filepath.Join(GetUserQuilibriumDir())
|
||||
}
|
||||
|
||||
// IsClientConfigured checks if the client is configured
|
||||
|
||||
@ -53,7 +53,7 @@ func GetLatestVersion(releaseType ReleaseType) (string, error) {
|
||||
// DownloadReleaseFile downloads a release file from the Quilibrium releases server
|
||||
func DownloadReleaseFile(releaseType ReleaseType, fileName string, version string, showError bool) error {
|
||||
url := fmt.Sprintf("%s/%s", BaseReleaseURL, fileName)
|
||||
destDir := filepath.Join(DataPath, string(releaseType), version)
|
||||
destDir := filepath.Join(BinaryPath, string(releaseType), version)
|
||||
os.MkdirAll(destDir, 0755)
|
||||
destPath := filepath.Join(destDir, fileName)
|
||||
|
||||
@ -109,11 +109,13 @@ func DownloadReleaseSignatures(releaseType ReleaseType, version string) error {
|
||||
// Skip this file if it doesn't exist on the server
|
||||
fmt.Printf("Signature file %s not found on server, skipping\n", sigFile)
|
||||
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))
|
||||
}
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
files = append(files, fmt.Sprintf("%s.dgst.sig.%d", baseName, num))
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
@ -15,9 +17,10 @@ import (
|
||||
var DefaultNodeUser = "quilibrium"
|
||||
|
||||
var ClientInstallPath = filepath.Join("/opt/quilibrium/", string(ReleaseTypeQClient))
|
||||
var DataPath = filepath.Join("/var/quilibrium/", "data")
|
||||
var ClientDataPath = filepath.Join(DataPath, string(ReleaseTypeQClient))
|
||||
var NodeDataPath = filepath.Join(DataPath, string(ReleaseTypeNode))
|
||||
var RootQuilibriumPath = filepath.Join("/var/quilibrium/")
|
||||
var BinaryPath = filepath.Join(RootQuilibriumPath, "bin")
|
||||
var ClientDataPath = filepath.Join(BinaryPath, string(ReleaseTypeQClient))
|
||||
var NodeDataPath = filepath.Join(BinaryPath, string(ReleaseTypeNode))
|
||||
var DefaultSymlinkDir = "/usr/local/bin"
|
||||
var DefaultNodeSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeNode))
|
||||
var DefaultQClientSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeQClient))
|
||||
@ -68,6 +71,8 @@ func CreateSymlink(execPath, targetPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Creating symlink %s -> %s\n", targetPath, execPath)
|
||||
|
||||
// Create the symlink
|
||||
if err := os.Symlink(execPath, targetPath); err != nil {
|
||||
return fmt.Errorf("failed to create symlink: %w", err)
|
||||
@ -77,7 +82,7 @@ func CreateSymlink(execPath, targetPath string) error {
|
||||
}
|
||||
|
||||
// ValidateAndCreateDir validates a directory path and creates it if it doesn't exist
|
||||
func ValidateAndCreateDir(path string) error {
|
||||
func ValidateAndCreateDir(path string, user *user.User) error {
|
||||
// Check if the directory exists
|
||||
info, err := os.Stat(path)
|
||||
if err == nil {
|
||||
@ -90,9 +95,13 @@ func ValidateAndCreateDir(path string) error {
|
||||
|
||||
// Directory doesn't exist, try to create it
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("Creating directory %s\n", path)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %v", path, err)
|
||||
}
|
||||
if user != nil {
|
||||
ChownPath(path, user, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -135,3 +144,97 @@ func FileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func IsSudo() bool {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return user.Username == "root"
|
||||
}
|
||||
|
||||
// ChownPath changes the owner of a file or directory to the specified user
|
||||
func ChownPath(path string, user *user.User, isRecursive bool) error {
|
||||
// Change ownership of the path
|
||||
if isRecursive {
|
||||
fmt.Printf("Changing ownership of %s (recursive) to %s\n", path, user.Username)
|
||||
if err := exec.Command("chown", "-R", user.Uid+":"+user.Gid, path).Run(); err != nil {
|
||||
return fmt.Errorf("failed to change ownership of %s to %s (requires sudo): %v", path, user.Uid, err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Changing ownership of %s to %s\n", path, user.Username)
|
||||
if err := exec.Command("chown", user.Uid+":"+user.Gid, path).Run(); err != nil {
|
||||
return fmt.Errorf("failed to change ownership of %s to %s (requires sudo): %v", path, user.Uid, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ChmodPath(path string, mode os.FileMode, description string) error {
|
||||
fmt.Printf("Changing path: %s to %s (%s)\n", path, mode, description)
|
||||
return os.Chmod(path, mode)
|
||||
}
|
||||
|
||||
func WriteFile(path string, content string) error {
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// WriteFileAuto writes content to a file, automatically using sudo only if necessary
|
||||
func WriteFileAuto(path string, content string) error {
|
||||
// First check if file exists and is writable
|
||||
if FileExists(path) {
|
||||
// Try to open the file for writing to check permissions
|
||||
file, err := os.OpenFile(path, os.O_WRONLY, 0)
|
||||
if err == nil {
|
||||
// File is writable, close it and write normally
|
||||
file.Close()
|
||||
fmt.Printf("Writing to file %s using normal permissions\n", path)
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
} else {
|
||||
// Check if parent directory is writable
|
||||
dir := filepath.Dir(path)
|
||||
if IsWritable(dir) {
|
||||
fmt.Printf("Writing to file %s using normal permissions\n", path)
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, sudo is needed
|
||||
fmt.Printf("Writing to file %s using sudo\n", path)
|
||||
cmd := exec.Command("sudo", "tee", path)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
// Start the command
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start sudo command: %w", err)
|
||||
}
|
||||
|
||||
// Write content to stdin
|
||||
if _, err := io.WriteString(stdin, content); err != nil {
|
||||
return fmt.Errorf("failed to write to stdin: %w", err)
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
// Wait for the command to finish
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("sudo tee command failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFile copies a file from src to dst
|
||||
func CopyFile(src, dst string) error {
|
||||
fmt.Printf("Copying file from %s to %s\n", src, dst)
|
||||
sourceData, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(dst, sourceData, 0600)
|
||||
}
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetSystemInfo determines and validates the OS and architecture
|
||||
@ -26,3 +32,35 @@ func GetSystemInfo() (string, string, error) {
|
||||
|
||||
return osType, arch, nil
|
||||
}
|
||||
|
||||
func GetCurrentSudoUser() (*user.User, error) {
|
||||
if os.Geteuid() != 0 {
|
||||
return user.Current()
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", "env | grep SUDO_USER | cut -d= -f2 | cut -d\\n -f1")
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current sudo user: %v", err)
|
||||
}
|
||||
|
||||
userLookup, err := user.Lookup(strings.TrimSpace(out.String()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current sudo user: %v", err)
|
||||
}
|
||||
return userLookup, nil
|
||||
}
|
||||
|
||||
func GetUserQuilibriumDir() string {
|
||||
sudoUser, err := GetCurrentSudoUser()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting current sudo user")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return filepath.Join(sudoUser.HomeDir, ".quilibrium")
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user