mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-06 13:27:43 +08:00
feat: Support remote synchronization of script library scripts (#8371)
This commit is contained in:
parent
04f1722cce
commit
ce4a770e23
35 changed files with 314 additions and 802 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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+"%")
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -47,10 +47,12 @@ type SubTask struct {
|
|||
const (
|
||||
TaskUpgrade = "TaskUpgrade"
|
||||
TaskAddNode = "TaskAddNode"
|
||||
TaskSync = "TaskSync"
|
||||
)
|
||||
|
||||
const (
|
||||
TaskScopeSystem = "System"
|
||||
TaskScopeScript = "Script"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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: "ノードの更新を確認"
|
||||
|
|
|
@ -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: "노드 업데이트 확인"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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ó"
|
||||
|
|
|
@ -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: "Проверить обновления узла"
|
||||
|
|
|
@ -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: "檢查節點更新"
|
||||
|
|
|
@ -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: "检查节点更新"
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
18
core/init/cron/job/script.go
Normal file
18
core/init/cron/job/script.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 <<EOF > "$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 "$@"`
|
||||
}
|
|
@ -24,6 +24,7 @@ func Init() {
|
|||
migrations.AddSystemIP,
|
||||
migrations.InitScriptLibrary,
|
||||
migrations.AddOperationNode,
|
||||
migrations.AddScriptVersion,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onSync()">
|
||||
{{ $t('commons.button.sync') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onOpenGroupDialog()">
|
||||
{{ $t('commons.table.group') }}
|
||||
</el-button>
|
||||
|
@ -83,11 +86,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat, deepCopy, getCurrentDateFormatted } from '@/utils/util';
|
||||
import GroupDialog from '@/components/group/index.vue';
|
||||
import OperateDialog from '@/views/cronjob/library/operate/index.vue';
|
||||
import TerminalDialog from '@/views/cronjob/library/run/index.vue';
|
||||
import { deleteScript, searchScript } from '@/api/modules/cronjob';
|
||||
import { deleteScript, searchScript, syncScript } from '@/api/modules/cronjob';
|
||||
import { onMounted, reactive, ref } from '@vue/runtime-core';
|
||||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
import i18n from '@/lang';
|
||||
|
@ -170,6 +173,24 @@ const onDelete = async (row: Cronjob.ScriptInfo | null) => {
|
|||
});
|
||||
};
|
||||
|
||||
const onSync = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('cronjob.library.syncHelper', i18n.global.t('commons.button.sync')), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
await syncScript()
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
info: searchInfo.value,
|
||||
|
@ -214,6 +235,18 @@ const buttons = [
|
|||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.clone'),
|
||||
disabled: (row: any) => {
|
||||
return !row.isSystem;
|
||||
},
|
||||
click: (row: Cronjob.ScriptInfo) => {
|
||||
let item = deepCopy(row) as Cronjob.ScriptInfo;
|
||||
item.id = 0;
|
||||
item.name += '-' + getCurrentDateFormatted();
|
||||
onOpenDialog('create', item);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
disabled: (row: any) => {
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('setting.apiInterface')" prop="apiInterface">
|
||||
<el-form-item :label="$t('setting.apiInterface')" prop="apiInterface" v-if="isMaster">
|
||||
<el-switch
|
||||
@change="onChangeApiInterfaceStatus"
|
||||
v-model="form.apiInterfaceStatus"
|
||||
|
|
Loading…
Add table
Reference in a new issue