Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion agent/app/service/app_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,12 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
continue
}
if common.CompareVersion(detail.Version, install.Version) {
canUpgrade := common.CompareVersion(detail.Version, install.Version)
if app.Key == vllmAppKeyForUpgrade {
currentImage := loadVllmImageFromEnv(install.Env)
canUpgrade = isVllmUpgradeCandidate(install.Version, detail.Version, currentImage)
}
if canUpgrade {
var newCompose string
if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() {
filename := filepath.Base(detail.DownloadUrl)
Expand Down
30 changes: 27 additions & 3 deletions agent/app/service/app_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,9 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if err != nil {
return err
}
if install.App.Key == vllmAppKeyForUpgrade && !isVllmUpgradeVersionAllowed(install.Version, detail.Version, loadVllmImageFromEnv(install.Env)) {
return errors.New("vLLM can only upgrade within the same image type")
}
if install.Version == detail.Version {
return errors.New("two version is same")
}
Expand Down Expand Up @@ -748,8 +751,25 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if err != nil {
return err
}
if install.App.Key == vllmAppKeyForUpgrade {
envs := make(map[string]interface{})
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
return err
}
image := buildVllmUpgradeImage(loadVllmImageFromEnv(install.Env), oldVersion, detail.Version)
envs[vllmImageEnvKey] = image
paramByte, err := json.Marshal(envs)
if err != nil {
return err
}
install.Env = string(paramByte)
content = setVllmImageInEnvContent(content, image)
}
if req.PullImage {
composeContent := []byte(detail.DockerCompose)
if install.App.Key == vllmAppKeyForUpgrade {
composeContent = []byte(install.DockerCompose)
}
if req.DockerCompose != "" {
composeContent = []byte(req.DockerCompose)
}
Expand Down Expand Up @@ -811,9 +831,13 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
return err
}
if req.DockerCompose == "" {
newCompose, err = getUpgradeCompose(install, detail)
if err != nil {
return err
if install.App.Key == vllmAppKeyForUpgrade {
newCompose = install.DockerCompose
} else {
newCompose, err = getUpgradeCompose(install, detail)
if err != nil {
return err
}
}
} else {
newCompose = req.DockerCompose
Expand Down
126 changes: 126 additions & 0 deletions agent/app/service/vllm_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package service

import (
"encoding/json"
"strings"

"github.com/1Panel-dev/1Panel/agent/utils/common"
)

const (
vllmAppKeyForUpgrade = "vllm"
vllmImageEnvKey = "IMAGE"
vllmImageTypeNvidia = "nvidia"
vllmImageTypeIntel = "intel"
)

func resolveVllmVersionFamily(version, image string) string {
normalizedVersion := strings.ToLower(strings.TrimSpace(version))
if strings.HasPrefix(normalizedVersion, vllmImageTypeIntel+"-") {
return vllmImageTypeIntel
}
if strings.HasPrefix(normalizedVersion, vllmImageTypeNvidia+"-") {
return vllmImageTypeNvidia
}
normalizedImage := strings.ToLower(strings.TrimSpace(image))
if strings.Contains(normalizedImage, "intel/") || strings.Contains(normalizedImage, "llm-scaler-vllm") {
return vllmImageTypeIntel
}
return vllmImageTypeNvidia
}

func trimVllmVersionFamily(version string) string {
trimmed := strings.TrimSpace(version)
normalized := strings.ToLower(trimmed)
for _, family := range []string{vllmImageTypeNvidia, vllmImageTypeIntel} {
prefix := family + "-"
if strings.HasPrefix(normalized, prefix) {
return strings.TrimSpace(trimmed[len(prefix):])
}
}
return trimmed
}

func buildDefaultVllmImageByVersion(version string) string {
tag := trimVllmVersionFamily(version)
if resolveVllmVersionFamily(version, "") == vllmImageTypeIntel {
return "intel/llm-scaler-vllm:" + tag
}
if tag != "" && !strings.HasPrefix(strings.ToLower(tag), "v") {
tag = "v" + tag
}
return "vllm/vllm-openai:" + tag
}

func isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage string) bool {
currentFamily := resolveVllmVersionFamily(currentVersion, currentImage)
targetFamily := resolveVllmVersionFamily(targetVersion, "")
return currentFamily == targetFamily
}

func hasVllmVersionFamilyPrefix(version string) bool {
normalized := strings.ToLower(strings.TrimSpace(version))
return strings.HasPrefix(normalized, vllmImageTypeNvidia+"-") || strings.HasPrefix(normalized, vllmImageTypeIntel+"-")
}

func isVllmUpgradeCandidate(currentVersion, targetVersion, currentImage string) bool {
if strings.TrimSpace(currentVersion) == strings.TrimSpace(targetVersion) {
return false
}
if !isVllmUpgradeVersionAllowed(currentVersion, targetVersion, currentImage) {
return false
}
if common.CompareVersion(targetVersion, currentVersion) {
return true
}
return !hasVllmVersionFamilyPrefix(currentVersion) &&
resolveVllmVersionFamily(targetVersion, "") == vllmImageTypeNvidia &&
trimVllmVersionFamily(currentVersion) == trimVllmVersionFamily(targetVersion)
}

func buildVllmUpgradeImage(currentImage, currentVersion, targetVersion string) string {
trimmedImage := strings.TrimSpace(currentImage)
if trimmedImage == "" || trimmedImage == buildDefaultVllmImageByVersion(currentVersion) {
return buildDefaultVllmImageByVersion(targetVersion)
}
return trimmedImage
}

func loadVllmImageFromEnv(raw string) string {
envs := make(map[string]interface{})
if strings.TrimSpace(raw) == "" {
return ""
}
if err := json.Unmarshal([]byte(raw), &envs); err != nil {
return ""
}
if image, ok := envs[vllmImageEnvKey].(string); ok {
return strings.TrimSpace(image)
}
return ""
}

func setVllmImageInEnvContent(content []byte, image string) []byte {
normalizedImage := strings.TrimSpace(image)
if normalizedImage == "" {
return content
}
lines := strings.Split(string(content), "\n")
replaced := false
for index, line := range lines {
if strings.HasPrefix(line, vllmImageEnvKey+"=") {
lines[index] = vllmImageEnvKey + "=" + normalizedImage
replaced = true
break
}
}
if !replaced {
if len(lines) > 0 && lines[len(lines)-1] == "" {
lines[len(lines)-1] = vllmImageEnvKey + "=" + normalizedImage
lines = append(lines, "")
} else {
lines = append(lines, vllmImageEnvKey+"="+normalizedImage)
}
}
return []byte(strings.Join(lines, "\n"))
}
2 changes: 1 addition & 1 deletion agent/cmd/server/conf/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ base:
is_demo: false
is_offline: false
is_fxplay: false
is_enterprise: false
is_enterprise: true

log:
level: debug
Expand Down
20 changes: 20 additions & 0 deletions agent/init/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,38 @@ import (
)

func Init() {
global.LOG.Info("agent hook: init global data start")
initGlobalData()
global.LOG.Info("agent hook: init global data done")
global.LOG.Info("agent hook: handle cronjob status start")
handleCronjobStatus()
global.LOG.Info("agent hook: handle cronjob status done")
global.LOG.Info("agent hook: handle clam status start")
handleClamStatus()
global.LOG.Info("agent hook: handle clam status done")
global.LOG.Info("agent hook: handle record status start")
handleRecordStatus()
global.LOG.Info("agent hook: handle record status done")
global.LOG.Info("agent hook: handle snapshot status start")
handleSnapStatus()
global.LOG.Info("agent hook: handle snapshot status done")
global.LOG.Info("agent hook: handle ollama model status start")
handleOllamaModelStatus()
global.LOG.Info("agent hook: handle ollama model status done")

global.LOG.Info("agent hook: load local dir start")
loadLocalDir()
global.LOG.Info("agent hook: load local dir done")

global.LOG.Info("agent hook: init docker config start")
initDockerConf()
global.LOG.Info("agent hook: init docker config done")
global.LOG.Info("agent hook: init alert task start")
initAlertTask()
global.LOG.Info("agent hook: init alert task done")
global.LOG.Info("agent hook: init monitor db start")
initMonitorDB()
global.LOG.Info("agent hook: init monitor db done")
}

func initGlobalData() {
Expand Down
18 changes: 18 additions & 0 deletions agent/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,32 @@ func Start() {
viper.Init()
dir.Init()
log.Init()
global.LOG.Info("agent startup: logger initialized")
db.Init()
global.LOG.Info("agent startup: database initialized")
migration.Init()
global.LOG.Info("agent startup: migration initialized")
i18n.Init()
global.LOG.Info("agent startup: i18n initialized")
cache.Init()
global.LOG.Info("agent startup: cache initialized")
app.Init()
global.LOG.Info("agent startup: app initialized")
lang.Init()
global.LOG.Info("agent startup: language initialized")
validator.Init()
global.LOG.Info("agent startup: validator initialized")
cron.Run()
global.LOG.Info("agent startup: cron initialized")
hook.Init()
global.LOG.Info("agent startup: hook initialized")
go firewall.Init()
global.LOG.Info("agent startup: firewall init scheduled")
InitOthers()
global.LOG.Info("agent startup: edition initialized")

rootRouter := router.Routers()
global.LOG.Info("agent startup: router initialized")

server := &http.Server{
Handler: rootRouter,
Expand All @@ -120,6 +133,7 @@ func Start() {
}

if global.IsMaster {
global.LOG.Infof("agent startup: master mode, preparing unix socket %s", masterSocketPath)
if err := prepareMasterSocketDir(masterSocketDir); err != nil {
panic(err)
}
Expand All @@ -132,11 +146,14 @@ func Start() {
_ = listener.Close()
panic(err)
}
global.LOG.Infof("agent startup: listening on unix socket %s", masterSocketPath)
business.Init()
global.LOG.Info("agent startup: business initialized")
_ = server.Serve(listener)
return
} else {
server.Addr = fmt.Sprintf("0.0.0.0:%s", global.CONF.Base.Port)
global.LOG.Infof("agent startup: node mode, preparing https listener %s", server.Addr)
settingRepo := repo.NewISettingRepo()
certItem, err := settingRepo.Get(settingRepo.WithByKey("ServerCrt"))
if err != nil {
Expand Down Expand Up @@ -166,6 +183,7 @@ func Start() {
server.TLSConfig.ClientCAs = caCertPool
}
business.Init()
global.LOG.Info("agent startup: business initialized")
global.LOG.Infof("listen at https://0.0.0.0:%s", global.CONF.Base.Port)
if err := server.ListenAndServeTLS("", ""); err != nil {
panic(err)
Expand Down
2 changes: 1 addition & 1 deletion core/cmd/server/conf/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ base:
is_demo: false
is_offline: false
is_fxplay: false
is_enterprise: false
is_enterprise: true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore default non-enterprise mode

This embedded app.yaml is read by viper.Init when there is no external /opt/1panel/conf/app.yaml, and global.CONF.Base.IsEnterprise controls migrations, login settings, CLI behavior, and resource URLs. With this default set to true, ordinary/community installs can boot as Enterprise, skip normal admin credential initialization, and use Enterprise-only paths; the same flip in agent/cmd/server/conf/app.yaml also changes agent resource selection. Unless every build from this tree is Enterprise-only, keep the default false and enable Enterprise through the intended packaging/config path.

Useful? React with 👍 / 👎.

port: 9999
username: admin
password: admin123
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,10 @@ const message = {
requestBodySize: 'Request Body Size',
requestBodyTruncated: 'Truncated',
requestBodyHash: 'Request Body Hash',
responseBody: 'Response Body',
responseBodySize: 'Response Body Size',
responseBodyTruncated: 'Response Body Truncated',
responseBodyHash: 'Response Body Hash',
statusTitle: 'AI Gateway Status',
serviceEnabled: 'Service Auto-start',
proxyEnabled: 'Gateway Enabled',
Expand Down Expand Up @@ -996,6 +1000,8 @@ const message = {
inputTokens: 'Input Tokens',
outputTokens: 'Output Tokens',
totalToken: 'Total Tokens',
cachedToken: 'Cached Tokens',
cacheHitRate: 'Cache Hit Rate',
activeUsers: 'Active Users',
activeModels: 'Active Models',
failedRequests: 'Failed Requests',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,10 @@ const message = {
requestBodySize: 'Tamaño del cuerpo de solicitud',
requestBodyTruncated: 'Truncado',
requestBodyHash: 'Hash del cuerpo de solicitud',
responseBody: 'Cuerpo de respuesta',
responseBodySize: 'Tamaño del cuerpo de respuesta',
responseBodyTruncated: 'Cuerpo de respuesta truncado',
responseBodyHash: 'Hash del cuerpo de respuesta',
statusTitle: 'Estado del gateway de IA',
serviceEnabled: 'Inicio automático del servicio',
proxyEnabled: 'Gateway habilitado',
Expand Down Expand Up @@ -1010,6 +1014,8 @@ const message = {
inputTokens: 'Tokens de entrada',
outputTokens: 'Tokens de salida',
totalToken: 'Tokens totales',
cachedToken: 'Tokens en caché',
cacheHitRate: 'Tasa de acierto de caché',
activeUsers: 'Usuarios activos',
activeModels: 'Modelos activos',
failedRequests: 'Solicitudes fallidas',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,10 @@ const message = {
requestBodySize: 'リクエスト本文サイズ',
requestBodyTruncated: '切り詰め',
requestBodyHash: 'リクエスト本文 Hash',
responseBody: 'レスポンス本文',
responseBodySize: 'レスポンス本文サイズ',
responseBodyTruncated: 'レスポンス本文の切り詰め',
responseBodyHash: 'レスポンス本文 Hash',
statusTitle: 'AI ゲートウェイ状態',
serviceEnabled: 'サービス自動起動',
proxyEnabled: 'ゲートウェイ有効',
Expand Down Expand Up @@ -1000,6 +1004,8 @@ const message = {
inputTokens: '入力 Token',
outputTokens: '出力 Token',
totalToken: '合計 Token',
cachedToken: 'キャッシュ Token',
cacheHitRate: 'キャッシュヒット率',
activeUsers: 'アクティブユーザー',
activeModels: 'アクティブモデル',
failedRequests: '失敗リクエスト',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,10 @@ const message = {
requestBodySize: '요청 본문 크기',
requestBodyTruncated: '잘림 여부',
requestBodyHash: '요청 본문 Hash',
responseBody: '응답 본문',
responseBodySize: '응답 본문 크기',
responseBodyTruncated: '응답 본문 잘림 여부',
responseBodyHash: '응답 본문 Hash',
statusTitle: 'AI 게이트웨이 상태',
serviceEnabled: '서비스 자동 시작',
proxyEnabled: '게이트웨이 활성화',
Expand Down Expand Up @@ -984,6 +988,8 @@ const message = {
inputTokens: '입력 Token',
outputTokens: '출력 Token',
totalToken: '총 Token',
cachedToken: '캐시 Token',
cacheHitRate: '캐시 적중률',
activeUsers: '활성 사용자',
activeModels: '활성 모델',
failedRequests: '실패한 요청',
Expand Down
Loading
Loading