main.go 5.2 KB

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