decred.org/dcrdex@v1.0.5/server/cmd/dexadm/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"os/signal"
    14  	"path/filepath"
    15  
    16  	"decred.org/dcrdex/client/app"
    17  	"decred.org/dcrdex/dex"
    18  	"decred.org/dcrdex/server/comms"
    19  	"github.com/decred/dcrd/dcrutil/v4"
    20  	"github.com/go-chi/chi/v5"
    21  )
    22  
    23  const (
    24  	configFilenameMainnet = "dexadm.conf"
    25  	configFilenameTestnet = "dexadm_testnet.conf"
    26  	configFilenameHarness = "dexadm_harness.conf"
    27  	defaultPort           = "31043"
    28  )
    29  
    30  var (
    31  	defaultApplicationDirectory = dcrutil.AppDataDir("dexadm", false)
    32  )
    33  
    34  func main() {
    35  	if err := mainErr(); err != nil {
    36  		fmt.Fprint(os.Stderr, err, "\n")
    37  		os.Exit(1)
    38  	}
    39  	os.Exit(0)
    40  }
    41  
    42  func mainErr() error {
    43  	ctx, cancel := context.WithCancel(context.Background())
    44  	defer cancel()
    45  
    46  	comms.UseLogger(dex.StdOutLogger("SRV", dex.LevelInfo))
    47  
    48  	cfg, err := configure()
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	killChan := make(chan os.Signal, 1)
    54  	signal.Notify(killChan, os.Interrupt)
    55  	go func() {
    56  		<-killChan
    57  		fmt.Println("Shutting down...")
    58  		cancel()
    59  	}()
    60  
    61  	uri, err := url.Parse(cfg.AdminSrvURL)
    62  	if err != nil {
    63  		return fmt.Errorf("error parsing adminsrvurl: %w", err)
    64  	}
    65  
    66  	cl := http.DefaultClient
    67  
    68  	if len(cfg.AdminSrvCertPath) > 0 {
    69  		certB, err := os.ReadFile(cfg.AdminSrvCertPath)
    70  		if err != nil {
    71  			return fmt.Errorf("error reading certificate file: %w", err)
    72  		}
    73  		rootCAs, _ := x509.SystemCertPool()
    74  		if rootCAs == nil {
    75  			rootCAs = x509.NewCertPool()
    76  		}
    77  		if ok := rootCAs.AppendCertsFromPEM(certB); !ok {
    78  			return fmt.Errorf("error appending certificate: %w", err)
    79  		}
    80  		cl.Transport = &http.Transport{
    81  			TLSClientConfig: &tls.Config{
    82  				RootCAs:    rootCAs,
    83  				MinVersion: tls.VersionTLS12,
    84  				ServerName: uri.Hostname(),
    85  			},
    86  		}
    87  	}
    88  
    89  	srv, err := comms.NewServer(&comms.RPCConfig{
    90  		ListenAddrs: []string{"127.0.0.1:" + cfg.Port},
    91  		NoTLS:       true,
    92  	})
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	mux := srv.Mux()
    98  	fileServer(mux, "/", "index.html", "text/html")
    99  	fileServer(mux, "/script.js", "script.js", "text/javascript")
   100  
   101  	// Everything else goes to the admin server.
   102  	mux.NotFound(func(w http.ResponseWriter, r *http.Request) {
   103  		w.Header().Set("Connection", "close")
   104  		w.Header().Set("Content-Type", "application/json; charset=utf-8")
   105  		r.Close = true
   106  
   107  		uri := cfg.AdminSrvURL + "/api" + r.URL.Path + "?" + r.URL.RawQuery
   108  		req, err := http.NewRequest(r.Method, uri, r.Body)
   109  		if err != nil {
   110  			http.Error(w, fmt.Sprintf("Error constructing request: %v", err), http.StatusInternalServerError)
   111  			return
   112  		}
   113  
   114  		// Encode username and password
   115  		auth := cfg.AdminSrvUsername + ":" + cfg.AdminSrvPassword
   116  		encodedAuth := base64.StdEncoding.EncodeToString([]byte(auth))
   117  
   118  		// Set the Authorization header
   119  		req.Header = r.Header
   120  		req.Header.Add("Authorization", "Basic "+encodedAuth)
   121  
   122  		resp, err := cl.Do(req)
   123  		if err != nil {
   124  			http.Error(w, fmt.Sprintf("Request error: %v", err), http.StatusInternalServerError)
   125  			return
   126  		}
   127  		defer resp.Body.Close()
   128  
   129  		b, _ := io.ReadAll(resp.Body)
   130  		w.WriteHeader(resp.StatusCode)
   131  		if len(b) > 0 {
   132  			_, err = w.Write(append(b, byte('\n')))
   133  			if err != nil {
   134  				fmt.Printf("Write error: %v \n", err)
   135  			}
   136  		}
   137  	})
   138  
   139  	fmt.Println("Open http://127.0.0.1:" + cfg.Port + " in your browser")
   140  	srv.Run(ctx)
   141  
   142  	return nil
   143  }
   144  
   145  // writeJSON writes marshals the provided interface and writes the bytes to the
   146  func fileServer(r chi.Router, pattern, filePath, contentType string) {
   147  	// Define a http.HandlerFunc to serve files but not directory indexes.
   148  	hf := func(w http.ResponseWriter, r *http.Request) {
   149  		// Ensure the path begins with "/".
   150  
   151  		w.Header().Set("Content-Type", contentType)
   152  
   153  		// Generate the full file system path and test for existence.
   154  		fi, err := os.Stat(filePath)
   155  		if err != nil {
   156  			http.NotFound(w, r)
   157  			return
   158  		}
   159  
   160  		// Deny directory listings
   161  		if fi.IsDir() {
   162  			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
   163  			return
   164  		}
   165  
   166  		http.ServeFile(w, r, filePath)
   167  	}
   168  
   169  	// Mount the http.HandlerFunc on the pathPrefix.
   170  	r.Get(pattern, hf)
   171  }
   172  
   173  type Config struct {
   174  	AppData          string `long:"appdata" description:"Path to application directory."`
   175  	ConfigPath       string `long:"config" description:"Path to an INI configuration file."`
   176  	Testnet          bool   `long:"testnet" description:"use testnet"`
   177  	Harness          bool   `long:"harness" description:"use simnet harness"`
   178  	Port             string `long:"port" description:"Web server port"`
   179  	AdminSrvURL      string `long:"adminsrvurl" description:"Administration HTTPS server address (default: 127.0.0.1:6542)."`
   180  	AdminSrvUsername string `long:"adminsrvuser" description:"Username to user for authentication"`
   181  	AdminSrvPassword string `long:"adminsrvpass" description:"Admin server password. INSECURE. Do not set unless absolutely necessary."`
   182  	AdminSrvCertPath string `long:"adminsrvcertpath" description:"TLS certificate for connecting to the admin server"`
   183  }
   184  
   185  var DefaultConfig = Config{
   186  	AppData:          defaultApplicationDirectory,
   187  	AdminSrvUsername: "u",
   188  	Port:             defaultPort,
   189  }
   190  
   191  func configure() (*Config, error) {
   192  	// Pre-parse the command line options to see if an alternative config file
   193  	// or the version flag was specified. Override any environment variables
   194  	// with parsed command line flags.
   195  	preCfg := DefaultConfig
   196  	if err := app.ParseCLIConfig(&preCfg); err != nil {
   197  		return nil, err
   198  	}
   199  
   200  	if preCfg.AppData != defaultApplicationDirectory {
   201  		preCfg.AppData = dex.CleanAndExpandPath(preCfg.AppData)
   202  		// If the app directory has been changed, but the config file path hasn't,
   203  		// reform the config file path with the new directory.
   204  	}
   205  	if preCfg.ConfigPath == "" {
   206  		switch {
   207  		case preCfg.Harness:
   208  			preCfg.ConfigPath = configFilenameHarness
   209  		case preCfg.Testnet:
   210  			preCfg.ConfigPath = filepath.Join(preCfg.AppData, configFilenameTestnet)
   211  		default: //mainnet
   212  			preCfg.ConfigPath = filepath.Join(preCfg.AppData, configFilenameMainnet)
   213  		}
   214  	}
   215  	configPath := dex.CleanAndExpandPath(preCfg.ConfigPath)
   216  
   217  	// Load additional config from file.
   218  	cfg := DefaultConfig
   219  	if err := app.ParseFileConfig(configPath, &cfg); err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	if cfg.AdminSrvCertPath == "" {
   224  		return nil, fmt.Errorf("no adminsrvaddr argument in file or by command-line")
   225  	}
   226  
   227  	if cfg.AdminSrvPassword == "" {
   228  		return nil, fmt.Errorf("no adminsrvpass argument in file or by command-line")
   229  	}
   230  
   231  	if cfg.AdminSrvCertPath != "" {
   232  		cfg.AdminSrvCertPath = dex.CleanAndExpandPath(cfg.AdminSrvCertPath)
   233  	}
   234  
   235  	return &cfg, nil
   236  }