Initial prototype

Change-Id: I60a94e90aab48dfcf7c1f03fe5613d1db7d0df95
diff --git a/cmd/hichipbridge/serve.go b/cmd/hichipbridge/serve.go
new file mode 100644
index 0000000..b16de0a
--- /dev/null
+++ b/cmd/hichipbridge/serve.go
@@ -0,0 +1,205 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"github.com/flashmob/go-guerrilla"
+	"github.com/flashmob/go-guerrilla/backends"
+	"github.com/flashmob/go-guerrilla/log"
+
+	// Choose iconv or mail/encoding package which uses golang.org/x/net/html/charset
+	//_ "github.com/flashmob/go-guerrilla/mail/iconv"
+	_ "github.com/flashmob/go-guerrilla/mail/encoding"
+
+	"github.com/spf13/cobra"
+
+	_ "github.com/go-sql-driver/mysql"
+
+	"gomodules.avm99963.com/hichip2mqtt/mqtt_processor"
+)
+
+const (
+	defaultPidFile = "/var/run/hichipbridge.pid"
+)
+
+var (
+	configPath string
+	pidFile    string
+
+	serveCmd = &cobra.Command{
+		Use:   "serve",
+		Short: "start the daemon and start all available SMTP servers",
+		Run:   serve,
+	}
+
+	signalChannel = make(chan os.Signal, 1) // for trapping SIGHUP and friends
+	mainlog       log.Logger
+
+	d guerrilla.Daemon
+)
+
+func init() {
+	// log to stderr on startup
+	var err error
+	mainlog, err = log.GetLogger(log.OutputStderr.String(), log.InfoLevel.String())
+	if err != nil && mainlog != nil {
+		mainlog.WithError(err).Errorf("Failed creating a logger to %s", log.OutputStderr)
+	}
+	cfgFile := "config.json"
+	serveCmd.PersistentFlags().StringVarP(&configPath, "config", "c",
+		cfgFile, "Path to the configuration file")
+	// intentionally didn't specify default pidFile; value from config is used if flag is empty
+	serveCmd.PersistentFlags().StringVarP(&pidFile, "pidFile", "p",
+		"", "Path to the pid file")
+	rootCmd.AddCommand(serveCmd)
+}
+
+func sigHandler() {
+	signal.Notify(signalChannel,
+		syscall.SIGHUP,
+		syscall.SIGTERM,
+		syscall.SIGQUIT,
+		syscall.SIGINT,
+		syscall.SIGKILL,
+		syscall.SIGUSR1,
+		os.Kill,
+	)
+	for sig := range signalChannel {
+		if sig == syscall.SIGHUP {
+			if ac, err := readConfig(configPath, pidFile); err == nil {
+				_ = d.ReloadConfig(*ac)
+			} else {
+				mainlog.WithError(err).Error("Could not reload config")
+			}
+		} else if sig == syscall.SIGUSR1 {
+			if err := d.ReopenLogs(); err != nil {
+				mainlog.WithError(err).Error("reopening logs failed")
+			}
+		} else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT || sig == os.Kill {
+			mainlog.Infof("Shutdown signal caught")
+			go func() {
+				select {
+				// exit if graceful shutdown not finished in 60 sec.
+				case <-time.After(time.Second * 60):
+					mainlog.Error("graceful shutdown timed out")
+					os.Exit(1)
+				}
+			}()
+			d.Shutdown()
+			mainlog.Infof("Shutdown completed, exiting.")
+			return
+		} else {
+			mainlog.Infof("Shutdown, unknown signal caught")
+			return
+		}
+	}
+}
+
+func serve(cmd *cobra.Command, args []string) {
+	logVersion()
+	d = guerrilla.Daemon{Logger: mainlog}
+	d.AddProcessor("MQTT", mqtt_processor.Processor)
+
+	c, err := readConfig(configPath, pidFile)
+	if err != nil {
+		mainlog.WithError(err).Fatal("Error while reading config")
+	}
+	_ = d.SetConfig(*c)
+
+	// Check that max clients is not greater than system open file limit.
+	if ok, maxClients, fileLimit := guerrilla.CheckFileLimit(c); !ok {
+		mainlog.Fatalf("Combined max clients for all servers (%d) is greater than open file limit (%d). "+
+			"Please increase your open file limit or decrease max clients.", maxClients, fileLimit)
+	}
+
+	err = d.Start()
+	if err != nil {
+		mainlog.WithError(err).Error("Error(s) when creating new server(s)")
+		os.Exit(1)
+	}
+	sigHandler()
+
+}
+
+func lookupPrefixedEnv(coreName string) (string, error) {
+	name := "HICHIPBRIDGE_" + coreName
+	v, ok := os.LookupEnv(name)
+	if !ok {
+		return v, fmt.Errorf("Environment variable %s isn't set", name)
+	}
+
+	return v, nil
+}
+
+// ReadConfig is called at startup, or when a SIG_HUP is caught
+func readConfig(path string, pidFile string) (*guerrilla.AppConfig, error) {
+	// Environment variables where some settings are configured (without the prefix "HICHIPBRIDGE_"):
+	envNames := []string{
+		"SMTP_HOST",
+		"MQTT_BROKER",
+		"MQTT_CLIENTID",
+		"MQTT_USERNAME",
+		"MQTT_PASSWORD",
+		"MQTT_TOPIC",
+		"EMAIL_TOKEN",
+	}
+
+	// Set default configuration (overridable)
+	defaultAppConfig := guerrilla.AppConfig{
+		BackendConfig: backends.BackendConfig{
+			"save_workers_size":  1,
+			"log_received_mails": false,
+		},
+	}
+
+	d.SetConfig(defaultAppConfig)
+
+	// Load in the config.
+	appConfig, err := d.LoadConfig(path)
+	if err != nil {
+		return &appConfig, fmt.Errorf("could not read config file: %s", err.Error())
+	}
+
+	// override config pidFile with with flag from the command line
+	if len(pidFile) > 0 {
+		appConfig.PidFile = pidFile
+	} else if len(appConfig.PidFile) == 0 {
+		appConfig.PidFile = defaultPidFile
+	}
+	if verbose {
+		appConfig.LogLevel = "debug"
+	}
+
+	// Override compulsory settings
+	var envs = make(map[string]string)
+	for _, env := range envNames {
+		v, err := lookupPrefixedEnv(env)
+		if err != nil {
+			return &appConfig, err
+		}
+		envs[env] = v
+	}
+
+	appConfig.AllowedHosts = []string{envs["SMTP_HOST"]}
+	appConfig.BackendConfig = backends.BackendConfig{
+		"save_process":      "HeadersParser|Hasher|Debugger|MQTT",
+		"validate_process":  "",
+		"primary_mail_host": envs["SMTP_HOST"],
+		"mqtt_broker":       envs["MQTT_BROKER"],
+		"mqtt_clientid":     envs["MQTT_CLIENTID"],
+		"mqtt_username":     envs["MQTT_USERNAME"],
+		"mqtt_password":     envs["MQTT_PASSWORD"],
+		"mqtt_topic":        envs["MQTT_TOPIC"],
+		"mqtt_email_token":  envs["EMAIL_TOKEN"],
+	}
+
+	for i, _ := range appConfig.Servers {
+		appConfig.Servers[i].Hostname = envs["SMTP_HOST"]
+	}
+
+	return &appConfig, nil
+}