main.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "log"
  8. "os"
  9. "os/signal"
  10. "strings"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "github.com/jmoiron/sqlx"
  15. "github.com/knadh/koanf"
  16. "github.com/knadh/koanf/providers/env"
  17. "github.com/knadh/listmonk/internal/buflog"
  18. "github.com/knadh/listmonk/internal/i18n"
  19. "github.com/knadh/listmonk/internal/manager"
  20. "github.com/knadh/listmonk/internal/media"
  21. "github.com/knadh/listmonk/internal/messenger"
  22. "github.com/knadh/listmonk/internal/subimporter"
  23. "github.com/knadh/stuffbin"
  24. )
  25. const (
  26. emailMsgr = "email"
  27. )
  28. // App contains the "global" components that are
  29. // passed around, especially through HTTP handlers.
  30. type App struct {
  31. fs stuffbin.FileSystem
  32. db *sqlx.DB
  33. queries *Queries
  34. constants *constants
  35. manager *manager.Manager
  36. importer *subimporter.Importer
  37. messengers map[string]messenger.Messenger
  38. media media.Store
  39. i18n *i18n.I18n
  40. notifTpls *template.Template
  41. log *log.Logger
  42. bufLog *buflog.BufLog
  43. // Channel for passing reload signals.
  44. sigChan chan os.Signal
  45. // Global variable that stores the state indicating that a restart is required
  46. // after a settings update.
  47. needsRestart bool
  48. // Global state that stores data on an available remote update.
  49. update *AppUpdate
  50. sync.Mutex
  51. }
  52. var (
  53. // Buffered log writer for storing N lines of log entries for the UI.
  54. bufLog = buflog.New(5000)
  55. lo = log.New(io.MultiWriter(os.Stdout, bufLog), "",
  56. log.Ldate|log.Ltime|log.Lshortfile)
  57. ko = koanf.New(".")
  58. fs stuffbin.FileSystem
  59. db *sqlx.DB
  60. queries *Queries
  61. buildString string
  62. versionString string
  63. )
  64. func init() {
  65. initFlags()
  66. // Display version.
  67. if ko.Bool("version") {
  68. fmt.Println(buildString)
  69. os.Exit(0)
  70. }
  71. lo.Println(buildString)
  72. // Generate new config.
  73. if ko.Bool("new-config") {
  74. if err := newConfigFile(); err != nil {
  75. lo.Println(err)
  76. os.Exit(1)
  77. }
  78. lo.Println("generated config.toml. Edit and run --install")
  79. os.Exit(0)
  80. }
  81. // Load config files to pick up the database settings first.
  82. initConfigFiles(ko.Strings("config"), ko)
  83. // Load environment variables and merge into the loaded config.
  84. if err := ko.Load(env.Provider("LISTMONK_", ".", func(s string) string {
  85. return strings.Replace(strings.ToLower(
  86. strings.TrimPrefix(s, "LISTMONK_")), "__", ".", -1)
  87. }), nil); err != nil {
  88. lo.Fatalf("error loading config from env: %v", err)
  89. }
  90. // Connect to the database, load the filesystem to read SQL queries.
  91. db = initDB()
  92. fs = initFS(ko.String("static-dir"), ko.String("i18n-dir"))
  93. // Installer mode? This runs before the SQL queries are loaded and prepared
  94. // as the installer needs to work on an empty DB.
  95. if ko.Bool("install") {
  96. // Save the version of the last listed migration.
  97. install(migList[len(migList)-1].version, db, fs, !ko.Bool("yes"))
  98. os.Exit(0)
  99. }
  100. // Check if the DB schema is installed.
  101. if ok, err := checkSchema(db); err != nil {
  102. log.Fatalf("error checking schema in DB: %v", err)
  103. } else if !ok {
  104. lo.Fatal("the database does not appear to be setup. Run --install.")
  105. }
  106. if ko.Bool("upgrade") {
  107. upgrade(db, fs, !ko.Bool("yes"))
  108. os.Exit(0)
  109. }
  110. // Before the queries are prepared, see if there are pending upgrades.
  111. checkUpgrade(db)
  112. // Load the SQL queries from the filesystem.
  113. _, queries := initQueries(queryFilePath, db, fs, true)
  114. // Load settings from DB.
  115. initSettings(queries.GetSettings)
  116. }
  117. func main() {
  118. // Initialize the main app controller that wraps all of the app's
  119. // components. This is passed around HTTP handlers.
  120. app := &App{
  121. fs: fs,
  122. db: db,
  123. constants: initConstants(),
  124. media: initMediaStore(),
  125. messengers: make(map[string]messenger.Messenger),
  126. log: lo,
  127. bufLog: bufLog,
  128. }
  129. // Load i18n language map.
  130. app.i18n = initI18n(app.constants.Lang, fs)
  131. _, app.queries = initQueries(queryFilePath, db, fs, true)
  132. app.manager = initCampaignManager(app.queries, app.constants, app)
  133. app.importer = initImporter(app.queries, db, app)
  134. app.notifTpls = initNotifTemplates("/email-templates/*.html", fs, app.i18n, app.constants)
  135. // Initialize the default SMTP (`email`) messenger.
  136. app.messengers[emailMsgr] = initSMTPMessenger(app.manager)
  137. // Initialize any additional postback messengers.
  138. for _, m := range initPostbackMessengers(app.manager) {
  139. app.messengers[m.Name()] = m
  140. }
  141. // Attach all messengers to the campaign manager.
  142. for _, m := range app.messengers {
  143. app.manager.AddMessenger(m)
  144. }
  145. // Start the campaign workers. The campaign batches (fetch from DB, push out
  146. // messages) get processed at the specified interval.
  147. go app.manager.Run(time.Second * 5)
  148. // Start the app server.
  149. srv := initHTTPServer(app)
  150. // Star the update checker.
  151. if ko.Bool("app.check_updates") {
  152. go checkUpdates(versionString, time.Hour*24, app)
  153. }
  154. // Wait for the reload signal with a callback to gracefully shut down resources.
  155. // The `wait` channel is passed to awaitReload to wait for the callback to finish
  156. // within N seconds, or do a force reload.
  157. app.sigChan = make(chan os.Signal)
  158. signal.Notify(app.sigChan, syscall.SIGHUP)
  159. closerWait := make(chan bool)
  160. <-awaitReload(app.sigChan, closerWait, func() {
  161. // Stop the HTTP server.
  162. ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  163. defer cancel()
  164. srv.Shutdown(ctx)
  165. // Close the campaign manager.
  166. app.manager.Close()
  167. // Close the DB pool.
  168. app.db.DB.Close()
  169. // Close the messenger pool.
  170. for _, m := range app.messengers {
  171. m.Close()
  172. }
  173. // Signal the close.
  174. closerWait <- true
  175. })
  176. }