Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reverse proxy deployments #301

Merged
merged 4 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ const (
defaultPoolFee = 0.01
defaultLastNPeriod = time.Hour * 24
defaultSoloPool = false
defaultGUIPort = 8080
defaultGUIPort = "8080"
defaultGUIListen = "0.0.0.0"
defaultGUIDir = "gui"
defaultUseLEHTTPS = false
defaultMinerPort = 5550
defaultMinerPort = "5550"
defaultMinerListen = "0.0.0.0"
defaultDesignation = "YourPoolNameHere"
defaultMaxConnectionsPerHost = 100 // 100 connected clients per host
defaultWalletAccount = 0
Expand All @@ -61,6 +63,7 @@ const (
defaultPGDBName = "dcrpooldb"
defaultMonitorCycle = time.Minute * 2
defaultMaxUpgradeTries = 10
defaultNoGuiTLS = false
)

var (
Expand Down Expand Up @@ -93,7 +96,7 @@ type config struct {
ConfigFile string `long:"configfile" ini-name:"configfile" description:"Path to configuration file."`
DataDir string `long:"datadir" ini-name:"datadir" description:"The data directory."`
ActiveNet string `long:"activenet" ini-name:"activenet" description:"The active network being mined on. {testnet3, mainnet, simnet}"`
GUIPort uint32 `long:"guiport" ini-name:"guiport" description:"The pool GUI port."`
GUIListen string `long:"guilisten" ini-name:"guilisten" description:"The address:port for pool GUI listening."`
DebugLevel string `long:"debuglevel" ini-name:"debuglevel" description:"Logging level for all subsystems. {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
LogDir string `long:"logdir" ini-name:"logdir" description:"Directory to log output."`
DBFile string `long:"dbfile" ini-name:"dbfile" description:"Path to the database file."`
Expand Down Expand Up @@ -124,7 +127,7 @@ type config struct {
Designation string `long:"designation" ini-name:"designation" description:"The designated codename for this pool. Customises the logo in the top toolbar."`
MaxConnectionsPerHost uint32 `long:"maxconnperhost" ini-name:"maxconnperhost" description:"The maximum number of connections allowed per host."`
Profile string `long:"profile" ini-name:"profile" description:"Enable HTTP profiling on given [addr:]port -- NOTE port must be between 1024 and 65536"`
MinerPort uint32 `long:"minerport" ini-name:"minerport" description:"Miner connection port."`
MinerListen string `long:"minerlisten" ini-name:"minerlisten" description:"The address:port for miner connections."`
CoinbaseConfTimeout time.Duration `long:"conftimeout" ini-name:"conftimeout" description:"The duration to wait for coinbase confirmations."`
GenCertsOnly bool `long:"gencertsonly" ini-name:"gencertsonly" description:"Only generate needed TLS key pairs and terminate."`
UsePostgres bool `long:"postgres" ini-name:"postgres" description:"Use postgres database instead of bolt."`
Expand All @@ -136,6 +139,7 @@ type config struct {
PurgeDB bool `long:"purgedb" ini-name:"purgedb" description:"Wipes all existing data on startup for a postgres backend. This intended for simnet testing purposes only."`
MonitorCycle time.Duration `long:"monitorcycle" ini-name:"monitorcycle" description:"Time spent monitoring a mining client for possible upgrades."`
MaxUpgradeTries uint32 `long:"maxupgradetries" ini-name:"maxupgradetries" description:"Maximum consecuctive miner monitoring and upgrade tries."`
NoGuiTLS bool `long:"noguitls" ini-name:"noguitls" description:"Disable TLS on GUI endpoint (eg. for reverse proxy with a dedicated webserver)."`
poolFeeAddrs []dcrutil.Address
dcrdRPCCerts []byte
net *params
Expand Down Expand Up @@ -353,7 +357,7 @@ func loadConfig() (*config, []string, error) {
PaymentMethod: defaultPaymentMethod,
LastNPeriod: defaultLastNPeriod,
SoloPool: defaultSoloPool,
GUIPort: defaultGUIPort,
GUIListen: defaultGUIListen,
dnldd marked this conversation as resolved.
Show resolved Hide resolved
GUIDir: defaultGUIDir,
UseLEHTTPS: defaultUseLEHTTPS,
TLSCert: defaultTLSCertFile,
Expand All @@ -362,7 +366,7 @@ func loadConfig() (*config, []string, error) {
WalletTLSKey: defaultWalletTLSKeyFile,
Designation: defaultDesignation,
MaxConnectionsPerHost: defaultMaxConnectionsPerHost,
MinerPort: defaultMinerPort,
MinerListen: defaultMinerListen,
dnldd marked this conversation as resolved.
Show resolved Hide resolved
WalletAccount: defaultWalletAccount,
CoinbaseConfTimeout: defaultCoinbaseConfTimeout,
UsePostgres: defaultUsePostgres,
Expand All @@ -373,6 +377,7 @@ func loadConfig() (*config, []string, error) {
PGDBName: defaultPGDBName,
MonitorCycle: defaultMonitorCycle,
MaxUpgradeTries: defaultMaxUpgradeTries,
NoGuiTLS: defaultNoGuiTLS,
}

// Service options which are only added on Windows.
Expand Down Expand Up @@ -641,6 +646,9 @@ func loadConfig() (*config, []string, error) {
cfg.DcrdRPCHost = normalizeAddress(cfg.DcrdRPCHost, cfg.net.DcrdRPCServerPort)
cfg.WalletGRPCHost = normalizeAddress(cfg.WalletGRPCHost, cfg.net.WalletGRPCServerPort)

cfg.MinerListen = normalizeAddress(cfg.MinerListen, defaultMinerPort)
cfg.GUIListen = normalizeAddress(cfg.GUIListen, defaultGUIPort)

if !cfg.SoloPool {
// Ensure a valid payment method is set.
if cfg.PaymentMethod != pool.PPS && cfg.PaymentMethod != pool.PPLNS {
Expand Down Expand Up @@ -723,6 +731,13 @@ func loadConfig() (*config, []string, error) {
mpLog.Warnf("%v", configFileError)
}

if cfg.NoGuiTLS && cfg.UseLEHTTPS {
err := fmt.Errorf("only one of uselehttps and noguitls can be specified")
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
}

// Ensure a domain is set if HTTPS via letsencrypt is preferred.
if cfg.UseLEHTTPS && cfg.Domain == "" {
err := fmt.Errorf("a valid domain is required for HTTPS " +
Expand Down
7 changes: 4 additions & 3 deletions dcrpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func newPool(db pool.Database, cfg *config) (*miningPool, error) {
PoolFeeAddrs: cfg.poolFeeAddrs,
SoloPool: cfg.SoloPool,
NonceIterations: iterations,
MinerPort: cfg.MinerPort,
MinerListen: cfg.MinerListen,
MaxConnectionsPerHost: cfg.MaxConnectionsPerHost,
WalletAccount: cfg.WalletAccount,
CoinbaseConfTimeout: cfg.CoinbaseConfTimeout,
Expand Down Expand Up @@ -92,8 +92,9 @@ func newPool(db pool.Database, cfg *config) (*miningPool, error) {
SoloPool: cfg.SoloPool,
GUIDir: cfg.GUIDir,
AdminPass: cfg.AdminPass,
GUIPort: cfg.GUIPort,
GUIListen: cfg.GUIListen,
UseLEHTTPS: cfg.UseLEHTTPS,
NoGuiTLS: cfg.NoGuiTLS,
Domain: cfg.Domain,
TLSCertFile: cfg.TLSCert,
TLSKeyFile: cfg.TLSKey,
Expand All @@ -102,7 +103,7 @@ func newPool(db pool.Database, cfg *config) (*miningPool, error) {
Designation: cfg.Designation,
PoolFee: cfg.PoolFee,
CSRFSecret: csrfSecret,
MinerPort: cfg.MinerPort,
MinerListen: cfg.MinerListen,
WithinLimit: p.hub.WithinLimit,
FetchLastWorkHeight: p.hub.FetchLastWorkHeight,
FetchLastPaymentInfo: p.hub.FetchLastPaymentInfo,
Expand Down
60 changes: 37 additions & 23 deletions gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"html/template"
"net/http"
"os"
Expand Down Expand Up @@ -40,14 +39,16 @@ type Config struct {
CSRFSecret []byte
// AdminPass represents the admin password.
AdminPass string
// GUIPort represents the port the frontend is served on.
GUIPort uint32
// GUIListen represents the listening address the frontend is served on.
GUIListen string
// TLSCertFile represents the TLS certificate file path.
TLSCertFile string
// TLSKeyFile represents the TLS key file path.
TLSKeyFile string
// UseLEHTTPS represents Letsencrypt HTTPS mode.
UseLEHTTPS bool
// NoGuiTLS starts the webserver listening for plain HTTP.
NoGuiTLS bool
// Domain represents the domain name of the pool.
Domain string
// ActiveNet represents the active network being mined on.
Expand All @@ -58,8 +59,8 @@ type Config struct {
Designation string
// PoolFee represents the fee charged to participating accounts of the pool.
PoolFee float64
// MinerPort represents the miner connection port for the pool.
MinerPort uint32
// MinerListen represents the listening address for miner connections.
MinerListen string
// WithinLimit returns if a client is within its request limits.
WithinLimit func(string, int) bool
// FetchLastWorkHeight returns the last work height of the pool.
Expand Down Expand Up @@ -247,24 +248,8 @@ func (ui *GUI) loadTemplates() error {
// Run starts the user interface.
func (ui *GUI) Run(ctx context.Context) {
go func() {
if !ui.cfg.UseLEHTTPS {
log.Infof("Starting GUI server on port %d (https)", ui.cfg.GUIPort)
ui.server = &http.Server{
WriteTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
IdleTimeout: time.Second * 30,
Addr: fmt.Sprintf("0.0.0.0:%v", ui.cfg.GUIPort),
Handler: ui.router,
}

if err := ui.server.ListenAndServeTLS(ui.cfg.TLSCertFile,
ui.cfg.TLSKeyFile); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}

if ui.cfg.UseLEHTTPS {
switch {
case ui.cfg.UseLEHTTPS:
certMgr := &autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache("certs"),
Expand Down Expand Up @@ -295,6 +280,35 @@ func (ui *GUI) Run(ctx context.Context) {
if err := ui.server.ListenAndServeTLS("", ""); err != nil {
log.Error(err)
}
case ui.cfg.NoGuiTLS:
log.Infof("Starting GUI server on %s (http)", ui.cfg.GUIListen)
ui.server = &http.Server{
WriteTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
IdleTimeout: time.Second * 30,
Addr: ui.cfg.GUIListen,
Handler: ui.router,
}

if err := ui.server.ListenAndServe(); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
default:
log.Infof("Starting GUI server on %s (https)", ui.cfg.GUIListen)
ui.server = &http.Server{
WriteTimeout: time.Second * 30,
ReadTimeout: time.Second * 30,
IdleTimeout: time.Second * 30,
Addr: ui.cfg.GUIListen,
Handler: ui.router,
}

if err := ui.server.ListenAndServeTLS(ui.cfg.TLSCertFile,
ui.cfg.TLSKeyFile); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}
}()

Expand Down
11 changes: 9 additions & 2 deletions gui/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package gui

import (
"net"
"net/http"

"github.com/gorilla/csrf"
Expand All @@ -15,7 +16,7 @@ import (
type indexPageData struct {
HeaderData headerData
PoolStatsData poolStatsData
MinerPort uint32
MinerPort string
MinedWork []*minedWork
RewardQuotas []*rewardQuota
Address string
Expand Down Expand Up @@ -43,6 +44,12 @@ func (ui *GUI) renderIndex(w http.ResponseWriter, r *http.Request, modalError st
address = ui.cfg.Domain
}

_, minerPort, err := net.SplitHostPort(ui.cfg.MinerListen)
if err != nil {
log.Errorf("Failed to parse port from miner listening address %q",
dnldd marked this conversation as resolved.
Show resolved Hide resolved
ui.cfg.MinerListen)
}

data := indexPageData{
HeaderData: headerData{
CSRF: csrf.TemplateField(r),
Expand All @@ -60,7 +67,7 @@ func (ui *GUI) renderIndex(w http.ResponseWriter, r *http.Request, modalError st
},
RewardQuotas: rewardQuotas,
MinedWork: confirmedWork,
MinerPort: ui.cfg.MinerPort,
MinerPort: minerPort,
ModalError: modalError,
Address: address,
}
Expand Down
20 changes: 10 additions & 10 deletions pool/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type connection struct {

// Endpoint represents a stratum endpoint.
type Endpoint struct {
port uint32
listenAddr string
connCh chan *connection
discCh chan struct{}
listener net.Listener
Expand All @@ -82,17 +82,17 @@ type Endpoint struct {
}

// NewEndpoint creates an new miner endpoint.
func NewEndpoint(eCfg *EndpointConfig, port uint32) (*Endpoint, error) {
func NewEndpoint(eCfg *EndpointConfig, listenAddr string) (*Endpoint, error) {
endpoint := &Endpoint{
port: port,
cfg: eCfg,
clients: make(map[string]*Client),
connCh: make(chan *connection, bufferSize),
discCh: make(chan struct{}, bufferSize),
listenAddr: listenAddr,
cfg: eCfg,
clients: make(map[string]*Client),
connCh: make(chan *connection, bufferSize),
discCh: make(chan struct{}, bufferSize),
}
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "0.0.0.0", endpoint.port))
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
desc := fmt.Sprintf("unable to create endpoint on port %d", port)
desc := fmt.Sprintf("unable to create endpoint on %s", listenAddr)
return nil, errs.PoolError(errs.Listener, desc)
}
endpoint.listener = listener
Expand All @@ -110,7 +110,7 @@ func (e *Endpoint) removeClient(c *Client) {
// listen accepts incoming client connections on the endpoint.
// It must be run as a goroutine.
func (e *Endpoint) listen() {
log.Infof("listening on :%d", e.port)
log.Infof("listening on %s", e.listenAddr)
for {
conn, err := e.listener.Accept()
if err != nil {
Expand Down
10 changes: 3 additions & 7 deletions pool/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package pool
import (
"context"
"errors"
"fmt"
"math"
"math/big"
"net"
Expand Down Expand Up @@ -75,8 +74,7 @@ func testEndpoint(t *testing.T) {
MonitorCycle: time.Minute,
MaxUpgradeTries: 5,
}
port := uint32(3030)
endpoint, err := NewEndpoint(eCfg, port)
endpoint, err := NewEndpoint(eCfg, "0.0.0.0:3030")
if err != nil {
t.Fatalf("[NewEndpoint] unexpected error: %v", err)
}
Expand All @@ -86,8 +84,7 @@ func testEndpoint(t *testing.T) {
go endpoint.run(ctx)
time.Sleep(time.Millisecond * 100)

laddr, err := net.ResolveTCPAddr("tcp",
fmt.Sprintf("%s:%d", "127.0.0.1", port+1))
laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:3031")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down Expand Up @@ -219,8 +216,7 @@ func testEndpoint(t *testing.T) {
}

// Ensure the endpoint listener can create connections.
ep, err := net.ResolveTCPAddr("tcp",
fmt.Sprintf("%s:%d", "127.0.0.1", port))
ep, err := net.ResolveTCPAddr("tcp", "127.0.0.1:3030")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down
6 changes: 3 additions & 3 deletions pool/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ type HubConfig struct {
AdminPass string
// NonceIterations returns the possible header nonce iterations.
NonceIterations float64
// MinerPort represents the miner connection port for the pool.
MinerPort uint32
// MinerListen represents the listening address for miner connections.
MinerListen string
// MaxConnectionsPerHost represents the maximum number of connections
// allowed per host.
MaxConnectionsPerHost uint32
Expand Down Expand Up @@ -329,7 +329,7 @@ func NewHub(cancel context.CancelFunc, hcfg *HubConfig) (*Hub, error) {
MaxUpgradeTries: h.cfg.MaxUpgradeTries,
}

h.endpoint, err = NewEndpoint(eCfg, h.cfg.MinerPort)
h.endpoint, err = NewEndpoint(eCfg, h.cfg.MinerListen)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pool/hub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func testHub(t *testing.T) {
PoolFeeAddrs: []dcrutil.Address{poolFeeAddrs},
MaxConnectionsPerHost: 10,
NonceIterations: iterations,
MinerPort: 5050,
MinerListen: "127.0.0.1:5050",
WalletAccount: 69,
MonitorCycle: time.Minute,
MaxUpgradeTries: 5,
Expand Down