You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gitea-fork-majority-judgment/modules/setting/setting.go

660 lines
19 KiB

10 years ago
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
10 years ago
package setting
10 years ago
import (
"fmt"
"net/url"
10 years ago
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
10 years ago
"strings"
"time"
10 years ago
"gopkg.in/ini.v1"
10 years ago
"github.com/Unknwon/com"
"github.com/go-macaron/session"
10 years ago
"github.com/gogits/gogs/modules/bindata"
10 years ago
"github.com/gogits/gogs/modules/log"
// "github.com/gogits/gogs/modules/ssh"
"github.com/gogits/gogs/modules/user"
10 years ago
)
10 years ago
type Scheme string
10 years ago
10 years ago
const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
FCGI Scheme = "fcgi"
10 years ago
)
10 years ago
10 years ago
type LandingPage string
const (
LANDING_PAGE_HOME LandingPage = "/"
LANDING_PAGE_EXPLORE LandingPage = "/explore"
)
10 years ago
var (
9 years ago
// Build information.
BuildTime string
BuildGitHash string
10 years ago
// App settings.
AppVer string
AppName string
AppUrl string
AppSubUrl string
AppDataPath = "data"
10 years ago
// Server settings.
Protocol Scheme
Domain string
HttpAddr, HttpPort string
DisableSSH bool
SSHPort int
SSHDomain string
10 years ago
OfflineMode bool
DisableRouterLog bool
CertFile, KeyFile string
StaticRootPath string
EnableGzip bool
10 years ago
LandingPageUrl LandingPage
10 years ago
// Security settings.
10 years ago
InstallLock bool
SecretKey string
LogInRememberDays int
CookieUserName string
CookieRememberName string
ReverseProxyAuthUser string
10 years ago
// Database settings.
UseSQLite3 bool
UseMySQL bool
UsePostgreSQL bool
9 years ago
UseTiDB bool
10 years ago
// Webhook settings.
Webhook struct {
QueueLength int
DeliverTimeout int
SkipTLSVerify bool
Types []string
PagingNum int
}
10 years ago
10 years ago
// Repository settings.
Repository struct {
AnsiCharset string
ForcePrivate bool
PullRequestQueueLength int
}
RepoRootPath string
ScriptType string
// UI settings.
ExplorePagingNum int
IssuePagingNum int
FeedMaxCommitNum int
AdminUserPagingNum int
AdminRepoPagingNum int
AdminNoticePagingNum int
AdminOrgPagingNum int
10 years ago
// Markdown sttings.
Markdown struct {
EnableHardLineBreak bool
}
10 years ago
// Picture settings.
PictureService string
AvatarUploadPath string
GravatarSource string
DisableGravatar bool
10 years ago
// Log settings.
10 years ago
LogRootPath string
LogModes []string
LogConfigs []string
10 years ago
// Attachment settings.
AttachmentPath string
AttachmentAllowedTypes string
AttachmentMaxSize int64
AttachmentMaxFiles int
AttachmentEnabled bool
// Time settings.
TimeFormat string
10 years ago
// Cache settings.
CacheAdapter string
CacheInternal int
CacheConn string
10 years ago
10 years ago
EnableRedis bool
EnableMemcache bool
// Session settings.
9 years ago
SessionConfig session.Options
10 years ago
// Git settings.
Git struct {
MaxGitDiffLines int
GcArgs []string `delim:" "`
}
// Cron tasks.
Cron struct {
UpdateMirror struct {
Enabled bool
RunAtStart bool
Schedule string
} `ini:"cron.update_mirrors"`
RepoHealthCheck struct {
Enabled bool
RunAtStart bool
Schedule string
Args []string `delim:" "`
} `ini:"cron.repo_health_check"`
CheckRepoStats struct {
Enabled bool
RunAtStart bool
Schedule string
} `ini:"cron.check_repo_stats"`
}
// I18n settings.
Langs, Names []string
dateLangs map[string]string
// Other settings.
ShowFooterBranding bool
10 years ago
// Global setting objects.
9 years ago
Cfg *ini.File
CustomPath string // Custom directory path.
CustomConf string
ProdMode bool
RunUser string
IsWindows bool
HasRobotsTxt bool
10 years ago
)
func DateLang(lang string) string {
name, ok := dateLangs[lang]
if ok {
return name
}
return "en"
}
func init() {
IsWindows = runtime.GOOS == "windows"
log.NewLogger(0, "console", `{"level": 0}`)
}
func ExecPath() (string, error) {
10 years ago
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
p, err := filepath.Abs(file)
if err != nil {
return "", err
}
return p, nil
}
// WorkDir returns absolute path of work directory.
func WorkDir() (string, error) {
wd := os.Getenv("GOGS_WORK_DIR")
if len(wd) > 0 {
return wd, nil
}
execPath, err := ExecPath()
if err != nil {
return execPath, err
}
// Note: we don't use path.Dir here because it does not handle case
// which path starts with two "/" in Windows: "//psf/Home/..."
execPath = strings.Replace(execPath, "\\", "/", -1)
i := strings.LastIndex(execPath, "/")
if i == -1 {
return execPath, nil
}
return execPath[:i], nil
10 years ago
}
func forcePathSeparator(path string) {
if strings.Contains(path, "\\") {
log.Fatal(4, "Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places")
}
}
9 years ago
// NewContext initializes configuration context.
10 years ago
// NOTE: do not print any log except error.
9 years ago
func NewContext() {
10 years ago
workDir, err := WorkDir()
if err != nil {
log.Fatal(4, "Fail to get work directory: %v", err)
10 years ago
}
Cfg, err = ini.Load(bindata.MustAsset("conf/app.ini"))
10 years ago
if err != nil {
log.Fatal(4, "Fail to parse 'conf/app.ini': %v", err)
10 years ago
}
CustomPath = os.Getenv("GOGS_CUSTOM")
if len(CustomPath) == 0 {
CustomPath = workDir + "/custom"
10 years ago
}
if len(CustomConf) == 0 {
CustomConf = CustomPath + "/conf/app.ini"
}
if com.IsFile(CustomConf) {
if err = Cfg.Append(CustomConf); err != nil {
log.Fatal(4, "Fail to load custom conf '%s': %v", CustomConf, err)
10 years ago
}
} else {
log.Warn("Custom config (%s) not found, ignore this if you're running first time", CustomConf)
10 years ago
}
Cfg.NameMapper = ini.AllCapsUnderscore
10 years ago
9 years ago
LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(workDir, "log"))
forcePathSeparator(LogRootPath)
9 years ago
sec := Cfg.Section("server")
AppName = Cfg.Section("").Key("APP_NAME").MustString("Gogs: Go Git Service")
AppUrl = sec.Key("ROOT_URL").MustString("http://localhost:3000/")
10 years ago
if AppUrl[len(AppUrl)-1] != '/' {
AppUrl += "/"
}
10 years ago
// Check if has app suburl.
url, err := url.Parse(AppUrl)
if err != nil {
log.Fatal(4, "Invalid ROOT_URL(%s): %s", AppUrl, err)
}
AppSubUrl = strings.TrimSuffix(url.Path, "/")
10 years ago
Protocol = HTTP
9 years ago
if sec.Key("PROTOCOL").String() == "https" {
10 years ago
Protocol = HTTPS
9 years ago
CertFile = sec.Key("CERT_FILE").String()
KeyFile = sec.Key("KEY_FILE").String()
} else if sec.Key("PROTOCOL").String() == "fcgi" {
Protocol = FCGI
}
9 years ago
Domain = sec.Key("DOMAIN").MustString("localhost")
HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HttpPort = sec.Key("HTTP_PORT").MustString("3000")
DisableSSH = sec.Key("DISABLE_SSH").MustBool()
SSHDomain = sec.Key("SSH_DOMAIN").MustString(Domain)
SSHPort = sec.Key("SSH_PORT").MustInt(22)
9 years ago
OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir)
EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
switch sec.Key("LANDING_PAGE").MustString("home") {
10 years ago
case "explore":
LandingPageUrl = LANDING_PAGE_EXPLORE
default:
LandingPageUrl = LANDING_PAGE_HOME
}
9 years ago
sec = Cfg.Section("security")
InstallLock = sec.Key("INSTALL_LOCK").MustBool()
SecretKey = sec.Key("SECRET_KEY").String()
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt()
CookieUserName = sec.Key("COOKIE_USERNAME").String()
CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String()
ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
sec = Cfg.Section("attachment")
AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
if !filepath.IsAbs(AttachmentPath) {
AttachmentPath = path.Join(workDir, AttachmentPath)
}
AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1)
AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
9 years ago
AttachmentEnabled = sec.Key("ENABLE").MustBool(true)
TimeFormat = map[string]string{
"ANSIC": time.ANSIC,
"UnixDate": time.UnixDate,
"RubyDate": time.RubyDate,
"RFC822": time.RFC822,
"RFC822Z": time.RFC822Z,
"RFC850": time.RFC850,
"RFC1123": time.RFC1123,
"RFC1123Z": time.RFC1123Z,
"RFC3339": time.RFC3339,
"RFC3339Nano": time.RFC3339Nano,
"Kitchen": time.Kitchen,
"Stamp": time.Stamp,
"StampMilli": time.StampMilli,
"StampMicro": time.StampMicro,
"StampNano": time.StampNano,
9 years ago
}[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")]
9 years ago
RunUser = Cfg.Section("").Key("RUN_USER").String()
curUser := user.CurrentUsername()
10 years ago
// Does not check run user when the install lock is off.
if InstallLock && RunUser != curUser {
log.Fatal(4, "Expect user(%s) but current user is: %s", RunUser, curUser)
10 years ago
}
// Determine and create root git repository path.
10 years ago
homeDir, err := com.HomeDir()
if err != nil {
log.Fatal(4, "Fail to get home directory: %v", err)
10 years ago
}
homeDir = strings.Replace(homeDir, "\\", "/", -1)
9 years ago
sec = Cfg.Section("repository")
RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gogs-repositories"))
forcePathSeparator(RepoRootPath)
if !filepath.IsAbs(RepoRootPath) {
RepoRootPath = path.Join(workDir, RepoRootPath)
} else {
RepoRootPath = path.Clean(RepoRootPath)
}
9 years ago
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
Repository.AnsiCharset = sec.Key("ANSI_CHARSET").String()
Repository.ForcePrivate = sec.Key("FORCE_PRIVATE").MustBool()
Repository.PullRequestQueueLength = sec.Key("PULL_REQUEST_QUEUE_LENGTH").MustInt(10000)
10 years ago
// UI settings.
sec = Cfg.Section("ui")
ExplorePagingNum = sec.Key("EXPLORE_PAGING_NUM").MustInt(20)
IssuePagingNum = sec.Key("ISSUE_PAGING_NUM").MustInt(10)
FeedMaxCommitNum = sec.Key("FEED_MAX_COMMIT_NUM").MustInt(5)
sec = Cfg.Section("ui.admin")
AdminUserPagingNum = sec.Key("USER_PAGING_NUM").MustInt(50)
AdminRepoPagingNum = sec.Key("REPO_PAGING_NUM").MustInt(50)
AdminNoticePagingNum = sec.Key("NOTICE_PAGING_NUM").MustInt(50)
AdminOrgPagingNum = sec.Key("ORG_PAGING_NUM").MustInt(50)
9 years ago
sec = Cfg.Section("picture")
PictureService = sec.Key("SERVICE").In("server", []string{"server"})
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
forcePathSeparator(AvatarUploadPath)
if !filepath.IsAbs(AvatarUploadPath) {
AvatarUploadPath = path.Join(workDir, AvatarUploadPath)
}
switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
case "duoshuo":
GravatarSource = "http://gravatar.duoshuo.com/avatar/"
case "gravatar":
GravatarSource = "//1.gravatar.com/avatar/"
default:
GravatarSource = source
}
9 years ago
DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
if OfflineMode {
DisableGravatar = true
}
if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
log.Fatal(4, "Fail to map Markdown settings: %v", err)
} else if err = Cfg.Section("git").MapTo(&Git); err != nil {
log.Fatal(4, "Fail to map Git settings: %v", err)
} else if Cfg.Section("cron").MapTo(&Cron); err != nil {
log.Fatal(4, "Fail to map Cron settings: %v", err)
}
9 years ago
Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
dateLangs = Cfg.Section("i18n.datelang").KeysHash()
ShowFooterBranding = Cfg.Section("other").Key("SHOW_FOOTER_BRANDING").MustBool()
HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))
10 years ago
}
10 years ago
var Service struct {
ActiveCodeLives int
ResetPwdCodeLives int
10 years ago
RegisterEmailConfirm bool
DisableRegistration bool
ShowRegistrationButton bool
10 years ago
RequireSignInView bool
EnableCacheAvatar bool
EnableNotifyMail bool
EnableReverseProxyAuth bool
EnableReverseProxyAutoRegister bool
DisableMinimumKeySizeCheck bool
MinimumKeySizes map[string]int
EnableCaptcha bool
10 years ago
}
10 years ago
func newService() {
sec := Cfg.Section("service")
Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration)
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
Service.EnableCacheAvatar = sec.Key("ENABLE_CACHE_AVATAR").MustBool()
Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
9 years ago
Service.DisableMinimumKeySizeCheck = sec.Key("DISABLE_MINIMUM_KEY_SIZE_CHECK").MustBool()
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool()
minimumKeySizes := Cfg.Section("service.minimum_key_sizes").Keys()
Service.MinimumKeySizes = make(map[string]int)
for _, key := range minimumKeySizes {
Service.MinimumKeySizes[key.Name()] = key.MustInt()
}
10 years ago
}
var logLevels = map[string]string{
"Trace": "0",
"Debug": "1",
"Info": "2",
"Warn": "3",
"Error": "4",
"Critical": "5",
}
func newLogService() {
log.Info("%s %s", AppName, AppVer)
10 years ago
9 years ago
if len(BuildTime) > 0 {
log.Info("Build Time: %s", BuildTime)
log.Info("Build Git Hash: %s", BuildGitHash)
}
// Get and check log mode.
9 years ago
LogModes = strings.Split(Cfg.Section("log").Key("MODE").MustString("console"), ",")
LogConfigs = make([]string, len(LogModes))
for i, mode := range LogModes {
mode = strings.TrimSpace(mode)
9 years ago
sec, err := Cfg.GetSection("log." + mode)
if err != nil {
log.Fatal(4, "Unknown log mode: %s", mode)
}
validLevels := []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"}
// Log level.
levelName := Cfg.Section("log."+mode).Key("LEVEL").In(
Cfg.Section("log").Key("LEVEL").In("Trace", validLevels),
validLevels)
level, ok := logLevels[levelName]
if !ok {
log.Fatal(4, "Unknown log level: %s", levelName)
}
// Generate log configuration.
switch mode {
case "console":
LogConfigs[i] = fmt.Sprintf(`{"level":%s}`, level)
case "file":
9 years ago
logPath := sec.Key("FILE_NAME").MustString(path.Join(LogRootPath, "gogs.log"))
os.MkdirAll(path.Dir(logPath), os.ModePerm)
LogConfigs[i] = fmt.Sprintf(
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
logPath,
9 years ago
sec.Key("LOG_ROTATE").MustBool(true),
sec.Key("MAX_LINES").MustInt(1000000),
1<<uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28)),
sec.Key("DAILY_ROTATE").MustBool(true),
sec.Key("MAX_DAYS").MustInt(7))
case "conn":
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
9 years ago
sec.Key("RECONNECT_ON_MSG").MustBool(),
sec.Key("RECONNECT").MustBool(),
sec.Key("PROTOCOL").In("tcp", []string{"tcp", "unix", "udp"}),
sec.Key("ADDR").MustString(":7020"))
case "smtp":
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
9 years ago
sec.Key("USER").MustString("example@example.com"),
sec.Key("PASSWD").MustString("******"),
sec.Key("HOST").MustString("127.0.0.1:25"),
sec.Key("RECEIVERS").MustString("[]"),
sec.Key("SUBJECT").MustString("Diagnostic message from serve"))
case "database":
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"driver":"%s","conn":"%s"}`, level,
9 years ago
sec.Key("DRIVER").String(),
sec.Key("CONN").String())
}
9 years ago
log.NewLogger(Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000), mode, LogConfigs[i])
log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName)
10 years ago
}
}
func newCacheService() {
9 years ago
CacheAdapter = Cfg.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
if EnableRedis {
9 years ago
log.Info("Redis Supported")
}
if EnableMemcache {
9 years ago
log.Info("Memcache Supported")
}
10 years ago
switch CacheAdapter {
case "memory":
9 years ago
CacheInternal = Cfg.Section("cache").Key("INTERVAL").MustInt(60)
10 years ago
case "redis", "memcache":
9 years ago
CacheConn = strings.Trim(Cfg.Section("cache").Key("HOST").String(), "\" ")
10 years ago
default:
log.Fatal(4, "Unknown cache adapter: %s", CacheAdapter)
10 years ago
}
log.Info("Cache Service Enabled")
}
func newSessionService() {
9 years ago
SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
[]string{"memory", "file", "redis", "mysql"})
9 years ago
SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").String(), "\" ")
SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gogits")
10 years ago
SessionConfig.CookiePath = AppSubUrl
9 years ago
SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool()
SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
10 years ago
log.Info("Session Service Enabled")
}
10 years ago
// Mailer represents mail service.
type Mailer struct {
9 years ago
QueueLength int
Name string
Host string
From string
User, Passwd string
DisableHelo bool
HeloHostname string
SkipVerify bool
UseCertificate bool
CertFile, KeyFile string
10 years ago
}
var (
MailService *Mailer
10 years ago
)
10 years ago
func newMailService() {
9 years ago
sec := Cfg.Section("mailer")
10 years ago
// Check mailer setting.
9 years ago
if !sec.Key("ENABLED").MustBool() {
10 years ago
return
}
MailService = &Mailer{
9 years ago
QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100),
Name: sec.Key("NAME").MustString(AppName),
Host: sec.Key("HOST").String(),
User: sec.Key("USER").String(),
Passwd: sec.Key("PASSWD").String(),
DisableHelo: sec.Key("DISABLE_HELO").MustBool(),
HeloHostname: sec.Key("HELO_HOSTNAME").String(),
SkipVerify: sec.Key("SKIP_VERIFY").MustBool(),
UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(),
CertFile: sec.Key("CERT_FILE").String(),
KeyFile: sec.Key("KEY_FILE").String(),
10 years ago
}
9 years ago
MailService.From = sec.Key("FROM").MustString(MailService.User)
10 years ago
log.Info("Mail Service Enabled")
}
func newRegisterMailService() {
9 years ago
if !Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
10 years ago
return
} else if MailService == nil {
log.Warn("Register Mail Service: Mail Service is not enabled")
return
}
Service.RegisterEmailConfirm = true
log.Info("Register Mail Service Enabled")
}
func newNotifyMailService() {
9 years ago
if !Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
10 years ago
return
} else if MailService == nil {
log.Warn("Notify Mail Service: Mail Service is not enabled")
return
}
10 years ago
Service.EnableNotifyMail = true
10 years ago
log.Info("Notify Mail Service Enabled")
}
10 years ago
func newWebhookService() {
sec := Cfg.Section("webhook")
Webhook.QueueLength = sec.Key("QUEUE_LENGTH").MustInt(1000)
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5)
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
Webhook.Types = []string{"gogs", "slack"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
10 years ago
}
10 years ago
func NewServices() {
10 years ago
newService()
newLogService()
newCacheService()
newSessionService()
newMailService()
newRegisterMailService()
newNotifyMailService()
10 years ago
newWebhookService()
// ssh.Listen("2222")
10 years ago
}