Browse Source

[release] v0.5.12-unstable

Yann Stepienik 2 years ago
parent
commit
3a228a9831

+ 0 - 66
.circleci/config.yml

@@ -11,38 +11,7 @@ jobs:
           name: install dependencies
           command: sudo apt-get install bash curl
 
-      - run:
-          name: download Go
-          command: wget https://golang.org/dl/go1.20.2.linux-amd64.tar.gz
-      
-      - run:
-          name: install Go
-          command: sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.20.2.linux-amd64.tar.gz
-      
-      - run:
-          name: set Go path
-          command: echo 'export PATH=$PATH:/usr/local/go/bin' >> $BASH_ENV
-
-      - run: |
-            echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
-            echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
-     
-      - run: |
-          node -v
-
-      - run: |
-          nvm install v16
-          node -v
-          nvm alias default v16
-
-      - run: |
-          node -v
-
       - run: docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
-
-      - run:
-          name: Install dependencies
-          command: npm install
           
       - run:
           name: Download GeoLite2-Country database
@@ -70,38 +39,7 @@ jobs:
           name: install dependencies
           command: sudo apt-get install bash curl
 
-      - run:
-          name: download Go
-          command: wget https://golang.org/dl/go1.20.2.linux-arm64.tar.gz
-      
-      - run:
-          name: install Go
-          command: sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.20.2.linux-arm64.tar.gz
-      
-      - run:
-          name: set Go path
-          command: echo 'export PATH=$PATH:/usr/local/go/bin' >> $BASH_ENV
-
-      - run: |
-            echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
-            echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
-     
-      - run: |
-          node -v
-
-      - run: |
-          nvm install v16
-          node -v
-          nvm alias default v16
-
-      - run: |
-          node -v
-
       - run: docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD
-
-      - run:
-          name: Install dependencies
-          command: npm install
           
       - run:
           name: Download GeoLite2-Country database
@@ -109,10 +47,6 @@ jobs:
             curl -s -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=$MAX_TOKEN&suffix=tar.gz" -o GeoLite2-Country.tar.gz
             tar -xzf GeoLite2-Country.tar.gz --strip-components 1 --wildcards "*.mmdb"
 
-      - run:
-          name: Build UI
-          command: npm run client-build
-
       - run:
           name: Build and publish dockerfiles
           command: sh docker.arm64.sh

+ 3 - 0
.dockerignore

@@ -0,0 +1,3 @@
+node_modules
+static
+build

+ 9 - 2
client/src/pages/servapps/actionBar.jsx

