config.go raw

   1  // Package podopts is a configuration system to fit with the all-in-one philosophy guiding the design of the parallelcoin
   2  // pod.
   3  //
   4  // The configuration is stored by each component of the connected applications, so all data is stored in concurrent-safe
   5  // atomics, and there is a facility to invoke a function in response to a new value written into a field by other
   6  // threads.
   7  //
   8  // There is a custom JSON marshal/unmarshal for each field type and for the whole configuration that only saves values
   9  // that differ from the defaults, similar to 'omitempty' in struct tags but where 'empty' is the default value instead
  10  // of the default zero created by Go's memory allocator. This enables easy compositing of multiple sources.
  11  //
  12  package config
  13  
  14  import (
  15  	"encoding/hex"
  16  	"encoding/json"
  17  	"fmt"
  18  	"io/ioutil"
  19  	"os"
  20  	"os/user"
  21  	"path/filepath"
  22  	"reflect"
  23  	"sort"
  24  	"strings"
  25  	"time"
  26  	"unicode/utf8"
  27  
  28  	"lukechampine.com/blake3"
  29  
  30  	"github.com/p9c/p9/pkg/log"
  31  	"github.com/p9c/p9/pkg/apputil"
  32  	"github.com/p9c/p9/pkg/constant"
  33  	"github.com/p9c/p9/pkg/opts/binary"
  34  	"github.com/p9c/p9/pkg/opts/cmds"
  35  	"github.com/p9c/p9/pkg/opts/duration"
  36  	"github.com/p9c/p9/pkg/opts/float"
  37  	"github.com/p9c/p9/pkg/opts/integer"
  38  	"github.com/p9c/p9/pkg/opts/list"
  39  	"github.com/p9c/p9/pkg/opts/opt"
  40  	"github.com/p9c/p9/pkg/opts/text"
  41  )
  42  
  43  // Configs is the source location for the Config items, which is used to generate the Config struct
  44  type Configs map[string]opt.Option
  45  type ConfigSliceElement struct {
  46  	Opt  opt.Option
  47  	Name string
  48  }
  49  type ConfigSlice []ConfigSliceElement
  50  
  51  func (c ConfigSlice) Len() int           { return len(c) }
  52  func (c ConfigSlice) Less(i, j int) bool { return c[i].Name < c[j].Name }
  53  func (c ConfigSlice) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
  54  
  55  // Initialize loads in configuration from disk and from environment on top of the default base
  56  func (c *Config) Initialize(hf func(ifc interface{}) error) (e error) {
  57  	// the several places configuration is sourced from are overlaid in the following order:
  58  	// default -> config file -> environment variables -> commandline flags
  59  	T.Ln("initializing configuration...")
  60  	// first lint the configuration
  61  	var aos map[string][]string
  62  	if aos, e = getAllOptionStrings(c); E.Chk(e) {
  63  		return
  64  	}
  65  	// this function will panic if there is potential for ambiguity in the commandline configuration args.
  66  	T.Ln("linting configuration items")
  67  	if _, e = findConflictingItems(aos); E.Chk(e) {
  68  	}
  69  	// generate and add the help commands to the help tree
  70  	c.GetHelp(hf)
  71  	// process the commandline
  72  	T.Ln("processing commandline arguments", os.Args[1:])
  73  	var cm *cmds.Command
  74  	var options []opt.Option
  75  	var optVals []string
  76  	if c.ExtraArgs, cm, options, optVals, e = c.processCommandlineArgs(os.Args[1:]); E.Chk(e) {
  77  		return
  78  	}
  79  	// I.S(options)
  80  	if cm != nil {
  81  		c.RunningCommand = *cm
  82  	}
  83  	// if the user sets the configfile directly, or the datadir on the commandline we need to load it from that path
  84  	T.Ln("checking from where to load the configuration file")
  85  	datadir := c.DataDir.V()
  86  	var configPath string
  87  	for i := range options {
  88  		if options[i].Name() == "configfile" {
  89  			if _, e = options[i].ReadInput(optVals[i]); E.Chk(e) {
  90  				configPath = optVals[i]
  91  			}
  92  		}
  93  		if options[i].Name() == "datadir" {
  94  			I.Ln("datadir was set", optVals[i])
  95  			if _, e = options[i].ReadInput(optVals[i]); !E.Chk(e) {
  96  				datadir = options[i].Type().(*text.Opt).V()
  97  				// reset all defaults that base on the datadir to apply hereafter
  98  				// if the value is default, update it to the new datadir, and update the default field, otherwise assume
  99  				// it has been set in the commandline args, and if it is different in environment or config file
 100  				// it will be loaded in next, with the command line option value overriding at the end.
 101  				if c.CAFile.V() == c.CAFile.Def {
 102  					e = c.CAFile.Set(filepath.Join(datadir, "ca.cert"))
 103  				}
 104  				c.CAFile.Def = filepath.Join(datadir, "ca.cert")
 105  				if c.ConfigFile.V() == c.ConfigFile.Def {
 106  					e = c.ConfigFile.Set(filepath.Join(datadir, "pod.json"))
 107  				}
 108  				c.ConfigFile.Def = filepath.Join(datadir, constant.PodConfigFilename)
 109  				if c.RPCKey.V() == c.RPCKey.Def {
 110  					e = c.RPCKey.Set(filepath.Join(datadir, "rpc.key"))
 111  				}
 112  				c.RPCKey.Def = filepath.Join(datadir, "rpc.key")
 113  				if c.RPCCert.V() == c.RPCCert.Def {
 114  					e = c.RPCCert.Set(filepath.Join(datadir, "rpc.cert"))
 115  				}
 116  				c.RPCCert.Def = filepath.Join(datadir, "rpc.cert")
 117  			}
 118  		}
 119  	}
 120  	for i := range options {
 121  		if options[i].Name() == "network" {
 122  			I.Ln("network was set", optVals[i])
 123  			if c.WalletFile.V() == c.WalletFile.Def {
 124  				_, e = c.WalletFile.ReadInput(filepath.Join(datadir, optVals[i], "wallet.db"))
 125  			}
 126  			c.WalletFile.Def = filepath.Join(datadir, optVals[i], constant.DbName)
 127  			if c.LogDir.V() == c.LogDir.Def {
 128  				_, e = c.LogDir.ReadInput(filepath.Join(datadir, optVals[i]))
 129  			}
 130  			c.LogDir.Def = filepath.Join(datadir, optVals[i])
 131  		}
 132  	}
 133  	// load the configuration file into the config
 134  	resolvedConfigPath := c.ConfigFile.V()
 135  	if configPath != "" {
 136  		T.Ln("loading config from", configPath)
 137  		resolvedConfigPath = configPath
 138  	} else {
 139  		if datadir != "" {
 140  			if strings.HasPrefix(datadir, "~") {
 141  				var homeDir string
 142  				var usr *user.User
 143  				var e error
 144  				if usr, e = user.Current(); e == nil {
 145  					homeDir = usr.HomeDir
 146  				}
 147  				// Fall back to standard HOME environment variable that works for most POSIX OSes if the directory from the Go
 148  				// standard lib failed.
 149  				if e != nil || homeDir == "" {
 150  					homeDir = os.Getenv("HOME")
 151  				}
 152  
 153  				datadir = strings.Replace(datadir, "~", homeDir, 1)
 154  			}
 155  
 156  			if resolvedConfigPath, e = filepath.Abs(filepath.Clean(filepath.Join(datadir, constant.PodConfigFilename,
 157  			),
 158  			),
 159  			); E.Chk(e) {
 160  			}
 161  			T.Ln("loading config from", resolvedConfigPath)
 162  		}
 163  	}
 164  	var configExists bool
 165  	if e = c.loadConfig(resolvedConfigPath); !D.Chk(e) {
 166  		configExists = true
 167  	}
 168  	// read the environment variables into the config
 169  	I.Ln("reading environment variables...")
 170  	if e = c.loadEnvironment(); D.Chk(e) {
 171  	}
 172  	// read in the commandline options over top as they have highest priority
 173  	I.Ln("decoding option inputs")
 174  	for i := range options {
 175  		if _, e = options[i].ReadInput(optVals[i]); E.Chk(e) {
 176  		}
 177  	}
 178  	if !configExists || c.Save.True() {
 179  		c.Save.F()
 180  		// save the configuration file
 181  		I.Ln("saving configuration file...")
 182  		var j []byte
 183  		// c.ShowAll=true
 184  		if j, e = json.MarshalIndent(c, "", "    "); !E.Chk(e) {
 185  			I.F("saving config\n%s\n", string(j))
 186  			apputil.EnsureDir(resolvedConfigPath)
 187  			if e = ioutil.WriteFile(resolvedConfigPath, j, 0660); E.Chk(e) {
 188  				panic(e)
 189  			}
 190  		}
 191  		I.Ln("configuration file saved")
 192  	}
 193  	I.Ln("configuration initialised")
 194  	return
 195  }
 196  
 197  // loadEnvironment scans the environment variables for values relevant to pod
 198  func (c *Config) loadEnvironment() (e error) {
 199  	env := os.Environ()
 200  	c.ForEach(func(o opt.Option) bool {
 201  		varName := "POD_" + strings.ToUpper(o.Name())
 202  		for i := range env {
 203  			if strings.HasPrefix(env[i], varName) {
 204  				envVal := strings.Split(env[i], varName)[1]
 205  				if _, e = o.LoadInput(envVal); D.Chk(e) {
 206  				}
 207  			}
 208  		}
 209  		return true
 210  	},
 211  	)
 212  
 213  	return
 214  }
 215  
 216  // loadConfig loads the config from a file and unmarshals it into the config
 217  func (c *Config) loadConfig(path string) (e error) {
 218  	e = fmt.Errorf("no config found at %s", path)
 219  	var cf []byte
 220  	if !apputil.FileExists(path) {
 221  		return
 222  	} else if cf, e = ioutil.ReadFile(path); !D.Chk(e) {
 223  		I.Ln("read in file from", path)
 224  		if e = json.Unmarshal(cf, c); D.Chk(e) {
 225  			panic(e)
 226  		}
 227  		I.Ln("unmarshalled", path)
 228  		c.WalletPass.Set("")
 229  	}
 230  	return
 231  }
 232  
 233  // WriteToFile writes the current config to a file as json
 234  func (c *Config) WriteToFile(filename string) (e error) {
 235  	var j []byte
 236  	I.S(c.MulticastPass.Bytes())
 237  	wpp := c.WalletPass.Bytes()
 238  	wp := make([]byte, len(wpp))
 239  	copy(wp,wpp)
 240  	if len(wp) > 0 {
 241  		bhb := blake3.Sum256(wpp)
 242  		bh := hex.EncodeToString(bhb[:])
 243  		c.WalletPass.Set(bh)
 244  	}
 245  	if j, e = json.MarshalIndent(c, "", "  "); E.Chk(e) {
 246  		return
 247  	}
 248  	if e = ioutil.WriteFile(filename, j, 0660); E.Chk(e) {
 249  	}
 250  	if len(wp) > 0 {
 251  		c.WalletPass.SetBytes(wp)
 252  	}
 253  	return
 254  }
 255  
 256  // ForEach iterates the options in defined order with a closure that takes an opt.Option
 257  func (c *Config) ForEach(fn func(ifc opt.Option) bool) bool {
 258  	t := reflect.ValueOf(c)
 259  	t = t.Elem()
 260  	for i := 0; i < t.NumField(); i++ {
 261  		// asserting to an Option ensures we skip the ancillary fields
 262  		if iff, ok := t.Field(i).Interface().(opt.Option); ok {
 263  			if !fn(iff) {
 264  				return false
 265  			}
 266  		}
 267  	}
 268  	return true
 269  }
 270  
 271  // GetOption searches for a match amongst the podopts
 272  func (c *Config) GetOption(input string) (
 273  	op opt.Option, value string,
 274  	e error,
 275  ) {
 276  	T.Ln("checking arg for opt:", input)
 277  	found := false
 278  	if c.ForEach(func(ifc opt.Option) bool {
 279  		aos := ifc.GetAllOptionStrings()
 280  		for i := range aos {
 281  			if strings.HasPrefix(input, aos[i]) {
 282  				value = input[len(aos[i]):]
 283  				found = true
 284  				op = ifc
 285  				return false
 286  			}
 287  		}
 288  		return true
 289  	},
 290  	) {
 291  	}
 292  	if !found {
 293  		e = fmt.Errorf("opt not found")
 294  	}
 295  	return
 296  }
 297  
 298  // MarshalJSON implements the json marshaller for the config. It only stores non-default values so can be composited.
 299  func (c *Config) MarshalJSON() (b []byte, e error) {
 300  	outMap := make(map[string]interface{})
 301  	c.ForEach(
 302  		func(ifc opt.Option) bool {
 303  			switch ii := ifc.(type) {
 304  			case *binary.Opt:
 305  				if ii.True() == ii.Def && ii.Data.OmitEmpty && !c.ShowAll {
 306  					return true
 307  				}
 308  				outMap[ii.Option] = ii.True()
 309  			case *list.Opt:
 310  				v := ii.S()
 311  				if len(v) == len(ii.Def) && ii.Data.OmitEmpty && !c.ShowAll {
 312  					foundMismatch := false
 313  					for i := range v {
 314  						if v[i] != ii.Def[i] {
 315  							foundMismatch = true
 316  							break
 317  						}
 318  					}
 319  					if !foundMismatch {
 320  						return true
 321  					}
 322  				}
 323  				outMap[ii.Option] = v
 324  			case *float.Opt:
 325  				if ii.Value.Load() == ii.Def && ii.Data.OmitEmpty && !c.ShowAll {
 326  					return true
 327  				}
 328  				outMap[ii.Option] = ii.Value.Load()
 329  			case *integer.Opt:
 330  				if ii.Value.Load() == ii.Def && ii.Data.OmitEmpty && !c.ShowAll {
 331  					return true
 332  				}
 333  				outMap[ii.Option] = ii.Value.Load()
 334  			case *text.Opt:
 335  				v := string(ii.Value.Load().([]byte))
 336  				// fmt.Printf("def: '%s'", v)
 337  				// spew.Dump(ii.def)
 338  				if v == ii.Def && ii.Data.OmitEmpty && !c.ShowAll {
 339  					return true
 340  				}
 341  				outMap[ii.Option] = v
 342  			case *duration.Opt:
 343  				if ii.Value.Load() == ii.Def && ii.Data.OmitEmpty && !c.ShowAll {
 344  					return true
 345  				}
 346  				outMap[ii.Option] = fmt.Sprint(ii.Value.Load())
 347  			default:
 348  			}
 349  			return true
 350  		},
 351  	)
 352  	return json.Marshal(&outMap)
 353  }
 354  
 355  // UnmarshalJSON implements the Unmarshaller interface so it only writes to fields with those non-default values set.
 356  func (c *Config) UnmarshalJSON(data []byte) (e error) {
 357  	ifc := make(map[string]interface{})
 358  	if e = json.Unmarshal(data, &ifc); E.Chk(e) {
 359  		return
 360  	}
 361  	// I.S(ifc)
 362  	c.ForEach(func(iii opt.Option) bool {
 363  		switch ii := iii.(type) {
 364  		case *binary.Opt:
 365  			if i, ok := ifc[ii.Option]; ok {
 366  				var ir bool
 367  				if ir, ok = i.(bool); ir != ii.Def {
 368  					// I.Ln(ii.Option+":", i.(binary), "default:", ii.def, "prev:", c.Map[ii.Option].(*Opt).True())
 369  					ii.Set(ir)
 370  				}
 371  			}
 372  		case *list.Opt:
 373  			matched := true
 374  			if d, ok := ifc[ii.Option]; ok {
 375  				if ds, ok2 := d.([]interface{}); ok2 {
 376  					for i := range ds {
 377  						if len(ii.Def) >= len(ds) {
 378  							if ds[i] != ii.Def[i] {
 379  								matched = false
 380  								break
 381  							}
 382  						} else {
 383  							matched = false
 384  						}
 385  					}
 386  					if matched {
 387  						return true
 388  					}
 389  					// I.Ln(ii.Option+":", ds, "default:", ii.def, "prev:", c.Map[ii.Option].(*Opt).S())
 390  					ii.Set(ifcToStrings(ds))
 391  				}
 392  			}
 393  		case *float.Opt:
 394  			if d, ok := ifc[ii.Option]; ok {
 395  				// I.Ln(ii.Option+":", d.(float64), "default:", ii.def, "prev:", c.Map[ii.Option].(*Opt).V())
 396  				ii.Set(d.(float64))
 397  			}
 398  		case *integer.Opt:
 399  			if d, ok := ifc[ii.Option]; ok {
 400  				// I.Ln(ii.Option+":", int64(d.(float64)), "default:", ii.def, "prev:", c.Map[ii.Option].(*Opt).V())
 401  				ii.Set(int(d.(float64)))
 402  			}
 403  		case *text.Opt:
 404  			if d, ok := ifc[ii.Option]; ok {
 405  				if ds, ok2 := d.(string); ok2 {
 406  					if ds != ii.Def {
 407  						// I.Ln(ii.Option+":", d.(string), "default:", ii.def, "prev:", c.Map[ii.Option].(*Opt).V())
 408  						ii.Set(d.(string))
 409  					}
 410  				}
 411  			}
 412  		case *duration.Opt:
 413  			if d, ok := ifc[ii.Option]; ok {
 414  				var parsed time.Duration
 415  				parsed, e = time.ParseDuration(d.(string))
 416  				// I.Ln(ii.Option+":", parsed, "default:", ii.Opt(), "prev:", c.Map[ii.Option].(*Opt).V())
 417  				ii.Set(parsed)
 418  			}
 419  		default:
 420  		}
 421  		return true
 422  	},
 423  	)
 424  	return
 425  }
 426  
 427  func (c *Config) processCommandlineArgs(args []string) (
 428  	remArgs []string, cm *cmds.Command, op []opt.Option,
 429  	optVals []string, e error,
 430  ) {
 431  	// first we will locate all the commands specified to mark the 3 sections, opt, commands, and the remainder is
 432  	// arbitrary for the node
 433  	commands := make(map[int]cmds.Command)
 434  	var commandsStart, commandsEnd int = -1, -1
 435  	var found, helpFound bool
 436  	for i := range args {
 437  		T.Ln("checking for commands:", args[i], commandsStart, commandsEnd, "current arg index:", i)
 438  		var depth, dist int
 439  		if found, depth, dist, cm, e = c.Commands.Find(args[i], depth, dist, false); E.Chk(e) {
 440  			continue
 441  		}
 442  		if cm != nil {
 443  			if cm.Name == "help" {
 444  				helpFound = true
 445  			}
 446  			if cm.Parent != nil {
 447  				if cm.Parent.Name == "help" && !helpFound {
 448  					// I.S(commands)
 449  					if commands[0].Name != "help" {
 450  						found = false
 451  					}
 452  				}
 453  			}
 454  		}
 455  		if found {
 456  			if commandsStart == -1 {
 457  				commandsStart = i
 458  				commandsEnd = i + 1
 459  			}
 460  			if oc, ok := commands[depth]; ok {
 461  				if !helpFound {
 462  					e = fmt.Errorf("second command found at same depth '%s' and '%s'", oc.Name, cm.Name)
 463  					return
 464  				} else {
 465  					// if this is a command match after help is found it is a
 466  					// help command, resume the search
 467  					if cmdd, ok := commands[depth]; ok {
 468  						I.Ln("base level command is already occupied by",
 469  							cmdd.Name+"; marking end of commands")
 470  						// commandsEnd--
 471  						T.Ln("commandStart", commandsStart, commandsEnd, args[commandsStart:commandsEnd])
 472  						break
 473  					}
 474  					I.Ln("found help subcommand:", cm.Name, depth)
 475  					depth--
 476  					dist++
 477  					if found, depth, dist, cm, e = c.Commands.Find(args[i],
 478  						depth, dist, true); E.Chk(e) {
 479  						I.Ln("we didn't attach this help command")
 480  						continue
 481  					}
 482  					// I.S(cm)
 483  				}
 484  			}
 485  			commandsEnd = i + 1
 486  			T.Ln("commandStart", commandsStart, commandsEnd, args[commandsStart:commandsEnd])
 487  			T.Ln("found command", cm.Name, "argument number", i, "at depth", depth, "distance", dist)
 488  			commands[depth] = *cm
 489  		} else {
 490  			// commandsStart=i+1
 491  			// commandsEnd=i+1
 492  			// T.Ln("not found:", args[i], "commandStart", commandsStart, commandsEnd, args[commandsStart:commandsEnd])
 493  			T.Ln("argument", args[i], "is not a command", commandsStart, commandsEnd)
 494  			c.FoundArgs = append(c.FoundArgs, args[i])
 495  		}
 496  	}
 497  	// I.S(commands, cm)
 498  	// commandsEnd++
 499  	cmds := []int{}
 500  	if len(commands) == 0 {
 501  		I.Ln("setting default command")
 502  		commands[0] = c.Commands[0]
 503  		// I.S(commands[0])
 504  		ibs := commands[0]
 505  		cm = &ibs
 506  		log.AppColorizer = commands[0].Colorizer
 507  		log.App = commands[0].AppText
 508  	} else {
 509  		T.Ln("checking commands")
 510  		// I.S(commands)
 511  		for i := range commands {
 512  			cmds = append(cmds, i)
 513  		}
 514  		I.S(cmds)
 515  		if len(cmds) > 0 {
 516  			sort.Ints(cmds)
 517  			var cms []string
 518  			for i := range commands {
 519  				cms = append(cms, commands[i].Name)
 520  			}
 521  			if cmds[0] != 1 {
 522  				e = fmt.Errorf("commands must include base level item for disambiguation %v", cms)
 523  			}
 524  			prev := cmds[0]
 525  			for i := range cmds {
 526  				if i == 0 {
 527  					continue
 528  				}
 529  				if cmds[i] != prev+1 {
 530  					e = fmt.Errorf("more than one command specified, %v", cms)
 531  					return
 532  				}
 533  				found = false
 534  				for j := range commands[cmds[i-1]].Commands {
 535  					if commands[cmds[i]].Name == commands[cmds[i-1]].Commands[j].Name {
 536  						found = true
 537  					}
 538  				}
 539  				if !found {
 540  					e = fmt.Errorf("multiple commands are not a path on the command tree %v", cms)
 541  					return
 542  				}
 543  			}
 544  		}
 545  		T.Ln("commands:", commandsStart, commandsEnd)
 546  		T.Ln("length of gathered commands", len(commands))
 547  		if len(commands) == 1 {
 548  			for _, x := range commands {
 549  				cm = &x
 550  				if cm.Colorizer != nil {
 551  					log.AppColorizer = cm.Colorizer
 552  					log.App = cm.AppText
 553  				}
 554  			}
 555  		}
 556  	}
 557  	// if there was no command the commands start and end after all the args
 558  	if commandsStart < 0 || commandsEnd < 0 {
 559  		commandsStart = len(args)
 560  		commandsEnd = commandsStart
 561  	}
 562  	T.Ln("commands section:", commandsStart, commandsEnd)
 563  	if commandsStart > 0 {
 564  		T.Ln("opts found", args[:commandsStart])
 565  		// we have opts to check
 566  		for i := range args {
 567  			// if i == 0 {
 568  			// 	continue
 569  			// }
 570  			if i >= commandsStart {
 571  				break
 572  			}
 573  			var val string
 574  			var o opt.Option
 575  			if o, val, e = c.GetOption(args[i]); E.Chk(e) {
 576  				e = fmt.Errorf("argument %d: '%s' lacks a valid opt prefix", i, args[i])
 577  				return
 578  			}
 579  			if _, e = o.ReadInput(val); E.Chk(e) {
 580  				return
 581  			}
 582  			T.Ln("found opt:", o.String())
 583  			op = append(op, o)
 584  			optVals = append(optVals, val)
 585  		}
 586  	}
 587  	T.Ln("options to pass to child processes to match config:", args[:commandsStart])
 588  
 589  	if len(cmds) < 1 {
 590  		cmds = []int{0}
 591  		commands[0] = c.Commands[0]
 592  	}
 593  	if commandsEnd > 0 && len(args) > commandsEnd {
 594  		remArgs = args[commandsEnd:]
 595  	}
 596  	D.F("args that will pass to command: %v", remArgs)
 597  	// I.S(commands, cm)
 598  	// I.S(commands[cmds[len(cmds)-1]], op, args[commandsEnd:])
 599  	return
 600  }
 601  
 602  // ReadCAFile reads in the configured Certificate Authority for TLS connections
 603  func (c *Config) ReadCAFile() []byte {
 604  	// Read certificate file if TLS is not disabled.
 605  	var certs []byte
 606  	if c.ClientTLS.True() {
 607  		var e error
 608  		if certs, e = ioutil.ReadFile(c.CAFile.V()); E.Chk(e) {
 609  			// If there's an error reading the CA file, continue with nil certs and without the client connection.
 610  			certs = nil
 611  		}
 612  	} else {
 613  		I.Ln("chain server RPC TLS is disabled")
 614  	}
 615  	return certs
 616  }
 617  
 618  func ifcToStrings(ifc []interface{}) (o []string) {
 619  	for i := range ifc {
 620  		o = append(o, ifc[i].(string))
 621  	}
 622  	return
 623  }
 624  
 625  type details struct {
 626  	name, option, desc, def string
 627  	aliases                 []string
 628  	documentation           string
 629  }
 630  
 631  // GetHelp walks the command tree and gathers the options and creates a set of help functions for all commands and
 632  // options in the set
 633  func (c *Config) GetHelp(hf func(ifc interface{}) error) {
 634  	helpCommand := cmds.Command{
 635  		Name:       "help",
 636  		Title:      "prints information about how to use pod",
 637  		Entrypoint: hf,
 638  		Commands:   nil,
 639  	}
 640  	// first add all the options
 641  	c.ForEach(func(ifc opt.Option) bool {
 642  		o := fmt.Sprintf("Parallelcoin Pod All-in-One Suite\n\n")
 643  		var dt details
 644  		switch ii := ifc.(type) {
 645  		case *binary.Opt:
 646  			dt = details{
 647  				ii.GetMetadata().Name, ii.Option, ii.Description,
 648  				fmt.Sprint(ii.Def), ii.Aliases,
 649  				ii.Documentation,
 650  			}
 651  		case *list.Opt:
 652  			dt = details{
 653  				ii.GetMetadata().Name, ii.Option, ii.Description,
 654  				fmt.Sprint(ii.Def), ii.Aliases,
 655  				ii.Documentation,
 656  			}
 657  		case *float.Opt:
 658  			dt = details{
 659  				ii.GetMetadata().Name, ii.Option, ii.Description,
 660  				fmt.Sprint(ii.Def), ii.Aliases,
 661  				ii.Documentation,
 662  			}
 663  		case *integer.Opt:
 664  			dt = details{
 665  				ii.GetMetadata().Name, ii.Option, ii.Description,
 666  				fmt.Sprint(ii.Def), ii.Aliases,
 667  				ii.Documentation,
 668  			}
 669  		case *text.Opt:
 670  			dt = details{
 671  				ii.GetMetadata().Name, ii.Option, ii.Description,
 672  				fmt.Sprint(ii.Def), ii.Aliases,
 673  				ii.Documentation,
 674  			}
 675  		case *duration.Opt:
 676  			dt = details{
 677  				ii.GetMetadata().Name, ii.Option, ii.Description,
 678  				fmt.Sprint(ii.Def), ii.Aliases,
 679  				ii.Documentation,
 680  			}
 681  		}
 682  		allNames := append([]string{dt.option}, dt.aliases...)
 683  		for i := range allNames {
 684  			helpCommand.Commands = append(helpCommand.Commands, cmds.Command{
 685  				Name:  allNames[i],
 686  				Title: dt.desc,
 687  				Entrypoint: func(ifc interface{}) (e error) {
 688  					o += fmt.Sprintf(
 689  						"Help information about %s\n\n"+
 690  							"\toption name:\n\t\t%s\n"+
 691  							"\taliases:\n\t\t%s\n"+
 692  							"\tdescription:\n\t\t%s\n"+
 693  							"\tdefault:\n\t\t%v\n",
 694  						dt.name, dt.option, dt.aliases, dt.desc, dt.def,
 695  					)
 696  					if dt.documentation != "" {
 697  						o += "\tdocumentation:\n\t\t" + dt.documentation + "\n\n"
 698  					}
 699  					fmt.Fprint(os.Stderr, o)
 700  					return
 701  				},
 702  				Commands: nil,
 703  				Parent:   &helpCommand,
 704  			},
 705  			)
 706  		}
 707  		// for i := range
 708  		return true
 709  	},
 710  	)
 711  	// next add all the commands
 712  	c.Commands.ForEach(func(cm cmds.Command) bool {
 713  		helpCommand.Commands = append(helpCommand.Commands, cmds.Command{
 714  			Name:        cm.Name,
 715  			Title:       cm.Title,
 716  			Description: cm.Description,
 717  			Entrypoint: func(ifc interface{}) (e error) {
 718  				o := fmt.Sprintf(
 719  					"Help information about command '%s'\n\n"+
 720  						"%s\n\n",
 721  					cm.Name, cm.Title,
 722  				)
 723  				if cm.Description != "" {
 724  					split := strings.Split(cm.Description, "\n")
 725  					docs := "\t" + strings.Join(split, "\n\n\t")
 726  					o += docs + "\n\n"
 727  				}
 728  				o += "Related options:\n\n"
 729  				descs := make(map[string]string)
 730  				c.ForEach(func(ifc opt.Option) bool {
 731  					meta := ifc.GetMetadata()
 732  					found := false
 733  					for i := range meta.Tags {
 734  						if meta.Tags[i] == cm.Name {
 735  							found = true
 736  						}
 737  					}
 738  					if !found {
 739  						// skip item as it isn't tagged with this program name
 740  						return true
 741  					}
 742  					oo := fmt.Sprintf("\t%s %v", meta.Option, meta.Aliases)
 743  					nrunes := utf8.RuneCountInString(oo)
 744  					var def string
 745  					switch ii := ifc.(type) {
 746  					case *binary.Opt:
 747  						def = fmt.Sprint(ii.Def)
 748  					case *list.Opt:
 749  						def = fmt.Sprint(ii.Def)
 750  					case *float.Opt:
 751  						def = fmt.Sprint(ii.Def)
 752  					case *integer.Opt:
 753  						def = fmt.Sprint(ii.Def)
 754  					case *text.Opt:
 755  						def = fmt.Sprint(ii.Def)
 756  					case *duration.Opt:
 757  						def = fmt.Sprint(ii.Def)
 758  					}
 759  					descs[meta.Group] += oo + fmt.Sprintf(strings.Repeat(" ", 32-nrunes)+"%s, default: %s\n", meta.Description, def)
 760  					return true
 761  				},
 762  				)
 763  				var cats []string
 764  				for i := range descs {
 765  					cats = append(cats, i)
 766  				}
 767  				// I.S(cats)
 768  				sort.Strings(cats)
 769  				for i := range cats {
 770  					if cats[i] != "" {
 771  						o += "\n" + cats[i] + "\n"
 772  					}
 773  					o += descs[cats[i]]
 774  				}
 775  				fmt.Fprint(os.Stderr, o)
 776  				return
 777  			},
 778  			Parent: &helpCommand,
 779  		})
 780  		return true
 781  	}, 0, 0,
 782  	)
 783  	c.Commands = append(c.Commands, helpCommand)
 784  	return
 785  }
 786  
 787  func getAllOptionStrings(c *Config) (s map[string][]string, e error) {
 788  	s = make(map[string][]string)
 789  	if c.ForEach(func(ifc opt.Option) bool {
 790  		md := ifc.GetMetadata()
 791  		if _, ok := s[ifc.Name()]; ok {
 792  			e = fmt.Errorf("conflicting opt names: %v %v", ifc.GetAllOptionStrings(), s[ifc.Name()])
 793  			return false
 794  		}
 795  		s[ifc.Name()] = md.GetAllOptionStrings()
 796  		return true
 797  	},
 798  	) {
 799  	}
 800  	// s["commandslist"] = c.Commands.GetAllCommands()
 801  	return
 802  }
 803  
 804  func findConflictingItems(valOpts map[string][]string) (o []string, e error) {
 805  	var ss, ls string
 806  	for i := range valOpts {
 807  		for j := range valOpts {
 808  			if i == j {
 809  				continue
 810  			}
 811  			a := valOpts[i]
 812  			b := valOpts[j]
 813  			for ii := range a {
 814  				for jj := range b {
 815  					ss, ls = shortestString(a[ii], b[jj])
 816  					if ss == ls[:len(ss)] {
 817  						E.F("conflict between %s and %s, ", ss, ls)
 818  						o = append(o, ss, ls)
 819  					}
 820  				}
 821  			}
 822  		}
 823  	}
 824  	if len(o) > 0 {
 825  		panic(fmt.Sprintf("conflicts found: %v", o))
 826  	}
 827  	return
 828  }
 829  
 830  func shortestString(a, b string) (s, l string) {
 831  	switch {
 832  	case len(a) > len(b):
 833  		s, l = b, a
 834  	default:
 835  		s, l = a, b
 836  	}
 837  	return
 838  }
 839