From b0bc6b61269bc4b1cc16fc2e66ee90efa9db0ba2 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Mon, 27 Nov 2023 18:43:28 +0000 Subject: [PATCH 01/31] [skip ci] pre-0.14 --- changelog.md | 6 + client/src/api/docker.jsx | 5 +- .../servapps/containers/docker-compose.jsx | 380 +++++++++--------- .../pages/servapps/containers/newService.jsx | 6 +- src/configapi/patch.go | 3 +- src/docker/api_blueprint.go | 104 ++++- src/docker/bootstrap.go | 290 ++++++------- src/docker/compose.go | 58 +++ src/docker/events.go | 2 +- src/docker/ip.go | 60 ++- src/httpServer.go | 12 +- src/index.go | 5 +- src/proxy/TCPProxy.go | 142 +++++++ src/utils/db.go | 14 + src/utils/utils.go | 3 +- 15 files changed, 721 insertions(+), 369 deletions(-) create mode 100644 src/docker/compose.go create mode 100644 src/proxy/TCPProxy.go diff --git a/changelog.md b/changelog.md index 398fa310..5538abba 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +## Version 0.14.0 + - Cosmos is now fully dockerless + - Improved network IP resolution for containers, including supporting any network mode + - Integrated MongoDB as container + - Removed all sort of container bootstrapping (much faster boot) + ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx index e0418c94..1100bce5 100644 --- a/client/src/api/docker.jsx +++ b/client/src/api/docker.jsx @@ -1,4 +1,5 @@ import wrap from './wrap'; +import yaml from 'js-yaml'; function list() { return wrap(fetch('/cosmos/api/servapps', { @@ -177,9 +178,9 @@ function createService(serviceData, onProgress) { const requestOptions = { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/yaml' }, - body: JSON.stringify(serviceData) + body: serviceData }; return fetch('/cosmos/api/docker-service', requestOptions) diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index 4b1770eb..5695995d 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -176,196 +176,196 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul try { doc = yaml.load(dockerCompose); - if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 && - !doc.services && !doc.networks && !doc.volumes) { - doc = { - services: Object.assign({}, doc) - } - } - - // convert to the proper format - if (doc.services) { - Object.keys(doc.services).forEach((key) => { - // convert volumes - if (doc.services[key].volumes) { - if (Array.isArray(doc.services[key].volumes)) { - let volumes = []; - doc.services[key].volumes.forEach((volume) => { - if (typeof volume === 'object') { - volumes.push(volume); - } else { - let volumeSplit = volume.split(':'); - let volumeObj = { - source: volumeSplit[0], - target: volumeSplit[1], - type: (volume[0] === '/' || volume[0] === '.') ? 'bind' : 'volume', - }; - volumes.push(volumeObj); - } - }); - doc.services[key].volumes = volumes; - } - } - - if(doc.services[key].volumes) - Object.values(doc.services[key].volumes).forEach((volume) => { - if (volume.source && volume.source[0] === '.') { - let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr" - volume.source = defaultPath + volume.source.replace('.', ''); - } - }); - - // convert expose - if (doc.services[key].expose) { - doc.services[key].expose = doc.services[key].expose.map((port) => { - return '' + port; - }) - } - - //convert user - if (doc.services[key].user) { - doc.services[key].user = '' + doc.services[key].user; - } - - // convert labels: - if (doc.services[key].labels) { - if (Array.isArray(doc.services[key].labels)) { - let labels = {}; - doc.services[key].labels.forEach((label) => { - const [key, value] = label.split(/=(.*)/s); - labels['' + key] = '' + value; - }); - doc.services[key].labels = labels; - } - if (typeof doc.services[key].labels == 'object') { - let labels = {}; - Object.keys(doc.services[key].labels).forEach((keylabel) => { - labels['' + keylabel] = '' + doc.services[key].labels[keylabel]; - }); - doc.services[key].labels = labels; - } - } - - // convert environment - if (doc.services[key].environment) { - if (!Array.isArray(doc.services[key].environment)) { - let environment = []; - Object.keys(doc.services[key].environment).forEach((keyenv) => { - environment.push(keyenv + '=' + doc.services[key].environment[keyenv]); - }); - doc.services[key].environment = environment; - } - } - - // convert network - if (doc.services[key].networks) { - if (Array.isArray(doc.services[key].networks)) { - let networks = {}; - doc.services[key].networks.forEach((network) => { - if (typeof network === 'object') { - networks['' + network.name] = network; - } - else - networks['' + network] = {}; - }); - doc.services[key].networks = networks; - } - } - - // convert devices - if (doc.services[key].devices) { - console.log(1) - if (Array.isArray(doc.services[key].devices)) { - console.log(2) - let devices = []; - doc.services[key].devices.forEach((device) => { - if(device.indexOf(':') === -1) { - devices.push(device + ':' + device); - } else { - devices.push(device); - } - }); - doc.services[key].devices = devices; - } - } - - // convert command - if (doc.services[key].command) { - if (typeof doc.services[key].command !== 'string') { - doc.services[key].command = doc.services[key].command.join(' '); - } - } - - // ensure container_name - if (!doc.services[key].container_name) { - doc.services[key].container_name = key; - } - - // convert healthcheck - if (doc.services[key].healthcheck) { - const toConvert = ["timeout", "interval", "start_period"]; - toConvert.forEach((valT) => { - if(typeof doc.services[key].healthcheck[valT] === 'string') { - let original = doc.services[key].healthcheck[valT]; - let value = parseInt(original); - if (original.endsWith('m')) { - value = value * 60; - } else if (original.endsWith('h')) { - value = value * 60 * 60; - } else if (original.endsWith('d')) { - value = value * 60 * 60 * 24; - } - doc.services[key].healthcheck[valT] = value; - } - }); - } - }); - } - - // convert networks - if (doc.networks) { - if (Array.isArray(doc.networks)) { - let networks = {}; - doc.networks.forEach((network) => { - if (typeof network === 'object') { - networks['' + network.name] = network; - } - else - networks['' + network] = {}; - }); - doc.networks = networks; - } else { - let networks = {}; - Object.keys(doc.networks).forEach((key) => { - networks['' + key] = doc.networks[key] || {}; - }); - doc.networks = networks; - } - } - - // convert volumes - if (doc.volumes) { - if (Array.isArray(doc.volumes)) { - let volumes = {}; - doc.volumes.forEach((volume) => { - if (!volume) { - volume = {}; - } - if (typeof volume === 'object') { - volumes['' + volume.name] = volume; - } - else - volumes['' + volume] = {}; - }); - doc.volumes = volumes; - } else { - let volumes = {}; - Object.keys(doc.volumes).forEach((key) => { - volumes['' + key] = doc.volumes[key] || {}; - }); - doc.volumes = volumes; - } - } + // if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 && + // !doc.services && !doc.networks && !doc.volumes) { + // doc = { + // services: Object.assign({}, doc) + // } + // } + + // // convert to the proper format + // if (doc.services) { + // Object.keys(doc.services).forEach((key) => { + // // convert volumes + // if (doc.services[key].volumes) { + // if (Array.isArray(doc.services[key].volumes)) { + // let volumes = []; + // doc.services[key].volumes.forEach((volume) => { + // if (typeof volume === 'object') { + // volumes.push(volume); + // } else { + // let volumeSplit = volume.split(':'); + // let volumeObj = { + // source: volumeSplit[0], + // target: volumeSplit[1], + // type: (volume[0] === '/' || volume[0] === '.') ? 'bind' : 'volume', + // }; + // volumes.push(volumeObj); + // } + // }); + // doc.services[key].volumes = volumes; + // } + // } + + // if(doc.services[key].volumes) + // Object.values(doc.services[key].volumes).forEach((volume) => { + // if (volume.source && volume.source[0] === '.') { + // let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr" + // volume.source = defaultPath + volume.source.replace('.', ''); + // } + // }); + + // // convert expose + // if (doc.services[key].expose) { + // doc.services[key].expose = doc.services[key].expose.map((port) => { + // return '' + port; + // }) + // } + + // //convert user + // if (doc.services[key].user) { + // doc.services[key].user = '' + doc.services[key].user; + // } + + // // convert labels: + // if (doc.services[key].labels) { + // if (Array.isArray(doc.services[key].labels)) { + // let labels = {}; + // doc.services[key].labels.forEach((label) => { + // const [key, value] = label.split(/=(.*)/s); + // labels['' + key] = '' + value; + // }); + // doc.services[key].labels = labels; + // } + // if (typeof doc.services[key].labels == 'object') { + // let labels = {}; + // Object.keys(doc.services[key].labels).forEach((keylabel) => { + // labels['' + keylabel] = '' + doc.services[key].labels[keylabel]; + // }); + // doc.services[key].labels = labels; + // } + // } + + // // convert environment + // if (doc.services[key].environment) { + // if (!Array.isArray(doc.services[key].environment)) { + // let environment = []; + // Object.keys(doc.services[key].environment).forEach((keyenv) => { + // environment.push(keyenv + '=' + doc.services[key].environment[keyenv]); + // }); + // doc.services[key].environment = environment; + // } + // } + + // // convert network + // if (doc.services[key].networks) { + // if (Array.isArray(doc.services[key].networks)) { + // let networks = {}; + // doc.services[key].networks.forEach((network) => { + // if (typeof network === 'object') { + // networks['' + network.name] = network; + // } + // else + // networks['' + network] = {}; + // }); + // doc.services[key].networks = networks; + // } + // } + + // // convert devices + // if (doc.services[key].devices) { + // console.log(1) + // if (Array.isArray(doc.services[key].devices)) { + // console.log(2) + // let devices = []; + // doc.services[key].devices.forEach((device) => { + // if(device.indexOf(':') === -1) { + // devices.push(device + ':' + device); + // } else { + // devices.push(device); + // } + // }); + // doc.services[key].devices = devices; + // } + // } + + // // convert command + // if (doc.services[key].command) { + // if (typeof doc.services[key].command !== 'string') { + // doc.services[key].command = doc.services[key].command.join(' '); + // } + // } + + // // ensure container_name + // if (!doc.services[key].container_name) { + // doc.services[key].container_name = key; + // } + + // // convert healthcheck + // if (doc.services[key].healthcheck) { + // const toConvert = ["timeout", "interval", "start_period"]; + // toConvert.forEach((valT) => { + // if(typeof doc.services[key].healthcheck[valT] === 'string') { + // let original = doc.services[key].healthcheck[valT]; + // let value = parseInt(original); + // if (original.endsWith('m')) { + // value = value * 60; + // } else if (original.endsWith('h')) { + // value = value * 60 * 60; + // } else if (original.endsWith('d')) { + // value = value * 60 * 60 * 24; + // } + // doc.services[key].healthcheck[valT] = value; + // } + // }); + // } + // }); + // } + + // // convert networks + // if (doc.networks) { + // if (Array.isArray(doc.networks)) { + // let networks = {}; + // doc.networks.forEach((network) => { + // if (typeof network === 'object') { + // networks['' + network.name] = network; + // } + // else + // networks['' + network] = {}; + // }); + // doc.networks = networks; + // } else { + // let networks = {}; + // Object.keys(doc.networks).forEach((key) => { + // networks['' + key] = doc.networks[key] || {}; + // }); + // doc.networks = networks; + // } + // } + + // // convert volumes + // if (doc.volumes) { + // if (Array.isArray(doc.volumes)) { + // let volumes = {}; + // doc.volumes.forEach((volume) => { + // if (!volume) { + // volume = {}; + // } + // if (typeof volume === 'object') { + // volumes['' + volume.name] = volume; + // } + // else + // volumes['' + volume] = {}; + // }); + // doc.volumes = volumes; + // } else { + // let volumes = {}; + // Object.keys(doc.volumes).forEach((key) => { + // volumes['' + key] = doc.volumes[key] || {}; + // }); + // doc.volumes = volumes; + // } + // } } catch (e) { setYmlError(e.message); diff --git a/client/src/pages/servapps/containers/newService.jsx b/client/src/pages/servapps/containers/newService.jsx index 021ed92b..024eb671 100644 --- a/client/src/pages/servapps/containers/newService.jsx +++ b/client/src/pages/servapps/containers/newService.jsx @@ -21,6 +21,7 @@ import { Link } from 'react-router-dom'; import { smartDockerLogConcat, tryParseProgressLog } from '../../../utils/docker'; import { LoadingButton } from '@mui/lab'; import LogLine from '../../../components/logLine'; +import yaml from 'js-yaml'; const preStyle = { backgroundColor: '#000', @@ -84,7 +85,7 @@ const NewDockerService = ({service, refresh}) => { setLog([ 'Creating Service... ', ]) - API.docker.createService(service, (newlog) => { + API.docker.createService(yaml.dump(service), (newlog) => { setLog((old) => smartDockerLogConcat(old, newlog)); preRef.current.scrollTop = preRef.current.scrollHeight; if (newlog.includes('[OPERATION SUCCEEDED]')) { @@ -118,8 +119,7 @@ const NewDockerService = ({service, refresh}) => {
         {!log.length && `
 # You are about to create the following service(s):
-
-${JSON.stringify(service, false ,2)}`
+${yaml.dump(service)}`
         }
         {log.map((l) => {
           return 
diff --git a/src/configapi/patch.go b/src/configapi/patch.go
index 91303df6..9e487b45 100644
--- a/src/configapi/patch.go
+++ b/src/configapi/patch.go
@@ -115,7 +115,8 @@ func ConfigApiPatch(w http.ResponseWriter, req *http.Request) {
 		
 		utils.Log("RouteSettingsUpdate: Service needs update: "+name)
 
-		utils.ReBootstrapContainer(name)
+		// TODO CACHE BURST IN IP RESOLUTION
+		// utils.ReBootstrapContainer(name)
 	}
 
 	json.NewEncoder(w).Encode(map[string]interface{}{
diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go
index e7ec40f0..db57119c 100644
--- a/src/docker/api_blueprint.go
+++ b/src/docker/api_blueprint.go
@@ -1,7 +1,7 @@
 package docker
 
 import (
-	"encoding/json"
+	// "encoding/json"
 	"fmt"
 	"net/http"
 	"strings"
@@ -12,6 +12,7 @@ import (
 	"io/ioutil"
 	"os/user"
 	"errors"
+	// "gopkg.in/yaml.v2"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/api/types/network"
@@ -229,23 +230,76 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
 				return 
 		}
 
-		decoder := json.NewDecoder(req.Body)
-		var serviceRequest DockerServiceCreateRequest
-		err := decoder.Decode(&serviceRequest)
+		// decoder := yaml.NewDecoder(req.Body)
+		// var serviceRequest DockerServiceCreateRequest
+		// err := decoder.Decode(&serviceRequest)
+		// if err != nil {
+		// 	utils.Error("CreateService - decode - ", err)
+		// 	fmt.Fprintf(w, "[OPERATION FAILED] Bad request: "+err.Error(), http.StatusBadRequest, "DS003")
+		// 	flusher.Flush()
+		// 	utils.HTTPError(w, "Bad request: " + err.Error(), http.StatusBadRequest, "DS003")
+		// 	return
+		// }
+
+		/*CreateService(serviceRequest, 
+			func (msg string) {
+				fmt.Fprintf(w, msg)
+				flusher.Flush()
+			},
+		)*/
+
+		ServiceName := "test-wesh"
+
+		filePath := utils.CONFIGFOLDER + "compose/" + ServiceName
+
+		// Create the folder if does not exist, and output docker-compose.yml
+		if _, err := os.Stat(utils.CONFIGFOLDER + "compose/"); os.IsNotExist(err) {
+			os.MkdirAll(utils.CONFIGFOLDER + "compose/", 0750)
+		}
+		if _, err := os.Stat(filePath); os.IsNotExist(err) {
+			os.MkdirAll(filePath, 0750)
+		}
+
+		// create or truncate the file
+		file, err := os.Create(filePath + "/docker-compose.yml")
 		if err != nil {
-			utils.Error("CreateService - decode - ", err)
-			fmt.Fprintf(w, "[OPERATION FAILED] Bad request: "+err.Error(), http.StatusBadRequest, "DS003")
+			utils.Error("CreateService - create - ", err)
+			fmt.Fprintf(w, "[OPERATION FAILED] Internal server error: "+err.Error(), http.StatusInternalServerError, "DS004")
 			flusher.Flush()
-			utils.HTTPError(w, "Bad request: " + err.Error(), http.StatusBadRequest, "DS003")
 			return
 		}
+		defer file.Close()
 
-		CreateService(serviceRequest, 
-			func (msg string) {
-				fmt.Fprintf(w, msg)
-				flusher.Flush()
-			},
-		)
+		// write to file
+		ymlBody := req.Body
+		writer := bufio.NewWriter(file)
+		_, err = writer.ReadFrom(ymlBody)
+		if err != nil {
+			utils.Error("CreateService - write - ", err)
+			fmt.Fprintf(w, "[OPERATION FAILED] Internal server error: "+err.Error(), http.StatusInternalServerError, "DS005")
+			flusher.Flush()
+			return
+		}
+
+		writer.Flush()
+
+		// Compose up
+		err = ComposeUp(filePath, func(message string, outputType int) {
+			fmt.Fprintf(w, "%s\n", message)
+			flusher.Flush()
+		})
+
+		if err != nil {
+			utils.Error("CreateService - composeup - ", err)
+			fmt.Fprintf(w, "[OPERATION FAILED] Internal server error: "+err.Error(), http.StatusInternalServerError, "DS006")
+			flusher.Flush()
+			return
+		}
+
+
+		// Write a response to the client
+		fmt.Fprintf(w, "[OPERATION SUCCESSFUL] Service created successfully", http.StatusOK, "DS007")
+		flusher.Flush()
 	} else {
 		utils.Error("CreateService: Method not allowed" + req.Method, nil)
 		utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
@@ -286,6 +340,21 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 	var rollbackActions []DockerServiceCreateRollback
 	var err error
 
+	serviceName := ""
+	for serviceName = range serviceRequest.Services {
+		break
+	}
+
+	// check if serviceRequest.Networks contains service-default, if not, create it
+	if _, ok := serviceRequest.Networks[serviceName + "-default"]; !ok {
+		serviceRequest.Networks[serviceName + "-default"] = ContainerCreateRequestNetwork{
+			Name: serviceName + "-default",
+			Driver: "bridge",
+			Attachable: true,
+			Internal: false,
+		}
+	}
+
 	// Create networks
 	for networkToCreateName, networkToCreate := range serviceRequest.Networks {
 		utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName))
@@ -296,7 +365,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 
 		if err == nil {
 			if networkToCreate.Driver == "" {
-				networkToCreate.Driver = "bridge"
+				networkToCreate.Driver = serviceName + "-default"
 			}
 
 			if (exNetworkDef.Driver != networkToCreate.Driver) {
@@ -422,7 +491,8 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 		OnLog(fmt.Sprintf("Checking service %s...\n", serviceName))
 
 		// If container request a Cosmos network, create and attach it
-		if (container.Labels["cosmos-force-network-secured"] == "true" || strings.ToLower(container.Labels["cosmos-network-name"]) == "auto") &&
+		
+		/*if (container.Labels["cosmos-force-network-secured"] == "true" || strings.ToLower(container.Labels["cosmos-network-name"]) == "auto") &&
 					container.Labels["cosmos-network-name"] == "" {
 			utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName))
 			OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName))
@@ -465,7 +535,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 	
 				AttachNetworkToCosmos(container.Labels["cosmos-network-name"])
 			}
-		}
+		}*/
 
 		utils.Log(fmt.Sprintf("Creating container %s...", container.Name))
 		OnLog(fmt.Sprintf("Creating container %s...\n", container.Name))
@@ -819,6 +889,8 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 		// connect to networks
 		for netName, netConfig := range container.Networks {
 			utils.Log("CreateService: Connecting to network: " + netName)
+			
+			//TODO: THIS IS WRONG https://pkg.go.dev/github.com/docker/docker@v24.0.7+incompatible/api/types/network#EndpointSettings
 			err = DockerClient.NetworkConnect(DockerContext, netName, container.Name, &network.EndpointSettings{
 				Aliases:     netConfig.Aliases,
 				IPAddress:   netConfig.IPV4Address,
diff --git a/src/docker/bootstrap.go b/src/docker/bootstrap.go
index 0880977e..713c3a46 100644
--- a/src/docker/bootstrap.go
+++ b/src/docker/bootstrap.go
@@ -1,150 +1,150 @@
 package docker
 
-import (	
-	"github.com/azukaar/cosmos-server/src/utils" 
-	"github.com/docker/docker/api/types"
-	"os"
-	"fmt"
-	"regexp"
-)
-
-func BootstrapAllContainersFromTags() []error {
-	errD := Connect()
-	if errD != nil {
-		return []error{errD}
-	}
-
-	errors := []error{}
+// import (	
+// 	"github.com/azukaar/cosmos-server/src/utils" 
+// 	"github.com/docker/docker/api/types"
+// 	"os"
+// 	"fmt"
+// 	"regexp"
+// )
+
+// func BootstrapAllContainersFromTags() []error {
+// 	errD := Connect()
+// 	if errD != nil {
+// 		return []error{errD}
+// 	}
+
+// 	errors := []error{}
 	
-	containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{})
-	if err != nil {
-		utils.Error("Docker Container List", err)
-		return []error{err}
-	}
-
-	for _, container := range containers {
-		errB := BootstrapContainerFromTags(container.ID)
-		if errB != nil {
-			utils.Error("Bootstrap Container From Tags", errB)
-			errors = append(errors, errB)
-		}
-	}
-
-	return errors
-}
-
-func UnsecureContainer(container types.ContainerJSON) (string, error) {
-	RemoveLabels(container, []string{
-		"cosmos-force-network-secured",
-	});
-	return EditContainer(container.ID, container, false)
-}
-
-func BootstrapContainerFromTags(containerID string) error {
-	errD := Connect()
-	if errD != nil {
-		return errD
-	}
-
-	selfContainer := types.ContainerJSON{}
-	if os.Getenv("HOSTNAME") != "" {
-		var errS error 
-		selfContainer, errS = DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME"))
-		if errS != nil {
-			utils.Error("DockerContainerBootstrapSelfInspect", errS)
-			return errS
-		}
-	}
-
-	utils.Log("Bootstrap Container From Tags: " + containerID)
-
-	container, err := DockerClient.ContainerInspect(DockerContext, containerID)
-	if err != nil {
-		utils.Error("DockerContainerBootstrapInspect", err)
-		return err
-	}
-
-	// check if any route has been added to the container
-	config := utils.GetMainConfig()
-	if(!HasLabel(container, "cosmos-network-name")) {
-		for _, route := range config.HTTPConfig.ProxyConfig.Routes {
-				utils.Debug("No cosmos-network-name label on container "+container.Name)
-				pattern := fmt.Sprintf(`(?i)^(([a-z]+):\/\/)?%s(:?[0-9]+)?$`, container.Name[1:])
-				match, _ := regexp.MatchString(pattern, route.Target)
-				if route.Mode == "SERVAPP" && match {
-					utils.Log("Adding cosmos-network-name label to container "+container.Name)
-					AddLabels(container, map[string]string{
-						"cosmos-network-name": "auto",
-					})	
-				}
-		}
-	}
-
-	// Check cosmos-network-name tag
-	isCosmosCon, _, needsUpdate := IsConnectedToASecureCosmosNetwork(selfContainer, container)
-
-	if(IsLabel(container, "cosmos-force-network-secured")) {
-		utils.Log(container.Name+": Checking Force network secured")
-
-		// check if connected to bridge and to a cosmos network
-		isCon := IsConnectedToNetwork(container, "bridge")
+// 	containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{})
+// 	if err != nil {
+// 		utils.Error("Docker Container List", err)
+// 		return []error{err}
+// 	}
+
+// 	for _, container := range containers {
+// 		errB := BootstrapContainerFromTags(container.ID)
+// 		if errB != nil {
+// 			utils.Error("Bootstrap Container From Tags", errB)
+// 			errors = append(errors, errB)
+// 		}
+// 	}
+
+// 	return errors
+// }
+
+// func UnsecureContainer(container types.ContainerJSON) (string, error) {
+// 	RemoveLabels(container, []string{
+// 		"cosmos-force-network-secured",
+// 	});
+// 	return EditContainer(container.ID, container, false)
+// }
+
+// func BootstrapContainerFromTags(containerID string) error {
+// 	errD := Connect()
+// 	if errD != nil {
+// 		return errD
+// 	}
+
+// 	selfContainer := types.ContainerJSON{}
+// 	if os.Getenv("HOSTNAME") != "" {
+// 		var errS error 
+// 		selfContainer, errS = DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME"))
+// 		if errS != nil {
+// 			utils.Error("DockerContainerBootstrapSelfInspect", errS)
+// 			return errS
+// 		}
+// 	}
+
+// 	utils.Log("Bootstrap Container From Tags: " + containerID)
+
+// 	container, err := DockerClient.ContainerInspect(DockerContext, containerID)
+// 	if err != nil {
+// 		utils.Error("DockerContainerBootstrapInspect", err)
+// 		return err
+// 	}
+
+// 	// check if any route has been added to the container
+// 	config := utils.GetMainConfig()
+// 	if(!HasLabel(container, "cosmos-network-name")) {
+// 		for _, route := range config.HTTPConfig.ProxyConfig.Routes {
+// 				utils.Debug("No cosmos-network-name label on container "+container.Name)
+// 				pattern := fmt.Sprintf(`(?i)^(([a-z]+):\/\/)?%s(:?[0-9]+)?$`, container.Name[1:])
+// 				match, _ := regexp.MatchString(pattern, route.Target)
+// 				if route.Mode == "SERVAPP" && match {
+// 					utils.Log("Adding cosmos-network-name label to container "+container.Name)
+// 					AddLabels(container, map[string]string{
+// 						"cosmos-network-name": "auto",
+// 					})	
+// 				}
+// 		}
+// 	}
+
+// 	// Check cosmos-network-name tag
+// 	isCosmosCon, _, needsUpdate := IsConnectedToASecureCosmosNetwork(selfContainer, container)
+
+// 	if(IsLabel(container, "cosmos-force-network-secured")) {
+// 		utils.Log(container.Name+": Checking Force network secured")
+
+// 		// check if connected to bridge and to a cosmos network
+// 		isCon := IsConnectedToNetwork(container, "bridge")
 		
-		if isCon || !isCosmosCon {
-			utils.Log(container.Name+": Needs isolating on a secured network")
-			needsRestart := false
-			var errCT error
-			if !isCosmosCon {
-				utils.Debug(container.Name+": Not connected to a cosmos network")
-				needsRestart, errCT = ConnectToSecureNetwork(container)
-				if errCT != nil {
-					utils.Warn("DockerContainerBootstrapConnectToSecureNetwork -- Cannot connect to network, removing force secure")
-					_, errUn := UnsecureContainer(container)
-					if errUn != nil {
-						utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn)
-						return errCT
-					}
-					return errCT
-				}
-				if needsRestart {
-					utils.Log(container.Name+": Will restart to apply changes")
-					needsUpdate = true
-				} else {
-					utils.Log(container.Name+": Connected to new network")
-				}
-			}
-			if !needsRestart && isCon {
-				utils.Log(container.Name+": Disconnecting from bridge network")
-				errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true) 
-				if errDisc != nil {
-					utils.Warn("DockerContainerBootstrapDisconnectFromBridge -- Cannot disconnect from Bridge, removing force secure")
-					_, errUn := UnsecureContainer(container)
-					if errUn != nil {
-						utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn)
-						return errDisc
-					}
-					return errDisc
-				}
-			}
-		}
-
-		if(len(GetAllPorts(container)) > 0) {
-			utils.Log("Removing unsecure ports bindings from "+container.Name)
-			// remove all ports			
-			UnexposeAllPorts(&container)
-			needsUpdate = true
-		}
-	}
+// 		if isCon || !isCosmosCon {
+// 			utils.Log(container.Name+": Needs isolating on a secured network")
+// 			needsRestart := false
+// 			var errCT error
+// 			if !isCosmosCon {
+// 				utils.Debug(container.Name+": Not connected to a cosmos network")
+// 				needsRestart, errCT = ConnectToSecureNetwork(container)
+// 				if errCT != nil {
+// 					utils.Warn("DockerContainerBootstrapConnectToSecureNetwork -- Cannot connect to network, removing force secure")
+// 					_, errUn := UnsecureContainer(container)
+// 					if errUn != nil {
+// 						utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn)
+// 						return errCT
+// 					}
+// 					return errCT
+// 				}
+// 				if needsRestart {
+// 					utils.Log(container.Name+": Will restart to apply changes")
+// 					needsUpdate = true
+// 				} else {
+// 					utils.Log(container.Name+": Connected to new network")
+// 				}
+// 			}
+// 			if !needsRestart && isCon {
+// 				utils.Log(container.Name+": Disconnecting from bridge network")
+// 				errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true) 
+// 				if errDisc != nil {
+// 					utils.Warn("DockerContainerBootstrapDisconnectFromBridge -- Cannot disconnect from Bridge, removing force secure")
+// 					_, errUn := UnsecureContainer(container)
+// 					if errUn != nil {
+// 						utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn)
+// 						return errDisc
+// 					}
+// 					return errDisc
+// 				}
+// 			}
+// 		}
+
+// 		if(len(GetAllPorts(container)) > 0) {
+// 			utils.Log("Removing unsecure ports bindings from "+container.Name)
+// 			// remove all ports			
+// 			UnexposeAllPorts(&container)
+// 			needsUpdate = true
+// 		}
+// 	}
 	
-	if(needsUpdate) {
-		_, errEdit := EditContainer(containerID, container, false)
-		if errEdit != nil {
-			utils.Error("Docker Boostrap, couldn't update container: ", errEdit)
-			return errEdit
-		}
-		utils.Debug("Done updating Container From Tags after Bootstrapping: " + container.Name)
-	}
-
-	utils.Log("Done bootstrapping Container From Tags: " + container.Name)
-
-	return nil
-}
+// 	if(needsUpdate) {
+// 		_, errEdit := EditContainer(containerID, container, false)
+// 		if errEdit != nil {
+// 			utils.Error("Docker Boostrap, couldn't update container: ", errEdit)
+// 			return errEdit
+// 		}
+// 		utils.Debug("Done updating Container From Tags after Bootstrapping: " + container.Name)
+// 	}
+
+// 	utils.Log("Done bootstrapping Container From Tags: " + container.Name)
+
+// 	return nil
+// }
diff --git a/src/docker/compose.go b/src/docker/compose.go
new file mode 100644
index 00000000..892510b3
--- /dev/null
+++ b/src/docker/compose.go
@@ -0,0 +1,58 @@
+package docker 
+
+import (
+	"bufio"
+	"io"
+	"os/exec"
+
+	"github.com/azukaar/cosmos-server/src/utils"
+)
+
+const (
+    Stdout = iota
+    Stderr
+)
+
+
+// readOutput reads from the given reader and uses the callback function to handle the output.
+func readOutput(r io.Reader, callback func(message string, outputType int), outputType int) {
+	scanner := bufio.NewScanner(r)
+	for scanner.Scan() {
+			callback(scanner.Text(), outputType)
+	}
+}
+
+// ExecCommand runs the specified command and uses a callback function to handle the output.
+func ExecCommand(callback func(message string, outputType int), args ...string) error {
+    cmd := exec.Command(args[0], args[1:]...)
+		utils.Debug("Running command: " +  cmd.String())
+
+		callback("Running command: " +  cmd.String(), Stdout)
+
+    // Create pipes for stdout and stderr
+    stdoutPipe, err := cmd.StdoutPipe()
+    if err != nil {
+        return err
+    }
+
+    stderrPipe, err := cmd.StderrPipe()
+    if err != nil {
+        return err
+    }
+
+    // Start the command
+    if err := cmd.Start(); err != nil {
+        return err
+    }
+
+    // Read from stdout and stderr in separate goroutines
+    go readOutput(stdoutPipe, callback, Stdout)
+    go readOutput(stderrPipe, callback, Stderr)
+
+    // Wait for the command to finish
+    return cmd.Wait()
+}
+
+func ComposeUp(filePath string, callback func(message string, outputType int)) error {
+	return ExecCommand(callback, "docker", "compose", "--project-directory", filePath, "up", "--remove-orphans", "-d")
+}
\ No newline at end of file
diff --git a/src/docker/events.go b/src/docker/events.go
index e62e306f..047ea7d9 100644
--- a/src/docker/events.go
+++ b/src/docker/events.go
@@ -140,7 +140,7 @@ func DebouncedExportDocker() {
 
 func onDockerStarted(containerID string) {
 	utils.Debug("onDockerStarted: " + containerID)
-	BootstrapContainerFromTags(containerID)
+	// BootstrapContainerFromTags(containerID)
 	DebouncedExportDocker()
 }
 
diff --git a/src/docker/ip.go b/src/docker/ip.go
index c9ca3370..8a2e7dc8 100644
--- a/src/docker/ip.go
+++ b/src/docker/ip.go
@@ -4,8 +4,10 @@ import (
 	"fmt"
 	"sync"
 	"time"
+	"strings"
 
 	"github.com/azukaar/cosmos-server/src/utils"
+	"github.com/docker/docker/api/types"
 )
 
 type Cache struct {
@@ -49,22 +51,68 @@ func (c *Cache) Set(key string, value string, duration time.Duration) {
 }
 
 func _getContainerIPByName(containerName string) (string, error) {
+	errD := Connect()
+	if errD != nil {
+		return "", errD
+	}
+
 	container, err := DockerClient.ContainerInspect(DockerContext, containerName)
 	if err != nil {
 		return "", err
 	}
 
 	// Prioritize "host"
-	if net, ok := container.NetworkSettings.Networks["host"]; ok && net.IPAddress != "" {
-		return net.IPAddress, nil
-	}
+	// if net, ok := container.NetworkSettings.Networks["host"]; ok && net.IPAddress != "" {
+	// 	return "localhost", nil
+	// }
 
 	// Next, prioritize "bridge"
-	if net, ok := container.NetworkSettings.Networks["bridge"]; ok && net.IPAddress != "" {
-		return net.IPAddress, nil
+	// if net, ok := container.NetworkSettings.Networks["bridge"]; ok && net.IPAddress != "" {
+	// 	return net.IPAddress, nil
+	// }
+
+	// if container is in network mode host
+	if container.HostConfig.NetworkMode == "host" {
+		return "localhost", nil
+	} else if container.HostConfig.NetworkMode == "bridge" || container.HostConfig.NetworkMode == "default" || container.HostConfig.NetworkMode == "" {
+		// if container is in network mode bridge or default or not set
+		
+		for _, net := range container.NetworkSettings.Networks {
+			// if this network is using the bridge driver 
+			if net.IPAddress != "" {
+				return net.IPAddress, nil
+			}
+		}
+	} else if strings.HasPrefix(string(container.HostConfig.NetworkMode), "container:") {
+		// if container is in network mode container:
+
+		// get the container name
+		otherContainerName := strings.TrimPrefix(string(container.HostConfig.NetworkMode), "container:")
+		// get the ip of the other container
+		ip, err := _getContainerIPByName(otherContainerName)
+		if err != nil {
+			return "", err
+		}
+		return ip, nil
+	} else {
+		// if container is in network mode 
+
+		// get the network name
+		networkName := string(container.HostConfig.NetworkMode)
+		// get the network
+		network, err := DockerClient.NetworkInspect(DockerContext, networkName, types.NetworkInspectOptions{})
+		if err != nil {
+			return "", err
+		}
+		// get the ip of the container in the network
+		for _, containerEP := range network.Containers {
+			if containerEP.Name == container.Name {
+				return network.IPAM.Config[0].Gateway, nil
+			}
+		}
 	}
 
-	// Finally, return the IP of the first network we find
+	// Finally, if nothing, return the IP of the first network we find
 	for _, net := range container.NetworkSettings.Networks {
 		if net.IPAddress != "" {
 			return net.IPAddress, nil
diff --git a/src/httpServer.go b/src/httpServer.go
index 12a9244d..db94ce1d 100644
--- a/src/httpServer.go
+++ b/src/httpServer.go
@@ -41,7 +41,11 @@ func startHTTPServer(router *mux.Router) error {
 		DisableGeneralOptionsHandler: true,
 	}
 	
-	docker.CheckPorts()
+	if os.Getenv("HOSTNAME") != "" {
+		docker.CheckPorts()
+	} else {
+		proxy.InitInternalTCPProxy()
+	}
 	
 	utils.Log("Listening to HTTP on : 0.0.0.0:" + serverPortHTTP)
 
@@ -119,7 +123,11 @@ func startHTTPSServer(router *mux.Router) error {
 	}
 
 	// Redirect ports 
-	docker.CheckPorts()
+	if os.Getenv("HOSTNAME") != "" {
+		docker.CheckPorts()
+	} else {
+		proxy.InitInternalTCPProxy()
+	}
 
 	utils.Log("Now listening to HTTPS on :" + serverPortHTTPS)
 
diff --git a/src/index.go b/src/index.go
index 385bb2a4..02f07cf2 100644
--- a/src/index.go
+++ b/src/index.go
@@ -16,8 +16,9 @@ import (
 func main() {
 	utils.Log("Starting...")
 	
-	utils.ReBootstrapContainer = docker.BootstrapContainerFromTags
+	// utils.ReBootstrapContainer = docker.BootstrapContainerFromTags
 	utils.PushShieldMetrics = metrics.PushShieldMetrics
+	utils.GetContainerIPByName = docker.GetContainerIPByName
 
 	rand.Seed(time.Now().UnixNano())
 
@@ -31,7 +32,7 @@ func main() {
 
 	docker.DockerListenEvents()
 
-	docker.BootstrapAllContainersFromTags()
+	// docker.BootstrapAllContainersFromTags()
 
 	docker.RemoveSelfUpdater()
 
diff --git a/src/proxy/TCPProxy.go b/src/proxy/TCPProxy.go
new file mode 100644
index 00000000..fc9d927d
--- /dev/null
+++ b/src/proxy/TCPProxy.go
@@ -0,0 +1,142 @@
+package proxy
+
+import (
+    "io"
+    "net"
+    "sync"
+    "strings"
+
+    "github.com/azukaar/cosmos-server/src/utils"
+)
+
+var (
+    activeProxies map[string]chan bool
+    proxiesLock   sync.Mutex
+)
+
+func handleClient(client net.Conn, server net.Conn, stop chan bool) {
+    defer client.Close()
+    defer server.Close()
+
+    // Forward data between client and server, and watch for stop signal
+    done := make(chan struct{})
+    go func() {
+        io.Copy(server, client)
+        done <- struct{}{}
+    }()
+    go func() {
+        io.Copy(client, server)
+        done <- struct{}{}
+    }()
+
+    select {
+    case <-stop:
+        return
+    case <-done:
+        return
+    }
+}
+
+func startProxy(listenAddr string, target string, stop chan bool) {
+    listener, err := net.Listen("tcp", listenAddr)
+    if err != nil {
+        utils.Error("Failed to listen on " + listenAddr, err)
+    }
+    defer listener.Close()
+
+    utils.Log("Proxy listening on "+listenAddr+", forwarding to " + target)
+
+    for {
+        select {
+        case <-stop:
+            return
+        default:
+            client, err := listener.Accept()
+            if err != nil {
+                utils.Error("Failed to accept connection: %v", err)
+                continue
+            }
+
+            server, err := net.Dial("tcp", target)
+            if err != nil {
+                utils.Error("Failed to connect to server: %v", err)
+                client.Close()
+                continue
+            }
+
+            go handleClient(client, server, stop)
+        }
+    }
+}
+
+func initInternalPortProxy(ports []string, destination string) {
+    proxiesLock.Lock()
+    defer proxiesLock.Unlock()
+
+    // Initialize activeProxies map if it's nil
+    if activeProxies == nil {
+        activeProxies = make(map[string]chan bool)
+    }
+
+    // Stop any existing proxies that are not in the new list
+    for port, stop := range activeProxies {
+        if !contains(ports, port) {
+            close(stop)
+            delete(activeProxies, port)
+        }
+    }
+
+    // Start new proxies for ports in the list that aren't already running
+    for _, port := range ports {
+        if _, exists := activeProxies[port]; !exists {
+            utils.Log("Network Starting internal proxy for port " + port)
+            stop := make(chan bool)
+            activeProxies[port] = stop
+            go startProxy(":"+port, destination, stop)
+        }
+    }
+}
+
+// Helper function to check if a slice contains a string
+func contains(slice []string, item string) bool {
+    for _, a := range slice {
+        if a == item {
+            return true
+        }
+    }
+    return false
+}
+
+func InitInternalTCPProxy() {
+    utils.Log("Network: Initializing internal TCP proxy")
+    
+    config := utils.GetMainConfig()
+    expectedPorts := []string{}
+	isHTTPS := utils.IsHTTPS
+	HTTPPort := config.HTTPConfig.HTTPPort
+	HTTPSPort := config.HTTPConfig.HTTPSPort
+	routes := config.HTTPConfig.ProxyConfig.Routes
+	targetPort := HTTPPort
+	if isHTTPS {
+		targetPort = HTTPSPort
+	}
+
+	for _, route := range routes {
+		if route.UseHost && strings.Contains(route.Host, ":") {
+			hostname := route.Host
+			port := strings.Split(hostname, ":")[1]
+			expectedPorts = append(expectedPorts, port)
+		}
+	}
+
+	// append hostname port 
+	hostname := config.HTTPConfig.Hostname
+	if strings.Contains(hostname, ":") {
+		hostnameport := strings.Split(hostname, ":")[1]
+        if hostnameport != targetPort {
+    		expectedPorts = append(expectedPorts, hostnameport)
+        }
+	}
+
+    initInternalPortProxy(expectedPorts, "localhost:"+targetPort)
+}
\ No newline at end of file
diff --git a/src/utils/db.go b/src/utils/db.go
index 1f60d359..5479cd95 100644
--- a/src/utils/db.go
+++ b/src/utils/db.go
@@ -16,6 +16,7 @@ import (
 
 
 var client *mongo.Client
+var IsDBaContainer bool
 
 func DB() error {
 	if(GetMainConfig().DisableUserManagement)	{
@@ -37,6 +38,19 @@ func DB() error {
 	var err error
 
 	opts := options.Client().ApplyURI(mongoURL).SetRetryWrites(true).SetWriteConcern(writeconcern.New(writeconcern.WMajority()))
+
+	IsDBaContainer = false
+
+	if os.Getenv("HOSTNAME") == "" {
+		hostname := opts.Hosts[0]
+		Log("Getting Mongo DB IP from name : " + hostname)
+		ip, _ := GetContainerIPByName(hostname)
+		if ip != "" {
+			IsDBaContainer = true
+			opts.SetHosts([]string{ip + ":27017"})
+			Log("Mongo DB IP : " + ip)
+		}
+	}
 	
 	client, err = mongo.Connect(context.TODO(), opts)
 
diff --git a/src/utils/utils.go b/src/utils/utils.go
index 2d0c8502..829457f1 100644
--- a/src/utils/utils.go
+++ b/src/utils/utils.go
@@ -33,7 +33,8 @@ var NeedsRestart = false
 var UpdateAvailable = map[string]bool{}
 
 var RestartHTTPServer func()
-var ReBootstrapContainer func(string) error
+// var ReBootstrapContainer func(string) error
+var GetContainerIPByName func(string) (string, error)
 
 var LetsEncryptErrors = []string{}
 

From e46dc10fccf9462e99cd7d0f39ac177ca1983117 Mon Sep 17 00:00:00 2001
From: Yann Stepienik 
Date: Sun, 21 Jan 2024 14:56:17 +0000
Subject: [PATCH 02/31] [skip ci] pre-0.14

---
 changelog.md                                  |   2 +
 client/src/api/docker.jsx                     |   4 +-
 client/src/pages/config/users/configman.jsx   |  10 +-
 .../servapps/containers/docker-compose.jsx    | 429 ++++++++++--------
 .../pages/servapps/containers/newService.jsx  |   5 +-
 .../src/pages/servapps/containers/volumes.jsx |   7 +-
 client/src/pages/servapps/servapps.jsx        |  61 +--
 package.json                                  |   2 +-
 readme.md                                     |   1 +
 src/docker/api_blueprint.go                   | 172 ++-----
 src/docker/compose.go                         |  58 ---
 src/docker/network.go                         |  99 +---
 src/utils/types.go                            |   1 +
 13 files changed, 359 insertions(+), 492 deletions(-)
 delete mode 100644 src/docker/compose.go

diff --git a/changelog.md b/changelog.md
index 5538abba..d2e2edb6 100644
--- a/changelog.md
+++ b/changelog.md
@@ -3,6 +3,8 @@
  - Improved network IP resolution for containers, including supporting any network mode
  - Integrated MongoDB as container
  - Removed all sort of container bootstrapping (much faster boot)
+ - Added image clean up
+ - Replaced network clean up by vanilla docker prune
 
 ## Version 0.13.2
  - Fix display issue with fault network configurations
diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx
index 1100bce5..84e0a8c0 100644
--- a/client/src/api/docker.jsx
+++ b/client/src/api/docker.jsx
@@ -178,9 +178,9 @@ function createService(serviceData, onProgress) {
   const requestOptions = {
     method: 'POST',
     headers: {
-      'Content-Type': 'application/yaml'
+      'Content-Type': 'application/json'
     },
-    body: serviceData
+    body: JSON.stringify(serviceData)
   };
 
   return fetch('/cosmos/api/docker-service', requestOptions)
diff --git a/client/src/pages/config/users/configman.jsx b/client/src/pages/config/users/configman.jsx
index 87fcfe9d..c794ae7a 100644
--- a/client/src/pages/config/users/configman.jsx
+++ b/client/src/pages/config/users/configman.jsx
@@ -118,6 +118,7 @@ const ConfigManagement = () => {
           Email_AllowInsecureTLS : config.EmailConfig.AllowInsecureTLS,
 
           SkipPruneNetwork: config.DockerConfig.SkipPruneNetwork,
+          SkipPruneImages: config.DockerConfig.SkipPruneImages,
           DefaultDataPath: config.DockerConfig.DefaultDataPath || "/usr",
 
           Background: config && config.HomepageConfig && config.HomepageConfig.Background,
@@ -175,6 +176,7 @@ const ConfigManagement = () => {
             DockerConfig: {
               ...config.DockerConfig,
               SkipPruneNetwork: values.SkipPruneNetwork,
+              SkipPruneImages: values.SkipPruneImages,
               DefaultDataPath: values.DefaultDataPath
             },
             HomepageConfig: {
@@ -537,11 +539,17 @@ const ConfigManagement = () => {
               
                 
                   
 
+                  
+
                    {
@@ -176,196 +177,244 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
       try {
         doc = yaml.load(dockerCompose);
 
-        // if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 &&
-        //   !doc.services && !doc.networks && !doc.volumes) {
-        //   doc = {
-        //     services: Object.assign({}, doc)
-        //   }
-        // }
-
-        // // convert to the proper format
-        // if (doc.services) {
-        //   Object.keys(doc.services).forEach((key) => {
-        //     // convert volumes
-        //     if (doc.services[key].volumes) {
-        //       if (Array.isArray(doc.services[key].volumes)) {
-        //         let volumes = [];
-        //         doc.services[key].volumes.forEach((volume) => {
-        //           if (typeof volume === 'object') {
-        //             volumes.push(volume);
-        //           } else {
-        //             let volumeSplit = volume.split(':');
-        //             let volumeObj = {
-        //               source: volumeSplit[0],
-        //               target: volumeSplit[1],
-        //               type: (volume[0] === '/' || volume[0] === '.') ? 'bind' : 'volume',
-        //             };
-        //             volumes.push(volumeObj);
-        //           }
-        //         });
-        //         doc.services[key].volumes = volumes;
-        //       }
-        //     }
-
-        //     if(doc.services[key].volumes)
-        //       Object.values(doc.services[key].volumes).forEach((volume) => {
-        //         if (volume.source && volume.source[0] === '.') {
-        //           let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr"
-        //           volume.source = defaultPath + volume.source.replace('.', '');
-        //         }
-        //       });
-
-        //     // convert expose
-        //     if (doc.services[key].expose) {
-        //       doc.services[key].expose = doc.services[key].expose.map((port) => {
-        //         return '' + port;
-        //       })
-        //     }
-
-        //     //convert user
-        //     if (doc.services[key].user) {
-        //       doc.services[key].user = '' + doc.services[key].user;
-        //     }
-
-        //     // convert labels: 
-        //     if (doc.services[key].labels) {
-        //       if (Array.isArray(doc.services[key].labels)) {
-        //         let labels = {};
-        //         doc.services[key].labels.forEach((label) => {
-        //           const [key, value] = label.split(/=(.*)/s);
-        //           labels['' + key] = '' + value;
-        //         });
-        //         doc.services[key].labels = labels;
-        //       }
-        //       if (typeof doc.services[key].labels == 'object') {
-        //         let labels = {};
-        //         Object.keys(doc.services[key].labels).forEach((keylabel) => {
-        //           labels['' + keylabel] = '' + doc.services[key].labels[keylabel];
-        //         });
-        //         doc.services[key].labels = labels;
-        //       }
-        //     }
-
-        //     // convert environment
-        //     if (doc.services[key].environment) {
-        //       if (!Array.isArray(doc.services[key].environment)) {
-        //         let environment = [];
-        //         Object.keys(doc.services[key].environment).forEach((keyenv) => {
-        //           environment.push(keyenv + '=' + doc.services[key].environment[keyenv]);
-        //         });
-        //         doc.services[key].environment = environment;
-        //       }
-        //     }
-
-        //     // convert network
-        //     if (doc.services[key].networks) {
-        //       if (Array.isArray(doc.services[key].networks)) {
-        //         let networks = {};
-        //         doc.services[key].networks.forEach((network) => {
-        //           if (typeof network === 'object') {
-        //             networks['' + network.name] = network;
-        //           }
-        //           else
-        //             networks['' + network] = {};
-        //         });
-        //         doc.services[key].networks = networks;
-        //       }
-        //     }
-
-        //     // convert devices
-        //     if (doc.services[key].devices) {
-        //       console.log(1)
-        //       if (Array.isArray(doc.services[key].devices)) {
-        //         console.log(2)
-        //         let devices = [];
-        //         doc.services[key].devices.forEach((device) => {
-        //           if(device.indexOf(':') === -1) {
-        //             devices.push(device + ':' + device);
-        //           } else {
-        //             devices.push(device);
-        //           }
-        //         });
-        //         doc.services[key].devices = devices;
-        //       }
-        //     }
-
-        //     // convert command 
-        //     if (doc.services[key].command) {
-        //       if (typeof doc.services[key].command !== 'string') {
-        //         doc.services[key].command = doc.services[key].command.join(' ');
-        //       }
-        //     }
-
-        //     // ensure container_name
-        //     if (!doc.services[key].container_name) {
-        //       doc.services[key].container_name = key;
-        //     }
-
-        //     // convert healthcheck
-        //     if (doc.services[key].healthcheck) {
-        //       const toConvert = ["timeout", "interval", "start_period"];
-        //       toConvert.forEach((valT) => {
-        //         if(typeof doc.services[key].healthcheck[valT] === 'string') {
-        //           let original = doc.services[key].healthcheck[valT];
-        //           let value = parseInt(original);
-        //           if (original.endsWith('m')) {
-        //             value = value * 60;
-        //           } else if (original.endsWith('h')) {
-        //             value = value * 60 * 60;
-        //           } else if (original.endsWith('d')) {
-        //             value = value * 60 * 60 * 24;
-        //           }
-        //           doc.services[key].healthcheck[valT] = value;
-        //         }
-        //       });
-        //     }
-        //   });
-        // }
-
-        // // convert networks
-        // if (doc.networks) {
-        //   if (Array.isArray(doc.networks)) {
-        //     let networks = {};
-        //     doc.networks.forEach((network) => {
-        //       if (typeof network === 'object') {
-        //         networks['' + network.name] = network;
-        //       }
-        //       else
-        //         networks['' + network] = {};
-        //     });
-        //     doc.networks = networks;
-        //   } else {
-        //     let networks = {};
-        //     Object.keys(doc.networks).forEach((key) => {
-        //       networks['' + key] = doc.networks[key] || {};
-        //     });
-        //     doc.networks = networks;
-        //   }
-        // }
-
-        // // convert volumes
-        // if (doc.volumes) {
-        //   if (Array.isArray(doc.volumes)) {
-        //     let volumes = {};
-        //     doc.volumes.forEach((volume) => {
-        //       if (!volume) {
-        //         volume = {};
-        //       }
-        //       if (typeof volume === 'object') {
-        //         volumes['' + volume.name] = volume;
-        //       }
-        //       else
-        //         volumes['' + volume] = {};
-        //     });
-        //     doc.volumes = volumes;
-        //   } else {
-        //     let volumes = {};
-        //     Object.keys(doc.volumes).forEach((key) => {
-        //       volumes['' + key] = doc.volumes[key] || {};
-        //     });
-        //     doc.volumes = volumes;
-        //   }
-        // }
+        if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 &&
+          !doc.services && !doc.networks && !doc.volumes) {
+          doc = {
+            services: Object.assign({}, doc)
+          }
+        }
+
+        // convert to the proper format
+        if (doc.services) {
+          Object.keys(doc.services).forEach((key) => {
+            // convert volumes
+            if (doc.services[key].volumes) {
+              if (Array.isArray(doc.services[key].volumes)) {
+                let volumes = [];
+                doc.services[key].volumes.forEach((volume) => {
+                  if (typeof volume === 'object') {
+                    volumes.push(volume);
+                  } else {
+                    let volumeSplit = volume.split(':');
+                    let volumeObj = {
+                      source: volumeSplit[0],
+                      target: volumeSplit[1],
+                      type: (volume[0] === '/' || volume[0] === '.') ? 'bind' : 'volume',
+                    };
+                    volumes.push(volumeObj);
+                  }
+                });
+                doc.services[key].volumes = volumes;
+              }
+            }
+
+            if(doc.services[key].volumes)
+              Object.values(doc.services[key].volumes).forEach((volume) => {
+                if (volume.source && volume.source[0] === '.') {
+                  let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr"
+                  volume.source = defaultPath + volume.source.replace('.', '');
+                }
+              });
+
+            // convert expose
+            if (doc.services[key].expose) {
+              doc.services[key].expose = doc.services[key].expose.map((port) => {
+                return '' + port;
+              })
+            }
+
+            //convert user
+            if (doc.services[key].user) {
+              doc.services[key].user = '' + doc.services[key].user;
+            }
+
+            // convert labels: 
+            if (doc.services[key].labels) {
+              if (Array.isArray(doc.services[key].labels)) {
+                let labels = {};
+                doc.services[key].labels.forEach((label) => {
+                  const [key, value] = label.split(/=(.*)/s);
+                  labels['' + key] = '' + value;
+                });
+                doc.services[key].labels = labels;
+              }
+              if (typeof doc.services[key].labels == 'object') {
+                let labels = {};
+                Object.keys(doc.services[key].labels).forEach((keylabel) => {
+                  labels['' + keylabel] = '' + doc.services[key].labels[keylabel];
+                });
+                doc.services[key].labels = labels;
+              }
+            }
+
+            // convert environment
+            if (doc.services[key].environment) {
+              if (!Array.isArray(doc.services[key].environment)) {
+                let environment = [];
+                Object.keys(doc.services[key].environment).forEach((keyenv) => {
+                  environment.push(keyenv + '=' + doc.services[key].environment[keyenv]);
+                });
+                doc.services[key].environment = environment;
+              }
+            }
+
+            // convert network
+            if (doc.services[key].networks) {
+              if (Array.isArray(doc.services[key].networks)) {
+                let networks = {};
+                doc.services[key].networks.forEach((network) => {
+                  if (typeof network === 'object') {
+                    networks['' + network.name] = network;
+                  }
+                  else
+                    networks['' + network] = {};
+                });
+                doc.services[key].networks = networks;
+              }
+            }
+
+            // convert devices
+            if (doc.services[key].devices) {
+              console.log(1)
+              if (Array.isArray(doc.services[key].devices)) {
+                console.log(2)
+                let devices = [];
+                doc.services[key].devices.forEach((device) => {
+                  if(device.indexOf(':') === -1) {
+                    devices.push(device + ':' + device);
+                  } else {
+                    devices.push(device);
+                  }
+                });
+                doc.services[key].devices = devices;
+              }
+            }
+
+            // convert command 
+            if (doc.services[key].command) {
+              if (typeof doc.services[key].command !== 'string') {
+                doc.services[key].command = doc.services[key].command.join(' ');
+              }
+            }
+
+            // ensure container_name
+            if (!doc.services[key].container_name) {
+              doc.services[key].container_name = key;
+            }
+
+            // convert healthcheck
+            if (doc.services[key].healthcheck) {
+              const toConvert = ["timeout", "interval", "start_period"];
+              toConvert.forEach((valT) => {
+                if(typeof doc.services[key].healthcheck[valT] === 'string') {
+                  let original = doc.services[key].healthcheck[valT];
+                  let value = parseInt(original);
+                  if (original.endsWith('m')) {
+                    value = value * 60;
+                  } else if (original.endsWith('h')) {
+                    value = value * 60 * 60;
+                  } else if (original.endsWith('d')) {
+                    value = value * 60 * 60 * 24;
+                  }
+                  doc.services[key].healthcheck[valT] = value;
+                }
+              });
+            }
+          });
+        }
+
+        // convert networks
+        if (doc.networks) {
+          if (Array.isArray(doc.networks)) {
+            let networks = {};
+            doc.networks.forEach((network) => {
+              if (typeof network === 'object') {
+                networks['' + network.name] = network;
+              }
+              else
+                networks['' + network] = {};
+            });
+            doc.networks = networks;
+          } else {
+            let networks = {};
+            Object.keys(doc.networks).forEach((key) => {
+              networks['' + key] = doc.networks[key] || {};
+            });
+            doc.networks = networks;
+          }
+        }
+
+        // convert volumes
+        if (doc.volumes) {
+          if (Array.isArray(doc.volumes)) {
+            let volumes = {};
+            doc.volumes.forEach((volume) => {
+              if (!volume) {
+                volume = {};
+              }
+              if (typeof volume === 'object') {
+                volumes['' + volume.name] = volume;
+              }
+              else
+                volumes['' + volume] = {};
+            });
+            doc.volumes = volumes;
+          } else {
+            let volumes = {};
+            Object.keys(doc.volumes).forEach((key) => {
+              volumes['' + key] = doc.volumes[key] || {};
+            });
+            doc.volumes = volumes;
+          }
+        }
+
+        // create default network
+        let hasDefaultNetwork = false;
+        if (doc.services) {
+          Object.keys(doc.services).forEach((key) => {
+            if(!doc.services[key].network_mode) {
+              doc.services[key].network_mode = 'cosmos-' + serviceName + '-default';
+              hasDefaultNetwork = true;
+            }
+          });
+        }
+
+        if(hasDefaultNetwork) {
+          if(!doc.networks) {
+            doc.networks = {}
+          }
+          
+          doc.networks['cosmos-' + serviceName + '-default'] = {
+            Labels: {
+              'cosmos.stack': serviceName,
+            }
+          }
+        }
+
+        // stack up
+        if (doc.services && Object.keys(doc.services).length > 1) {
+          let hasMain = false;
+          Object.keys(doc.services).forEach((key) => {
+            if(!doc.services[key].labels) {
+              doc.services[key].labels = {};
+            }
+            if(!doc.services[key].labels['cosmos.stack'])
+              doc.services[key].labels['cosmos.stack'] = serviceName;
+            if(doc.services[key].labels['cosmos.stack.main'])
+              hasMain = true;
+          });
+
+          if(!hasMain) {
+            Object.keys(doc.services).forEach((key) => {
+              if(!hasMain) {
+                if(doc.services[key].labels['cosmos.stack'] == serviceName) {
+                  doc.services[key].labels['cosmos.stack.main'] = true;
+                  hasMain = true;
+                }
+              }
+            });
+          }
+        }
 
       } catch (e) {
         setYmlError(e.message);
diff --git a/client/src/pages/servapps/containers/newService.jsx b/client/src/pages/servapps/containers/newService.jsx
index 024eb671..74830585 100644
--- a/client/src/pages/servapps/containers/newService.jsx
+++ b/client/src/pages/servapps/containers/newService.jsx
@@ -21,7 +21,6 @@ import { Link } from 'react-router-dom';
 import { smartDockerLogConcat, tryParseProgressLog } from '../../../utils/docker';
 import { LoadingButton } from '@mui/lab';
 import LogLine from '../../../components/logLine';
-import yaml from 'js-yaml';
 
 const preStyle = {
   backgroundColor: '#000',
@@ -85,7 +84,7 @@ const NewDockerService = ({service, refresh}) => {
     setLog([
       'Creating Service...                              ',
     ])
-    API.docker.createService(yaml.dump(service), (newlog) => {
+    API.docker.createService(service, (newlog) => {
       setLog((old) => smartDockerLogConcat(old, newlog));
       preRef.current.scrollTop = preRef.current.scrollHeight;
       if (newlog.includes('[OPERATION SUCCEEDED]')) {
@@ -119,7 +118,7 @@ const NewDockerService = ({service, refresh}) => {
       
         {!log.length && `
 # You are about to create the following service(s):
-${yaml.dump(service)}`
+${JSON.stringify(service, false ,2)}`
         }
         {log.map((l) => {
           return 
diff --git a/client/src/pages/servapps/containers/volumes.jsx b/client/src/pages/servapps/containers/volumes.jsx
index 628b0a29..85f8b951 100644
--- a/client/src/pages/servapps/containers/volumes.jsx
+++ b/client/src/pages/servapps/containers/volumes.jsx
@@ -85,7 +85,12 @@ const VolumeContainerSetup = ({ noCard, containerInfo, frozenVolumes = [], refre
           if(newContainer) return;
           setSubmitting(true);
           const realvalues = {
-            Volumes: values.volumes
+            Volumes: values.volumes.map((volume) => ({
+              Type: volume.Type,
+              Source: volume.Source,
+              Target: volume.Target,
+              ReadOnly: !volume.RW
+            }))
           };
           return API.docker.updateContainer(containerInfo.Name.replace('/', ''), realvalues)
             .then((res) => {
diff --git a/client/src/pages/servapps/servapps.jsx b/client/src/pages/servapps/servapps.jsx
index 3a9d6b02..d88b0c67 100644
--- a/client/src/pages/servapps/servapps.jsx
+++ b/client/src/pages/servapps/servapps.jsx
@@ -136,9 +136,9 @@ const ServApps = ({stack}) => {
 
   const servAppsStacked = servApps && servApps.reduce((acc, app) => {
     // if has label cosmos-stack, add to stack
-    if(!stack && (app.Labels['cosmos-stack'] || app.Labels['com.docker.compose.project'])) {
-      let stackName = app.Labels['cosmos-stack'] || app.Labels['com.docker.compose.project'];
-      let stackMain = app.Labels['cosmos-stack-main'] || (app.Labels['com.docker.compose.container-number'] == '1' && app.Names[0].replace('/', ''));
+    if(!stack && (app.Labels['cosmos-stack'] || app.Labels['cosmos.stack'] || app.Labels['com.docker.compose.project'])) {
+      let stackName = app.Labels['cosmos-stack'] || app.Labels['cosmos.stack'] || app.Labels['com.docker.compose.project'];
+      let stackMain = app.Labels['cosmos-stack-main'] ||  app.Labels['cosmos.stack.main'] || (app.Labels['com.docker.compose.container-number'] == '1' && app.Names[0].replace('/', ''));
       
       if(!acc[stackName]) {
         acc[stackName] = {
@@ -190,7 +190,7 @@ const ServApps = ({stack}) => {
       if(stackMain == app.Names[0].replace('/', '') || !acc[stackName].app) {
         acc[stackName].app = app;
       }
-    } else if (!stack || (stack && (app.Labels['cosmos-stack'] === stack || app.Labels['com.docker.compose.project'] === stack))){
+    } else if (!stack || (stack && (app.Labels['cosmos-stack'] === stack || app.Labels['cosmos.stack'] === stack || app.Labels['com.docker.compose.project'] === stack))){
       // else add to default stack
       acc[app.Names[0]] = {
         type: 'app',
@@ -370,12 +370,36 @@ const ServApps = ({stack}) => {
                   })}
                 
               
+              
+                
+                  URLs
+                
+                
+                  {getContainersRoutes(config, app.name.replace('/', '')).map((route) => {
+                    return 
+                  })}
+                  {/* {getContainersRoutes(config, app.Names[0].replace('/', '')).length == 0 && */}
+                    }
+                      onClick={() => {
+                        setOpenModal(app.app);
+                      }}
+                      onDelete={() => {
+                        setOpenModal(app.app);
+                      }}
+                    />
+                    {/* } */}
+                
+              
               {app.isUpdating ? 
: - + {/* Settings {app.type == "app" && (app.state !== 'running' ? '(Start container to edit)' : '')} @@ -396,7 +420,7 @@ const ServApps = ({stack}) => { }) }} /> Isolate Container Network - + */} { } - - - URLs - - - {getContainersRoutes(config, app.name.replace('/', '')).map((route) => { - return - })} - {/* {getContainersRoutes(config, app.Names[0].replace('/', '')).length == 0 && */} - } - onClick={() => { - setOpenModal(app.app); - }} - onDelete={() => { - setOpenModal(app.app); - }} - /> - {/* } */} - -
{ ["cosmos.system.docker.ram." + app.name.replace('/', '')]: "RAM" }}/>
+
null null null +MELVAR null

diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go index db57119c..4ee3227c 100644 --- a/src/docker/api_blueprint.go +++ b/src/docker/api_blueprint.go @@ -1,7 +1,7 @@ package docker import ( - // "encoding/json" + "encoding/json" "fmt" "net/http" "strings" @@ -12,7 +12,6 @@ import ( "io/ioutil" "os/user" "errors" - // "gopkg.in/yaml.v2" "github.com/docker/go-connections/nat" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" @@ -230,76 +229,23 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) { return } - // decoder := yaml.NewDecoder(req.Body) - // var serviceRequest DockerServiceCreateRequest - // err := decoder.Decode(&serviceRequest) - // if err != nil { - // utils.Error("CreateService - decode - ", err) - // fmt.Fprintf(w, "[OPERATION FAILED] Bad request: "+err.Error(), http.StatusBadRequest, "DS003") - // flusher.Flush() - // utils.HTTPError(w, "Bad request: " + err.Error(), http.StatusBadRequest, "DS003") - // return - // } - - /*CreateService(serviceRequest, - func (msg string) { - fmt.Fprintf(w, msg) - flusher.Flush() - }, - )*/ - - ServiceName := "test-wesh" - - filePath := utils.CONFIGFOLDER + "compose/" + ServiceName - - // Create the folder if does not exist, and output docker-compose.yml - if _, err := os.Stat(utils.CONFIGFOLDER + "compose/"); os.IsNotExist(err) { - os.MkdirAll(utils.CONFIGFOLDER + "compose/", 0750) - } - if _, err := os.Stat(filePath); os.IsNotExist(err) { - os.MkdirAll(filePath, 0750) - } - - // create or truncate the file - file, err := os.Create(filePath + "/docker-compose.yml") - if err != nil { - utils.Error("CreateService - create - ", err) - fmt.Fprintf(w, "[OPERATION FAILED] Internal server error: "+err.Error(), http.StatusInternalServerError, "DS004") - flusher.Flush() - return - } - defer file.Close() - - // write to file - ymlBody := req.Body - writer := bufio.NewWriter(file) - _, err = writer.ReadFrom(ymlBody) - if err != nil { - utils.Error("CreateService - write - ", err) - fmt.Fprintf(w, "[OPERATION FAILED] Internal server error: "+err.Error(), http.StatusInternalServerError, "DS005") - flusher.Flush() - return - } - - writer.Flush() - - // Compose up - err = ComposeUp(filePath, func(message string, outputType int) { - fmt.Fprintf(w, "%s\n", message) - flusher.Flush() - }) - + decoder := json.NewDecoder(req.Body) + var serviceRequest DockerServiceCreateRequest + err := decoder.Decode(&serviceRequest) if err != nil { - utils.Error("CreateService - composeup - ", err) - fmt.Fprintf(w, "[OPERATION FAILED] Internal server error: "+err.Error(), http.StatusInternalServerError, "DS006") + utils.Error("CreateService - decode - ", err) + fmt.Fprintf(w, "[OPERATION FAILED] Bad request: "+err.Error(), http.StatusBadRequest, "DS003") flusher.Flush() + utils.HTTPError(w, "Bad request: " + err.Error(), http.StatusBadRequest, "DS003") return } - - // Write a response to the client - fmt.Fprintf(w, "[OPERATION SUCCESSFUL] Service created successfully", http.StatusOK, "DS007") - flusher.Flush() + CreateService(serviceRequest, + func (msg string) { + fmt.Fprintf(w, msg) + flusher.Flush() + }, + ) } else { utils.Error("CreateService: Method not allowed" + req.Method, nil) utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") @@ -340,21 +286,6 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) var rollbackActions []DockerServiceCreateRollback var err error - serviceName := "" - for serviceName = range serviceRequest.Services { - break - } - - // check if serviceRequest.Networks contains service-default, if not, create it - if _, ok := serviceRequest.Networks[serviceName + "-default"]; !ok { - serviceRequest.Networks[serviceName + "-default"] = ContainerCreateRequestNetwork{ - Name: serviceName + "-default", - Driver: "bridge", - Attachable: true, - Internal: false, - } - } - // Create networks for networkToCreateName, networkToCreate := range serviceRequest.Networks { utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName)) @@ -365,7 +296,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) if err == nil { if networkToCreate.Driver == "" { - networkToCreate.Driver = serviceName + "-default" + networkToCreate.Driver = "bridge" } if (exNetworkDef.Driver != networkToCreate.Driver) { @@ -491,51 +422,50 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) OnLog(fmt.Sprintf("Checking service %s...\n", serviceName)) // If container request a Cosmos network, create and attach it - - /*if (container.Labels["cosmos-force-network-secured"] == "true" || strings.ToLower(container.Labels["cosmos-network-name"]) == "auto") && - container.Labels["cosmos-network-name"] == "" { - utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName)) - OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName)) + // if (container.Labels["cosmos-force-network-secured"] == "true" || strings.ToLower(container.Labels["cosmos-network-name"]) == "auto") && + // container.Labels["cosmos-network-name"] == "" { + // utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName)) + // OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName)) - newNetwork, errNC := CreateCosmosNetwork(serviceName) - if errNC != nil { - utils.Error("CreateService: Network", err) - OnLog(utils.DoErr("Network %s cant be created\n", newNetwork)) - Rollback(rollbackActions, OnLog) - return err - } + // newNetwork, errNC := CreateCosmosNetwork(serviceName) + // if errNC != nil { + // utils.Error("CreateService: Network", err) + // OnLog(utils.DoErr("Network %s cant be created\n", newNetwork)) + // Rollback(rollbackActions, OnLog) + // return err + // } - container.Labels["cosmos-network-name"] = newNetwork + // container.Labels["cosmos-network-name"] = newNetwork - AttachNetworkToCosmos(newNetwork) + // AttachNetworkToCosmos(newNetwork) - if container.Networks == nil { - container.Networks = make(map[string]ContainerCreateRequestServiceNetwork) - } + // if container.Networks == nil { + // container.Networks = make(map[string]ContainerCreateRequestServiceNetwork) + // } - container.Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} + // container.Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} - rollbackActions = append(rollbackActions, DockerServiceCreateRollback{ - Action: "remove", - Type: "network", - Name: newNetwork, - }) + // rollbackActions = append(rollbackActions, DockerServiceCreateRollback{ + // Action: "remove", + // Type: "network", + // Name: newNetwork, + // }) - utils.Log(fmt.Sprintf("Created secure network %s", newNetwork)) - OnLog(fmt.Sprintf("Created secure network %s\n", newNetwork)) - } else if container.Labels["cosmos-network-name"] != "" { - // Container has a declared a Cosmos network, check if it exists and connect to it - utils.Log(fmt.Sprintf("Checking declared network %s...", container.Labels["cosmos-network-name"])) - OnLog(fmt.Sprintf("Checking declared network %s...\n", container.Labels["cosmos-network-name"])) - - _, err := DockerClient.NetworkInspect(DockerContext, container.Labels["cosmos-network-name"], doctype.NetworkInspectOptions{}) - if err == nil { - utils.Log(fmt.Sprintf("Connecting to declared network %s...", container.Labels["cosmos-network-name"])) - OnLog(fmt.Sprintf("Connecting to declared network %s...\n", container.Labels["cosmos-network-name"])) + // utils.Log(fmt.Sprintf("Created secure network %s", newNetwork)) + // OnLog(fmt.Sprintf("Created secure network %s\n", newNetwork)) + // } else if container.Labels["cosmos-network-name"] != "" { + // // Container has a declared a Cosmos network, check if it exists and connect to it + // utils.Log(fmt.Sprintf("Checking declared network %s...", container.Labels["cosmos-network-name"])) + // OnLog(fmt.Sprintf("Checking declared network %s...\n", container.Labels["cosmos-network-name"])) + + // _, err := DockerClient.NetworkInspect(DockerContext, container.Labels["cosmos-network-name"], doctype.NetworkInspectOptions{}) + // if err == nil { + // utils.Log(fmt.Sprintf("Connecting to declared network %s...", container.Labels["cosmos-network-name"])) + // OnLog(fmt.Sprintf("Connecting to declared network %s...\n", container.Labels["cosmos-network-name"])) - AttachNetworkToCosmos(container.Labels["cosmos-network-name"]) - } - }*/ + // AttachNetworkToCosmos(container.Labels["cosmos-network-name"]) + // } + // } utils.Log(fmt.Sprintf("Creating container %s...", container.Name)) OnLog(fmt.Sprintf("Creating container %s...\n", container.Name)) @@ -889,8 +819,6 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) // connect to networks for netName, netConfig := range container.Networks { utils.Log("CreateService: Connecting to network: " + netName) - - //TODO: THIS IS WRONG https://pkg.go.dev/github.com/docker/docker@v24.0.7+incompatible/api/types/network#EndpointSettings err = DockerClient.NetworkConnect(DockerContext, netName, container.Name, &network.EndpointSettings{ Aliases: netConfig.Aliases, IPAddress: netConfig.IPV4Address, diff --git a/src/docker/compose.go b/src/docker/compose.go deleted file mode 100644 index 892510b3..00000000 --- a/src/docker/compose.go +++ /dev/null @@ -1,58 +0,0 @@ -package docker - -import ( - "bufio" - "io" - "os/exec" - - "github.com/azukaar/cosmos-server/src/utils" -) - -const ( - Stdout = iota - Stderr -) - - -// readOutput reads from the given reader and uses the callback function to handle the output. -func readOutput(r io.Reader, callback func(message string, outputType int), outputType int) { - scanner := bufio.NewScanner(r) - for scanner.Scan() { - callback(scanner.Text(), outputType) - } -} - -// ExecCommand runs the specified command and uses a callback function to handle the output. -func ExecCommand(callback func(message string, outputType int), args ...string) error { - cmd := exec.Command(args[0], args[1:]...) - utils.Debug("Running command: " + cmd.String()) - - callback("Running command: " + cmd.String(), Stdout) - - // Create pipes for stdout and stderr - stdoutPipe, err := cmd.StdoutPipe() - if err != nil { - return err - } - - stderrPipe, err := cmd.StderrPipe() - if err != nil { - return err - } - - // Start the command - if err := cmd.Start(); err != nil { - return err - } - - // Read from stdout and stderr in separate goroutines - go readOutput(stdoutPipe, callback, Stdout) - go readOutput(stderrPipe, callback, Stderr) - - // Wait for the command to finish - return cmd.Wait() -} - -func ComposeUp(filePath string, callback func(message string, outputType int)) error { - return ExecCommand(callback, "docker", "compose", "--project-directory", filePath, "up", "--remove-orphans", "-d") -} \ No newline at end of file diff --git a/src/docker/network.go b/src/docker/network.go index 17a9f18f..1a4d606f 100644 --- a/src/docker/network.go +++ b/src/docker/network.go @@ -9,7 +9,7 @@ import ( "fmt" "github.com/azukaar/cosmos-server/src/utils" - + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types" network "github.com/docker/docker/api/types/network" natting "github.com/docker/go-connections/nat" @@ -393,95 +393,26 @@ var DebouncedNetworkCleanUp = _debounceNetworkCleanUp() func NetworkCleanUp() { config := utils.GetMainConfig() - if(config.DockerConfig.SkipPruneNetwork) { - return - } - DockerNetworkLock <- true - defer func() { <-DockerNetworkLock }() - + defer func() { <-DockerNetworkLock }() - utils.Log("Cleaning up orphan networks...") - - // list every network - networks, err := DockerClient.NetworkList(DockerContext, types.NetworkListOptions{}) - if err != nil { - utils.Error("NetworkCleanUpList", err) - return - } - + pruneFilters := filters.NewArgs() - // check if network is empty or has only self as container - for _, networkHollow := range networks { - utils.Debug("Checking network: " + networkHollow.Name) + if(!config.DockerConfig.SkipPruneNetwork) { + report, err := DockerClient.NetworksPrune(DockerContext, pruneFilters) + if err != nil { + utils.Error("[DOCKER] Error pruning networks", err) + } - if(networkHollow.Name == "bridge" || networkHollow.Name == "host" || networkHollow.Name == "none") { - continue - } - - // inspect network because the Docker API is a complete mess :) - network, err := DockerClient.NetworkInspect(DockerContext, networkHollow.ID, types.NetworkInspectOptions{}) + utils.Log("Pruned networks: " + fmt.Sprintf("%v", report.NetworksDeleted)) + } + + if(!config.DockerConfig.SkipPruneImages) { + report, err := DockerClient.ImagesPrune(DockerContext, pruneFilters) if err != nil { - utils.Error("NetworkCleanUpInspect", err) - continue - } - - if(len(network.Containers) > 1) { - continue - } - - utils.Debug("Ready to Check network: " + network.Name) - - if(len(network.Containers) == 0) { - utils.Log("Removing orphan network: " + network.Name) - err := DockerClient.NetworkRemove(DockerContext, network.ID) - if err != nil { - utils.Error("DockerNetworkCleanupRemove", err) - } - continue + utils.Error("[DOCKER] Error pruning images", err) } - - self := os.Getenv("HOSTNAME") - if self == "" { - utils.Warn("Skipping zombie network cleanup because not a docker cosmos container") - continue - } - - utils.Debug("Checking self name: " + self) - utils.Debug("Checking non-empty network: " + network.Name) - containsCosmos := false - for _, container := range network.Containers { - utils.Debug("Checking name: " + container.Name) - if(container.Name == self) { - containsCosmos = true - } - } - - if(containsCosmos) { - // docker inspect self - selfContainer, err := DockerClient.ContainerInspect(DockerContext, self) - if err != nil { - utils.Error("NetworkCleanUpInspectSelf", err) - continue - } - - // check if self is network_mode to this network - if(string(selfContainer.HostConfig.NetworkMode) == network.Name) { - utils.Warn("Skipping network cleanup because self is network_mode to this network") - continue - } - - utils.Log("Disconnecting and removing zombie network: " + network.Name) - err = DockerClient.NetworkDisconnect(DockerContext, network.ID, self, true) - if err != nil { - utils.Error("DockerNetworkCleanupDisconnect", err) - continue - } - err = DockerClient.NetworkRemove(DockerContext, network.ID) - if err != nil { - utils.Error("DockerNetworkCleanupRemove", err) - } - } + utils.Log("Pruned images: " + fmt.Sprintf("%v", report.ImagesDeleted)) } } diff --git a/src/utils/types.go b/src/utils/types.go index 366df921..a0743237 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -147,6 +147,7 @@ type SmartShieldPolicy struct { type DockerConfig struct { SkipPruneNetwork bool + SkipPruneImages bool DefaultDataPath string } From a2876401f4a3ccf9dbdf800111f09ce78a203737 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 21 Jan 2024 15:38:18 +0000 Subject: [PATCH 03/31] [skip ci] pre-0.14 --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index d2e2edb6..01965a9b 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ - Removed all sort of container bootstrapping (much faster boot) - Added image clean up - Replaced network clean up by vanilla docker prune + - Fixed display issue with container having no IP ## Version 0.13.2 - Fix display issue with fault network configurations From 451fe2db2668c9126e4b4633017a5c970a798caa Mon Sep 17 00:00:00 2001 From: Kawanaao <149584315+Kawanaao@users.noreply.github.com> Date: Mon, 22 Jan 2024 01:49:48 +0200 Subject: [PATCH 04/31] Fixed XSS (#184) --- client/src/components/logLine.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/logLine.jsx b/client/src/components/logLine.jsx index 2f07af53..af4c14a6 100644 --- a/client/src/components/logLine.jsx +++ b/client/src/components/logLine.jsx @@ -10,8 +10,8 @@ function decodeUnicode(str) { const LogLine = ({ message, docker, isMobile }) => { let html = decodeUnicode(message) .replace('\u0001\u0000\u0000\u0000\u0000\u0000\u0000', '') + .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') .replace(/(?:\r\n|\r|\n)/g, '
') - .replace(/ /g, ' ') .replace(/�/g, '') .replace(/\x1b\[([0-9]{1,2}(?:;[0-9]{1,2})*)?m/g, (match, p1) => { if (!p1) { From a13b7e8a81e40c836ff467fe6e5302c66ea50bab Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Mon, 22 Jan 2024 19:13:22 +0000 Subject: [PATCH 05/31] [skip ci] pre-0.14 --- changelog.md | 7 +- client/src/api/docker.jsx | 11 + client/src/components/apiModal.jsx | 2 +- client/src/components/logsInModal.jsx | 2 +- .../servapps/containers/compose-editor.jsx | 59 ++++ .../servapps/containers/docker-compose.jsx | 253 ++++++++++-------- .../src/pages/servapps/containers/index.jsx | 5 + .../pages/servapps/containers/newService.jsx | 41 ++- .../pages/servapps/containers/overview.jsx | 4 +- readme.md | 1 - src/docker/api_containers.go | 79 ++++++ src/docker/api_listcontainers.go | 43 --- src/docker/export.go | 93 ++++--- src/httpServer.go | 1 + 14 files changed, 382 insertions(+), 219 deletions(-) create mode 100644 client/src/pages/servapps/containers/compose-editor.jsx create mode 100644 src/docker/api_containers.go delete mode 100644 src/docker/api_listcontainers.go diff --git a/changelog.md b/changelog.md index 01965a9b..1e8da138 100644 --- a/changelog.md +++ b/changelog.md @@ -1,11 +1,12 @@ ## Version 0.14.0 - - Cosmos is now fully dockerless + - Cosmos is now fully functional dockerless + - Reworked Cosmos Compose for better compatibility with docker-compose.yml files + - Added a "compose" tab to edit containers in text mode - Improved network IP resolution for containers, including supporting any network mode - - Integrated MongoDB as container + - Added support for markets and template directly with docker-compose.yml files - Removed all sort of container bootstrapping (much faster boot) - Added image clean up - Replaced network clean up by vanilla docker prune - - Fixed display issue with container having no IP ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx index 84e0a8c0..fd7cf57d 100644 --- a/client/src/api/docker.jsx +++ b/client/src/api/docker.jsx @@ -111,6 +111,16 @@ function updateContainer(containerId, values) { })) } +function exportContainer(containerId, values) { + return wrap(fetch('/cosmos/api/servapps/' + containerId + '/export', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(values), + })) +} + function listContainerNetworks(containerId) { return wrap(fetch('/cosmos/api/servapps/' + containerId + '/networks', { method: 'GET', @@ -349,4 +359,5 @@ export { pullImage, autoUpdate, updateContainerImage, + exportContainer, }; \ No newline at end of file diff --git a/client/src/components/apiModal.jsx b/client/src/components/apiModal.jsx index f2c6cbe4..e5d7bff0 100644 --- a/client/src/components/apiModal.jsx +++ b/client/src/components/apiModal.jsx @@ -15,7 +15,7 @@ const preStyle = { padding: '10px', borderRadius: '5px', overflow: 'auto', - maxHeight: '500px', + maxHeight: '520px', maxWidth: '100%', width: '100%', margin: '0', diff --git a/client/src/components/logsInModal.jsx b/client/src/components/logsInModal.jsx index 03565a38..bb22493f 100644 --- a/client/src/components/logsInModal.jsx +++ b/client/src/components/logsInModal.jsx @@ -16,7 +16,7 @@ const preStyle = { padding: '10px', borderRadius: '5px', overflow: 'auto', - maxHeight: '500px', + maxHeight: '520px', maxWidth: '100%', width: '100%', margin: '0', diff --git a/client/src/pages/servapps/containers/compose-editor.jsx b/client/src/pages/servapps/containers/compose-editor.jsx new file mode 100644 index 00000000..ae0502e6 --- /dev/null +++ b/client/src/pages/servapps/containers/compose-editor.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Alert, Checkbox, Chip, CircularProgress, Stack, Typography, useMediaQuery } from '@mui/material'; +import MainCard from '../../../components/MainCard'; +import { ContainerOutlined, DashboardOutlined, DesktopOutlined, InfoCircleOutlined, NodeExpandOutlined, PlayCircleOutlined, PlusCircleOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons'; +import { getFaviconURL, getContainersRoutes } from '../../../utils/routes'; +import HostChip from '../../../components/hostChip'; +import ExposeModal from '../exposeModal'; +import * as API from '../../../api'; +import RestartModal from '../../config/users/restart'; +import GetActions from '../actionBar'; +import { ServAppIcon } from '../../../utils/servapp-icon'; +import MiniPlotComponent from '../../dashboard/components/mini-plot'; +import UploadButtons from '../../../components/fileUpload'; +import NewDockerService from './newService'; + +const info = { + backgroundColor: 'rgba(0, 0, 0, 0.1)', + padding: '10px', + borderRadius: '5px', +} + +const ContainerComposeEdit = ({ containerInfo, config, refresh, updatesAvailable, selfName }) => { + const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm')); + const [openModal, setOpenModal] = React.useState(false); + const [openRestartModal, setOpenRestartModal] = React.useState(false); + const [isUpdating, setIsUpdating] = React.useState(false); + + const { Name, Config, NetworkSettings, State } = containerInfo; + const Image = Config.Image; + const IPAddress = NetworkSettings.Networks?.[Object.keys(NetworkSettings.Networks)[0]]?.IPAddress; + const Health = State.Health; + const healthStatus = Health ? Health.Status : 'Healthy'; + const healthIconColor = healthStatus === 'Healthy' ? 'green' : 'red'; + const routes = getContainersRoutes(config, Name.replace('/', '')); + + const [exportedCompose, setExportedCompose] = React.useState(null); + + React.useEffect(() => { + API.docker.exportContainer(Name.replace('/', '')).then((res) => { + setExportedCompose({ + services: { + [Name.replace('/', '')]: res.data + } + }); + }); + }, [Name]); + + let refreshAll = refresh ? (() => refresh().then(() => { + setIsUpdating(false); + })) : (() => {setIsUpdating(false);}); + + return ( +
+ {exportedCompose && } +
+ ); +}; + +export default ContainerComposeEdit; \ No newline at end of file diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index 25265c28..1a940cd3 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -35,7 +35,7 @@ import cmp from 'semver-compare'; import { HostnameChecker, getHostnameFromName } from '../../../utils/routes'; import { CosmosContainerPicker } from '../../config/users/containerPicker'; import { randomString } from '../../../utils/indexs'; -import { has } from 'lodash'; +import { has, set } from 'lodash'; function checkIsOnline() { API.isOnline().then((res) => { @@ -53,7 +53,7 @@ const preStyle = { padding: '10px', borderRadius: '5px', overflow: 'auto', - maxHeight: '500px', + maxHeight: '520px', maxWidth: '100%', width: '100%', margin: '0', @@ -77,101 +77,7 @@ const isNewerVersion = (minver) => { return cmp(version, minver) === -1; } -const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaultName }) => { - const cleanDefaultName = defaultName && defaultName.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-]/g, ''); - const [step, setStep] = useState(0); - const [isLoading, setIsLoading] = useState(false); - const [openModal, setOpenModal] = useState(false); - const [dockerCompose, setDockerCompose] = useState(''); - const [service, setService] = useState({}); - const [ymlError, setYmlError] = useState(''); - const [serviceName, setServiceName] = useState(cleanDefaultName || 'my-service'); - const [hostnames, setHostnames] = useState({}); - const [overrides, setOverrides] = useState({}); - const [context, setContext] = useState({}); - const [installer, setInstaller] = useState(installerInit); - const [config, setConfig] = useState({}); - - let hostnameErrors = () => { - let broken = false; - Object.values(hostnames).forEach((service) => { - Object.values(service).forEach((route) => { - if(!route.host || route.host.match(/\s/g)) { - broken = true; - } - }); - }); - return broken; - } - - const [passwords, setPasswords] = useState([ - randomString(24), - randomString(24), - randomString(24), - randomString(24) - ]); - - const resetPassword = () => { - setPasswords([ - randomString(24), - randomString(24), - randomString(24), - randomString(24) - ]); - } - - - function refreshConfig() { - API.config.get().then((res) => { - setConfig(res.data); - }); - } - - React.useEffect(() => { - refreshConfig(); - }, []); - - useEffect(() => { - if (!openModal) { - return; - } - if(dockerComposeInit) - fetch(dockerComposeInit) - .then((res) => res.text()) - .then((text) => { - setDockerCompose(text); - }); - }, [openModal, dockerComposeInit]); - - useEffect(() => { - if (!openModal || installer) { - return; - } - - setYmlError(''); - if (dockerCompose === '') { - return; - } - - let isJson = dockerCompose && dockerCompose.trim().startsWith('{') && dockerCompose.trim().endsWith('}'); - - if (isJson) { - if (dockerCompose.trim().match(/{\n*\s*"cosmos-installer"\s*:\s*{/)) { - setInstaller(true); - return; - } - try { - let doc = JSON.parse(dockerCompose); - if(typeof doc['cosmos-installer'] === 'object') { - setInstaller(true); - } - setService(doc); - } catch (e) { - setYmlError(e.message); - } - } - else { - // if Yml +const convertDockerCompose = (config, serviceName, dockerCompose, setYmlError) => { let doc; try { @@ -408,7 +314,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul Object.keys(doc.services).forEach((key) => { if(!hasMain) { if(doc.services[key].labels['cosmos.stack'] == serviceName) { - doc.services[key].labels['cosmos.stack.main'] = true; + doc.services[key].labels['cosmos.stack.main'] = "true"; hasMain = true; } } @@ -416,14 +322,101 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } } + // cosmos features + if (doc.services) { + Object.keys(doc.services).forEach((key) => { + if(doc.services[key]['x-routes']) { + doc.services[key].routes = doc.services[key]['x-routes']; + delete doc.services[key]['x-routes']; + } + }); + + if(doc.services['x-post_install']) { + doc['cosmos-installer'] = { + post_install: doc.services['x-post_install'], + } + delete doc.services['x-post_install']; + } + } + + // enable market features + if (doc['x-cosmos-installer']) { + doc['cosmos-installer'] = doc['x-cosmos-installer']; + delete doc['x-cosmos-installer']; + } + + return doc; } catch (e) { setYmlError(e.message); return; } +} + +const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaultName }) => { + const cleanDefaultName = defaultName && defaultName.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-]/g, ''); + const [step, setStep] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const [openModal, setOpenModal] = useState(false); + const [dockerCompose, setDockerCompose] = useState(''); + const [service, setService] = useState({}); + const [ymlError, setYmlError] = useState(''); + const [serviceName, setServiceName] = useState(null); + const [hostnames, setHostnames] = useState({}); + const [overrides, setOverrides] = useState({}); + const [context, setContext] = useState({}); + const [installer, setInstaller] = useState(installerInit); + const [config, setConfig] = useState({}); - setService(doc); + let hostnameErrors = () => { + let broken = false; + Object.values(hostnames).forEach((service) => { + Object.values(service).forEach((route) => { + if(!route.host || route.host.match(/\s/g)) { + broken = true; + } + }); + }); + return broken; + } + + const [passwords, setPasswords] = useState([ + randomString(24), + randomString(24), + randomString(24), + randomString(24) + ]); + + const resetPassword = () => { + setPasswords([ + randomString(24), + randomString(24), + randomString(24), + randomString(24) + ]); + } + + + function refreshConfig() { + API.config.get().then((res) => { + setConfig(res.data); + }); + } + + React.useEffect(() => { + refreshConfig(); + }, []); + + useEffect(() => { + if (!openModal) { + return; } - }, [openModal, dockerCompose]); + if(dockerComposeInit) + fetch(dockerComposeInit) + .then((res) => res.text()) + .then((text) => { + setDockerCompose(text); + }); + }, [openModal, dockerComposeInit]); useEffect(() => { setOverrides({}); @@ -434,19 +427,43 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul return; } + setYmlError(''); + if (dockerCompose === '') { + return; + } + + let isJson = dockerCompose && dockerCompose.trim().startsWith('{') && dockerCompose.trim().endsWith('}'); + try { - if (installer) { - const rendered = whiskers.render(dockerCompose.replace(/{StaticServiceName}/ig, serviceName), { - ServiceName: serviceName, - Hostnames: hostnames, - Context: context, - Passwords: passwords, - CPU_ARCH: API.CPU_ARCH, - CPU_AVX: API.CPU_AVX, - DefaultDataPath: (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr", - }); - - const jsoned = JSON.parse(rendered); + const rendered = whiskers.render(dockerCompose.replace(/{StaticServiceName}/ig, serviceName), { + ServiceName: serviceName, + Hostnames: hostnames, + Context: context, + Passwords: passwords, + CPU_ARCH: API.CPU_ARCH, + CPU_AVX: API.CPU_AVX, + DefaultDataPath: (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr", + }); + + let jsoned; + if(isJson) { + jsoned = JSON.parse(rendered); + } else { + jsoned = convertDockerCompose(config, serviceName, rendered, setYmlError); + } + + if(!serviceName) { + if(jsoned['name'] && jsoned['name'].trim() !== '') { + setServiceName(jsoned['name']); + } else if (jsoned['services'] && Object.keys(jsoned['services']).length > 0) { + setServiceName(Object.keys(jsoned['services'])[0]); + } else { + setServiceName(cleanDefaultName || 'default-name'); + } + } + + if (typeof jsoned['cosmos-installer'] === 'object' || typeof jsoned['x-cosmos-installer'] === 'object') { + setInstaller(true); if (jsoned.services) { // GENERATE HOSTNAMES FORM @@ -549,6 +566,12 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } setService(jsoned); + } else { + if(!isJson) { + setService(convertDockerCompose(config, serviceName, dockerCompose, setYmlError)); + } else { + setService(JSON.parse(dockerCompose)); + } } } catch (e) { setYmlError(e.message); @@ -566,7 +589,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul setContext({}); setDockerCompose(''); setInstaller(installerInit); - setServiceName(cleanDefaultName || 'default-name'); + setServiceName(null); resetPassword(); } @@ -606,7 +629,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul color: '#EEE', } }} - rows={20}> + minRows={20}> } {step === 0 && installer && <> @@ -806,7 +829,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul setDockerCompose(''); setYmlError(''); setInstaller(false); - setServiceName(''); + setServiceName(null); setContext({}); setHostnames({}); setOverrides({}); diff --git a/client/src/pages/servapps/containers/index.jsx b/client/src/pages/servapps/containers/index.jsx index 574d961b..4eec1854 100644 --- a/client/src/pages/servapps/containers/index.jsx +++ b/client/src/pages/servapps/containers/index.jsx @@ -19,6 +19,7 @@ import VolumeContainerSetup from './volumes'; import DockerTerminal from './terminal'; import ContainerMetrics from '../../dashboard/containerMetrics'; import EventExplorerStandalone from '../../dashboard/eventsExplorerStandalone'; +import ContainerComposeEdit from './compose-editor'; const ContainerIndex = () => { const { containerName } = useParams(); @@ -73,6 +74,10 @@ const ContainerIndex = () => { title: 'Terminal', children: }, + { + title: 'Compose', + children: + }, { title: 'Docker', children: diff --git a/client/src/pages/servapps/containers/newService.jsx b/client/src/pages/servapps/containers/newService.jsx index 74830585..6e88321c 100644 --- a/client/src/pages/servapps/containers/newService.jsx +++ b/client/src/pages/servapps/containers/newService.jsx @@ -1,7 +1,7 @@ import * as React from 'react'; import MainCard from '../../../components/MainCard'; import RestartModal from '../../config/users/restart'; -import { Alert, Button, Chip, Divider, Stack, useMediaQuery } from '@mui/material'; +import { Alert, Button, Chip, Divider, Stack, TextField, useMediaQuery } from '@mui/material'; import HostChip from '../../../components/hostChip'; import { RouteMode, RouteSecurity } from '../../../components/routeComponents'; import { getFaviconURL } from '../../../utils/routes'; @@ -28,7 +28,7 @@ const preStyle = { padding: '10px', borderRadius: '5px', overflow: 'auto', - maxHeight: '500px', + maxHeight: '520px', maxWidth: '100%', width: '100%', margin: '0', @@ -52,7 +52,7 @@ const preStyle = { opacity: '1', } -const NewDockerService = ({service, refresh}) => { +const NewDockerService = ({service, refresh, edit}) => { const { containerName } = useParams(); const [container, setContainer] = React.useState(null); const [config, setConfig] = React.useState(null); @@ -61,6 +61,7 @@ const NewDockerService = ({service, refresh}) => { const [openModal, setOpenModal] = React.useState(false); const preRef = React.useRef(null); const screenMin = useMediaQuery((theme) => theme.breakpoints.up('sm')) + const [dockerCompose, setDockerCompose] = React.useState(JSON.stringify(service, null, 2)); const installer = {...service['cosmos-installer']}; service = {...service}; @@ -84,7 +85,7 @@ const NewDockerService = ({service, refresh}) => { setLog([ 'Creating Service... ', ]) - API.docker.createService(service, (newlog) => { + API.docker.createService(JSON.parse(dockerCompose), (newlog) => { setLog((old) => smartDockerLogConcat(old, newlog)); preRef.current.scrollTop = preRef.current.scrollHeight; if (newlog.includes('[OPERATION SUCCEEDED]')) { @@ -97,7 +98,7 @@ const NewDockerService = ({service, refresh}) => { } return
- + {!isDone && { variant="contained" color="primary" fullWidth - className='shinyButton' + className={edit ? '' : 'shinyButton'} loading={log.length && !isDone} - startIcon={} - >Create} + startIcon={edit ? : } + >{edit ? 'Edit': 'Create'}} {isDone && Service Created! {installer && installer['post-install'] && installer['post-install'].map(m =>{ return {m.label} })} } -
+      {edit && !isDone && log.length ?  : null}
+      {log.length || !edit ? 
         {!log.length && `
-# You are about to create the following service(s):
-${JSON.stringify(service, false ,2)}`
+${dockerCompose}`
         }
         {log.map((l) => {
           return 
         })}
-      
+
: + + setDockerCompose(e.target.value)} + sx={{...preStyle, + }} + InputProps={{ + sx: { + color: '#EEE', + }, + }} + > + }
; diff --git a/client/src/pages/servapps/containers/overview.jsx b/client/src/pages/servapps/containers/overview.jsx index f993a232..7bb0c261 100644 --- a/client/src/pages/servapps/containers/overview.jsx +++ b/client/src/pages/servapps/containers/overview.jsx @@ -145,7 +145,7 @@ const ContainerOverview = ({ containerInfo, config, refresh, updatesAvailable, s
{healthStatus}
Settings {State.Status !== 'running' ? '(Start container to edit)' : ''} - + {/* Isolate Container Network - + */} null null null -MELVAR null

diff --git a/src/docker/api_containers.go b/src/docker/api_containers.go new file mode 100644 index 00000000..27d33229 --- /dev/null +++ b/src/docker/api_containers.go @@ -0,0 +1,79 @@ +package docker + +import ( + "net/http" + "strconv" + "encoding/json" + + "github.com/azukaar/cosmos-server/src/utils" + + "github.com/gorilla/mux" +) + +var maxLimit = 1000 + +func ListContainersRoute(w http.ResponseWriter, req *http.Request) { + if utils.AdminOnly(w, req) != nil { + return + } + + limit, _ := strconv.Atoi(req.URL.Query().Get("limit")) + // from, _ := req.URL.Query().Get("from") + + if limit == 0 { + limit = maxLimit + } + + if(req.Method == "GET") { + containers, err := ListContainers() + + if err != nil { + utils.Error("ListContainersRoute: Error while getting containers", err) + utils.HTTPError(w, "Containers Get Error", http.StatusInternalServerError, "DL001") + return + } + + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + "data": containers, + }) + } else { + utils.Error("UserList: Method not allowed" + req.Method, nil) + utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") + return + } +} + +func ExportContainerRoute(w http.ResponseWriter, req *http.Request) { + if utils.AdminOnly(w, req) != nil { + return + } + + if req.Method == "GET" { + vars := mux.Vars(req) + containerID := vars["containerId"] + + errD := Connect() + if errD != nil { + utils.Error("exportContainer", errD) + utils.HTTPError(w, "Internal server error: "+errD.Error(), http.StatusInternalServerError, "EC001") + return + } + + service, err := ExportContainer(containerID) + if err != nil { + utils.Error("exportContainer: Error while exporting container", err) + utils.HTTPError(w, "Container Export Error: "+err.Error(), http.StatusInternalServerError, "EC002") + return + } + + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + "data": service, + }) + } else { + utils.Error("exportContainer: Method not allowed " + req.Method, nil) + utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") + return + } +} diff --git a/src/docker/api_listcontainers.go b/src/docker/api_listcontainers.go deleted file mode 100644 index e270c52f..00000000 --- a/src/docker/api_listcontainers.go +++ /dev/null @@ -1,43 +0,0 @@ -package docker - -import ( - "net/http" - "strconv" - "encoding/json" - - "github.com/azukaar/cosmos-server/src/utils" -) - -var maxLimit = 1000 - -func ListContainersRoute(w http.ResponseWriter, req *http.Request) { - if utils.AdminOnly(w, req) != nil { - return - } - - limit, _ := strconv.Atoi(req.URL.Query().Get("limit")) - // from, _ := req.URL.Query().Get("from") - - if limit == 0 { - limit = maxLimit - } - - if(req.Method == "GET") { - containers, err := ListContainers() - - if err != nil { - utils.Error("ListContainersRoute: Error while getting containers", err) - utils.HTTPError(w, "Containers Get Error", http.StatusInternalServerError, "DL001") - return - } - - json.NewEncoder(w).Encode(map[string]interface{}{ - "status": "OK", - "data": containers, - }) - } else { - utils.Error("UserList: Method not allowed" + req.Method, nil) - utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") - return - } -} \ No newline at end of file diff --git a/src/docker/export.go b/src/docker/export.go index d3421082..078088ce 100644 --- a/src/docker/export.go +++ b/src/docker/export.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "bytes" + "errors" "gopkg.in/yaml.v2" "os" @@ -19,41 +20,12 @@ import ( var ExportError = "" -func ExportDocker() { - config := utils.GetMainConfig() - if config.NewInstall { - return - } - - ExportError = "" - - errD := Connect() - if errD != nil { - ExportError = "Export Docker - cannot connect - " + errD.Error() - utils.MajorError("ExportDocker - connect - ", errD) - return - } - - finalBackup := DockerServiceCreateRequest{} - - // List containers - containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{}) - if err != nil { - utils.MajorError("ExportDocker - Cannot list containers", err) - ExportError = "Export Docker - Cannot list containers - " + err.Error() - return - } - - - // Convert the containers into your custom format - var services = make(map[string]ContainerCreateRequestContainer) - for _, container := range containers { +func ExportContainer(containerID string) (ContainerCreateRequestContainer, error) { // Fetch detailed info of each container - detailedInfo, err := DockerClient.ContainerInspect(DockerContext, container.ID) + detailedInfo, err := DockerClient.ContainerInspect(DockerContext, containerID) if err != nil { - utils.MajorError("Export Docker - Cannot inspect container" + container.Names[0], err) - ExportError = "Export Docker - Cannot inspect container" + container.Names[0] + " - " + err.Error() - return + ExportError = "Export Docker - Cannot inspect container" + containerID + " - " + err.Error() + return ContainerCreateRequestContainer{}, errors.New(ExportError) } // Map the detailedInfo to your ContainerCreateRequestContainer struct @@ -105,12 +77,12 @@ func ExportDocker() { Volumes: func() []mount.Mount { mounts := []mount.Mount{} for _, m := range detailedInfo.Mounts { - mount := mount.Mount{ - Type: m.Type, - Source: m.Source, - Target: m.Destination, - ReadOnly: !m.RW, - // Consistency: mount.Consistency(m.Consistency), + mount := mount.Mount{ + Type: m.Type, + Source: m.Source, + Target: m.Destination, + ReadOnly: !m.RW, + // Consistency: mount.Consistency(m.Consistency), } if m.Type == "volume" { @@ -175,8 +147,47 @@ func ExportDocker() { // for _, port := range detailedInfo.Config.ExposedPorts { // } - - services[strings.TrimPrefix(detailedInfo.Name, "/")] = service + + return service, nil +} + +func ExportDocker() { + config := utils.GetMainConfig() + if config.NewInstall { + return + } + + ExportError = "" + + errD := Connect() + if errD != nil { + ExportError = "Export Docker - cannot connect - " + errD.Error() + utils.MajorError("ExportDocker - connect - ", errD) + return + } + + finalBackup := DockerServiceCreateRequest{} + + // List containers + containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{}) + if err != nil { + utils.MajorError("ExportDocker - Cannot list containers", err) + ExportError = "Export Docker - Cannot list containers - " + err.Error() + return + } + + + // Convert the containers into your custom format + var services = make(map[string]ContainerCreateRequestContainer) + + for _, container := range containers { + service, err := ExportContainer(container.ID) + if err != nil { + utils.MajorError("ExportDocker - Cannot export container", err) + return + } + + services[strings.TrimPrefix(service.Name, "/")] = service } // List networks diff --git a/src/httpServer.go b/src/httpServer.go index db94ce1d..d8cf7745 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -391,6 +391,7 @@ func InitServer() *mux.Router { srapi.HandleFunc("/api/servapps/{containerId}/logs", docker.GetContainerLogsRoute) srapi.HandleFunc("/api/servapps/{containerId}/terminal/{action}", docker.TerminalRoute) srapi.HandleFunc("/api/servapps/{containerId}/update", docker.UpdateContainerRoute) + srapi.HandleFunc("/api/servapps/{containerId}/export", docker.ExportContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/", docker.GetContainerRoute) srapi.HandleFunc("/api/servapps/{containerId}/network/{networkId}", docker.NetworkContainerRoutes) srapi.HandleFunc("/api/servapps/{containerId}/networks", docker.NetworkContainerRoutes) From 5cae6d92530adeff86f023f16c9f23e5148019ad Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Tue, 23 Jan 2024 18:42:39 +0000 Subject: [PATCH 06/31] [release] v0.14.0-unstable1 --- client/src/pages/home/index.jsx | 7 + .../servapps/containers/docker-compose.jsx | 8 +- src/docker/api_blueprint.go | 71 ++--- src/docker/bootstrap.go | 300 +++++++++--------- src/docker/docker.go | 17 + src/docker/events.go | 2 +- src/docker/run.go | 22 +- src/httpServer.go | 2 + src/index.go | 2 + src/proxy/routeTo.go | 2 +- src/status.go | 2 + src/utils/db.go | 2 +- src/utils/utils.go | 7 + 13 files changed, 254 insertions(+), 190 deletions(-) diff --git a/client/src/pages/home/index.jsx b/client/src/pages/home/index.jsx index a1a6e507..b0745b00 100644 --- a/client/src/pages/home/index.jsx +++ b/client/src/pages/home/index.jsx @@ -316,6 +316,13 @@ const HomePage = () => { )} + {isAdmin && coStatus && !coStatus.hostmode && ( + + Your Cosmos server is not running in the docker host network mode. + This will make your life harder, consider switching the network mode to host mode on the cosmos container. + + )} + {isAdmin && coStatus && coStatus.needsRestart && ( You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes. diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index 1a940cd3..8ed9929b 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -451,11 +451,11 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } else { jsoned = convertDockerCompose(config, serviceName, rendered, setYmlError); } - - if(!serviceName) { + + if(!serviceName && !Object.keys(service).length) { if(jsoned['name'] && jsoned['name'].trim() !== '') { setServiceName(jsoned['name']); - } else if (jsoned['services'] && Object.keys(jsoned['services']).length > 0) { + } else if (jsoned['services'] && Object.keys(jsoned['services']).length > 0 && Object.keys(jsoned['services'])[0].trim() !== '') { setServiceName(Object.keys(jsoned['services'])[0]); } else { setServiceName(cleanDefaultName || 'default-name'); @@ -639,7 +639,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul {!ymlError && (<>Choose your service name - setServiceName(e.target.value)} /> + setServiceName(e.target.value)} /> {service['cosmos-installer'] && service['cosmos-installer'].form && service['cosmos-installer'].form.map((formElement) => { return formElement.type === 'checkbox' ? diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go index 4ee3227c..6945690d 100644 --- a/src/docker/api_blueprint.go +++ b/src/docker/api_blueprint.go @@ -422,50 +422,49 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) OnLog(fmt.Sprintf("Checking service %s...\n", serviceName)) // If container request a Cosmos network, create and attach it - // if (container.Labels["cosmos-force-network-secured"] == "true" || strings.ToLower(container.Labels["cosmos-network-name"]) == "auto") && - // container.Labels["cosmos-network-name"] == "" { - // utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName)) - // OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName)) + if strings.ToLower(container.Labels["cosmos-network-name"]) == "auto" { + utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName)) + OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName)) - // newNetwork, errNC := CreateCosmosNetwork(serviceName) - // if errNC != nil { - // utils.Error("CreateService: Network", err) - // OnLog(utils.DoErr("Network %s cant be created\n", newNetwork)) - // Rollback(rollbackActions, OnLog) - // return err - // } + newNetwork, errNC := CreateCosmosNetwork(serviceName) + if errNC != nil { + utils.Error("CreateService: Network", err) + OnLog(utils.DoErr("Network %s cant be created\n", newNetwork)) + Rollback(rollbackActions, OnLog) + return err + } - // container.Labels["cosmos-network-name"] = newNetwork + container.Labels["cosmos-network-name"] = newNetwork - // AttachNetworkToCosmos(newNetwork) + AttachNetworkToCosmos(newNetwork) - // if container.Networks == nil { - // container.Networks = make(map[string]ContainerCreateRequestServiceNetwork) - // } + if container.Networks == nil { + container.Networks = make(map[string]ContainerCreateRequestServiceNetwork) + } - // container.Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} + container.Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} - // rollbackActions = append(rollbackActions, DockerServiceCreateRollback{ - // Action: "remove", - // Type: "network", - // Name: newNetwork, - // }) + rollbackActions = append(rollbackActions, DockerServiceCreateRollback{ + Action: "remove", + Type: "network", + Name: newNetwork, + }) - // utils.Log(fmt.Sprintf("Created secure network %s", newNetwork)) - // OnLog(fmt.Sprintf("Created secure network %s\n", newNetwork)) - // } else if container.Labels["cosmos-network-name"] != "" { - // // Container has a declared a Cosmos network, check if it exists and connect to it - // utils.Log(fmt.Sprintf("Checking declared network %s...", container.Labels["cosmos-network-name"])) - // OnLog(fmt.Sprintf("Checking declared network %s...\n", container.Labels["cosmos-network-name"])) - - // _, err := DockerClient.NetworkInspect(DockerContext, container.Labels["cosmos-network-name"], doctype.NetworkInspectOptions{}) - // if err == nil { - // utils.Log(fmt.Sprintf("Connecting to declared network %s...", container.Labels["cosmos-network-name"])) - // OnLog(fmt.Sprintf("Connecting to declared network %s...\n", container.Labels["cosmos-network-name"])) + utils.Log(fmt.Sprintf("Created secure network %s", newNetwork)) + OnLog(fmt.Sprintf("Created secure network %s\n", newNetwork)) + } else if container.Labels["cosmos-network-name"] != "" { + // Container has a declared a Cosmos network, check if it exists and connect to it + utils.Log(fmt.Sprintf("Checking declared network %s...", container.Labels["cosmos-network-name"])) + OnLog(fmt.Sprintf("Checking declared network %s...\n", container.Labels["cosmos-network-name"])) + + _, err := DockerClient.NetworkInspect(DockerContext, container.Labels["cosmos-network-name"], doctype.NetworkInspectOptions{}) + if err == nil { + utils.Log(fmt.Sprintf("Connecting to declared network %s...", container.Labels["cosmos-network-name"])) + OnLog(fmt.Sprintf("Connecting to declared network %s...\n", container.Labels["cosmos-network-name"])) - // AttachNetworkToCosmos(container.Labels["cosmos-network-name"]) - // } - // } + AttachNetworkToCosmos(container.Labels["cosmos-network-name"]) + } + } utils.Log(fmt.Sprintf("Creating container %s...", container.Name)) OnLog(fmt.Sprintf("Creating container %s...\n", container.Name)) diff --git a/src/docker/bootstrap.go b/src/docker/bootstrap.go index 713c3a46..a5f94821 100644 --- a/src/docker/bootstrap.go +++ b/src/docker/bootstrap.go @@ -1,150 +1,160 @@ package docker -// import ( -// "github.com/azukaar/cosmos-server/src/utils" -// "github.com/docker/docker/api/types" -// "os" -// "fmt" -// "regexp" -// ) - -// func BootstrapAllContainersFromTags() []error { -// errD := Connect() -// if errD != nil { -// return []error{errD} -// } - -// errors := []error{} +import ( + "github.com/azukaar/cosmos-server/src/utils" + "github.com/docker/docker/api/types" + "os" + "fmt" + "regexp" +) + +func BootstrapAllContainersFromTags() []error { + // no need to bootstrap if we are in host network + if os.Getenv("HOSTNAME") == "" || utils.IsHostNetwork { + return nil + } + + errD := Connect() + if errD != nil { + return []error{errD} + } + + errors := []error{} -// containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{}) -// if err != nil { -// utils.Error("Docker Container List", err) -// return []error{err} -// } - -// for _, container := range containers { -// errB := BootstrapContainerFromTags(container.ID) -// if errB != nil { -// utils.Error("Bootstrap Container From Tags", errB) -// errors = append(errors, errB) -// } -// } - -// return errors -// } - -// func UnsecureContainer(container types.ContainerJSON) (string, error) { -// RemoveLabels(container, []string{ -// "cosmos-force-network-secured", -// }); -// return EditContainer(container.ID, container, false) -// } - -// func BootstrapContainerFromTags(containerID string) error { -// errD := Connect() -// if errD != nil { -// return errD -// } - -// selfContainer := types.ContainerJSON{} -// if os.Getenv("HOSTNAME") != "" { -// var errS error -// selfContainer, errS = DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME")) -// if errS != nil { -// utils.Error("DockerContainerBootstrapSelfInspect", errS) -// return errS -// } -// } - -// utils.Log("Bootstrap Container From Tags: " + containerID) - -// container, err := DockerClient.ContainerInspect(DockerContext, containerID) -// if err != nil { -// utils.Error("DockerContainerBootstrapInspect", err) -// return err -// } - -// // check if any route has been added to the container -// config := utils.GetMainConfig() -// if(!HasLabel(container, "cosmos-network-name")) { -// for _, route := range config.HTTPConfig.ProxyConfig.Routes { -// utils.Debug("No cosmos-network-name label on container "+container.Name) -// pattern := fmt.Sprintf(`(?i)^(([a-z]+):\/\/)?%s(:?[0-9]+)?$`, container.Name[1:]) -// match, _ := regexp.MatchString(pattern, route.Target) -// if route.Mode == "SERVAPP" && match { -// utils.Log("Adding cosmos-network-name label to container "+container.Name) -// AddLabels(container, map[string]string{ -// "cosmos-network-name": "auto", -// }) -// } -// } -// } - -// // Check cosmos-network-name tag -// isCosmosCon, _, needsUpdate := IsConnectedToASecureCosmosNetwork(selfContainer, container) - -// if(IsLabel(container, "cosmos-force-network-secured")) { -// utils.Log(container.Name+": Checking Force network secured") - -// // check if connected to bridge and to a cosmos network -// isCon := IsConnectedToNetwork(container, "bridge") + containers, err := DockerClient.ContainerList(DockerContext, types.ContainerListOptions{}) + if err != nil { + utils.Error("Docker Container List", err) + return []error{err} + } + + for _, container := range containers { + errB := BootstrapContainerFromTags(container.ID) + if errB != nil { + utils.Error("Bootstrap Container From Tags", errB) + errors = append(errors, errB) + } + } + + return errors +} + +func UnsecureContainer(container types.ContainerJSON) (string, error) { + RemoveLabels(container, []string{ + "cosmos-force-network-secured", + }); + return EditContainer(container.ID, container, false) +} + +func BootstrapContainerFromTags(containerID string) error { + // no need to bootstrap if we are in host network + if os.Getenv("HOSTNAME") == "" || utils.IsHostNetwork { + return nil + } + + errD := Connect() + if errD != nil { + return errD + } + + selfContainer := types.ContainerJSON{} + if os.Getenv("HOSTNAME") != "" { + var errS error + selfContainer, errS = DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME")) + if errS != nil { + utils.Error("DockerContainerBootstrapSelfInspect", errS) + return errS + } + } + + utils.Log("Bootstrap Container From Tags: " + containerID) + + container, err := DockerClient.ContainerInspect(DockerContext, containerID) + if err != nil { + utils.Error("DockerContainerBootstrapInspect", err) + return err + } + + // check if any route has been added to the container + config := utils.GetMainConfig() + if(!HasLabel(container, "cosmos-network-name")) { + for _, route := range config.HTTPConfig.ProxyConfig.Routes { + utils.Debug("No cosmos-network-name label on container "+container.Name) + pattern := fmt.Sprintf(`(?i)^(([a-z]+):\/\/)?%s(:?[0-9]+)?$`, container.Name[1:]) + match, _ := regexp.MatchString(pattern, route.Target) + if route.Mode == "SERVAPP" && match { + utils.Log("Adding cosmos-network-name label to container "+container.Name) + AddLabels(container, map[string]string{ + "cosmos-network-name": "auto", + }) + } + } + } + + // Check cosmos-network-name tag + _, _, needsUpdate := IsConnectedToASecureCosmosNetwork(selfContainer, container) + + // if(IsLabel(container, "cosmos-force-network-secured")) { + // utils.Log(container.Name+": Checking Force network secured") + + // // check if connected to bridge and to a cosmos network + // isCon := IsConnectedToNetwork(container, "bridge") -// if isCon || !isCosmosCon { -// utils.Log(container.Name+": Needs isolating on a secured network") -// needsRestart := false -// var errCT error -// if !isCosmosCon { -// utils.Debug(container.Name+": Not connected to a cosmos network") -// needsRestart, errCT = ConnectToSecureNetwork(container) -// if errCT != nil { -// utils.Warn("DockerContainerBootstrapConnectToSecureNetwork -- Cannot connect to network, removing force secure") -// _, errUn := UnsecureContainer(container) -// if errUn != nil { -// utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn) -// return errCT -// } -// return errCT -// } -// if needsRestart { -// utils.Log(container.Name+": Will restart to apply changes") -// needsUpdate = true -// } else { -// utils.Log(container.Name+": Connected to new network") -// } -// } -// if !needsRestart && isCon { -// utils.Log(container.Name+": Disconnecting from bridge network") -// errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true) -// if errDisc != nil { -// utils.Warn("DockerContainerBootstrapDisconnectFromBridge -- Cannot disconnect from Bridge, removing force secure") -// _, errUn := UnsecureContainer(container) -// if errUn != nil { -// utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn) -// return errDisc -// } -// return errDisc -// } -// } -// } - -// if(len(GetAllPorts(container)) > 0) { -// utils.Log("Removing unsecure ports bindings from "+container.Name) -// // remove all ports -// UnexposeAllPorts(&container) -// needsUpdate = true -// } -// } + // if isCon || !isCosmosCon { + // utils.Log(container.Name+": Needs isolating on a secured network") + // needsRestart := false + // var errCT error + // if !isCosmosCon { + // utils.Debug(container.Name+": Not connected to a cosmos network") + // needsRestart, errCT = ConnectToSecureNetwork(container) + // if errCT != nil { + // utils.Warn("DockerContainerBootstrapConnectToSecureNetwork -- Cannot connect to network, removing force secure") + // _, errUn := UnsecureContainer(container) + // if errUn != nil { + // utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn) + // return errCT + // } + // return errCT + // } + // if needsRestart { + // utils.Log(container.Name+": Will restart to apply changes") + // needsUpdate = true + // } else { + // utils.Log(container.Name+": Connected to new network") + // } + // } + // if !needsRestart && isCon { + // utils.Log(container.Name+": Disconnecting from bridge network") + // errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true) + // if errDisc != nil { + // utils.Warn("DockerContainerBootstrapDisconnectFromBridge -- Cannot disconnect from Bridge, removing force secure") + // _, errUn := UnsecureContainer(container) + // if errUn != nil { + // utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn) + // return errDisc + // } + // return errDisc + // } + // } + // } + + // if(len(GetAllPorts(container)) > 0) { + // utils.Log("Removing unsecure ports bindings from "+container.Name) + // // remove all ports + // UnexposeAllPorts(&container) + // needsUpdate = true + // } + // } -// if(needsUpdate) { -// _, errEdit := EditContainer(containerID, container, false) -// if errEdit != nil { -// utils.Error("Docker Boostrap, couldn't update container: ", errEdit) -// return errEdit -// } -// utils.Debug("Done updating Container From Tags after Bootstrapping: " + container.Name) -// } - -// utils.Log("Done bootstrapping Container From Tags: " + container.Name) - -// return nil -// } + if(needsUpdate) { + _, errEdit := EditContainer(containerID, container, false) + if errEdit != nil { + utils.Error("Docker Boostrap, couldn't update container: ", errEdit) + return errEdit + } + utils.Debug("Done updating Container From Tags after Bootstrapping: " + container.Name) + } + + utils.Log("Done bootstrapping Container From Tags: " + container.Name) + + return nil +} diff --git a/src/docker/docker.go b/src/docker/docker.go index 6527d100..84ae2cb6 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -826,4 +826,21 @@ func Stats(container types.Container) (ContainerStats, error) { utils.Error("StopContainer", err) return } + } + + func CheckDockerNetworkMode() string { + if os.Getenv("HOSTNAME") != "" { + errD := Connect() + if errD != nil { + utils.Error("Checking Host Network", errD) + return "" + } + + container, err := DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME")) + if err != nil { + utils.Error("Checking Host Network", err) + } + return string(container.HostConfig.NetworkMode) + } + return "" } \ No newline at end of file diff --git a/src/docker/events.go b/src/docker/events.go index 047ea7d9..e62e306f 100644 --- a/src/docker/events.go +++ b/src/docker/events.go @@ -140,7 +140,7 @@ func DebouncedExportDocker() { func onDockerStarted(containerID string) { utils.Debug("onDockerStarted: " + containerID) - // BootstrapContainerFromTags(containerID) + BootstrapContainerFromTags(containerID) DebouncedExportDocker() } diff --git a/src/docker/run.go b/src/docker/run.go index a3ed3509..d4cf5d74 100644 --- a/src/docker/run.go +++ b/src/docker/run.go @@ -40,7 +40,7 @@ func NewDB(w http.ResponseWriter, req *http.Request) (string, error) { mongoPass := utils.GenerateRandomString(24) monHost := "cosmos-mongo-" + id - imageName := "mongo:latest" + imageName := "mongo:6" //if ARM use arm64v8/mongo if runtime.GOARCH == "arm64" { @@ -56,18 +56,21 @@ func NewDB(w http.ResponseWriter, req *http.Request) (string, error) { service := DockerServiceCreateRequest{ Services: map[string]ContainerCreateRequestContainer {}, } + service.Services[monHost] = ContainerCreateRequestContainer{ Name: monHost, Image: imageName, RestartPolicy: "always", + // Command: "--wiredTigerCacheSizeGB 0.25", Environment: []string{ "MONGO_INITDB_ROOT_USERNAME=" + mongoUser, "MONGO_INITDB_ROOT_PASSWORD=" + mongoPass, }, Labels: map[string]string{ - "cosmos-force-network-secured": "true", + "cosmos-auto-update": "true", }, + Networks: map[string]ContainerCreateRequestServiceNetwork{}, Volumes: []mount.Mount{ { Type: mount.TypeVolume, @@ -82,6 +85,21 @@ func NewDB(w http.ResponseWriter, req *http.Request) (string, error) { }, }; + if os.Getenv("HOSTNAME") != "" && !utils.IsHostNetwork { + newNetwork, errNC := CreateCosmosNetwork(monHost) + if errNC != nil { + utils.Error("CreateService: Network", errNC) + fmt.Fprintf(w, "CreateService: Network: %s\n", errNC) + flusher.Flush() + return "", errNC + } + + service.Services[monHost].Labels["cosmos-network-name"] = newNetwork + service.Services[monHost].Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} + + AttachNetworkToCosmos(newNetwork) + } + err := CreateService(service, func (msg string) { utils.Log(msg) diff --git a/src/httpServer.go b/src/httpServer.go index d8cf7745..5b3c372b 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -508,6 +508,8 @@ func RestartServer() { utils.Log("Restarting HTTP Server...") LoadConfig() + docker.BootstrapAllContainersFromTags() + go func() { if HTTPServer2 != nil { HTTPServer2.Shutdown(context.Background()) diff --git a/src/index.go b/src/index.go index 02f07cf2..e904837d 100644 --- a/src/index.go +++ b/src/index.go @@ -19,11 +19,13 @@ func main() { // utils.ReBootstrapContainer = docker.BootstrapContainerFromTags utils.PushShieldMetrics = metrics.PushShieldMetrics utils.GetContainerIPByName = docker.GetContainerIPByName + utils.CheckDockerNetworkMode = docker.CheckDockerNetworkMode rand.Seed(time.Now().UnixNano()) LoadConfig() + utils.CheckHostNetwork() utils.InitDBBuffers() go CRON() diff --git a/src/proxy/routeTo.go b/src/proxy/routeTo.go index b51a636e..56e64318 100644 --- a/src/proxy/routeTo.go +++ b/src/proxy/routeTo.go @@ -70,7 +70,7 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH req.URL.Scheme = url.Scheme req.URL.Host = url.Host - if route.Mode == "SERVAPP" && os.Getenv("HOSTNAME") == "" { + if route.Mode == "SERVAPP" && (os.Getenv("HOSTNAME") == "" || utils.IsHostNetwork) { targetHost := url.Hostname() targetIP, err := docker.GetContainerIPByName(targetHost) diff --git a/src/status.go b/src/status.go index 20589918..0cea7e23 100644 --- a/src/status.go +++ b/src/status.go @@ -3,6 +3,7 @@ package main import ( "net/http" "encoding/json" + "os" "runtime" "golang.org/x/sys/cpu" @@ -51,6 +52,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) { // "disk": utils.GetDiskUsage(), // "network": utils.GetNetworkUsage(), }, + "hostmode": utils.IsHostNetwork || os.Getenv("HOSTNAME") == "", "database": databaseStatus, "docker": docker.DockerIsConnected, "backup_status": docker.ExportError, diff --git a/src/utils/db.go b/src/utils/db.go index 5479cd95..a079202b 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -41,7 +41,7 @@ func DB() error { IsDBaContainer = false - if os.Getenv("HOSTNAME") == "" { + if os.Getenv("HOSTNAME") == "" || IsHostNetwork { hostname := opts.Hosts[0] Log("Getting Mongo DB IP from name : " + hostname) ip, _ := GetContainerIPByName(hostname) diff --git a/src/utils/utils.go b/src/utils/utils.go index 829457f1..8daeb4ee 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -30,11 +30,14 @@ var NewVersionAvailable = false var NeedsRestart = false +var IsHostNetwork = false + var UpdateAvailable = map[string]bool{} var RestartHTTPServer func() // var ReBootstrapContainer func(string) error var GetContainerIPByName func(string) (string, error) +var CheckDockerNetworkMode func() string var LetsEncryptErrors = []string{} @@ -678,4 +681,8 @@ func IsDomain(domain string) bool { return true } return false +} + +func CheckHostNetwork() { + IsHostNetwork = CheckDockerNetworkMode() == "host" } \ No newline at end of file From fcf9862405d492c0e9e73abeb9aeeaa6df18fd0e Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Tue, 23 Jan 2024 22:54:35 +0000 Subject: [PATCH 07/31] [release] v0.14.0-unstable2 --- changelog.md | 1 + package.json | 2 +- src/utils/db.go | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 1e8da138..2d26bcff 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ - Removed all sort of container bootstrapping (much faster boot) - Added image clean up - Replaced network clean up by vanilla docker prune + - Force low RAM usage on the MongoDB container (we don't need much!) ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/package.json b/package.json index a4273aab..a318e6bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable1", + "version": "0.14.0-unstable2", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/utils/db.go b/src/utils/db.go index a079202b..67eb18f2 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -6,6 +6,7 @@ import ( "errors" "sync" "time" + "strings" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -43,11 +44,21 @@ func DB() error { if os.Getenv("HOSTNAME") == "" || IsHostNetwork { hostname := opts.Hosts[0] - Log("Getting Mongo DB IP from name : " + hostname) + // split port + hostnameParts := strings.Split(hostname, ":") + hostname = hostnameParts[0] + port := "27017" + + if len(hostnameParts) > 1 { + port = hostnameParts[1] + } + + Log("Getting Mongo DB IP from name : " + hostname + " (port " + port + ")") + ip, _ := GetContainerIPByName(hostname) if ip != "" { IsDBaContainer = true - opts.SetHosts([]string{ip + ":27017"}) + opts.SetHosts([]string{ip + ":" + port}) Log("Mongo DB IP : " + ip) } } From f20ad6d06fc10cee76fa96739f555d494df663ed Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Fri, 26 Jan 2024 13:59:50 +0000 Subject: [PATCH 08/31] [skip ci] park lungo --- client/src/pages/home/index.jsx | 3 +- go.mod | 5 +- go.sum | 10 +- src/CRON.go | 26 +----- src/authorizationserver/oauth2_user.go | 3 +- src/constellation/api_devices_block.go | 3 +- src/constellation/api_devices_create.go | 5 +- src/constellation/api_devices_list.go | 6 +- src/constellation/nebula.go | 9 +- src/image.go | 117 ++++++++++++++++++++++++ src/index.go | 1 + src/newInstall.go | 3 +- src/user/2fa_check.go | 4 +- src/user/2fa_new.go | 3 +- src/user/2fa_reset.go | 3 +- src/user/create.go | 3 +- src/user/delete.go | 3 +- src/user/edit.go | 3 +- src/user/get.go | 3 +- src/user/list.go | 3 +- src/user/login.go | 3 +- src/user/password_reset.go | 3 +- src/user/register.go | 3 +- src/user/resend.go | 3 +- src/user/token.go | 3 +- src/utils/db.go | 27 ++++++ src/utils/types.go | 64 ++++++------- src/utils/utils.go | 25 ++++- 28 files changed, 263 insertions(+), 84 deletions(-) diff --git a/client/src/pages/home/index.jsx b/client/src/pages/home/index.jsx index b0745b00..acbe6e5d 100644 --- a/client/src/pages/home/index.jsx +++ b/client/src/pages/home/index.jsx @@ -284,8 +284,7 @@ const HomePage = () => { {isAdmin && coStatus && !coStatus.database && ( - No Database is setup for Cosmos! User Management and Authentication will not work.
- You can either setup the database, or disable user management in the configuration panel.
+ Database cannot connect, this will impact multiple feature of Cosmos. Please fix ASAP!
)} diff --git a/go.mod b/go.mod index 1e0d88b9..9996894d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/azukaar/cosmos-server go 1.20 require ( + github.com/256dpi/lungo v0.3.5 github.com/Masterminds/semver v1.5.0 github.com/docker/cli v24.0.5+incompatible github.com/docker/docker v23.0.3+incompatible @@ -25,7 +26,7 @@ require ( github.com/roberthodgen/spa-server v0.0.0-20171007154335-bb87b4ff3253 github.com/shirou/gopsutil/v3 v3.23.9 go.deanishe.net/favicon v0.1.0 - go.mongodb.org/mongo-driver v1.11.3 + go.mongodb.org/mongo-driver v1.11.7 golang.org/x/crypto v0.10.0 golang.org/x/net v0.11.0 golang.org/x/sys v0.12.0 @@ -183,6 +184,7 @@ require ( github.com/sacloud/packages-go v0.0.9 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/softlayer-go v1.1.2 // indirect @@ -197,6 +199,7 @@ require ( github.com/subosito/gotenv v1.2.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect + github.com/tidwall/btree v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/transip/gotransip/v6 v6.20.0 // indirect diff --git a/go.sum b/go.sum index 91cda2ca..f1c7a69b 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/256dpi/lungo v0.3.5 h1:2bD6HLLToLB0Ndr+bA6xdNHGR50iDxYjwwAPQAfxddA= +github.com/256dpi/lungo v0.3.5/go.mod h1:lGMNvhUBwCNvVSUInfrg0Ra6q0v+MO5WbxB+7GIDjlo= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= @@ -1266,6 +1268,8 @@ github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= @@ -1361,6 +1365,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz2 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ= +github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= +github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/gjson v1.7.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= @@ -1442,8 +1448,8 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= -go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= -go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs= +go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= diff --git a/src/CRON.go b/src/CRON.go index 97195ef4..fe0b7cb5 100644 --- a/src/CRON.go +++ b/src/CRON.go @@ -10,36 +10,12 @@ import ( "encoding/json" "github.com/jasonlvhit/gocron" - "github.com/Masterminds/semver" ) type Version struct { Version string `json:"version"` } -// compareSemver compares two semantic version strings. -// Returns: -// 0 if v1 == v2 -// 1 if v1 > v2 -// -1 if v1 < v2 -// error if there's a problem parsing either version string -func compareSemver(v1, v2 string) (int, error) { - ver1, err := semver.NewVersion(v1) - if err != nil { - utils.Error("compareSemver 1 " + v1, err) - return 0, err - } - - ver2, err := semver.NewVersion(v2) - if err != nil { - utils.Error("compareSemver 2 " + v2, err) - return 0, err - } - - return ver1.Compare(ver2), nil -} - - func checkVersion() { utils.NewVersionAvailable = false @@ -82,7 +58,7 @@ func checkVersion() { return } - cp, errc := compareSemver(myVersion, string(body)) + cp, errc := utils.CompareSemver(myVersion, string(body)) if errc != nil { utils.Error("checkVersion", errc) diff --git a/src/authorizationserver/oauth2_user.go b/src/authorizationserver/oauth2_user.go index f474354d..48988569 100644 --- a/src/authorizationserver/oauth2_user.go +++ b/src/authorizationserver/oauth2_user.go @@ -48,7 +48,8 @@ func userInfosEndpoint(rw http.ResponseWriter, req *http.Request) { nickname := interim["sub"].(string) - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(rw, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/constellation/api_devices_block.go b/src/constellation/api_devices_block.go index 14568de3..e348ffc9 100644 --- a/src/constellation/api_devices_block.go +++ b/src/constellation/api_devices_block.go @@ -41,7 +41,8 @@ func DeviceBlock(w http.ResponseWriter, req *http.Request) { utils.Log("ConstellationDeviceBlocking: Blocking Device " + deviceName) - c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/constellation/api_devices_create.go b/src/constellation/api_devices_create.go index 4bb44364..3c786ac8 100644 --- a/src/constellation/api_devices_create.go +++ b/src/constellation/api_devices_create.go @@ -52,7 +52,8 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) { utils.Log("ConstellationDeviceCreation: Creating Device " + deviceName) - c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") @@ -109,7 +110,7 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) { if err3 != nil { utils.Error("DeviceCreation: Error while creating Device", err3) - utils.HTTPError(w, "Device Creation Error: " + err.Error(), + utils.HTTPError(w, "Device Creation Error: " + err3.Error(), http.StatusInternalServerError, "DC004") return } diff --git a/src/constellation/api_devices_list.go b/src/constellation/api_devices_list.go index df8b1519..11fc6cb4 100644 --- a/src/constellation/api_devices_list.go +++ b/src/constellation/api_devices_list.go @@ -3,6 +3,7 @@ package constellation import ( "net/http" "encoding/json" + "fmt" "github.com/azukaar/cosmos-server/src/utils" @@ -22,7 +23,8 @@ func DeviceList(w http.ResponseWriter, req *http.Request) { isAdmin := utils.IsAdmin(req) // Connect to the collection - c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") @@ -47,6 +49,8 @@ func DeviceList(w http.ResponseWriter, req *http.Request) { utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002") return } + + fmt.Println(devices) } else { // If not admin, get user's devices based on their nickname nickname := req.Header.Get("x-cosmos-user") diff --git a/src/constellation/nebula.go b/src/constellation/nebula.go index 90e3b60d..5a2ab2ec 100644 --- a/src/constellation/nebula.go +++ b/src/constellation/nebula.go @@ -99,7 +99,8 @@ func ResetNebula() error { os.RemoveAll(utils.CONFIGFOLDER + "cosmos.key") // remove everything in db - c, err := utils.GetCollection(utils.GetRootAppId(), "devices") + c, closeDb, err := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() if err != nil { return err } @@ -122,7 +123,8 @@ func ResetNebula() error { } func GetAllLightHouses() ([]utils.ConstellationDevice, error) { - c, err := utils.GetCollection(utils.GetRootAppId(), "devices") + c, closeDb, err := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() if err != nil { return []utils.ConstellationDevice{}, err } @@ -143,7 +145,8 @@ func GetAllLightHouses() ([]utils.ConstellationDevice, error) { } func GetBlockedDevices() ([]utils.ConstellationDevice, error) { - c, err := utils.GetCollection(utils.GetRootAppId(), "devices") + c, closeDb, err := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() if err != nil { return []utils.ConstellationDevice{}, err } diff --git a/src/image.go b/src/image.go index a689275f..bfa7380e 100644 --- a/src/image.go +++ b/src/image.go @@ -9,10 +9,13 @@ import ( "encoding/json" "strings" "fmt" + "context" "github.com/gorilla/mux" "github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/docker" + + "go.mongodb.org/mongo-driver/mongo" ) var validExtensions = map[string]bool{ @@ -231,4 +234,118 @@ func MigratePre013() { utils.SetBaseMainConfig(config) } } +} + +func CommitMigrationPre014() { + config := utils.ReadConfigFromFile() + config.LastMigration = "0.14.0" + utils.SetBaseMainConfig(config) +} + +func MigratePre014() { + config := utils.ReadConfigFromFile() + if config.LastMigration != "" { + c, err := utils.CompareSemver(config.LastMigration, "0.14.0") + if err != nil { + utils.Fatal("Can't read field 'lastMigration' from config", err) + } + if(c > 0) { + return + } + } + + if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil { + utils.Log("MigratePre014: No database found, trying to migrate pre-0.14 data") + + cu2, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + if errCo != nil { + // Assuming we dont need to migrate + utils.Log("MigratePre014: No database, assuming we dont need to migrate") + utils.Debug(errCo.Error()) + CommitMigrationPre014() + return + } + + cd2, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") + if errCo != nil { + // Assuming we dont need to migrate + utils.Log("MigratePre014: No database, assuming we dont need to migrate") + utils.Debug(errCo.Error()) + CommitMigrationPre014() + return + } + + utils.Log("MigratePre014: Migrating pre-0.14 data") + + cu, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + + if errCo != nil { + utils.Fatal("Error trying to migrate pre-0.14 data [1]", errCo) + return + } + + // copy users from cu2 to cu + cursor, err := cu2.Find(nil, map[string]interface{}{}) + if err != nil && err != mongo.ErrNoDocuments { + utils.Fatal("Error trying to migrate pre-0.14 data [3]", err) + return + } + defer cursor.Close(nil) + + users := []utils.User{} + + if err = cursor.All(nil, &users); err != nil { + utils.Fatal("Error trying to migrate pre-0.14 data [4]", err) + return + } + + for _, user := range users { + _, err := cu.InsertOne(nil, user) + if err != nil { + utils.Fatal("Error trying to migrate pre-0.14 data [5]", err) + return + } + } + + closeDb() + + utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(users)) + " users") + + + cd, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() + + if errCo != nil { + utils.Fatal("Error trying to migrate pre-0.14 data [2]", errCo) + return + } + + // copy devices from cd2 to cd + cursor, err = cd2.Find(nil, map[string]interface{}{}) + if err != nil && err != mongo.ErrNoDocuments { + utils.Fatal("Error trying to migrate pre-0.14 data [6]", err) + return + } + + defer cursor.Close(nil) + + devices := []utils.ConstellationDevice{} + + if err = cursor.All(context.Background(), &devices); err != nil { + utils.Fatal("Error trying to migrate pre-0.14 data [7]", err) + return + } + + for _, device := range devices { + _, err := cd.InsertOne(context.Background(), device) + if err != nil { + utils.Fatal("Error trying to migrate pre-0.14 data [8]", err) + return + } + } + + utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(devices)) + " devices") + + CommitMigrationPre014() + } } \ No newline at end of file diff --git a/src/index.go b/src/index.go index e904837d..6bc074f2 100644 --- a/src/index.go +++ b/src/index.go @@ -51,6 +51,7 @@ func main() { config := utils.GetMainConfig() if !config.NewInstall { MigratePre013() + MigratePre014() utils.Log("Starting monitoring services...") diff --git a/src/newInstall.go b/src/newInstall.go index 2d43a796..3f1b9def 100644 --- a/src/newInstall.go +++ b/src/newInstall.go @@ -150,7 +150,8 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { } // Admin User - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/2fa_check.go b/src/user/2fa_check.go index 2a1513f3..913f484e 100644 --- a/src/user/2fa_check.go +++ b/src/user/2fa_check.go @@ -27,7 +27,9 @@ func Check2FA(w http.ResponseWriter, req *http.Request) { return } - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() + if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/2fa_new.go b/src/user/2fa_new.go index bc0547af..5b2c7630 100644 --- a/src/user/2fa_new.go +++ b/src/user/2fa_new.go @@ -36,7 +36,8 @@ func New2FA(w http.ResponseWriter, req *http.Request) { "Was2FAVerified": false, } - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/2fa_reset.go b/src/user/2fa_reset.go index 83a1253c..9bd85c98 100644 --- a/src/user/2fa_reset.go +++ b/src/user/2fa_reset.go @@ -26,7 +26,8 @@ func Delete2FA(w http.ResponseWriter, req *http.Request) { nickname := request.Nickname - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/create.go b/src/user/create.go index 9edef5e1..d8900ca0 100644 --- a/src/user/create.go +++ b/src/user/create.go @@ -40,7 +40,8 @@ func UserCreate(w http.ResponseWriter, req *http.Request) { nickname := utils.Sanitize(request.Nickname) email := utils.Sanitize(request.Email) - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/delete.go b/src/user/delete.go index 8432c1b9..56c30d1f 100644 --- a/src/user/delete.go +++ b/src/user/delete.go @@ -18,7 +18,8 @@ func UserDelete(w http.ResponseWriter, req *http.Request) { if(req.Method == "DELETE") { - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/edit.go b/src/user/edit.go index 6dfcb084..1a42dc8e 100644 --- a/src/user/edit.go +++ b/src/user/edit.go @@ -36,7 +36,8 @@ func UserEdit(w http.ResponseWriter, req *http.Request) { return } - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/get.go b/src/user/get.go index 2eb3058a..f10c01a0 100644 --- a/src/user/get.go +++ b/src/user/get.go @@ -21,7 +21,8 @@ func UserGet(w http.ResponseWriter, req *http.Request) { if(req.Method == "GET") { - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/list.go b/src/user/list.go index fc61cfa3..f531abf2 100644 --- a/src/user/list.go +++ b/src/user/list.go @@ -24,7 +24,8 @@ func UserList(w http.ResponseWriter, req *http.Request) { } if(req.Method == "GET") { - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/login.go b/src/user/login.go index f62ee9a6..982dcff3 100644 --- a/src/user/login.go +++ b/src/user/login.go @@ -35,7 +35,8 @@ func UserLogin(w http.ResponseWriter, req *http.Request) { return } - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database Error", http.StatusInternalServerError, "DB001") diff --git a/src/user/password_reset.go b/src/user/password_reset.go index 6ae4e3a1..618503ae 100644 --- a/src/user/password_reset.go +++ b/src/user/password_reset.go @@ -41,7 +41,8 @@ func ResetPassword(w http.ResponseWriter, req *http.Request) { utils.Debug("Sending password reset to: " + nickname) - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/register.go b/src/user/register.go index 6324e271..0cc909ec 100644 --- a/src/user/register.go +++ b/src/user/register.go @@ -50,7 +50,8 @@ func UserRegister(w http.ResponseWriter, req *http.Request) { return } - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/resend.go b/src/user/resend.go index 17e020a5..2a0b457b 100644 --- a/src/user/resend.go +++ b/src/user/resend.go @@ -32,7 +32,8 @@ func UserResendInviteLink(w http.ResponseWriter, req *http.Request) { utils.Debug("Re-Sending an invite to " + nickname) - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/user/token.go b/src/user/token.go index ad30ab31..d3ec9dd8 100644 --- a/src/user/token.go +++ b/src/user/token.go @@ -127,7 +127,8 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err userInBase := utils.User{} - c, errCo := utils.GetCollection(utils.GetRootAppId(), "users") + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + defer closeDb() if errCo != nil { utils.Error("Database Connect", errCo) utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") diff --git a/src/utils/db.go b/src/utils/db.go index 67eb18f2..33bfeebf 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -13,6 +13,8 @@ import ( "go.mongodb.org/mongo-driver/mongo/readpref" "go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/bson" + + lungo "github.com/256dpi/lungo" ) @@ -92,6 +94,31 @@ func DisconnectDB() { client = nil } +func GetEmbeddedCollection(applicationId string, collection string) (lungo.ICollection, func(), error) { + opts := lungo.Options{ + Store: lungo.NewFileStore(CONFIGFOLDER + "database", 700), + } + + name := os.Getenv("MONGODB_NAME"); if name == "" { + name = "COSMOS" + } + + // open database + client, engine, err := lungo.Open(nil, opts) + if err != nil { + return nil, nil, err + } + + // ensure engine is closed + // defer engine.Close() + + c := client.Database(name).Collection(applicationId + "_" + collection) + + return c, func() { + engine.Close() + }, nil +} + func GetCollection(applicationId string, collection string) (*mongo.Collection, error) { if client == nil { errCo := DB() diff --git a/src/utils/types.go b/src/utils/types.go index a0743237..815d1d25 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -56,21 +56,22 @@ type FileStats struct { type User struct { ID primitive.ObjectID `json:"-" bson:"_id,omitempty"` - Nickname string `validate:"required" json:"nickname"` - Password string `validate:"required" json:"-"` - RegisterKey string `json:"registerKey"` - RegisterKeyExp time.Time `json:"registerKeyExp"` - Role Role `validate:"required" json:"role"` - PasswordCycle int `json:"-"` - Link string `json:"link"` - Email string `validate:"email" json:"email"` - RegisteredAt time.Time `json:"registeredAt"` - LastPasswordChangedAt time.Time `json:"lastPasswordChangedAt"` - CreatedAt time.Time `json:"createdAt"` - LastLogin time.Time `json:"lastLogin"` - MFAKey string `json:"-"` - Was2FAVerified bool `json:"-"` - MFAState int `json:"-"` // 0 = done, 1 = needed, 2 = not set + Nickname string `validate:"required" json:"nickname" bson:"Nickname"` + Password string `validate:"required" json:"-" bson:"Password"` + RegisterKey string `json:"registerKey" bson:"RegisterKey"` + RegisterKeyExp time.Time `json:"registerKeyExp" bson:"RegisterKeyExp"` + Role Role `validate:"required" json:"role" bson:"Role"` + PasswordCycle int `json:"-" bson:"PasswordCycle"` + Link string `json:"link" bson:"-"` + Email string `validate:"email" json:"email" bson:"Email"` + RegisteredAt time.Time `json:"registeredAt" bson:"RegisteredAt"` + LastPasswordChangedAt time.Time `json:"lastPasswordChangedAt" bson:"LastPasswordChangedAt"` + CreatedAt time.Time `json:"createdAt" bson:"CreatedAt"` + LastLogin time.Time `json:"lastLogin" bson:"LastLogin"` + MFAKey string `json:"-" bson:"MFAKey"` + Was2FAVerified bool `json:"-" bson:"Was2FAVerified"` + MFAState int `json:"-" bson:"-"` + // 0 = done, 1 = needed, 2 = not set } type Config struct { @@ -93,6 +94,7 @@ type Config struct { ConstellationConfig ConstellationConfig MonitoringDisabled bool MonitoringAlerts map[string]Alert + LastMigration string } type HomepageConfig struct { @@ -238,17 +240,17 @@ type ConstellationDNSEntry struct { Value string } type ConstellationDevice struct { - Nickname string `json:"nickname"` - DeviceName string `json:"deviceName"` - PublicKey string `json:"publicKey"` - IP string `json:"ip"` - IsLighthouse bool `json:"isLighthouse"` - IsRelay bool `json:"isRelay"` - PublicHostname string `json:"publicHostname"` - Port string `json:"port"` - Blocked bool `json:"blocked"` - Fingerprint string `json:"fingerprint"` - APIKey string `json:"-"` + Nickname string `json:"nickname" bson:"Nickname"` + DeviceName string `json:"deviceName" bson:"DeviceName"` + PublicKey string `json:"publicKey" bson:"PublicKey"` + IP string `json:"ip" bson:"IP"` + IsLighthouse bool `json:"isLighthouse" bson:"IsLighthouse"` + IsRelay bool `json:"isRelay" bson:"IsRelay"` + PublicHostname string `json:"publicHostname" bson:"PublicHostname"` + Port string `json:"port" bson:"Port"` + Blocked bool `json:"blocked" bson:"Blocked"` + Fingerprint string `json:"fingerprint" bson:"Fingerprint"` + APIKey string `json:"-" bson:"APIKey"` } type NebulaFirewallRule struct { @@ -322,11 +324,11 @@ type NebulaConfig struct { } type Device struct { - DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"` - Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"` - PublicKey string `json:"publicKey",omitempty` - PrivateKey string `json:"privateKey",omitempty` - IP string `json:"ip",validate:"required,ipv4"` + DeviceName string `json:"deviceName"validate:"required,min=3,max=32,alphanum",bson:"DeviceName"` + Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum",bson:"Nickname"` + PublicKey string `json:"publicKey",omitempty,bson:"PublicKey"` + PrivateKey string `json:"privateKey",omitempty,bson:"PrivateKey"` + IP string `json:"ip",validate:"required,ipv4",bson:"IP"` } type Alert struct { diff --git a/src/utils/utils.go b/src/utils/utils.go index 8daeb4ee..08410723 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -19,6 +19,7 @@ import ( "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/net" "golang.org/x/net/publicsuffix" + "github.com/Masterminds/semver" ) var ConfigLock sync.Mutex @@ -685,4 +686,26 @@ func IsDomain(domain string) bool { func CheckHostNetwork() { IsHostNetwork = CheckDockerNetworkMode() == "host" -} \ No newline at end of file +} + +// compareSemver compares two semantic version strings. +// Returns: +// 0 if v1 == v2 +// 1 if v1 > v2 +// -1 if v1 < v2 +// error if there's a problem parsing either version string +func CompareSemver(v1, v2 string) (int, error) { + ver1, err := semver.NewVersion(v1) + if err != nil { + Error("compareSemver 1 " + v1, err) + return 0, err + } + + ver2, err := semver.NewVersion(v2) + if err != nil { + Error("compareSemver 2 " + v2, err) + return 0, err + } + + return ver1.Compare(ver2), nil +} From 918ff46f676d705c8bc08be5f1fbd4a71fa1987a Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 10:54:44 +0000 Subject: [PATCH 09/31] [skip ci] park lungo --- readme.md | 1 - src/image.go | 202 ++++++++++++++++++++++++------------------------- src/migrate.go | 149 ++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 102 deletions(-) create mode 100644 src/migrate.go diff --git a/readme.md b/readme.md index 9511cc04..4248229e 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,6 @@

null CupofDalek null -Clayton Stone null null null diff --git a/src/image.go b/src/image.go index bfa7380e..608a4f7b 100644 --- a/src/image.go +++ b/src/image.go @@ -242,110 +242,110 @@ func CommitMigrationPre014() { utils.SetBaseMainConfig(config) } -func MigratePre014() { - config := utils.ReadConfigFromFile() - if config.LastMigration != "" { - c, err := utils.CompareSemver(config.LastMigration, "0.14.0") - if err != nil { - utils.Fatal("Can't read field 'lastMigration' from config", err) - } - if(c > 0) { - return - } - } - - if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil { - utils.Log("MigratePre014: No database found, trying to migrate pre-0.14 data") - - cu2, errCo := utils.GetCollection(utils.GetRootAppId(), "users") - if errCo != nil { - // Assuming we dont need to migrate - utils.Log("MigratePre014: No database, assuming we dont need to migrate") - utils.Debug(errCo.Error()) - CommitMigrationPre014() - return - } - - cd2, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") - if errCo != nil { - // Assuming we dont need to migrate - utils.Log("MigratePre014: No database, assuming we dont need to migrate") - utils.Debug(errCo.Error()) - CommitMigrationPre014() - return - } - - utils.Log("MigratePre014: Migrating pre-0.14 data") - - cu, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") - - if errCo != nil { - utils.Fatal("Error trying to migrate pre-0.14 data [1]", errCo) - return - } - - // copy users from cu2 to cu - cursor, err := cu2.Find(nil, map[string]interface{}{}) - if err != nil && err != mongo.ErrNoDocuments { - utils.Fatal("Error trying to migrate pre-0.14 data [3]", err) - return - } - defer cursor.Close(nil) - - users := []utils.User{} - - if err = cursor.All(nil, &users); err != nil { - utils.Fatal("Error trying to migrate pre-0.14 data [4]", err) - return - } - - for _, user := range users { - _, err := cu.InsertOne(nil, user) - if err != nil { - utils.Fatal("Error trying to migrate pre-0.14 data [5]", err) - return - } - } +// func MigratePre014() { +// config := utils.ReadConfigFromFile() +// if config.LastMigration != "" { +// c, err := utils.CompareSemver(config.LastMigration, "0.14.0") +// if err != nil { +// utils.Fatal("Can't read field 'lastMigration' from config", err) +// } +// if(c > 0) { +// return +// } +// } + +// if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil { +// utils.Log("MigratePre014: No database found, trying to migrate pre-0.14 data") + +// cu2, errCo := utils.GetCollection(utils.GetRootAppId(), "users") +// if errCo != nil { +// // Assuming we dont need to migrate +// utils.Log("MigratePre014: No database, assuming we dont need to migrate") +// utils.Debug(errCo.Error()) +// CommitMigrationPre014() +// return +// } + +// cd2, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") +// if errCo != nil { +// // Assuming we dont need to migrate +// utils.Log("MigratePre014: No database, assuming we dont need to migrate") +// utils.Debug(errCo.Error()) +// CommitMigrationPre014() +// return +// } + +// utils.Log("MigratePre014: Migrating pre-0.14 data") + +// cu, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") + +// if errCo != nil { +// utils.Fatal("Error trying to migrate pre-0.14 data [1]", errCo) +// return +// } + +// // copy users from cu2 to cu +// cursor, err := cu2.Find(nil, map[string]interface{}{}) +// if err != nil && err != mongo.ErrNoDocuments { +// utils.Fatal("Error trying to migrate pre-0.14 data [3]", err) +// return +// } +// defer cursor.Close(nil) + +// users := []utils.User{} + +// if err = cursor.All(nil, &users); err != nil { +// utils.Fatal("Error trying to migrate pre-0.14 data [4]", err) +// return +// } + +// for _, user := range users { +// _, err := cu.InsertOne(nil, user) +// if err != nil { +// utils.Fatal("Error trying to migrate pre-0.14 data [5]", err) +// return +// } +// } - closeDb() +// closeDb() - utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(users)) + " users") +// utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(users)) + " users") - cd, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") - defer closeDb() - - if errCo != nil { - utils.Fatal("Error trying to migrate pre-0.14 data [2]", errCo) - return - } - - // copy devices from cd2 to cd - cursor, err = cd2.Find(nil, map[string]interface{}{}) - if err != nil && err != mongo.ErrNoDocuments { - utils.Fatal("Error trying to migrate pre-0.14 data [6]", err) - return - } - - defer cursor.Close(nil) - - devices := []utils.ConstellationDevice{} - - if err = cursor.All(context.Background(), &devices); err != nil { - utils.Fatal("Error trying to migrate pre-0.14 data [7]", err) - return - } - - for _, device := range devices { - _, err := cd.InsertOne(context.Background(), device) - if err != nil { - utils.Fatal("Error trying to migrate pre-0.14 data [8]", err) - return - } - } +// cd, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") +// defer closeDb() + +// if errCo != nil { +// utils.Fatal("Error trying to migrate pre-0.14 data [2]", errCo) +// return +// } + +// // copy devices from cd2 to cd +// cursor, err = cd2.Find(nil, map[string]interface{}{}) +// if err != nil && err != mongo.ErrNoDocuments { +// utils.Fatal("Error trying to migrate pre-0.14 data [6]", err) +// return +// } + +// defer cursor.Close(nil) + +// devices := []utils.ConstellationDevice{} + +// if err = cursor.All(context.Background(), &devices); err != nil { +// utils.Fatal("Error trying to migrate pre-0.14 data [7]", err) +// return +// } + +// for _, device := range devices { +// _, err := cd.InsertOne(context.Background(), device) +// if err != nil { +// utils.Fatal("Error trying to migrate pre-0.14 data [8]", err) +// return +// } +// } - utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(devices)) + " devices") +// utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(devices)) + " devices") - CommitMigrationPre014() - } -} \ No newline at end of file +// CommitMigrationPre014() +// } +// } \ No newline at end of file diff --git a/src/migrate.go b/src/migrate.go new file mode 100644 index 00000000..fdb2742b --- /dev/null +++ b/src/migrate.go @@ -0,0 +1,149 @@ +package main + +import ( + "os" + "strings" + "context" + // "fmt" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" + "go.mongodb.org/mongo-driver/mongo/writeconcern" + "go.mongodb.org/mongo-driver/bson" + + "github.com/azukaar/cosmos-server/src/utils" +) +func MigratePre014Coll(collection string, from *mongo.Client) { + name := os.Getenv("MONGODB_NAME") + if name == "" { + name = "COSMOS" + } + + utils.Log("Migrating collection " + collection + " from database " + name) + + applicationId := utils.GetRootAppId() + + cf := from.Database(name).Collection(applicationId + "_" + collection) + ct, closeDb, err := utils.GetEmbeddedCollection(utils.GetRootAppId(), collection) + if err != nil { + utils.Error("Error getting collection " + applicationId + "_" + collection + " from database " + name, err) + return + } + defer closeDb() + + // get all documents + opts := options.Find() + cur, err := cf.Find(context.Background(), bson.D{}, opts) + if err != nil { + utils.Error("Error getting documents from " + collection + " collection", err) + return + } + defer cur.Close(context.Background()) + + var batch []interface{} + batchSize := 100 // Define a suitable batch size + + for cur.Next(context.Background()) { + var elem bson.D + if err := cur.Decode(&elem); err != nil { + utils.Error("Error decoding document from " + collection + " collection", err) + continue + } + + batch = append(batch, elem) + + if len(batch) >= batchSize { + if _, err := ct.InsertMany(context.Background(), batch); err != nil { + utils.Error("Error inserting batch into " + collection + " collection", err) + } + batch = batch[:0] // Clear the batch + } + } + + // Insert any remaining documents + if len(batch) > 0 { + if _, err := ct.InsertMany(context.Background(), batch); err != nil { + utils.Error("Error inserting remaining documents into " + collection + " collection", err) + } + } + + if err := cur.Err(); err != nil { + utils.Error("Error iterating over documents from " + collection + " collection", err) + } +} + + +func MigratePre014() { + config := utils.GetMainConfig() + + // check if COSMOS.db does NOT exist + if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil && config.MongoDB != "" { + // connect to MongoDB + utils.Log("Connecting to MongoDB...") + + + mongoURL := utils.GetMainConfig().MongoDB + + var err error + + opts := options.Client().ApplyURI(mongoURL).SetRetryWrites(true).SetWriteConcern(writeconcern.New(writeconcern.WMajority())) + + if os.Getenv("HOSTNAME") == "" || utils.IsHostNetwork { + hostname := opts.Hosts[0] + // split port + hostnameParts := strings.Split(hostname, ":") + hostname = hostnameParts[0] + port := "27017" + + if len(hostnameParts) > 1 { + port = hostnameParts[1] + } + + utils.Log("Getting Mongo DB IP from name : " + hostname + " (port " + port + ")") + + ip, _ := utils.GetContainerIPByName(hostname) + if ip != "" { + // IsDBaContainer = true + opts.SetHosts([]string{ip + ":" + port}) + utils.Log("Mongo DB IP : " + ip) + } + } + + client, err := mongo.Connect(context.TODO(), opts) + + if err != nil { + panic(err) + } + + // Ping the primary + if err := client.Ping(context.TODO(), readpref.Primary()); err != nil { + panic(err) + } + + utils.Log("Successfully connected to the database.") + + ct, closeDb, err := utils.GetEmbeddedCollection(utils.GetRootAppId(), "events") + if err != nil { + return + } + defer closeDb() + + // Create a date index + model := mongo.IndexModel{ + Keys: bson.M{"Date": -1}, + } + + // Creating the index + _, err = ct.Indexes().CreateOne(context.Background(), model) + if err != nil { + utils.Error("Metrics - Create Index", err) + return // Handle error appropriately + } + + MigratePre014Coll("users", client) + MigratePre014Coll("devices", client) + // MigratePre014Coll("events", client) + // MigratePre014Coll("notifications", client) + // MigratePre014Coll("metrics", client) + } +} \ No newline at end of file From a91cfa8c324a5cfb40be9d0da2f42cb6819d85b1 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 12:01:33 +0000 Subject: [PATCH 10/31] [release] v0.14.0-unstable3 --- changelog.md | 2 + package.json | 2 +- src/docker/api_newDB.go | 56 +++++----- src/docker/docker.go | 238 ++++++++++++++++++++++------------------ src/docker/run.go | 182 ++++++++++++++---------------- src/image.go | 5 +- src/index.go | 5 +- src/migrate.go | 86 ++++++++++++++- src/newInstall.go | 2 +- src/utils/db.go | 40 +++++-- src/utils/types.go | 11 ++ 11 files changed, 371 insertions(+), 258 deletions(-) diff --git a/changelog.md b/changelog.md index 2d26bcff..575907c6 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ - Cosmos is now fully functional dockerless - Reworked Cosmos Compose for better compatibility with docker-compose.yml files - Added a "compose" tab to edit containers in text mode + - Moved critical data (credentials and VPN) out of the database, to keep Cosmos online in case of incidents + - New Database "puppet" mode that allows Cosmos to manage the database for you - Improved network IP resolution for containers, including supporting any network mode - Added support for markets and template directly with docker-compose.yml files - Removed all sort of container bootstrapping (much faster boot) diff --git a/package.json b/package.json index a318e6bf..efe59858 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable2", + "version": "0.14.0-unstable3", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/api_newDB.go b/src/docker/api_newDB.go index 142a7c23..a85b80d5 100644 --- a/src/docker/api_newDB.go +++ b/src/docker/api_newDB.go @@ -1,12 +1,8 @@ package docker import ( - "net/http" - "encoding/json" - "time" "os" - - "github.com/azukaar/cosmos-server/src/utils" + "time" ) func restart() { @@ -14,32 +10,32 @@ func restart() { os.Exit(0) } -func NewDBRoute(w http.ResponseWriter, req *http.Request) { - if utils.AdminOnly(w, req) != nil { - return - } +// func NewDBRoute(w http.ResponseWriter, req *http.Request) { +// if utils.AdminOnly(w, req) != nil { +// return +// } - if(req.Method == "GET") { - costr, err := NewDB(w, req) +// if(req.Method == "GET") { +// costr, err := NewDB(w, req) - if err != nil { - utils.Error("NewDB: Error while creating new DB", err) - utils.HTTPError(w, "Error while creating new DB", http.StatusInternalServerError, "DB001") - return - } +// if err != nil { +// utils.Error("NewDB: Error while creating new DB", err) +// utils.HTTPError(w, "Error while creating new DB", http.StatusInternalServerError, "DB001") +// return +// } - config := utils.GetBaseMainConfig() - config.MongoDB = costr - utils.SaveConfigTofile(config) +// config := utils.GetBaseMainConfig() +// config.MongoDB = costr +// utils.SaveConfigTofile(config) - json.NewEncoder(w).Encode(map[string]interface{}{ - "status": "OK", - }) - - go restart() - } else { - utils.Error("UserList: Method not allowed" + req.Method, nil) - utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") - return - } -} \ No newline at end of file +// json.NewEncoder(w).Encode(map[string]interface{}{ +// "status": "OK", +// }) + +// go restart() +// } else { +// utils.Error("UserList: Method not allowed" + req.Method, nil) +// utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") +// return +// } +// } \ No newline at end of file diff --git a/src/docker/docker.go b/src/docker/docker.go index 84ae2cb6..f42c372d 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -713,134 +713,160 @@ type ContainerStats struct { } func Stats(container types.Container) (ContainerStats, error) { - // utils.Debug("StatsAll - Getting stats for " + container.Names[0]) - // utils.Debug("Time: " + time.Now().String()) - - statsBody, err := DockerClient.ContainerStats(DockerContext, container.ID, false) - if err != nil { - return ContainerStats{}, fmt.Errorf("error fetching stats for container %s: %s", container.ID, err) - } + // utils.Debug("StatsAll - Getting stats for " + container.Names[0]) + // utils.Debug("Time: " + time.Now().String()) + + statsBody, err := DockerClient.ContainerStats(DockerContext, container.ID, false) + if err != nil { + return ContainerStats{}, fmt.Errorf("error fetching stats for container %s: %s", container.ID, err) + } - defer statsBody.Body.Close() + defer statsBody.Body.Close() - stats := types.StatsJSON{} - if err := json.NewDecoder(statsBody.Body).Decode(&stats); err != nil { - return ContainerStats{}, fmt.Errorf("error decoding stats for container %s: %s", container.ID, err) - } + stats := types.StatsJSON{} + if err := json.NewDecoder(statsBody.Body).Decode(&stats); err != nil { + return ContainerStats{}, fmt.Errorf("error decoding stats for container %s: %s", container.ID, err) + } - previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage - previousSystem := stats.PreCPUStats.SystemUsage + previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage + previousSystem := stats.PreCPUStats.SystemUsage - cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) - systemDelta := float64(stats.CPUStats.SystemUsage) - float64(previousSystem) + cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU) + systemDelta := float64(stats.CPUStats.SystemUsage) - float64(previousSystem) - perCore := len(stats.CPUStats.CPUUsage.PercpuUsage) - if perCore == 0 { - utils.Debug("StatsAll - Docker CPU PercpuUsage is 0") - perCore = 1 - } + perCore := len(stats.CPUStats.CPUUsage.PercpuUsage) + if perCore == 0 { + utils.Debug("StatsAll - Docker CPU PercpuUsage is 0") + perCore = 1 + } + + // utils.Debug("StatsAll - CPU CPUUsage TotalUsage " + strconv.FormatUint(stats.CPUStats.CPUUsage.TotalUsage, 10)) + // utils.Debug("StatsAll - CPU PreCPUStats TotalUsage " + strconv.FormatUint(stats.PreCPUStats.CPUUsage.TotalUsage, 10)) + // utils.Debug("StatsAll - CPU CPUUsage PercpuUsage " + strconv.Itoa(perCore)) + // utils.Debug("StatsAll - CPU CPUUsage SystemUsage " + strconv.FormatUint(stats.CPUStats.SystemUsage, 10)) + + // utils.Debug("StatsAll - CPU CPUUsage CPU Delta " + strconv.FormatFloat(cpuDelta, 'f', 6, 64)) + // utils.Debug("StatsAll - CPU CPUUsage System Delta " + strconv.FormatFloat(systemDelta, 'f', 6, 64)) - // utils.Debug("StatsAll - CPU CPUUsage TotalUsage " + strconv.FormatUint(stats.CPUStats.CPUUsage.TotalUsage, 10)) - // utils.Debug("StatsAll - CPU PreCPUStats TotalUsage " + strconv.FormatUint(stats.PreCPUStats.CPUUsage.TotalUsage, 10)) - // utils.Debug("StatsAll - CPU CPUUsage PercpuUsage " + strconv.Itoa(perCore)) - // utils.Debug("StatsAll - CPU CPUUsage SystemUsage " + strconv.FormatUint(stats.CPUStats.SystemUsage, 10)) + cpuUsage := 0.0 + + if systemDelta > 0 && cpuDelta > 0 { + cpuUsage = (cpuDelta / systemDelta) * float64(perCore) * 100 - // utils.Debug("StatsAll - CPU CPUUsage CPU Delta " + strconv.FormatFloat(cpuDelta, 'f', 6, 64)) - // utils.Debug("StatsAll - CPU CPUUsage System Delta " + strconv.FormatFloat(systemDelta, 'f', 6, 64)) + // utils.Debug("StatsAll - CPU CPUUsage " + strconv.FormatFloat(cpuUsage, 'f', 6, 64)) + } else { + utils.Debug("StatsAll - Error calculating CPU usage for " + container.Names[0]) + } + + // memUsage := float64(stats.MemoryStats.Usage) / float64(stats.MemoryStats.Limit) * 100 + + netRx := 0.0 + netTx := 0.0 + + for _, net := range stats.Networks { + netRx += float64(net.RxBytes) + netTx += float64(net.TxBytes) + } - cpuUsage := 0.0 + containerStats := ContainerStats{ + Name: strings.TrimPrefix(container.Names[0], "/"), + CPUUsage: cpuUsage * 100, + MemUsage: stats.MemoryStats.Usage, + MemLimit: stats.MemoryStats.Limit, + NetworkRx: netRx, + NetworkTx: netTx, + } - if systemDelta > 0 && cpuDelta > 0 { - cpuUsage = (cpuDelta / systemDelta) * float64(perCore) * 100 - - // utils.Debug("StatsAll - CPU CPUUsage " + strconv.FormatFloat(cpuUsage, 'f', 6, 64)) - } else { - utils.Debug("StatsAll - Error calculating CPU usage for " + container.Names[0]) - } + return containerStats, nil +} - // memUsage := float64(stats.MemoryStats.Usage) / float64(stats.MemoryStats.Limit) * 100 - - netRx := 0.0 - netTx := 0.0 - - for _, net := range stats.Networks { - netRx += float64(net.RxBytes) - netTx += float64(net.TxBytes) - } +func StatsAll() ([]ContainerStats, error) { + containers, err := ListContainers() + if err != nil { + utils.Error("StatsAll", err) + return nil, err + } + + var containerStatsList []ContainerStats + var wg sync.WaitGroup + semaphore := make(chan struct{}, 5) // A channel with a buffer size of 5 for controlling parallelism. - containerStats := ContainerStats{ - Name: strings.TrimPrefix(container.Names[0], "/"), - CPUUsage: cpuUsage * 100, - MemUsage: stats.MemoryStats.Usage, - MemLimit: stats.MemoryStats.Limit, - NetworkRx: netRx, - NetworkTx: netTx, + for _, container := range containers { + // If not running, skip this container + if container.State != "running" { + continue } - return containerStats, nil + wg.Add(1) + semaphore <- struct{}{} // Acquire a semaphore slot, limiting parallelism. + + go func(container types.Container) { + defer func() { + <-semaphore // Release the semaphore slot when done. + wg.Done() + }() + + stat, err := Stats(container) + if err != nil { + utils.Error("StatsAll", err) + return + } + containerStatsList = append(containerStatsList, stat) + }(container) } - func StatsAll() ([]ContainerStats, error) { - containers, err := ListContainers() - if err != nil { - utils.Error("StatsAll", err) - return nil, err + wg.Wait() // Wait for all goroutines to finish. + + return containerStatsList, nil +} + +func StopContainer(containerName string) { + err := DockerClient.ContainerStop(DockerContext, containerName, container.StopOptions{}) + if err != nil { + utils.Error("StopContainer", err) + return + } +} + +func CheckDockerNetworkMode() string { + if os.Getenv("HOSTNAME") != "" { + errD := Connect() + if errD != nil { + utils.Error("Checking Host Network", errD) + return "" } - var containerStatsList []ContainerStats - var wg sync.WaitGroup - semaphore := make(chan struct{}, 5) // A channel with a buffer size of 5 for controlling parallelism. - - for _, container := range containers { - // If not running, skip this container - if container.State != "running" { - continue - } - - wg.Add(1) - semaphore <- struct{}{} // Acquire a semaphore slot, limiting parallelism. - - go func(container types.Container) { - defer func() { - <-semaphore // Release the semaphore slot when done. - wg.Done() - }() - - stat, err := Stats(container) - if err != nil { - utils.Error("StatsAll", err) - return - } - containerStatsList = append(containerStatsList, stat) - }(container) + container, err := DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME")) + if err != nil { + utils.Error("Checking Host Network", err) } - - wg.Wait() // Wait for all goroutines to finish. - - return containerStatsList, nil + return string(container.HostConfig.NetworkMode) } + return "" +} - func StopContainer(containerName string) { - err := DockerClient.ContainerStop(DockerContext, containerName, container.StopOptions{}) +func InspectContainer(containerName string) (types.ContainerJSON, error) { + errD := Connect() + if errD != nil { + utils.Error("InspectContainer", errD) + return types.ContainerJSON{}, errD + } + + container, err := DockerClient.ContainerInspect(DockerContext, containerName) if err != nil { - utils.Error("StopContainer", err) - return + utils.Error("InspectContainer", err) + return types.ContainerJSON{}, err } - } - func CheckDockerNetworkMode() string { - if os.Getenv("HOSTNAME") != "" { - errD := Connect() - if errD != nil { - utils.Error("Checking Host Network", errD) - return "" - } + return container, nil +} - container, err := DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME")) - if err != nil { - utils.Error("Checking Host Network", err) +func GetEnv(env []string, key string) string { + for _, kv := range env { + parts := strings.SplitN(kv, "=", 2) + if len(parts) == 2 && parts[0] == key { + return parts[1] } - return string(container.HostConfig.NetworkMode) - } - return "" - } \ No newline at end of file + } + return "" +} \ No newline at end of file diff --git a/src/docker/run.go b/src/docker/run.go index d4cf5d74..99c93c03 100644 --- a/src/docker/run.go +++ b/src/docker/run.go @@ -2,51 +2,53 @@ package docker import ( "github.com/azukaar/cosmos-server/src/utils" - "io" "os" "net/http" "fmt" "errors" // "github.com/docker/docker/client" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types" + // "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" "runtime" "golang.org/x/sys/cpu" ) -type VolumeMount struct { - Destination string - Volume *types.Volume +func CheckPuppetDB() { + config := utils.GetMainConfig() + if config.Database.PuppetMode { + utils.Log("Puppet mode enabled. Checking for DB...") + err := utils.DB() + if err != nil { + utils.Error("Puppet mode enabled. DB not found. Recreating DB...", err) + service, err := RunDB(config.Database) + if err != nil { + utils.Error("Puppet mode enabled. DB not found. Error while recreating DB...", err) + return + } + + err = CreateService(service, + func (msg string) { + utils.Log(msg) + }, + ) + + if err != nil { + utils.Error("Puppet mode enabled. DB not found. Error while recreating DB...", err) + return + } + } + } } -func NewDB(w http.ResponseWriter, req *http.Request) (string, error) { - w.Header().Set("X-Content-Type-Options", "nosniff") - w.Header().Set("Transfer-Encoding", "chunked") - - flusher, ok := w.(http.Flusher) - if !ok { - http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) - return "", errors.New("Streaming unsupported!") - } - - fmt.Fprintf(w, "NewInstall: Create DB\n") - flusher.Flush() - - id := utils.GenerateRandomString(3) - mongoUser := "cosmos-" + utils.GenerateRandomString(5) - mongoPass := utils.GenerateRandomString(24) - monHost := "cosmos-mongo-" + id - - imageName := "mongo:6" +func RunDB(db utils.DatabaseConfig) (DockerServiceCreateRequest, error) { + imageName := "mongo:" + db.Version //if ARM use arm64v8/mongo if runtime.GOARCH == "arm64" { utils.Warn("ARM64 detected. Using ARM mongo 4.4.18") imageName = "arm64v8/mongo:4.4.18" - // if CPU is missing AVX, use 4.4 } else if runtime.GOARCH == "amd64" && !cpu.X86.HasAVX { utils.Warn("CPU does not support AVX. Using mongo 4.4") @@ -57,15 +59,14 @@ func NewDB(w http.ResponseWriter, req *http.Request) (string, error) { Services: map[string]ContainerCreateRequestContainer {}, } - - service.Services[monHost] = ContainerCreateRequestContainer{ - Name: monHost, + service.Services[db.Hostname] = ContainerCreateRequestContainer{ + Name: db.Hostname, Image: imageName, RestartPolicy: "always", // Command: "--wiredTigerCacheSizeGB 0.25", Environment: []string{ - "MONGO_INITDB_ROOT_USERNAME=" + mongoUser, - "MONGO_INITDB_ROOT_PASSWORD=" + mongoPass, + "MONGO_INITDB_ROOT_USERNAME=" + db.Username, + "MONGO_INITDB_ROOT_PASSWORD=" + db.Password, }, Labels: map[string]string{ "cosmos-auto-update": "true", @@ -74,105 +75,86 @@ func NewDB(w http.ResponseWriter, req *http.Request) (string, error) { Volumes: []mount.Mount{ { Type: mount.TypeVolume, - Source: "cosmos-mongo-data-" + id, + Source: db.DbVolume, Target: "/data/db", }, { Type: mount.TypeVolume, - Source: "cosmos-mongo-config-" + id, + Source: db.ConfigVolume, Target: "/data/configdb", }, }, }; if os.Getenv("HOSTNAME") != "" && !utils.IsHostNetwork { - newNetwork, errNC := CreateCosmosNetwork(monHost) + newNetwork, errNC := CreateCosmosNetwork(db.Hostname) if errNC != nil { - utils.Error("CreateService: Network", errNC) - fmt.Fprintf(w, "CreateService: Network: %s\n", errNC) - flusher.Flush() - return "", errNC + return DockerServiceCreateRequest{}, errNC } - service.Services[monHost].Labels["cosmos-network-name"] = newNetwork - service.Services[monHost].Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} + service.Services[db.Hostname].Labels["cosmos-network-name"] = newNetwork + service.Services[db.Hostname].Networks[newNetwork] = ContainerCreateRequestServiceNetwork{} AttachNetworkToCosmos(newNetwork) } - err := CreateService(service, - func (msg string) { - utils.Log(msg) - fmt.Fprintf(w, msg + "\n") - flusher.Flush() - }, - ) - - if err != nil { - return "", err - } - - return "mongodb://"+mongoUser+":"+mongoPass+"@"+monHost+":27017", nil + return service, nil } -func RunContainer(imagename string, containername string, inputEnv []string, volumes []VolumeMount) error { - errD := Connect() - if errD != nil { - utils.Error("Docker Connect", errD) - return errD - } - - pull, errPull := DockerPullImage(imagename) - if errPull != nil { - utils.Error("Docker Pull", errPull) - return errPull +func NewDB(w http.ResponseWriter, req *http.Request) (utils.DatabaseConfig, error) { + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Transfer-Encoding", "chunked") + + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) + return utils.DatabaseConfig{}, errors.New("Streaming unsupported!") } - io.Copy(os.Stdout, pull) - var mounts []mount.Mount + fmt.Fprintf(w, "NewInstall: Create DB\n") + flusher.Flush() - for _, volume := range volumes { - mount := mount.Mount{ - Type: mount.TypeVolume, - Source: volume.Volume.Name, - Target: volume.Destination, - } - mounts = append(mounts, mount) + id := utils.GenerateRandomString(3) + mongoUser := "cosmos-" + utils.GenerateRandomString(5) + mongoPass := utils.GenerateRandomString(24) + monHost := "cosmos-mongo-" + id + + imageVersion := "6" + + dbConf := utils.DatabaseConfig { + PuppetMode: true, + Hostname: monHost, + DbVolume: "cosmos-mongo-data-" + id, + ConfigVolume: "cosmos-mongo-config-" + id, + Version: imageVersion, + Username: mongoUser, + Password: mongoPass, } - hostConfig := &container.HostConfig{ - Mounts : mounts, - RestartPolicy: container.RestartPolicy{ - Name: "always", - }, + service, err := RunDB(dbConf) + + if err != nil { + utils.Error("NewDB: Error while creating new DB", err) + fmt.Fprintf(w, "NewDB: Error while creating new DB: %s\n", err) + flusher.Flush() + return dbConf, err } - config := &container.Config{ - Image: imagename, - Env: inputEnv, - Hostname: containername, - Labels: map[string]string{ - "cosmos-force-network-secured": "true", + err = CreateService(service, + func (msg string) { + utils.Log(msg) + fmt.Fprintf(w, msg + "\n") + flusher.Flush() }, - // ExposedPorts: exposedPorts, - } - - cont, err := DockerClient.ContainerCreate( - DockerContext, - config, - hostConfig, - nil, - nil, - containername, ) if err != nil { - utils.Error("Docker Container Create", err) - return err + utils.Error("NewDB: Error while creating new DB", err) + fmt.Fprintf(w, "NewDB: Error while creating new DB: %s\n", err) + flusher.Flush() + return dbConf, err } - DockerClient.ContainerStart(DockerContext, cont.ID, types.ContainerStartOptions{}) - utils.Log("Container created " + cont.ID) - - return nil + // return "mongodb://"+mongoUser+":"+mongoPass+"@"+monHost+":27017", nil + return dbConf, nil } diff --git a/src/image.go b/src/image.go index 608a4f7b..4ce80f02 100644 --- a/src/image.go +++ b/src/image.go @@ -9,13 +9,10 @@ import ( "encoding/json" "strings" "fmt" - "context" - + "github.com/gorilla/mux" "github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/docker" - - "go.mongodb.org/mongo-driver/mongo" ) var validExtensions = map[string]bool{ diff --git a/src/index.go b/src/index.go index 6bc074f2..4d079593 100644 --- a/src/index.go +++ b/src/index.go @@ -26,7 +26,6 @@ func main() { LoadConfig() utils.CheckHostNetwork() - utils.InitDBBuffers() go CRON() @@ -53,6 +52,10 @@ func main() { MigratePre013() MigratePre014() + docker.CheckPuppetDB() + + utils.InitDBBuffers() + utils.Log("Starting monitoring services...") metrics.Init() diff --git a/src/migrate.go b/src/migrate.go index fdb2742b..135efd7f 100644 --- a/src/migrate.go +++ b/src/migrate.go @@ -12,7 +12,9 @@ import ( "go.mongodb.org/mongo-driver/bson" "github.com/azukaar/cosmos-server/src/utils" + "github.com/azukaar/cosmos-server/src/docker" ) + func MigratePre014Coll(collection string, from *mongo.Client) { name := os.Getenv("MONGODB_NAME") if name == "" { @@ -76,11 +78,14 @@ func MigratePre014Coll(collection string, from *mongo.Client) { func MigratePre014() { config := utils.GetMainConfig() + utils.Log("MigratePre014: Migration of database...") + // check if COSMOS.db does NOT exist if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil && config.MongoDB != "" { // connect to MongoDB utils.Log("Connecting to MongoDB...") + if false { mongoURL := utils.GetMainConfig().MongoDB @@ -142,8 +147,83 @@ func MigratePre014() { MigratePre014Coll("users", client) MigratePre014Coll("devices", client) - // MigratePre014Coll("events", client) - // MigratePre014Coll("notifications", client) - // MigratePre014Coll("metrics", client) + + } + + // Migrate DB to puppet mode + + utils.DB() + + if utils.DBContainerName != "" { + utils.Log("Migrating database to puppet mode...") + + mongoContainer, err := docker.InspectContainer(utils.DBContainerName) + if err != nil { + utils.Fatal("MigratePre014 - Cannot migrate database to puppet mode, container " + utils.DBContainerName + " not found", err) + return + } + + dbVolume := "" + dbConfigVolume := "" + + for _, mount := range mongoContainer.Mounts { + if mount.Destination == "/data/db" { + dbVolume = mount.Name + } else if mount.Destination == "/data/configdb" { + dbConfigVolume = mount.Name + } + } + + if dbVolume == "" || dbConfigVolume == "" { + utils.Error("MigratePre014 - Cannot migrate database to puppet mode, volumes not found", nil) + MigratePre014_FallBackNoPuppet() + return + } + + currentVersion := docker.GetEnv(mongoContainer.Config.Env, "MONGO_VERSION") + username := docker.GetEnv(mongoContainer.Config.Env, "ME_CONFIG_MONGODB_ADMINUSERNAME") + if username == "" { + username = docker.GetEnv(mongoContainer.Config.Env, "MONGO_INITDB_ROOT_USERNAME") + } + password := docker.GetEnv(mongoContainer.Config.Env, "ME_CONFIG_MONGODB_ADMINPASSWORD") + if password == "" { + password = docker.GetEnv(mongoContainer.Config.Env, "MONGO_INITDB_ROOT_PASSWORD") + } + + if currentVersion == "" { + utils.Error("MigratePre014 - Cannot migrate database to puppet mode, version not found", nil) + MigratePre014_FallBackNoPuppet() + return + } + + + if username == "" || password == "" { + utils.Error("MigratePre014 - Cannot migrate database to puppet mode, credentials not found", nil) + MigratePre014_FallBackNoPuppet() + return + } + + dbconfig := utils.DatabaseConfig{ + PuppetMode: true, + Hostname: utils.DBContainerName, + DbVolume: dbVolume, + ConfigVolume: dbConfigVolume, + Version: strings.Split(currentVersion, ".")[0], + Username: username, + Password: password, + } + + config.Database = dbconfig + + utils.SetBaseMainConfig(config) + } + } +} + +func MigratePre014_FallBackNoPuppet() { + config := utils.ReadConfigFromFile() + config.Database = utils.DatabaseConfig{ + PuppetMode: false, } + utils.SetBaseMainConfig(config) } \ No newline at end of file diff --git a/src/newInstall.go b/src/newInstall.go index 3f1b9def..7b32b658 100644 --- a/src/newInstall.go +++ b/src/newInstall.go @@ -107,7 +107,7 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) { return } - newConfig.MongoDB = strco + newConfig.Database = strco utils.SaveConfigTofile(newConfig) utils.LoadBaseMainConfig(newConfig) utils.Log("NewInstall: MongoDB created, waiting for it to be ready") diff --git a/src/utils/db.go b/src/utils/db.go index 33bfeebf..1e641154 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -19,20 +19,32 @@ import ( var client *mongo.Client -var IsDBaContainer bool +var DBContainerName string func DB() error { - if(GetMainConfig().DisableUserManagement) { + config := GetMainConfig() + + if(config.DisableUserManagement) { return errors.New("User Management is disabled") } - mongoURL := GetMainConfig().MongoDB + mongoURL := config.MongoDB if(client != nil && client.Ping(context.TODO(), readpref.Primary()) == nil) { return nil } Log("(Re) Connecting to the database...") + + DBContainerName = "" + + isPuppetMode := config.Database.PuppetMode + puppetHostname := config.Database.Hostname + + if isPuppetMode { + mongoURL = "mongodb://" + puppetHostname + ":27017" + DBContainerName = puppetHostname + } if mongoURL == "" { return errors.New("MongoDB URL is not set, cannot connect to the database.") @@ -42,24 +54,28 @@ func DB() error { opts := options.Client().ApplyURI(mongoURL).SetRetryWrites(true).SetWriteConcern(writeconcern.New(writeconcern.WMajority())) - IsDBaContainer = false - if os.Getenv("HOSTNAME") == "" || IsHostNetwork { - hostname := opts.Hosts[0] - // split port - hostnameParts := strings.Split(hostname, ":") - hostname = hostnameParts[0] + hostname := "" port := "27017" - if len(hostnameParts) > 1 { - port = hostnameParts[1] + if !isPuppetMode { + hostname = opts.Hosts[0] + // split port + hostnameParts := strings.Split(hostname, ":") + hostname = hostnameParts[0] + + if len(hostnameParts) > 1 { + port = hostnameParts[1] + } + } else { + hostname = puppetHostname } Log("Getting Mongo DB IP from name : " + hostname + " (port " + port + ")") ip, _ := GetContainerIPByName(hostname) if ip != "" { - IsDBaContainer = true + DBContainerName = hostname opts.SetHosts([]string{ip + ":" + port}) Log("Mongo DB IP : " + ip) } diff --git a/src/utils/types.go b/src/utils/types.go index 815d1d25..b0887e88 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -77,6 +77,7 @@ type User struct { type Config struct { LoggingLevel LoggingLevel `required,validate:"oneof=DEBUG INFO WARNING ERROR"` MongoDB string + Database DatabaseConfig `validate:"dive"` DisableUserManagement bool NewInstall bool `validate:"boolean"` HTTPConfig HTTPConfig `validate:"required,dive,required"` @@ -97,6 +98,16 @@ type Config struct { LastMigration string } +type DatabaseConfig struct { + PuppetMode bool + Hostname string + DbVolume string + ConfigVolume string + Version string + Username string + Password string +} + type HomepageConfig struct { Background string Widgets []string From 268ea68da92a7debcf6d3baaaf2f0291ae870901 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 12:11:52 +0000 Subject: [PATCH 11/31] [release] v0.14.0-unstable4 --- package.json | 2 +- src/migrate.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index efe59858..aa2fcbf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable3", + "version": "0.14.0-unstable4", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/migrate.go b/src/migrate.go index 135efd7f..9286efc9 100644 --- a/src/migrate.go +++ b/src/migrate.go @@ -85,8 +85,6 @@ func MigratePre014() { // connect to MongoDB utils.Log("Connecting to MongoDB...") - if false { - mongoURL := utils.GetMainConfig().MongoDB var err error @@ -148,7 +146,7 @@ func MigratePre014() { MigratePre014Coll("users", client) MigratePre014Coll("devices", client) - } + // Migrate DB to puppet mode From 3f52fa9c28e50c8963372c25a057e0b7319cc6ed Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 12:37:20 +0000 Subject: [PATCH 12/31] [release] v0.14.0-unstable5 --- package.json | 2 +- src/constellation/DNS.go | 4 ++-- src/utils/utils.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index aa2fcbf8..f61fa193 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable4", + "version": "0.14.0-unstable5", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/constellation/DNS.go b/src/constellation/DNS.go index 40864cd8..822c939e 100644 --- a/src/constellation/DNS.go +++ b/src/constellation/DNS.go @@ -179,8 +179,8 @@ func InitDNS() { if(!config.ConstellationConfig.DNSDisabled) { go (func() { dns.HandleFunc(".", handleDNSRequest) - server := &dns.Server{Addr: ":" + DNSPort, Net: "udp"} - + server := &dns.Server{Addr: "192.168.201.1:" + DNSPort, Net: "udp"} + utils.Log("Starting DNS server on :" + DNSPort) if err := server.ListenAndServe(); err != nil { utils.Fatal("Failed to start server: %s\n", err) diff --git a/src/utils/utils.go b/src/utils/utils.go index 08410723..1febc91a 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -686,6 +686,7 @@ func IsDomain(domain string) bool { func CheckHostNetwork() { IsHostNetwork = CheckDockerNetworkMode() == "host" + utils.Log("Cosmos IsHostNetwork: " + strconv.FormatBool(IsHostNetwork)) } // compareSemver compares two semantic version strings. From 6abf24d23d3bfbc2684759d2e9e0956b6a0b26c9 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 12:47:17 +0000 Subject: [PATCH 13/31] [release] v0.14.0-unstable6 --- package.json | 2 +- src/constellation/DNS.go | 4 ++-- src/utils/utils.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f61fa193..06aa37f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable5", + "version": "0.14.0-unstable6", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/constellation/DNS.go b/src/constellation/DNS.go index 822c939e..c3ea1d5b 100644 --- a/src/constellation/DNS.go +++ b/src/constellation/DNS.go @@ -180,10 +180,10 @@ func InitDNS() { go (func() { dns.HandleFunc(".", handleDNSRequest) server := &dns.Server{Addr: "192.168.201.1:" + DNSPort, Net: "udp"} - + utils.Log("Starting DNS server on :" + DNSPort) if err := server.ListenAndServe(); err != nil { - utils.Fatal("Failed to start server: %s\n", err) + utils.Fatal("Failed to start server", err) } })() } diff --git a/src/utils/utils.go b/src/utils/utils.go index 1febc91a..82bd477b 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -686,7 +686,7 @@ func IsDomain(domain string) bool { func CheckHostNetwork() { IsHostNetwork = CheckDockerNetworkMode() == "host" - utils.Log("Cosmos IsHostNetwork: " + strconv.FormatBool(IsHostNetwork)) + Log("Cosmos IsHostNetwork: " + strconv.FormatBool(IsHostNetwork)) } // compareSemver compares two semantic version strings. From d886a86a667c70afd7a67a579d62abdd6c1a8933 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 12:58:38 +0000 Subject: [PATCH 14/31] [release] v0.14.0-unstable7 --- package.json | 2 +- src/constellation/DNS.go | 2 +- src/index.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 06aa37f6..9270f1e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable6", + "version": "0.14.0-unstable7", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/constellation/DNS.go b/src/constellation/DNS.go index c3ea1d5b..be5293a9 100644 --- a/src/constellation/DNS.go +++ b/src/constellation/DNS.go @@ -183,7 +183,7 @@ func InitDNS() { utils.Log("Starting DNS server on :" + DNSPort) if err := server.ListenAndServe(); err != nil { - utils.Fatal("Failed to start server", err) + utils.Error("Failed to start DNS server", err) } })() } diff --git a/src/index.go b/src/index.go index 4d079593..09845f32 100644 --- a/src/index.go +++ b/src/index.go @@ -70,10 +70,10 @@ func main() { utils.Log("Starting constellation services...") - constellation.InitDNS() - constellation.Init() + constellation.InitDNS() + utils.Log("Starting server...") } From c49a2b430e801805abdcda8cf85b3e119601a286 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 13:14:15 +0000 Subject: [PATCH 15/31] [release] v0.14.0-unstable8 --- package.json | 2 +- src/utils/db.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9270f1e0..9193a0fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable7", + "version": "0.14.0-unstable8", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/utils/db.go b/src/utils/db.go index 1e641154..659f5c00 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -40,9 +40,11 @@ func DB() error { isPuppetMode := config.Database.PuppetMode puppetHostname := config.Database.Hostname + username := config.Database.Username + password := config.Database.Password if isPuppetMode { - mongoURL = "mongodb://" + puppetHostname + ":27017" + mongoURL = "mongodb://" + username + ":" + password + "@" + puppetHostname + ":27017" DBContainerName = puppetHostname } From 0b63ded805752d0b60061fb362da5cf7a0c5c053 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 13:34:00 +0000 Subject: [PATCH 16/31] [release] v0.14.0-unstable9 --- package.json | 2 +- src/utils/db.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9193a0fe..87fb0e4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable8", + "version": "0.14.0-unstable9", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/utils/db.go b/src/utils/db.go index 659f5c00..534123b3 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -55,6 +55,8 @@ func DB() error { var err error opts := options.Client().ApplyURI(mongoURL).SetRetryWrites(true).SetWriteConcern(writeconcern.New(writeconcern.WMajority())) + + opts.SetConnectTimeout(5 * time.Second) if os.Getenv("HOSTNAME") == "" || IsHostNetwork { hostname := "" From 80bc5a7a50332c223839f4d4b092067d2bee1a44 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 14:54:34 +0000 Subject: [PATCH 17/31] [release] v0.14.0-unstable10 --- package.json | 2 +- src/status.go | 20 ++++++++++---------- src/utils/db.go | 10 ++++++++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 87fb0e4f..be08a60d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable9", + "version": "0.14.0-unstable10", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/status.go b/src/status.go index 0cea7e23..f16c44b8 100644 --- a/src/status.go +++ b/src/status.go @@ -11,6 +11,7 @@ import ( "github.com/azukaar/cosmos-server/src/docker" ) + func StatusRoute(w http.ResponseWriter, req *http.Request) { config := utils.GetMainConfig() @@ -20,17 +21,16 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) { if(req.Method == "GET") { utils.Log("API: Status") - - databaseStatus := true - if(!config.DisableUserManagement) { - err := utils.DB() - if err != nil { - utils.Error("Status: Database error", err) - databaseStatus = false + if config.NewInstall { + if(!config.DisableUserManagement) { + err := utils.DB() + if err != nil { + utils.Error("Status: Database error", err) + } + } else { + utils.Log("Status: User management is disabled, skipping database check") } - } else { - utils.Log("Status: User management is disabled, skipping database check") } if(!docker.DockerIsConnected) { @@ -53,7 +53,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) { // "network": utils.GetNetworkUsage(), }, "hostmode": utils.IsHostNetwork || os.Getenv("HOSTNAME") == "", - "database": databaseStatus, + "database": utils.DBStatus, "docker": docker.DockerIsConnected, "backup_status": docker.ExportError, "letsencrypt": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode == "LETSENCRYPT" && utils.GetMainConfig().HTTPConfig.SSLEmail == "", diff --git a/src/utils/db.go b/src/utils/db.go index 534123b3..77c9e426 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -20,17 +20,20 @@ import ( var client *mongo.Client var DBContainerName string +var DBStatus bool func DB() error { config := GetMainConfig() if(config.DisableUserManagement) { + DBStatus = false return errors.New("User Management is disabled") } mongoURL := config.MongoDB if(client != nil && client.Ping(context.TODO(), readpref.Primary()) == nil) { + DBStatus = true return nil } @@ -49,6 +52,7 @@ func DB() error { } if mongoURL == "" { + DBStatus = false return errors.New("MongoDB URL is not set, cannot connect to the database.") } @@ -88,18 +92,20 @@ func DB() error { client, err = mongo.Connect(context.TODO(), opts) if err != nil { + DBStatus = false return err } - defer func() { - }() // Ping the primary if err := client.Ping(context.TODO(), readpref.Primary()); err != nil { + DBStatus = false return err } initDB() + DBStatus = true + Log("Successfully connected to the database.") return nil } From 8f6ee67c8c16fa2051b89b7756f9aaadf721fd8a Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 28 Jan 2024 18:33:00 +0000 Subject: [PATCH 18/31] [release] v0.14.0-unstable11 --- package.json | 2 +- src/utils/db.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index be08a60d..bd5fb7f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable10", + "version": "0.14.0-unstable11", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/utils/db.go b/src/utils/db.go index 77c9e426..1ec0596d 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -152,6 +152,8 @@ func GetCollection(applicationId string, collection string) (*mongo.Collection, return nil, errCo } } + + DBStatus = true name := os.Getenv("MONGODB_NAME"); if name == "" { name = "COSMOS" From 5a0e81be09bf625bf7e12a56266c1c3fcfaa132d Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Fri, 2 Feb 2024 22:48:42 +0000 Subject: [PATCH 19/31] [release] v0.14.0-unstable12 --- changelog.md | 6 +- client/src/api/wrap.js | 12 +- client/src/components/MainCard.jsx | 6 - client/src/pages/config/routes/routeman.jsx | 13 +- client/src/pages/config/users/configman.jsx | 38 +++++- client/src/pages/home/index.jsx | 3 +- .../servapps/containers/docker-compose.jsx | 30 +++++ .../pages/servapps/containers/newService.jsx | 51 +++++--- package-lock.json | 15 ++- package.json | 4 +- readme.md | 2 +- src/CRON.go | 3 + src/backup.go | 92 ++++++++++++++ src/configapi/get.go | 2 + src/httpServer.go | 110 +++++++++-------- src/image.go | 114 ------------------ src/index.go | 1 - src/status.go | 2 +- src/utils/types.go | 5 +- 19 files changed, 306 insertions(+), 203 deletions(-) create mode 100644 src/backup.go diff --git a/changelog.md b/changelog.md index 575907c6..b0dafb48 100644 --- a/changelog.md +++ b/changelog.md @@ -3,13 +3,17 @@ - Reworked Cosmos Compose for better compatibility with docker-compose.yml files - Added a "compose" tab to edit containers in text mode - Moved critical data (credentials and VPN) out of the database, to keep Cosmos online in case of incidents + - Added an auto .zip backup mechanism + - Added a syntax highlighter to the compose editor - New Database "puppet" mode that allows Cosmos to manage the database for you - Improved network IP resolution for containers, including supporting any network mode - Added support for markets and template directly with docker-compose.yml files + - Add whitelist and constellation restriction options to the admin panel + - Force low RAM usage on the MongoDB container (we don't need much!) - Removed all sort of container bootstrapping (much faster boot) - Added image clean up - Replaced network clean up by vanilla docker prune - - Force low RAM usage on the MongoDB container (we don't need much!) + - Fix issue with removing IP whitelist ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/client/src/api/wrap.js b/client/src/api/wrap.js index e6e45ba6..8242565a 100644 --- a/client/src/api/wrap.js +++ b/client/src/api/wrap.js @@ -4,7 +4,17 @@ export default function wrap(apicall, noError = false) { return apicall.then(async (response) => { let rep; try { - rep = await response.json(); + rep = await response.text(); + + try { + rep = JSON.parse(rep); + } catch (err) { + rep = { + message: rep, + status: response.status, + code: response.status + }; + } } catch (err) { if (!noError) { snackit('Server error'); diff --git a/client/src/components/MainCard.jsx b/client/src/components/MainCard.jsx index b68d6367..ce116d47 100644 --- a/client/src/components/MainCard.jsx +++ b/client/src/components/MainCard.jsx @@ -53,12 +53,6 @@ const MainCard = forwardRef( ':hover': { boxShadow: boxShadow ? shadow || theme.customShadows.z1 : 'inherit' }, - '& pre': { - m: 0, - p: '16px !important', - fontFamily: theme.typography.fontFamily, - fontSize: '0.75rem' - } }} > {/* card header and action */} diff --git a/client/src/pages/config/routes/routeman.jsx b/client/src/pages/config/routes/routeman.jsx index 62b1d91f..7218ff0b 100644 --- a/client/src/pages/config/routes/routeman.jsx +++ b/client/src/pages/config/routes/routeman.jsx @@ -83,8 +83,11 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC return false; } else { let commaSepIps = values.WhitelistInboundIPs; - if(commaSepIps) { + + if(commaSepIps && commaSepIps.length) { values.WhitelistInboundIPs = commaSepIps.split(',').map((ip) => ip.trim()); + } else { + values.WhitelistInboundIPs = []; } let fullValues = { @@ -116,6 +119,14 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC } }} validate={(values) => { + let commaSepIps = values.WhitelistInboundIPs; + + if(commaSepIps && commaSepIps.length) { + values.WhitelistInboundIPs = commaSepIps.split(',').map((ip) => ip.trim()); + } else { + values.WhitelistInboundIPs = []; + } + let fullValues = { ...routeConfig, ...values, diff --git a/client/src/pages/config/users/configman.jsx b/client/src/pages/config/users/configman.jsx index c794ae7a..a5b93720 100644 --- a/client/src/pages/config/users/configman.jsx +++ b/client/src/pages/config/users/configman.jsx @@ -127,6 +127,11 @@ const ConfigManagement = () => { SecondaryColor: config && config.ThemeConfig && config.ThemeConfig.SecondaryColor, MonitoringEnabled: !config.MonitoringDisabled, + + BackupOutputDir: config.BackupOutputDir, + + AdminWhitelistIPs: config.AdminWhitelistIPs && config.AdminWhitelistIPs.join(', '), + AdminConstellationOnly: config.AdminConstellationOnly, }} validationSchema={Yup.object().shape({ @@ -147,6 +152,10 @@ const ConfigManagement = () => { BlockedCountries: values.GeoBlocking, CountryBlacklistIsWhitelist: values.CountryBlacklistIsWhitelist, MonitoringDisabled: !values.MonitoringEnabled, + BackupOutputDir: values.BackupOutputDir, + AdminConstellationOnly: values.AdminConstellationOnly, + AdminWhitelistIPs: (values.AdminWhitelistIPs && values.AdminWhitelistIPs != "") ? + values.AdminWhitelistIPs.split(',').map((x) => x.trim()) : [], HTTPConfig: { ...config.HTTPConfig, Hostname: values.Hostname, @@ -241,7 +250,7 @@ const ConfigManagement = () => { This page allow you to edit the configuration file. Any Environment Variable overwritting configuration won't appear here. - + { + + Level of logging (Default: INFO) @@ -591,6 +607,26 @@ const ConfigManagement = () => { formik.setFieldValue("CountryBlacklistIsWhitelist", false) }} variant="outlined">Reset to default (most dangerous countries) + + + + + Use those options to restrict access to the admin panel. Be careful, if you lock yourself out, you will need to manually edit the config file. + To restrict the access to your local network, you can use the "Admin Whitelist" with the IP range 192.168.0.0/16 + + + + + diff --git a/client/src/pages/home/index.jsx b/client/src/pages/home/index.jsx index acbe6e5d..df0730c8 100644 --- a/client/src/pages/home/index.jsx +++ b/client/src/pages/home/index.jsx @@ -317,8 +317,7 @@ const HomePage = () => { {isAdmin && coStatus && !coStatus.hostmode && ( - Your Cosmos server is not running in the docker host network mode. - This will make your life harder, consider switching the network mode to host mode on the cosmos container. + Your Cosmos server is not running in the docker host network mode. consider switching the network mode of the container to host mode. )} diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index 8ed9929b..2e093fbb 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -563,6 +563,36 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul }; }); } + + // CREATE DEFAULT NETWORK + if(!jsoned['cosmos-installer'] || !jsoned['cosmos-installer']['skip-default-network']) { + let hasDefaultNetwork = false; + if (jsoned.services) { + Object.keys(jsoned.services).forEach((key) => { + if(!jsoned.services[key].network_mode) { + jsoned.services[key].network_mode = 'cosmos-' + serviceName + '-default'; + hasDefaultNetwork = true; + } + }); + } + + if(hasDefaultNetwork) { + if(!jsoned.networks) { + jsoned.networks = {} + } + + jsoned.networks['cosmos-' + serviceName + '-default'] = { + Labels: { + 'cosmos.stack': serviceName, + } + } + } + } + + // REMOIVE COSMOS-INSTALLER + if (jsoned['cosmos-installer']) { + delete jsoned['cosmos-installer']; + } } setService(jsoned); diff --git a/client/src/pages/servapps/containers/newService.jsx b/client/src/pages/servapps/containers/newService.jsx index 6e88321c..616b5791 100644 --- a/client/src/pages/servapps/containers/newService.jsx +++ b/client/src/pages/servapps/containers/newService.jsx @@ -21,6 +21,13 @@ import { Link } from 'react-router-dom'; import { smartDockerLogConcat, tryParseProgressLog } from '../../../utils/docker'; import { LoadingButton } from '@mui/lab'; import LogLine from '../../../components/logLine'; +import Highlighter from '../../../components/third-party/Highlighter'; + +import Editor from 'react-simple-code-editor'; +import { highlight, languages } from 'prismjs/components/prism-core'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-yaml'; +import 'prismjs/themes/prism-okaidia.css'; const preStyle = { backgroundColor: '#000', @@ -97,6 +104,11 @@ const NewDockerService = ({service, refresh, edit}) => { }); } + let isJSON = false; + if(dockerCompose.trim().startsWith('{') && dockerCompose.trim().endsWith('}')) { + isJSON = true; + } + return

@@ -116,30 +128,31 @@ const NewDockerService = ({service, refresh, edit}) => { return {m.label} })} } + + {edit && !isDone && log.length ? : null} - {log.length || !edit ?
-        {!log.length && `
-${dockerCompose}`
-        }
+
+      {log.length ? 
         {log.map((l) => {
           return 
         })}
       
: - - setDockerCompose(e.target.value)} - sx={{...preStyle, - }} - InputProps={{ - sx: { - color: '#EEE', - }, - }} - > +
+ setDockerCompose(code)} + highlight={code => highlight(code, isJSON ? languages.json : languages.yaml)} + padding={10} + tabSize={2} + insertSpaces={true} + style={{ + fontFamily: '"Fira code", "Fira Mono", monospace', + fontSize: 12, + backgroundColor: '#000', + }} + /> +
} diff --git a/package-lock.json b/package-lock.json index 4a25ed2a..96247b32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cosmos-server", - "version": "0.12.0-unstable42", + "version": "0.14.0-unstable11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cosmos-server", - "version": "0.12.0-unstable42", + "version": "0.14.0-unstable11", "dependencies": { "@ant-design/colors": "^6.0.0", "@ant-design/icons": "^4.7.0", @@ -34,6 +34,7 @@ "history": "^5.3.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "prismjs": "^1.29.0", "prop-types": "^15.8.1", "qrcode": "^1.5.3", "react": "^18.2.0", @@ -53,6 +54,7 @@ "react-redux": "^8.0.4", "react-router": "^6.4.1", "react-router-dom": "^6.4.1", + "react-simple-code-editor": "^0.13.1", "react-syntax-highlighter": "^15.5.0", "react-window": "^1.8.7", "redux": "^4.2.0", @@ -9346,6 +9348,15 @@ "react-dom": ">=16.8" } }, + "node_modules/react-simple-code-editor": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.13.1.tgz", + "integrity": "sha512-XYeVwRZwgyKtjNIYcAEgg2FaQcCZwhbarnkJIV20U2wkCU9q/CPFBo8nRXrK4GXUz3AvbqZFsZRrpUTkqqEYyQ==", + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/react-syntax-highlighter": { "version": "15.5.0", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", diff --git a/package.json b/package.json index bd5fb7f2..997f72d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable11", + "version": "0.14.0-unstable12", "description": "", "main": "test-server.js", "bugs": { @@ -34,6 +34,7 @@ "history": "^5.3.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "prismjs": "^1.29.0", "prop-types": "^15.8.1", "qrcode": "^1.5.3", "react": "^18.2.0", @@ -53,6 +54,7 @@ "react-redux": "^8.0.4", "react-router": "^6.4.1", "react-router-dom": "^6.4.1", + "react-simple-code-editor": "^0.13.1", "react-syntax-highlighter": "^15.5.0", "react-window": "^1.8.7", "redux": "^4.2.0", diff --git a/readme.md b/readme.md index 4248229e..c140feda 100644 --- a/readme.md +++ b/readme.md @@ -6,10 +6,10 @@

Thanks to the sponsors:


null CupofDalek -null null null null +null null

diff --git a/src/CRON.go b/src/CRON.go index fe0b7cb5..9ebcdf15 100644 --- a/src/CRON.go +++ b/src/CRON.go @@ -95,6 +95,9 @@ func checkCerts() { func CRON() { go func() { s := gocron.NewScheduler() + s.Every(2).Hours().Do(func() { + go RunBackup() + }) s.Every(1).Day().At("00:00").Do(checkVersion) s.Every(1).Day().At("01:00").Do(checkCerts) s.Every(6).Hours().Do(checkUpdatesAvailable) diff --git a/src/backup.go b/src/backup.go new file mode 100644 index 00000000..844e7b4e --- /dev/null +++ b/src/backup.go @@ -0,0 +1,92 @@ +package main + +import ( + "archive/zip" + "io" + "os" + "path/filepath" + "github.com/azukaar/cosmos-server/src/utils" +) + +func RunBackup() { + utils.Log("Backup: Running backup") + + inputDir := utils.CONFIGFOLDER + outputDir := utils.CONFIGFOLDER + + if utils.GetMainConfig().BackupOutputDir != "" { + outputDir = utils.GetMainConfig().BackupOutputDir + if os.Getenv("HOSTNAME") != "" { + outputDir = "/mnt/host/" + os.Getenv("HOSTNAME") + } + } + + // Recursive file listing helper + var fileList []string + err := filepath.Walk(inputDir, func(path string, f os.FileInfo, err error) error { + if !f.IsDir() && filepath.Ext(f.Name()) != ".zip" { + relPath, err := filepath.Rel(inputDir, path) + if err != nil { + return err + } + fileList = append(fileList, relPath) + } + return nil + }) + if err != nil { + utils.MajorError("Backup: Error reading directory", err) + return + } + + // create a new zip file + zipfile, err := os.Create(outputDir + "/cosmos-backup" + ".temp.zip") + if err != nil { + utils.MajorError("Backup: Error creating zip file", err) + return + } + defer zipfile.Close() + + // create a new zip archive + zipw := zip.NewWriter(zipfile) + defer zipw.Close() + + // loop through the files + for _, file := range fileList { + f, err := os.Open(filepath.Join(inputDir, file)) + if err != nil { + utils.MajorError("Backup: Error opening file", err) + return + } + defer f.Close() + + // add file to zip + zipf, err := zipw.Create(file) + if err != nil { + utils.MajorError("Backup: Error adding file to zip", err) + return + } + + _, err = io.Copy(zipf, f) + if err != nil { + utils.MajorError("Backup: Error copying file", err) + return + } + } + + // rename the file + err = os.Rename(outputDir+"/cosmos-backup.temp.zip", outputDir+"/cosmos-backup.zip") + if err != nil { + utils.MajorError("Backup: Error renaming file", err) + return + } + + utils.Log("Backup: Backup complete") + + utils.TriggerEvent( + "cosmos.backup", + "Cosmos Backup Succesful", + "success", + "", + map[string]interface{}{}, + ) +} diff --git a/src/configapi/get.go b/src/configapi/get.go index 4cec5849..d8a62cf0 100644 --- a/src/configapi/get.go +++ b/src/configapi/get.go @@ -27,6 +27,8 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) { config.EmailConfig.Password = "***" config.EmailConfig.Username = "***" config.EmailConfig.Host = "***" + config.Database.Password = "***" + config.Database.Username = "***" // filter admin only routes filteredRoutes := make([]utils.ProxyRouteConfig, 0) diff --git a/src/httpServer.go b/src/httpServer.go index 5b3c372b..5497a057 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -41,7 +41,7 @@ func startHTTPServer(router *mux.Router) error { DisableGeneralOptionsHandler: true, } - if os.Getenv("HOSTNAME") != "" { + if os.Getenv("HOSTNAME") != "" && !utils.IsHostNetwork { docker.CheckPorts() } else { proxy.InitInternalTCPProxy() @@ -123,7 +123,7 @@ func startHTTPSServer(router *mux.Router) error { } // Redirect ports - if os.Getenv("HOSTNAME") != "" { + if os.Getenv("HOSTNAME") != "" && !utils.IsHostNetwork { docker.CheckPorts() } else { proxy.InitInternalTCPProxy() @@ -355,81 +355,89 @@ func InitServer() *mux.Router { srapi.HandleFunc("/api/login", user.UserLogin) srapi.HandleFunc("/api/password-reset", user.ResetPassword) srapi.HandleFunc("/api/mfa", user.API2FA) - - srapi.HandleFunc("/api/dns", GetDNSRoute) - srapi.HandleFunc("/api/dns-check", CheckDNSRoute) - srapi.Use(utils.SetSecurityHeaders) - srapi.HandleFunc("/api/status", StatusRoute) srapi.HandleFunc("/api/can-send-email", CanSendEmail) - srapi.HandleFunc("/api/favicon", GetFavicon) - srapi.HandleFunc("/api/ping", PingURL) srapi.HandleFunc("/api/newInstall", NewInstallRoute) srapi.HandleFunc("/api/logout", user.UserLogout) srapi.HandleFunc("/api/register", user.UserRegister) - srapi.HandleFunc("/api/invite", user.UserResendInviteLink) + srapi.HandleFunc("/api/dns", GetDNSRoute) + srapi.HandleFunc("/api/dns-check", CheckDNSRoute) + srapi.HandleFunc("/api/favicon", GetFavicon) + srapi.HandleFunc("/api/ping", PingURL) srapi.HandleFunc("/api/me", user.Me) - srapi.HandleFunc("/api/config", configapi.ConfigRoute) - srapi.HandleFunc("/api/restart", configapi.ConfigApiRestart) + + srapiAdmin := router.PathPrefix("/cosmos").Subrouter() - srapi.HandleFunc("/api/users/{nickname}", user.UsersIdRoute) - srapi.HandleFunc("/api/users", user.UsersRoute) + srapiAdmin.HandleFunc("/api/config", configapi.ConfigRoute) + srapiAdmin.HandleFunc("/api/restart", configapi.ConfigApiRestart) + + srapiAdmin.HandleFunc("/api/invite", user.UserResendInviteLink) + srapiAdmin.HandleFunc("/api/users/{nickname}", user.UsersIdRoute) + srapiAdmin.HandleFunc("/api/users", user.UsersRoute) - srapi.HandleFunc("/api/images/pull-if-missing", docker.PullImageIfMissing) - srapi.HandleFunc("/api/images/pull", docker.PullImage) - srapi.HandleFunc("/api/images", docker.InspectImageRoute) + srapiAdmin.HandleFunc("/api/images/pull-if-missing", docker.PullImageIfMissing) + srapiAdmin.HandleFunc("/api/images/pull", docker.PullImage) + srapiAdmin.HandleFunc("/api/images", docker.InspectImageRoute) - srapi.HandleFunc("/api/volume/{volumeName}", docker.DeleteVolumeRoute) - srapi.HandleFunc("/api/volumes", docker.VolumesRoute) + srapiAdmin.HandleFunc("/api/volume/{volumeName}", docker.DeleteVolumeRoute) + srapiAdmin.HandleFunc("/api/volumes", docker.VolumesRoute) - srapi.HandleFunc("/api/network/{networkID}", docker.DeleteNetworkRoute) - srapi.HandleFunc("/api/networks", docker.NetworkRoutes) + srapiAdmin.HandleFunc("/api/network/{networkID}", docker.DeleteNetworkRoute) + srapiAdmin.HandleFunc("/api/networks", docker.NetworkRoutes) - srapi.HandleFunc("/api/servapps/{containerId}/manage/{action}", docker.ManageContainerRoute) - srapi.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute) - srapi.HandleFunc("/api/servapps/{containerId}/auto-update/{status}", docker.AutoUpdateContainerRoute) - srapi.HandleFunc("/api/servapps/{containerId}/logs", docker.GetContainerLogsRoute) - srapi.HandleFunc("/api/servapps/{containerId}/terminal/{action}", docker.TerminalRoute) - srapi.HandleFunc("/api/servapps/{containerId}/update", docker.UpdateContainerRoute) - srapi.HandleFunc("/api/servapps/{containerId}/export", docker.ExportContainerRoute) - srapi.HandleFunc("/api/servapps/{containerId}/", docker.GetContainerRoute) - srapi.HandleFunc("/api/servapps/{containerId}/network/{networkId}", docker.NetworkContainerRoutes) - srapi.HandleFunc("/api/servapps/{containerId}/networks", docker.NetworkContainerRoutes) - srapi.HandleFunc("/api/servapps/{containerId}/check-update", docker.CanUpdateImageRoute) - srapi.HandleFunc("/api/servapps", docker.ContainersRoute) - srapi.HandleFunc("/api/docker-service", docker.CreateServiceRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/manage/{action}", docker.ManageContainerRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/auto-update/{status}", docker.AutoUpdateContainerRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/logs", docker.GetContainerLogsRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/terminal/{action}", docker.TerminalRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/update", docker.UpdateContainerRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/export", docker.ExportContainerRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/", docker.GetContainerRoute) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/network/{networkId}", docker.NetworkContainerRoutes) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/networks", docker.NetworkContainerRoutes) + srapiAdmin.HandleFunc("/api/servapps/{containerId}/check-update", docker.CanUpdateImageRoute) + srapiAdmin.HandleFunc("/api/servapps", docker.ContainersRoute) + srapiAdmin.HandleFunc("/api/docker-service", docker.CreateServiceRoute) - srapi.HandleFunc("/api/markets", market.MarketGet) + srapiAdmin.HandleFunc("/api/markets", market.MarketGet) + + srapiAdmin.HandleFunc("/api/upload/{name}", UploadImage) + srapiAdmin.HandleFunc("/api/image/{name}", GetImage) - srapi.HandleFunc("/api/upload/{name}", UploadImage) - srapi.HandleFunc("/api/image/{name}", GetImage) + srapiAdmin.HandleFunc("/api/get-backup", configapi.BackupFileApiGet) - srapi.HandleFunc("/api/get-backup", configapi.BackupFileApiGet) + srapiAdmin.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices) + srapiAdmin.HandleFunc("/api/constellation/restart", constellation.API_Restart) + srapiAdmin.HandleFunc("/api/constellation/reset", constellation.API_Reset) + srapiAdmin.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting) + srapiAdmin.HandleFunc("/api/constellation/config", constellation.API_GetConfig) + srapiAdmin.HandleFunc("/api/constellation/logs", constellation.API_GetLogs) + srapiAdmin.HandleFunc("/api/constellation/block", constellation.DeviceBlock) - srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices) - srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart) - srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset) - srapi.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting) - srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig) - srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs) - srapi.HandleFunc("/api/constellation/block", constellation.DeviceBlock) + srapiAdmin.HandleFunc("/api/events", metrics.API_ListEvents) - srapi.HandleFunc("/api/events", metrics.API_ListEvents) + srapiAdmin.HandleFunc("/api/metrics", metrics.API_GetMetrics) + srapiAdmin.HandleFunc("/api/reset-metrics", metrics.API_ResetMetrics) + srapiAdmin.HandleFunc("/api/list-metrics", metrics.ListMetrics) - srapi.HandleFunc("/api/metrics", metrics.API_GetMetrics) - srapi.HandleFunc("/api/reset-metrics", metrics.API_ResetMetrics) - srapi.HandleFunc("/api/list-metrics", metrics.ListMetrics) + srapiAdmin.HandleFunc("/api/notifications/read", utils.MarkAsRead) + srapiAdmin.HandleFunc("/api/notifications", utils.NotifGet) - srapi.HandleFunc("/api/notifications/read", utils.MarkAsRead) - srapi.HandleFunc("/api/notifications", utils.NotifGet) + srapiAdmin.Use(utils.Restrictions(config.AdminConstellationOnly, config.AdminWhitelistIPs)) + + srapi.Use(utils.SetSecurityHeaders) + srapiAdmin.Use(utils.SetSecurityHeaders) if(!config.HTTPConfig.AcceptAllInsecureHostname) { srapi.Use(utils.EnsureHostname) + srapiAdmin.Use(utils.EnsureHostname) } srapi.Use(utils.EnsureHostnameCosmosAPI) + srapiAdmin.Use(utils.EnsureHostnameCosmosAPI) SecureAPI(srapi, false, false) + SecureAPI(srapiAdmin, false, false) pwd,_ := os.Getwd() utils.Log("Starting in " + pwd) diff --git a/src/image.go b/src/image.go index 4ce80f02..ff575192 100644 --- a/src/image.go +++ b/src/image.go @@ -232,117 +232,3 @@ func MigratePre013() { } } } - -func CommitMigrationPre014() { - config := utils.ReadConfigFromFile() - config.LastMigration = "0.14.0" - utils.SetBaseMainConfig(config) -} - -// func MigratePre014() { -// config := utils.ReadConfigFromFile() -// if config.LastMigration != "" { -// c, err := utils.CompareSemver(config.LastMigration, "0.14.0") -// if err != nil { -// utils.Fatal("Can't read field 'lastMigration' from config", err) -// } -// if(c > 0) { -// return -// } -// } - -// if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil { -// utils.Log("MigratePre014: No database found, trying to migrate pre-0.14 data") - -// cu2, errCo := utils.GetCollection(utils.GetRootAppId(), "users") -// if errCo != nil { -// // Assuming we dont need to migrate -// utils.Log("MigratePre014: No database, assuming we dont need to migrate") -// utils.Debug(errCo.Error()) -// CommitMigrationPre014() -// return -// } - -// cd2, errCo := utils.GetCollection(utils.GetRootAppId(), "devices") -// if errCo != nil { -// // Assuming we dont need to migrate -// utils.Log("MigratePre014: No database, assuming we dont need to migrate") -// utils.Debug(errCo.Error()) -// CommitMigrationPre014() -// return -// } - -// utils.Log("MigratePre014: Migrating pre-0.14 data") - -// cu, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") - -// if errCo != nil { -// utils.Fatal("Error trying to migrate pre-0.14 data [1]", errCo) -// return -// } - -// // copy users from cu2 to cu -// cursor, err := cu2.Find(nil, map[string]interface{}{}) -// if err != nil && err != mongo.ErrNoDocuments { -// utils.Fatal("Error trying to migrate pre-0.14 data [3]", err) -// return -// } -// defer cursor.Close(nil) - -// users := []utils.User{} - -// if err = cursor.All(nil, &users); err != nil { -// utils.Fatal("Error trying to migrate pre-0.14 data [4]", err) -// return -// } - -// for _, user := range users { -// _, err := cu.InsertOne(nil, user) -// if err != nil { -// utils.Fatal("Error trying to migrate pre-0.14 data [5]", err) -// return -// } -// } - -// closeDb() - -// utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(users)) + " users") - - -// cd, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") -// defer closeDb() - -// if errCo != nil { -// utils.Fatal("Error trying to migrate pre-0.14 data [2]", errCo) -// return -// } - -// // copy devices from cd2 to cd -// cursor, err = cd2.Find(nil, map[string]interface{}{}) -// if err != nil && err != mongo.ErrNoDocuments { -// utils.Fatal("Error trying to migrate pre-0.14 data [6]", err) -// return -// } - -// defer cursor.Close(nil) - -// devices := []utils.ConstellationDevice{} - -// if err = cursor.All(context.Background(), &devices); err != nil { -// utils.Fatal("Error trying to migrate pre-0.14 data [7]", err) -// return -// } - -// for _, device := range devices { -// _, err := cd.InsertOne(context.Background(), device) -// if err != nil { -// utils.Fatal("Error trying to migrate pre-0.14 data [8]", err) -// return -// } -// } - -// utils.Log("MigratePre014: Migrated " + fmt.Sprint(len(devices)) + " devices") - -// CommitMigrationPre014() -// } -// } \ No newline at end of file diff --git a/src/index.go b/src/index.go index 09845f32..d030d883 100644 --- a/src/index.go +++ b/src/index.go @@ -75,7 +75,6 @@ func main() { constellation.InitDNS() utils.Log("Starting server...") - } StartServer() diff --git a/src/status.go b/src/status.go index f16c44b8..e3759ac0 100644 --- a/src/status.go +++ b/src/status.go @@ -52,7 +52,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) { // "disk": utils.GetDiskUsage(), // "network": utils.GetNetworkUsage(), }, - "hostmode": utils.IsHostNetwork || os.Getenv("HOSTNAME") == "", + "hostmode": utils.IsHostNetwork || os.Getenv("HOSTNAME") == "" || utils.GetMainConfig().DisableHostModeWarning, "database": utils.DBStatus, "docker": docker.DockerIsConnected, "backup_status": docker.ExportError, diff --git a/src/utils/types.go b/src/utils/types.go index b0887e88..665834fc 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -95,7 +95,10 @@ type Config struct { ConstellationConfig ConstellationConfig MonitoringDisabled bool MonitoringAlerts map[string]Alert - LastMigration string + BackupOutputDir string + DisableHostModeWarning bool + AdminWhitelistIPs []string + AdminConstellationOnly bool } type DatabaseConfig struct { From 87c1636ff7e98e83c36cd27be5a2d56fb5d65bd6 Mon Sep 17 00:00:00 2001 From: Kawanaao <149584315+Kawanaao@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:37:58 +0200 Subject: [PATCH 20/31] [skip ci] Optimizations/react (#186) * Replaced the lodash module with submodules * Using lazy imports of heavy modules * Replaced the old authorization verification system with PrivateRoute * Dynamic import of all paths * Minifying the syntax highlighting library * Added missing imports * Added expires to head meta * Don't force the code to follow unnecessary redirects * Kawanaao accept CLA --------- Co-authored-by: Yann S <7872597+azukaar@users.noreply.github.com> --- .clabot | 2 +- client/index.html | 1 + client/src/PrivateRoute.jsx | 18 +++++++ client/src/api/authentication.jsx | 4 +- .../components/third-party/Highlighter.jsx | 4 +- client/src/isLoggedIn.jsx | 36 +++++++------- client/src/pages/authentication/openid.jsx | 2 - client/src/pages/config/routeConfigPage.jsx | 2 - client/src/pages/config/routes/newRoute.jsx | 1 - .../src/pages/config/routes/routeoverview.jsx | 1 - client/src/pages/config/users/configman.jsx | 3 -- client/src/pages/config/users/proxyman.jsx | 3 -- client/src/pages/config/users/restart.jsx | 1 - .../src/pages/config/users/usermanagement.jsx | 2 - client/src/pages/constellation/dns.jsx | 1 - client/src/pages/constellation/index.jsx | 3 -- client/src/pages/constellation/vpn.jsx | 1 - client/src/pages/dashboard/AlertPage.jsx | 3 -- .../pages/dashboard/components/mini-plot.jsx | 4 +- .../src/pages/dashboard/components/plot.jsx | 4 +- .../src/pages/dashboard/components/table.jsx | 2 - .../src/pages/dashboard/containerMetrics.jsx | 3 -- client/src/pages/dashboard/index.jsx | 3 -- .../src/pages/dashboard/routeMonitoring.jsx | 3 -- client/src/pages/home/index.jsx | 10 ++-- client/src/pages/market/sources.jsx | 1 - client/src/pages/newInstall/newInstall.jsx | 1 - client/src/pages/openid/openid-edit.jsx | 1 - client/src/pages/openid/openid-list.jsx | 3 -- .../servapps/containers/docker-compose.jsx | 2 - .../src/pages/servapps/containers/index.jsx | 2 - .../pages/servapps/containers/newService.jsx | 1 - .../servapps/containers/newServiceForm.jsx | 2 - client/src/pages/servapps/index.jsx | 3 -- client/src/pages/servapps/servapps.jsx | 1 - client/src/pages/servapps/volumes.jsx | 1 - client/src/routes/LoginRoutes.jsx | 14 +++--- client/src/routes/MainRoutes.jsx | 30 +++++++----- client/src/themes/overrides/index.jsx | 2 +- client/src/utils/SyntaxHighlight.jsx | 7 ++- package-lock.json | 47 +++++++++++++++++-- package.json | 7 ++- 42 files changed, 130 insertions(+), 112 deletions(-) create mode 100644 client/src/PrivateRoute.jsx diff --git a/.clabot b/.clabot index 67ec21ff..dac82718 100644 --- a/.clabot +++ b/.clabot @@ -1,4 +1,4 @@ { - "contributors": ["azukaar", "jwr1", "Jogai", "InterN0te", "catmandx", "revam"], + "contributors": ["azukaar", "jwr1", "Jogai", "InterN0te", "catmandx", "revam", "Kawanaao"], "message": "We require contributors to sign our [Contributor License Agreement](https://github.com/azukaar/Cosmos-Server/blob/master/cla.md). In order for us to review and merge your code, add yourself to the .clabot file as contributor, as a way of signing the CLA." } diff --git a/client/index.html b/client/index.html index 354bddfc..8726b1e4 100644 --- a/client/index.html +++ b/client/index.html @@ -1,6 +1,7 @@ + Cosmos diff --git a/client/src/PrivateRoute.jsx b/client/src/PrivateRoute.jsx new file mode 100644 index 00000000..b087d113 --- /dev/null +++ b/client/src/PrivateRoute.jsx @@ -0,0 +1,18 @@ +import { Suspense } from "react" +import { Await, Navigate } from "react-router" +import isLoggedIn from "./isLoggedIn" + +function PrivateRoute({ children }) { + return + + {authStatus => { + switch (authStatus) { + case "OK": return children + default: return + } + }} + + +} + +export default PrivateRoute diff --git a/client/src/api/authentication.jsx b/client/src/api/authentication.jsx index 36a14948..07bfb1dd 100644 --- a/client/src/api/authentication.jsx +++ b/client/src/api/authentication.jsx @@ -11,7 +11,7 @@ function login(values) { } function me() { - return fetch('/cosmos/api/me/', { + return fetch('/cosmos/api/me', { method: 'GET', headers: { 'Content-Type': 'application/json' @@ -21,7 +21,7 @@ function me() { } function logout() { - return wrap(fetch('/cosmos/api/logout/', { + return wrap(fetch('/cosmos/api/logout', { method: 'GET', headers: { 'Content-Type': 'application/json' diff --git a/client/src/components/third-party/Highlighter.jsx b/client/src/components/third-party/Highlighter.jsx index 410eddc2..3d9679a7 100644 --- a/client/src/components/third-party/Highlighter.jsx +++ b/client/src/components/third-party/Highlighter.jsx @@ -1,12 +1,12 @@ import PropTypes from 'prop-types'; -import { useState } from 'react'; +import { lazy, useState } from 'react'; // material-ui import { Box, CardActions, Collapse, Divider, IconButton, Tooltip } from '@mui/material'; // third-party import { CopyToClipboard } from 'react-copy-to-clipboard'; -import reactElementToJSXString from 'react-element-to-jsx-string'; +const reactElementToJSXString = lazy(() => import('react-element-to-jsx-string')); // project import import SyntaxHighlight from '../../utils/SyntaxHighlight'; diff --git a/client/src/isLoggedIn.jsx b/client/src/isLoggedIn.jsx index 29744c74..fb9f3d38 100644 --- a/client/src/isLoggedIn.jsx +++ b/client/src/isLoggedIn.jsx @@ -1,25 +1,25 @@ import * as API from './api'; -import { useEffect } from 'react'; -import { redirectToLocal } from './utils/indexs'; -const IsLoggedIn = () => useEffect(() => { +async function isLoggedIn() { const urlSearch = encodeURIComponent(window.location.search); const redirectToURL = (window.location.pathname + urlSearch); + const data = await API.auth.me(); - API.auth.me().then((data) => { - if(data.status != 'OK') { - if(data.status == 'NEW_INSTALL') { - redirectToLocal('/cosmos-ui/newInstall'); - } else if (data.status == 'error' && data.code == "HTTP004") { - redirectToLocal('/cosmos-ui/login?redirect=' + redirectToURL); - } else if (data.status == 'error' && data.code == "HTTP006") { - redirectToLocal('/cosmos-ui/loginmfa?redirect=' + redirectToURL); - } else if (data.status == 'error' && data.code == "HTTP007") { - redirectToLocal('/cosmos-ui/newmfa?redirect=' + redirectToURL); - } - } - }) -}, []); + if (data.status == 'NEW_INSTALL') { + return '/cosmos-ui/newInstall'; + } else if (data.status == 'error' && data.code == "HTTP004") { + return '/cosmos-ui/login?redirect=' + redirectToURL; + } else if (data.status == 'error' && data.code == "HTTP006") { + return '/cosmos-ui/loginmfa?redirect=' + redirectToURL; + } else if (data.status == 'error' && data.code == "HTTP007") { + return '/cosmos-ui/newmfa?redirect=' + redirectToURL; + } else if (data.status == 'OK') { + return data.status + } else { + console.warn(`Status "${data.status}" does not have a navigation handler, will be interpreted as OK!`) + return 'OK' + } +}; -export default IsLoggedIn; \ No newline at end of file +export default isLoggedIn; diff --git a/client/src/pages/authentication/openid.jsx b/client/src/pages/authentication/openid.jsx index 1756d34e..898b4f16 100644 --- a/client/src/pages/authentication/openid.jsx +++ b/client/src/pages/authentication/openid.jsx @@ -7,7 +7,6 @@ import { Checkbox, Grid, Stack, Typography } from '@mui/material'; import AuthLogin from './auth-forms/AuthLogin'; import AuthWrapper from './AuthWrapper'; import { getFaviconURL } from '../../utils/routes'; -import IsLoggedIn from '../../isLoggedIn'; import { LoadingButton } from '@mui/lab'; import { Field, useFormik } from 'formik'; import { useState } from 'react'; @@ -52,7 +51,6 @@ const OpenID = () => { } return ( - diff --git a/client/src/pages/config/routeConfigPage.jsx b/client/src/pages/config/routeConfigPage.jsx index 8eb21cb2..0118ccaf 100644 --- a/client/src/pages/config/routeConfigPage.jsx +++ b/client/src/pages/config/routeConfigPage.jsx @@ -7,7 +7,6 @@ import { useEffect, useState } from "react"; import * as API from "../../api"; import RouteSecurity from "./routes/routeSecurity"; import RouteOverview from "./routes/routeoverview"; -import IsLoggedIn from "../../isLoggedIn"; import RouteMetrics from "../dashboard/routeMonitoring"; import EventExplorerStandalone from "../dashboard/eventsExplorerStandalone"; @@ -31,7 +30,6 @@ const RouteConfigPage = () => { }, []); return
-

diff --git a/client/src/pages/config/routes/newRoute.jsx b/client/src/pages/config/routes/newRoute.jsx index 9fda288c..8e62b3ab 100644 --- a/client/src/pages/config/routes/newRoute.jsx +++ b/client/src/pages/config/routes/newRoute.jsx @@ -8,7 +8,6 @@ import Paper from '@mui/material/Paper'; import { styled } from '@mui/material/styles'; import * as API from '../../../api'; -import IsLoggedIn from '../../../isLoggedIn'; import RestartModal from '../../config/users/restart'; import RouteManagement from '../../config/routes/routeman'; import { ValidateRoute, getFaviconURL, sanitizeRoute } from '../../../utils/routes'; diff --git a/client/src/pages/config/routes/routeoverview.jsx b/client/src/pages/config/routes/routeoverview.jsx index 4d04d909..f8fc312e 100644 --- a/client/src/pages/config/routes/routeoverview.jsx +++ b/client/src/pages/config/routes/routeoverview.jsx @@ -7,7 +7,6 @@ import { RouteMode, RouteSecurity } from '../../../components/routeComponents'; import { getFaviconURL } from '../../../utils/routes'; import * as API from '../../../api'; import { CheckOutlined, ClockCircleOutlined, ContainerOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, InfoCircleFilled, InfoCircleOutlined, LockOutlined, NodeExpandOutlined, SafetyCertificateOutlined, UpOutlined } from "@ant-design/icons"; -import IsLoggedIn from '../../../isLoggedIn'; import { redirectToLocal } from '../../../utils/indexs'; import { CosmosCheckbox } from '../users/formShortcuts'; import { Field } from 'formik'; diff --git a/client/src/pages/config/users/configman.jsx b/client/src/pages/config/users/configman.jsx index a5b93720..9392901c 100644 --- a/client/src/pages/config/users/configman.jsx +++ b/client/src/pages/config/users/configman.jsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import IsLoggedIn from '../../../isLoggedIn'; import * as API from '../../../api'; import MainCard from '../../../components/MainCard'; import { Formik, Field } from 'formik'; @@ -60,8 +59,6 @@ const ConfigManagement = () => { }, []); return
- - + + Migrate + + + + +
+ +
+ + ); +}; + +export default Migrate014; \ No newline at end of file diff --git a/client/src/pages/servapps/createNetwork.jsx b/client/src/pages/servapps/createNetwork.jsx index 1aa9ae62..3a79eb9b 100644 --- a/client/src/pages/servapps/createNetwork.jsx +++ b/client/src/pages/servapps/createNetwork.jsx @@ -1,16 +1,14 @@ // material-ui import * as React from 'react'; import { Alert, Button, Checkbox, FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material'; -import { PlusCircleFilled, PlusCircleOutlined, WarningOutlined } from '@ant-design/icons'; +import { PlusCircleOutlined } from '@ant-design/icons'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; -import CircularProgress from '@mui/material/CircularProgress'; -import { useEffect, useState } from 'react'; import { LoadingButton } from '@mui/lab'; -import { FormikProvider, useFormik } from 'formik'; +import { useFormik, FormikProvider } from 'formik'; import * as Yup from 'yup'; import * as API from '../../api'; @@ -18,16 +16,22 @@ import { CosmosCheckbox } from '../config/users/formShortcuts'; import ResponsiveButton from '../../components/responseiveButton'; const NewNetworkButton = ({ fullWidth, refresh }) => { - const [isOpened, setIsOpened] = useState(false); + const [isOpened, setIsOpened] = React.useState(false); + const formik = useFormik({ initialValues: { name: '', driver: 'bridge', attachCosmos: false, + parentInterface: '', }, validationSchema: Yup.object({ name: Yup.string().required('Required'), driver: Yup.string().required('Required'), + parentInterface: Yup.string().when('driver', { + is: 'mcvlan', + then: Yup.string().required('Parent interface is required for MCVLAN') + }), }), onSubmit: (values, { setErrors, setStatus, setSubmitting }) => { setSubmitting(true); @@ -45,82 +49,100 @@ const NewNetworkButton = ({ fullWidth, refresh }) => { }, }); - return <> - setIsOpened(false)}> - - New Network - - -
- - - - - Driver - - - - -
- {formik.errors.submit && ( - - {formik.errors.submit} - - )} -
-
- { - - - Create - - } -
-
- setIsOpened(true)} - startIcon={} - > - New Network - - ; + Driver + + + + {formik.values.driver === 'mcvlan' && ( + + )} + + +
+ + {formik.errors.submit && ( + + {formik.errors.submit} + + )} + + + + + + Create + + + + + setIsOpened(true)} + startIcon={} + > + New Network + + + ); }; export default NewNetworkButton; diff --git a/client/src/pages/servapps/createVolumes.jsx b/client/src/pages/servapps/createVolumes.jsx index d403aa7a..18bea7bc 100644 --- a/client/src/pages/servapps/createVolumes.jsx +++ b/client/src/pages/servapps/createVolumes.jsx @@ -26,7 +26,7 @@ const NewVolumeButton = ({ fullWidth, refresh }) => { const formik = useFormik({ initialValues: { name: '', - driver: '', + driver: 'local', }, validationSchema: Yup.object({ name: Yup.string().required('Required'), diff --git a/package.json b/package.json index 9691c851..0094eca4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable12", + "version": "0.14.0-unstable13", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/api_migrate.go b/src/docker/api_migrate.go new file mode 100644 index 00000000..1a3aa1bc --- /dev/null +++ b/src/docker/api_migrate.go @@ -0,0 +1,59 @@ +package docker + +import ( + "encoding/json" + "net/http" + + "github.com/azukaar/cosmos-server/src/utils" +) + +type migrateToHostMode struct { + HTTPPort string `json:"http"` + HTTPSPort string `json:"https"` +} + +func MigrateToHostModeRoute(w http.ResponseWriter, req *http.Request) { + if utils.AdminOnly(w, req) != nil { + return + } + + if req.Method == "POST" { + errD := Connect() + if errD != nil { + utils.Error("MigrateToHostMode", errD) + utils.HTTPError(w, "Internal server error: "+errD.Error(), http.StatusInternalServerError, "CN001") + return + } + + var payload migrateToHostMode + err := json.NewDecoder(req.Body).Decode(&payload) + if err != nil { + utils.Error("MigrateToHostMode: Error reading request body", err) + utils.HTTPError(w, "Error reading request body: "+err.Error(), http.StatusBadRequest, "CN002") + return + } + + config := utils.ReadConfigFromFile() + config.HTTPConfig.HTTPPort = payload.HTTPPort + config.HTTPConfig.HTTPSPort = payload.HTTPSPort + utils.SetBaseMainConfig(config) + + utils.TriggerEvent( + "cosmos.settings", + "Settings updated", + "success", + "", + map[string]interface{}{ + }) + + SelfAction("host") + + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + }) + } else { + utils.Error("MigrateToHostMode: Method not allowed " + req.Method, nil) + utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") + return + } +} \ No newline at end of file diff --git a/src/docker/api_networks.go b/src/docker/api_networks.go index ec287d2e..6dea319d 100644 --- a/src/docker/api_networks.go +++ b/src/docker/api_networks.go @@ -251,6 +251,7 @@ type createNetworkPayload struct { Name string `json:"name"` Driver string `json:"driver"` AttachCosmos bool `json:"attachCosmos"` + parentInterface string `json:"parentInterface"` } func CreateNetworkRoute(w http.ResponseWriter, req *http.Request) { @@ -277,6 +278,9 @@ func CreateNetworkRoute(w http.ResponseWriter, req *http.Request) { networkCreate := types.NetworkCreate{ CheckDuplicate: true, Driver: payload.Driver, + Options: map[string]string{ + "parent": payload.parentInterface, + }, } resp, err := DockerClient.NetworkCreate(context.Background(), payload.Name, networkCreate) diff --git a/src/docker/docker.go b/src/docker/docker.go index f42c372d..2f109dc5 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -609,6 +609,10 @@ func RemoveSelfUpdater() error { } func SelfRecreate() error { + return SelfAction("recreate") +} + +func SelfAction(action string) error { utils.Log("SelfRecreate - Starting...") if os.Getenv("HOSTNAME") == "" { @@ -638,7 +642,7 @@ func SelfRecreate() error { "important", "", map[string]interface{}{ - "action": "recreate", + "action": action, "container": containerName, }) diff --git a/src/httpServer.go b/src/httpServer.go index 5497a057..bfe0edc3 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -384,6 +384,8 @@ func InitServer() *mux.Router { srapiAdmin.HandleFunc("/api/network/{networkID}", docker.DeleteNetworkRoute) srapiAdmin.HandleFunc("/api/networks", docker.NetworkRoutes) + + srapiAdmin.HandleFunc("/api/migrate-host", docker.MigrateToHostModeRoute) srapiAdmin.HandleFunc("/api/servapps/{containerId}/manage/{action}", docker.ManageContainerRoute) srapiAdmin.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute) From ebcd8fc0f6b9a9b19113b36b8d6cf13fe5eb3791 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 4 Feb 2024 13:39:47 +0000 Subject: [PATCH 23/31] [release] v0.14.0-unstable14 --- src/utils/middleware.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/middleware.go b/src/utils/middleware.go index a95cbff1..8dd6e5af 100644 --- a/src/utils/middleware.go +++ b/src/utils/middleware.go @@ -243,7 +243,7 @@ func BlockByCountryMiddleware(blockedCountries []string, CountryBlacklistIsWhite return } } else { - Warn("Missing geolocation information to block IPs") + Debug("Missing geolocation information to block IPs") } } else { for _, blockedCountry := range blockedCountries { @@ -254,7 +254,7 @@ func BlockByCountryMiddleware(blockedCountries []string, CountryBlacklistIsWhite } } } else { - Warn("Missing geolocation information to block IPs") + Debug("Missing geolocation information to block IPs") } next.ServeHTTP(w, r) From e1c56e64a09ca49b3ebc90879f906fc28d10cfb2 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 4 Feb 2024 13:52:40 +0000 Subject: [PATCH 24/31] [release] v0.14.0-unstable15 --- package.json | 2 +- src/docker/docker.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0094eca4..e70e319c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable13", + "version": "0.14.0-unstable15", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/docker.go b/src/docker/docker.go index 2f109dc5..5f4afe27 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -655,7 +655,7 @@ func SelfAction(action string) error { }, Environment: []string{ "CONTAINER_NAME=" + containerName, - "ACTION=recreate", + "ACTION=" + action, "DOCKER_HOST=" + os.Getenv("DOCKER_HOST"), }, Volumes: []mountType.Mount{ From b8ec9d679b39adce879ae5e2ce03a7b4b5b2cf65 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sun, 4 Feb 2024 14:54:08 +0000 Subject: [PATCH 25/31] [release] v0.14.0-unstable16 --- client/src/pages/home/migrate014.jsx | 18 ++++++++++++++---- package.json | 2 +- src/docker/api_migrate.go | 9 ++++++++- src/docker/run.go | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/client/src/pages/home/migrate014.jsx b/client/src/pages/home/migrate014.jsx index 45df2b53..f6ec4209 100644 --- a/client/src/pages/home/migrate014.jsx +++ b/client/src/pages/home/migrate014.jsx @@ -22,6 +22,7 @@ import * as API from '../../api'; const Migrate014 = ({ config }) => { const [isOpened, setIsOpened] = useState(false); + const [isSubmitted, setIsSubmitted] = useState(false); const formik = useFormik({ initialValues: { @@ -38,9 +39,9 @@ const Migrate014 = ({ config }) => { setSubmitting(true); return API.docker.migrateHost(values) .then((res) => { + isSubmitted(true); setStatus({ success: true }); setSubmitting(false); - setIsOpened(false); }).catch((err) => { setStatus({ success: false }); setErrors({ submit: err.message }); @@ -54,6 +55,15 @@ const Migrate014 = ({ config }) => { setIsOpened(false)}> Migrate to Host Mode + {isSubmitted ? + + + Migration successful! Your Cosmos instance will now restart in Host Mode. + Refresh the page to see the changes (it might take a minute!). + if you are experiencing issues, please check the logs for more information. + + + : In order to continue to improve your experience, Cosmos now supports the Host Mode of networking. @@ -99,8 +109,8 @@ const Migrate014 = ({ config }) => { {formik.errors.submit} )} - - + } + {!isSubmitted && { > Migrate - + }
diff --git a/package.json b/package.json index e70e319c..6e123d2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable15", + "version": "0.14.0-unstable16", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/api_migrate.go b/src/docker/api_migrate.go index 1a3aa1bc..6ed42909 100644 --- a/src/docker/api_migrate.go +++ b/src/docker/api_migrate.go @@ -46,7 +46,14 @@ func MigrateToHostModeRoute(w http.ResponseWriter, req *http.Request) { map[string]interface{}{ }) - SelfAction("host") + err = SelfAction("host") + if err != nil { + utils.Error("MigrateToHostMode: Error migrating to host mode", err) + utils.HTTPError(w, "Error migrating to host mode: "+err.Error(), http.StatusInternalServerError, "CN003") + return + } + + utils.SetBaseMainConfig(config) json.NewEncoder(w).Encode(map[string]interface{}{ "status": "OK", diff --git a/src/docker/run.go b/src/docker/run.go index 99c93c03..4c618545 100644 --- a/src/docker/run.go +++ b/src/docker/run.go @@ -63,7 +63,7 @@ func RunDB(db utils.DatabaseConfig) (DockerServiceCreateRequest, error) { Name: db.Hostname, Image: imageName, RestartPolicy: "always", - // Command: "--wiredTigerCacheSizeGB 0.25", + Command: "--wiredTigerCacheSizeGB 0.25", Environment: []string{ "MONGO_INITDB_ROOT_USERNAME=" + db.Username, "MONGO_INITDB_ROOT_PASSWORD=" + db.Password, From dc95a6c69897b966bb8aeb110f19b31305456276 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Mon, 5 Feb 2024 21:16:11 +0000 Subject: [PATCH 26/31] [release] v0.14.0-unstable17 --- changelog.md | 2 ++ client/src/components/persistentInput.jsx | 27 +++++++++++++++++++ client/src/pages/market/listing.jsx | 22 +++++++++++++++ client/src/pages/market/sources.jsx | 5 ++++ .../servapps/containers/docker-compose.jsx | 11 ++++++++ package.json | 2 +- src/constellation/index.go | 2 +- 7 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 client/src/components/persistentInput.jsx diff --git a/changelog.md b/changelog.md index 017340e3..69ebe579 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,8 @@ - Fix issue with removing IP whitelist - Add UI to create MCVlan networks - Add a migration script to host mode + - UI optimizations (thanks @Kawanaao) + - Add duplicate filter on store listing ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/client/src/components/persistentInput.jsx b/client/src/components/persistentInput.jsx new file mode 100644 index 00000000..34cfbbd5 --- /dev/null +++ b/client/src/components/persistentInput.jsx @@ -0,0 +1,27 @@ +import { Checkbox } from "@mui/material"; +import { useEffect } from "react"; + +export const PersistentCheckbox = ({ name, label, value, onChange }) => { + let eKey = "PersistentCheckbox-" + name; + + useEffect(() => { + if (localStorage.getItem(eKey) === null) { + localStorage.setItem(eKey, value); + } else { + onChange(localStorage.getItem(eKey) === "true"); + } + }, [name, value]); + + const handleChange = (e) => { + onChange(e.target.checked); + localStorage.setItem(eKey, e.target.checked); + } + + return ( +
+ + + {label} +
+ ); +} \ No newline at end of file diff --git a/client/src/pages/market/listing.jsx b/client/src/pages/market/listing.jsx index 5986be79..0fa8b842 100644 --- a/client/src/pages/market/listing.jsx +++ b/client/src/pages/market/listing.jsx @@ -14,6 +14,7 @@ import { AppstoreAddOutlined, SearchOutlined, WarningOutlined } from "@ant-desig import ResponsiveButton from "../../components/responseiveButton"; import { useClientInfos } from "../../utils/hooks"; import EditSourcesModal from "./sources"; +import { PersistentCheckbox } from "../../components/persistentInput"; function Screenshots({ screenshots }) { const aspectRatioContainerStyle = { @@ -135,6 +136,7 @@ const MarketPage = () => { const isDark = theme.palette.mode === 'dark'; const { appName, appStore } = useParams(); const [search, setSearch] = useState(""); + const [filterDups, setFilterDups] = useState(false); const {role} = useClientInfos(); const isAdmin = role === "2"; @@ -181,6 +183,14 @@ const MarketPage = () => { return -1; } + if (a.appstore > b.appstore || b.appstore === 'cosmos-cloud') { + return 1; + } + + if (a.appstore < b.appstore || a.appstore === 'cosmos-cloud') { + return -1; + } + return 0; }); @@ -318,6 +328,7 @@ const MarketPage = () => { { }} /> + {(!apps || !Object.keys(apps).length) && { return app.name.toLowerCase().includes(search.toLowerCase()) || app.tags.join(' ').toLowerCase().includes(search.toLowerCase()); }) + .filter((app) => { + if (!filterDups) { + return true; + } else if(app.appstore === 'cosmos-cloud') { + return true; + } else if(appList.filter((a) => a.name === app.name && a.appstore === 'cosmos-cloud').length > 0) { + return false; + } + + return true; + }) .map((app) => { return { return (<> setOpen(false)} maxWidth="sm" fullWidth> Edit Sources + {config &&
+ + This allows you to add additional 3rd party Cosmos app-markets to the market.
+ To find new sources, start here +
{formik.values.sources && formik.values.sources .map((action, index) => { return !action.removed && <> diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index d5d8767c..523ba967 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -127,6 +127,17 @@ const convertDockerCompose = (config, serviceName, dockerCompose, setYmlError) = }) } + // convert ports + if (doc.services[key].ports && Array.isArray(doc.services[key].ports)) { + let ports = []; + doc.services[key].ports.forEach((port) => { + port.target = '' + port.target; + port.published = '' + port.published; + ports.push(port); + }); + doc.services[key].ports = ports; + } + //convert user if (doc.services[key].user) { doc.services[key].user = '' + doc.services[key].user; diff --git a/package.json b/package.json index 6e123d2d..fda60cbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable16", + "version": "0.14.0-unstable17", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/constellation/index.go b/src/constellation/index.go index d1563e7c..8a0f9e23 100644 --- a/src/constellation/index.go +++ b/src/constellation/index.go @@ -11,7 +11,7 @@ func Init() { // if date is > 1st of January 2024 timeNow := time.Now() - if timeNow.Year() > 2024 || (timeNow.Year() == 2024 && timeNow.Month() > 3) { + if timeNow.Year() > 2024 || (timeNow.Year() == 2024 && timeNow.Month() > 6) { utils.Error("Constellation: this preview version has expired, please update to use the lastest version of Constellation.", nil) // disable constellation configFile := utils.ReadConfigFromFile() From 3db8f182cfa68b1ece814f52864fd2e7069fff91 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Mon, 5 Feb 2024 22:03:46 +0000 Subject: [PATCH 27/31] [release] v0.14.0-unstable18 --- client/src/pages/servapps/containers/docker-compose.jsx | 8 +++++--- package.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index 523ba967..d992f908 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -131,9 +131,11 @@ const convertDockerCompose = (config, serviceName, dockerCompose, setYmlError) = if (doc.services[key].ports && Array.isArray(doc.services[key].ports)) { let ports = []; doc.services[key].ports.forEach((port) => { - port.target = '' + port.target; - port.published = '' + port.published; - ports.push(port); + if (typeof port === 'string') { + ports.push(port); + return; + } + ports.push(`${port.published}:${port.target}/${port.protocol || 'tcp'}`); }); doc.services[key].ports = ports; } diff --git a/package.json b/package.json index fda60cbd..4bc91aca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable17", + "version": "0.14.0-unstable18", "description": "", "main": "test-server.js", "bugs": { From f5046b60e59c29a6247b666eb2af5b66ffd30ac6 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Mon, 5 Feb 2024 22:57:03 +0000 Subject: [PATCH 28/31] [release] v0.14.0-unstable19 --- client/src/pages/config/users/containerPicker.jsx | 2 +- .../pages/servapps/containers/docker-compose.jsx | 14 ++++++++------ package.json | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/client/src/pages/config/users/containerPicker.jsx b/client/src/pages/config/users/containerPicker.jsx index c4c8d74d..bf90f051 100644 --- a/client/src/pages/config/users/containerPicker.jsx +++ b/client/src/pages/config/users/containerPicker.jsx @@ -171,7 +171,7 @@ export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetConta const newTarget = formik.values[name]; React.useEffect(() => { if(onTargetChange) { - onTargetChange(newTarget, targetResult.container.replace("/", ""), targetResult) + onTargetChange(newTarget, targetResult.container !== 'null' ? targetResult.container.replace("/", "") : "", targetResult) } }, [newTarget]) diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index d992f908..e176fe8c 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -75,6 +75,13 @@ const isNewerVersion = (minver) => { return cmp(version, minver) === -1; } +const cleanUpStore = (service) => { + let newService = Object.assign({}, service); + delete newService['cosmos-installer']; + delete newService['x-cosmos-installer']; + return newService; +} + const convertDockerCompose = (config, serviceName, dockerCompose, setYmlError) => { let doc; @@ -599,11 +606,6 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } } } - - // REMOIVE COSMOS-INSTALLER - if (jsoned['cosmos-installer']) { - delete jsoned['cosmos-installer']; - } } setService(jsoned); @@ -854,7 +856,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
} {step === 1 && - + }
diff --git a/package.json b/package.json index 4bc91aca..342a41df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable18", + "version": "0.14.0-unstable19", "description": "", "main": "test-server.js", "bugs": { From 60fcbc1ac88f8086826f61eca845c59529bf6648 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Wed, 7 Feb 2024 19:38:09 +0000 Subject: [PATCH 29/31] [release] v0.14.0-unstable20 --- changelog.md | 2 ++ client/src/pages/config/users/proxyman.jsx | 2 +- client/src/pages/home/migrate014.jsx | 7 ++++--- package.json | 2 +- src/constellation/DNS.go | 18 +++++++++++++++++- src/constellation/index.go | 7 +++++++ src/constellation/nebula.go | 17 ++++++++++------- src/index.go | 6 ++++-- src/migrate.go | 4 ++-- src/utils/db.go | 3 ++- 10 files changed, 50 insertions(+), 18 deletions(-) diff --git a/changelog.md b/changelog.md index 69ebe579..f24921ce 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,8 @@ - Add a migration script to host mode - UI optimizations (thanks @Kawanaao) - Add duplicate filter on store listing + - Fixed an issue where container picker would select 'null' as container + - Fix bug where Enabled checkbox was broken after a search ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/client/src/pages/config/users/proxyman.jsx b/client/src/pages/config/users/proxyman.jsx index 2c10d1d3..585630b1 100644 --- a/client/src/pages/config/users/proxyman.jsx +++ b/client/src/pages/config/users/proxyman.jsx @@ -174,7 +174,7 @@ const ProxyManagement = () => { title: 'Enabled', clickable:true, field: (r, k) => , }, diff --git a/client/src/pages/home/migrate014.jsx b/client/src/pages/home/migrate014.jsx index f6ec4209..535992b3 100644 --- a/client/src/pages/home/migrate014.jsx +++ b/client/src/pages/home/migrate014.jsx @@ -35,11 +35,10 @@ const Migrate014 = ({ config }) => { https: Yup.number().required('HTTPS port is required').typeError('HTTPS port must be a number'), }), onSubmit: (values, { setErrors, setStatus, setSubmitting }) => { - console.log(values); setSubmitting(true); return API.docker.migrateHost(values) .then((res) => { - isSubmitted(true); + setIsSubmitted(true); setStatus({ success: true }); setSubmitting(false); }).catch((err) => { @@ -67,13 +66,15 @@ const Migrate014 = ({ config }) => { In order to continue to improve your experience, Cosmos now supports the Host Mode of networking. - It will the recommended way to run your Cosmos container from now on. Example of how it makes your life easier: + It will the recommended way to run your Cosmos container from now on. If you are using Windows do not do this, it's not compatible (you can disable this warning in the config file). +
Example of how it makes your life easier:
  • You will be able to connect to other services using localhost
  • You won't need all the cosmos-network anymore to connect to containers
  • You can see all your server's networking data in the monitoring tab (not just the docker data)
  • Docker-compose imported container will work out-of-the-box
  • Remove the docker overhead (small performance gain) and ensure Docker does not meddle with IP informations
  • +
  • Faster boot time (no bootstrapping of containers needed)
Cosmos can automatically migrate to the host mode, but before you do so, please confirm what ports you want to use with Cosmos (default are 80/443). The reason why we ask you to do this is that the host mode will use your server's network directly, not the docker virtual network. This means your port redirection (ex. -p 80:8080) will not be there anymore, and you need to tell Cosmos what ports to actually use directly. This form will save the settings for you and start the migration. diff --git a/package.json b/package.json index 342a41df..387d1ae5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable19", + "version": "0.14.0-unstable20", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/constellation/DNS.go b/src/constellation/DNS.go index be5293a9..52797082 100644 --- a/src/constellation/DNS.go +++ b/src/constellation/DNS.go @@ -136,6 +136,12 @@ func loadRawBlockList(DNSBlacklistRaw string) { } func InitDNS() { + ConstellationInitLock.Lock() + defer ConstellationInitLock.Unlock() + + ProcessMux.Lock() + defer ProcessMux.Unlock() + config := utils.GetMainConfig() DNSPort := config.ConstellationConfig.DNSPort DNSBlockBlacklist := config.ConstellationConfig.DNSBlockBlacklist @@ -182,7 +188,17 @@ func InitDNS() { server := &dns.Server{Addr: "192.168.201.1:" + DNSPort, Net: "udp"} utils.Log("Starting DNS server on :" + DNSPort) - if err := server.ListenAndServe(); err != nil { + var err error + + err = server.ListenAndServe(); + retries := 0 + for err != nil && retries < 4 { + time.Sleep(time.Duration(2 * (retries + 1)) * time.Second) + err = server.ListenAndServe(); + retries++ + utils.Debug("Retrying to start DNS server") + } + if err != nil { utils.Error("Failed to start DNS server", err) } })() diff --git a/src/constellation/index.go b/src/constellation/index.go index 8a0f9e23..cd4199b9 100644 --- a/src/constellation/index.go +++ b/src/constellation/index.go @@ -6,7 +6,14 @@ import ( "time" ) +var NebulaStarted = false + func Init() { + ConstellationInitLock.Lock() + defer ConstellationInitLock.Unlock() + + NebulaStarted = false + var err error // if date is > 1st of January 2024 diff --git a/src/constellation/nebula.go b/src/constellation/nebula.go index 5a2ab2ec..8c39fbbd 100644 --- a/src/constellation/nebula.go +++ b/src/constellation/nebula.go @@ -21,7 +21,8 @@ var logBuffer *lumberjack.Logger var ( process *exec.Cmd - processMux sync.Mutex + ProcessMux sync.Mutex + ConstellationInitLock sync.Mutex ) func binaryToRun() string { @@ -32,8 +33,8 @@ func binaryToRun() string { } func startNebulaInBackground() error { - processMux.Lock() - defer processMux.Unlock() + ProcessMux.Lock() + defer ProcessMux.Unlock() if process != nil { return errors.New("nebula is already running") @@ -64,13 +65,15 @@ func startNebulaInBackground() error { return err } + NebulaStarted = true + utils.Log(fmt.Sprintf("%s started with PID %d\n", binaryToRun(), process.Process.Pid)) return nil } func stop() error { - processMux.Lock() - defer processMux.Unlock() + ProcessMux.Lock() + defer ProcessMux.Unlock() if process == nil { return nil @@ -363,8 +366,8 @@ func getCApki() (string, error) { } func killAllNebulaInstances() error { - processMux.Lock() - defer processMux.Unlock() + ProcessMux.Lock() + defer ProcessMux.Unlock() cmd := exec.Command("ps", "-e", "-o", "pid,command") output, err := cmd.CombinedOutput() diff --git a/src/index.go b/src/index.go index d030d883..84c43848 100644 --- a/src/index.go +++ b/src/index.go @@ -33,7 +33,7 @@ func main() { docker.DockerListenEvents() - // docker.BootstrapAllContainersFromTags() + docker.BootstrapAllContainersFromTags() docker.RemoveSelfUpdater() @@ -72,7 +72,9 @@ func main() { constellation.Init() - constellation.InitDNS() + if constellation.NebulaStarted { + go constellation.InitDNS() + } utils.Log("Starting server...") } diff --git a/src/migrate.go b/src/migrate.go index 9286efc9..1fea3cec 100644 --- a/src/migrate.go +++ b/src/migrate.go @@ -78,10 +78,10 @@ func MigratePre014Coll(collection string, from *mongo.Client) { func MigratePre014() { config := utils.GetMainConfig() - utils.Log("MigratePre014: Migration of database...") - // check if COSMOS.db does NOT exist if _, err := os.Stat(utils.CONFIGFOLDER + "database"); err != nil && config.MongoDB != "" { + utils.Log("MigratePre014: Migration of database...") + // connect to MongoDB utils.Log("Connecting to MongoDB...") diff --git a/src/utils/db.go b/src/utils/db.go index 1ec0596d..5bdf2d2e 100644 --- a/src/utils/db.go +++ b/src/utils/db.go @@ -256,7 +256,8 @@ func WriteToDatabase(collection *mongo.Collection, objects []map[string]interfac func ListAllUsers(role string) []User { // list all users - c, errCo := GetCollection(GetRootAppId(), "users") + c, closeDb, errCo := GetEmbeddedCollection(GetRootAppId(), "users") + defer closeDb() if errCo != nil { Error("Database Connect", errCo) return []User{} From 6aa0ffefedfb90375c654dde0ff62203488de9e3 Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Thu, 8 Feb 2024 18:11:21 +0000 Subject: [PATCH 30/31] [release] v0.14.0-unstable21 --- changelog.md | 1 + client/src/pages/home/migrate014.jsx | 29 +++++++++++++++++++------- package.json | 2 +- readme.md | 1 - src/docker/checkPorts.go | 6 +++++- src/docker/docker.go | 31 ++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index f24921ce..1d504bf2 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ - Replaced network clean up by vanilla docker prune - Fix issue with removing IP whitelist - Add UI to create MCVlan networks + - Add a log file in config folder for the selfupdater - Add a migration script to host mode - UI optimizations (thanks @Kawanaao) - Add duplicate filter on store listing diff --git a/client/src/pages/home/migrate014.jsx b/client/src/pages/home/migrate014.jsx index 535992b3..36a9431f 100644 --- a/client/src/pages/home/migrate014.jsx +++ b/client/src/pages/home/migrate014.jsx @@ -15,6 +15,7 @@ import { MenuItem, Grid, FormHelperText, + CircularProgress, } from '@mui/material'; import { ArrowRightOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { LoadingButton } from '@mui/lab'; @@ -22,7 +23,7 @@ import * as API from '../../api'; const Migrate014 = ({ config }) => { const [isOpened, setIsOpened] = useState(false); - const [isSubmitted, setIsSubmitted] = useState(false); + const [isSubmitted, setIsSubmitted] = useState(0); const formik = useFormik({ initialValues: { @@ -35,10 +36,13 @@ const Migrate014 = ({ config }) => { https: Yup.number().required('HTTPS port is required').typeError('HTTPS port must be a number'), }), onSubmit: (values, { setErrors, setStatus, setSubmitting }) => { - setSubmitting(true); return API.docker.migrateHost(values) - .then((res) => { - setIsSubmitted(true); + .then((res) => { + setSubmitting(true); + setIsSubmitted(1); + setTimeout(() => { + setIsSubmitted(2); + }, 15000); setStatus({ success: true }); setSubmitting(false); }).catch((err) => { @@ -54,19 +58,29 @@ const Migrate014 = ({ config }) => { setIsOpened(false)}> Migrate to Host Mode - {isSubmitted ? + {isSubmitted ? ((isSubmitted == 2) ? Migration successful! Your Cosmos instance will now restart in Host Mode. - Refresh the page to see the changes (it might take a minute!). + Refresh the page to see the changes (it might take a few minutes!). if you are experiencing issues, please check the logs for more information. + : + + +
+ +
+
+
) : In order to continue to improve your experience, Cosmos now supports the Host Mode of networking. - It will the recommended way to run your Cosmos container from now on. If you are using Windows do not do this, it's not compatible (you can disable this warning in the config file). + It will be the recommended way to run your Cosmos container from now on. If you are using Windows do not do this, it's not compatible (you can disable this warning in the config file).
Example of how it makes your life easier:
  • You will be able to connect to other services using localhost
  • @@ -78,6 +92,7 @@ const Migrate014 = ({ config }) => {
Cosmos can automatically migrate to the host mode, but before you do so, please confirm what ports you want to use with Cosmos (default are 80/443). The reason why we ask you to do this is that the host mode will use your server's network directly, not the docker virtual network. This means your port redirection (ex. -p 80:8080) will not be there anymore, and you need to tell Cosmos what ports to actually use directly. This form will save the settings for you and start the migration. + If you have very customized Cosmos networking settings (ex. macvlan), the auto migration will not work, and you will need to do it manually.

diff --git a/package.json b/package.json index 387d1ae5..edc33595 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable20", + "version": "0.14.0-unstable21", "description": "", "main": "test-server.js", "bugs": { diff --git a/readme.md b/readme.md index c140feda..29e36dbb 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,6 @@ null null null -null

--- diff --git a/src/docker/checkPorts.go b/src/docker/checkPorts.go index 0e441354..6312f809 100644 --- a/src/docker/checkPorts.go +++ b/src/docker/checkPorts.go @@ -157,8 +157,10 @@ func UpdatePorts(finalPorts []string) error { }, }, }; - + + utils.Log("SelUpdatePorts - Creating updater service") + utils.Log("Creating self-updater service: docker run -d --name cosmos-self-updater-agent -e CONTAINER_NAME=" + containerName + " -e ACTION=ports -e DOCKER_HOST=" + os.Getenv("DOCKER_HOST") + " -e PORTS=" + strings.Join(finalPorts, ",") + " -v /var/run/docker.sock:/var/run/docker.sock azukaar/docker-self-updater:" + version) err := CreateService(service, func (msg string) {}) @@ -166,5 +168,7 @@ func UpdatePorts(finalPorts []string) error { return err } + go redirectLogs("cosmos-self-updater-agent", utils.CONFIGFOLDER + "/logs-cosmos-self-updater-agent.log") + return nil } \ No newline at end of file diff --git a/src/docker/docker.go b/src/docker/docker.go index 5f4afe27..3b4c2b35 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -667,15 +667,46 @@ func SelfAction(action string) error { }, }; + utils.Log("Creating self-updater service: docker run -d --name cosmos-self-updater-agent -e CONTAINER_NAME=" + containerName + " -e ACTION=" + action + " -e DOCKER_HOST=" + os.Getenv("DOCKER_HOST") + " -v /var/run/docker.sock:/var/run/docker.sock azukaar/docker-self-updater:" + version) + err := CreateService(service, func (msg string) {}) if err != nil { return err } + // attach logs + go redirectLogs("cosmos-self-updater-agent", utils.CONFIGFOLDER + "/logs-cosmos-self-updater-agent.log") + return nil } +func redirectLogs(containerName string, logFile string) { + // attach logs + logs, err := DockerClient.ContainerLogs(DockerContext, containerName, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Follow: true, + }) + if err != nil { + utils.Error("redirectLogs", err) + } + + // replace file if exist + file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0770) + if err != nil { + utils.Error("redirectLogs", err) + } + + defer file.Close() + defer logs.Close() + + _, err = io.Copy(file, logs) + if err != nil { + utils.Error("redirectLogs", err) + } +} + func DockerPullImage(image string) (io.ReadCloser, error) { utils.Debug("DockerPull - Preparing Pulling image " + image) From 2122d2dca5eef3df3b425d902a1df7c3b8724aaf Mon Sep 17 00:00:00 2001 From: Yann Stepienik Date: Sat, 10 Feb 2024 10:47:24 +0000 Subject: [PATCH 31/31] [release] v0.14.0-unstable22 --- changelog.md | 1 + package.json | 2 +- src/docker/api_updateContainer.go | 5 +++++ src/docker/checkPorts.go | 2 -- src/docker/docker.go | 10 +++++----- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 1d504bf2..d4895b9e 100644 --- a/changelog.md +++ b/changelog.md @@ -21,6 +21,7 @@ - Add duplicate filter on store listing - Fixed an issue where container picker would select 'null' as container - Fix bug where Enabled checkbox was broken after a search + - remove mac address when switching to host mode ## Version 0.13.2 - Fix display issue with fault network configurations diff --git a/package.json b/package.json index edc33595..bb2fc060 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.14.0-unstable21", + "version": "0.14.0-unstable22", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/api_updateContainer.go b/src/docker/api_updateContainer.go index 95aa6412..065a8848 100644 --- a/src/docker/api_updateContainer.go +++ b/src/docker/api_updateContainer.go @@ -120,6 +120,11 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) { } if(form.NetworkMode != "") { container.HostConfig.NetworkMode = containerType.NetworkMode(form.NetworkMode) + // if not bridge, remove mac address + if form.NetworkMode != "bridge" && + form.NetworkMode != "default" { + container.Config.MacAddress = "" + } } _, err = EditContainer(container.ID, container, false) diff --git a/src/docker/checkPorts.go b/src/docker/checkPorts.go index 6312f809..98a86261 100644 --- a/src/docker/checkPorts.go +++ b/src/docker/checkPorts.go @@ -168,7 +168,5 @@ func UpdatePorts(finalPorts []string) error { return err } - go redirectLogs("cosmos-self-updater-agent", utils.CONFIGFOLDER + "/logs-cosmos-self-updater-agent.log") - return nil } \ No newline at end of file diff --git a/src/docker/docker.go b/src/docker/docker.go index 3b4c2b35..87f3ff67 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -588,9 +588,12 @@ func RemoveSelfUpdater() error { return err } + for _, container := range containers { if container.Names[0] == "/cosmos-self-updater-agent" { - utils.Log("Found. Removing self updater agent") + utils.Log("Found. Copying logs and removing self updater agent") + redirectLogs("cosmos-self-updater-agent", utils.CONFIGFOLDER + "/logs-cosmos-self-updater-agent.log") + err := DockerClient.ContainerKill(DockerContext, container.ID, "SIGKILL") if err != nil { utils.Error("RemoveSelfUpdater", err) @@ -675,9 +678,6 @@ func SelfAction(action string) error { return err } - // attach logs - go redirectLogs("cosmos-self-updater-agent", utils.CONFIGFOLDER + "/logs-cosmos-self-updater-agent.log") - return nil } @@ -686,7 +686,7 @@ func redirectLogs(containerName string, logFile string) { logs, err := DockerClient.ContainerLogs(DockerContext, containerName, types.ContainerLogsOptions{ ShowStdout: true, ShowStderr: true, - Follow: true, + Follow: false, }) if err != nil { utils.Error("redirectLogs", err)