From ce4a770e230d2c92d6e63ca10b20100a3fb906f7 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:08:02 +0800 Subject: [PATCH] feat: Support remote synchronization of script library scripts (#8371) --- agent/utils/common/common.go | 4 +- core/app/api/v2/script_library.go | 17 +- core/app/model/script_library.go | 1 - core/app/repo/script_library.go | 23 +- core/app/service/script_library.go | 130 ++- core/app/service/upgrade.go | 2 +- core/app/task/task.go | 2 + core/global/config.go | 3 +- core/go.mod | 2 +- core/i18n/lang/en.yaml | 4 + core/i18n/lang/ja.yaml | 4 + core/i18n/lang/ko.yaml | 4 + core/i18n/lang/ms.yml | 4 + core/i18n/lang/pt-BR.yaml | 4 + core/i18n/lang/ru.yaml | 4 + core/i18n/lang/zh-Hant.yaml | 4 + core/i18n/lang/zh.yaml | 4 + core/init/cron/cron.go | 10 +- core/init/cron/job/script.go | 18 + core/init/migration/helper/script.go | 781 ------------------- core/init/migration/migrate.go | 1 + core/init/migration/migrations/init.go | 14 +- core/router/ro_script_library.go | 1 + frontend/src/api/modules/cronjob.ts | 3 + frontend/src/lang/modules/en.ts | 2 + frontend/src/lang/modules/ja.ts | 3 + frontend/src/lang/modules/ko.ts | 3 + frontend/src/lang/modules/ms.ts | 2 + frontend/src/lang/modules/pt-br.ts | 3 + frontend/src/lang/modules/ru.ts | 3 + frontend/src/lang/modules/zh-Hant.ts | 2 + frontend/src/lang/modules/zh.ts | 2 + frontend/src/utils/util.ts | 13 + frontend/src/views/cronjob/library/index.vue | 37 +- frontend/src/views/setting/panel/index.vue | 2 +- 35 files changed, 314 insertions(+), 802 deletions(-) create mode 100644 core/init/cron/job/script.go delete mode 100644 core/init/migration/helper/script.go diff --git a/agent/utils/common/common.go b/agent/utils/common/common.go index 5b3c6eab0..11de0834d 100644 --- a/agent/utils/common/common.go +++ b/agent/utils/common/common.go @@ -386,11 +386,11 @@ func RestartService(core, agent, reload bool) { } switch { case core && agent: - command += "systemctl restart 1panel-core.service && systemctl restart 1pane-agent.service" + command += "systemctl restart 1panel-core.service && systemctl restart 1panel-agent.service" case core: command += "systemctl restart 1panel-core.service" case agent: - command += "systemctl restart 1pane-agent.service" + command += "systemctl restart 1panel-agent.service" default: return } diff --git a/core/app/api/v2/script_library.go b/core/app/api/v2/script_library.go index 83d68f519..96c3d7e1e 100644 --- a/core/app/api/v2/script_library.go +++ b/core/app/api/v2/script_library.go @@ -53,7 +53,7 @@ func (b *BaseApi) SearchScript(c *gin.Context) { return } - total, list, err := scriptService.Search(req) + total, list, err := scriptService.Search(c, req) if err != nil { helper.InternalServer(c, err) return @@ -87,6 +87,21 @@ func (b *BaseApi) DeleteScript(c *gin.Context) { helper.SuccessWithOutData(c) } +// @Tags ScriptLibrary +// @Summary Sync script from remote +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /script/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步脚本库脚本","formatEN":"sync scripts"} +func (b *BaseApi) SyncScript(c *gin.Context) { + if err := scriptService.Sync(); err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithOutData(c) +} + // @Tags ScriptLibrary // @Summary Update script // @Accept json diff --git a/core/app/model/script_library.go b/core/app/model/script_library.go index 91061a316..774e834f5 100644 --- a/core/app/model/script_library.go +++ b/core/app/model/script_library.go @@ -3,7 +3,6 @@ package model type ScriptLibrary struct { BaseModel Name string `json:"name" gorm:"not null;"` - Lable string `json:"lable"` Script string `json:"script" gorm:"not null;"` Groups string `json:"groups"` IsSystem bool `json:"isSystem"` diff --git a/core/app/repo/script_library.go b/core/app/repo/script_library.go index 9a132724e..69960b3bd 100644 --- a/core/app/repo/script_library.go +++ b/core/app/repo/script_library.go @@ -9,10 +9,11 @@ import ( type IScriptRepo interface { Get(opts ...global.DBOption) (model.ScriptLibrary, error) GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) - Create(snap *model.ScriptLibrary) error + Create(script *model.ScriptLibrary) error Update(id uint, vars map[string]interface{}) error Page(limit, offset int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) Delete(opts ...global.DBOption) error + SyncAll(scripts []model.ScriptLibrary) error WithByInfo(info string) global.DBOption } @@ -34,13 +35,13 @@ func (u *ScriptRepo) Get(opts ...global.DBOption) (model.ScriptLibrary, error) { } func (u *ScriptRepo) GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) { - var snaps []model.ScriptLibrary + var scripts []model.ScriptLibrary db := global.DB.Model(&model.ScriptLibrary{}) for _, opt := range opts { db = opt(db) } - err := db.Find(&snaps).Error - return snaps, err + err := db.Find(&scripts).Error + return scripts, err } func (u *ScriptRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) { @@ -71,6 +72,20 @@ func (u *ScriptRepo) Delete(opts ...global.DBOption) error { return db.Delete(&model.ScriptLibrary{}).Error } +func (u *ScriptRepo) SyncAll(scripts []model.ScriptLibrary) error { + tx := global.DB.Begin() + if err := tx.Where("is_system = ?", 1).Delete(&model.ScriptLibrary{}).Error; err != nil { + tx.Rollback() + return err + } + if err := tx.Save(&scripts).Error; err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + func (u *ScriptRepo) WithByInfo(info string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("name LIKE ? OR description LIKE ?", "%"+info+"%", "%"+info+"%") diff --git a/core/app/service/script_library.go b/core/app/service/script_library.go index bc3227840..92491e0fa 100644 --- a/core/app/service/script_library.go +++ b/core/app/service/script_library.go @@ -1,31 +1,45 @@ package service import ( + "encoding/json" + "fmt" + "net/http" + "os" + "path" "strconv" "strings" "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/app/task" "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/files" + "github.com/1Panel-dev/1Panel/core/utils/req_helper" + "github.com/gin-gonic/gin" "github.com/jinzhu/copier" + "gopkg.in/yaml.v2" ) type ScriptService struct{} type IScriptService interface { - Search(req dto.SearchPageWithGroup) (int64, interface{}, error) + Search(ctx *gin.Context, req dto.SearchPageWithGroup) (int64, interface{}, error) Create(req dto.ScriptOperate) error Update(req dto.ScriptOperate) error Delete(ids dto.OperateByIDs) error + Sync() error } func NewIScriptService() IScriptService { return &ScriptService{} } -func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{}, error) { +func (u *ScriptService) Search(ctx *gin.Context, req dto.SearchPageWithGroup) (int64, interface{}, error) { options := []global.DBOption{repo.WithOrderBy("created_at desc")} if len(req.Info) != 0 { options = append(options, scriptRepo.WithByInfo(req.Info)) @@ -45,7 +59,20 @@ func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{}, if err := copier.Copy(&item, &itemData); err != nil { global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) } - matchGourp := false + if item.IsSystem { + lang := strings.ToLower(common.GetLang(ctx)) + var nameMap = make(map[string]string) + _ = json.Unmarshal([]byte(item.Name), &nameMap) + var descriptionMap = make(map[string]string) + _ = json.Unmarshal([]byte(item.Description), &descriptionMap) + if val, ok := nameMap[lang]; ok { + item.Name = val + } + if val, ok := descriptionMap[lang]; ok { + item.Description = val + } + } + matchGroup := false groupIDs := strings.Split(itemData.Groups, ",") for _, idItem := range groupIDs { id, _ := strconv.Atoi(idItem) @@ -53,7 +80,7 @@ func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{}, continue } if uint(id) == req.GroupID { - matchGourp = true + matchGroup = true } item.GroupList = append(item.GroupList, uint(id)) item.GroupBelong = append(item.GroupBelong, groupMap[uint(id)]) @@ -62,7 +89,7 @@ func (u *ScriptService) Search(req dto.SearchPageWithGroup) (int64, interface{}, data = append(data, item) continue } - if matchGourp { + if matchGroup { data = append(data, item) } } @@ -125,3 +152,96 @@ func (u *ScriptService) Update(req dto.ScriptOperate) error { func LoadScriptInfo(id uint) (model.ScriptLibrary, error) { return scriptRepo.Get(repo.WithByID(id)) } + +func (u *ScriptService) Sync() error { + syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("LocalApp"), task.TaskSync, task.TaskScopeScript, "", 0) + if err != nil { + global.LOG.Errorf("create sync task failed %v", err) + return err + } + + syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("LocalApp"), task.TaskSync, task.TaskScopeScript), func(t *task.Task) (err error) { + versionUrl := fmt.Sprintf("%s/scripts/version.txt", global.CONF.RemoteURL.ResourceURL) + _, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return fmt.Errorf("load scripts version from remote failed, err: %v", err) + } + var scriptSetting model.Setting + _ = global.DB.Where("key = ?", "ScriptVersion").First(&scriptSetting).Error + if scriptSetting.Value == string(versionRes) { + syncTask.Log("The local and remote versions are detected to be consistent. Skip...") + return nil + } + + syncTask.Log("start to download data.yaml") + dataUrl := fmt.Sprintf("%s/scripts/data.yaml", global.CONF.RemoteURL.ResourceURL) + _, dataRes, err := req_helper.HandleRequest(dataUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return fmt.Errorf("load scripts data.yaml from remote failed, err: %v", err) + } + + syncTask.Log("download successful!") + var scripts Scripts + if err = yaml.Unmarshal(dataRes, &scripts); err != nil { + return fmt.Errorf("the format of data.yaml is err: %v", err) + } + + syncTask.Log("start to download scripts.tar.gz") + tmpDir := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/script") + if _, err := os.Stat(tmpDir); err != nil { + _ = os.MkdirAll(tmpDir, 0755) + } + scriptsUrl := fmt.Sprintf("%s/scripts/scripts.tar.gz", global.CONF.RemoteURL.ResourceURL) + if err := files.DownloadFile(scriptsUrl, tmpDir+"/scripts.tar.gz"); err != nil { + return fmt.Errorf("download scripts.tar.gz failed, err: %v", err) + } + syncTask.Log("download successful! now start to decompress...") + if err := files.HandleUnTar(tmpDir+"/scripts.tar.gz", tmpDir, ""); err != nil { + return fmt.Errorf("handle decompress scripts.tar.gz failed, err: %v", err) + } + var scriptsForDB []model.ScriptLibrary + for _, item := range scripts.Scripts.Sh { + itemName, _ := json.Marshal(item.Name) + itemDescription, _ := json.Marshal(item.Description) + shell, _ := os.ReadFile(fmt.Sprintf("%s/scripts/sh/%s.sh", tmpDir, item.Key)) + scriptItem := model.ScriptLibrary{ + Name: string(itemName), + IsSystem: true, + Script: string(shell), + Description: string(itemDescription), + } + scriptsForDB = append(scriptsForDB, scriptItem) + } + + syncTask.Log("analytic completion! now start to refresh database...") + if err := scriptRepo.SyncAll(scriptsForDB); err != nil { + return fmt.Errorf("sync script with db failed, err: %v", err) + } + _ = os.RemoveAll(tmpDir) + if err := global.DB.Model(&model.Setting{}).Where("key = ?", "ScriptVersion").Updates(map[string]interface{}{"value": string(versionRes)}).Error; err != nil { + return fmt.Errorf("update script version in db failed, err: %v", err) + } + return nil + }, nil) + + if err := syncTask.Execute(); err != nil { + return fmt.Errorf("sync scripts from remote failed, err: %v", err) + } + return nil +} + +type Scripts struct { + Scripts ScriptDetail `json:"scripts"` +} + +type ScriptDetail struct { + Sh []ScriptHelper `json:"sh"` +} + +type ScriptHelper struct { + Key string `json:"key"` + Sort uint `json:"sort"` + Groups string `json:"groups"` + Name map[string]string `json:"name"` + Description map[string]string `json:"description"` +} diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go index 3b950babb..59a5664d9 100644 --- a/core/app/service/upgrade.go +++ b/core/app/service/upgrade.go @@ -174,7 +174,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { _ = settingRepo.Update("SystemStatus", "Free") _, _ = cmd.ExecWithTimeOut("systemctl daemon-reload", 30*time.Second) - _, _ = cmd.ExecWithTimeOut("systemctl restart 1pane-agent.service", 1*time.Second) + _, _ = cmd.ExecWithTimeOut("systemctl restart 1panel-agent.service", 1*time.Second) _, _ = cmd.ExecWithTimeOut("systemctl restart 1panel-core.service", 1*time.Second) }() return nil diff --git a/core/app/task/task.go b/core/app/task/task.go index 7cacd80e9..3ee0e313b 100644 --- a/core/app/task/task.go +++ b/core/app/task/task.go @@ -47,10 +47,12 @@ type SubTask struct { const ( TaskUpgrade = "TaskUpgrade" TaskAddNode = "TaskAddNode" + TaskSync = "TaskSync" ) const ( TaskScopeSystem = "System" + TaskScopeScript = "Script" ) const ( diff --git a/core/global/config.go b/core/global/config.go index f6fe16899..5b97e506c 100644 --- a/core/global/config.go +++ b/core/global/config.go @@ -36,7 +36,8 @@ type ApiInterface struct { } type RemoteURL struct { - RepoUrl string `mapstructure:"repo_url"` + RepoUrl string `mapstructure:"repo_url"` + ResourceURL string `mapstructure:"resource_url"` } type LogConfig struct { diff --git a/core/go.mod b/core/go.mod index d74b21f1d..0578119a8 100644 --- a/core/go.mod +++ b/core/go.mod @@ -45,6 +45,7 @@ require ( golang.org/x/sys v0.26.0 golang.org/x/term v0.25.0 golang.org/x/text v0.19.0 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.25.11 ) @@ -120,7 +121,6 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/sqlite v1.4.4 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 003412a1d..4a338a78b 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} succeeded" SubTaskFailed: "{{ .name }} failed: {{ .err }}" TaskInstall: "Install" TaskUpgrade: "Upgrade" +TaskSync: 'Synchronize' SuccessStatus: "{{ .name }} succeeded" FailedStatus: "{{ .name }} failed {{ .err }}" Start: "Start" SubTask: "Subtask" +#script +ScriptLibrary: "Script Library" + #upgrade node NodeUpgrade: "Upgrade node {{ .name }}" UpgradeCheck: "Check for node updates" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index 4307af3cc..2f7ff1a42 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -83,11 +83,15 @@ SubTaskSuccess: "{{ .name }} 成功" SubTaskFailed: "{{ .name }} 失敗: {{ .err }}" TaskInstall: "インストール" TaskUpgrade: "アップグレード" +TaskSync: '同期' SuccessStatus: "{{ .name }} 成功" FailedStatus: "{{ .name }} 失敗 {{ .err }}" Start: "開始" SubTask: "サブタスク" +#script +ScriptLibrary: "スクリプトライブラリ" + #upgrade node NodeUpgrade: "{{ .name }} ノードのアップグレード" UpgradeCheck: "ノードの更新を確認" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index e3b217c65..0cf16e859 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} 성공" SubTaskFailed: "{{ .name }} 실패: {{ .err }}" TaskInstall: "설치" TaskUpgrade: "업그레이드" +TaskSync: '동기화' SuccessStatus: "{{ .name }} 성공" FailedStatus: "{{ .name }} 실패 {{ .err }}" Start: "시작" SubTask: "서브 작업" +#script +ScriptLibrary: "스크립트 라이브러리" + #upgrade node NodeUpgrade: "{{ .name }} 노드 업그레이드" UpgradeCheck: "노드 업데이트 확인" diff --git a/core/i18n/lang/ms.yml b/core/i18n/lang/ms.yml index 67a060ee5..39fbe2836 100644 --- a/core/i18n/lang/ms.yml +++ b/core/i18n/lang/ms.yml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} berjaya" SubTaskFailed: "{{ .name }} gagal: {{ .err }}" TaskInstall: "Pasang" TaskUpgrade: "Kemas kini" +TaskSync: 'Selaraskan' SuccessStatus: "{{ .name }} berjaya" FailedStatus: "{{ .name }} gagal {{ .err }}" Start: "Mula" SubTask: "Tugas Sub" +#script +ScriptLibrary: "Pustaka Skrip" + #upgrade node NodeUpgrade: "Naik taraf node {{ .name }}" UpgradeCheck: "Periksa kemas kini nod" diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index 8283d472b..7f788f946 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} bem-sucedido" SubTaskFailed: "{{ .name }} falhou: {{ .err }}" TaskInstall: "Instalar" TaskUpgrade: "Atualizar" +TaskSync: 'Sincronizar' SuccessStatus: "{{ .name }} bem-sucedido" FailedStatus: "{{ .name }} falhou {{ .err }}" Start: "Iniciar" SubTask: "Subtarefa" +#script +ScriptLibrary: "Biblioteca de Scripts" + #upgrade node NodeUpgrade: "Atualizar nó {{ .name }}" UpgradeCheck: "Verificar atualizações do nó" diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index b3db71042..5024559a1 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} успешно" SubTaskFailed: "{{ .name }} не удалось: {{ .err }}" TaskInstall: "Установить" TaskUpgrade: "Обновить" +TaskSync: 'Синхронизация' SuccessStatus: "{{ .name }} успешно" FailedStatus: "{{ .name }} не удалось {{ .err }}" Start: "Начать" SubTask: "Подзадача" +#script +ScriptLibrary: "Библиотека скриптов" + #upgrade node NodeUpgrade: "Обновление узла {{ .name }}" UpgradeCheck: "Проверить обновления узла" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index b1ef5b49d..5aeb904cc 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} 成功" SubTaskFailed: "{{ .name }} 失敗: {{ .err }}" TaskInstall: "安裝" TaskUpgrade: "升級" +TaskSync: '同步' SuccessStatus: "{{ .name }} 成功" FailedStatus: "{{ .name }} 失敗 {{ .err }}" Start: "開始" SubTask: "子任務" +#script +ScriptLibrary: "腳本庫" + #upgrade node NodeUpgrade: "升級節點 {{ .name }}" UpgradeCheck: "檢查節點更新" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index 9f38f9a82..4892179ce 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -82,11 +82,15 @@ SubTaskSuccess: "{{ .name }} 成功" SubTaskFailed: "{{ .name }} 失败: {{ .err }}" TaskInstall: "安装" TaskUpgrade: "升级" +TaskSync: "同步" SuccessStatus: "{{ .name }} 成功" FailedStatus: "{{ .name }} 失败 {{ .err }}" Start: "开始" SubTask: "子任务" +#script +ScriptLibrary: "脚本库" + #upgrade node NodeUpgrade: "升级节点 {{ .name }}" UpgradeCheck: "检查节点更新" diff --git a/core/init/cron/cron.go b/core/init/cron/cron.go index 95f81694c..95f903fd3 100644 --- a/core/init/cron/cron.go +++ b/core/init/cron/cron.go @@ -1,6 +1,8 @@ package cron import ( + "fmt" + mathRand "math/rand" "time" "github.com/1Panel-dev/1Panel/core/global" @@ -14,7 +16,13 @@ func Init() { global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger))) if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil { - global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error()) + global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error()) + } + + scriptJob := job.NewScriptJob() + scriptJob.Run() + if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), scriptJob); err != nil { + global.LOG.Errorf("[core] can not add script sync corn job: %s", err.Error()) } global.Cron.Start() } diff --git a/core/init/cron/job/script.go b/core/init/cron/job/script.go new file mode 100644 index 000000000..9cf591941 --- /dev/null +++ b/core/init/cron/job/script.go @@ -0,0 +1,18 @@ +package job + +import ( + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/global" +) + +type script struct{} + +func NewScriptJob() *script { + return &script{} +} + +func (s *script) Run() { + if err := service.NewIScriptService().Sync(); err != nil { + global.LOG.Errorf("sync scripts from remote failed, err: %v", err) + } +} diff --git a/core/init/migration/helper/script.go b/core/init/migration/helper/script.go deleted file mode 100644 index 82b8b4c85..000000000 --- a/core/init/migration/helper/script.go +++ /dev/null @@ -1,781 +0,0 @@ -package helper - -import ( - "fmt" - - "github.com/1Panel-dev/1Panel/core/app/model" - "github.com/1Panel-dev/1Panel/core/global" -) - -func LoadScript() { - groups := []model.Group{ - {Name: "install", Type: "script", IsDefault: false}, - {Name: "docker", Type: "script", IsDefault: false}, - {Name: "firewall", Type: "script", IsDefault: false}, - {Name: "supervisor", Type: "script", IsDefault: false}, - {Name: "clamav", Type: "script", IsDefault: false}, - {Name: "ftp", Type: "script", IsDefault: false}, - {Name: "fail2ban", Type: "script", IsDefault: false}} - _ = global.DB.Where("`type` = ?", "script").Delete(&model.Group{}).Error - _ = global.DB.Create(&groups).Error - - _ = global.DB.Where("is_system = ?", 1).Delete(model.ScriptLibrary{}).Error - list := []model.ScriptLibrary{ - {Name: "Install Docker", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[1].ID, groups[0].ID), Script: "bash <(curl -sSL https://linuxmirrors.cn/docker.sh)"}, - - {Name: "Install Firewall", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[2].ID, groups[0].ID), Script: loadInstallFirewall()}, - - {Name: "Install Supervisor", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[3].ID, groups[0].ID), Script: loadInstallSupervisor()}, - - {Name: "Install ClamAV", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[4].ID, groups[0].ID), Script: loadInstallClamAV()}, - - {Name: "Install Pure-FTPd", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[5].ID, groups[0].ID), Script: loadInstallFTP()}, - - {Name: "Install Fail2ban", IsSystem: true, Groups: fmt.Sprintf("%v,%v", groups[6].ID, groups[0].ID), Script: loadInstallFail2ban()}, - } - - _ = global.DB.Create(&list).Error -} - -func loadInstallFirewall() string { - return `#!/bin/bash - -# 防火墙 安装配置脚本 -# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -FIREWALL="" - - -# 检测操作系统 -detect_os() { - if [ -f /etc/os-release ]; then - . /etc/os-release - OS=$ID - VERSION=$VERSION_ID - elif type lsb_release >/dev/null 2>&1; then - OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]') - VERSION=$(lsb_release -sr) - elif [ -f /etc/redhat-release ]; then - OS="rhel" - VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) - elif [ -f /etc/alpine-release ]; then - OS="alpine" - VERSION=$(cat /etc/alpine-release) - else - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - VERSION=$(uname -r) - fi -} - -# 安装防火墙 -install_firewall() { - if [ "$OS" == "ubuntu" ] || [ "$OS" == "debian" ]; then - echo -e "${GREEN}检测到操作系统: $OS $VERSION,正在安装 ufw...${NC}" - FIREWALL="ufw" - apt-get update - apt-get install -y ufw - elif [ "$OS" == "centos" ] || [ "$OS" == "rhel" ] || [ "$OS_LIKE" == "rhel" ]; then - echo -e "${GREEN}检测到操作系统: $OS $VERSION,正在安装 firewall...${NC}" - FIREWALL="firewalld" - yum update - yum install -y firewalld - else - echo -e "${RED}不支持的操作系统${NC}" - exit 1 - fi -} - -# 初始化并启动 -start_with_init() { - read -p "请输入需要放行的端口(多个端口用空格分隔,如 80 443 22): " PORTS - - # 验证端口输入 - if [ -z "$PORTS" ]; then - echo -e "${RED}错误:未输入任何端口${NC}" - exit 1 - fi - - case $FIREWALL in - firewalld) - echo -e "${GREEN}配置firewalld...${NC}" - echo "初始化并启动firewalld..." - systemctl start firewalld - systemctl enable firewalld - - for port in $PORTS; do - firewall-cmd --zone=public --permanent --add-port="$port/tcp" - done - - firewall-cmd --reload - echo -e "${GREEN}已放行以下TCP端口: $PORTS ${NC}" - ;; - - ufw) - echo -e "${GREEN}初始化并启动ufw...${NC}" - ufw --force enable - - for port in $PORTS; do - ufw allow "$port/tcp" - done - - echo -e "${GREEN}已放行以下TCP端口: $PORTS ${NC}" - ;; - esac -} - -# 检查防火墙是否正常运行 -check_install() { - if [ "$FIREWALL" = "firewalld" ]; then - if command -v firewall-cmd &> /dev/null; then - systemctl status firewalld || true - fi - else - if command -v ufw &> /dev/null; then - ufw status || true - fi - fi - - echo -e "${GREEN}$FIREWALL 安装完成并启动${NC}" -} - -# 主函数 -main() { - detect_os - install_firewall - start_with_init - check_install -} - -main "$@"` -} - -func loadInstallFTP() string { - return `#!/bin/bash - -# Pure-FTPd 安装配置脚本 -# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - - -# 检测操作系统 -detect_os() { - if [ -f /etc/os-release ]; then - . /etc/os-release - OS=$ID - VERSION=$VERSION_ID - elif type lsb_release >/dev/null 2>&1; then - OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]') - VERSION=$(lsb_release -sr) - elif [ -f /etc/redhat-release ]; then - OS="rhel" - VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) - elif [ -f /etc/alpine-release ]; then - OS="alpine" - VERSION=$(cat /etc/alpine-release) - else - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - VERSION=$(uname -r) - fi -} - -# 安装Pure-FTPd -install_pureftpd() { - echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}" - - case "$OS" in - ubuntu|debian) - apt-get update - apt-get install -y pure-ftpd - ;; - centos|rhel|fedora) - if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then - dnf install -y epel-release - dnf install -y pure-ftpd - else - yum install -y epel-release - yum install -y pure-ftpd - fi - ;; - alpine) - apk add --no-cache pure-ftpd - ;; - arch) - pacman -Sy --noconfirm pure-ftpd - ;; - *) - echo -e "${RED}不支持的操作系统${NC}" - exit 1 - ;; - esac - - if ! command -v pure-ftpd &> /dev/null; then - echo -e "${RED}Pure-FTPd安装失败${NC}" - exit 1 - fi -} - -# 配置Pure-FTPd -configure_pureftpd() { - echo -e "${GREEN}配置Pure-FTPd...${NC}" - - PURE_FTPD_CONF="/etc/pure-ftpd/pure-ftpd.conf" - if [ -f "$PURE_FTPD_CONF" ]; then - cp "$PURE_FTPD_CONF" "$PURE_FTPD_CONF.bak" - sed -i 's/^NoAnonymous[[:space:]]\+no$/NoAnonymous yes/' "$PURE_FTPD_CONF" - sed -i 's/^PAMAuthentication[[:space:]]\+yes$/PAMAuthentication no/' "$PURE_FTPD_CONF" - sed -i 's/^# PassivePortRange[[:space:]]\+30000 50000$/PassivePortRange 39000 40000/' "$PURE_FTPD_CONF" - sed -i 's/^VerboseLog[[:space:]]\+no$/VerboseLog yes/' "$PURE_FTPD_CONF" - sed -i 's/^# PureDB[[:space:]]\+\/etc\/pure-ftpd\/pureftpd\.pdb[[:space:]]*$/PureDB \/etc\/pure-ftpd\/pureftpd.pdb/' "$PURE_FTPD_CONF" - else - touch /etc/pure-ftpd/pureftpd.pdb - chmod 644 /etc/pure-ftpd/pureftpd.pdb - echo '/etc/pure-ftpd/pureftpd.pdb' > /etc/pure-ftpd/conf/PureDB - echo yes > /etc/pure-ftpd/conf/VerboseLog - echo yes > /etc/pure-ftpd/conf/NoAnonymous - echo '39000 40000' > /etc/pure-ftpd/conf/PassivePortRange - echo 'no' > /etc/pure-ftpd/conf/PAMAuthentication - echo 'no' > /etc/pure-ftpd/conf/UnixAuthentication - echo 'clf:/var/log/pure-ftpd/transfer.log' > /etc/pure-ftpd/conf/AltLog - ln -s /etc/pure-ftpd/conf/PureDB /etc/pure-ftpd/auth/50puredb - fi -} - -# 启动服务 -start_service() { - echo -e "${GREEN}启动Pure-FTPd服务...${NC}" - - case "$OS" in - ubuntu|debian) - systemctl enable pure-ftpd - systemctl restart pure-ftpd - ;; - centos|rhel|fedora) - systemctl enable pure-ftpd - systemctl restart pure-ftpd - ;; - alpine) - rc-update add pure-ftpd - rc-service pure-ftpd start - ;; - arch) - systemctl enable pure-ftpd - systemctl restart pure-ftpd - ;; - *) - echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}" - ;; - esac - - # 验证服务状态 - if command -v systemctl &> /dev/null; then - systemctl status pure-ftpd || true - else - rc-service pure-ftpd status || true - fi -} - - - -# 主函数 -main() { - detect_os - install_pureftpd - configure_pureftpd - start_service -} - -main "$@"` -} - -func loadInstallClamAV() string { - return `#!/bin/bash - -# ClamAV 安装启动脚本 -# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -# 检测操作系统 -detect_os() { - if [ -f /etc/os-release ]; then - . /etc/os-release - OS=$ID - VERSION=$VERSION_ID - elif type lsb_release >/dev/null 2>&1; then - OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]') - VERSION=$(lsb_release -sr) - elif [ -f /etc/redhat-release ]; then - OS="rhel" - VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) - elif [ -f /etc/alpine-release ]; then - OS="alpine" - VERSION=$(cat /etc/alpine-release) - else - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - VERSION=$(uname -r) - fi -} - -# 安装ClamAV -install_clamav() { - echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}" - - case "$OS" in - ubuntu|debian) - apt-get update - apt-get install -y clamav clamav-daemon clamav-freshclam - ;; - centos|rhel|fedora) - if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then - dnf install -y epel-release - dnf install -y clamav clamd clamav-update - else - yum install -y epel-release - yum install -y clamav clamd clamav-update - fi - ;; - alpine) - apk add --no-cache clamav clamav-libunrar clamav-daemon clamav-freshclam - ;; - arch) - pacman -Sy --noconfirm clamav - ;; - *) - echo -e "${RED}不支持的操作系统${NC}" - exit 1 - ;; - esac - - if ! command -v clamscan &> /dev/null; then - echo -e "${RED}ClamAV安装失败${NC}" - exit 1 - fi -} - -# clamd -configure_clamd() { - echo -e "${GREEN}配置clamd...${NC}" - - # 备份原始配置 - CLAMD_CONF="" - if [ -f "/etc/clamd.d/scan.conf" ]; then - CLAMD_CONF="/etc/clamd.d/scan.conf" - elif [ -f "/etc/clamav/clamd.conf" ]; then - CLAMD_CONF="/etc/clamav/clamd.conf" - else - echo "未找到 freshclam 配置文件,请手动配置" - exit 1 - fi - cp "$CLAMD_CONF" "$CLAMD_CONF.bak" - - # 禁用检查新版本以避免权限问题 - sed -i 's|^LogFileMaxSize .*|LogFileMaxSize 2M|' "$CLAMD_CONF" - sed -i 's|^PidFile .*|PidFile /run/clamd.scan/clamd.pid|' "$CLAMD_CONF" - sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$CLAMD_CONF" - sed -i 's|^LocalSocket .*|LocalSocket /run/clamd.scan/clamd.sock|' "$CLAMD_CONF" -} - -# 配置freshclam -configure_freshclam() { - echo -e "${GREEN}配置freshclam...${NC}" - - # 备份原始配置 - FRESHCLAM_CONF="" - if [ -f "/etc/freshclam.conf" ]; then - FRESHCLAM_CONF="/etc/freshclam.conf" - elif [ -f "/etc/clamav/freshclam.conf" ]; then - FRESHCLAM_CONF="/etc/clamav/freshclam.conf" - else - echo "未找到 freshclam 配置文件,请手动配置" - exit 1 - fi - cp "$FRESHCLAM_CONF" "$FRESHCLAM_CONF.bak" - - # 禁用检查新版本以避免权限问题 - sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /var/lib/clamav|' "$FRESHCLAM_CONF" - sed -i 's|^PidFile .*|PidFile /var/run/freshclam.pid|' "$FRESHCLAM_CONF" - sed -i '/^DatabaseMirror/d' "$FRESHCLAM_CONF" - echo "DatabaseMirror database.clamav.net" | sudo tee -a "$FRESHCLAM_CONF" - sed -i 's|^Checks .*|Checks 12|' "$FRESHCLAM_CONF" -} - -# 下载病毒数据库 -download_database() { - systemctl stop clamav-freshclam - echo -e "${GREEN}开始下载病毒数据库...${NC}" - - MAX_RETRIES=5 - RETRY_DELAY=60 - ATTEMPT=1 - - while [ $ATTEMPT -le $MAX_RETRIES ]; do - echo -e "${YELLOW}尝试 $ATTEMPT/$MAX_RETRIES: 运行freshclam...${NC}" - - if freshclam --verbose; then - echo -e "${GREEN}成功下载病毒数据库${NC}" - return 0 - fi - - if [ $ATTEMPT -lt $MAX_RETRIES ]; then - echo -e "${YELLOW}下载失败,等待 $RETRY_DELAY 秒后重试...${NC}" - sleep $RETRY_DELAY - fi - - ATTEMPT=$((ATTEMPT+1)) - done - - echo -e "${RED}错误: 无法在 $MAX_RETRIES 次尝试后下载病毒数据库${NC}" >&2 - exit 1 -} - -# 启动ClamAV服务 -start_services() { - echo -e "${GREEN}启动ClamAV服务...${NC}" - - case "$OS" in - ubuntu|debian) - systemctl enable --now clamav-daemon - systemctl enable --now clamav-freshclam - ;; - centos|rhel|fedora) - systemctl enable --now clamd@scan - systemctl enable --now clamav-freshclam - ;; - alpine) - rc-update add clamd boot - rc-update add freshclam boot - rc-service clamd start - rc-service freshclam start - ;; - arch) - systemctl enable --now clamav-daemon - systemctl enable --now clamav-freshclam - ;; - *) - echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}" - ;; - esac - - # 验证服务状态 - if command -v systemctl &> /dev/null; then - systemctl status clamav-daemon || true - systemctl status clamav-freshclam || true - fi - - echo -e "${GREEN}ClamAV安装完成并启动${NC}" -} - -# 主函数 -main() { - detect_os - install_clamav - configure_clamd - configure_freshclam - download_database - start_services -} - -main "$@"` -} - -func loadInstallFail2ban() string { - return `#!/bin/bash - -# Fail2ban 安装配置脚本 -# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - - -# 检测操作系统 -detect_os() { - if [ -f /etc/os-release ]; then - . /etc/os-release - OS=$ID - VERSION=$VERSION_ID - elif type lsb_release >/dev/null 2>&1; then - OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]') - VERSION=$(lsb_release -sr) - elif [ -f /etc/redhat-release ]; then - OS="rhel" - VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) - elif [ -f /etc/alpine-release ]; then - OS="alpine" - VERSION=$(cat /etc/alpine-release) - else - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - VERSION=$(uname -r) - fi -} - -# 安装Fail2ban -install_fail2ban() { - echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}" - - case "$OS" in - ubuntu|debian) - apt-get update - apt-get install -y fail2ban - ;; - centos|rhel|fedora) - if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then - dnf install -y epel-release - dnf install -y fail2ban - else - yum install -y epel-release - yum install -y fail2ban - fi - ;; - alpine) - apk add --no-cache fail2ban - ;; - arch) - pacman -Sy --noconfirm fail2ban - ;; - *) - echo -e "${RED}不支持的操作系统${NC}" - exit 1 - ;; - esac - - sleep 2 - if command -v systemctl &> /dev/null; then - systemctl status fail2ban --no-pager || true - else - rc-service fail2ban status || true - fi - - fail2ban-client status -} - -# 配置Fail2ban -configure_fail2ban() { - echo -e "${GREEN}配置Fail2ban...${NC}" - - FAIL2BAN_CONF="/etc/fail2ban/jail.local" - LOG_FILE="" - BAN_ACTION="" - - if systemctl is-active --quiet firewalld 2>/dev/null; then - BAN_ACTION="firewallcmd-ipset" - elif systemctl is-active --quiet ufw 2>/dev/null || service ufw status 2>/dev/null | grep -q "active"; then - BAN_ACTION="ufw" - else - BAN_ACTION="iptables-allports" - fi - - if [ -f /var/log/secure ]; then - LOG_FILE="/var/log/secure" - else - LOG_FILE="/var/log/auth.log" - fi - - cat < "$FAIL2BAN_CONF" -#DEFAULT-START -[DEFAULT] -bantime = 600 -findtime = 300 -maxretry = 5 -banaction = $BAN_ACTION -action = %(action_mwl)s -#DEFAULT-END - -[sshd] -ignoreip = 127.0.0.1/8 -enabled = true -filter = sshd -port = 22 -maxretry = 5 -findtime = 300 -bantime = 600 -banaction = $BAN_ACTION -action = %(action_mwl)s -logpath = $LOG_FILE -EOF -} - -# 启动服务 -start_service() { - echo -e "${GREEN}启动Fail2ban服务...${NC}" - - case "$OS" in - ubuntu|debian) - systemctl enable fail2ban - systemctl restart fail2ban - ;; - centos|rhel|fedora) - systemctl enable fail2ban - systemctl restart fail2ban - ;; - alpine) - rc-update add fail2ban - rc-service fail2ban start - ;; - arch) - systemctl enable fail2ban - systemctl restart fail2ban - ;; - *) - echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}" - ;; - esac - - # 验证服务状态 - if command -v systemctl &> /dev/null; then - systemctl status fail2ban || true - else - rc-service fail2ban status || true - fi -} - - - -# 主函数 -main() { - detect_os - install_fail2ban - configure_fail2ban - start_service -} - -main "$@"` -} - -func loadInstallSupervisor() string { - return `#!/bin/bash - -# Supervisor 安装管理脚本 -# 功能:自动安装 + 基础配置 + 进程管理模板 -# 支持 Ubuntu/Debian/CentOS/RHEL/Alpine/Arch Linux - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -# 检测操作系统 -detect_os() { - if [ -f /etc/os-release ]; then - . /etc/os-release - OS=$ID - VERSION=$VERSION_ID - elif type lsb_release >/dev/null 2>&1; then - OS=$(lsb_release -si | tr '[:upper:]' '[:lower:]') - VERSION=$(lsb_release -sr) - elif [ -f /etc/redhat-release ]; then - OS="rhel" - VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release) - elif [ -f /etc/alpine-release ]; then - OS="alpine" - VERSION=$(cat /etc/alpine-release) - else - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - VERSION=$(uname -r) - fi -} - -# 安装Supervisor -install_supervisor() { - echo -e "${GREEN}检测到操作系统: $OS $VERSION${NC}" - - case "$OS" in - ubuntu|debian) - apt-get update - apt-get install -y supervisor - ;; - centos|rhel|fedora) - if [ "$OS" = "rhel" ] && [ "${VERSION%%.*}" -ge 8 ]; then - dnf install -y supervisor - else - yum install -y supervisor - fi - ;; - alpine) - apk add --no-cache supervisor - mkdir -p /etc/supervisor.d - ;; - arch) - pacman -Sy --noconfirm supervisor - ;; - *) - echo -e "${RED}不支持的操作系统,尝试pip安装...${NC}" - if ! command -v pip &> /dev/null; then - python -m ensurepip --upgrade - fi - pip install supervisor - ;; - esac -} - -# 启动服务 -start_service() { - echo -e "${GREEN}启动Supervisor服务...${NC}" - - case "$OS" in - ubuntu|debian) - systemctl enable supervisor - systemctl restart supervisor - ;; - centos|rhel|fedora) - systemctl enable supervisor - systemctl restart supervisor - ;; - alpine) - rc-update add supervisor - rc-service supervisor start - ;; - arch) - systemctl enable supervisor - systemctl restart supervisor - ;; - *) - echo -e "${YELLOW}无法自动启动服务,请手动启动${NC}" - ;; - esac - - # 验证服务状态 - if ! command -v supervisord &> /dev/null; then - echo -e "${RED}Supervisor安装失败${NC}" - exit 1 - fi -} - -# 主函数 -main() { - detect_os - install_supervisor - start_service -} - -main "$@"` -} diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 4703fcc10..f596957d3 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -24,6 +24,7 @@ func Init() { migrations.AddSystemIP, migrations.InitScriptLibrary, migrations.AddOperationNode, + migrations.AddScriptVersion, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index 29381f0dc..2122f583c 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -328,7 +328,9 @@ var InitScriptLibrary = &gormigrate.Migration{ if err := tx.AutoMigrate(&model.ScriptLibrary{}); err != nil { return err } - helper.LoadScript() + if err := tx.Create(&model.Group{Name: "Default", Type: "script", IsDefault: true}).Error; err != nil { + return err + } return nil }, } @@ -342,3 +344,13 @@ var AddOperationNode = &gormigrate.Migration{ return nil }, } + +var AddScriptVersion = &gormigrate.Migration{ + ID: "20250410-add-script-version", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "ScriptVersion", Value: ""}).Error; err != nil { + return err + } + return nil + }, +} diff --git a/core/router/ro_script_library.go b/core/router/ro_script_library.go index b57a996ac..fae176472 100644 --- a/core/router/ro_script_library.go +++ b/core/router/ro_script_library.go @@ -18,6 +18,7 @@ func (s *ScriptRouter) InitRouter(Router *gin.RouterGroup) { scriptRouter.POST("/search", baseApi.SearchScript) scriptRouter.POST("/del", baseApi.DeleteScript) scriptRouter.POST("/update", baseApi.UpdateScript) + scriptRouter.POST("/sync", baseApi.SyncScript) scriptRouter.GET("/run", baseApi.RunScript) } } diff --git a/frontend/src/api/modules/cronjob.ts b/frontend/src/api/modules/cronjob.ts index a05ddeb55..2e0cdb72d 100644 --- a/frontend/src/api/modules/cronjob.ts +++ b/frontend/src/api/modules/cronjob.ts @@ -63,6 +63,9 @@ export const searchScript = (params: SearchWithPage) => { export const addScript = (params: Cronjob.ScriptOperate) => { return http.post(`/core/script`, params); }; +export const syncScript = () => { + return http.post(`/core/script/sync`, {}, TimeoutEnum.T_60S); +}; export const editScript = (params: Cronjob.ScriptOperate) => { return http.post(`/core/script/update`, params); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 7ebc42561..64eb92c78 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -36,6 +36,7 @@ const message = { view: 'View', watch: 'Watch', handle: 'Handle', + clone: 'Clone', expand: 'Expand', collapse: 'Collapse', log: 'Log', @@ -1047,6 +1048,7 @@ const message = { 'Set different groups based on script characteristics, which allows for faster script filtering operations.', handleHelper: 'Execute script {1} on {0}, continue?', noSuchApp: 'The {0} service was not detected. Please install it quickly using the script library first!', + syncHelper: 'About to sync system script library. This operation only affects system scripts. Continue?', }, }, monitor: { diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index b80de88cc..bb3bc68f0 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -34,6 +34,7 @@ const message = { view: 'ビュー', watch: '時計', handle: 'トリガー', + clone: 'クローン', expand: '拡大する', collapse: '崩壊', log: 'ログ', @@ -1007,6 +1008,8 @@ const message = { handleHelper: '{0} で {1} スクリプトを実行します。続行しますか?', noSuchApp: '{0} サービスが検出されませんでした。スクリプトライブラリを使って素早くインストールしてください!', + syncHelper: + 'システムスクリプトライブラリを同期します。この操作はシステムスクリプトのみ影響します。続行しますか?', }, }, monitor: { diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 81040a1fe..349b96a50 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -34,6 +34,7 @@ const message = { view: '보기', watch: '감시', handle: '트리거', + clone: '복제', expand: '확장', collapse: '축소', log: '로그', @@ -1000,6 +1001,8 @@ const message = { '스크립트 특성에 따라 다양한 그룹을 설정하여 스크립트 필터링 작업을 더 빠르게 수행할 수 있습니다.', handleHelper: '{0} 에서 {1} 스크립트를 실행합니다. 계속하시겠습니까?', noSuchApp: '{0} 서비스가 감지되지 않았습니다. 스크립트 라이브러리를 사용하여 먼저 빠르게 설치하세요!', + syncHelper: + '시스템 스크립트 라이브러리를 동기화합니다. 이 작업은 시스템 스크립트에만 적용됩니다. 계속하시겠습니까?', }, }, monitor: { diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index b3a0bdb4d..99e2646dc 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -34,6 +34,7 @@ const message = { view: 'Lihat', watch: 'Pantau', handle: 'Picu', + clone: 'Klon', expand: 'Kembang', collapse: 'Runtuh', log: 'Log', @@ -1037,6 +1038,7 @@ const message = { handleHelper: 'Akan melaksanakan skrip {1} pada {0}, teruskan?', noSuchApp: 'Perkhidmatan {0} tidak dikesan. Sila pasang dengan cepat menggunakan pustaka skrip terlebih dahulu!', + syncHelper: 'Akan menyelaraskan pustaka skrip sistem. Operasi ini hanya melibatkan skrip sistem. Teruskan?', }, }, monitor: { diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 2a1d6ce5c..2186f3fea 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -34,6 +34,7 @@ const message = { view: 'Visualizar', watch: 'Monitorar', handle: 'Disparar', + clone: 'Clonar', expand: 'Expandir', collapse: 'Recolher', log: 'Logs', @@ -1026,6 +1027,8 @@ const message = { handleHelper: 'Executar o script {1} em {0}, continuar?', noSuchApp: 'O serviço {0} não foi detectado. Por favor, instale-o rapidamente usando a biblioteca de scripts primeiro!', + syncHelper: + 'Preparando para sincronizar a biblioteca de scripts do sistema. Esta operação afeta apenas scripts do sistema. Continuar?', }, }, monitor: { diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 6618a3f7b..fef0e2708 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -35,6 +35,7 @@ const message = { watch: 'Наблюдать', handle: 'Запустить', expand: 'Развернуть', + clone: 'Клонировать', collapse: 'Свернуть', log: 'Логи', back: 'Назад', @@ -1032,6 +1033,8 @@ const message = { handleHelper: 'Выполнить сценарий {1} на {0}, продолжить?', noSuchApp: 'Служба {0} не обнаружена. Пожалуйста, сначала быстро установите её, используя библиотеку скриптов!', + syncHelper: + 'Выполнить синхронизацию библиотеки системных скриптов? Это действие затрагивает только системные скрипты. Продолжить?', }, }, monitor: { diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 4a42ea6e3..b2f1b7b7a 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -36,6 +36,7 @@ const message = { view: '詳情', watch: '追蹤', handle: '執行', + clone: '克隆', expand: '展開', collapse: '收起', log: '日誌', @@ -994,6 +995,7 @@ const message = { groupHelper: '根據腳本特徵設置不同的分組,可以更快地對腳本進行篩選操作。', handleHelper: '將在 {0} 上執行 {1} 腳本,是否繼續?', noSuchApp: '未檢測到 {0} 服務,請先使用腳本庫快速安裝!', + syncHelper: '即將同步系統腳本庫,該操作僅針對系統腳本,是否繼續?', }, }, monitor: { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 82d3d2e04..8b2c3949a 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -36,6 +36,7 @@ const message = { view: '详情', watch: '追踪', handle: '执行', + clone: '克隆', expand: '展开', collapse: '收起', log: '日志', @@ -992,6 +993,7 @@ const message = { groupHelper: '针对脚本特征设置不同的分组,可以更加快速的对脚本进行筛选操作。', handleHelper: '将在 {0} 上执行 {1} 脚本,是否继续?', noSuchApp: '未检测到 {0} 服务,请先使用脚本库安装脚本快速安装!', + syncHelper: '即将同步系统脚本库,该操作仅针对系统脚本,是否继续?', }, }, monitor: { diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 2ac17a817..9da5636de 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -84,6 +84,19 @@ export function loadUpTime(uptime: number) { return seconds + i18n.global.t('commons.units.second'); } +// 20250310 +export function getCurrentDateFormatted() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + + return `${year}${month}${day}${hours}${minutes}${seconds}`; +} + export function dateFormat(row: any, col: any, dataStr: any) { const date = new Date(dataStr); const y = date.getFullYear(); diff --git a/frontend/src/views/cronjob/library/index.vue b/frontend/src/views/cronjob/library/index.vue index b75cc2baf..3e9c56647 100644 --- a/frontend/src/views/cronjob/library/index.vue +++ b/frontend/src/views/cronjob/library/index.vue @@ -5,6 +5,9 @@ {{ $t('commons.button.add') }} + + {{ $t('commons.button.sync') }} + {{ $t('commons.table.group') }} @@ -83,11 +86,11 @@