serve.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. mainlog.WithError(err).Error("Error while ReadConfig (reload)")
  59. } else {
  60. cmdConfig = newConfig
  61. mainlog.Infof("Configuration was reloaded at %s", guerrilla.ConfigLoadTime)
  62. cmdConfig.emitChangeEvents(&oldConfig, app)
  63. }
  64. } else if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGINT {
  65. mainlog.Infof("Shutdown signal caught")
  66. app.Shutdown()
  67. mainlog.Infof("Shutdown completed, exiting.")
  68. return
  69. } else {
  70. mainlog.Infof("Shutdown, unknown signal caught")
  71. return
  72. }
  73. }
  74. }
  75. func subscribeBackendEvent(event string, backend backends.Backend, app guerrilla.Guerrilla) {
  76. app.Subscribe(event, func(cmdConfig *CmdConfig) {
  77. logger, _ := log.GetLogger(cmdConfig.LogFile)
  78. var err error
  79. if err = backend.Shutdown(); err != nil {
  80. logger.WithError(err).Warn("Backend failed to shutdown")
  81. return
  82. }
  83. backend, err = backends.New(cmdConfig.BackendName, cmdConfig.BackendConfig, logger)
  84. if err != nil {
  85. logger.WithError(err).Fatalf("Error while loading the backend %q",
  86. cmdConfig.BackendName)
  87. } else {
  88. logger.Info("Backend started:", cmdConfig.BackendName)
  89. }
  90. })
  91. }
  92. func serve(cmd *cobra.Command, args []string) {
  93. logVersion()
  94. err := readConfig(configPath, pidFile, &cmdConfig)
  95. if err != nil {
  96. mainlog.WithError(err).Fatal("Error while reading config")
  97. }
  98. // Check that max clients is not greater than system open file limit.
  99. fileLimit := getFileLimit()
  100. if fileLimit > 0 {
  101. maxClients := 0
  102. for _, s := range cmdConfig.Servers {
  103. maxClients += s.MaxClients
  104. }
  105. if maxClients > fileLimit {
  106. mainlog.Fatalf("Combined max clients for all servers (%d) is greater than open file limit (%d). "+
  107. "Please increase your open file limit or decrease max clients.", maxClients, fileLimit)
  108. }
  109. }
  110. // Backend setup
  111. var backend backends.Backend
  112. backend, err = backends.New(cmdConfig.BackendName, cmdConfig.BackendConfig, mainlog)
  113. if err != nil {
  114. mainlog.WithError(err).Fatalf("Error while loading the backend %q",
  115. cmdConfig.BackendName)
  116. }
  117. app, err := guerrilla.New(&cmdConfig.AppConfig, backend, mainlog)
  118. if err != nil {
  119. mainlog.WithError(err).Error("Error(s) when creating new server(s)")
  120. }
  121. // start the app
  122. err = app.Start()
  123. if err != nil {
  124. mainlog.WithError(err).Error("Error(s) when starting server(s)")
  125. }
  126. subscribeBackendEvent("config_change:backend_config", backend, app)
  127. subscribeBackendEvent("config_change:backend_name", backend, app)
  128. // Write out our PID
  129. writePid(cmdConfig.PidFile)
  130. // ...and write out our pid whenever the file name changes in the config
  131. app.Subscribe("config_change:pid_file", func(ac *guerrilla.AppConfig) {
  132. writePid(ac.PidFile)
  133. })
  134. // change the logger from stdrerr to one from config
  135. mainlog.Infof("main log configured to %s", cmdConfig.LogFile)
  136. var logOpenError error
  137. if mainlog, logOpenError = log.GetLogger(cmdConfig.LogFile); logOpenError != nil {
  138. mainlog.WithError(logOpenError).Errorf("Failed changing to a custom logger [%s]", cmdConfig.LogFile)
  139. }
  140. app.SetLogger(mainlog)
  141. sigHandler(app)
  142. }
  143. // Superset of `guerrilla.AppConfig` containing options specific
  144. // the the command line interface.
  145. type CmdConfig struct {
  146. guerrilla.AppConfig
  147. BackendName string `json:"backend_name"`
  148. BackendConfig backends.BackendConfig `json:"backend_config"`
  149. }
  150. func (c *CmdConfig) load(jsonBytes []byte) error {
  151. c.AppConfig.Load(jsonBytes)
  152. err := json.Unmarshal(jsonBytes, &c)
  153. if err != nil {
  154. return fmt.Errorf("Could not parse config file: %s", err.Error())
  155. }
  156. return nil
  157. }
  158. func (c *CmdConfig) emitChangeEvents(oldConfig *CmdConfig, app guerrilla.Guerrilla) {
  159. // has backend changed?
  160. if !reflect.DeepEqual((*c).BackendConfig, (*oldConfig).BackendConfig) {
  161. app.Publish("config_change:backend_config", c)
  162. }
  163. if c.BackendName != oldConfig.BackendName {
  164. app.Publish("config_change:backend_name", c)
  165. }
  166. // call other emitChangeEvents
  167. c.AppConfig.EmitChangeEvents(&oldConfig.AppConfig, app)
  168. }
  169. // ReadConfig which should be called at startup, or when a SIG_HUP is caught
  170. func readConfig(path string, pidFile string, config *CmdConfig) error {
  171. // load in the config.
  172. data, err := ioutil.ReadFile(path)
  173. if err != nil {
  174. return fmt.Errorf("Could not read config file: %s", err.Error())
  175. }
  176. if err := config.load(data); err != nil {
  177. return err
  178. }
  179. // override config pidFile with with flag from the command line
  180. if len(pidFile) > 0 {
  181. config.AppConfig.PidFile = pidFile
  182. } else if len(config.AppConfig.PidFile) == 0 {
  183. config.AppConfig.PidFile = defaultPidFile
  184. }
  185. if len(config.AllowedHosts) == 0 {
  186. return errors.New("Empty `allowed_hosts` is not allowed")
  187. }
  188. guerrilla.ConfigLoadTime = time.Now()
  189. return nil
  190. }
  191. func getFileLimit() int {
  192. cmd := exec.Command("ulimit", "-n")
  193. out, err := cmd.Output()
  194. if err != nil {
  195. return -1
  196. }
  197. limit, err := strconv.Atoi(strings.TrimSpace(string(out)))
  198. if err != nil {
  199. return -1
  200. }
  201. return limit
  202. }
  203. func writePid(pidFile string) {
  204. if len(pidFile) > 0 {
  205. if f, err := os.Create(pidFile); err == nil {
  206. defer f.Close()
  207. pid := os.Getpid()
  208. if _, err := f.WriteString(fmt.Sprintf("%d", pid)); err == nil {
  209. f.Sync()
  210. mainlog.Infof("pid_file (%s) written with pid:%v", pidFile, pid)
  211. } else {
  212. mainlog.WithError(err).Fatalf("Error while writing pidFile (%s)", pidFile)
  213. }
  214. } else {
  215. mainlog.WithError(err).Fatalf("Error while creating pidFile (%s)", pidFile)
  216. }
  217. }
  218. }