123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- package config
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strconv"
- "github.com/adrg/xdg"
- "github.com/asaskevich/govalidator"
- "github.com/claudiodangelis/qrcp/util"
- "github.com/manifoldco/promptui"
- )
- // Config of qrcp
- type Config struct {
- FQDN string `json:"fqdn"`
- Interface string `json:"interface"`
- Port int `json:"port"`
- KeepAlive bool `json:"keepAlive"`
- Path string `json:"path"`
- Secure bool `json:"secure"`
- TLSKey string `json:"tls-key"`
- TLSCert string `json:"tls-cert"`
- Output string `json:"output"`
- }
- var configFile string
- // Options of the qrcp configuration
- type Options struct {
- Interface string
- Port int
- Path string
- FQDN string
- KeepAlive bool
- Interactive bool
- ListAllInterfaces bool
- Secure bool
- TLSCert string
- TLSKey string
- Output string
- }
- func chooseInterface(opts Options) (string, error) {
- interfaces, err := util.Interfaces(opts.ListAllInterfaces)
- if err != nil {
- return "", err
- }
- if len(interfaces) == 0 {
- return "", errors.New("no interfaces found")
- }
- if len(interfaces) == 1 && !opts.Interactive {
- for name := range interfaces {
- fmt.Printf("only one interface found: %s, using this one\n", name)
- return name, nil
- }
- }
- // Map for pretty printing
- m := make(map[string]string)
- items := []string{}
- for name, ip := range interfaces {
- label := fmt.Sprintf("%s (%s)", name, ip)
- m[label] = name
- items = append(items, label)
- }
- // Add the "any" interface
- anyIP := "0.0.0.0"
- anyName := "any"
- anyLabel := fmt.Sprintf("%s (%s)", anyName, anyIP)
- m[anyLabel] = anyName
- items = append(items, anyLabel)
- prompt := promptui.Select{
- Items: items,
- Label: "Choose interface",
- }
- _, result, err := prompt.Run()
- if err != nil {
- return "", err
- }
- return m[result], nil
- }
- // Load a new configuration
- func Load(opts Options) (Config, error) {
- var cfg Config
- // Read the configuration file, if it exists
- if file, err := ioutil.ReadFile(configFile); err == nil {
- // Read the config
- if err := json.Unmarshal(file, &cfg); err != nil {
- return cfg, err
- }
- }
- // Prompt if needed
- if cfg.Interface == "" {
- iface, err := chooseInterface(opts)
- if err != nil {
- return cfg, err
- }
- cfg.Interface = iface
- // Write config
- if err := write(cfg); err != nil {
- return cfg, err
- }
- }
- return cfg, nil
- }
- // Wizard starts an interactive configuration managements
- func Wizard(path string, listAllInterfaces bool) error {
- if err := setConfigFile(path); err != nil {
- return err
- }
- var cfg Config
- if file, err := ioutil.ReadFile(configFile); err == nil {
- // Read the config
- if err := json.Unmarshal(file, &cfg); err != nil {
- return err
- }
- }
- // Ask for interface
- opts := Options{
- Interactive: true,
- ListAllInterfaces: listAllInterfaces,
- }
- iface, err := chooseInterface(opts)
- if err != nil {
- return err
- }
- cfg.Interface = iface
- // Ask for fully qualified domain name
- validateFqdn := func(input string) error {
- if input != "" && !govalidator.IsDNSName(input) {
- return errors.New("invalid domain")
- }
- return nil
- }
- promptFqdn := promptui.Prompt{
- Validate: validateFqdn,
- Label: "Choose fully-qualified domain name",
- Default: "",
- }
- if promptFqdnString, err := promptFqdn.Run(); err == nil {
- cfg.FQDN = promptFqdnString
- }
- // Ask for port
- validatePort := func(input string) error {
- _, err := strconv.ParseUint(input, 10, 16)
- if err != nil {
- return errors.New("invalid number")
- }
- return nil
- }
- promptPort := promptui.Prompt{
- Validate: validatePort,
- Label: "Choose port, 0 means random port",
- Default: fmt.Sprintf("%d", cfg.Port),
- }
- if promptPortResultString, err := promptPort.Run(); err == nil {
- if port, err := strconv.ParseUint(promptPortResultString, 10, 16); err == nil {
- cfg.Port = int(port)
- }
- }
- validateIsDir := func(input string) error {
- if input == "" {
- return nil
- }
- path, err := filepath.Abs(input)
- if err != nil {
- return err
- }
- f, err := os.Stat(path)
- if err != nil {
- return err
- }
- if !f.IsDir() {
- return errors.New("path is not a directory")
- }
- return nil
- }
- promptOutput := promptui.Prompt{
- Label: "Choose default output directory for received files, empty does not set a default",
- Default: cfg.Output,
- Validate: validateIsDir,
- }
- if promptOutputResultString, err := promptOutput.Run(); err == nil {
- if promptOutputResultString != "" {
- p, _ := filepath.Abs(promptOutputResultString)
- cfg.Output = p
- }
- }
- // Ask for path
- promptPath := promptui.Prompt{
- Label: "Choose path, empty means random",
- Default: cfg.Path,
- }
- if promptPathResultString, err := promptPath.Run(); err == nil {
- if promptPathResultString != "" {
- cfg.Path = promptPathResultString
- }
- }
- // Ask for keep alive
- promptKeepAlive := promptui.Select{
- Items: []string{"No", "Yes"},
- Label: "Should the server keep alive after transferring?",
- }
- if _, promptKeepAliveResultString, err := promptKeepAlive.Run(); err == nil {
- if promptKeepAliveResultString == "Yes" {
- cfg.KeepAlive = true
- } else {
- cfg.KeepAlive = false
- }
- }
- // TLS
- promptSecure := promptui.Select{
- Items: []string{"No", "Yes"},
- Label: "Should files be securely transferred with HTTPS?",
- }
- if _, promptSecureResultString, err := promptSecure.Run(); err == nil {
- if promptSecureResultString == "Yes" {
- cfg.Secure = true
- } else {
- cfg.Secure = false
- }
- }
- pathIsReadable := func(input string) error {
- if input == "" {
- return nil
- }
- path, err := filepath.Abs(util.Expand(input))
- if err != nil {
- return err
- }
- fmt.Println(path)
- fileinfo, err := os.Stat(path)
- if err != nil {
- return err
- }
- if fileinfo.Mode().IsDir() {
- return fmt.Errorf(fmt.Sprintf("%s is a directory", input))
- }
- return nil
- }
- // TLS Cert
- promptTLSCert := promptui.Prompt{
- Label: "Choose TLS certificate path. Empty if not using HTTPS.",
- Default: cfg.TLSCert,
- Validate: pathIsReadable,
- }
- if promptTLSCertString, err := promptTLSCert.Run(); err == nil {
- cfg.TLSCert = util.Expand(promptTLSCertString)
- }
- // TLS key
- promptTLSKey := promptui.Prompt{
- Label: "Choose TLS certificate key. Empty if not using HTTPS.",
- Default: cfg.TLSKey,
- Validate: pathIsReadable,
- }
- if promptTLSKeyString, err := promptTLSKey.Run(); err == nil {
- cfg.TLSKey = util.Expand(promptTLSKeyString)
- }
- // Write it down
- if err := write(cfg); err != nil {
- return err
- }
- b, err := json.MarshalIndent(cfg, "", " ")
- if err != nil {
- return err
- }
- fmt.Printf("Configuration updated:\n%s\n", string(b))
- return nil
- }
- // write the configuration file to disk
- func write(cfg Config) error {
- j, err := json.MarshalIndent(cfg, "", " ")
- if err != nil {
- return err
- }
- if err := ioutil.WriteFile(configFile, j, 0644); err != nil {
- return err
- }
- return nil
- }
- func pathExists(path string) bool {
- _, err := os.Stat(path)
- return !os.IsNotExist(err)
- }
- func setConfigFile(path string) error {
- // If not explicitly set then use the default
- if path == "" {
- // First try legacy location
- var legacyConfigFile = filepath.Join(xdg.Home, ".qrcp.json")
- if pathExists(legacyConfigFile) {
- configFile = legacyConfigFile
- return nil
- }
- // Else use modern location, first ensuring that the directory
- // exists
- var configDir = filepath.Join(xdg.ConfigHome, "qrcp")
- if !pathExists(configDir) {
- if err := os.Mkdir(configDir, 0744); err != nil {
- panic(err)
- }
- }
- configFile = filepath.Join(configDir, "config.json")
- return nil
- }
- absolutepath, err := filepath.Abs(path)
- if err != nil {
- return err
- }
- fileinfo, err := os.Stat(absolutepath)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- if fileinfo != nil && fileinfo.IsDir() {
- return fmt.Errorf("%s is not a file", absolutepath)
- }
- configFile = absolutepath
- return nil
- }
- // New returns a new configuration struct. It loads defaults, then overrides
- // values if any.
- func New(path string, opts Options) (Config, error) {
- var cfg Config
- // Set configFile
- if err := setConfigFile(path); err != nil {
- return cfg, err
- }
- // Load saved file / defaults
- cfg, err := Load(opts)
- if err != nil {
- return cfg, err
- }
- if opts.Interface != "" {
- cfg.Interface = opts.Interface
- }
- if opts.FQDN != "" {
- if !govalidator.IsDNSName(opts.FQDN) {
- return cfg, errors.New("invalid value for fully-qualified domain name")
- }
- cfg.FQDN = opts.FQDN
- }
- if opts.Port != 0 {
- cfg.Port = opts.Port
- } else if portVal, ok := os.LookupEnv("QRCP_PORT"); ok {
- port, err := strconv.Atoi(portVal)
- if err != nil {
- return cfg, errors.New("could not parse port from environment variable QRCP_PORT")
- }
- cfg.Port = port
- }
- if cfg.Port != 0 && !govalidator.IsPort(fmt.Sprintf("%d", cfg.Port)) {
- return cfg, fmt.Errorf("%d is not a valid port", cfg.Port)
- }
- if opts.KeepAlive {
- cfg.KeepAlive = true
- }
- if opts.Path != "" {
- cfg.Path = opts.Path
- }
- if opts.Secure {
- cfg.Secure = true
- }
- if opts.TLSCert != "" {
- cfg.TLSCert = opts.TLSCert
- }
- if opts.TLSKey != "" {
- cfg.TLSKey = opts.TLSKey
- }
- if opts.Output != "" {
- cfg.Output = opts.Output
- }
- return cfg, nil
- }
|