2024-07-23 14:48:37 +08:00
package service
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
2024-08-13 15:33:34 +08:00
"net/http"
"os"
2024-08-28 17:48:16 +08:00
"path"
2024-08-13 15:33:34 +08:00
"path/filepath"
"reflect"
"strconv"
"strings"
2024-07-23 14:48:37 +08:00
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
2024-07-29 18:09:57 +08:00
"github.com/1Panel-dev/1Panel/agent/app/task"
2024-07-23 14:48:37 +08:00
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files"
http2 "github.com/1Panel-dev/1Panel/agent/utils/http"
httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"gopkg.in/yaml.v3"
)
type AppService struct {
}
type IAppService interface {
PageApp ( req request . AppSearch ) ( interface { } , error )
GetAppTags ( ) ( [ ] response . TagDTO , error )
GetApp ( key string ) ( * response . AppDTO , error )
GetAppDetail ( appId uint , version , appType string ) ( response . AppDetailDTO , error )
2024-07-29 18:09:57 +08:00
Install ( req request . AppInstallCreate ) ( * model . AppInstall , error )
2024-08-22 18:48:55 +08:00
SyncAppListFromRemote ( taskID string ) error
2024-07-23 14:48:37 +08:00
GetAppUpdate ( ) ( * response . AppUpdateRes , error )
GetAppDetailByID ( id uint ) ( * response . AppDetailDTO , error )
2024-08-23 17:22:05 +08:00
SyncAppListFromLocal ( taskID string )
2024-07-23 14:48:37 +08:00
GetIgnoredApp ( ) ( [ ] response . IgnoredApp , error )
2024-08-26 18:34:34 +08:00
GetAppstoreConfig ( ) ( * response . AppstoreConfig , error )
UpdateAppstoreConfig ( req request . AppstoreUpdate ) error
2024-07-23 14:48:37 +08:00
}
func NewIAppService ( ) IAppService {
return & AppService { }
}
func ( a AppService ) PageApp ( req request . AppSearch ) ( interface { } , error ) {
var opts [ ] repo . DBOption
opts = append ( opts , appRepo . OrderByRecommend ( ) )
if req . Name != "" {
2024-08-21 18:04:51 +08:00
opts = append ( opts , appRepo . WithByLikeName ( req . Name ) )
2024-07-23 14:48:37 +08:00
}
if req . Type != "" {
opts = append ( opts , appRepo . WithType ( req . Type ) )
}
if req . Recommend {
opts = append ( opts , appRepo . GetRecommend ( ) )
}
if req . Resource != "" && req . Resource != "all" {
opts = append ( opts , appRepo . WithResource ( req . Resource ) )
}
2024-12-23 22:05:44 +08:00
2024-08-22 15:08:55 +08:00
if req . ShowCurrentArch {
info , err := NewIDashboardService ( ) . LoadOsInfo ( )
if err != nil {
return nil , err
}
opts = append ( opts , appRepo . WithArch ( info . KernelArch ) )
}
2024-07-23 14:48:37 +08:00
if len ( req . Tags ) != 0 {
tags , err := tagRepo . GetByKeys ( req . Tags )
if err != nil {
return nil , err
}
var tagIds [ ] uint
for _ , t := range tags {
tagIds = append ( tagIds , t . ID )
}
appTags , err := appTagRepo . GetByTagIds ( tagIds )
if err != nil {
return nil , err
}
var appIds [ ] uint
for _ , t := range appTags {
appIds = append ( appIds , t . AppId )
}
2025-01-02 17:20:03 +08:00
opts = append ( opts , repo . WithByIDs ( appIds ) )
2024-07-23 14:48:37 +08:00
}
var res response . AppRes
2024-12-23 22:05:44 +08:00
2024-07-23 14:48:37 +08:00
total , apps , err := appRepo . Page ( req . Page , req . PageSize , opts ... )
if err != nil {
return nil , err
}
var appDTOs [ ] * response . AppDto
2024-12-23 22:05:44 +08:00
info := & dto . SettingInfo { }
if req . Type == "php" {
info , _ = NewISettingService ( ) . GetSettingInfo ( )
}
2024-07-23 14:48:37 +08:00
for _ , ap := range apps {
2024-12-23 22:05:44 +08:00
if req . Type == "php" {
if ap . RequiredPanelVersion == 0 || ! common . CompareAppVersion ( fmt . Sprintf ( "%f" , ap . RequiredPanelVersion ) , info . SystemVersion ) {
continue
}
}
2024-07-23 14:48:37 +08:00
appDTO := & response . AppDto {
ID : ap . ID ,
Name : ap . Name ,
Key : ap . Key ,
Type : ap . Type ,
Icon : ap . Icon ,
ShortDescZh : ap . ShortDescZh ,
ShortDescEn : ap . ShortDescEn ,
Resource : ap . Resource ,
Limit : ap . Limit ,
2024-08-22 15:08:55 +08:00
Website : ap . Website ,
Github : ap . Github ,
2024-08-26 13:40:27 +08:00
GpuSupport : ap . GpuSupport ,
2024-10-14 10:37:34 +08:00
Recommend : ap . Recommend ,
2024-07-23 14:48:37 +08:00
}
appDTOs = append ( appDTOs , appDTO )
appTags , err := appTagRepo . GetByAppId ( ap . ID )
if err != nil {
continue
}
var tagIds [ ] uint
for _ , at := range appTags {
tagIds = append ( tagIds , at . TagId )
}
tags , err := tagRepo . GetByIds ( tagIds )
if err != nil {
continue
}
appDTO . Tags = tags
installs , _ := appInstallRepo . ListBy ( appInstallRepo . WithAppId ( ap . ID ) )
appDTO . Installed = len ( installs ) > 0
}
res . Items = appDTOs
res . Total = total
return res , nil
}
func ( a AppService ) GetAppTags ( ) ( [ ] response . TagDTO , error ) {
tags , err := tagRepo . All ( )
if err != nil {
return nil , err
}
var res [ ] response . TagDTO
for _ , tag := range tags {
res = append ( res , response . TagDTO {
Tag : tag ,
} )
}
return res , nil
}
func ( a AppService ) GetApp ( key string ) ( * response . AppDTO , error ) {
var appDTO response . AppDTO
2024-11-30 19:42:10 +08:00
if key == "postgres" {
key = "postgresql"
}
2024-07-23 14:48:37 +08:00
app , err := appRepo . GetFirst ( appRepo . WithKey ( key ) )
if err != nil {
return nil , err
}
appDTO . App = app
details , err := appDetailRepo . GetBy ( appDetailRepo . WithAppId ( app . ID ) )
if err != nil {
return nil , err
}
var versionsRaw [ ] string
for _ , detail := range details {
versionsRaw = append ( versionsRaw , detail . Version )
}
appDTO . Versions = common . GetSortedVersions ( versionsRaw )
return & appDTO , nil
}
func ( a AppService ) GetAppDetail ( appID uint , version , appType string ) ( response . AppDetailDTO , error ) {
var (
appDetailDTO response . AppDetailDTO
opts [ ] repo . DBOption
)
opts = append ( opts , appDetailRepo . WithAppId ( appID ) , appDetailRepo . WithVersion ( version ) )
detail , err := appDetailRepo . GetFirst ( opts ... )
if err != nil {
return appDetailDTO , err
}
appDetailDTO . AppDetail = detail
appDetailDTO . Enable = true
if appType == "runtime" {
2025-01-02 17:20:03 +08:00
app , err := appRepo . GetFirst ( repo . WithByID ( appID ) )
2024-07-23 14:48:37 +08:00
if err != nil {
return appDetailDTO , err
}
fileOp := files . NewFileOp ( )
versionPath := filepath . Join ( app . GetAppResourcePath ( ) , detail . Version )
if ! fileOp . Stat ( versionPath ) || detail . Update {
2024-08-14 18:29:59 +08:00
if err = downloadApp ( app , detail , nil , nil ) ; err != nil && ! fileOp . Stat ( versionPath ) {
2024-07-23 14:48:37 +08:00
return appDetailDTO , err
}
}
switch app . Type {
case constant . RuntimePHP :
2024-09-02 21:56:24 +08:00
paramsPath := filepath . Join ( versionPath , "data.yml" )
2024-07-23 14:48:37 +08:00
if ! fileOp . Stat ( paramsPath ) {
2024-09-02 21:56:24 +08:00
return appDetailDTO , buserr . WithDetail ( constant . ErrFileNotExist , paramsPath , nil )
2024-07-23 14:48:37 +08:00
}
param , err := fileOp . GetContent ( paramsPath )
if err != nil {
return appDetailDTO , err
}
paramMap := make ( map [ string ] interface { } )
2024-09-02 21:56:24 +08:00
if err = yaml . Unmarshal ( param , & paramMap ) ; err != nil {
2024-07-23 14:48:37 +08:00
return appDetailDTO , err
}
2024-09-02 21:56:24 +08:00
appDetailDTO . Params = paramMap [ "additionalProperties" ]
composePath := filepath . Join ( versionPath , "docker-compose.yml" )
2024-07-23 14:48:37 +08:00
if ! fileOp . Stat ( composePath ) {
2024-09-02 21:56:24 +08:00
return appDetailDTO , buserr . WithDetail ( constant . ErrFileNotExist , composePath , nil )
2024-07-23 14:48:37 +08:00
}
compose , err := fileOp . GetContent ( composePath )
if err != nil {
return appDetailDTO , err
}
composeMap := make ( map [ string ] interface { } )
if err := yaml . Unmarshal ( compose , & composeMap ) ; err != nil {
return appDetailDTO , err
}
if service , ok := composeMap [ "services" ] ; ok {
servicesMap := service . ( map [ string ] interface { } )
for k := range servicesMap {
appDetailDTO . Image = k
}
}
}
} else {
paramMap := make ( map [ string ] interface { } )
if err := json . Unmarshal ( [ ] byte ( detail . Params ) , & paramMap ) ; err != nil {
return appDetailDTO , err
}
appDetailDTO . Params = paramMap
}
if appDetailDTO . DockerCompose == "" {
filename := filepath . Base ( appDetailDTO . DownloadUrl )
dockerComposeUrl := fmt . Sprintf ( "%s%s" , strings . TrimSuffix ( appDetailDTO . DownloadUrl , filename ) , "docker-compose.yml" )
statusCode , composeRes , err := httpUtil . HandleGet ( dockerComposeUrl , http . MethodGet , constant . TimeOut20s )
if err != nil {
return appDetailDTO , buserr . WithDetail ( "ErrGetCompose" , err . Error ( ) , err )
}
if statusCode > 200 {
return appDetailDTO , buserr . WithDetail ( "ErrGetCompose" , string ( composeRes ) , err )
}
detail . DockerCompose = string ( composeRes )
_ = appDetailRepo . Update ( context . Background ( ) , detail )
appDetailDTO . DockerCompose = string ( composeRes )
}
appDetailDTO . HostMode = isHostModel ( appDetailDTO . DockerCompose )
2025-01-02 17:20:03 +08:00
app , err := appRepo . GetFirst ( repo . WithByID ( detail . AppId ) )
2024-07-23 14:48:37 +08:00
if err != nil {
return appDetailDTO , err
}
if err := checkLimit ( app ) ; err != nil {
appDetailDTO . Enable = false
}
2024-08-22 15:08:55 +08:00
appDetailDTO . Architectures = app . Architectures
2024-08-26 13:40:27 +08:00
appDetailDTO . MemoryRequired = app . MemoryRequired
appDetailDTO . GpuSupport = app . GpuSupport
2024-07-23 14:48:37 +08:00
return appDetailDTO , nil
}
func ( a AppService ) GetAppDetailByID ( id uint ) ( * response . AppDetailDTO , error ) {
res := & response . AppDetailDTO { }
2025-01-02 17:20:03 +08:00
appDetail , err := appDetailRepo . GetFirst ( repo . WithByID ( id ) )
2024-07-23 14:48:37 +08:00
if err != nil {
return nil , err
}
res . AppDetail = appDetail
paramMap := make ( map [ string ] interface { } )
if err := json . Unmarshal ( [ ] byte ( appDetail . Params ) , & paramMap ) ; err != nil {
return nil , err
}
res . Params = paramMap
res . HostMode = isHostModel ( appDetail . DockerCompose )
return res , nil
}
func ( a AppService ) GetIgnoredApp ( ) ( [ ] response . IgnoredApp , error ) {
var res [ ] response . IgnoredApp
details , _ := appDetailRepo . GetBy ( appDetailRepo . WithIgnored ( ) )
if len ( details ) == 0 {
return res , nil
}
for _ , detail := range details {
2025-01-02 17:20:03 +08:00
app , err := appRepo . GetFirst ( repo . WithByID ( detail . AppId ) )
2024-07-23 14:48:37 +08:00
if err != nil {
return nil , err
}
res = append ( res , response . IgnoredApp {
Name : app . Name ,
Version : detail . Version ,
DetailID : detail . ID ,
Icon : app . Icon ,
} )
}
return res , nil
}
2024-07-29 18:09:57 +08:00
func ( a AppService ) Install ( req request . AppInstallCreate ) ( appInstall * model . AppInstall , err error ) {
2024-07-23 14:48:37 +08:00
if err = docker . CreateDefaultDockerNetwork ( ) ; err != nil {
err = buserr . WithDetail ( constant . Err1PanelNetworkFailed , err . Error ( ) , nil )
return
}
2025-01-02 17:20:03 +08:00
if list , _ := appInstallRepo . ListBy ( repo . WithByName ( req . Name ) ) ; len ( list ) > 0 {
2024-07-23 14:48:37 +08:00
err = buserr . New ( constant . ErrAppNameExist )
return
}
var (
httpPort int
httpsPort int
appDetail model . AppDetail
app model . App
)
2025-01-02 17:20:03 +08:00
appDetail , err = appDetailRepo . GetFirst ( repo . WithByID ( req . AppDetailId ) )
2024-07-23 14:48:37 +08:00
if err != nil {
return
}
2025-01-02 17:20:03 +08:00
app , err = appRepo . GetFirst ( repo . WithByID ( appDetail . AppId ) )
2024-07-23 14:48:37 +08:00
if err != nil {
return
}
if DatabaseKeys [ app . Key ] > 0 {
2025-01-02 17:20:03 +08:00
if existDatabases , _ := databaseRepo . GetList ( repo . WithByName ( req . Name ) ) ; len ( existDatabases ) > 0 {
2024-07-23 14:48:37 +08:00
err = buserr . New ( constant . ErrRemoteExist )
return
}
}
2024-08-28 17:48:16 +08:00
if app . Key == "openresty" && app . Resource == "remote" && common . CompareVersion ( appDetail . Version , "1.21.4.3-3-3" ) {
if dir , ok := req . Params [ "WEBSITE_DIR" ] ; ok {
siteDir := dir . ( string )
if siteDir == "" || ! strings . HasPrefix ( siteDir , "/" ) {
siteDir = path . Join ( constant . DataDir , dir . ( string ) )
}
req . Params [ "WEBSITE_DIR" ] = siteDir
2025-01-06 17:09:48 +08:00
oldWebStePath , _ := settingRepo . GetValueByKey ( "WEBSITE_DIR" )
if oldWebStePath != "" && oldWebStePath != siteDir {
_ = files . NewFileOp ( ) . Rename ( oldWebStePath , siteDir )
}
_ = settingRepo . UpdateOrCreate ( "WEBSITE_DIR" , siteDir )
2024-08-28 17:48:16 +08:00
}
}
2024-07-23 14:48:37 +08:00
for key := range req . Params {
if ! strings . Contains ( key , "PANEL_APP_PORT" ) {
continue
}
var port int
if port , err = checkPort ( key , req . Params ) ; err == nil {
if key == "PANEL_APP_PORT_HTTP" {
httpPort = port
}
if key == "PANEL_APP_PORT_HTTPS" {
httpsPort = port
}
} else {
return
}
}
if err = checkRequiredAndLimit ( app ) ; err != nil {
return
}
appInstall = & model . AppInstall {
Name : req . Name ,
AppId : appDetail . AppId ,
AppDetailId : appDetail . ID ,
Version : appDetail . Version ,
Status : constant . Installing ,
HttpPort : httpPort ,
HttpsPort : httpsPort ,
App : app ,
}
composeMap := make ( map [ string ] interface { } )
if req . EditCompose {
if err = yaml . Unmarshal ( [ ] byte ( req . DockerCompose ) , & composeMap ) ; err != nil {
return
}
} else {
if err = yaml . Unmarshal ( [ ] byte ( appDetail . DockerCompose ) , & composeMap ) ; err != nil {
return
}
}
value , ok := composeMap [ "services" ]
if ! ok || value == nil {
err = buserr . New ( constant . ErrFileParse )
return
}
servicesMap := value . ( map [ string ] interface { } )
containerName := constant . ContainerPrefix + app . Key + "-" + common . RandStr ( 4 )
if req . Advanced && req . ContainerName != "" {
containerName = req . ContainerName
appInstalls , _ := appInstallRepo . ListBy ( appInstallRepo . WithContainerName ( containerName ) )
if len ( appInstalls ) > 0 {
err = buserr . New ( constant . ErrContainerName )
return
}
containerExist := false
containerExist , err = checkContainerNameIsExist ( req . ContainerName , appInstall . GetPath ( ) )
if err != nil {
return
}
if containerExist {
err = buserr . New ( constant . ErrContainerName )
return
}
}
req . Params [ constant . ContainerName ] = containerName
appInstall . ContainerName = containerName
index := 0
serviceName := ""
for k := range servicesMap {
serviceName = k
if index > 0 {
continue
}
index ++
}
if app . Limit == 0 && appInstall . Name != serviceName && len ( servicesMap ) == 1 {
servicesMap [ appInstall . Name ] = servicesMap [ serviceName ]
delete ( servicesMap , serviceName )
serviceName = appInstall . Name
}
appInstall . ServiceName = serviceName
if err = addDockerComposeCommonParam ( composeMap , appInstall . ServiceName , req . AppContainerConfig , req . Params ) ; err != nil {
return
}
var (
composeByte [ ] byte
paramByte [ ] byte
)
composeByte , err = yaml . Marshal ( composeMap )
if err != nil {
return
}
appInstall . DockerCompose = string ( composeByte )
if hostName , ok := req . Params [ "PANEL_DB_HOST" ] ; ok {
2025-01-02 17:20:03 +08:00
database , _ := databaseRepo . Get ( repo . WithByName ( hostName . ( string ) ) )
2024-07-23 14:48:37 +08:00
if ! reflect . DeepEqual ( database , model . Database { } ) {
req . Params [ "PANEL_DB_HOST" ] = database . Address
req . Params [ "PANEL_DB_PORT" ] = database . Port
req . Params [ "PANEL_DB_HOST_NAME" ] = hostName
}
}
paramByte , err = json . Marshal ( req . Params )
if err != nil {
return
}
appInstall . Env = string ( paramByte )
2024-07-29 18:09:57 +08:00
if err = appInstallRepo . Create ( context . Background ( ) , appInstall ) ; err != nil {
2024-07-23 14:48:37 +08:00
return
}
2024-07-29 18:09:57 +08:00
2024-08-02 16:00:15 +08:00
installTask , err := task . NewTaskWithOps ( appInstall . Name , task . TaskInstall , task . TaskScopeApp , req . TaskID , appInstall . ID )
2024-07-29 18:09:57 +08:00
if err != nil {
2024-07-23 14:48:37 +08:00
return
}
2024-07-29 18:09:57 +08:00
if err = createLink ( context . Background ( ) , installTask , app , appInstall , req . Params ) ; err != nil {
return
}
installApp := func ( t * task . Task ) error {
if err = copyData ( t , app , appDetail , appInstall , req ) ; err != nil {
return err
2024-07-23 14:48:37 +08:00
}
2024-07-29 18:09:57 +08:00
if err = runScript ( t , appInstall , "init" ) ; err != nil {
return err
}
2024-12-23 16:59:46 +08:00
if err = upApp ( t , appInstall , req . PullImage ) ; err != nil {
return err
}
2024-07-29 18:09:57 +08:00
updateToolApp ( appInstall )
return nil
}
2024-08-02 16:00:15 +08:00
handleAppStatus := func ( t * task . Task ) {
2024-07-29 18:09:57 +08:00
appInstall . Status = constant . UpErr
appInstall . Message = installTask . Task . ErrorMsg
_ = appInstallRepo . Save ( context . Background ( ) , appInstall )
}
installTask . AddSubTask ( task . GetTaskName ( appInstall . Name , task . TaskInstall , task . TaskScopeApp ) , installApp , handleAppStatus )
go func ( ) {
if taskErr := installTask . Execute ( ) ; taskErr != nil {
appInstall . Status = constant . InstallErr
appInstall . Message = taskErr . Error ( )
2025-01-09 17:02:22 +08:00
if strings . Contains ( taskErr . Error ( ) , "Timeout" ) && strings . Contains ( taskErr . Error ( ) , "Pulling" ) {
appInstall . Message = buserr . New ( "PullImageTimeout" ) . Error ( ) + appInstall . Message
}
2024-07-29 18:09:57 +08:00
_ = appInstallRepo . Save ( context . Background ( ) , appInstall )
2024-07-23 14:48:37 +08:00
}
} ( )
2024-07-29 18:09:57 +08:00
2024-07-23 14:48:37 +08:00
return
}
2024-08-23 17:22:05 +08:00
func ( a AppService ) SyncAppListFromLocal ( TaskID string ) {
2024-07-23 14:48:37 +08:00
var (
err error
dirEntries [ ] os . DirEntry
localApps [ ] model . App
)
2024-08-23 17:22:05 +08:00
syncTask , err := task . NewTaskWithOps ( i18n . GetMsgByKey ( "LocalApp" ) , task . TaskSync , task . TaskScopeAppStore , TaskID , 0 )
2024-07-23 14:48:37 +08:00
if err != nil {
2024-08-23 17:22:05 +08:00
global . LOG . Errorf ( "Create sync task failed %v" , err )
2024-07-23 14:48:37 +08:00
return
}
2024-08-23 17:22:05 +08:00
syncTask . AddSubTask ( task . GetTaskName ( i18n . GetMsgByKey ( "LocalApp" ) , task . TaskSync , task . TaskScopeAppStore ) , func ( t * task . Task ) ( err error ) {
fileOp := files . NewFileOp ( )
localAppDir := constant . LocalAppResourceDir
if ! fileOp . Stat ( localAppDir ) {
return nil
}
dirEntries , err = os . ReadDir ( localAppDir )
if err != nil {
return
}
for _ , dirEntry := range dirEntries {
if dirEntry . IsDir ( ) {
appDir := filepath . Join ( localAppDir , dirEntry . Name ( ) )
appDirEntries , err := os . ReadDir ( appDir )
if err != nil {
t . Log ( i18n . GetWithNameAndErr ( "ErrAppDirNull" , dirEntry . Name ( ) , err ) )
continue
}
app , err := handleLocalApp ( appDir )
if err != nil {
t . Log ( i18n . GetWithNameAndErr ( "LocalAppErr" , dirEntry . Name ( ) , err ) )
continue
}
var appDetails [ ] model . AppDetail
for _ , appDirEntry := range appDirEntries {
if appDirEntry . IsDir ( ) {
appDetail := model . AppDetail {
Version : appDirEntry . Name ( ) ,
Status : constant . AppNormal ,
}
versionDir := filepath . Join ( appDir , appDirEntry . Name ( ) )
if err = handleLocalAppDetail ( versionDir , & appDetail ) ; err != nil {
t . Log ( i18n . GetMsgWithMap ( "LocalAppVersionErr" , map [ string ] interface { } { "name" : app . Name , "version" : appDetail . Version , "err" : err . Error ( ) } ) )
continue
}
appDetails = append ( appDetails , appDetail )
2024-07-23 14:48:37 +08:00
}
}
2024-08-23 17:22:05 +08:00
if len ( appDetails ) > 0 {
app . Details = appDetails
localApps = append ( localApps , * app )
} else {
t . Log ( i18n . GetWithName ( "LocalAppVersionNull" , app . Name ) )
}
2024-07-23 14:48:37 +08:00
}
}
2024-08-23 17:22:05 +08:00
var (
newApps [ ] model . App
deleteApps [ ] model . App
updateApps [ ] model . App
oldAppIds [ ] uint
2024-07-23 14:48:37 +08:00
2024-08-23 17:22:05 +08:00
deleteAppIds [ ] uint
deleteAppDetails [ ] model . AppDetail
newAppDetails [ ] model . AppDetail
updateDetails [ ] model . AppDetail
2024-07-23 14:48:37 +08:00
2024-08-23 17:22:05 +08:00
appTags [ ] * model . AppTag
)
2024-07-23 14:48:37 +08:00
2024-08-23 17:22:05 +08:00
oldApps , _ := appRepo . GetBy ( appRepo . WithResource ( constant . AppResourceLocal ) )
apps := make ( map [ string ] model . App , len ( oldApps ) )
for _ , old := range oldApps {
old . Status = constant . AppTakeDown
apps [ old . Key ] = old
}
for _ , app := range localApps {
if oldApp , ok := apps [ app . Key ] ; ok {
app . ID = oldApp . ID
appDetails := make ( map [ string ] model . AppDetail , len ( oldApp . Details ) )
for _ , old := range oldApp . Details {
old . Status = constant . AppTakeDown
appDetails [ old . Version ] = old
}
for i , newDetail := range app . Details {
version := newDetail . Version
newDetail . Status = constant . AppNormal
newDetail . AppId = app . ID
oldDetail , exist := appDetails [ version ]
if exist {
newDetail . ID = oldDetail . ID
delete ( appDetails , version )
}
app . Details [ i ] = newDetail
}
for _ , v := range appDetails {
app . Details = append ( app . Details , v )
2024-07-23 14:48:37 +08:00
}
}
2024-08-23 17:22:05 +08:00
app . TagsKey = append ( app . TagsKey , constant . AppResourceLocal )
apps [ app . Key ] = app
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
for _ , app := range apps {
if app . ID == 0 {
newApps = append ( newApps , app )
} else {
oldAppIds = append ( oldAppIds , app . ID )
if app . Status == constant . AppTakeDown {
installs , _ := appInstallRepo . ListBy ( appInstallRepo . WithAppId ( app . ID ) )
if len ( installs ) > 0 {
updateApps = append ( updateApps , app )
continue
}
deleteAppIds = append ( deleteAppIds , app . ID )
deleteApps = append ( deleteApps , app )
deleteAppDetails = append ( deleteAppDetails , app . Details ... )
} else {
2024-07-23 14:48:37 +08:00
updateApps = append ( updateApps , app )
}
}
2024-08-23 17:22:05 +08:00
}
2024-07-23 14:48:37 +08:00
2024-08-23 17:22:05 +08:00
tags , _ := tagRepo . All ( )
tagMap := make ( map [ string ] uint , len ( tags ) )
for _ , tag := range tags {
tagMap [ tag . Key ] = tag . ID
}
2024-07-23 14:48:37 +08:00
2024-08-23 17:22:05 +08:00
tx , ctx := getTxAndContext ( )
defer tx . Rollback ( )
if len ( newApps ) > 0 {
if err = appRepo . BatchCreate ( ctx , newApps ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
for _ , update := range updateApps {
if err = appRepo . Save ( ctx , & update ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
if len ( deleteApps ) > 0 {
if err = appRepo . BatchDelete ( ctx , deleteApps ) ; err != nil {
return
}
if err = appDetailRepo . DeleteByAppIds ( ctx , deleteAppIds ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
if err = appTagRepo . DeleteByAppIds ( ctx , oldAppIds ) ; err != nil {
2024-07-23 14:48:37 +08:00
return
}
2024-08-23 17:22:05 +08:00
for _ , newApp := range newApps {
if newApp . ID > 0 {
for _ , detail := range newApp . Details {
detail . AppId = newApp . ID
newAppDetails = append ( newAppDetails , detail )
}
2024-07-23 14:48:37 +08:00
}
}
2024-08-23 17:22:05 +08:00
for _ , update := range updateApps {
for _ , detail := range update . Details {
if detail . ID == 0 {
detail . AppId = update . ID
newAppDetails = append ( newAppDetails , detail )
2024-07-23 14:48:37 +08:00
} else {
2024-08-23 17:22:05 +08:00
if detail . Status == constant . AppNormal {
updateDetails = append ( updateDetails , detail )
} else {
deleteAppDetails = append ( deleteAppDetails , detail )
}
2024-07-23 14:48:37 +08:00
}
}
}
2024-08-23 17:22:05 +08:00
allApps := append ( newApps , updateApps ... )
for _ , app := range allApps {
for _ , t := range app . TagsKey {
tagId , ok := tagMap [ t ]
if ok {
appTags = append ( appTags , & model . AppTag {
AppId : app . ID ,
TagId : tagId ,
} )
}
2024-07-23 14:48:37 +08:00
}
}
2024-08-23 17:22:05 +08:00
if len ( newAppDetails ) > 0 {
if err = appDetailRepo . BatchCreate ( ctx , newAppDetails ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
for _ , updateAppDetail := range updateDetails {
if err = appDetailRepo . Update ( ctx , updateAppDetail ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
if len ( deleteAppDetails ) > 0 {
if err = appDetailRepo . BatchDelete ( ctx , deleteAppDetails ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
if len ( oldAppIds ) > 0 {
if err = appTagRepo . DeleteByAppIds ( ctx , oldAppIds ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
if len ( appTags ) > 0 {
if err = appTagRepo . BatchCreate ( ctx , appTags ) ; err != nil {
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
tx . Commit ( )
global . LOG . Infof ( "Synchronization of local applications completed" )
return nil
} , nil )
go func ( ) {
_ = syncTask . Execute ( )
} ( )
2024-07-23 14:48:37 +08:00
}
func ( a AppService ) GetAppUpdate ( ) ( * response . AppUpdateRes , error ) {
res := & response . AppUpdateRes {
CanUpdate : false ,
}
versionUrl := fmt . Sprintf ( "%s/%s/1panel.json.version.txt" , global . CONF . System . AppRepo , global . CONF . System . Mode )
_ , versionRes , err := http2 . HandleGet ( versionUrl , http . MethodGet , constant . TimeOut20s )
if err != nil {
return nil , err
}
lastModifiedStr := string ( versionRes )
lastModified , err := strconv . Atoi ( lastModifiedStr )
if err != nil {
return nil , err
}
setting , err := NewISettingService ( ) . GetSettingInfo ( )
if err != nil {
return nil , err
}
if setting . AppStoreSyncStatus == constant . Syncing {
res . IsSyncing = true
return res , nil
}
appStoreLastModified , _ := strconv . Atoi ( setting . AppStoreLastModified )
res . AppStoreLastModified = appStoreLastModified
if setting . AppStoreLastModified == "" || lastModified != appStoreLastModified {
res . CanUpdate = true
return res , err
}
apps , _ := appRepo . GetBy ( appRepo . WithResource ( constant . AppResourceRemote ) )
for _ , app := range apps {
if app . Icon == "" {
res . CanUpdate = true
return res , err
}
}
list , err := getAppList ( )
if err != nil {
return res , err
}
if list . Extra . Version != "" && setting . SystemVersion != list . Extra . Version && ! common . CompareVersion ( setting . SystemVersion , list . Extra . Version ) {
2024-12-23 22:05:44 +08:00
global . LOG . Errorf ( "The current version %s is too low to synchronize with the App Store. The minimum required version is %s" , setting . SystemVersion , list . Extra . Version )
2024-07-23 14:48:37 +08:00
return nil , buserr . New ( "ErrVersionTooLow" )
}
res . AppList = list
return res , nil
}
func getAppFromRepo ( downloadPath string ) error {
downloadUrl := downloadPath
global . LOG . Infof ( "[AppStore] download file from %s" , downloadUrl )
fileOp := files . NewFileOp ( )
packagePath := filepath . Join ( constant . ResourceDir , filepath . Base ( downloadUrl ) )
if err := fileOp . DownloadFileWithProxy ( downloadUrl , packagePath ) ; err != nil {
return err
}
if err := fileOp . Decompress ( packagePath , constant . ResourceDir , files . SdkZip , "" ) ; err != nil {
return err
}
defer func ( ) {
_ = fileOp . DeleteFile ( packagePath )
} ( )
return nil
}
func getAppList ( ) ( * dto . AppList , error ) {
list := & dto . AppList { }
if err := getAppFromRepo ( fmt . Sprintf ( "%s/%s/1panel.json.zip" , global . CONF . System . AppRepo , global . CONF . System . Mode ) ) ; err != nil {
return nil , err
}
listFile := filepath . Join ( constant . ResourceDir , "1panel.json" )
content , err := os . ReadFile ( listFile )
if err != nil {
return nil , err
}
if err = json . Unmarshal ( content , list ) ; err != nil {
return nil , err
}
return list , nil
}
var InitTypes = map [ string ] struct { } {
"runtime" : { } ,
"php" : { } ,
"node" : { } ,
}
2025-01-03 16:54:09 +08:00
func deleteCustomApp ( ) {
var appIDS [ ] uint
installs , _ := appInstallRepo . ListBy ( )
for _ , install := range installs {
appIDS = append ( appIDS , install . AppId )
}
var ops [ ] repo . DBOption
ops = append ( ops , repo . WithByIDNotIn ( appIDS ) )
if len ( appIDS ) > 0 {
ops = append ( ops , repo . WithByIDNotIn ( appIDS ) )
}
apps , _ := appRepo . GetBy ( ops ... )
var deleteIDS [ ] uint
for _ , app := range apps {
if app . Resource == constant . AppResourceCustom {
deleteIDS = append ( deleteIDS , app . ID )
}
}
_ = appRepo . DeleteByIDs ( context . Background ( ) , deleteIDS )
_ = appDetailRepo . DeleteByAppIds ( context . Background ( ) , deleteIDS )
}
2024-08-22 18:48:55 +08:00
func ( a AppService ) SyncAppListFromRemote ( taskID string ) ( err error ) {
2024-12-30 21:04:11 +08:00
if xpack . IsUseCustomApp ( ) {
return nil
}
2024-08-22 18:48:55 +08:00
syncTask , err := task . NewTaskWithOps ( i18n . GetMsgByKey ( "App" ) , task . TaskSync , task . TaskScopeAppStore , taskID , 0 )
2024-07-23 14:48:37 +08:00
if err != nil {
return err
}
2024-08-22 18:48:55 +08:00
syncTask . AddSubTask ( task . GetTaskName ( i18n . GetMsgByKey ( "App" ) , task . TaskSync , task . TaskScopeAppStore ) , func ( t * task . Task ) ( err error ) {
updateRes , err := a . GetAppUpdate ( )
2024-07-23 14:48:37 +08:00
if err != nil {
return err
}
2024-08-22 18:48:55 +08:00
if ! updateRes . CanUpdate {
if updateRes . IsSyncing {
t . Log ( i18n . GetMsgByKey ( "AppStoreIsSyncing" ) )
return nil
}
t . Log ( i18n . GetMsgByKey ( "AppStoreIsLastVersion" ) )
return nil
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
list := & dto . AppList { }
if updateRes . AppList == nil {
list , err = getAppList ( )
if err != nil {
return err
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
} else {
list = updateRes . AppList
}
settingService := NewISettingService ( )
_ = settingService . Update ( "AppStoreSyncStatus" , constant . Syncing )
2024-12-03 10:12:02 +08:00
setting , err := settingService . GetSettingInfo ( )
if err != nil {
return err
}
2024-08-22 18:48:55 +08:00
var (
tags [ ] * model . Tag
appTags [ ] * model . AppTag
oldAppIds [ ] uint
)
for _ , t := range list . Extra . Tags {
tags = append ( tags , & model . Tag {
Key : t . Key ,
Name : t . Name ,
Sort : t . Sort ,
} )
}
2025-01-03 16:54:09 +08:00
deleteCustomApp ( )
oldApps , err := appRepo . GetBy ( appRepo . WithNotLocal ( ) )
2024-08-22 18:48:55 +08:00
if err != nil {
return err
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
for _ , old := range oldApps {
oldAppIds = append ( oldAppIds , old . ID )
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
transport := xpack . LoadRequestTransport ( )
baseRemoteUrl := fmt . Sprintf ( "%s/%s/1panel" , global . CONF . System . AppRepo , global . CONF . System . Mode )
2024-08-27 14:39:24 +08:00
appsMap := getApps ( oldApps , list . Apps , setting . SystemVersion , t )
2024-07-23 14:48:37 +08:00
2024-08-22 18:48:55 +08:00
t . LogStart ( i18n . GetMsgByKey ( "SyncAppDetail" ) )
for _ , l := range list . Apps {
app := appsMap [ l . AppProperty . Key ]
_ , iconRes , err := httpUtil . HandleGetWithTransport ( l . Icon , http . MethodGet , transport , constant . TimeOut20s )
if err != nil {
return err
}
iconStr := ""
if ! strings . Contains ( string ( iconRes ) , "<xml>" ) {
iconStr = base64 . StdEncoding . EncodeToString ( iconRes )
}
2024-07-23 14:48:37 +08:00
2024-08-22 18:48:55 +08:00
app . Icon = iconStr
app . TagsKey = l . AppProperty . Tags
if l . AppProperty . Recommend > 0 {
app . Recommend = l . AppProperty . Recommend
} else {
app . Recommend = 9999
}
app . ReadMe = l . ReadMe
app . LastModified = l . LastModified
versions := l . Versions
detailsMap := getAppDetails ( app . Details , versions )
for _ , v := range versions {
version := v . Name
detail := detailsMap [ version ]
versionUrl := fmt . Sprintf ( "%s/%s/%s" , baseRemoteUrl , app . Key , version )
2024-12-03 10:12:02 +08:00
paramByte , _ := json . Marshal ( v . AppForm )
var appForm dto . AppForm
_ = json . Unmarshal ( paramByte , & appForm )
if appForm . SupportVersion > 0 && common . CompareVersion ( strconv . FormatFloat ( appForm . SupportVersion , 'f' , - 1 , 64 ) , setting . SystemVersion ) {
delete ( detailsMap , version )
continue
}
2024-08-22 18:48:55 +08:00
if _ , ok := InitTypes [ app . Type ] ; ok {
dockerComposeUrl := fmt . Sprintf ( "%s/%s" , versionUrl , "docker-compose.yml" )
_ , composeRes , err := httpUtil . HandleGetWithTransport ( dockerComposeUrl , http . MethodGet , transport , constant . TimeOut20s )
if err != nil {
return err
}
detail . DockerCompose = string ( composeRes )
} else {
detail . DockerCompose = ""
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
detail . Params = string ( paramByte )
detail . DownloadUrl = fmt . Sprintf ( "%s/%s" , versionUrl , app . Key + "-" + version + ".tar.gz" )
detail . DownloadCallBackUrl = v . DownloadCallBackUrl
detail . Update = true
detail . LastModified = v . LastModified
detailsMap [ version ] = detail
}
var newDetails [ ] model . AppDetail
for _ , detail := range detailsMap {
newDetails = append ( newDetails , detail )
}
app . Details = newDetails
appsMap [ l . AppProperty . Key ] = app
}
t . LogSuccess ( i18n . GetMsgByKey ( "SyncAppDetail" ) )
var (
addAppArray [ ] model . App
updateAppArray [ ] model . App
deleteAppArray [ ] model . App
deleteIds [ ] uint
tagMap = make ( map [ string ] uint , len ( tags ) )
)
for _ , v := range appsMap {
if v . ID == 0 {
addAppArray = append ( addAppArray , v )
2024-07-23 14:48:37 +08:00
} else {
2024-08-22 18:48:55 +08:00
if v . Status == constant . AppTakeDown {
installs , _ := appInstallRepo . ListBy ( appInstallRepo . WithAppId ( v . ID ) )
if len ( installs ) > 0 {
updateAppArray = append ( updateAppArray , v )
continue
}
deleteAppArray = append ( deleteAppArray , v )
deleteIds = append ( deleteIds , v . ID )
} else {
updateAppArray = append ( updateAppArray , v )
}
2024-07-23 14:48:37 +08:00
}
}
2024-08-22 18:48:55 +08:00
2024-08-23 17:22:05 +08:00
tx , ctx := getTxAndContext ( )
defer func ( ) {
if err != nil {
tx . Rollback ( )
return
}
} ( )
2024-08-22 18:48:55 +08:00
if len ( addAppArray ) > 0 {
2024-08-23 17:22:05 +08:00
if err = appRepo . BatchCreate ( ctx , addAppArray ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
if len ( deleteAppArray ) > 0 {
2024-08-23 17:22:05 +08:00
if err = appRepo . BatchDelete ( ctx , deleteAppArray ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-08-23 17:22:05 +08:00
if err = appDetailRepo . DeleteByAppIds ( ctx , deleteIds ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
if err = tagRepo . DeleteAll ( ctx ) ; err != nil {
2024-07-23 14:48:37 +08:00
return
}
2024-08-22 18:48:55 +08:00
if len ( tags ) > 0 {
2024-08-23 17:22:05 +08:00
if err = tagRepo . BatchCreate ( ctx , tags ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
for _ , tag := range tags {
tagMap [ tag . Key ] = tag . ID
}
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
for _ , update := range updateAppArray {
2024-08-23 17:22:05 +08:00
if err = appRepo . Save ( ctx , & update ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
2024-07-23 14:48:37 +08:00
}
}
2024-08-22 18:48:55 +08:00
apps := append ( addAppArray , updateAppArray ... )
var (
addDetails [ ] model . AppDetail
updateDetails [ ] model . AppDetail
deleteDetails [ ] model . AppDetail
)
for _ , app := range apps {
for _ , tag := range app . TagsKey {
tagId , ok := tagMap [ tag ]
if ok {
appTags = append ( appTags , & model . AppTag {
AppId : app . ID ,
TagId : tagId ,
} )
}
}
for _ , d := range app . Details {
d . AppId = app . ID
if d . ID == 0 {
addDetails = append ( addDetails , d )
} else {
if d . Status == constant . AppTakeDown {
runtime , _ := runtimeRepo . GetFirst ( runtimeRepo . WithDetailId ( d . ID ) )
if runtime != nil {
updateDetails = append ( updateDetails , d )
continue
}
installs , _ := appInstallRepo . ListBy ( appInstallRepo . WithDetailIdsIn ( [ ] uint { d . ID } ) )
if len ( installs ) > 0 {
updateDetails = append ( updateDetails , d )
continue
}
deleteDetails = append ( deleteDetails , d )
} else {
2024-07-23 14:48:37 +08:00
updateDetails = append ( updateDetails , d )
}
}
}
}
2024-08-22 18:48:55 +08:00
if len ( addDetails ) > 0 {
2024-08-23 17:22:05 +08:00
if err = appDetailRepo . BatchCreate ( ctx , addDetails ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
if len ( deleteDetails ) > 0 {
2024-08-23 17:22:05 +08:00
if err = appDetailRepo . BatchDelete ( ctx , deleteDetails ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
for _ , u := range updateDetails {
2024-08-23 17:22:05 +08:00
if err = appDetailRepo . Update ( ctx , u ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
if len ( oldAppIds ) > 0 {
2024-08-23 17:22:05 +08:00
if err = appTagRepo . DeleteByAppIds ( ctx , oldAppIds ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-22 18:48:55 +08:00
if len ( appTags ) > 0 {
2024-08-23 17:22:05 +08:00
if err = appTagRepo . BatchCreate ( ctx , appTags ) ; err != nil {
2024-08-22 18:48:55 +08:00
return
}
2024-07-23 14:48:37 +08:00
}
2024-08-23 17:22:05 +08:00
tx . Commit ( )
2024-07-23 14:48:37 +08:00
2024-08-22 18:48:55 +08:00
_ = settingService . Update ( "AppStoreSyncStatus" , constant . SyncSuccess )
_ = settingService . Update ( "AppStoreLastModified" , strconv . Itoa ( list . LastModified ) )
t . Log ( i18n . GetMsgByKey ( "AppStoreSyncSuccess" ) )
return nil
} , nil )
2024-07-23 14:48:37 +08:00
2024-08-26 13:40:27 +08:00
go func ( ) {
if err = syncTask . Execute ( ) ; err != nil {
2025-01-03 16:54:09 +08:00
_ = NewISettingService ( ) . Update ( "AppStoreLastModified" , "0" )
2024-08-26 13:40:27 +08:00
_ = NewISettingService ( ) . Update ( "AppStoreSyncStatus" , constant . Error )
}
} ( )
return nil
2024-07-23 14:48:37 +08:00
}
2024-08-26 18:34:34 +08:00
func ( a AppService ) UpdateAppstoreConfig ( req request . AppstoreUpdate ) error {
settingService := NewISettingService ( )
return settingService . Update ( "AppDefaultDomain" , req . DefaultDomain )
}
func ( a AppService ) GetAppstoreConfig ( ) ( * response . AppstoreConfig , error ) {
defaultDomain , _ := settingRepo . Get ( settingRepo . WithByKey ( "AppDefaultDomain" ) )
res := & response . AppstoreConfig { }
res . DefaultDomain = defaultDomain . Value
return res , nil
}