ceremonyclient/client/cmd/root.go
2025-04-07 10:11:03 -08:00

260 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
"github.com/cloudflare/circl/sign/ed448"
"github.com/multiformats/go-multiaddr"
mn "github.com/multiformats/go-multiaddr/net"
"github.com/spf13/cobra"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
"source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/config"
)
var configDirectory string
var signatureCheck bool = true
var NodeConfig *config.Config
var simulateFail bool
var LightNode bool = false
var DryRun bool = false
var publicRPC bool = false
var rootCmd = &cobra.Command{
Use: "qclient",
Short: "Quilibrium client",
Long: `Quilibrium client is a command-line tool for managing Quilibrium nodes.
It provides commands for installing, updating, and managing Quilibrium nodes.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if signatureCheck {
ex, err := os.Executable()
if err != nil {
panic(err)
}
b, err := os.ReadFile(ex)
if err != nil {
fmt.Println(
"Error encountered during signature check are you running this " +
"from source? (use --signature-check=false)",
)
panic(err)
}
checksum := sha3.Sum256(b)
digest, err := os.ReadFile(ex + ".dgst")
if err != nil {
fmt.Println("The digest file was not found. Do you want to continue without signature verification? (y/n)")
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')
response = strings.TrimSpace(strings.ToLower(response))
if response != "y" && response != "yes" {
fmt.Println("Exiting due to missing digest file")
os.Exit(1)
}
fmt.Println("Continuing without signature verification")
signatureCheck = false
} else {
parts := strings.Split(string(digest), " ")
if len(parts) != 2 {
fmt.Println("Invalid digest file format")
os.Exit(1)
}
digestBytes, err := hex.DecodeString(parts[1][:64])
if err != nil {
fmt.Println("Invalid digest file format")
os.Exit(1)
}
if !bytes.Equal(checksum[:], digestBytes) {
fmt.Println("Invalid digest for node")
os.Exit(1)
}
count := 0
for i := 1; i <= len(config.Signatories); i++ {
signatureFile := fmt.Sprintf(ex+".dgst.sig.%d", i)
sig, err := os.ReadFile(signatureFile)
if err != nil {
continue
}
pubkey, _ := hex.DecodeString(config.Signatories[i-1])
if !ed448.Verify(pubkey, digest, sig, "") {
fmt.Printf("Failed signature check for signatory #%d\n", i)
os.Exit(1)
}
count++
}
if count < ((len(config.Signatories)-4)/2)+((len(config.Signatories)-4)%2) {
fmt.Printf("Quorum on signatures not met")
os.Exit(1)
}
fmt.Println("Signature check passed")
}
} else {
fmt.Println("Signature check bypassed, be sure you know what you're doing")
}
// Skip config checks for node and link commands
if len(os.Args) > 1 && (os.Args[1] != "node" && os.Args[1] != "link") {
// These commands handle their own configuration
_, err := os.Stat(configDirectory)
if os.IsNotExist(err) {
fmt.Printf("config directory doesn't exist: %s\n", configDirectory)
os.Exit(1)
}
NodeConfig, err = config.LoadConfig(configDirectory, "", false)
if err != nil {
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 Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func GetGRPCClient() (*grpc.ClientConn, error) {
addr := "rpc.quilibrium.com:8337"
credentials := credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})
if !LightNode {
ma, err := multiaddr.NewMultiaddr(NodeConfig.ListenGRPCMultiaddr)
if err != nil {
panic(err)
}
_, addr, err = mn.DialArgs(ma)
if err != nil {
panic(err)
}
credentials = insecure.NewCredentials()
}
return grpc.Dial(
addr,
grpc.WithTransportCredentials(
credentials,
),
grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(600*1024*1024),
grpc.MaxCallRecvMsgSize(600*1024*1024),
),
)
}
func signatureCheckDefault() bool {
envVarValue, envVarExists := os.LookupEnv("QUILIBRIUM_SIGNATURE_CHECK")
if envVarExists {
def, err := strconv.ParseBool(envVarValue)
if err == nil {
return def
} else {
fmt.Println("Invalid environment variable QUILIBRIUM_SIGNATURE_CHECK, must be 'true' or 'false'. Got: " + envVarValue)
}
}
return true
}
func init() {
rootCmd.PersistentFlags().StringVar(
&configDirectory,
"config",
".config/",
"config directory (default is .config/)",
)
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",
signatureCheckDefault(),
"bypass signature check (not recommended for binaries) (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
)
rootCmd.PersistentFlags().BoolVar(
&publicRPC,
"public-rpc",
false,
"uses the public RPCd",
)
// Create config directory if it doesn't exist
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
// Skip for help command
if cmd.Name() == "help" {
return nil
}
// Create config directory if it doesn't exist
if err := os.MkdirAll(utils.ClientConfigDir, 0755); err != nil {
return fmt.Errorf("failed to create config directory: %v", err)
}
// Check for signature files
ex, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to get executable path: %v", err)
}
digestPath := ex + ".dgst"
if signatureCheck && !utils.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 file...")
// TODO: Implement signature download logic
fmt.Println("Signature download not implemented yet. Please download manually.")
} else {
fmt.Println("Continuing without signature verification")
signatureCheck = false
}
}
return nil
}
// Add the node command
rootCmd.AddCommand(node.NodeCmd)
}