config.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package config
  2. import (
  3. "bytes"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/rand"
  7. "crypto/x509"
  8. "encoding/json"
  9. "encoding/pem"
  10. "fmt"
  11. "github.com/Jinnrry/pmail/utils/context"
  12. "github.com/Jinnrry/pmail/utils/errors"
  13. "github.com/Jinnrry/pmail/utils/file"
  14. log "github.com/sirupsen/logrus"
  15. "os"
  16. "path/filepath"
  17. "strings"
  18. "time"
  19. )
  20. var IsInit bool
  21. type Config struct {
  22. LogLevel string `json:"logLevel"` // 日志级别
  23. Domain string `json:"domain"`
  24. Domains []string `json:"domains"` //多域名设置,把所有收信域名都填进去
  25. WebDomain string `json:"webDomain"`
  26. DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
  27. SSLType string `json:"sslType"` // 0表示自动生成证书,HTTP挑战模式,1表示用户上传证书,2表示自动-DNS挑战模式
  28. SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
  29. SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
  30. DbDSN string `json:"dbDSN"`
  31. DbType string `json:"dbType"`
  32. HttpsEnabled int `json:"httpsEnabled"` //后台页面是否启用https,0默认(启用),1启用,2不启用
  33. SpamFilterLevel int `json:"spamFilterLevel"` //垃圾邮件过滤级别,0不过滤、1 spf dkim 校验均失败时过滤,2 spf校验不通过时过滤
  34. HttpPort int `json:"httpPort"` //http服务端口设置,默认80
  35. HttpsPort int `json:"httpsPort"` //https服务端口,默认443
  36. WeChatPushAppId string `json:"weChatPushAppId"`
  37. WeChatPushSecret string `json:"weChatPushSecret"`
  38. WeChatPushTemplateId string `json:"weChatPushTemplateId"`
  39. WeChatPushUserId string `json:"weChatPushUserId"`
  40. TgBotToken string `json:"tgBotToken"`
  41. TgChatId string `json:"tgChatId"`
  42. IsInit bool `json:"isInit"`
  43. WebPushUrl string `json:"webPushUrl"`
  44. WebPushToken string `json:"webPushToken"`
  45. Tables map[string]string `json:"-"`
  46. TablesInitData map[string]string `json:"-"`
  47. setupPort int // 初始化阶段端口
  48. }
  49. var ROOT_PATH = ""
  50. func init() {
  51. envs := os.Environ()
  52. for _, env := range envs {
  53. if strings.HasPrefix(env, "PMail_ROOT=") {
  54. ROOT_PATH = strings.TrimSpace(strings.ReplaceAll(env, "PMail_ROOT=", ""))
  55. if !strings.HasSuffix(ROOT_PATH, "/") {
  56. ROOT_PATH += "/"
  57. }
  58. fmt.Println("Env Root Path:", ROOT_PATH)
  59. return
  60. }
  61. }
  62. ex, err := os.Executable()
  63. if err != nil {
  64. panic(err)
  65. }
  66. exPath := filepath.Dir(ex)
  67. realPath, err := filepath.EvalSymlinks(exPath)
  68. if err != nil {
  69. panic(err)
  70. }
  71. // 如果是Goland运行,不修改根路径
  72. if strings.Contains(realPath, "GoLand") && strings.Contains(realPath, "JetBrains") {
  73. return
  74. }
  75. if !strings.HasSuffix(realPath, "/") {
  76. realPath += "/"
  77. }
  78. ROOT_PATH = realPath
  79. fmt.Println("Root Path:", ROOT_PATH)
  80. }
  81. func (c *Config) GetSetupPort() int {
  82. return c.setupPort
  83. }
  84. func (c *Config) SetSetupPort(setupPort int) {
  85. c.setupPort = setupPort
  86. }
  87. const DBTypeMySQL = "mysql"
  88. const DBTypeSQLite = "sqlite"
  89. const DBTypePostgres = "postgres"
  90. const SSLTypeAutoHTTP = "0" //自动生成证书
  91. const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证
  92. const SSLTypeUser = "1" //用户上传证书
  93. var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite, DBTypePostgres}
  94. var Instance *Config = &Config{}
  95. type logFormatter struct {
  96. }
  97. // Format 定义日志输出格式
  98. func (l *logFormatter) Format(entry *log.Entry) ([]byte, error) {
  99. b := bytes.Buffer{}
  100. b.WriteString(fmt.Sprintf("[%s]", entry.Level.String()))
  101. b.WriteString(fmt.Sprintf("[%s]", entry.Time.Format("2006-01-02 15:04:05")))
  102. if entry.Context != nil {
  103. ctx := entry.Context.(*context.Context)
  104. if ctx != nil {
  105. b.WriteString(fmt.Sprintf("[%s]", ctx.GetValue(context.LogID)))
  106. }
  107. }
  108. b.WriteString(fmt.Sprintf("[%s:%d]", entry.Caller.File, entry.Caller.Line))
  109. b.WriteString(entry.Message)
  110. b.WriteString("\n")
  111. return b.Bytes(), nil
  112. }
  113. func Init() {
  114. var cfgData []byte
  115. var err error
  116. args := os.Args
  117. if len(args) >= 2 && args[len(args)-1] == "dev" {
  118. cfgData, err = os.ReadFile(ROOT_PATH + "./config/config.dev.json")
  119. if err != nil {
  120. return
  121. }
  122. } else {
  123. cfgData, err = os.ReadFile(ROOT_PATH + "./config/config.json")
  124. if err != nil {
  125. log.Errorf("config file not found,%s", err.Error())
  126. return
  127. }
  128. }
  129. err = json.Unmarshal(cfgData, &Instance)
  130. Instance.fixPath()
  131. if err != nil {
  132. return
  133. }
  134. if len(Instance.Domains) == 0 && Instance.Domain != "" {
  135. Instance.Domains = []string{Instance.Domain}
  136. }
  137. if Instance.Domain != "" && Instance.IsInit {
  138. IsInit = true
  139. }
  140. // 设置日志格式为json格式
  141. log.SetFormatter(&logFormatter{})
  142. log.SetReportCaller(true)
  143. // 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
  144. // 日志消息输出可以是任意的io.writer类型
  145. log.SetOutput(os.Stdout)
  146. var cstZone = time.FixedZone("CST", 8*3600)
  147. time.Local = cstZone
  148. if Instance != nil {
  149. switch Instance.LogLevel {
  150. case "":
  151. log.SetLevel(log.InfoLevel)
  152. case "debug":
  153. log.SetLevel(log.DebugLevel)
  154. case "info":
  155. log.SetLevel(log.InfoLevel)
  156. case "warn":
  157. log.SetLevel(log.WarnLevel)
  158. case "error":
  159. log.SetLevel(log.ErrorLevel)
  160. default:
  161. log.SetLevel(log.InfoLevel)
  162. }
  163. } else {
  164. log.SetLevel(log.InfoLevel)
  165. }
  166. }
  167. func ReadPrivateKey() (*ecdsa.PrivateKey, bool) {
  168. key, err := os.ReadFile(ROOT_PATH + "./config/ssl/account_private.pem")
  169. if err != nil {
  170. return createNewPrivateKey(), true
  171. }
  172. block, _ := pem.Decode(key)
  173. x509Encoded := block.Bytes
  174. privateKey, _ := x509.ParseECPrivateKey(x509Encoded)
  175. return privateKey, false
  176. }
  177. func createNewPrivateKey() *ecdsa.PrivateKey {
  178. // Create a user. New accounts need an email and private key to start.
  179. privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  180. if err != nil {
  181. panic(err)
  182. }
  183. x509Encoded, _ := x509.MarshalECPrivateKey(privateKey)
  184. // 将ec 密钥写入到 pem文件里
  185. keypem, _ := os.OpenFile(ROOT_PATH+"./config/ssl/account_private.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  186. err = pem.Encode(keypem, &pem.Block{Type: "EC PRIVATE KEY", Bytes: x509Encoded})
  187. if err != nil {
  188. panic(err)
  189. }
  190. return privateKey
  191. }
  192. func WriteConfig(cfg *Config) error {
  193. bytes, _ := json.Marshal(cfg)
  194. _ = os.MkdirAll(ROOT_PATH+"/config/", 0755)
  195. err := os.WriteFile(ROOT_PATH+"./config/config.json", bytes, 0666)
  196. if err != nil {
  197. return errors.Wrap(err)
  198. }
  199. return nil
  200. }
  201. func ReadConfig() (*Config, error) {
  202. configData := Config{
  203. DkimPrivateKeyPath: ROOT_PATH + "config/dkim/dkim.priv",
  204. SSLPrivateKeyPath: ROOT_PATH + "config/ssl/private.key",
  205. SSLPublicKeyPath: ROOT_PATH + "config/ssl/public.crt",
  206. }
  207. if !file.PathExist(ROOT_PATH + "./config/config.json") {
  208. bytes, _ := json.Marshal(configData)
  209. _ = os.MkdirAll(ROOT_PATH+"/config/", 0755)
  210. err := os.WriteFile(ROOT_PATH+"./config/config.json", bytes, 0666)
  211. if err != nil {
  212. log.Errorf("Write Config Error:%s", err.Error())
  213. return nil, errors.Wrap(err)
  214. }
  215. } else {
  216. cfgData, err := os.ReadFile(ROOT_PATH + "./config/config.json")
  217. if err != nil {
  218. log.Errorf("Read Config Error:%s", err.Error())
  219. return nil, errors.Wrap(err)
  220. }
  221. err = json.Unmarshal(cfgData, &configData)
  222. configData.fixPath()
  223. if err != nil {
  224. log.Errorf("Read Config Unmarshal Error:%s", err.Error())
  225. return nil, errors.Wrap(err)
  226. }
  227. }
  228. return &configData, nil
  229. }
  230. func (c *Config) fixPath() {
  231. if c.DbType == DBTypeSQLite && !strings.HasPrefix(c.DbDSN, "/") {
  232. c.DbDSN = ROOT_PATH + c.DbDSN
  233. }
  234. if !strings.HasPrefix(c.SSLPublicKeyPath, "/") {
  235. c.SSLPublicKeyPath = ROOT_PATH + c.SSLPublicKeyPath
  236. }
  237. if !strings.HasPrefix(c.SSLPrivateKeyPath, "/") {
  238. c.SSLPrivateKeyPath = ROOT_PATH + c.SSLPrivateKeyPath
  239. }
  240. }