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
+}