Tiger Wang 2 years ago
parent
commit
ff6cdb6fda
6 changed files with 170 additions and 122 deletions
  1. 4 2
      model/docker.go
  2. 36 36
      pkg/docker/helper.go
  3. 7 6
      pkg/utils/command/command_helper.go
  4. 23 16
      pkg/utils/file/file.go
  5. 31 32
      route/route.go
  6. 69 30
      route/v1/app.go

+ 4 - 2
model/docker.go

@@ -17,6 +17,8 @@ type DockerStatsModel struct {
 	Previous interface{} `json:"previous"`
 }
 
-type DeckerDaemonModel struct {
-	Graph string `json:"graph"`
+// reference - https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
+type DockerDaemonConfigurationModel struct {
+	// e.g. `/var/lib/docker`
+	Root string `json:"data-root,omitempty"`
 }

+ 36 - 36
pkg/docker/helper.go

@@ -15,7 +15,6 @@ import (
 )
 
 func NewSshClient(user, password string, port string) (*ssh.Client, error) {
-
 	// connet to ssh
 	// addr = fmt.Sprintf("%s:%d", host, port)
 
@@ -23,10 +22,10 @@ func NewSshClient(user, password string, port string) (*ssh.Client, error) {
 		Timeout:         time.Second * 5,
 		User:            user,
 		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
-		//HostKeyCallback: ,
-		//HostKeyCallback: hostKeyCallBackFunc(h.Host),
+		// HostKeyCallback: ,
+		// HostKeyCallback: hostKeyCallBackFunc(h.Host),
 	}
-	//if h.Type == "password" {
+	// if h.Type == "password" {
 	config.Auth = []ssh.AuthMethod{ssh.Password(password)}
 	//} else {
 	//	config.Auth = []ssh.AuthMethod{publicKeyAuthFunc(h.Key)}
@@ -90,11 +89,11 @@ func (w *wsBufferWriter) Write(p []byte) (int, error) {
 	defer w.mu.Unlock()
 	return w.buffer.Write(p)
 }
+
 func (s *SshConn) Close() {
 	if s.Session != nil {
 		s.Session.Close()
 	}
-
 }
 
 const (
@@ -102,16 +101,15 @@ const (
 	wsMsgResize = "resize"
 )
 
-//ReceiveWsMsg  receive websocket msg do some handling then write into ssh.session.stdin
+// ReceiveWsMsg  receive websocket msg do some handling then write into ssh.session.stdin
 func ReceiveWsMsgUser(wsConn *websocket.Conn, logBuff *bytes.Buffer) string {
-	//tells other go routine quit
+	// tells other go routine quit
 	username := ""
 	for {
 
-		//read websocket msg
+		// read websocket msg
 		_, wsData, err := wsConn.ReadMessage()
 		if err != nil {
-
 			return ""
 		}
 
@@ -125,8 +123,8 @@ func ReceiveWsMsgUser(wsConn *websocket.Conn, logBuff *bytes.Buffer) string {
 		//}
 		switch msgObj.Type {
 		case wsMsgCmd:
-			//handle xterm.js stdin
-			//decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
+			// handle xterm.js stdin
+			// decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
 			decodeBytes := []byte(msgObj.Cmd)
 			if msgObj.Cmd == "\u007f" {
 				if len(username) == 0 {
@@ -144,7 +142,7 @@ func ReceiveWsMsgUser(wsConn *websocket.Conn, logBuff *bytes.Buffer) string {
 			if err := wsConn.WriteMessage(websocket.TextMessage, decodeBytes); err != nil {
 				logrus.WithError(err).Error("ws cmd bytes write to ssh.stdin pipe failed")
 			}
-			//write input cmd to log buffer
+			// write input cmd to log buffer
 			if _, err := logBuff.Write(decodeBytes); err != nil {
 				logrus.WithError(err).Error("write received cmd into log buffer failed")
 			}
@@ -154,11 +152,11 @@ func ReceiveWsMsgUser(wsConn *websocket.Conn, logBuff *bytes.Buffer) string {
 }
 
 func ReceiveWsMsgPassword(wsConn *websocket.Conn, logBuff *bytes.Buffer) string {
-	//tells other go routine quit
+	// tells other go routine quit
 	password := ""
 	for {
 
-		//read websocket msg
+		// read websocket msg
 		_, wsData, err := wsConn.ReadMessage()
 		if err != nil {
 			logrus.WithError(err).Error("reading webSocket message failed")
@@ -175,8 +173,8 @@ func ReceiveWsMsgPassword(wsConn *websocket.Conn, logBuff *bytes.Buffer) string
 		//}
 		switch msgObj.Type {
 		case wsMsgCmd:
-			//handle xterm.js stdin
-			//decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
+			// handle xterm.js stdin
+			// decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
 			if msgObj.Cmd == "\r" {
 				return password
 			}
@@ -194,16 +192,16 @@ func ReceiveWsMsgPassword(wsConn *websocket.Conn, logBuff *bytes.Buffer) string
 	}
 }
 
-//ReceiveWsMsg  receive websocket msg do some handling then write into ssh.session.stdin
+// ReceiveWsMsg  receive websocket msg do some handling then write into ssh.session.stdin
 func (ssConn *SshConn) ReceiveWsMsg(wsConn *websocket.Conn, logBuff *bytes.Buffer, exitCh chan bool) {
-	//tells other go routine quit
+	// tells other go routine quit
 	defer setQuit(exitCh)
 	for {
 		select {
 		case <-exitCh:
 			return
 		default:
-			//read websocket msg
+			// read websocket msg
 			_, wsData, err := wsConn.ReadMessage()
 			if err != nil {
 				logrus.WithError(err).Error("reading webSocket message failed")
@@ -227,15 +225,15 @@ func (ssConn *SshConn) ReceiveWsMsg(wsConn *websocket.Conn, logBuff *bytes.Buffe
 			switch msgObj.Type {
 
 			case wsMsgResize:
-				//handle xterm.js size change
+				// handle xterm.js size change
 				if msgObj.Cols > 0 && msgObj.Rows > 0 {
 					if err := ssConn.Session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
 						logrus.WithError(err).Error("ssh pty change windows size failed")
 					}
 				}
 			case wsMsgCmd:
-				//handle xterm.js stdin
-				//decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
+				// handle xterm.js stdin
+				// decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
 				decodeBytes := []byte(msgObj.Cmd)
 				if err != nil {
 					logrus.WithError(err).Error("websock cmd string base64 decoding failed")
@@ -243,7 +241,7 @@ func (ssConn *SshConn) ReceiveWsMsg(wsConn *websocket.Conn, logBuff *bytes.Buffe
 				if _, err := ssConn.StdinPipe.Write(decodeBytes); err != nil {
 					logrus.WithError(err).Error("ws cmd bytes write to ssh.stdin pipe failed")
 				}
-				//write input cmd to log buffer
+				// write input cmd to log buffer
 				if _, err := logBuff.Write(decodeBytes); err != nil {
 					logrus.WithError(err).Error("write received cmd into log buffer failed")
 				}
@@ -253,17 +251,17 @@ func (ssConn *SshConn) ReceiveWsMsg(wsConn *websocket.Conn, logBuff *bytes.Buffe
 }
 
 func (ssConn *SshConn) SendComboOutput(wsConn *websocket.Conn, exitCh chan bool) {
-	//tells other go routine quit
-	//defer setQuit(exitCh)
+	// tells other go routine quit
+	// defer setQuit(exitCh)
 
-	//every 120ms write combine output bytes into websocket response
+	// every 120ms write combine output bytes into websocket response
 	tick := time.NewTicker(time.Millisecond * time.Duration(120))
-	//for range time.Tick(120 * time.Millisecond){}
+	// for range time.Tick(120 * time.Millisecond){}
 	defer tick.Stop()
 	for {
 		select {
 		case <-tick.C:
-			//write combine output bytes into websocket response
+			// write combine output bytes into websocket response
 			if err := flushComboOutput(ssConn.ComboOutput, wsConn); err != nil {
 				logrus.WithError(err).Error("ssh sending combo output to webSocket failed")
 				return
@@ -273,6 +271,7 @@ func (ssConn *SshConn) SendComboOutput(wsConn *websocket.Conn, exitCh chan bool)
 		}
 	}
 }
+
 func flushComboOutput(w *wsBufferWriter, wsConn *websocket.Conn) error {
 	if w.buffer.Len() != 0 {
 		err := wsConn.WriteMessage(websocket.TextMessage, w.buffer.Bytes())
@@ -284,16 +283,16 @@ func flushComboOutput(w *wsBufferWriter, wsConn *websocket.Conn) error {
 	return nil
 }
 
-//ReceiveWsMsg  receive websocket msg do some handling then write into ssh.session.stdin
+// ReceiveWsMsg  receive websocket msg do some handling then write into ssh.session.stdin
 func (ssConn *SshConn) Login(wsConn *websocket.Conn, logBuff *bytes.Buffer, exitCh chan bool) {
-	//tells other go routine quit
+	// tells other go routine quit
 	defer setQuit(exitCh)
 	for {
 		select {
 		case <-exitCh:
 			return
 		default:
-			//read websocket msg
+			// read websocket msg
 			_, wsData, err := wsConn.ReadMessage()
 			if err != nil {
 				logrus.WithError(err).Error("reading webSocket message failed")
@@ -317,15 +316,15 @@ func (ssConn *SshConn) Login(wsConn *websocket.Conn, logBuff *bytes.Buffer, exit
 			switch msgObj.Type {
 
 			case wsMsgResize:
-				//handle xterm.js size change
+				// handle xterm.js size change
 				if msgObj.Cols > 0 && msgObj.Rows > 0 {
 					if err := ssConn.Session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
 						logrus.WithError(err).Error("ssh pty change windows size failed")
 					}
 				}
 			case wsMsgCmd:
-				//handle xterm.js stdin
-				//decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
+				// handle xterm.js stdin
+				// decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
 				decodeBytes := []byte(msgObj.Cmd)
 				if err != nil {
 					logrus.WithError(err).Error("websock cmd string base64 decoding failed")
@@ -333,7 +332,7 @@ func (ssConn *SshConn) Login(wsConn *websocket.Conn, logBuff *bytes.Buffer, exit
 				if _, err := ssConn.StdinPipe.Write(decodeBytes); err != nil {
 					logrus.WithError(err).Error("ws cmd bytes write to ssh.stdin pipe failed")
 				}
-				//write input cmd to log buffer
+				// write input cmd to log buffer
 				if _, err := logBuff.Write(decodeBytes); err != nil {
 					logrus.WithError(err).Error("write received cmd into log buffer failed")
 				}
@@ -341,6 +340,7 @@ func (ssConn *SshConn) Login(wsConn *websocket.Conn, logBuff *bytes.Buffer, exit
 		}
 	}
 }
+
 func (ssConn *SshConn) SessionWait(quitChan chan bool) {
 	if err := ssConn.Session.Wait(); err != nil {
 		logrus.WithError(err).Error("ssh session wait failed")
@@ -395,7 +395,7 @@ func WsReaderCopy(reader *websocket.Conn, writer io.Writer) {
 			if err = json2.Unmarshal(p, &msgObj); err != nil {
 				writer.Write(p)
 			} else if msgObj.Type == wsMsgResize {
-				//writer.Write([]byte("stty rows " + strconv.Itoa(msgObj.Rows) + " && stty cols " + strconv.Itoa(msgObj.Cols) + " \r"))
+				// writer.Write([]byte("stty rows " + strconv.Itoa(msgObj.Rows) + " && stty cols " + strconv.Itoa(msgObj.Cols) + " \r"))
 			}
 		}
 	}

+ 7 - 6
pkg/utils/command/command_helper.go

@@ -35,8 +35,8 @@ func ExecResultStrArray(cmdStr string) []string {
 		fmt.Println(err)
 		return nil
 	}
-	//str, err := ioutil.ReadAll(stdout)
-	var networklist = []string{}
+	// str, err := ioutil.ReadAll(stdout)
+	networklist := []string{}
 	outputBuf := bufio.NewReader(stdout)
 	for {
 		output, _, err := outputBuf.ReadLine()
@@ -54,6 +54,8 @@ func ExecResultStrArray(cmdStr string) []string {
 
 func ExecResultStr(cmdStr string) string {
 	cmd := exec.Command("/bin/bash", "-c", cmdStr)
+	println(cmd.String())
+
 	stdout, err := cmd.StdoutPipe()
 	if err != nil {
 		fmt.Println(err)
@@ -73,7 +75,7 @@ func ExecResultStr(cmdStr string) string {
 	return string(str)
 }
 
-//执行 lsblk 命令
+// 执行 lsblk 命令
 func ExecLSBLK() []byte {
 	output, err := exec.Command("lsblk", "-O", "-J", "-b").Output()
 	if err != nil {
@@ -83,7 +85,7 @@ func ExecLSBLK() []byte {
 	return output
 }
 
-//执行 lsblk 命令
+// 执行 lsblk 命令
 func ExecLSBLKByPath(path string) []byte {
 	output, err := exec.Command("lsblk", path, "-O", "-J", "-b").Output()
 	if err != nil {
@@ -93,7 +95,7 @@ func ExecLSBLKByPath(path string) []byte {
 	return output
 }
 
-//exec smart
+// exec smart
 func ExecSmartCTLByPath(path string) []byte {
 	timeout := 3
 	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
@@ -107,6 +109,5 @@ func ExecSmartCTLByPath(path string) []byte {
 }
 
 func ExecEnabledSMART(path string) {
-
 	exec.Command("smartctl", "-s on", path).Output()
 }

+ 23 - 16
pkg/utils/file/file.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"io/ioutil"
 	"log"
 	"mime/multipart"
@@ -60,7 +61,7 @@ func MkDir(src string) error {
 	if err != nil {
 		return err
 	}
-	os.Chmod(src, 0777)
+	os.Chmod(src, 0o777)
 
 	return nil
 }
@@ -103,7 +104,7 @@ func MustOpen(fileName, filePath string) (*os.File, error) {
 		return nil, fmt.Errorf("file.IsNotExistMkDir src: %s, err: %v", src, err)
 	}
 
-	f, err := Open(src+fileName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
+	f, err := Open(src+fileName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o644)
 	if err != nil {
 		return nil, fmt.Errorf("Fail to OpenFile :%v", err)
 	}
@@ -113,7 +114,7 @@ func MustOpen(fileName, filePath string) (*os.File, error) {
 
 // 判断所给路径文件/文件夹是否存在
 func Exists(path string) bool {
-	_, err := os.Stat(path) //os.Stat获取文件信息
+	_, err := os.Stat(path) // os.Stat获取文件信息
 	if err != nil {
 		if os.IsExist(err) {
 			return true
@@ -147,7 +148,7 @@ func CreateFile(path string) error {
 }
 
 func CreateFileAndWriteContent(path string, content string) error {
-	file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
+	file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666)
 	if err != nil {
 		return err
 	}
@@ -163,7 +164,7 @@ func CreateFileAndWriteContent(path string, content string) error {
 
 // IsNotExistMkDir create a directory if it does not exist
 func IsNotExistCreateFile(src string) error {
-	if notExist := CheckNotExist(src); notExist == true {
+	if notExist := CheckNotExist(src); notExist {
 		if err := CreateFile(src); err != nil {
 			return err
 		}
@@ -267,7 +268,7 @@ func CopySingleFile(src, dst, style string) error {
 	return os.Chmod(dst, srcinfo.Mode())
 }
 
-//Check for duplicate file names
+// Check for duplicate file names
 func GetNoDuplicateFileName(fullPath string) string {
 	path, fileName := filepath.Split(fullPath)
 	fileSuffix := path2.Ext(fileName)
@@ -293,7 +294,7 @@ func CopyDir(src string, dst string, style string) error {
 		}
 		return nil
 	}
-	//dstPath := dst
+	// dstPath := dst
 	lastPath := src[strings.LastIndex(src, "/")+1:]
 	dst += "/" + lastPath
 	// for i := 0; Exists(dst); i++ {
@@ -314,7 +315,7 @@ func CopyDir(src string, dst string, style string) error {
 	}
 	for _, fd := range fds {
 		srcfp := path.Join(src, fd.Name())
-		dstfp := dst //path.Join(dst, fd.Name())
+		dstfp := dst // path.Join(dst, fd.Name())
 
 		if fd.IsDir() {
 			if err = CopyDir(srcfp, dstfp, style); err != nil {
@@ -336,10 +337,17 @@ func WriteToPath(data []byte, path, name string) error {
 	} else {
 		fullPath += "/" + name
 	}
-	IsNotExistCreateFile(fullPath)
+	return WriteToFullPath(data, fullPath, 0o666)
+}
+
+func WriteToFullPath(data []byte, fullPath string, perm fs.FileMode) error {
+	if err := IsNotExistCreateFile(fullPath); err != nil {
+		return err
+	}
+
 	file, err := os.OpenFile(fullPath,
 		os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
-		0666,
+		perm,
 	)
 	if err != nil {
 		return err
@@ -350,16 +358,15 @@ func WriteToPath(data []byte, path, name string) error {
 	return err
 }
 
-//最终拼接
+// 最终拼接
 func SpliceFiles(dir, path string, length int, startPoint int) error {
-
 	fullPath := path
 
 	IsNotExistCreateFile(fullPath)
 
 	file, _ := os.OpenFile(fullPath,
 		os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
-		0666,
+		0o666,
 	)
 	defer file.Close()
 	bufferedWriter := bufio.NewWriter(file)
@@ -380,7 +387,6 @@ func SpliceFiles(dir, path string, length int, startPoint int) error {
 }
 
 func GetCompressionAlgorithm(t string) (string, archiver.Writer, error) {
-
 	switch t {
 	case "zip", "":
 		return ".zip", archiver.NewZip(), nil
@@ -400,8 +406,8 @@ func GetCompressionAlgorithm(t string) (string, archiver.Writer, error) {
 		return "", nil, errors.New("format not implemented")
 	}
 }
-func AddFile(ar archiver.Writer, path, commonPath string) error {
 
+func AddFile(ar archiver.Writer, path, commonPath string) error {
 	info, err := os.Stat(path)
 	if err != nil {
 		return err
@@ -447,6 +453,7 @@ func AddFile(ar archiver.Writer, path, commonPath string) error {
 
 	return nil
 }
+
 func CommonPrefix(sep byte, paths ...string) string {
 	// Handle special cases.
 	switch len(paths) {
@@ -513,7 +520,7 @@ func GetFileOrDirSize(path string) (int64, error) {
 	return fileInfo.Size(), nil
 }
 
-//getFileSize get file size by path(B)
+// getFileSize get file size by path(B)
 func DirSizeB(path string) (int64, error) {
 	var size int64
 	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {

+ 31 - 32
route/route.go

@@ -11,7 +11,6 @@ import (
 )
 
 func InitRouter() *gin.Engine {
-
 	r := gin.Default()
 
 	r.Use(middleware.Cors())
@@ -35,7 +34,7 @@ func InitRouter() *gin.Engine {
 	// r.GET("/v1/users/image", v1.GetUserImage)
 
 	// r.GET("/v1/users/status", v1.GetUserStatus) //init/check
-	//r.GET("/v1/guide/check", v1.GetGuideCheck)         // /v1/sys/guide_check
+	// r.GET("/v1/guide/check", v1.GetGuideCheck)         // /v1/sys/guide_check
 	r.GET("/v1/sys/debug", v1.GetSystemConfigDebug) // //debug
 
 	r.GET("/v1/sys/socket-port", v1.GetSystemSocketPort) //sys/socket_port
@@ -73,7 +72,7 @@ func InitRouter() *gin.Engine {
 		v1AppsGroup := v1Group.Group("/apps")
 		v1AppsGroup.Use()
 		{
-			v1AppsGroup.GET("", v1.AppList) //list
+			v1AppsGroup.GET("", v1.AppList) // list
 			v1AppsGroup.GET("/:id", v1.AppInfo)
 		}
 		v1ContainerGroup := v1Group.Group("/container")
@@ -84,24 +83,24 @@ func InitRouter() *gin.Engine {
 			v1ContainerGroup.GET("/usage", v1.AppUsageList)
 			v1ContainerGroup.GET("/:id", v1.ContainerUpdateInfo)    ///update/:id/info
 			v1ContainerGroup.GET("/:id/logs", v1.ContainerLog)      // /app/logs/:id
-			v1ContainerGroup.GET("/networks", v1.GetDockerNetworks) //app/install/config
+			v1ContainerGroup.GET("/networks", v1.GetDockerNetworks) // app/install/config
 
-			v1ContainerGroup.GET("/:id/state", v1.GetContainerState) //app/state/:id ?state=install_progress
+			v1ContainerGroup.GET("/:id/state", v1.GetContainerState) // app/state/:id ?state=install_progress
 			// there are problems, temporarily do not deal with
-			v1ContainerGroup.GET("/:id/terminal", v1.DockerTerminal) //app/terminal/:id
-			v1ContainerGroup.POST("", v1.InstallApp)                 //app/install
-			//v1ContainerGroup.GET("/:id", v1.ContainerInfo) // /app/info/:id
+			v1ContainerGroup.GET("/:id/terminal", v1.DockerTerminal) // app/terminal/:id
+			v1ContainerGroup.POST("", v1.InstallApp)                 // app/install
+			// v1ContainerGroup.GET("/:id", v1.ContainerInfo) // /app/info/:id
 
 			v1ContainerGroup.PUT("/:id", v1.UpdateSetting) ///update/:id/setting
 
 			v1ContainerGroup.PUT("/:id/state", v1.ChangAppState) // /app/state/:id
-			v1ContainerGroup.DELETE("/:id", v1.UnInstallApp)     //app/uninstall/:id
-			//Not used
+			v1ContainerGroup.DELETE("/:id", v1.UnInstallApp)     // app/uninstall/:id
+			// Not used
 			v1ContainerGroup.PUT("/:id/latest", v1.PutAppUpdate)
-			//Not used
+			// Not used
 			v1ContainerGroup.POST("/share", v1.ShareAppFile)
-			v1ContainerGroup.GET("/info", v1.GetcontainerInfo)
-			v1ContainerGroup.PUT("/info", v1.PutcontainerInfo)
+			v1ContainerGroup.GET("/info", v1.GetDockerDaemonConfiguration)
+			v1ContainerGroup.PUT("/info", v1.PutDockerDaemonConfiguration)
 
 		}
 		v1AppCategoriesGroup := v1Group.Group("/app-categories")
@@ -113,19 +112,19 @@ func InitRouter() *gin.Engine {
 		v1SysGroup := v1Group.Group("/sys")
 		v1SysGroup.Use()
 		{
-			v1SysGroup.GET("/version", v1.GetSystemCheckVersion) //version/check
+			v1SysGroup.GET("/version", v1.GetSystemCheckVersion) // version/check
 
 			v1SysGroup.POST("/update", v1.SystemUpdate)
 
-			v1SysGroup.GET("/hardware", v1.GetSystemHardwareInfo) //hardware/info
+			v1SysGroup.GET("/hardware", v1.GetSystemHardwareInfo) // hardware/info
 
 			v1SysGroup.GET("/wsssh", v1.WsSsh)
 			v1SysGroup.POST("/ssh-login", v1.PostSshLogin)
-			//v1SysGroup.GET("/config", v1.GetSystemConfig) //delete
-			//v1SysGroup.POST("/config", v1.PostSetSystemConfig)
-			v1SysGroup.GET("/logs", v1.GetCasaOSErrorLogs) //error/logs
-			//v1SysGroup.GET("/widget/config", v1.GetWidgetConfig)//delete
-			//v1SysGroup.POST("/widget/config", v1.PostSetWidgetConfig)//delete
+			// v1SysGroup.GET("/config", v1.GetSystemConfig) //delete
+			// v1SysGroup.POST("/config", v1.PostSetSystemConfig)
+			v1SysGroup.GET("/logs", v1.GetCasaOSErrorLogs) // error/logs
+			// v1SysGroup.GET("/widget/config", v1.GetWidgetConfig)//delete
+			// v1SysGroup.POST("/widget/config", v1.PostSetWidgetConfig)//delete
 
 			v1SysGroup.POST("/stop", v1.PostKillCasaOS)
 
@@ -141,31 +140,31 @@ func InitRouter() *gin.Engine {
 			v1SysGroup.GET("/server-info", nil)
 			v1SysGroup.PUT("/server-info", nil)
 			v1SysGroup.GET("/apps-state", v1.GetSystemAppsStatus)
-			//v1SysGroup.GET("/port", v1.GetCasaOSPort)
-			//v1SysGroup.PUT("/port", v1.PutCasaOSPort)
+			// v1SysGroup.GET("/port", v1.GetCasaOSPort)
+			// v1SysGroup.PUT("/port", v1.PutCasaOSPort)
 			v1SysGroup.GET("/proxy", v1.GetSystemProxy)
 		}
 		v1PortGroup := v1Group.Group("/port")
 		v1PortGroup.Use()
 		{
-			v1PortGroup.GET("/", v1.GetPort)              //app/port
-			v1PortGroup.GET("/state/:port", v1.PortCheck) //app/check/:port
+			v1PortGroup.GET("/", v1.GetPort)              // app/port
+			v1PortGroup.GET("/state/:port", v1.PortCheck) // app/check/:port
 		}
 
 		v1FileGroup := v1Group.Group("/file")
 		v1FileGroup.Use()
 		{
-			v1FileGroup.GET("", v1.GetDownloadSingleFile) //download/:path
+			v1FileGroup.GET("", v1.GetDownloadSingleFile) // download/:path
 			v1FileGroup.POST("", v1.PostCreateFile)
 			v1FileGroup.PUT("", v1.PutFileContent)
 			v1FileGroup.PUT("/name", v1.RenamePath)
-			//file/rename
-			v1FileGroup.GET("/content", v1.GetFilerContent) //file/read
+			// file/rename
+			v1FileGroup.GET("/content", v1.GetFilerContent) // file/read
 
-			//File uploads need to be handled separately, and will not be modified here
+			// File uploads need to be handled separately, and will not be modified here
 			v1FileGroup.POST("/upload", v1.PostFileUpload)
 			v1FileGroup.GET("/upload", v1.GetFileUpload)
-			//v1FileGroup.GET("/download", v1.UserFileDownloadCommonService)
+			// v1FileGroup.GET("/download", v1.UserFileDownloadCommonService)
 		}
 		v1FolderGroup := v1Group.Group("/folder")
 		v1FolderGroup.Use()
@@ -178,9 +177,9 @@ func InitRouter() *gin.Engine {
 		v1BatchGroup.Use()
 		{
 
-			v1BatchGroup.DELETE("", v1.DeleteFile) //file/delete
+			v1BatchGroup.DELETE("", v1.DeleteFile) // file/delete
 			v1BatchGroup.DELETE("/:id/task", v1.DeleteOperateFileOrDir)
-			v1BatchGroup.POST("/task", v1.PostOperateFileOrDir) //file/operate
+			v1BatchGroup.POST("/task", v1.PostOperateFileOrDir) // file/operate
 			v1BatchGroup.GET("", v1.GetDownloadFile)
 		}
 		v1ImageGroup := v1Group.Group("/image")
@@ -211,7 +210,7 @@ func InitRouter() *gin.Engine {
 		v1NotifyGroup.Use()
 		{
 			v1NotifyGroup.POST("/:path", v1.PostNotifyMessage)
-			//merge to system
+			// merge to system
 			v1NotifyGroup.POST("/system_status", v1.PostSystemStatusNotify)
 		}
 	}

+ 69 - 30
route/v1/app.go

@@ -2,8 +2,9 @@ package v1
 
 import (
 	"encoding/json"
-	"fmt"
 	"io/ioutil"
+	"net/http"
+	"path/filepath"
 	"strconv"
 
 	"github.com/IceWhaleTech/CasaOS/model"
@@ -16,6 +17,10 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+const (
+	dockerDaemonConfigurationFilePath = "/etc/docker/daemon.json"
+)
+
 // @Summary 获取远程列表
 // @Produce  application/json
 // @Accept application/json
@@ -29,8 +34,7 @@ import (
 // @Success 200 {string} string "ok"
 // @Router /app/list [get]
 func AppList(c *gin.Context) {
-
-	//service.MyService.Docker().DockerContainerCommit("test2")
+	// service.MyService.Docker().DockerContainerCommit("test2")
 
 	index := c.DefaultQuery("index", "1")
 	size := c.DefaultQuery("size", "10000")
@@ -139,7 +143,7 @@ func MyAppList(c *gin.Context) {
 func AppUsageList(c *gin.Context) {
 	list := service.MyService.App().GetHardwareUsage()
 	c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: list})
-	//c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: nil})
+	// c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: nil})
 }
 
 // @Summary 应用详情
@@ -151,7 +155,6 @@ func AppUsageList(c *gin.Context) {
 // @Success 200 {string} string "ok"
 // @Router /app/appinfo/{id} [get]
 func AppInfo(c *gin.Context) {
-
 	id := c.Param("id")
 	language := c.GetHeader("Language")
 	info, err := service.MyService.Casa().GetServerAppInfo(id, "", language)
@@ -213,7 +216,7 @@ func AppInfo(c *gin.Context) {
 	// 	return c1.Type < c2.Type
 	// }
 
-	//sort
+	// sort
 	// if info.NetworkModel != "host" {
 	// 	sort.PortsSort(portOrder).Sort(info.Configures.TcpPorts)
 	// 	sort.PortsSort(portOrder).Sort(info.Configures.UdpPorts)
@@ -265,53 +268,89 @@ func ShareAppFile(c *gin.Context) {
 	c.JSON(common_err.SUCCESS, json.RawMessage(content))
 }
 
-func GetcontainerInfo(c *gin.Context) {
+func GetDockerDaemonConfiguration(c *gin.Context) {
 	// info, err := service.MyService.Docker().GetDockerInfo()
 	// if err != nil {
 	// 	c.JSON(common_err.SERVICE_ERROR, &model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
 	// 	return
 	// }
-	daemon := model.DeckerDaemonModel{}
+	dockerConfig := model.DockerDaemonConfigurationModel{}
 	data := make(map[string]interface{}, 1)
 	data["docker_root_dir"] = ""
-	if file.Exists("/etc/docker/daemon.json") {
-		byteResult := file.ReadFullFile("/etc/docker/daemon.json")
-		err := json.Unmarshal(byteResult, &daemon)
+
+	// TODO read dockerRootDir from /etc/casaos/casaos.conf
+	if file.Exists(dockerDaemonConfigurationFilePath) {
+		byteResult := file.ReadFullFile(dockerDaemonConfigurationFilePath)
+		err := json.Unmarshal(byteResult, &dockerConfig)
 		if err != nil {
 			c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
 			return
 		}
-		data["docker_root_dir"] = daemon.Graph
+
+		if dockerConfig.Root != "" {
+			data["docker_root_dir"] = dockerConfig.Root
+		} else {
+			data["docker_root_dir"] = "/var/lib/docker"
+		}
 	}
 	c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
 }
-func PutcontainerInfo(c *gin.Context) {
+
+func PutDockerDaemonConfiguration(c *gin.Context) {
 	js := make(map[string]interface{})
-	err := c.BindJSON(&js)
-	if err != nil {
-		c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
+	if err := c.BindJSON(&js); err != nil {
+		c.JSON(http.StatusBadRequest, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
+		return
+	}
+
+	value, ok := js["docker_root_dir"]
+	if !ok {
+		c.JSON(http.StatusBadRequest, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: "`docker_root_dir` should not empty"})
 		return
 	}
-	dockerRootDir := js["docker_root_dir"].(string)
-	daemon := model.DeckerDaemonModel{}
-	if file.Exists("/etc/docker/daemon.json") {
-		byteResult := file.ReadFullFile("/etc/docker/daemon.json")
-		err := json.Unmarshal(byteResult, &daemon)
+
+	dockerConfig := model.DockerDaemonConfigurationModel{}
+	if file.Exists(dockerDaemonConfigurationFilePath) {
+		byteResult := file.ReadFullFile(dockerDaemonConfigurationFilePath)
+		err := json.Unmarshal(byteResult, &dockerConfig)
 		if err != nil {
-			c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: err.Error()})
+			c.JSON(http.StatusInternalServerError, &model.Result{Success: common_err.SERVICE_ERROR, Message: "error when trying to deserialize " + dockerDaemonConfigurationFilePath, Data: err.Error()})
 			return
 		}
 	}
-	if !file.Exists(dockerRootDir) {
-		c.JSON(common_err.CLIENT_ERROR, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS), Data: common_err.GetMsg(common_err.DIR_NOT_EXISTS)})
+
+	dockerRootDir := value.(string)
+	if dockerRootDir == "/" {
+		dockerConfig.Root = "" // omitempty - empty string will not be serialized
+	} else {
+		if !file.Exists(dockerRootDir) {
+			c.JSON(http.StatusBadRequest, &model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.DIR_NOT_EXISTS), Data: common_err.GetMsg(common_err.DIR_NOT_EXISTS)})
+			return
+		}
+
+		dockerConfig.Root = filepath.Join(dockerRootDir, "docker")
+
+		if err := file.IsNotExistMkDir(dockerConfig.Root); err != nil {
+			c.JSON(http.StatusInternalServerError, &model.Result{Success: common_err.SERVICE_ERROR, Message: "error when trying to create " + dockerConfig.Root, Data: err.Error()})
+			return
+		}
+	}
+
+	byteMode, err := json.Marshal(dockerConfig)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, &model.Result{Success: common_err.CLIENT_ERROR, Message: "error when trying to serialize docker config", Data: dockerConfig})
 		return
 	}
-	daemon.Graph = dockerRootDir
-	byteMode, _ := json.Marshal(daemon)
-	file.WriteToPath(byteMode, "/etc/docker", "daemon.json")
 
-	fmt.Println(command.ExecResultStr("systemctl daemon-reload"))
-	fmt.Println(command.ExecResultStr("systemctl restart docker"))
+	if err := file.WriteToFullPath(byteMode, dockerDaemonConfigurationFilePath, 0o644); err != nil {
+		c.JSON(http.StatusInternalServerError, &model.Result{Success: common_err.SERVICE_ERROR, Message: "error when trying to write to " + dockerDaemonConfigurationFilePath, Data: err.Error()})
+		return
+	}
+
+	// TODO also write dockerRootDir to /etc/casaos/casaos.conf
+
+	println(command.ExecResultStr("systemctl daemon-reload"))
+	println(command.ExecResultStr("systemctl restart docker"))
 
-	c.JSON(common_err.SUCCESS, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: js})
+	c.JSON(http.StatusOK, &model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: js})
 }