serve.go 7.1 KB

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