vspd/cmd/vspadmin/main.go
jholdstock 1fa81d3697 vspadmin: Reject private keys in fee xpub config.
The provided key should always be a public key, enforcing this in
software provides an extra safety net for users.
2024-06-19 09:56:29 +01:00

175 lines
4.2 KiB
Go

// Copyright (c) 2024 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/hdkeychain/v3"
"github.com/decred/vspd/database"
"github.com/decred/vspd/internal/config"
"github.com/decred/vspd/internal/vspd"
"github.com/jessevdk/go-flags"
)
const (
configFilename = "vspd.conf"
dbFilename = "vspd.db"
)
type conf struct {
HomeDir string `long:"homedir" description:"Path to application home directory."`
Network string `long:"network" description:"Decred network to use." choice:"mainnet" choice:"testnet" choice:"simnet"`
}
var defaultConf = conf{
HomeDir: dcrutil.AppDataDir("vspd", false),
Network: "mainnet",
}
func log(format string, a ...any) {
fmt.Printf(format+"\n", a...)
}
// fileExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); os.IsNotExist(err) {
return false
}
return true
}
func createDatabase(homeDir string, feeXPub string, network *config.Network) error {
dataDir := filepath.Join(homeDir, "data", network.Name)
dbFile := filepath.Join(dataDir, dbFilename)
// Return error if database already exists.
if fileExists(dbFile) {
return fmt.Errorf("%s database already exists in %s", network.Name, dataDir)
}
// Ensure provided xpub is a valid key for the selected network.
feeXpub, err := hdkeychain.NewKeyFromString(feeXPub, network.Params)
if err != nil {
return fmt.Errorf("failed to parse feexpub: %w", err)
}
// Ensure key is public.
if feeXpub.IsPrivate() {
return errors.New("feexpub is a private key, should be public")
}
// Ensure the data directory exists.
err = os.MkdirAll(dataDir, 0700)
if err != nil {
return fmt.Errorf("failed to create data directory: %w", err)
}
// Create new database.
err = database.CreateNew(dbFile, feeXPub)
if err != nil {
return fmt.Errorf("error creating db file %s: %w", dbFile, err)
}
return nil
}
func writeConfig(homeDir string) error {
configFile := filepath.Join(homeDir, configFilename)
// Return an error if the config file already exists.
if fileExists(configFile) {
return fmt.Errorf("config file already exists in %s", homeDir)
}
// Ensure the directory exists.
err := os.MkdirAll(homeDir, 0700)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Write a config file with default values to the provided home directory.
preParser := flags.NewParser(&vspd.DefaultConfig, flags.None)
preIni := flags.NewIniParser(preParser)
err = preIni.WriteFile(configFile,
flags.IniIncludeComments|flags.IniIncludeDefaults)
if err != nil {
return fmt.Errorf("failed to create config file: %w", err)
}
return nil
}
// run is the real main function for vspadmin. It is necessary to work around
// the fact that deferred functions do not run when os.Exit() is called.
func run() int {
cfg := defaultConf
// If command line options are requesting help, write it to stdout and exit.
if config.WriteHelp(&cfg) {
return 0
}
// Parse command line options.
remainingArgs, err := flags.Parse(&cfg)
if err != nil {
// Don't need to log the error, flags lib has already done it.
return 1
}
network, err := config.NetworkFromName(cfg.Network)
if err != nil {
log("%v", err)
return 1
}
if len(remainingArgs) < 1 {
log("No command specified")
return 1
}
switch remainingArgs[0] {
case "createdatabase":
if len(remainingArgs) != 2 {
log("createdatabase has one required argument, fee xpub")
return 1
}
feeXPub := remainingArgs[1]
err = createDatabase(cfg.HomeDir, feeXPub, network)
if err != nil {
log("createdatabase failed: %v", err)
return 1
}
log("New %s vspd database created in %s", network.Name, cfg.HomeDir)
case "writeconfig":
err = writeConfig(cfg.HomeDir)
if err != nil {
log("writeconfig failed: %v", err)
return 1
}
log("Config file with default values written to %s", cfg.HomeDir)
log("Edit the file and fill in values specific to your vspd deployment")
default:
log("%q is not a valid command", remainingArgs[0])
return 1
}
return 0
}
func main() {
os.Exit(run())
}