From f020c1162d685da2e57ba841d1c179bbb1a6ee57 Mon Sep 17 00:00:00 2001 From: link Date: Tue, 9 Nov 2021 18:57:50 +0800 Subject: [PATCH] Fixing bugs Resolve application installation path errors --- UI | 2 +- main.go | 1 + model/sys_common.go | 2 + model/system_app/sync.go | 8 ++ pkg/docker/volumes.go | 6 +- pkg/utils/file/file.go | 13 +++ route/init.go | 188 +++++++++++++++++++++++++++++++++++ route/route.go | 17 +--- route/v1/docker.go | 4 + route/v1/sync.go | 22 ++++ service/app.go | 72 +++++++++++++- service/docker.go | 12 ++- service/model/o_container.go | 2 + service/service.go | 1 + service/system.go | 1 - 15 files changed, 327 insertions(+), 24 deletions(-) create mode 100644 model/system_app/sync.go create mode 100644 route/init.go create mode 100644 route/v1/sync.go diff --git a/UI b/UI index 11d304e..741aadb 160000 --- a/UI +++ b/UI @@ -1 +1 @@ -Subproject commit 11d304e96ddcd9fedea690ab3c8f7750b0ea8a41 +Subproject commit 741aadb0110cf4def85c83d8123da9bbd66c34d1 diff --git a/main.go b/main.go index e54d7b7..934ca7d 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ func init() { //gredis.GetRedisConn(config.RedisInfo), service.MyService = service.NewService(sqliteDB, loger2.NewOLoger()) service.Cache = cache.Init() + route.InitFunction() } // @title casaOS API diff --git a/model/sys_common.go b/model/sys_common.go index a1e2789..b7f2d00 100644 --- a/model/sys_common.go +++ b/model/sys_common.go @@ -64,4 +64,6 @@ type SystemConfig struct { ConfigStr string `json:"config_str"` WidgetList string `json:"widget_list"` ConfigPath string `json:"config_path"` + SyncPort string `json:"sync_port"` + SyncKey string `json:"sync_key"` } diff --git a/model/system_app/sync.go b/model/system_app/sync.go new file mode 100644 index 0000000..9d1f710 --- /dev/null +++ b/model/system_app/sync.go @@ -0,0 +1,8 @@ +package system_app + +import "encoding/xml" + +type SyncConfig struct { + XMLName xml.Name `xml:"configuration"` + Key string `xml:"gui>apikey"` +} diff --git a/pkg/docker/volumes.go b/pkg/docker/volumes.go index dc4c734..f7cbce4 100644 --- a/pkg/docker/volumes.go +++ b/pkg/docker/volumes.go @@ -10,16 +10,20 @@ func GetDir(id, envName string) string { } switch { - case strings.Contains(strings.ToLower(envName), "config"): + case strings.Contains(strings.ToLower(envName), "config") || strings.Contains(strings.ToLower(envName), "photoprism/storage") || strings.Contains(strings.ToLower(envName), "config"): path = "/DATA/AppData/" + id + "/" case strings.Contains(strings.ToLower(envName), "movie"): path = "/DATA/Media/Movies/" case strings.Contains(strings.ToLower(envName), "music"): path = "/DATA/Media/Music/" + case strings.Contains(strings.ToLower(envName), "photoprism/originals"): + path = "/DATA/Gallery" case strings.Contains(strings.ToLower(envName), "download"): path = "/DATA/Downloads/" case strings.Contains(strings.ToLower(envName), "photo") || strings.Contains(strings.ToLower(envName), "pictures"): path = "/DATA/Downloads/" + case strings.ToLower(envName) == "/srv": + path = "/DATA/" default: //path = "/media" } diff --git a/pkg/utils/file/file.go b/pkg/utils/file/file.go index 7ba10f3..78c79c4 100644 --- a/pkg/utils/file/file.go +++ b/pkg/utils/file/file.go @@ -146,3 +146,16 @@ func IsNotExistCreateFile(src string) error { return nil } + +func ReadFullFile(path string) []byte { + file, err := os.Open(path) + if err != nil { + return []byte("") + } + defer file.Close() + content, err := ioutil.ReadAll(file) + if err != nil { + return []byte("") + } + return content +} diff --git a/route/init.go b/route/init.go new file mode 100644 index 0000000..4c69ab3 --- /dev/null +++ b/route/init.go @@ -0,0 +1,188 @@ +package route + +import ( + "encoding/json" + "encoding/xml" + "strconv" + "time" + + "github.com/IceWhaleTech/CasaOS/model" + "github.com/IceWhaleTech/CasaOS/model/system_app" + "github.com/IceWhaleTech/CasaOS/pkg/config" + "github.com/IceWhaleTech/CasaOS/pkg/docker" + "github.com/IceWhaleTech/CasaOS/pkg/utils/env_helper" + "github.com/IceWhaleTech/CasaOS/pkg/utils/file" + "github.com/IceWhaleTech/CasaOS/pkg/utils/port" + "github.com/IceWhaleTech/CasaOS/service" + model2 "github.com/IceWhaleTech/CasaOS/service/model" + uuid "github.com/satori/go.uuid" +) + +func InitFunction() { + go checkSystemApp() +} + +var syncIsExistence = false + +func installSyncthing(appId string) { + + var appInfo model.ServerAppList + m := model.CustomizationPostData{} + var dockerImage string + var dockerImageVersion string + + appInfo = service.MyService.OAPI().GetServerAppInfo(appId) + + dockerImage = appInfo.Image + + if len(appInfo.ImageVersion) == 0 { + dockerImageVersion = "latest" + } + + if appInfo.NetworkModel != "host" { + for i := 0; i < len(appInfo.Ports); i++ { + if p, _ := strconv.Atoi(appInfo.Ports[i].ContainerPort); port.IsPortAvailable(p, appInfo.Ports[i].Protocol) { + appInfo.Ports[i].CommendPort = strconv.Itoa(p) + } else { + if appInfo.Ports[i].Protocol == "tcp" { + if p, err := port.GetAvailablePort("tcp"); err == nil { + appInfo.Ports[i].CommendPort = strconv.Itoa(p) + } + } else if appInfo.Ports[i].Protocol == "upd" { + if p, err := port.GetAvailablePort("udp"); err == nil { + appInfo.Ports[i].CommendPort = strconv.Itoa(p) + } + } + } + + if appInfo.Ports[i].Type == 0 { + appInfo.PortMap = appInfo.Ports[i].CommendPort + } + } + } + + for i := 0; i < len(appInfo.Devices); i++ { + if !file.CheckNotExist(appInfo.Devices[i].ContainerPath) { + appInfo.Devices[i].Path = appInfo.Devices[i].ContainerPath + } + } + if len(appInfo.Tip) > 0 { + appInfo.Tip = env_helper.ReplaceStringDefaultENV(appInfo.Tip) + } + + for i := 0; i < len(appInfo.Volumes); i++ { + appInfo.Volumes[i].Path = docker.GetDir("", appInfo.Volumes[i].ContainerPath) + } + appInfo.MaxMemory = service.MyService.ZiMa().GetMemInfo().Total >> 20 + + id := uuid.NewV4().String() + + installLog := model2.AppNotify{} + + // step:下载镜像 + err := service.MyService.Docker().DockerPullImage(dockerImage+":"+dockerImageVersion, installLog) + if err != nil { + //pull image error + return + } + + for !service.MyService.Docker().IsExistImage(dockerImage + ":" + dockerImageVersion) { + time.Sleep(time.Second) + } + + m.CpuShares = 50 + m.Envs = appInfo.Envs + m.Memory = int64(appInfo.MaxMemory) + m.Origin = "system" + m.PortMap = appInfo.PortMap + m.Ports = appInfo.Ports + m.Restart = "" + m.Volumes = appInfo.Volumes + + containerId, err := service.MyService.Docker().DockerContainerCreate(dockerImage+":"+dockerImageVersion, id, m, appInfo.NetworkModel) + + if err != nil { + // create container error + return + } + + //step:start container + err = service.MyService.Docker().DockerContainerStart(id) + if err != nil { + //start container error + return + } + + portsStr, _ := json.Marshal(appInfo.Ports) + envsStr, _ := json.Marshal(appInfo.Envs) + volumesStr, _ := json.Marshal(appInfo.Volumes) + devicesStr, _ := json.Marshal(appInfo.Devices) + //step: 保存数据到数据库 + md := model2.AppListDBModel{ + CustomId: id, + Title: appInfo.Title, + //ScreenshotLink: appInfo.ScreenshotLink, + Slogan: appInfo.Tagline, + Description: appInfo.Description, + //Tags: appInfo.Tags, + Icon: appInfo.Icon, + Version: dockerImageVersion, + ContainerId: containerId, + Image: dockerImage, + Index: appInfo.Index, + PortMap: appInfo.PortMap, + Label: appInfo.Title, + EnableUPNP: false, + Ports: string(portsStr), + Envs: string(envsStr), + Volumes: string(volumesStr), + Position: true, + NetModel: appInfo.NetworkModel, + Restart: m.Restart, + CpuShares: 50, + Memory: int64(appInfo.MaxMemory), + Devices: string(devicesStr), + Origin: m.Origin, + CreatedAt: strconv.FormatInt(time.Now().Unix(), 10), + UpdatedAt: strconv.FormatInt(time.Now().Unix(), 10), + } + service.MyService.App().SaveContainer(md) + + checkSystemApp() +} + +// check if the system application is installed +func checkSystemApp() { + list := service.MyService.App().GetSystemAppList() + for _, v := range *list { + if v.Image == "linuxserver/syncthing" { + syncIsExistence = true + if config.SystemConfigInfo.SyncPort != v.Port { + config.SystemConfigInfo.SyncPort = v.Port + } + var paths []model.PathMap + json.Unmarshal([]byte(v.Volumes), &paths) + path := "" + for _, i := range paths { + if i.ContainerPath == "/config" { + path = docker.GetDir(v.CustomId, i.ContainerPath) + "config.xml" + for i := 0; i < 10; i++ { + if file.CheckNotExist(path) { + time.Sleep(1 * time.Second) + } else { + break + } + } + break + } + } + content := file.ReadFullFile(path) + syncConfig := &system_app.SyncConfig{} + xml.Unmarshal(content, &syncConfig) + config.SystemConfigInfo.SyncKey = syncConfig.Key + } + } + if !syncIsExistence { + installSyncthing("44") + } +} diff --git a/route/route.go b/route/route.go index 5341749..a26e216 100644 --- a/route/route.go +++ b/route/route.go @@ -1,10 +1,7 @@ package route import ( - "fmt" "net/http" - "net/http/httputil" - "net/url" "github.com/IceWhaleTech/CasaOS/middleware" "github.com/IceWhaleTech/CasaOS/pkg/config" @@ -41,19 +38,6 @@ func InitRouter() *gin.Engine { //get user info r.GET("/v1/user/info", v1.UserInfo) - r.GET("/syncthing/*url", func(c *gin.Context) { - ur := c.Param("url") - fmt.Println(ur) - target := "http://localhost:8384" //最终要访问的服务 - remote, err := url.Parse(target) - if err != nil { - fmt.Println(err) - } - proxy := httputil.NewSingleHostReverseProxy(remote) - c.Request.URL.Path = "/" + ur //请求API - proxy.ServeHTTP(c.Writer, c.Request) - }) - v1Group := r.Group("/v1") v1Group.Use(jwt2.JWT(swagHandler)) @@ -282,6 +266,7 @@ func InitRouter() *gin.Engine { { v1SearchGroup.GET("/search", v1.GetSearchList) } + v1Group.GET("/sync/*url", v1.SyncToSyncthing) } return r } diff --git a/route/v1/docker.go b/route/v1/docker.go index 29c0749..86d5037 100644 --- a/route/v1/docker.go +++ b/route/v1/docker.go @@ -1135,6 +1135,10 @@ func ContainerUpdateInfo(c *gin.Context) { var vol model.PathArray json2.Unmarshal([]byte(appInfo.Volumes), &vol) + for i := 0; i < len(vol); i++ { + vol[i].Path = strings.ReplaceAll(vol[i].Path, "$AppID", appId) + } + var dir model.PathArray json2.Unmarshal([]byte(appInfo.Devices), &dir) diff --git a/route/v1/sync.go b/route/v1/sync.go new file mode 100644 index 0000000..ab0e299 --- /dev/null +++ b/route/v1/sync.go @@ -0,0 +1,22 @@ +package v1 + +import ( + "net/http/httputil" + "net/url" + + "github.com/IceWhaleTech/CasaOS/pkg/config" + "github.com/gin-gonic/gin" +) + +func SyncToSyncthing(c *gin.Context) { + u := c.Param("url") + target := "http://127.0.0.1:" + config.SystemConfigInfo.SyncPort + remote, err := url.Parse(target) + if err != nil { + return + } + proxy := httputil.NewSingleHostReverseProxy(remote) + c.Request.Header.Add("X-API-Key", config.SystemConfigInfo.SyncKey) + c.Request.URL.Path = u + proxy.ServeHTTP(c.Writer, c.Request) +} diff --git a/service/app.go b/service/app.go index a0e29c7..8130ac7 100644 --- a/service/app.go +++ b/service/app.go @@ -27,6 +27,7 @@ type AppService interface { UpdateApp(m model2.AppListDBModel) GetSimpleContainerInfo(name string) (types.Container, error) DelAppConfigDir(id, path string) + GetSystemAppList() *[]model2.MyAppList } type appStruct struct { @@ -52,7 +53,7 @@ func (a *appStruct) GetMyList(index, size int, position bool) *[]model2.MyAppLis //获取本地数据库应用 var lm []model2.AppListDBModel - a.db.Table(model2.CONTAINERTABLENAME).Select("title,icon,port_map,`index`,container_id,position,label,slogan").Find(&lm) + a.db.Table(model2.CONTAINERTABLENAME).Select("title,icon,port_map,`index`,container_id,position,label,slogan,image").Find(&lm) list := []model2.MyAppList{} lMap := make(map[string]interface{}) @@ -65,6 +66,67 @@ func (a *appStruct) GetMyList(index, size int, position bool) *[]model2.MyAppLis lMap[dbModel.ContainerId] = dbModel } } + for _, container := range containers { + + if lMap[container.ID] != nil && container.Labels["origin"] != "system" { + var m model2.AppListDBModel + m = lMap[container.ID].(model2.AppListDBModel) + if len(m.Label) == 0 { + m.Label = m.Title + } + + info, err := cli.ContainerInspect(context.Background(), container.ID) + var tm string + if err != nil { + tm = time.Now().String() + } else { + tm = info.State.StartedAt + } + list = append(list, model2.MyAppList{ + Name: m.Label, + Icon: m.Icon, + State: container.State, + CustomId: strings.ReplaceAll(container.Names[0], "/", ""), + Port: m.PortMap, + Index: m.Index, + UpTime: tm, + Image: m.Image, + Slogan: m.Slogan, + //Rely: m.Rely, + }) + } + + } + + return &list + +} + +//system application list +func (a *appStruct) GetSystemAppList() *[]model2.MyAppList { + //获取docker应用 + cli, err := client2.NewClientWithOpts(client2.FromEnv) + if err != nil { + a.log.Error("初始化client失败", "app.getmylist", "line:36", err) + } + defer cli.Close() + fts := filters.NewArgs() + fts.Add("label", "origin=system") + containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true, Filters: fts}) + if err != nil { + a.log.Error("获取docker容器失败", "app.getmylist", "line:42", err) + } + + //获取本地数据库应用 + + var lm []model2.AppListDBModel + a.db.Table(model2.CONTAINERTABLENAME).Select("title,icon,port_map,`index`,container_id,position,label,slogan,image,volumes").Find(&lm) + + list := []model2.MyAppList{} + lMap := make(map[string]interface{}) + for _, dbModel := range lm { + lMap[dbModel.ContainerId] = dbModel + } for _, container := range containers { if lMap[container.ID] != nil { @@ -89,7 +151,9 @@ func (a *appStruct) GetMyList(index, size int, position bool) *[]model2.MyAppLis Port: m.PortMap, Index: m.Index, UpTime: tm, + Image: m.Image, Slogan: m.Slogan, + Volumes: m.Volumes, //Rely: m.Rely, }) } @@ -168,6 +232,12 @@ func (a *appStruct) RemoveContainerById(id string) { a.db.Table(model2.CONTAINERTABLENAME).Where("custom_id = ?", id).Delete(&model2.AppListDBModel{}) } +// init install +func Init() { + +} + func NewAppService(db *gorm.DB, logger loger2.OLog) AppService { + Init() return &appStruct{db: db, log: logger} } diff --git a/service/docker.go b/service/docker.go index d97ca09..9f0d3e1 100644 --- a/service/docker.go +++ b/service/docker.go @@ -7,6 +7,7 @@ import ( "encoding/binary" json2 "encoding/json" "fmt" + "reflect" "regexp" "syscall" @@ -332,10 +333,13 @@ func (ds *dockerService) DockerPullImage(imageName string, m model2.AppNotify) e } break } - m.Type = types2.NOTIFY_TYPE_INSTALL_LOG - m.State = 0 - m.Message = string(buf[:n]) - MyService.Notify().UpdateLog(m) + if !reflect.DeepEqual(m, model2.AppNotify{}) { + m.Type = types2.NOTIFY_TYPE_INSTALL_LOG + m.State = 0 + m.Message = string(buf[:n]) + MyService.Notify().UpdateLog(m) + } + } return err } diff --git a/service/model/o_container.go b/service/model/o_container.go index d2a9a24..ee0ffe2 100644 --- a/service/model/o_container.go +++ b/service/model/o_container.go @@ -61,4 +61,6 @@ type MyAppList struct { UpTime string `json:"up_time"` Slogan string `json:"slogan"` Rely model.MapStrings `json:"rely"` //[{"mysql":"id"},{"mysql":"id"}] + Image string `json:"image"` + Volumes string `json:"volumes"` } diff --git a/service/service.go b/service/service.go index 8fde8ee..d187bea 100644 --- a/service/service.go +++ b/service/service.go @@ -30,6 +30,7 @@ type Repository interface { } func NewService(db *gorm.DB, log loger2.OLog) Repository { + return &store{ app: NewAppService(db, log), ddns: NewDDNSService(db, log), diff --git a/service/system.go b/service/system.go index 91390a8..fd2b83c 100644 --- a/service/system.go +++ b/service/system.go @@ -39,7 +39,6 @@ func (s *systemService) UpSystemConfig(str string, widget string) { } config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath) } - func (s *systemService) GetCasaOSLogs(lineNumber int) string { file, err := os.Open(s.log.Path()) if err != nil {