Skip to content

Commit

Permalink
vspadmin: New binary to create empty databases.
Browse files Browse the repository at this point in the history
vspadmin is a new binary which implements one-off admin tasks which are
necessarily interactive and thus do not fit neatly into the long-lived
vspd binary.

The first behaviour added to vspadmin is the ability to create a new
vspd database. This behaviour is removed from vspd with the associated
config option deprecated. Documentation and scripts updated to reflect
the change.
  • Loading branch information
jholdstock committed Jun 19, 2024
1 parent 0633260 commit 997205e
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 50 deletions.
30 changes: 30 additions & 0 deletions cmd/vspadmin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# vspadmin

vspadmin is a tool to perform various VSP administration tasks.

## Usage

```no-highlight
vspadmin [OPTIONS] COMMAND
```

## Options

```no-highlight
--homedir= Path to application home directory. (default: /home/user/.vspd)
--network=[mainnet|testnet|simnet] Decred network to use. (default: mainnet)
-h, --help Show help message
```

## Commands

### `createdatabase`

Creates a new database for a new deployment of vspd. Accepts the xpub key to be
used for collecting fees as a parameter.

Example:

```no-highlight
go run ./cmd/vspadmin createdatabase <xpub>
```
130 changes: 130 additions & 0 deletions cmd/vspadmin/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// 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 (
"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/jessevdk/go-flags"
)

const (
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.
_, err := hdkeychain.NewKeyFromString(feeXPub, network.Params)
if err != nil {
return fmt.Errorf("failed to parse feexpub: %w", err)
}

// 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
}

// 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)

default:
log("%q is not a valid command", remainingArgs[0])
return 1
}

return 0
}

func main() {
os.Exit(run())
}
6 changes: 6 additions & 0 deletions cmd/vspd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ func run() int {
log.Warnf("")
}

if cfg.FeeXPub != "" {
log.Warnf("")
log.Warnf("\tWARNING: Config --feexpub is set. This behavior has been moved into vspadmin and will be removed from vspd in a future release")
log.Warnf("")
}

// Open database.
db, err := database.Open(cfg.DatabaseFile(), makeLogger(" DB"), maxVoteChangeRecords)
if err != nil {
Expand Down
9 changes: 4 additions & 5 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,14 @@ vspd. **Do not run a voting wallet on your webserver.**
config file to set your dcrd and dcrwallet connection details, and any other
required customization.

1. A vspd database must be initialized before vpsd can be started. To do this,
provide vspd with the xpub key it should use for collecting fees:
1. Use [vspadmin](./cmd/vspadmin) to initialize a vpsd database. The xpub key to
be used for collecting fees must be passed in as an argument.

```no-highlight
$ vspd --feexpub=tpubVppjaMjp8GEW...
$ go run ./cmd/vspadmin createdatabase tpubVppjaMjp8GEW...
```
1. Once the database is initialized, vspd can be started for normal operation by
running it without the `--feexpub` flag.
1. Once the database is initialized, vspd can be started for normal operation.
1. Configure nginx with SSL and set up reverse proxy to forward requests to the
vspd process. nginx must also set the `X-Forwarded-For` header to make vspd
Expand Down
4 changes: 2 additions & 2 deletions harness.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# Copyright (c) 2020-2023 The Decred developers
# Copyright (c) 2020-2024 The Decred developers
# Use of this source code is governed by an ISC
# license that can be found in the LICENSE file.
#
Expand Down Expand Up @@ -170,7 +170,7 @@ EOF
tmux new-window -t $TMUX_SESSION -n "vspd"

echo "Creating vspd database"
tmux send-keys "vspd --homedir=${HARNESS_ROOT}/vspd --feexpub=${VSPD_FEE_XPUB}" C-m
tmux send-keys "go run ./cmd/vspadmin --homedir=${HARNESS_ROOT}/vspd --network=testnet createdatabase ${VSPD_FEE_XPUB}" C-m
sleep 3 # wait for database creation and ensure dcrwallet rpc listeners are started
echo "Starting vspd"
tmux send-keys "vspd --homedir=${HARNESS_ROOT}/vspd" C-m
Expand Down
48 changes: 5 additions & 43 deletions internal/vspd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
"time"

"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/version"
flags "github.com/jessevdk/go-flags"
Expand Down Expand Up @@ -55,12 +53,11 @@ type Config struct {

// The following flags should be set on CLI only, not via config file.
ShowVersion bool `long:"version" no-ini:"true" description:"Display version information and exit."`
FeeXPub string `long:"feexpub" no-ini:"true" description:"Cold wallet xpub used for collecting fees. Should be provided once to initialize a vspd database."`
FeeXPub string `long:"feexpub" no-ini:"true" description:"DEPRECATED: This behavior has been moved into vspadmin and will be removed from vspd in a future version of the software."`
HomeDir string `long:"homedir" no-ini:"true" description:"Path to application home directory. Used for storing VSP database and logs."`
ConfigFile string `long:"configfile" no-ini:"true" description:"DEPRECATED: This behavior is no longer available and this option will be removed in a future version of the software."`

// The following fields are derived from the above fields by LoadConfig().
dataDir string
network *config.Network
dcrdDetails *DcrdDetails
walletDetails *WalletDetails
Expand Down Expand Up @@ -89,7 +86,7 @@ func (cfg *Config) LogDir() string {
}

func (cfg *Config) DatabaseFile() string {
return filepath.Join(cfg.dataDir, dbFilename)
return filepath.Join(cfg.HomeDir, "data", cfg.network.Name, dbFilename)
}

func (cfg *Config) DcrdDetails() *DcrdDetails {
Expand Down Expand Up @@ -420,45 +417,10 @@ func LoadConfig() (*Config, error) {
Certs: walletCerts,
}

// Create the data directory.
cfg.dataDir = filepath.Join(cfg.HomeDir, "data", cfg.network.Name)
err = os.MkdirAll(cfg.dataDir, 0700)
if err != nil {
return nil, fmt.Errorf("failed to create data directory: %w", err)
}

dbPath := cfg.DatabaseFile()

// If xpub has been provided, create a new database and exit.
if cfg.FeeXPub != "" {
// If database already exists, return error.
if fileExists(dbPath) {
return nil, fmt.Errorf("database already initialized at %s, "+
"--feexpub option is not needed", dbPath)
}

// Ensure provided value is a valid key for the selected network.
_, err = hdkeychain.NewKeyFromString(cfg.FeeXPub, cfg.network.Params)
if err != nil {
return nil, fmt.Errorf("failed to parse feexpub: %w", err)
}

// Create new database.
fmt.Printf("Initializing new database at %s\n", dbPath)
err = database.CreateNew(dbPath, cfg.FeeXPub)
if err != nil {
return nil, fmt.Errorf("error creating db file %s: %w", dbPath, err)
}

// Exit with success
fmt.Printf("Database initialized\n")
os.Exit(0)
}

// If database does not exist, return error.
if !fileExists(dbPath) {
return nil, fmt.Errorf("no database exists in %s. Run vspd with the"+
" --feexpub option to initialize one", cfg.dataDir)
if !fileExists(cfg.DatabaseFile()) {
return nil, fmt.Errorf("no %s database exists in %s. A new database can"+
" be created with vspadmin", cfg.network.Name, cfg.HomeDir)
}

return &cfg, nil
Expand Down

0 comments on commit 997205e

Please sign in to comment.