blob: 17b944e18711c01b00e24a13b5db4066c2536e04 [file] [log] [blame]
avm9996388e622d2021-01-22 18:57:58 +01001package main
2
3import (
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
25const (
26 defaultPidFile = "/var/run/hichipbridge.pid"
27)
28
29var (
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
45func 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
61func 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
102func 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
128func 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
139func 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
avm9996388e622d2021-01-22 18:57:58 +0100151 // Load in the config.
152 appConfig, err := d.LoadConfig(path)
153 if err != nil {
154 return &appConfig, fmt.Errorf("could not read config file: %s", err.Error())
155 }
156
157 // override config pidFile with with flag from the command line
158 if len(pidFile) > 0 {
159 appConfig.PidFile = pidFile
160 } else if len(appConfig.PidFile) == 0 {
161 appConfig.PidFile = defaultPidFile
162 }
163 if verbose {
164 appConfig.LogLevel = "debug"
165 }
166
167 // Override compulsory settings
168 var envs = make(map[string]string)
169 for _, env := range envNames {
170 v, err := lookupPrefixedEnv(env)
171 if err != nil {
172 return &appConfig, err
173 }
174 envs[env] = v
175 }
176
177 appConfig.AllowedHosts = []string{envs["SMTP_HOST"]}
178 appConfig.BackendConfig = backends.BackendConfig{
179 "save_process": "HeadersParser|Hasher|Debugger|MQTT",
180 "validate_process": "",
181 "primary_mail_host": envs["SMTP_HOST"],
182 "mqtt_broker": envs["MQTT_BROKER"],
183 "mqtt_clientid": envs["MQTT_CLIENTID"],
184 "mqtt_username": envs["MQTT_USERNAME"],
185 "mqtt_password": envs["MQTT_PASSWORD"],
186 "mqtt_topic": envs["MQTT_TOPIC"],
187 "mqtt_email_token": envs["EMAIL_TOKEN"],
188 }
189
190 for i, _ := range appConfig.Servers {
191 appConfig.Servers[i].Hostname = envs["SMTP_HOST"]
192 }
193
194 return &appConfig, nil
195}