avm99963 | 88e622d | 2021-01-22 18:57:58 +0100 | [diff] [blame] | 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "os" |
| 6 | "os/signal" |
| 7 | "syscall" |
| 8 | "time" |
| 9 | |
| 10 | "github.com/flashmob/go-guerrilla" |
| 11 | "github.com/flashmob/go-guerrilla/backends" |
| 12 | "github.com/flashmob/go-guerrilla/log" |
| 13 | |
| 14 | // Choose iconv or mail/encoding package which uses golang.org/x/net/html/charset |
| 15 | //_ "github.com/flashmob/go-guerrilla/mail/iconv" |
| 16 | _ "github.com/flashmob/go-guerrilla/mail/encoding" |
| 17 | |
| 18 | "github.com/spf13/cobra" |
| 19 | |
| 20 | _ "github.com/go-sql-driver/mysql" |
| 21 | |
| 22 | "gomodules.avm99963.com/hichip2mqtt/mqtt_processor" |
| 23 | ) |
| 24 | |
| 25 | const ( |
| 26 | defaultPidFile = "/var/run/hichipbridge.pid" |
| 27 | ) |
| 28 | |
| 29 | var ( |
| 30 | configPath string |
| 31 | pidFile string |
| 32 | |
| 33 | serveCmd = &cobra.Command{ |
| 34 | Use: "serve", |
| 35 | Short: "start the daemon and start all available SMTP servers", |
| 36 | Run: serve, |
| 37 | } |
| 38 | |
| 39 | signalChannel = make(chan os.Signal, 1) // for trapping SIGHUP and friends |
| 40 | mainlog log.Logger |
| 41 | |
| 42 | d guerrilla.Daemon |
| 43 | ) |
| 44 | |
| 45 | func init() { |
| 46 | // log to stderr on startup |
| 47 | var err error |
| 48 | mainlog, err = log.GetLogger(log.OutputStderr.String(), log.InfoLevel.String()) |
| 49 | if err != nil && mainlog != nil { |
| 50 | mainlog.WithError(err).Errorf("Failed creating a logger to %s", log.OutputStderr) |
| 51 | } |
| 52 | cfgFile := "config.json" |
| 53 | serveCmd.PersistentFlags().StringVarP(&configPath, "config", "c", |
| 54 | cfgFile, "Path to the configuration file") |
| 55 | // intentionally didn't specify default pidFile; value from config is used if flag is empty |
| 56 | serveCmd.PersistentFlags().StringVarP(&pidFile, "pidFile", "p", |
| 57 | "", "Path to the pid file") |
| 58 | rootCmd.AddCommand(serveCmd) |
| 59 | } |
| 60 | |
| 61 | func sigHandler() { |
| 62 | signal.Notify(signalChannel, |
| 63 | syscall.SIGHUP, |
| 64 | syscall.SIGTERM, |
| 65 | syscall.SIGQUIT, |
| 66 | syscall.SIGINT, |
| 67 | syscall.SIGKILL, |
| 68 | syscall.SIGUSR1, |
| 69 | os.Kill, |
| 70 | ) |
| 71 | for sig := range signalChannel { |
| 72 | if sig == syscall.SIGHUP { |
| 73 | if ac, err := readConfig(configPath, pidFile); err == nil { |
| 74 | _ = d.ReloadConfig(*ac) |
| 75 | } else { |
| 76 | mainlog.WithError(err).Error("Could not reload config") |
| 77 | } |
| 78 | } else if sig == syscall.SIGUSR1 { |
| 79 | if err := d.ReopenLogs(); err != nil { |
| 80 | mainlog.WithError(err).Error("reopening logs failed") |
| 81 | } |
| 82 | } else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT || sig == os.Kill { |
| 83 | mainlog.Infof("Shutdown signal caught") |
| 84 | go func() { |
| 85 | select { |
| 86 | // exit if graceful shutdown not finished in 60 sec. |
| 87 | case <-time.After(time.Second * 60): |
| 88 | mainlog.Error("graceful shutdown timed out") |
| 89 | os.Exit(1) |
| 90 | } |
| 91 | }() |
| 92 | d.Shutdown() |
| 93 | mainlog.Infof("Shutdown completed, exiting.") |
| 94 | return |
| 95 | } else { |
| 96 | mainlog.Infof("Shutdown, unknown signal caught") |
| 97 | return |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | func serve(cmd *cobra.Command, args []string) { |
| 103 | logVersion() |
| 104 | d = guerrilla.Daemon{Logger: mainlog} |
| 105 | d.AddProcessor("MQTT", mqtt_processor.Processor) |
| 106 | |
| 107 | c, err := readConfig(configPath, pidFile) |
| 108 | if err != nil { |
| 109 | mainlog.WithError(err).Fatal("Error while reading config") |
| 110 | } |
| 111 | _ = d.SetConfig(*c) |
| 112 | |
| 113 | // Check that max clients is not greater than system open file limit. |
| 114 | if ok, maxClients, fileLimit := guerrilla.CheckFileLimit(c); !ok { |
| 115 | mainlog.Fatalf("Combined max clients for all servers (%d) is greater than open file limit (%d). "+ |
| 116 | "Please increase your open file limit or decrease max clients.", maxClients, fileLimit) |
| 117 | } |
| 118 | |
| 119 | err = d.Start() |
| 120 | if err != nil { |
| 121 | mainlog.WithError(err).Error("Error(s) when creating new server(s)") |
| 122 | os.Exit(1) |
| 123 | } |
| 124 | sigHandler() |
| 125 | |
| 126 | } |
| 127 | |
| 128 | func lookupPrefixedEnv(coreName string) (string, error) { |
| 129 | name := "HICHIPBRIDGE_" + coreName |
| 130 | v, ok := os.LookupEnv(name) |
| 131 | if !ok { |
| 132 | return v, fmt.Errorf("Environment variable %s isn't set", name) |
| 133 | } |
| 134 | |
| 135 | return v, nil |
| 136 | } |
| 137 | |
| 138 | // ReadConfig is called at startup, or when a SIG_HUP is caught |
| 139 | func readConfig(path string, pidFile string) (*guerrilla.AppConfig, error) { |
| 140 | // Environment variables where some settings are configured (without the prefix "HICHIPBRIDGE_"): |
| 141 | envNames := []string{ |
| 142 | "SMTP_HOST", |
| 143 | "MQTT_BROKER", |
| 144 | "MQTT_CLIENTID", |
| 145 | "MQTT_USERNAME", |
| 146 | "MQTT_PASSWORD", |
| 147 | "MQTT_TOPIC", |
| 148 | "EMAIL_TOKEN", |
| 149 | } |
| 150 | |
| 151 | // Set default configuration (overridable) |
| 152 | defaultAppConfig := guerrilla.AppConfig{ |
| 153 | BackendConfig: backends.BackendConfig{ |
| 154 | "save_workers_size": 1, |
| 155 | "log_received_mails": false, |
| 156 | }, |
| 157 | } |
| 158 | |
| 159 | d.SetConfig(defaultAppConfig) |
| 160 | |
| 161 | // Load in the config. |
| 162 | appConfig, err := d.LoadConfig(path) |
| 163 | if err != nil { |
| 164 | return &appConfig, fmt.Errorf("could not read config file: %s", err.Error()) |
| 165 | } |
| 166 | |
| 167 | // override config pidFile with with flag from the command line |
| 168 | if len(pidFile) > 0 { |
| 169 | appConfig.PidFile = pidFile |
| 170 | } else if len(appConfig.PidFile) == 0 { |
| 171 | appConfig.PidFile = defaultPidFile |
| 172 | } |
| 173 | if verbose { |
| 174 | appConfig.LogLevel = "debug" |
| 175 | } |
| 176 | |
| 177 | // Override compulsory settings |
| 178 | var envs = make(map[string]string) |
| 179 | for _, env := range envNames { |
| 180 | v, err := lookupPrefixedEnv(env) |
| 181 | if err != nil { |
| 182 | return &appConfig, err |
| 183 | } |
| 184 | envs[env] = v |
| 185 | } |
| 186 | |
| 187 | appConfig.AllowedHosts = []string{envs["SMTP_HOST"]} |
| 188 | appConfig.BackendConfig = backends.BackendConfig{ |
| 189 | "save_process": "HeadersParser|Hasher|Debugger|MQTT", |
| 190 | "validate_process": "", |
| 191 | "primary_mail_host": envs["SMTP_HOST"], |
| 192 | "mqtt_broker": envs["MQTT_BROKER"], |
| 193 | "mqtt_clientid": envs["MQTT_CLIENTID"], |
| 194 | "mqtt_username": envs["MQTT_USERNAME"], |
| 195 | "mqtt_password": envs["MQTT_PASSWORD"], |
| 196 | "mqtt_topic": envs["MQTT_TOPIC"], |
| 197 | "mqtt_email_token": envs["EMAIL_TOKEN"], |
| 198 | } |
| 199 | |
| 200 | for i, _ := range appConfig.Servers { |
| 201 | appConfig.Servers[i].Hostname = envs["SMTP_HOST"] |
| 202 | } |
| 203 | |
| 204 | return &appConfig, nil |
| 205 | } |