blob: b16de0a680b9cbae68e1aab7488c7988d50ab1e8 [file] [log] [blame]
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
}