@@ -39,12 +39,19 @@ const GetActions = ({
 
   let actions = [
     {
-      t: 'Update Available',
+      t: 'Update Available, Click to Update',
       if: ['update_available'],
       e: <IconButton className="shinyButton" color='primary' onClick={() => {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}>
         <UpCircleOutlined />
       </IconButton>
     },
+    {
+      t: 'No Update Available. Click to Force Pull',
+      if: ['update_not_available'],
+      e: <IconButton onClick={() => {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}>
+        <UpCircleOutlined />
+      </IconButton>
+    },
     {
       t: 'Start',
       if: ['exited', 'created'],
@@ -117,7 +124,7 @@ const GetActions = ({
     />
     
     {!isUpdating && actions.filter((action) => {
-      return action.if.includes(state) || (updateAvailable && action.if.includes('update_available'));
+      return action.if.includes(state) || (updateAvailable && action.if.includes('update_available')) || (!updateAvailable && action.if.includes('update_not_available'));
     }).map((action) => {
       return <Tooltip title={action.t}>{action.e}</Tooltip>
     })}

+ 5 - 1
client/src/pages/servapps/containers/index.jsx

@@ -22,6 +22,8 @@ const ContainerIndex = () => {
   const { containerName } = useParams();
   const [container, setContainer] = React.useState(null);
   const [config, setConfig] = React.useState(null);
+  const [selfName, setSelfName] = React.useState("");
+  const [updatesAvailable, setUpdatesAvailable] = React.useState(null);
   
   const refreshContainer = () => {
     return Promise.all([API.docker.get(containerName).then((res) => {
@@ -29,6 +31,8 @@ const ContainerIndex = () => {
     }),
     API.config.get().then((res) => {
       setConfig(res.data);
+      setUpdatesAvailable(res.updates);
+      setSelfName(res.hostname);
     })]);
   };
 
@@ -49,7 +53,7 @@ const ContainerIndex = () => {
       tabs={[
         {
           title: 'Overview',
-          children: <ContainerOverview refresh={refreshContainer} containerInfo={container} config={config}/>
+          children: <ContainerOverview updatesAvailable={updatesAvailable} selfName={selfName} refresh={refreshContainer} containerInfo={container} config={config}/>
         },
         {
           title: 'Logs',

+ 4 - 2
client/src/pages/servapps/containers/overview.jsx

@@ -15,7 +15,7 @@ const info = {
   borderRadius: '5px',
 }
 
-const ContainerOverview = ({ containerInfo, config, refresh }) => {
+const ContainerOverview = ({ 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);
@@ -99,6 +99,7 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => {
                 setIsUpdatingId={() => {
                   setIsUpdating(true);
                 }}
+                updateAvailable={updatesAvailable && updatesAvailable[Name]}
               />
             </Stack>
             {containerInfo.State.Status !== 'running' && (
@@ -133,7 +134,8 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => {
             </Stack>
             <Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
               <Checkbox
-                checked={Config.Labels['cosmos-auto-update'] === 'true'}
+                checked={Config.Labels['cosmos-auto-update'] === 'true'  ||
+                  (selfName && Name.replace('/', '') == selfName && config.AutoUpdate)}
                 disabled={isUpdating}
                 onChange={(e) => {
                   setIsUpdating(true);

+ 5 - 2
client/src/pages/servapps/servapps.jsx

@@ -45,6 +45,7 @@ const ServeApps = () => {
   const [newRoute, setNewRoute] = useState(null);
   const [submitErrors, setSubmitErrors] = useState([]);
   const [openRestartModal, setOpenRestartModal] = useState(false);
+  const [selfName, setSelfName] = useState("");
 
   const refreshServeApps = () => {
     API.docker.list().then((res) => {
@@ -53,6 +54,7 @@ const ServeApps = () => {
     API.config.get().then((res) => {
       setConfig(res.data);
       setUpdatesAvailable(res.updates);
+      setSelfName(res.hostname);
     });
     setIsUpdating({});
   };
@@ -194,7 +196,7 @@ const ServeApps = () => {
                     </Stack>
                   </Stack>
                 </Stack>
-                <Stack direction="row" spacing={2} width='100%'>
+                <Stack direction="row" spacing={1} width='100%'>
                   <GetActions 
                     Id={app.Names[0].replace('/', '')}
                     image={app.Image}
@@ -256,7 +258,8 @@ const ServeApps = () => {
                   </Stack>
                   <Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
                     <Checkbox
-                      checked={app.Labels['cosmos-auto-update'] === 'true'}
+                      checked={app.Labels['cosmos-auto-update'] === 'true' ||
+                        (selfName && app.Names[0].replace('/', '') == selfName && config.AutoUpdate)}
                       disabled={app.State !== 'running'}
                       onChange={(e) => {
                         const name = app.Names[0].replace('/', '');

+ 0 - 2
docker.arm64.sh

@@ -12,8 +12,6 @@ fi
 
 echo "Pushing azukaar/cosmos-server:$VERSION and azukaar/cosmos-server:$LATEST"
 
-sh build.arm64.sh
-
 docker build \
   -t azukaar/cosmos-server:$VERSION-arm64 \
   -t azukaar/cosmos-server:$LATEST-arm64 \

+ 0 - 2
docker.sh

@@ -12,8 +12,6 @@ fi
 
 echo "Pushing azukaar/cosmos-server:$VERSION and azukaar/cosmos-server:$LATEST"
 
-sh build.sh
-
 docker build \
   -t azukaar/cosmos-server:$VERSION \
   -t azukaar/cosmos-server:$LATEST \

+ 24 - 6
dockerfile

@@ -10,11 +10,29 @@ RUN apt-get update && apt-get install -y ca-certificates openssl
 
 WORKDIR /app
 
-COPY build/cosmos .
-COPY build/cosmos_gray.png .
-COPY build/Logo.png .
-COPY build/GeoLite2-Country.mmdb .
-COPY build/meta.json .
-COPY static ./static
+# install go 1.20.2
+RUN apt-get install -y wget curl
+RUN wget https://golang.org/dl/go1.20.2.linux-amd64.tar.gz
+RUN tar -C /usr/local -xzf go1.20.2.linux-amd64.tar.gz
+ENV PATH=$PATH:/usr/local/go/bin
+
+# install nodejs 18.16.0
+RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
+RUN apt-get install -y nodejs
+
+COPY go.mod ./
+COPY go.sum ./
+RUN go mod download
+
+COPY package.json ./
+COPY package-lock.json ./
+RUN npm install
+
+COPY . .
+RUN ls 
+RUN npm run client-build
+RUN ./build.sh
+
+WORKDIR /app/build
 
 CMD ["./cosmos"]

+ 25 - 9
dockerfile.arm64

@@ -6,17 +6,33 @@ EXPOSE 443 80
 
 VOLUME /config
 
-RUN apt-get clean
-RUN apt-get update 
-RUN apt-get install -y ca-certificates openssl
+RUN apt-get update && apt-get install -y ca-certificates openssl
 
 WORKDIR /app
 
-COPY build/cosmos .
-COPY build/cosmos_gray.png .
-COPY build/Logo.png .
-COPY build/GeoLite2-Country.mmdb .
-COPY build/meta.json .
-COPY static ./static
+# install go 1.20.2
+RUN apt-get install -y wget curl
+RUN wget https://golang.org/dl/go1.20.2.linux-amd64.tar.gz
+RUN tar -C /usr/local -xzf go1.20.2.linux-amd64.tar.gz
+ENV PATH=$PATH:/usr/local/go/bin
+
+# install nodejs 18.16.0
+RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
+RUN apt-get install -y nodejs
+
+COPY go.mod ./
+COPY go.sum ./
+RUN go mod download
+
+COPY package.json ./
+COPY package-lock.json ./
+RUN npm install
+
+COPY . .
+RUN ls 
+RUN npm run client-build
+RUN ./build.sh
+
+WORKDIR /app/build
 
 CMD ["./cosmos"]

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "cosmos-server",
-  "version": "0.5.11",
+  "version": "0.5.12-unstable",
   "description": "",
   "main": "test-server.js",
   "bugs": {
@@ -61,6 +61,7 @@
     "dockerdevbuild": "sh build.sh && docker build --tag cosmos-dev .",
     "dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run -d -p 80:80 -p 443:443 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host  --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
     "dockerdev": "npm run dockerdevbuild && npm run dockerdevrun",
+    "dockerdevclient": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
     "demo": "vite build --base=/ui/ --mode demo",
     "devdemo": "vite --mode demo"
   },

+ 2 - 0
src/configapi/get.go

@@ -3,6 +3,7 @@ package configapi
 import (
 	"net/http"
 	"encoding/json"
+	"os"
 	"github.com/azukaar/cosmos-server/src/utils" 
 )
 
@@ -40,6 +41,7 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
 			"status": "OK",
 			"data": config,
 			"updates": utils.UpdateAvailable,
+			"hostname": os.Getenv("HOSTNAME"),
 		})
 	} else {
 		utils.Error("SettingGet: Method not allowed" + req.Method, nil)

+ 7 - 2
src/docker/api_autoupdate.go

@@ -20,8 +20,13 @@ func AutoUpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
 	status := utils.Sanitize(vars["status"])
 	
 	if os.Getenv("HOSTNAME") != "" && containerName == os.Getenv("HOSTNAME") {
-		utils.Error("AutoUpdateContainerRoute - Container cannot update itself", nil)
-		utils.HTTPError(w, "Container cannot update itself", http.StatusBadRequest, "DS003")
+		config := utils.ReadConfigFromFile()
+		config.AutoUpdate = status == "true"
+		utils.SaveConfigTofile(config)
+		utils.Log("API: Set Auto Update "+status+" : " + containerName)
+		json.NewEncoder(w).Encode(map[string]interface{}{
+			"status": "OK",
+		})
 		return
 	}
 	

+ 53 - 92
src/docker/api_blueprint.go

@@ -115,7 +115,7 @@ type DockerServiceCreateRollback struct {
 	Name string `json:"name"`
 }
 
-func Rollback(actions []DockerServiceCreateRollback , w http.ResponseWriter, flusher http.Flusher) {
+func Rollback(actions []DockerServiceCreateRollback , OnLog func(string)) {
 	for i := len(actions) - 1; i >= 0; i-- {
 		action := actions[i]
 		switch action.Type {
@@ -126,8 +126,7 @@ func Rollback(actions []DockerServiceCreateRollback , w http.ResponseWriter, flu
 				utils.Error("Rollback: Container", err)
 			} else {
 				utils.Log(fmt.Sprintf("Rolled back container %s", action.Name))
-				fmt.Fprintf(w, "Rolled back container %s\n", action.Name)
-				flusher.Flush()
+				OnLog(fmt.Sprintf("Rolled back container %s\n", action.Name))
 			}
 		case "volume":
 			err := DockerClient.VolumeRemove(DockerContext, action.Name, true)
@@ -135,8 +134,7 @@ func Rollback(actions []DockerServiceCreateRollback , w http.ResponseWriter, flu
 				utils.Error("Rollback: Volume", err)
 			} else {
 				utils.Log(fmt.Sprintf("Rolled back volume %s", action.Name))
-				fmt.Fprintf(w, "Rolled back volume %s\n", action.Name)
-				flusher.Flush()
+				OnLog(fmt.Sprintf("Rolled back volume %s\n", action.Name))
 			}
 		case "network":
 			if os.Getenv("HOSTNAME") != "" {
@@ -147,16 +145,14 @@ func Rollback(actions []DockerServiceCreateRollback , w http.ResponseWriter, flu
 				utils.Error("Rollback: Network", err)
 			} else {
 				utils.Log(fmt.Sprintf("Rolled back network %s", action.Name))
-				fmt.Fprintf(w, "Rolled back network %s\n", action.Name)
-				flusher.Flush()
+				OnLog(fmt.Sprintf("Rolled back network %s\n", action.Name))
 			}
 		}
 	}
 	
 	// After all operations
 	utils.Error("CreateService", fmt.Errorf("Operation failed. Changes have been rolled back."))
-	fmt.Fprintf(w, "[OPERATION FAILED]. CHANGES HAVE BEEN ROLLEDBACK.\n")
-	flusher.Flush()
+	OnLog("[OPERATION FAILED]. CHANGES HAVE BEEN ROLLEDBACK.\n")
 }
 
 func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
@@ -192,7 +188,12 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
 			return
 		}
 
-		CreateService(w, req, serviceRequest)
+		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")
@@ -201,23 +202,12 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
 }
 
 
-func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest DockerServiceCreateRequest) error {
-	// Enable streaming of response by setting appropriate headers
-	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!")
-	}
-	
+func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)) error {
 	utils.ConfigLock.Lock()
 	defer utils.ConfigLock.Unlock()
 	
 	utils.Log("Starting creation of new service...")
-	fmt.Fprintf(w, "Starting creation of new service...\n")
-	flusher.Flush()
+	OnLog("Starting creation of new service...\n")
 	
 	config := utils.ReadConfigFromFile()
 	configRoutes := config.HTTPConfig.ProxyConfig.Routes
@@ -228,20 +218,17 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 	// check if services have the cosmos-force-network-secured label
 	for serviceName, service := range serviceRequest.Services {
 		utils.Log(fmt.Sprintf("Checking service %s...", serviceName))
-		fmt.Fprintf(w, "Checking service %s...\n", serviceName)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Checking service %s...\n", serviceName))
 
 		if service.Labels["cosmos-force-network-secured"] == "true" {
 			utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName))
-			fmt.Fprintf(w, "Forcing secure %s...\n", serviceName)
-			flusher.Flush()
+			OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName))
 	
 			newNetwork, errNC := CreateCosmosNetwork()
 			if errNC != nil {
 				utils.Error("CreateService: Network", err)
-				fmt.Fprintf(w, "[ERROR] Network %s cant be created\n", newNetwork)
-				flusher.Flush()
-				Rollback(rollbackActions, w, flusher)
+				OnLog(fmt.Sprintf("[ERROR] Network %s cant be created\n", newNetwork))
+				Rollback(rollbackActions, OnLog)
 				return err
 			}
 
@@ -262,24 +249,21 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 			})
 			
 			utils.Log(fmt.Sprintf("Created secure network %s", newNetwork))
-			fmt.Fprintf(w, "Created secure network %s\n", newNetwork)
-			flusher.Flush()
+			OnLog(fmt.Sprintf("Created secure network %s\n", newNetwork))
 		}
 	}
 
 	// Create networks
 	for networkToCreateName, networkToCreate := range serviceRequest.Networks {
 		utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName))
-		fmt.Fprintf(w, "Creating network %s...\n", networkToCreateName)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Creating network %s...\n", networkToCreateName))
 
 		// check if network already exists
 		_, err = DockerClient.NetworkInspect(DockerContext, networkToCreateName, doctype.NetworkInspectOptions{})
 		if err == nil {
 			utils.Error("CreateService: Network", err)
-			fmt.Fprintf(w, "[ERROR] Network %s already exists\n", networkToCreateName)
-			flusher.Flush()
-			Rollback(rollbackActions, w, flusher)
+			OnLog(fmt.Sprintf("[ERROR] Network %s already exists\n", networkToCreateName))
+			Rollback(rollbackActions, OnLog)
 			return err
 		}
 
@@ -305,9 +289,8 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 
 		if err != nil {
 			utils.Error("CreateService: Rolling back changes because of -- Network", err)
-			fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Network creation error: "+err.Error())
-			flusher.Flush()
-			Rollback(rollbackActions, w, flusher)
+			OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Network creation error: %s\n", err.Error()))
+			Rollback(rollbackActions, OnLog)
 			return err
 		}
 
@@ -319,15 +302,13 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 	
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Network %s created", networkToCreateName))
-		fmt.Fprintf(w, "Network %s created\n", networkToCreateName)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Network %s created\n", networkToCreateName))
 	}
 
 	// Create volumes
 	for _, volume := range serviceRequest.Volumes {
 		utils.Log(fmt.Sprintf("Creating volume %s...", volume.Name))
-		fmt.Fprintf(w, "Creating volume %s...\n", volume.Name)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Creating volume %s...\n", volume.Name))
 		
 		_, err = DockerClient.VolumeCreate(DockerContext, volumetype.CreateOptions{
 			Driver:     volume.Driver,
@@ -336,9 +317,8 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 
 		if err != nil {
 			utils.Error("CreateService: Rolling back changes because of -- Volume", err)
-			fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Volume creation error: "+err.Error())
-			flusher.Flush()
-			Rollback(rollbackActions, w, flusher)
+			OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Volume creation error: %s\n", err.Error()))
+			Rollback(rollbackActions, OnLog)
 			return err
 		}
 
@@ -350,23 +330,20 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Volume %s created", volume.Name))
-		fmt.Fprintf(w, "Volume %s created\n", volume.Name)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Volume %s created\n", volume.Name))
 	}
 
 	// pull images
 	for _, container := range serviceRequest.Services {
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Pulling image %s", container.Image))
-		fmt.Fprintf(w, "Pulling image %s\n", container.Image)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Pulling image %s\n", container.Image))
 
 		out, err := DockerClient.ImagePull(DockerContext, container.Image, doctype.ImagePullOptions{})
 		if err != nil {
 			utils.Error("CreateService: Rolling back changes because of -- Image pull", err)
-			fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Image pull error: "+err.Error())
-			flusher.Flush()
-			Rollback(rollbackActions, w, flusher)
+			OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Image pull error: %s\n", err.Error()))
+			Rollback(rollbackActions, OnLog)
 			return err
 		}
 		defer out.Close()
@@ -374,21 +351,18 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 		// wait for image pull to finish
 		scanner := bufio.NewScanner(out)
 		for scanner.Scan() {
-			fmt.Fprintf(w, "%s\n", scanner.Text())
-			flusher.Flush()
+			OnLog(fmt.Sprintf("%s\n", scanner.Text()))
 		}
 		
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Image %s pulled", container.Image))
-		fmt.Fprintf(w, "Image %s pulled\n", container.Image)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Image %s pulled\n", container.Image))
 	}
 
 	// Create containers
 	for _, container := range serviceRequest.Services {
 		utils.Log(fmt.Sprintf("Creating container %s...", container.Name))
-		fmt.Fprintf(w, "Creating container %s...\n", container.Name)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Creating container %s...\n", container.Name))
 
 		containerConfig := &conttype.Config{
 			Image:        container.Image,
@@ -443,29 +417,25 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 				if os.Getenv("HOSTNAME") != "" {
 					if _, err := os.Stat("/mnt/host"); os.IsNotExist(err) {
 						utils.Error("CreateService: Unable to create directory for bind mount in the host directory. Please mount the host / in Cosmos with  -v /:/mnt/host to enable folder creations, or create the bind folder yourself", err)
-						fmt.Fprintf(w, "[ERROR] Unable to create directory for bind mount in the host directory. Please mount the host / in Cosmos with  -v /:/mnt/host to enable folder creations, or create the bind folder yourself: "+err.Error())
-						flusher.Flush()
-						Rollback(rollbackActions, w, flusher)
+						OnLog(fmt.Sprintf("[ERROR] Unable to create directory for bind mount in the host directory. Please mount the host / in Cosmos with  -v /:/mnt/host to enable folder creations, or create the bind folder yourself: %s\n", err.Error()))
+						Rollback(rollbackActions, OnLog)
 						return err
 					}
 					newSource = "/mnt/host" + newSource
 				}
 						
 				utils.Log(fmt.Sprintf("Checking directory %s for bind mount", newSource))
-				fmt.Fprintf(w, "Checking directory %s for bind mount\n", newSource)
-				flusher.Flush()
+				OnLog(fmt.Sprintf("Checking directory %s for bind mount\n", newSource))
 
 				if _, err := os.Stat(newSource); os.IsNotExist(err) {
 					utils.Log(fmt.Sprintf("Not found. Creating directory %s for bind mount", newSource))
-					fmt.Fprintf(w, "Not found. Creating directory %s for bind mount\n", newSource)
-					flusher.Flush()
+					OnLog(fmt.Sprintf("Not found. Creating directory %s for bind mount\n", newSource))
 	
 					err := os.MkdirAll(newSource, 0755)
 					if err != nil {
 						utils.Error("CreateService: Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory", err)
-						fmt.Fprintf(w, "[ERROR] Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory for bind mount: "+err.Error())
-						flusher.Flush()
-						Rollback(rollbackActions, w, flusher)
+						OnLog(fmt.Sprintf("[ERROR] Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory: %s\n", err.Error()))
+						Rollback(rollbackActions, OnLog)
 						return err
 					}
 		
@@ -474,16 +444,14 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 						userInfo, err := user.Lookup(container.User)
 						if err != nil {
 							utils.Error("CreateService: Unable to lookup user", err)
-							fmt.Fprintf(w, "[ERROR] Unable to lookup user " + container.User + "." +err.Error())
-							flusher.Flush()
+							OnLog(fmt.Sprintf("[ERROR] Unable to lookup user " + container.User + "." +err.Error()))
 						} else {
 							uid, _ := strconv.Atoi(userInfo.Uid)
 							gid, _ := strconv.Atoi(userInfo.Gid)
 							err = os.Chown(newSource, uid, gid)
 							if err != nil {
 								utils.Error("CreateService: Unable to change ownership of directory", err)
-								fmt.Fprintf(w, "[ERROR] Unable to change ownership of directory: "+err.Error())
-								flusher.Flush()
+								OnLog(fmt.Sprintf("[ERROR] Unable to change ownership of directory: " + err.Error()))
 							}
 						}	
 					}
@@ -553,9 +521,8 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 		
 		if err != nil {
 			utils.Error("CreateService: Rolling back changes because of -- Container", err)
-			fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container creation error: "+err.Error())
-			flusher.Flush()
-			Rollback(rollbackActions, w, flusher)
+			OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Container creation error: "+err.Error()))
+			Rollback(rollbackActions, OnLog)
 			return err
 		}
 		
@@ -579,26 +546,23 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 				configRoutes = append([]utils.ProxyRouteConfig{(utils.ProxyRouteConfig)(route)}, configRoutes...)
 			} else {
 				utils.Error("CreateService: Rolling back changes because of -- Route already exist", nil)
-				fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Route already exist")
-				flusher.Flush()
-				Rollback(rollbackActions, w, flusher)
+				OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Route already exist"))
+				Rollback(rollbackActions, OnLog)
 				return errors.New("Route already exist")
 			}
 		}
 		
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Container %s created", container.Name))
-		fmt.Fprintf(w, "Container %s created\n", container.Name)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Container %s created", container.Name))
 	}
 
 	// re-order containers dpeneding on depends_on
 	startOrder, err := ReOrderServices(serviceRequest.Services)
 	if err != nil {
 		utils.Error("CreateService: Rolling back changes because of -- Container", err)
-		fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container creation error: "+err.Error())
-		flusher.Flush()
-		Rollback(rollbackActions, w, flusher)
+		OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Container creation error: "+err.Error()))
+		Rollback(rollbackActions, OnLog)
 		return err
 	}
 
@@ -607,16 +571,14 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 		err = DockerClient.ContainerStart(DockerContext, container.Name, doctype.ContainerStartOptions{})
 		if err != nil {
 			utils.Error("CreateService: Start Container", err)
-			fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container start error: "+err.Error())
-			flusher.Flush()
-			Rollback(rollbackActions, w, flusher)
+			OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Container start error: "+err.Error()))
+			Rollback(rollbackActions, OnLog)
 			return err
 		}
 
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Container %s started", container.Name))
-		fmt.Fprintf(w, "Container %s started\n", container.Name)
-		flusher.Flush()
+		OnLog(fmt.Sprintf("Container %s started", container.Name))
 	}
 	
 	// Save the route configs 
@@ -626,8 +588,7 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
 
 	// After all operations
 	utils.Log("CreateService: Operation succeeded. SERVICE STARTED")
-	fmt.Fprintf(w, "[OPERATION SUCCEEDED]. SERVICE STARTED\n")
-	flusher.Flush()
+	OnLog("[OPERATION SUCCEEDED]. SERVICE STARTED\n")
 
 	return nil
 }

+ 3 - 3
src/docker/api_managecont.go

@@ -31,7 +31,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
 	// stop, start, restart, kill, remove, pause, unpause, recreate
 	action := utils.Sanitize(vars["action"])
 	
-	if os.Getenv("HOSTNAME") != "" && containerName == os.Getenv("HOSTNAME") {
+	if os.Getenv("HOSTNAME") != "" && containerName == os.Getenv("HOSTNAME") && action != "update" && action != "recreate" {
 		utils.Error("ManageContainer - Container cannot update itself", nil)
 		utils.HTTPError(w, "Container cannot update itself", http.StatusBadRequest, "DS003")
 		return
@@ -68,7 +68,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
 		case "unpause":
 			err = DockerClient.ContainerUnpause(DockerContext, container.ID)
 		case "recreate":
-			_, err = EditContainer(container.ID, container, false)
+			_, err = RecreateContainer(container.Name, container)
 		case "update":
 			out, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{})
 			if errPull != nil {
@@ -100,7 +100,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
 
 			utils.Log("Container Update - Image pulled " + imagename)
 
-			_, err = EditContainer(container.ID, container, false)
+			_, err = RecreateContainer(container.Name, container)
 
 			if err != nil {
 				utils.Error("Container Update - EditContainer", err)

+ 111 - 4
src/docker/docker.go

@@ -10,7 +10,9 @@ import (
 	"fmt"
 	"strings"
 	"strconv"
+	"runtime"
 	"github.com/azukaar/cosmos-server/src/utils" 
+	
 
 	"github.com/docker/docker/client"
 	// natting "github.com/docker/go-connections/nat"
@@ -86,6 +88,19 @@ func Connect() error {
 	return nil
 }
 
+func RecreateContainer(containerID string, containerConfig types.ContainerJSON) (string, error) {
+	if os.Getenv("HOSTNAME") != ""  && os.Getenv("HOSTNAME") == containerID[1:] {
+		err := SelfRecreate()
+		if err != nil {
+			return "", err
+		}
+	} else {
+		return EditContainer(containerID, containerConfig, false)
+	}
+
+	return "", nil
+}
+
 func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock bool) (string, error) {
 	if(oldContainerID != "" && !noLock) {
 		// no need to re-lock if we are reverting
@@ -424,6 +439,19 @@ func Test() error {
 	return nil
 }
 
+func HasAutoUpdateOn(containerConfig types.ContainerJSON) bool {
+	if containerConfig.Config.Labels["cosmos-auto-update"] == "true" {
+		return true
+	}
+
+	config := utils.ReadConfigFromFile()
+
+	if os.Getenv("HOSTNAME") == containerConfig.Name[1:] && config.AutoUpdate {
+		return true
+	}
+
+	return false
+}
 
 func CheckUpdatesAvailable() map[string]bool {
 	result := make(map[string]bool)
@@ -468,7 +496,7 @@ func CheckUpdatesAvailable() map[string]bool {
 				utils.Log("Updates available for " + container.Image)
 
 				result[container.Names[0]] = true
-				if !IsLabel(fullContainer, "cosmos-auto-update") {
+				if !HasAutoUpdateOn(fullContainer) {
 					rc.Close()
 					break
 				} else {
@@ -477,7 +505,7 @@ func CheckUpdatesAvailable() map[string]bool {
 			} else if strings.Contains(newStr, "\"status\":\"Status: Image is up to date") {
 				utils.Log("No updates available for " + container.Image)
 				
-				if !IsLabel(fullContainer, "cosmos-auto-update") {
+				if !HasAutoUpdateOn(fullContainer) {
 					rc.Close()
 					break
 				}
@@ -505,9 +533,9 @@ func CheckUpdatesAvailable() map[string]bool {
 			}
 		}
 
-		if needsUpdate && IsLabel(fullContainer, "cosmos-auto-update") {
+		if needsUpdate && HasAutoUpdateOn(fullContainer) {
 			utils.Log("Downlaoded new update for " + container.Image + " ready to install")
-			_, err := EditContainer(container.ID, fullContainer, false)
+			_, err := RecreateContainer(container.Names[0], fullContainer)
 			if err != nil {
 				utils.Error("CheckUpdatesAvailable - Failed to update - ", err)
 			} else {
@@ -518,3 +546,82 @@ func CheckUpdatesAvailable() map[string]bool {
 
 	return result
 }
+
+func RemoveSelfUpdater() error {
+	utils.Log("Checking for self updater agent")
+
+	// look for a container with the name cosmos-self-updater-agent
+	containers, err := ListContainers()
+	if err != nil {
+		utils.Error("RemoveSelfUpdater", err)
+		return err
+	}
+
+	for _, container := range containers {
+		if container.Names[0] == "/cosmos-self-updater-agent" {
+			utils.Log("Found. Removing self updater agent")
+			err := DockerClient.ContainerKill(DockerContext, container.ID, "SIGKILL")
+			if err != nil {
+				utils.Error("RemoveSelfUpdater", err)
+			}
+			err = DockerClient.ContainerRemove(DockerContext, container.ID, types.ContainerRemoveOptions{
+				Force: true,
+			})
+			if err != nil {
+				utils.Error("RemoveSelfUpdater", err)
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func SelfRecreate() error {
+	if os.Getenv("HOSTNAME") == "" {
+		utils.Error("SelfRecreate - not using Docker", nil)
+		return errors.New("SelfRecreate - not using Docker")
+	}
+
+	// make sure to remove resiude of old self updater
+	RemoveSelfUpdater()
+
+	containerName := os.Getenv("HOSTNAME")
+
+	version := "latest"
+
+	// if arm
+	if runtime.GOARCH == "arm" {
+		version = "latest-arm64"
+	}
+	
+	service := DockerServiceCreateRequest{
+		Services: map[string]ContainerCreateRequestContainer {},
+	}
+
+	service.Services["cosmos-self-updater-agent"] = ContainerCreateRequestContainer{
+		Name: "cosmos-self-updater-agent",
+		Image: "azukaar/docker-self-updater:" + version,
+		RestartPolicy: "no",
+		Environment: []string{
+			"CONTAINER_NAME=" + containerName,
+			"ACTION=recreate",
+			"DOCKER_HOST=" + os.Getenv("DOCKER_HOST"),
+		},
+		Volumes: []mountType.Mount{
+			{
+				Type: mountType.TypeBind,
+				Source: "/var/run/docker.sock",
+				Target: "/var/run/docker.sock",
+			},
+		},
+	};
+
+	err := CreateService(service, func (msg string) {})
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 7 - 2
src/docker/run.go

@@ -28,13 +28,18 @@ func NewDB(w http.ResponseWriter, req *http.Request) (string, error) {
 	monHost := "cosmos-mongo-" + id
 	
 	imageName := "mongo:latest"
+	
+	//if ARM use amd64/mongo
+	if runtime.GOARCH == "arm64" {
+		utils.Warn("ARM64 detected. Using ARM mongo 4.4")
+		imageName = "amd64/mongo:4.4"
 
 	// if CPU is missing AVX, use 4.4
-	if runtime.GOARCH == "amd64" && !cpu.X86.HasAVX {
+	} else if runtime.GOARCH == "amd64" && !cpu.X86.HasAVX {
 		utils.Warn("CPU does not support AVX. Using mongo 4.4")
 		imageName = "mongo:4.4"
 	}
-	
+
 	err := RunContainer(
 		imageName,
 		monHost,

+ 2 - 0
src/index.go

@@ -24,6 +24,8 @@ func main() {
 
 	docker.BootstrapAllContainersFromTags()
 
+	docker.RemoveSelfUpdater()
+
 	version, err := docker.DockerClient.ServerVersion(context.Background())
 	if err == nil {
 		utils.Log("Docker API version: " + version.APIVersion)

+ 0 - 4
vite.config.js

@@ -15,10 +15,6 @@ export default defineConfig({
         secure: false,
         ws: true,
       }
-    },
-    
-    watch: {
-      usePolling: true
     }
   }
 })