serve.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "github.com/flashmob/go-guerrilla"
  7. "github.com/flashmob/go-guerrilla/backends"
  8. "github.com/flashmob/go-guerrilla/log"
  9. "github.com/spf13/cobra"
  10. "io/ioutil"
  11. "os"
  12. "os/exec"
  13. "os/signal"
  14. "reflect"
  15. "strconv"
  16. "strings"
  17. "syscall"
  18. "time"
  19. )
  20. const (
  21. defaultPidFile = "/var/run/go-guerrilla.pid"
  22. )
  23. var (
  24. configPath string
  25. pidFile string
  26. serveCmd = &cobra.Command{
  27. Use: "serve",
  28. Short: "start the small SMTP server",
  29. Run: serve,
  30. }
  31. cmdConfig = CmdConfig{}
  32. signalChannel = make(chan os.Signal, 1) // for trapping SIG_HUP
  33. mainlog log.Logger
  34. )
  35. func init() {
  36. // log to stderr on startup
  37. var logOpenError error
  38. if mainlog, logOpenError = log.GetLogger(log.OutputStderr.String()); logOpenError != nil {
  39. mainlog.WithError(logOpenError).Errorf("Failed creating a logger to %s", log.OutputStderr)
  40. }
  41. serveCmd.PersistentFlags().StringVarP(&configPath, "config", "c",
  42. "goguerrilla.conf", "Path to the configuration file")
  43. // intentionally didn't specify default pidFile; value from config is used if flag is empty
  44. serveCmd.PersistentFlags().StringVarP(&pidFile, "pidFile", "p",
  45. "", "Path to the pid file")
  46. rootCmd.AddCommand(serveCmd)
  47. }
  48. func sigHandler(app guerrilla.Guerrilla) {
  49. // handle SIGHUP for reloading the configuration while running
  50. signal.Notify(signalChannel, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGKILL)
  51. for sig := range signalChannel {
  52. if sig == syscall.SIGHUP {
  53. // save old config & load in new one
  54. oldConfig := cmdConfig
  55. newConfig := CmdConfig{}
  56. err := readConfig(configPath, pidFile, &newConfig)
  57. if err != nil {
  58. // new config will not be applied
  59. mainlog.WithError(err).Error("Error while ReadConfig (reload)")
  60. // re-open logs
  61. cmdConfig.EmitLogReopenEvents(app)
  62. } else {
  63. cmdConfig = newConfig
  64. mainlog.Infof("Configuration was reloaded at %s", guerrilla.ConfigLoadTime)
  65. cmdConfig.emitChangeEvents(&oldConfig, app)
  66. }
  67. } else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT {
  68. mainlog.Infof("Shutdown signal caught")
  69. app.Shutdown()
  70. mainlog.Infof("Shutdown completed, exiting.")
  71. return
  72. } else {
  73. mainlog.Infof("Shutdown, unknown signal caught")
  74. return
  75. }
  76. }
  77. }
  78. func subscribeBackendEvent(event guerrilla.Event, backend backends.Backend, app guerrilla.Guerrilla) {
  79. app.Subscribe(event, func(cmdConfig *CmdConfig) {
  80. logger, _ := log.GetLogger(cmdConfig.LogFile)
  81. var err error
  82. if err = backend.Shutdown(); err != nil {
  83. logger.WithError(err).Warn("Backend failed to shutdown")
  84. return
  85. }
  86. backend, err = backends.New(cmdConfig.BackendName, cmdConfig.BackendConfig, logger)
  87. if err != nil {
  88. logger.WithError(err).Fatalf("Error while loading the backend %q",
  89. cmdConfig.BackendName)
  90. } else {
  91. logger.Info("Backend started:", cmdConfig.BackendName)
  92. }
  93. })
  94. }
  95. func serve(cmd *cobra.Command, args []string) {
  96. logVersion()
  97. err := readConfig(configPath, pidFile, &cmdConfig)
  98. if err != nil {
  99. mainlog.WithError(err).Fatal("Error while reading config")
  100. }
  101. // Check that max clients is not greater than system open file limit.
  102. fileLimit := getFileLimit()
  103. if fileLimit > 0 {
  104. maxClients := 0
  105. for _, s := range cmdConfig.Servers {
  106. maxClients += s.MaxClients
  107. }
  108. if maxClients > fileLimit {
  109. mainlog.Fatalf("Combined max clients for all servers (%d) is greater than open file limit (%d). "+
  110. "Please increase your open file limit or decrease max clients.", maxClients, fileLimit)
  111. }
  112. }
  113. // Backend setup
  114. var backend backends.Backend
  115. backend, err = backends.New(cmdConfig.BackendName, cmdConfig.BackendConfig, mainlog)
  116. if err != nil {
  117. mainlog.WithError(err).Fatalf("Error while loading the backend %q",
  118. cmdConfig.BackendName)
  119. }
  120. app, err := guerrilla.New(&cmdConfig.AppConfig, backend, mainlog)
  121. if err != nil {
  122. mainlog.WithError(err).Error("Error(s) when creating new server(s)")
  123. }
  124. // start the app
  125. err = app.Start()
  126. if err != nil {
  127. mainlog.WithError(err).Error("Error(s) when starting server(s)")
  128. }
  129. subscribeBackendEvent(guerrilla.EventConfigBackendConfig, backend, app)
  130. subscribeBackendEvent(guerrilla.EventConfigBackendName, backend, app)
  131. // Write out our PID
  132. writePid(cmdConfig.PidFile)
  133. // ...and write out our pid whenever the file name changes in the config
  134. app.Subscribe(guerrilla.EventConfigPidFile, func(ac *guerrilla.AppConfig) {
  135. writePid(ac.PidFile)
  136. })
  137. // change the logger from stdrerr to one from config
  138. mainlog.Infof("main log configured to %s", cmdConfig.LogFile)
  139. var logOpenError error
  140. if mainlog, logOpenError = log.GetLogger(cmdConfig.LogFile); logOpenError != nil {
  141. mainlog.WithError(logOpenError).Errorf("Failed changing to a custom logger [%s]", cmdConfig.LogFile)
  142. }
  143. app.SetLogger(mainlog)
  144. sigHandler(app)
  145. }
  146. // Superset of `guerrilla.AppConfig` containing options specific
  147. // the the command line interface.
  148. type CmdConfig struct {
  149. guerrilla.AppConfig
  150. BackendName string `json:"backend_name"`
  151. BackendConfig backends.BackendConfig `json:"backend_config"`
  152. }
  153. func (c *CmdConfig) load(jsonBytes []byte) error {
  154. err := json.Unmarshal(jsonBytes, &c)
  155. if err != nil {
  156. return fmt.Errorf("Could not parse config file: %s", err.Error())
  157. } else {
  158. // load in guerrilla.AppConfig
  159. return c.AppConfig.Load(jsonBytes)
  160. }
  161. }
  162. func (c *CmdConfig) emitChangeEvents(oldConfig *CmdConfig, app guerrilla.Guerrilla) {
  163. // has backend changed?
  164. if !reflect.DeepEqual((*c).BackendConfig, (*oldConfig).BackendConfig) {
  165. app.Publish(guerrilla.EventConfigBackendConfig, c)
  166. }
  167. if c.BackendName != oldConfig.BackendName {
  168. app.Publish(guerrilla.EventConfigBackendName, c)
  169. }
  170. // call other emitChangeEvents
  171. c.AppConfig.EmitChangeEvents(&oldConfig.AppConfig, app)
  172. }
  173. // ReadConfig which should be called at startup, or when a SIG_HUP is caught
  174. func readConfig(path string, pidFile string, config *CmdConfig) error {
  175. // load in the config.
  176. data, err := ioutil.ReadFile(path)
  177. if err != nil {
  178. return fmt.Errorf("Could not read config file: %s", err.Error())
  179. }
  180. if err := config.load(data); err != nil {
  181. return err
  182. }
  183. // override config pidFile with with flag from the command line
  184. if len(pidFile) > 0 {
  185. config.AppConfig.PidFile = pidFile
  186. } else if len(config.AppConfig.PidFile) == 0 {
  187. config.AppConfig.PidFile = defaultPidFile
  188. }
  189. if len(config.AllowedHosts) == 0 {
  190. return errors.New("Empty `allowed_hosts` is not allowed")
  191. }
  192. guerrilla.ConfigLoadTime = time.Now()
  193. return nil
  194. }
  195. func getFileLimit() int {
  196. cmd := exec.Command("ulimit", "-n")
  197. out, err := cmd.Output()
  198. if err != nil {
  199. return -1
  200. }
  201. limit, err := strconv.Atoi(strings.TrimSpace(string(out)))
  202. if err != nil {
  203. return -1
  204. }
  205. return limit
  206. }
  207. func writePid(pidFile string) {
  208. if len(pidFile) > 0 {
  209. if f, err := os.Create(pidFile); err == nil {
  210. defer f.Close()
  211. pid := os.Getpid()
  212. if _, err := f.WriteString(fmt.Sprintf("%d", pid)); err == nil {
  213. f.Sync()
  214. mainlog.Infof("pid_file (%s) written with pid:%v", pidFile, pid)
  215. } else {
  216. mainlog.WithError(err).Fatalf("Error while writing pidFile (%s)", pidFile)
  217. }
  218. } else {
  219. mainlog.WithError(err).Fatalf("Error while creating pidFile (%s)", pidFile)
  220. }
  221. }
  222. }