Files
komari/cmd/server.go
T

220 lines
6.6 KiB
Go

package cmd
import (
"context"
"log"
"log/slog"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/gookit/event"
"github.com/komari-monitor/komari/cmd/flags"
api "github.com/komari-monitor/komari/internal/api_v1"
"github.com/komari-monitor/komari/internal/conf"
"github.com/komari-monitor/komari/internal/database/accounts"
"github.com/komari-monitor/komari/internal/database/auditlog"
"github.com/komari-monitor/komari/internal/database/dbcore"
"github.com/komari-monitor/komari/internal/database/models"
d_notification "github.com/komari-monitor/komari/internal/database/notification"
"github.com/komari-monitor/komari/internal/database/records"
"github.com/komari-monitor/komari/internal/database/tasks"
"github.com/komari-monitor/komari/internal/eventType"
logutil "github.com/komari-monitor/komari/internal/log"
"github.com/komari-monitor/komari/internal/messageSender"
"github.com/komari-monitor/komari/internal/patch"
"github.com/komari-monitor/komari/internal/restore"
"github.com/komari-monitor/komari/pkg/cloudflared"
"github.com/komari-monitor/komari/server"
"github.com/spf13/cobra"
)
var ServerCmd = &cobra.Command{
Use: "server",
Short: "Start the server",
Long: `Start the server`,
Run: func(cmd *cobra.Command, args []string) {
RunServer()
},
}
func init() {
// 从环境变量获取监听地址
listenAddr := GetEnv("KOMARI_LISTEN", "0.0.0.0:25774")
ServerCmd.PersistentFlags().StringVarP(&flags.Listen, "listen", "l", listenAddr, "监听地址 [env: KOMARI_LISTEN]")
RootCmd.AddCommand(ServerCmd)
}
func RunServer() {
// #region 初始化
// 创建目录
if err := os.MkdirAll("./data/theme", os.ModePerm); err != nil {
log.Fatalf("Failed to create theme directory: %v", err)
}
// 进行备份恢复
if restore.NeedBackupRestore() {
restore.RestoreBackup()
}
conf.Load()
InitDatabase()
patch.ApplyPatch()
if conf.Version != conf.Version_Development {
gin.SetMode(gin.ReleaseMode)
}
config, err := conf.GetWithV1Format()
if err != nil {
log.Fatal(err)
}
r := gin.New()
r.Use(logutil.GinLogger())
r.Use(logutil.GinRecovery())
event.Trigger(eventType.ServerInitializeStart, event.M{"config": config, "engine": r})
go DoScheduledWork()
go messageSender.Initialize()
server.StartNezhaGRPCServer(config.NezhaCompatListen)
// 初始化 cloudflared
if strings.ToLower(GetEnv("KOMARI_ENABLE_CLOUDFLARED", "false")) == "true" {
err := cloudflared.RunCloudflared() // 阻塞,确保cloudflared跑起来
if err != nil {
log.Fatalf("Failed to run cloudflared: %v", err)
}
}
server.Init(r)
srv := &http.Server{
Addr: flags.Listen,
Handler: r,
}
event.Trigger(eventType.ServerInitializeDone, event.M{"config": config})
log.Printf("Starting server on %s ...", flags.Listen)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
OnFatal(err)
log.Fatalf("listen: %s\n", err)
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
OnShutdown()
event.Trigger(eventType.ProcessExit, event.M{})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
}
func InitDatabase() {
// // 打印数据库类型和连接信息
// if flags.DatabaseType == "mysql" {
// log.Printf("使用 MySQL 数据库连接: %s@%s:%s/%s",
// flags.DatabaseUser, flags.DatabaseHost, flags.DatabasePort, flags.DatabaseName)
// log.Printf("环境变量配置: [KOMARI_DB_TYPE=%s] [KOMARI_DB_HOST=%s] [KOMARI_DB_PORT=%s] [KOMARI_DB_USER=%s] [KOMARI_DB_NAME=%s]",
// os.Getenv("KOMARI_DB_TYPE"), os.Getenv("KOMARI_DB_HOST"), os.Getenv("KOMARI_DB_PORT"),
// os.Getenv("KOMARI_DB_USER"), os.Getenv("KOMARI_DB_NAME"))
// } else {
// log.Printf("使用 SQLite 数据库文件: %s", flags.DatabaseFile)
// log.Printf("环境变量配置: [KOMARI_DB_TYPE=%s] [KOMARI_DB_FILE=%s]",
// os.Getenv("KOMARI_DB_TYPE"), os.Getenv("KOMARI_DB_FILE"))
// }
var count int64 = 0
if dbcore.GetDBInstance().Model(&models.User{}).Count(&count); count == 0 {
user, passwd, err := accounts.CreateDefaultAdminAccount()
if err != nil {
panic(err)
}
log.Println("Default admin account created. Username:", user, ", Password:", passwd)
}
}
// #region 定时任务
func DoScheduledWork() {
tasks.ReloadPingSchedule()
d_notification.ReloadLoadNotificationSchedule()
//records.DeleteRecordBefore(time.Now().Add(-time.Hour * 24 * 30))
records.CompactRecord()
ScheduledEventTasksInit()
event.On(eventType.SchedulerEvery30Minutes, event.ListenerFunc(func(e event.Event) error {
cfg, err := conf.GetWithV1Format()
if err != nil {
slog.Warn("Failed to get config in scheduled task:", "error", err)
return err
}
records.DeleteRecordBefore(time.Now().Add(-time.Hour * time.Duration(cfg.RecordPreserveTime)))
records.CompactRecord()
tasks.ClearTaskResultsByTimeBefore(time.Now().Add(-time.Hour * time.Duration(cfg.RecordPreserveTime)))
tasks.DeletePingRecordsBefore(time.Now().Add(-time.Hour * time.Duration(cfg.PingRecordPreserveTime)))
auditlog.RemoveOldLogs()
return nil
}))
event.On(eventType.SchedulerEveryMinute, event.ListenerFunc(func(e event.Event) error {
cfg, err := conf.GetWithV1Format()
if err != nil {
slog.Warn("Failed to get config in scheduled task:", "error", err)
return err
}
api.SaveClientReportToDB()
if !cfg.RecordEnabled {
records.DeleteAll()
tasks.DeleteAllPingRecords()
}
return nil
}))
}
func OnShutdown() {
auditlog.Log("", "", "server is shutting down", "info")
cloudflared.Kill()
}
func OnFatal(err error) {
auditlog.Log("", "", "server encountered a fatal error: "+err.Error(), "error")
cloudflared.Kill()
}
func ScheduledEventTasksInit() {
every1m := time.NewTicker(1 * time.Minute)
every5m := time.NewTicker(5 * time.Minute)
every30m := time.NewTicker(30 * time.Minute)
every1h := time.NewTicker(1 * time.Hour)
every1d := time.NewTicker(24 * time.Hour)
for {
var err error = nil
var e event.Event
select {
case <-every1m.C:
err, e = event.Trigger(eventType.SchedulerEveryMinute, event.M{"interval": "1m"})
case <-every5m.C:
err, e = event.Trigger(eventType.SchedulerEvery5Minutes, event.M{"interval": "5m"})
case <-every30m.C:
err, e = event.Trigger(eventType.SchedulerEvery30Minutes, event.M{"interval": "30m"})
case <-every1h.C:
err, e = event.Trigger(eventType.SchedulerEveryHour, event.M{"interval": "1h"})
case <-every1d.C:
err, e = event.Trigger(eventType.SchedulerEveryDay, event.M{"interval": "1d"})
}
if err != nil {
slog.Warn("Scheduled task error:", "error", err, "event", e)
}
}
}