diff --git a/.circleci/config.yml b/.circleci/config.yml index ec169e7..b62ef48 100644 --- a/.circleci/config.yml +++ b/.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 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ac793ca --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +static +build \ No newline at end of file diff --git a/client/src/pages/servapps/actionBar.jsx b/client/src/pages/servapps/actionBar.jsx index bd0313d..3de70e1 100644 --- a/client/src/pages/servapps/actionBar.jsx +++ b/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: {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}> }, + { + t: 'No Update Available. Click to Force Pull', + if: ['update_not_available'], + e: {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}> + + + }, { 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 {action.e} })} diff --git a/client/src/pages/servapps/containers/index.jsx b/client/src/pages/servapps/containers/index.jsx index 6733d91..117f9a2 100644 --- a/client/src/pages/servapps/containers/index.jsx +++ b/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: + children: }, { title: 'Logs', diff --git a/client/src/pages/servapps/containers/overview.jsx b/client/src/pages/servapps/containers/overview.jsx index f144211..b85d2e4 100644 --- a/client/src/pages/servapps/containers/overview.jsx +++ b/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]} /> {containerInfo.State.Status !== 'running' && ( @@ -133,7 +134,8 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => { { setIsUpdating(true); diff --git a/client/src/pages/servapps/servapps.jsx b/client/src/pages/servapps/servapps.jsx index a29ff34..d895c9b 100644 --- a/client/src/pages/servapps/servapps.jsx +++ b/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 = () => { - + { { const name = app.Names[0].replace('/', ''); diff --git a/docker.arm64.sh b/docker.arm64.sh index 153f6d3..febc565 100644 --- a/docker.arm64.sh +++ b/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 \ diff --git a/docker.sh b/docker.sh index f2f7901..2050bb1 100644 --- a/docker.sh +++ b/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 \ diff --git a/dockerfile b/dockerfile index c0f5025..958a69c 100644 --- a/dockerfile +++ b/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"] diff --git a/dockerfile.arm64 b/dockerfile.arm64 index 9398bdc..5a6e1a0 100644 --- a/dockerfile.arm64 +++ b/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"] diff --git a/package.json b/package.json index 69e9293..2be8a70 100644 --- a/package.json +++ b/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" }, diff --git a/src/configapi/get.go b/src/configapi/get.go index 69c7cef..fac06ea 100644 --- a/src/configapi/get.go +++ b/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) diff --git a/src/docker/api_autoupdate.go b/src/docker/api_autoupdate.go index db81e7a..49654af 100644 --- a/src/docker/api_autoupdate.go +++ b/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 } diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go index e434693..5017f76 100644 --- a/src/docker/api_blueprint.go +++ b/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 } diff --git a/src/docker/api_managecont.go b/src/docker/api_managecont.go index 40e5000..4c8c36a 100644 --- a/src/docker/api_managecont.go +++ b/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) diff --git a/src/docker/docker.go b/src/docker/docker.go index c80e171..8e3405f 100644 --- a/src/docker/docker.go +++ b/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 +} \ No newline at end of file diff --git a/src/docker/run.go b/src/docker/run.go index d741974..7fab935 100644 --- a/src/docker/run.go +++ b/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, diff --git a/src/index.go b/src/index.go index 758a212..c8c3542 100644 --- a/src/index.go +++ b/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) diff --git a/vite.config.js b/vite.config.js index da5d2a2..c697baa 100644 --- a/vite.config.js +++ b/vite.config.js @@ -15,10 +15,6 @@ export default defineConfig({ secure: false, ws: true, } - }, - - watch: { - usePolling: true } } })