diff --git a/model/disk.go b/model/disk.go index 16e1d0d..a36dfdc 100644 --- a/model/disk.go +++ b/model/disk.go @@ -21,6 +21,7 @@ type LSBLKModel struct { Health string `json:"health"` HotPlug bool `json:"hotplug"` FSUsed string `json:"fsused"` + Temperature int `json:"temperature"` Tran string `json:"tran"` MinIO uint64 `json:"min-io"` UsedPercent float64 `json:"used_percent"` @@ -28,5 +29,7 @@ type LSBLKModel struct { Children []LSBLKModel `json:"children"` //详情特有 StartSector uint64 `json:"start_sector,omitempty"` + Rota bool `json:"rota"` //true(hhd) false(ssd) + DiskType string `json:"disk_type"` EndSector uint64 `json:"end_sector,omitempty"` } diff --git a/model/smartctl_model.go b/model/smartctl_model.go new file mode 100644 index 0000000..ec7f75c --- /dev/null +++ b/model/smartctl_model.go @@ -0,0 +1,69 @@ +package model + +// +type SmartctlA struct { + Smartctl struct { + Version []int `json:"version"` + SvnRevision string `json:"svn_revision"` + PlatformInfo string `json:"platform_info"` + BuildInfo string `json:"build_info"` + Argv []string `json:"argv"` + ExitStatus int `json:"exit_status"` + } `json:"smartctl"` + Device struct { + Name string `json:"name"` + InfoName string `json:"info_name"` + Type string `json:"type"` + Protocol string `json:"protocol"` + } `json:"device"` + ModelName string `json:"model_name"` + SerialNumber string `json:"serial_number"` + FirmwareVersion string `json:"firmware_version"` + UserCapacity struct { + Blocks int `json:"blocks"` + Bytes int64 `json:"bytes"` + } `json:"user_capacity"` + SmartStatus struct { + Passed bool `json:"passed"` + } `json:"smart_status"` + AtaSmartData struct { + OfflineDataCollection struct { + Status struct { + Value int `json:"value"` + String string `json:"string"` + } `json:"status"` + CompletionSeconds int `json:"completion_seconds"` + } `json:"offline_data_collection"` + SelfTest struct { + Status struct { + Value int `json:"value"` + String string `json:"string"` + Passed bool `json:"passed"` + } `json:"status"` + PollingMinutes struct { + Short int `json:"short"` + Extended int `json:"extended"` + Conveyance int `json:"conveyance"` + } `json:"polling_minutes"` + } `json:"self_test"` + Capabilities struct { + Values []int `json:"values"` + ExecOfflineImmediateSupported bool `json:"exec_offline_immediate_supported"` + OfflineIsAbortedUponNewCmd bool `json:"offline_is_aborted_upon_new_cmd"` + OfflineSurfaceScanSupported bool `json:"offline_surface_scan_supported"` + SelfTestsSupported bool `json:"self_tests_supported"` + ConveyanceSelfTestSupported bool `json:"conveyance_self_test_supported"` + SelectiveSelfTestSupported bool `json:"selective_self_test_supported"` + AttributeAutosaveEnabled bool `json:"attribute_autosave_enabled"` + ErrorLoggingSupported bool `json:"error_logging_supported"` + GpLoggingSupported bool `json:"gp_logging_supported"` + } `json:"capabilities"` + } `json:"ata_smart_data"` + PowerOnTime struct { + Hours int `json:"hours"` + } `json:"power_on_time"` + PowerCycleCount int `json:"power_cycle_count"` + Temperature struct { + Current int `json:"current"` + } `json:"temperature"` +} diff --git a/pkg/utils/command/command_helper.go b/pkg/utils/command/command_helper.go index aa9ce1a..9a2fded 100644 --- a/pkg/utils/command/command_helper.go +++ b/pkg/utils/command/command_helper.go @@ -2,9 +2,11 @@ package command import ( "bufio" + "context" "fmt" "io/ioutil" "os/exec" + "time" ) func OnlyExec(cmdStr string) { @@ -85,7 +87,26 @@ func ExecLSBLK() []byte { func ExecLSBLKByPath(path string) []byte { output, err := exec.Command("lsblk", path, "-O", "-J", "-b").Output() if err != nil { + fmt.Println("lsblk", err) return nil } return output } + +//exec smart +func ExecSmartCTLByPath(path string) []byte { + timeout := 3 + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + output, err := exec.CommandContext(ctx, "smartctl", "-a", path, "-j").Output() + if err != nil { + fmt.Println("smartctl", err) + return nil + } + return output +} + +func ExecEnabledSMART(path string) { + + exec.Command("smartctl", "-s on", path).Output() +} diff --git a/pkg/utils/oasis_err/e.go b/pkg/utils/oasis_err/e.go index fc589f8..bf76afa 100644 --- a/pkg/utils/oasis_err/e.go +++ b/pkg/utils/oasis_err/e.go @@ -21,6 +21,13 @@ const ( //zerotier GET_TOKEN_ERROR = 30001 + //disk + NAME_NOT_AVAILABLE = 40001 + DISK_NEEDS_FORMAT = 40002 + DISK_BUSYING = 40003 + REMOVE_MOUNT_POINT_ERROR = 40004 + FORMAT_ERROR = 40005 + //app UNINSTALL_APP_ERROR = 50001 PULL_IMAGE_ERROR = 50002 @@ -37,34 +44,41 @@ const ( var MsgFlags = map[int]string{ SUCCESS: "ok", ERROR: "fail", - INVALID_PARAMS: "Invalid params", - ERROR_AUTH_TOKEN: "error auth token", + INVALID_PARAMS: "Parameters Error", + ERROR_AUTH_TOKEN: "Error auth token", //user - PWD_INVALID: "Password invalid", + PWD_INVALID: "Invalid password", PWD_IS_EMPTY: "Password is empty", - PWD_INVALID_OLD: "Old Password invalid", - ACCOUNT_LOCK: "Account Lock", + PWD_INVALID_OLD: "Invalid old password", + ACCOUNT_LOCK: "Account is locked", //system - DIR_ALREADY_EXISTS: "Directory already exists", + DIR_ALREADY_EXISTS: "Folder already exists", FILE_ALREADY_EXISTS: "File already exists", - FILE_OR_DIR_EXISTS: "File or directory already exists", + FILE_OR_DIR_EXISTS: "File or folder already exists", PORT_IS_OCCUPIED: "Port is occupied", //zerotier GET_TOKEN_ERROR: "Get token error,Please log in to zerotier's official website to confirm whether the account is available", //app - UNINSTALL_APP_ERROR: "uninstall app error", - PULL_IMAGE_ERROR: "pull image error", - DEVICE_NOT_EXIST: "device not exist", + UNINSTALL_APP_ERROR: "Error uninstalling app", + PULL_IMAGE_ERROR: "Error pulling image", + DEVICE_NOT_EXIST: "Device does not exist", + + //disk + NAME_NOT_AVAILABLE: "Name not available", + DISK_NEEDS_FORMAT: "Drive needs to be formatted", + REMOVE_MOUNT_POINT_ERROR: "Failed to remove mount point", + DISK_BUSYING: "Drive is busy", + FORMAT_ERROR: "Formatting failed, please check if the directory is occupied", // - FILE_DOES_NOT_EXIST: "file does not exist", + FILE_DOES_NOT_EXIST: "File does not exist", - FILE_READ_ERROR: "file read error", - SHORTCUTS_URL_ERROR: "url error", + FILE_READ_ERROR: "File read error", + SHORTCUTS_URL_ERROR: "URL error", } //获取错误信息 diff --git a/route/init.go b/route/init.go index c2af48d..319fbef 100644 --- a/route/init.go +++ b/route/init.go @@ -3,6 +3,7 @@ package route import ( "encoding/json" "encoding/xml" + "fmt" "strconv" "time" @@ -34,10 +35,9 @@ func installSyncthing(appId string) { m := model.CustomizationPostData{} var dockerImage string var dockerImageVersion string - - appInfo = service.MyService.OAPI().GetServerAppInfo(appId) - + appInfo = service.MyService.OAPI().GetServerAppInfo(appId, "system") dockerImage = appInfo.Image + dockerImageVersion = appInfo.ImageVersion if len(appInfo.ImageVersion) == 0 { dockerImageVersion = "latest" @@ -84,9 +84,9 @@ func installSyncthing(appId string) { err := service.MyService.Docker().DockerPullImage(dockerImage+":"+dockerImageVersion, installLog) if err != nil { //pull image error + fmt.Println("pull image error", err, dockerImage, dockerImageVersion) return } - for !service.MyService.Docker().IsExistImage(dockerImage + ":" + dockerImageVersion) { time.Sleep(time.Second) } @@ -101,8 +101,8 @@ func installSyncthing(appId string) { m.Volumes = appInfo.Volumes containerId, err := service.MyService.Docker().DockerContainerCreate(dockerImage+":"+dockerImageVersion, id, m, appInfo.NetworkModel) - if err != nil { + fmt.Println("container create error", err) // create container error return } @@ -170,7 +170,7 @@ func checkSystemApp() { path := "" for _, i := range paths { if i.ContainerPath == "/config" { - path = docker.GetDir(v.CustomId, i.Path) + "config.xml" + path = docker.GetDir(v.CustomId, i.Path) + "/config.xml" for i := 0; i < 10; i++ { if file.CheckNotExist(path) { time.Sleep(1 * time.Second) @@ -197,21 +197,37 @@ func CheckSerialDiskMount() { list := service.MyService.Disk().LSBLK() mountPoint := make(map[string]string, len(dbList)) - + //remount + for _, v := range dbList { + mountPoint[v.Path] = v.MountPoint + } for _, v := range list { + command.ExecEnabledSMART(v.Path) if v.Children != nil { for _, h := range v.Children { - mountPoint[h.MountPoint] = "1" + if len(h.MountPoint) == 0 && len(v.Children) == 1 && h.FsType == "ext4" { + if m, ok := mountPoint[h.Path]; ok { + //mount point check + volume := m + if !file.CheckNotExist(m) { + for i := 0; file.CheckNotExist(volume); i++ { + volume = m + strconv.Itoa(i+1) + } + } + service.MyService.Disk().MountDisk(h.Path, volume) + if volume != m { + ms := model2.SerialDisk{} + ms.Serial = v.Serial + service.MyService.Disk().UpdateMountPoint(ms) + } + + } + } } } } - - //remount - for _, item := range dbList { - if _, ok := mountPoint[item.MountPoint]; !ok { - service.MyService.Disk().MountDisk(item.Path, item.MountPoint) - } - } + service.MyService.Disk().RemoveLSBLKCache() + command.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AutoRemoveUnuseDir") } func Update2_3() { diff --git a/route/route.go b/route/route.go index db0c19e..26e4a96 100644 --- a/route/route.go +++ b/route/route.go @@ -218,17 +218,23 @@ func InitRouter() *gin.Engine { v1DiskGroup.Use() { v1DiskGroup.GET("/check", v1.GetDiskCheck) - //获取磁盘列表 - v1DiskGroup.GET("/list", v1.GetPlugInDisk) + + v1DiskGroup.GET("/list", v1.GetDiskList) //获取磁盘详情 v1DiskGroup.GET("/info", v1.GetDiskInfo) - //格式化磁盘 + //format storage v1DiskGroup.POST("/format", v1.FormatDisk) - //添加分区 - v1DiskGroup.POST("/part", v1.AddPartition) + // add storage + v1DiskGroup.POST("/storage", v1.AddPartition) + + //mount SATA disk + v1DiskGroup.POST("/mount", v1.PostMountDisk) + + //umount sata disk + v1DiskGroup.POST("/umount", v1.PostDiskUmount) //获取可以格式化的内容 v1DiskGroup.GET("/type", v1.FormatDiskType) @@ -236,12 +242,6 @@ func InitRouter() *gin.Engine { //删除分区 v1DiskGroup.DELETE("/delpart", v1.RemovePartition) - //mount SATA disk - v1DiskGroup.POST("/mount", v1.PostMountDisk) - - //umount SATA disk - v1DiskGroup.POST("/umount", v1.PostDiskUmount) - v1DiskGroup.DELETE("/remove/:id", v1.DeleteDisk) } v1ShareGroup := v1Group.Group("/share") v1ShareGroup.Use() diff --git a/route/v1/app.go b/route/v1/app.go index 9fe0597..6776742 100644 --- a/route/v1/app.go +++ b/route/v1/app.go @@ -36,24 +36,24 @@ func AppList(c *gin.Context) { categoryId := c.DefaultQuery("category_id", "0") key := c.DefaultQuery("key", "") recommend, list, community := service.MyService.OAPI().GetServerList(index, size, t, categoryId, key) - for i := 0; i < len(recommend); i++ { - ct, _ := service.MyService.Docker().DockerListByImage(recommend[i].Image, recommend[i].ImageVersion) - if ct != nil { - list[i].State = ct.State - } - } - for i := 0; i < len(list); i++ { - ct, _ := service.MyService.Docker().DockerListByImage(list[i].Image, list[i].ImageVersion) - if ct != nil { - list[i].State = ct.State - } - } - for i := 0; i < len(community); i++ { - ct, _ := service.MyService.Docker().DockerListByImage(community[i].Image, community[i].ImageVersion) - if ct != nil { - list[i].State = ct.State - } - } + // for i := 0; i < len(recommend); i++ { + // ct, _ := service.MyService.Docker().DockerListByImage(recommend[i].Image, recommend[i].ImageVersion) + // if ct != nil { + // recommend[i].State = ct.State + // } + // } + // for i := 0; i < len(list); i++ { + // ct, _ := service.MyService.Docker().DockerListByImage(list[i].Image, list[i].ImageVersion) + // if ct != nil { + // list[i].State = ct.State + // } + // } + // for i := 0; i < len(community); i++ { + // ct, _ := service.MyService.Docker().DockerListByImage(community[i].Image, community[i].ImageVersion) + // if ct != nil { + // community[i].State = ct.State + // } + // } data := make(map[string]interface{}, 3) data["recommend"] = recommend data["list"] = list @@ -137,7 +137,7 @@ func AppUsageList(c *gin.Context) { func AppInfo(c *gin.Context) { id := c.Param("id") - info := service.MyService.OAPI().GetServerAppInfo(id) + info := service.MyService.OAPI().GetServerAppInfo(id, "") if info.NetworkModel != "host" { for i := 0; i < len(info.Ports); i++ { if p, _ := strconv.Atoi(info.Ports[i].ContainerPort); port2.IsPortAvailable(p, info.Ports[i].Protocol) { diff --git a/route/v1/disk.go b/route/v1/disk.go index 17df806..625dd2b 100644 --- a/route/v1/disk.go +++ b/route/v1/disk.go @@ -2,9 +2,13 @@ package v1 import ( "net/http" + "reflect" "strconv" + "strings" "github.com/IceWhaleTech/CasaOS/model" + "github.com/IceWhaleTech/CasaOS/pkg/config" + "github.com/IceWhaleTech/CasaOS/pkg/utils/file" "github.com/IceWhaleTech/CasaOS/pkg/utils/oasis_err" "github.com/IceWhaleTech/CasaOS/service" model2 "github.com/IceWhaleTech/CasaOS/service/model" @@ -12,18 +16,53 @@ import ( "github.com/shirou/gopsutil/v3/disk" ) -// @Summary 获取磁盘列表 +var diskMap = make(map[string]string) + +// @Summary disk list // @Produce application/json // @Accept application/json // @Tags disk // @Security ApiKeyAuth // @Success 200 {string} string "ok" // @Router /disk/list [get] -func GetPlugInDisk(c *gin.Context) { - +func GetDiskList(c *gin.Context) { list := service.MyService.Disk().LSBLK() + newList := []model.LSBLKModel{} + for i := len(list) - 1; i >= 0; i-- { + if list[i].Rota { + list[i].DiskType = "HDD" + } else { + list[i].DiskType = "SSD" + } + if list[i].Tran == "sata" { + temp := service.MyService.Disk().SmartCTL(list[i].Path) - c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: list}) + if reflect.DeepEqual(temp, model.SmartctlA{}) { + continue + } + if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 { + pathArr := strings.Split(list[i].Children[0].MountPoint, "/") + if len(pathArr) == 3 { + list[i].Children[0].Name = pathArr[2] + } + } + + list[i].Temperature = temp.Temperature.Current + list[i].Health = strconv.FormatBool(temp.SmartStatus.Passed) + + newList = append(newList, list[i]) + } else if len(list[i].Children) > 0 && list[i].Children[0].MountPoint == "/" { + //system + list[i].Children[0].Name = "System" + list[i].Model = "System" + list[i].DiskType = "EMMC" + list[i].Health = "true" + newList = append(newList, list[i]) + + } + } + + c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: newList}) } // @Summary get disk list @@ -60,25 +99,46 @@ func GetDiskInfo(c *gin.Context) { c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: m}) } -// @Summary format disk +// @Summary format storage // @Produce application/json // @Accept multipart/form-data // @Tags disk // @Security ApiKeyAuth -// @Param path formData string true "for example /dev/sda1" +// @Param path formData string true "e.g. /dev/sda1" +// @Param pwd formData string true "user password" +// @Param volume formData string true "mount point" // @Success 200 {string} string "ok" // @Router /disk/format [post] func FormatDisk(c *gin.Context) { path := c.PostForm("path") + t := "ext4" + pwd := c.PostForm("pwd") + volume := c.PostForm("volume") - t := c.PostForm("type") + if pwd != config.UserInfo.PWD { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.PWD_INVALID, Message: oasis_err.GetMsg(oasis_err.PWD_INVALID)}) + return + } if len(path) == 0 || len(t) == 0 { c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)}) return } - service.MyService.Disk().FormatDisk(path, t) - + if _, ok := diskMap[path]; ok { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_BUSYING, Message: oasis_err.GetMsg(oasis_err.DISK_BUSYING)}) + return + } + diskMap[path] = "busying" + service.MyService.Disk().UmountPointAndRemoveDir(path) + format := service.MyService.Disk().FormatDisk(path, t) + if len(format) == 0 { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.FORMAT_ERROR, Message: oasis_err.GetMsg(oasis_err.FORMAT_ERROR)}) + delete(diskMap, path) + return + } + service.MyService.Disk().MountDisk(path, volume) + service.MyService.Disk().RemoveLSBLKCache() + delete(diskMap, path) c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)}) } @@ -115,23 +175,64 @@ func RemovePartition(c *gin.Context) { c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)}) } -// @Summary serial number +// @Summary add storage // @Produce application/json // @Accept multipart/form-data // @Tags disk // @Security ApiKeyAuth -// @Param path formData string true "磁盘路径 例如/dev/sda" +// @Param path formData string true "disk path e.g. /dev/sda" // @Param serial formData string true "serial" +// @Param name formData string true "name" +// @Param format formData bool true "need format(true)" // @Success 200 {string} string "ok" -// @Router /disk/addpart [post] +// @Router /disk/storage [post] func AddPartition(c *gin.Context) { + name := c.PostForm("name") path := c.PostForm("path") serial := c.PostForm("serial") - if len(path) == 0 || len(serial) == 0 { + format, _ := strconv.ParseBool(c.PostForm("format")) + if len(name) == 0 || len(path) == 0 || len(serial) == 0 { c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)}) return } - service.MyService.Disk().AddPartition(path) + if _, ok := diskMap[serial]; ok { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_BUSYING, Message: oasis_err.GetMsg(oasis_err.DISK_BUSYING)}) + return + } + if !file.CheckNotExist("/mnt/" + name) { + // /mnt/name exist + c.JSON(http.StatusOK, model.Result{Success: oasis_err.NAME_NOT_AVAILABLE, Message: oasis_err.GetMsg(oasis_err.NAME_NOT_AVAILABLE)}) + return + } + diskMap[serial] = "busying" + currentDisk := service.MyService.Disk().GetDiskInfo(path) + if !format { + if len(currentDisk.Children) != 1 || !(len(currentDisk.Children) > 0 && currentDisk.Children[0].FsType == "ext4") { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_NEEDS_FORMAT, Message: oasis_err.GetMsg(oasis_err.DISK_NEEDS_FORMAT)}) + delete(diskMap, serial) + return + } + } else { + service.MyService.Disk().AddPartition(path) + } + + mountPath := "/mnt/" + name + + service.MyService.Disk().MountDisk(path, mountPath) + + m := model2.SerialDisk{} + m.MountPoint = mountPath + m.Path = path + "1" + m.Serial = serial + m.State = 0 + service.MyService.Disk().SaveMountPoint(m) + + //mount dir + service.MyService.Disk().MountDisk(path+"1", mountPath) + + service.MyService.Disk().RemoveLSBLKCache() + + delete(diskMap, serial) c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)}) } @@ -165,13 +266,13 @@ func PostMountDisk(c *gin.Context) { //mount dir service.MyService.Disk().MountDisk(path, mountPath) - //save to data + m := model2.SerialDisk{} m.MountPoint = mountPath m.Path = path m.Serial = serial m.State = 0 - service.MyService.Disk().SaveMountPoint(m) + //service.MyService.Disk().SaveMountPoint(m) c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)}) } @@ -180,19 +281,35 @@ func PostMountDisk(c *gin.Context) { // @Accept multipart/form-data // @Tags disk // @Security ApiKeyAuth -// @Param path formData string true "for example: /dev/sda1" -// @Param mount_point formData string true "for example: /mnt/volume1" +// @Param path formData string true "e.g. /dev/sda1" +// @Param mount_point formData string true "e.g. /mnt/volume1" +// @Param pwd formData string true "user password" // @Success 200 {string} string "ok" // @Router /disk/umount [post] func PostDiskUmount(c *gin.Context) { - // path := c.PostForm("path") - mountPoint := c.PostForm("mount_point") - service.MyService.Disk().UmountPointAndRemoveDir(path) + mountPoint := c.PostForm("volume") + pwd := c.PostForm("pwd") + if len(path) == 0 || len(mountPoint) == 0 { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.INVALID_PARAMS, Message: oasis_err.GetMsg(oasis_err.INVALID_PARAMS)}) + return + } + if pwd != config.UserInfo.PWD { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.PWD_INVALID, Message: oasis_err.GetMsg(oasis_err.PWD_INVALID)}) + return + } + + if _, ok := diskMap[path]; ok { + c.JSON(http.StatusOK, model.Result{Success: oasis_err.DISK_BUSYING, Message: oasis_err.GetMsg(oasis_err.DISK_BUSYING)}) + return + } + + service.MyService.Disk().UmountPointAndRemoveDir(path) //delete data service.MyService.Disk().DeleteMountPoint(path, mountPoint) + service.MyService.Disk().RemoveLSBLKCache() c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)}) } diff --git a/route/v1/docker.go b/route/v1/docker.go index 241a6bd..a4e4d09 100644 --- a/route/v1/docker.go +++ b/route/v1/docker.go @@ -174,7 +174,7 @@ func InstallApp(c *gin.Context) { dockerImageVersion = "latest" } if m.Origin != "custom" { - appInfo = service.MyService.OAPI().GetServerAppInfo(appId) + appInfo = service.MyService.OAPI().GetServerAppInfo(appId, "") } else { diff --git a/route/v1/system.go b/route/v1/system.go index 9f09c0d..43a1584 100644 --- a/route/v1/system.go +++ b/route/v1/system.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "os" + "reflect" "strconv" "strings" "time" @@ -107,9 +108,33 @@ func GetSystemConfigDebug(c *gin.Context) { array := service.MyService.System().GetSystemConfigDebug() disk := service.MyService.ZiMa().GetDiskInfo() - array = append(array, fmt.Sprintf("disk,total:%v,used:%v,UsedPercent:%v", disk.Total>>20, disk.Used>>20, disk.UsedPercent)) + sys := service.MyService.ZiMa().GetSysInfo() + //todo 准备sync需要显示的数据(镜像,容器) + var systemAppStatus string + images := service.MyService.Docker().IsExistImage("linuxserver/syncthing") + systemAppStatus += "Sync img: " + strconv.FormatBool(images) + "\n\t" - c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: array}) + list := service.MyService.App().GetSystemAppList() + for _, v := range *list { + systemAppStatus += v.Image + ",\n\t" + } + systemAppStatus += "Sync Key: " + config.SystemConfigInfo.SyncKey + "\n\t" + + var bugContent string = fmt.Sprintf(` + **Desktop (please complete the following information):** + - OS: %s + - CasaOS Version: %s + - Disk Total: %v + - Disk Used: %v + - Sync State: %s + - System Info: %s + - Browser: $Browser$ + - Version: $Version$ +`, sys.OS, types.CURRENTVERSION, disk.Total>>20, disk.Used>>20, systemAppStatus, array) + + // array = append(array, fmt.Sprintf("disk,total:%v,used:%v,UsedPercent:%v", disk.Total>>20, disk.Used>>20, disk.UsedPercent)) + + c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: bugContent}) } func Sys(c *gin.Context) { service.DockerPull() @@ -237,7 +262,42 @@ func Info(c *gin.Context) { var data = make(map[string]interface{}, 5) list := service.MyService.Disk().LSBLK() - data["disk"] = list + + newList := []model.LSBLKModel{} + for i := len(list) - 1; i >= 0; i-- { + if list[i].Rota { + list[i].DiskType = "HDD" + } else { + list[i].DiskType = "SSD" + } + if list[i].Tran == "sata" { + + temp := service.MyService.Disk().SmartCTL(list[i].Path) + if reflect.DeepEqual(temp, model.SmartctlA{}) { + continue + } + if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 { + pathArr := strings.Split(list[i].Children[0].MountPoint, "/") + if len(pathArr) == 3 { + list[i].Children[0].Name = pathArr[2] + } + } + + list[i].Temperature = temp.Temperature.Current + list[i].Health = strconv.FormatBool(temp.SmartStatus.Passed) + newList = append(newList, list[i]) + } else if len(list[i].Children) > 0 && list[i].Children[0].MountPoint == "/" { + //system + list[i].Children[0].Name = "System" + list[i].Model = "System" + list[i].DiskType = "EMMC" + list[i].Health = "true" + newList = append(newList, list[i]) + + } + } + + data["disk"] = newList cpu := service.MyService.ZiMa().GetCpuPercent() num := service.MyService.ZiMa().GetCpuCoreNum() cpuData := make(map[string]interface{}) diff --git a/service/casa.go b/service/casa.go index 2768433..2fcbcf6 100644 --- a/service/casa.go +++ b/service/casa.go @@ -16,7 +16,7 @@ type CasaService interface { GetServerList(index, size, tp, categoryId, key string) (recommend, list, community []model.ServerAppList) GetServerCategoryList() []model.ServerCategoryList GetTaskList(size int) []model2.TaskDBModel - GetServerAppInfo(id string) model.ServerAppList + GetServerAppInfo(id, t string) model.ServerAppList ShareAppFile(body []byte) string } @@ -88,13 +88,12 @@ func (o *casaService) GetServerCategoryList() []model.ServerCategoryList { return list } -func (o *casaService) GetServerAppInfo(id string) model.ServerAppList { +func (o *casaService) GetServerAppInfo(id, t string) model.ServerAppList { head := make(map[string]string) head["Authorization"] = GetToken() - - infoS := httper2.Get(config.ServerInfo.ServerApi+"/v2/app/info/"+id, head) + infoS := httper2.Get(config.ServerInfo.ServerApi+"/v2/app/info/"+id+"?t="+t, head) info := model.ServerAppList{} json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info) diff --git a/service/disk.go b/service/disk.go index 829c818..ea4f147 100644 --- a/service/disk.go +++ b/service/disk.go @@ -3,6 +3,7 @@ package service import ( json2 "encoding/json" "fmt" + "reflect" "strconv" "strings" "time" @@ -20,8 +21,9 @@ import ( type DiskService interface { GetPlugInDisk() []string LSBLK() []model.LSBLKModel - FormatDisk(path, format string) string - UmountPointAndRemoveDir(path string) string + SmartCTL(path string) model.SmartctlA + FormatDisk(path, format string) []string + UmountPointAndRemoveDir(path string) []string GetDiskInfo(path string) model.LSBLKModel DelPartition(path, num string) string AddPartition(path string) string @@ -31,30 +33,60 @@ type DiskService interface { SaveMountPoint(m model2.SerialDisk) DeleteMountPoint(path, mountPoint string) DeleteMount(id string) + UpdateMountPoint(m model2.SerialDisk) + RemoveLSBLKCache() } type diskService struct { log loger2.OLog db *gorm.DB } +func (d *diskService) RemoveLSBLKCache() { + key := "system_lsblk" + Cache.Delete(key) +} +func (d *diskService) SmartCTL(path string) model.SmartctlA { + + key := "system_smart_" + path + if result, ok := Cache.Get(key); ok { + + res, ok := result.(model.SmartctlA) + if ok { + return res + } + } + var m model.SmartctlA + str := command2.ExecSmartCTLByPath(path) + if str == nil { + d.log.Error("smartctl exec error,smartctl") + return m + } + + err := json2.Unmarshal([]byte(str), &m) + if err != nil { + d.log.Error("json ummarshal error", err) + } + if !reflect.DeepEqual(m, model.SmartctlA{}) { + Cache.Add(key, m, time.Second*10) + } + return m +} + //通过脚本获取外挂磁盘 func (d *diskService) GetPlugInDisk() []string { return command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetPlugInDisk") } //格式化硬盘 -func (d *diskService) FormatDisk(path, format string) string { - +func (d *diskService) FormatDisk(path, format string) []string { r := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;FormatDisk " + path + " " + format) - fmt.Println(r) - return "" + return r } //移除挂载点,删除目录 -func (d *diskService) UmountPointAndRemoveDir(path string) string { +func (d *diskService) UmountPointAndRemoveDir(path string) []string { r := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;UMountPorintAndRemoveDir " + path) - fmt.Println(r) - return "" + return r } //删除分区 @@ -66,8 +98,7 @@ func (d *diskService) DelPartition(path, num string) string { //part func (d *diskService) AddPartition(path string) string { - r := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AddPartition " + path) - fmt.Println(r) + command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AddPartition " + path) return "" } @@ -78,11 +109,10 @@ func (d *diskService) AddAllPartition(path string) { //获取硬盘详情 func (d *diskService) GetDiskInfoByPath(path string) *disk.UsageStat { diskInfo, err := disk.Usage(path + "1") + if err != nil { fmt.Println(err) } - fmt.Println(path) - fmt.Println(diskInfo) diskInfo.UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.UsedPercent), 64) diskInfo.InodesUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.InodesUsedPercent), 64) return diskInfo @@ -91,7 +121,6 @@ func (d *diskService) GetDiskInfoByPath(path string) *disk.UsageStat { //get disk details func (d *diskService) LSBLK() []model.LSBLKModel { key := "system_lsblk" - var n []model.LSBLKModel if result, ok := Cache.Get(key); ok { @@ -151,7 +180,7 @@ func (d *diskService) LSBLK() []model.LSBLKModel { } } if len(n) > 0 { - Cache.Add(key, n, time.Second*10) + Cache.Add(key, n, time.Second*100) } return n } @@ -162,6 +191,7 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel { d.log.Error("lsblk exec error,str") return model.LSBLKModel{} } + var ml []model.LSBLKModel err := json2.Unmarshal([]byte(gjson.Get(string(str), "blockdevices").String()), &ml) if err != nil { @@ -169,9 +199,13 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel { d.log.Error("json ummarshal error", err) return model.LSBLKModel{} } - //todo 需要判断长度 - m := ml[0] - //声明数组 + + m := model.LSBLKModel{} + if len(ml) > 0 { + m = ml[0] + } + return m + // 下面为计算是否可以继续分区的部分,暂时不需要 chiArr := make(map[string]string) chiList := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetPartitionSectors " + m.Path) if len(chiList) == 0 { @@ -182,7 +216,6 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel { tempArr := strings.Split(chiList[i], ",") chiArr[tempArr[0]] = chiList[i] } - var maxSector uint64 = 0 for i := 0; i < len(m.Children); i++ { tempArr := strings.Split(chiArr[m.Children[i].Path], ",") @@ -191,13 +224,13 @@ func (d *diskService) GetDiskInfo(path string) model.LSBLKModel { if m.Children[i].EndSector > maxSector { maxSector = m.Children[i].EndSector } + } diskEndSector := command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetDiskSizeAndSectors " + m.Path) if len(diskEndSector) < 2 { d.log.Error("diskEndSector length error") } - diskEndSectorInt, _ := strconv.ParseUint(diskEndSector[len(diskEndSector)-1], 10, 64) if (diskEndSectorInt-maxSector)*m.MinIO/1024/1024 > 100 { //添加可以分区情况 @@ -214,9 +247,14 @@ func (d *diskService) MountDisk(path, volume string) { } func (d *diskService) SaveMountPoint(m model2.SerialDisk) { + d.db.Where("serial = ?", m.Serial).Delete(&model2.SerialDisk{}) d.db.Create(&m) } +func (d *diskService) UpdateMountPoint(m model2.SerialDisk) { + d.db.Model(&model2.SerialDisk{}).Where("serial = ?", m.Serial).Update("mount_point", m.MountPoint) +} + func (d *diskService) DeleteMount(id string) { d.db.Delete(&model2.SerialDisk{}).Where("id = ?", id) @@ -224,7 +262,7 @@ func (d *diskService) DeleteMount(id string) { func (d *diskService) DeleteMountPoint(path, mountPoint string) { - d.db.Delete(&model2.SerialDisk{}).Where("path= ? && mount_point = ?", path, mountPoint) + d.db.Where("path = ? AND mount_point = ?", path, mountPoint).Delete(&model2.SerialDisk{}) command2.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;do_umount " + path) } diff --git a/shell/assist.sh b/shell/assist.sh index 2493931..f9d1964 100644 --- a/shell/assist.sh +++ b/shell/assist.sh @@ -9,4 +9,33 @@ version_0_2_3() { } +# add in v0.2.5 +readonly CASA_DEPANDS="curl smartmontools" +version_0_2_5{ + install_depends "$CASA_DEPANDS" +} + + +#Install Depends +install_depends() { + ((EUID)) && sudo_cmd="sudo" + if [[ ! -x "$(command -v '$1')" ]]; then + show 2 "Install the necessary dependencies: $1" + packagesNeeded=$1 + if [ -x "$(command -v apk)" ]; then + $sudo_cmd apk add --no-cache $packagesNeeded + elif [ -x "$(command -v apt-get)" ]; then + $sudo_cmd apt-get -y -q install $packagesNeeded + elif [ -x "$(command -v dnf)" ]; then + $sudo_cmd dnf install $packagesNeeded + elif [ -x "$(command -v zypper)" ]; then + $sudo_cmd zypper install $packagesNeeded + else + show 1 "Package manager not found. You must manually install: $packagesNeeded" + fi + fi +} + version_0_2_3 + +version_0_2_5 diff --git a/shell/helper.sh b/shell/helper.sh index f072b99..38ef73b 100644 --- a/shell/helper.sh +++ b/shell/helper.sh @@ -73,10 +73,8 @@ UMountPorintAndRemoveDir() { if [[ -z ${MOUNT_POINT} ]]; then ${log} "Warning: ${DEVICE} is not mounted" else - umount -l ${DEVICE} - ${log} "Unmounted ${DEVICE} from ${MOUNT_POINT}" + umount -lf ${DEVICE} /bin/rmdir "${MOUNT_POINT}" - sed -i.bak "\@${MOUNT_POINT}@d" /var/log/usb-mount.track fi } @@ -89,11 +87,11 @@ FormatDisk() { elif [ "$2" == "ntfs" ]; then mkfs.ntfs $1 elif [ "$2" == "ext4" ]; then - mkfs.ext4 -F $1 + mkfs.ext4 -m 1 -F $1 elif [ "$2" == "exfat" ]; then mkfs.exfat $1 else - mkfs.ext4 -F $1 + mkfs.ext4 -m 1 -F $1 fi } @@ -118,12 +116,10 @@ AddPartition() { parted -s $1 mkpart primary ext4 0 100% - mkfs.ext4 $11 + mkfs.ext4 -m 1 $11 partprobe $1 - # mount $11 $2 - } #磁盘类型 @@ -148,14 +144,14 @@ GetDiskHealthState() { #result bytes #result sectors GetDiskSizeAndSectors() { - fdisk $1 -l | grep "/dev/sda:" | awk -F, 'BEGIN {OFS="\n"}{print $2,$3}' | awk '{print $1}' + fdisk $1 -l | grep "$1:" | awk -F, 'BEGIN {OFS="\n"}{print $2,$3}' | awk '{print $1}' } #获取磁盘分区数据扇区 #param 磁盘路径 /dev/sda #result start,end,sectors GetPartitionSectors() { - fdisk $1 -l | grep "/dev/sda[1-9]" | awk 'BEGIN{OFS=","}{print $1,$2,$3,$4}' + fdisk $1 -l | grep "$1[1-9]" | awk 'BEGIN{OFS=","}{print $1,$2,$3,$4}' } #检查没有使用的挂载点删除文件夹 diff --git a/types/system.go b/types/system.go index fc30d4d..489cea9 100644 --- a/types/system.go +++ b/types/system.go @@ -1,4 +1,4 @@ package types -const CURRENTVERSION = "0.2.4" -const BODY = "