install.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "regexp"
  7. "syscall"
  8. "github.com/lib/pq"
  9. uuid "github.com/satori/go.uuid"
  10. "github.com/jmoiron/sqlx"
  11. "github.com/knadh/goyesql"
  12. "github.com/knadh/listmonk/models"
  13. "github.com/spf13/viper"
  14. "golang.org/x/crypto/bcrypt"
  15. "golang.org/x/crypto/ssh/terminal"
  16. )
  17. // install runs the first time setup of creating and
  18. // migrating the database and creating the super user.
  19. func install(app *App, qMap goyesql.Queries) {
  20. var (
  21. email, pw, pw2 []byte
  22. err error
  23. // Pseudo e-mail validation using Regexp, well ...
  24. emRegex, _ = regexp.Compile("(.+?)@(.+?)")
  25. )
  26. fmt.Println("** First time installation. **")
  27. fmt.Println("** IMPORTANT: This will wipe existing listmonk tables and types. **")
  28. fmt.Println("\n")
  29. for len(email) == 0 {
  30. fmt.Print("Enter the superadmin login e-mail: ")
  31. if _, err = fmt.Scanf("%s", &email); err != nil {
  32. logger.Fatalf("Error reading e-mail from the terminal: %v", err)
  33. }
  34. if !emRegex.Match(email) {
  35. logger.Println("Please enter a valid e-mail")
  36. email = []byte{}
  37. }
  38. }
  39. for len(pw) < 8 {
  40. fmt.Print("Enter the superadmin password (min 8 chars): ")
  41. if pw, err = terminal.ReadPassword(int(syscall.Stdin)); err != nil {
  42. logger.Fatalf("Error reading password from the terminal: %v", err)
  43. }
  44. fmt.Println("")
  45. if len(pw) < 8 {
  46. logger.Println("Password should be min 8 characters")
  47. pw = []byte{}
  48. }
  49. }
  50. for len(pw2) < 8 {
  51. fmt.Print("Repeat the superadmin password: ")
  52. if pw2, err = terminal.ReadPassword(int(syscall.Stdin)); err != nil {
  53. logger.Fatalf("Error reading password from the terminal: %v", err)
  54. }
  55. fmt.Println("")
  56. if len(pw2) < 8 {
  57. logger.Println("Password should be min 8 characters")
  58. pw2 = []byte{}
  59. }
  60. }
  61. // Validate.
  62. if !bytes.Equal(pw, pw2) {
  63. logger.Fatalf("Passwords don't match")
  64. }
  65. // Hash the password.
  66. hash, err := bcrypt.GenerateFromPassword(pw, bcrypt.DefaultCost)
  67. if err != nil {
  68. logger.Fatalf("Error hashing password: %v", err)
  69. }
  70. // Migrate the tables.
  71. err = installMigrate(app.DB)
  72. if err != nil {
  73. logger.Fatalf("Error migrating DB schema: %v", err)
  74. }
  75. // Load the queries.
  76. var q Queries
  77. if err := scanQueriesToStruct(&q, qMap, app.DB.Unsafe()); err != nil {
  78. logger.Fatalf("error loading SQL queries: %v", err)
  79. }
  80. // Create the superadmin user.
  81. if _, err := q.CreateUser.Exec(
  82. string(email),
  83. models.UserTypeSuperadmin, // name
  84. string(hash),
  85. models.UserTypeSuperadmin,
  86. models.UserStatusEnabled,
  87. ); err != nil {
  88. logger.Fatalf("Error creating superadmin user: %v", err)
  89. }
  90. // Sample list.
  91. var listID int
  92. if err := q.CreateList.Get(&listID,
  93. uuid.NewV4().String(),
  94. "Default list",
  95. models.ListTypePublic,
  96. pq.StringArray{"test"},
  97. ); err != nil {
  98. logger.Fatalf("Error creating superadmin user: %v", err)
  99. }
  100. // Sample subscriber.
  101. name := bytes.Split(email, []byte("@"))
  102. if _, err := q.UpsertSubscriber.Exec(
  103. uuid.NewV4(),
  104. email,
  105. bytes.Title(name[0]),
  106. `{"type": "known", "good": true}`,
  107. pq.Int64Array{int64(listID)},
  108. ); err != nil {
  109. logger.Fatalf("Error creating subscriber: %v", err)
  110. }
  111. // Default template.
  112. tplBody, err := ioutil.ReadFile("default-template.html")
  113. if err != nil {
  114. tplBody = []byte(tplTag)
  115. }
  116. var tplID int
  117. if err := q.CreateTemplate.Get(&tplID,
  118. "Default template",
  119. string(tplBody),
  120. ); err != nil {
  121. logger.Fatalf("Error creating default template: %v", err)
  122. }
  123. if _, err := q.SetDefaultTemplate.Exec(tplID); err != nil {
  124. logger.Fatalf("Error setting default template: %v", err)
  125. }
  126. logger.Printf("Setup complete")
  127. logger.Printf(`Run the program and login with the username "superadmin" and your password at %s`,
  128. viper.GetString("server.address"))
  129. }
  130. // installMigrate executes the SQL schema and creates the necessary tables and types.
  131. func installMigrate(db *sqlx.DB) error {
  132. q, err := ioutil.ReadFile("schema.sql")
  133. if err != nil {
  134. return err
  135. }
  136. _, err = db.Query(string(q))
  137. if err != nil {
  138. return err
  139. }
  140. return nil
  141. }