Files
1Panel/core/init/migration/migrations/init.go
ssongliu 7915230121 refactor: rebuild firewall management (#13628)
* refactor(firewall): rebuild rule management foundation

* refactor(firewall): streamline rule checks and inventory

* feat(firewall): improve native rule inventory

* refactor(firewall): refine rule management

* feat: add Docker port guard

* feat(firewall): support native nftables

* feat(firewall): add configurable firewall selection

* feat(firewall): support nftables docker port guard

* refactor(firewall): complete v2 rule management and migration

* refactor(firewall): align state and API contracts

* refactor(firewall): unify rule management operations

* feat: refine firewall v2 rules and forwarding

* fix(firewall): harden dual-stack rule management

* refactor(firewall): consolidate rule validation and persistence
2026-08-24 12:51:34 +08:00

1388 lines
39 KiB
Go

package migrations
import (
"encoding/json"
"errors"
"fmt"
"path"
"strings"
"time"
"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/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/init/migration/helper"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/1Panel-dev/1Panel/core/utils/ctl_conf"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
"github.com/1Panel-dev/1Panel/core/utils/menutree"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
var AddTable = &gormigrate.Migration{
ID: "20240506-add-table",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.OperationLog{},
&model.LoginLog{},
&model.Setting{},
&model.BackupAccount{},
&model.Group{},
&model.Command{},
&model.UpgradeLog{},
&model.ScriptLibrary{},
)
},
}
var InitSetting = &gormigrate.Migration{
ID: "20200908-add-table-setting",
Migrate: func(tx *gorm.DB) error {
encryptKey := common.RandStr(16)
global.CONF.Base.EncryptKey = encryptKey
language := "en"
if global.CONF.Base.Language == "zh" {
language = "zh"
}
if !global.CONF.Base.IsEnterprise {
if err := tx.Create(&model.Setting{Key: "UserName", Value: global.CONF.Base.Username}).Error; err != nil {
return err
}
pass, _ := encrypt.StringEncrypt(global.CONF.Base.Password)
if err := tx.Create(&model.Setting{Key: "Password", Value: pass}).Error; err != nil {
return err
}
}
_ = ctl_conf.UpdateInFile("/usr/local/bin/1pctl", "ORIGINAL_PASSWORD", "**********")
if err := tx.Create(&model.Setting{Key: "Theme", Value: "light"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "MenuTabs", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "MenuAccordion", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "PanelName", Value: "1Panel"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Edition", Value: global.CONF.Base.Edition}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Language", Value: language}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "DocSource", Value: "withByRegion"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SessionTimeout", Value: "86400"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SSLType", Value: "self"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SSLID", Value: "0"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SSL", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "DeveloperMode", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ProxyType", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ProxyUrl", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ProxyPort", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ProxyUser", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ProxyPasswd", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ProxyPasswdKeep", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "HideMenu", Value: helper.LoadMenus()}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ServerPort", Value: global.CONF.Conn.Port}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: global.CONF.Conn.Entrance}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: encryptKey}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ExpirationTime", Value: time.Now().AddDate(0, 0, 10).Format(constant.DateTimeLayout)}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ExpirationDays", Value: "0"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ComplexityVerification", Value: constant.StatusEnable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "MFAStatus", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "MFASecret", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "MFAInterval", Value: "30"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "PasskeyUserID", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "PasskeyCredentials", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "PasskeyTrustedProxies", Value: "127.0.0.1\n::1"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: global.CONF.Base.Version}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "SystemStatus", Value: "Free"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "BindAddress", Value: "0.0.0.0"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Ipv6", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "BindDomain", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "AllowIPs", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "AllowIPTrustedProxies", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "NoAuthSetting", Value: "200"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ApiInterfaceStatus", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ApiKey", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "IpWhiteList", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ApiTrustedProxies", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ApiKeyValidityTime", Value: "120"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ScriptVersion", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "UninstallDeleteImage", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "UpgradeBackup", Value: "Enable"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "UpgradeDeleteImage", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "UninstallDeleteBackup", Value: constant.StatusDisable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "InstallAllowPort", Value: constant.StatusDisable}).Error; err != nil {
return err
}
return nil
},
}
var AddPasskeySetting = &gormigrate.Migration{
ID: "20250910-add-passkey-setting",
Migrate: func(tx *gorm.DB) error {
var addSettingsIfMissing = func(tx *gorm.DB, key, value string) error {
var setting model.Setting
if err := tx.Where("key = ?", key).First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: key, Value: value}).Error
}
return err
}
return nil
}
if err := addSettingsIfMissing(tx, "PasskeyUserID", ""); err != nil {
return err
}
if err := addSettingsIfMissing(tx, "PasskeyCredentials", ""); err != nil {
return err
}
return nil
},
}
var AddPasskeyTrustedProxySetting = &gormigrate.Migration{
ID: "20260210-add-passkey-trusted-proxy-setting",
Migrate: func(tx *gorm.DB) error {
var addSettingsIfMissing = func(tx *gorm.DB, key, value string) error {
var setting model.Setting
if err := tx.Where("key = ?", key).First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: key, Value: value}).Error
}
return err
}
return nil
}
return addSettingsIfMissing(tx, "PasskeyTrustedProxies", "127.0.0.1\n::1")
},
}
var InitTerminalSetting = &gormigrate.Migration{
ID: "20240814-init-terminal-setting",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "LineHeight", Value: "1.2"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "LetterSpacing", Value: "0"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "FontSize", Value: "12"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "FontFamily", Value: "Monaco, Menlo, Consolas, 'Courier New', monospace"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "BackgroundColor", Value: "#000000"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ForegroundColor", Value: "#f5f5f5"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "CursorBlink", Value: constant.StatusEnable}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "CursorStyle", Value: "block"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "Scrollback", Value: "1000"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ScrollSensitivity", Value: "6"}).Error; err != nil {
return err
}
return nil
},
}
var InitHost = &gormigrate.Migration{
ID: "20240816-init-host",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Group{Name: "Default", Type: "host", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "Default", Type: "node", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "Default", Type: "command", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "Default", Type: "website", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "Default", Type: "redis", IsDefault: true}).Error; err != nil {
return err
}
if err := tx.Create(&model.Group{Name: "Default", Type: "script", IsDefault: true}).Error; err != nil {
return err
}
return nil
},
}
var InitOneDrive = &gormigrate.Migration{
ID: "20240808-init-one-drive",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "OneDriveID", Value: "MDEwOTM1YTktMWFhOS00ODU0LWExZGMtNmU0NWZlNjI4YzZi"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "OneDriveSc", Value: "akpuOFF+YkNXOU1OLWRzS1ZSRDdOcG1LT2ZRM0RLNmdvS1RkVWNGRA=="}).Error; err != nil {
return err
}
if err := tx.Create(&model.BackupAccount{
Name: "localhost",
Type: "LOCAL",
Vars: fmt.Sprintf("{\"dir\":\"%s\"}", path.Join(global.CONF.Base.InstallDir, "1panel/backup")),
}).Error; err != nil {
return err
}
return nil
},
}
var AddTaskDB = &gormigrate.Migration{
ID: "20241125-add-task-table",
Migrate: func(tx *gorm.DB) error {
return global.TaskDB.AutoMigrate(
&model.Task{},
)
},
}
var AddXpackHideMenu = &gormigrate.Migration{
ID: "20250529-add-xpack-hide-menu",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if strings.Contains(menuJSON, `"XApp"`) && strings.Contains(menuJSON, `"/xpack/app"`) {
return nil
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
newItem := dto.ShowMenu{
ID: "118",
Disabled: false,
Title: "xpack.app.app",
IsShow: true,
Label: "XApp",
Path: "/xpack/app",
}
for i, menu := range menus {
if menu.ID == "11" {
exists := false
for _, child := range menu.Children {
if child.ID == newItem.ID {
exists = true
break
}
}
if !exists {
menus[i].Children = append([]dto.ShowMenu{newItem}, menus[i].Children...)
}
break
}
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var UpdateXpackHideMenu = &gormigrate.Migration{
ID: "20250617-update-xpack-hide-menu",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
newItem := dto.ShowMenu{
ID: "119",
Disabled: false,
Title: "xpack.upage",
IsShow: true,
Label: "Upage",
Path: "/xpack/upage",
}
for i, menu := range menus {
if menu.ID == "11" {
exists := false
for _, child := range menu.Children {
if child.ID == newItem.ID {
exists = true
break
}
}
if exists {
break
}
insertIndex := -1
for j, child := range menu.Children {
if child.ID == "111" {
insertIndex = j
break
}
}
if insertIndex != -1 {
children := menu.Children
menus[i].Children = append(children[:insertIndex+1], append([]dto.ShowMenu{newItem}, children[insertIndex+1:]...)...)
} else {
menus[i].Children = append([]dto.ShowMenu{newItem}, menus[i].Children...)
}
break
}
}
for i, menu := range menus {
if menu.ID == "11" {
existsIndex := -1
for j, child := range menu.Children {
if child.ID == "118" {
existsIndex = j
break
}
}
if existsIndex == 0 {
break
}
var item118 dto.ShowMenu
if existsIndex != -1 {
item118 = menu.Children[existsIndex]
menus[i].Children = append(menu.Children[:existsIndex], menu.Children[existsIndex+1:]...)
} else {
item118 = dto.ShowMenu{
ID: "118",
Disabled: false,
Title: "xpack.app.app",
IsShow: true,
Label: "XApp",
Path: "/xpack/app",
}
}
menus[i].Children = append([]dto.ShowMenu{item118}, menus[i].Children...)
break
}
}
var idx9, idx10 = -1, -1
for i, menu := range menus {
if menu.ID == "9" && menu.Path == "/toolbox" {
idx9 = i
}
if menu.ID == "10" && menu.Path == "/cronjobs" {
idx10 = i
}
}
if idx9 != -1 && idx10 != -1 && idx10 > idx9 {
menus[idx9], menus[idx10] = menus[idx10], menus[idx9]
}
for i, menu := range menus {
if menu.ID == "7" {
for j, child := range menu.Children {
if child.ID == "75" {
if child.Title != "menu.processManage" {
menus[i].Children[j].Title = "menu.processManage"
}
break
}
}
break
}
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var UpdateOnedrive = &gormigrate.Migration{
ID: "20250704-update-onedrive",
Migrate: func(tx *gorm.DB) error {
if err := tx.Model(&model.Setting{}).
Where("key = ?", "OneDriveID").
Update("value", "NTQ0NmNmZTMtNGM3OS00N2EwLWFlMjUtZmM2NDU0NzhlMmQ5").Error; err != nil {
return err
}
if err := tx.Model(&model.Setting{}).
Where("key = ?", "OneDriveSc").
Update("value", "bGRlOFF+WEVrR1M0b25Vb1VsRWpMYzE2MW9rTXZEM25KdnZ1MGN6MA==").Error; err != nil {
return err
}
return nil
},
}
var AddClusterMenu = &gormigrate.Migration{
ID: "20250707-add-cluster-menu",
Migrate: func(tx *gorm.DB) error {
return helper.AddMenu(dto.ShowMenu{
ID: "120",
Disabled: false,
Title: "xpack.cluster.cluster",
IsShow: true,
Label: "Cluster",
Path: "/xpack/cluster",
}, "11", tx)
},
}
var DeleteXpackHideMenu = &gormigrate.Migration{
ID: "20250718-delete-xpack-hide-menu",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
menuJSON = helper.LoadMenus()
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
menus = helper.RemoveMenuByID(menus, "116")
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AddCronjobGroup = &gormigrate.Migration{
ID: "20250729-add-cronjob-group",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Group{Name: "Default", Type: "cronjob", IsDefault: true}).Error; err != nil {
return err
}
return nil
},
}
var AddDiskMenu = &gormigrate.Migration{
ID: "20250811-add-disk-menu",
Migrate: func(tx *gorm.DB) error {
return helper.AddMenu(dto.ShowMenu{
ID: "77",
Disabled: false,
Title: "menu.disk",
IsShow: true,
Label: "Disk",
Path: "/hosts/disk",
}, "7", tx)
},
}
var AddAgentsMenu = &gormigrate.Migration{
ID: "20260204-add-agents-menu",
Migrate: func(tx *gorm.DB) error {
return helper.AddMenu(dto.ShowMenu{
ID: "44",
Disabled: false,
Title: "aiTools.agents.agent",
IsShow: true,
Label: "Agents",
Path: "/ai/agents/agent",
}, "4", tx)
},
}
var AddSimpleNodeGroup = &gormigrate.Migration{
ID: "20250916-add-simple-node-group",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Group{Name: "Default", Type: "SimpleNode", IsDefault: true}).Error; err != nil {
return err
}
return nil
},
}
var AddUpgradeBackupCopies = &gormigrate.Migration{
ID: "20250925-add-upgrade-backup-copies",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "UpgradeBackupCopies", Value: "0"}).Error; err != nil {
return err
}
return nil
},
}
var AddScriptSync = &gormigrate.Migration{
ID: "20250916-add-script-sync",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "ScriptSync", Value: constant.StatusEnable}).Error; err != nil {
return err
}
return nil
},
}
var AddDashboardCarouselSetting = &gormigrate.Migration{
ID: "20260210-add-dashboard-carousel-setting",
Migrate: func(tx *gorm.DB) error {
var addSettingsIfMissing = func(tx *gorm.DB, key, value string) error {
var setting model.Setting
if err := tx.Where("key = ?", key).First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: key, Value: value}).Error
}
return err
}
return nil
}
if err := addSettingsIfMissing(tx, "DashboardMemoVisible", constant.StatusEnable); err != nil {
return err
}
if err := addSettingsIfMissing(tx, "DashboardSimpleNodeVisible", constant.StatusEnable); err != nil {
return err
}
return nil
},
}
var UpdateXpackHideMenuSort = &gormigrate.Migration{
ID: "20251009-update-xpack-hide-menu-sort",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
labelSortMap := make(map[string]int)
for _, item := range helper.MenuSort() {
labelSortMap[item.Label] = item.Sort
}
for i := range menus {
if sortVal, exists := labelSortMap[menus[i].Label]; exists {
menus[i].Sort = sortVal
} else if menus[i].Sort <= 0 {
menus[i].Sort = (i + 1) * 100
}
for j := range menus[i].Children {
if childSort, exists := labelSortMap[menus[i].Children[j].Label]; exists {
menus[i].Children[j].Sort = childSort
} else if menus[i].Children[j].Sort <= 0 {
menus[i].Children[j].Sort = menus[i].Sort + (j+1)*10
}
}
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AdjustXpackNode = &gormigrate.Migration{
ID: "20260108-adjust-xpack-node",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
menuJSON = helper.LoadMenus()
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
for i := 0; i < len(menus); i++ {
if menus[i].Label != "Xpack-Menu" {
continue
}
for j := 0; j < len(menus[i].Children); j++ {
if menus[i].Children[j].Label == "Node" {
menus[i].Children[j].Label = "NodeDashboard"
menus[i].Children[j].Path = "/xpack/node/dashboard"
break
}
}
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var UpdateAiAgentsMenu = &gormigrate.Migration{
ID: "20260209-update-ai-agents-menu",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
menuJSON = helper.LoadMenus()
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
foundAI := false
newItem := dto.ShowMenu{
ID: "44",
Disabled: false,
Title: "aiTools.agents.agent",
IsShow: true,
Label: "Agents",
Path: "/ai/agents/agent",
Sort: 50,
}
for i := range menus {
if menus[i].Label != "AI-Menu" {
continue
}
foundAI = true
menus[i].IsShow = true
exists := false
for j := range menus[i].Children {
child := &menus[i].Children[j]
if child.Label == newItem.Label {
exists = true
child.IsShow = true
if child.Title == "" {
child.Title = newItem.Title
}
if child.Sort == 0 {
child.Sort = newItem.Sort
}
break
}
}
if !exists {
menus[i].Children = append([]dto.ShowMenu{newItem}, menus[i].Children...)
}
break
}
if !foundAI {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AddEditionSetting = &gormigrate.Migration{
ID: "20260224-add-edition-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "Edition").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "Edition", Value: global.CONF.Base.Edition}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "Edition").Update("value", global.CONF.Base.Edition).Error
}
return nil
},
}
var AddDocSourceSetting = &gormigrate.Migration{
ID: "20260316-add-doc-source-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "DocSource").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "DocSource", Value: "withByRegion"}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "DocSource").Update("value", "withByRegion").Error
}
return nil
},
}
var AddAppStoreInstallAllowPortSetting = &gormigrate.Migration{
ID: "20260407-add-app-store-install-allow-port-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "InstallAllowPort").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "InstallAllowPort", Value: constant.StatusDisable}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "InstallAllowPort").Update("value", constant.StatusDisable).Error
}
return nil
},
}
var AddAppStoreUpgradeDeleteImageSetting = &gormigrate.Migration{
ID: "20260622-add-app-store-upgrade-delete-image-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "UpgradeDeleteImage").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "UpgradeDeleteImage", Value: constant.StatusDisable}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "UpgradeDeleteImage").Update("value", constant.StatusDisable).Error
}
return nil
},
}
var UpdateAiLocalModelMenuTitle = &gormigrate.Migration{
ID: "20260307-update-ai-local-model-menu-title",
Migrate: func(tx *gorm.DB) error {
return tx.Exec(
`UPDATE settings
SET value = REPLACE(value, 'aiTools.model.model', 'aiTools.model.localModel')
WHERE key = 'HideMenu'
AND value LIKE '%aiTools.model.model%'`,
).Error
},
}
var UpdateAiAgentsHideMenuTitle = &gormigrate.Migration{
ID: "20260324-update-ai-agents-hide-menu-title",
Migrate: func(tx *gorm.DB) error {
return tx.Exec(
`UPDATE settings
SET value = REPLACE(value, 'aiTools.agents.agents', 'aiTools.agents.agent')
WHERE key = 'HideMenu'
AND value LIKE '%aiTools.agents.agents%'`,
).Error
},
}
var UpdateAiModelMenuStructure = &gormigrate.Migration{
ID: "20260403-update-ai-model-menu-structure",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", helper.LoadMenus()).Error
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", helper.LoadMenus()).Error
}
for i := range menus {
if menus[i].Label != "AI-Menu" {
continue
}
menus[i].Path = "/ai/model/account"
menus[i].Children = buildAiMenuChildren(menus[i].Children)
break
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
func buildAiMenuChildren(children []dto.ShowMenu) []dto.ShowMenu {
result := []dto.ShowMenu{
normalizeAiMenuChild(children, dto.ShowMenu{
ID: "44",
Label: "Agents",
Disabled: false,
IsShow: true,
Title: "aiTools.agents.agent",
Path: "/ai/agents/agent",
Sort: 50,
}, "Agents"),
normalizeAiMenuChild(children, dto.ShowMenu{
ID: "41",
Label: "AIModel",
Disabled: false,
IsShow: true,
Title: "aiTools.model.model",
Path: "/ai/model/account",
Sort: 100,
}, "AIModel", "OllamaModel"),
}
if global.CONF.Base.IsEnterprise {
result = append(result, normalizeAiMenuChild(children, dto.ShowMenu{
ID: "46",
Label: "AIProxyManagement",
Disabled: false,
IsShow: true,
Title: "aiTools.aiProxy.title",
Path: "/ai/ai-proxy/model-pool",
Sort: 150,
}, "AIProxyManagement"))
result = append(result, normalizeAiMenuChild(children, dto.ShowMenu{
ID: "47",
Label: "SkillsHub",
Disabled: false,
IsShow: true,
Title: "aiTools.skillsHub.title",
Path: "/ai/skills-hub",
Sort: 155,
}, "SkillsHub"))
result = append(result, normalizeAiMenuChild(children, dto.ShowMenu{
ID: "45",
Label: "AIBenchmark",
Disabled: false,
IsShow: true,
Title: "aiTools.benchmark.title",
Path: "/ai/benchmark",
Sort: 160,
}, "AIBenchmark"))
}
result = append(result,
normalizeAiMenuChild(children, dto.ShowMenu{
ID: "42",
Label: "MCPServer",
Disabled: false,
IsShow: true,
Title: "menu.mcp",
Path: "/ai/mcp",
Sort: 200,
}, "MCPServer"),
normalizeAiMenuChild(children, dto.ShowMenu{
ID: "43",
Label: "GPU",
Disabled: false,
IsShow: true,
Title: "aiTools.gpu.gpu",
Path: "/ai/gpu",
Sort: 300,
}, "GPU"),
)
return result
}
func normalizeAiMenuChild(children []dto.ShowMenu, fallback dto.ShowMenu, labels ...string) dto.ShowMenu {
for _, child := range children {
for _, label := range labels {
if child.Label != label {
continue
}
child.ID = fallback.ID
child.Label = fallback.Label
child.Disabled = fallback.Disabled
child.Title = fallback.Title
child.Path = fallback.Path
child.Sort = fallback.Sort
child.Children = nil
return child
}
}
return fallback
}
var AddAIBenchmarkMenu = &gormigrate.Migration{
ID: "20260429-add-ai-benchmark-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsEnterprise {
return nil
}
return helper.UpsertChildMenuByLabel(tx, "AI-Menu", dto.ShowMenu{
ID: "45",
Disabled: false,
Title: "aiTools.benchmark.title",
IsShow: true,
Label: "AIBenchmark",
Path: "/ai/benchmark",
Sort: 160,
}, "AIProxyManagement")
},
}
var AddAIProxyMenu = &gormigrate.Migration{
ID: "20260509-add-ai-proxy-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsEnterprise {
return nil
}
return helper.UpdateHideMenu(tx, func(menus []dto.ShowMenu) []dto.ShowMenu {
for i := range menus {
if menus[i].Label != "AI-Menu" {
continue
}
menus[i].Children = buildAiMenuChildren(menus[i].Children)
break
}
return menus
})
},
}
var AddSkillsHubMenu = &gormigrate.Migration{
ID: "20260517-add-skills-hub-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsEnterprise {
return nil
}
return helper.UpsertChildMenuByLabel(tx, "AI-Menu", dto.ShowMenu{
ID: "47",
Disabled: false,
Title: "aiTools.skillsHub.title",
IsShow: true,
Label: "SkillsHub",
Path: "/ai/skills-hub",
Sort: 155,
}, "AIProxyManagement")
},
}
var UpdateXpackSyncMenu = &gormigrate.Migration{
ID: "20260616-update-xpack-sync-menu",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
menuJSON = helper.LoadMenus()
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
for i := range menus {
if menus[i].Label != "Xpack-Menu" {
continue
}
for j := range menus[i].Children {
if menus[i].Children[j].ID != "115" && menus[i].Children[j].Label != "FileExange" {
continue
}
menus[i].Children[j].Title = "xpack.sync.menu"
menus[i].Children[j].Label = "Sync"
menus[i].Children[j].Path = "/xpack/sync"
break
}
break
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AddUserManagementMenu = &gormigrate.Migration{
ID: "20260415-add-user-management-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsEnterprise {
return nil
}
return helper.UpsertChildMenuByLabel(tx, "Xpack-Menu", dto.ShowMenu{
ID: "121",
Disabled: false,
Title: "xpack.user.userManage",
IsShow: true,
Label: "UserManagement",
Path: "/enterprise/users",
Sort: 400,
}, "NodeDashboard")
},
}
var AddOpsReportMenu = &gormigrate.Migration{
ID: "20260512-add-ops-report-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsEnterprise {
return nil
}
return helper.UpsertChildMenuByLabel(tx, "Xpack-Menu", dto.ShowMenu{
ID: "122",
Disabled: false,
Title: "xpack.opsReport.name",
IsShow: true,
Label: "OpsReport",
Path: "/enterprise/ops-report",
Sort: 500,
}, "UserManagement")
},
}
var AddVirtualMachineMenu = &gormigrate.Migration{
ID: "20260623-add-virtual-machine-menu",
Migrate: func(tx *gorm.DB) error {
if !global.CONF.Base.IsEnterprise {
return nil
}
return helper.UpsertChildMenuByLabel(tx, "Xpack-Menu", dto.ShowMenu{
ID: "123",
Disabled: false,
Title: "xpack.vm.title",
IsShow: true,
Label: "VirtualMachine",
Path: "/enterprise/vm",
Sort: 900,
}, "Upage")
},
}
var UpdateXpackMenuSort = &gormigrate.Migration{
ID: "20260706-update-xpack-menu-sort",
Migrate: func(tx *gorm.DB) error {
return helper.UpdateChildMenuSortByLabel(tx, "Xpack-Menu", helper.XpackMenuSort())
},
}
var AddOperationLogUser = &gormigrate.Migration{
ID: "20260424-add-operation-log-user",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.OperationLog{})
},
}
var AddLoginLogUser = &gormigrate.Migration{
ID: "20260527-add-login-log-user",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.LoginLog{})
},
}
var AddAlertAuditUser = &gormigrate.Migration{
ID: "20260602-add-alert-audit-user",
Migrate: func(tx *gorm.DB) error {
if !global.AlertDB.Migrator().HasTable(&helper.AlertAuditAlert{}) || !global.AlertDB.Migrator().HasTable(&helper.AlertAuditConfig{}) {
return nil
}
if err := global.AlertDB.AutoMigrate(&helper.AlertAuditAlert{}, &helper.AlertAuditConfig{}); err != nil {
return err
}
username, err := repo.NewISettingRepo().GetValueByKey("UserName")
if err != nil || strings.TrimSpace(username) == "" {
username = global.CONF.Base.Username
}
username = strings.TrimSpace(username)
if username == "" {
return nil
}
updates := []struct {
table string
field string
}{
{table: "alerts", field: "create_user"},
{table: "alerts", field: "update_user"},
{table: "alert_configs", field: "create_user"},
{table: "alert_configs", field: "update_user"},
}
for _, item := range updates {
if err := global.AlertDB.Exec(fmt.Sprintf(
"UPDATE %s SET %s = ? WHERE %s IS NULL OR %s = ''",
item.table,
item.field,
item.field,
item.field,
), username).Error; err != nil {
return err
}
}
return nil
},
}
var AddMenuAccordionSetting = &gormigrate.Migration{
ID: "20260701-add-menu-accordion-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "MenuAccordion").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "MenuAccordion", Value: constant.StatusDisable}).Error
}
return err
}
if setting.Value == "" {
return tx.Model(&model.Setting{}).Where("key = ?", "MenuAccordion").Update("value", constant.StatusDisable).Error
}
return nil
},
}
var AddAPITrustedProxiesSetting = &gormigrate.Migration{
ID: "20260729-add-api-trusted-proxies-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "ApiTrustedProxies").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "ApiTrustedProxies", Value: ""}).Error
}
return err
}
return nil
},
}
var AddAllowIPTrustedProxiesSetting = &gormigrate.Migration{
ID: "20260820-add-allow-ip-trusted-proxies-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "AllowIPTrustedProxies").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "AllowIPTrustedProxies", Value: ""}).Error
}
return err
}
return nil
},
}
var AddWebsiteTemplateMenu = &gormigrate.Migration{
ID: "20260728-add-website-template-menu",
Migrate: func(tx *gorm.DB) error {
return helper.UpsertChildMenuByLabel(tx, "Website-Menu", dto.ShowMenu{
ID: "34",
Disabled: false,
Title: "menu.template",
IsShow: true,
Label: "WebsiteTemplate",
Path: "/websites/templates",
Sort: 250,
}, "SSL")
},
}
var RepairXpackAppMenus = &gormigrate.Migration{
ID: "20260818-repair-xapp-upage-hide-menu",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "HideMenu").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "HideMenu", Value: helper.LoadMenus()}).Error
}
return err
}
updateValue := func(value string) error {
return tx.Model(&setting).Update("value", value).Error
}
if strings.TrimSpace(setting.Value) == "" {
return updateValue(helper.LoadMenus())
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(setting.Value), &menus); err != nil || len(menus) == 0 {
return updateValue(helper.LoadMenus())
}
updatedMenus, changed := menutree.EnsureXpackAppMenus(menus, nil)
if !changed {
return nil
}
updatedJSON, err := json.Marshal(updatedMenus)
if err != nil {
return err
}
return updateValue(string(updatedJSON))
},
}
var UpdateFirewallMenuPath = &gormigrate.Migration{
ID: "20260819-update-firewall-menu-path",
Migrate: func(tx *gorm.DB) error {
return helper.UpdateHideMenu(tx, func(menus []dto.ShowMenu) []dto.ShowMenu {
for i := range menus {
for j := range menus[i].Children {
menu := &menus[i].Children[j]
if (menu.ID == "74" || menu.Label == "FirewallPort") && menu.Path == "/hosts/firewall/port" {
menu.Path = "/hosts/firewall/rules"
}
}
}
return menus
})
},
}