diff --git a/changelog.md b/changelog.md index ffeebbf..e228b59 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ ## Version 0.7.0 - Add Cosmos Market - - Fix issue with docker compose timeout healthcheck as string, and inverted ports + - Reforged the DNS CHallenge to be more user friendly. You can select your DNS provider in a list, and it will guide you through the process with the right fields to set (directly in the UI). No more env variables to set! + - Fix issue with docker compose timeout healthcheck as string, inverted ports, and supports for uid:gid syntax in user ## Version 0.6.0 // todo diff --git a/client/src/api/index.jsx b/client/src/api/index.jsx index 1d98761..6e9c000 100644 --- a/client/src/api/index.jsx +++ b/client/src/api/index.jsx @@ -13,6 +13,9 @@ import * as marketDemo from './market.demo'; import wrap from './wrap'; +export let CPU_ARCH = ''; +export let CPU_AVX = true; + let getStatus = () => { return wrap(fetch('/cosmos/api/status', { method: 'GET', @@ -20,6 +23,11 @@ let getStatus = () => { 'Content-Type': 'application/json' } })) + .then(async (response) => { + CPU_ARCH = response.data.CPU; + CPU_AVX = response.data.AVX; + return response + }); } let isOnline = () => { diff --git a/client/src/pages/config/users/formShortcuts.jsx b/client/src/pages/config/users/formShortcuts.jsx index c6d6650..78d9c43 100644 --- a/client/src/pages/config/users/formShortcuts.jsx +++ b/client/src/pages/config/users/formShortcuts.jsx @@ -128,7 +128,7 @@ export const CosmosInputPassword = ({ name, noStrength, type, placeholder, autoC } -export const CosmosSelect = ({ name, label, formik, disabled, options }) => { +export const CosmosSelect = ({ name, onChange, label, formik, disabled, options }) => { return {label} @@ -140,7 +140,10 @@ export const CosmosSelect = ({ name, label, formik, disabled, options }) => { disabled={disabled} select value={formik.values[name]} - onChange={formik.handleChange} + onChange={(...ar) => { + onChange && onChange(...ar); + formik.handleChange(...ar); + }} error={ formik.touched[name] && Boolean(formik.errors[name]) diff --git a/client/src/pages/market/listing.jsx b/client/src/pages/market/listing.jsx index 696ad92..9e1ea7d 100644 --- a/client/src/pages/market/listing.jsx +++ b/client/src/pages/market/listing.jsx @@ -13,7 +13,7 @@ import DockerComposeImport from '../servapps/containers/docker-compose'; function Screenshots({ screenshots }) { return ( - + { screenshots.map((item, i) => ) } @@ -23,7 +23,7 @@ function Screenshots({ screenshots }) { function Showcases({ showcase, isDark }) { return ( - + { showcase.map((item, i) => ) } @@ -33,7 +33,7 @@ function Showcases({ showcase, isDark }) { function ShowcasesItem({ isDark, item }) { return ( - - {/* */} {
- {openedApp.tags.slice(0, 8).map((tag) => )} + {openedApp.tags && openedApp.tags.slice(0, 8).map((tag) => )} +
+ +
+ {openedApp.supported_architectures && openedApp.supported_architectures.slice(0, 8).map((tag) => )}
diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index cd6e032..2dfb8f5 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -26,7 +26,7 @@ import ResponsiveButton from '../../../components/responseiveButton'; import UploadButtons from '../../../components/fileUpload'; import NewDockerService from './newService'; import yaml from 'js-yaml'; -import { CosmosCollapse, CosmosFormDivider, CosmosInputPassword, CosmosInputText } from '../../config/users/formShortcuts'; +import { CosmosCollapse, CosmosFormDivider, CosmosInputPassword, CosmosInputText, CosmosSelect } from '../../config/users/formShortcuts'; import VolumeContainerSetup from './volumes'; import DockerContainerSetup from './setup'; import whiskers from 'whiskers'; @@ -117,8 +117,11 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul let isJson = dockerCompose && dockerCompose.trim().startsWith('{') && dockerCompose.trim().endsWith('}'); - // if Json 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') { @@ -132,7 +135,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul else { // if Yml let doc; - let newService = {}; + try { doc = yaml.load(dockerCompose); @@ -231,8 +234,22 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } // convert healthcheck - if (doc.services[key].healthcheck && typeof doc.services[key].healthcheck.timeout === 'string') { - doc.services[key].healthcheck.timeout = parseInt(doc.services[key].healthcheck.timeout); + 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; + } + }); } }); } @@ -283,7 +300,6 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } } catch (e) { - console.log(e); setYmlError(e.message); return; } @@ -303,7 +319,6 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul try { if (installer) { - console.log(hostnames) const rendered = whiskers.render(dockerCompose.replace(/{StaticServiceName}/ig, serviceName), { ServiceName: serviceName, Hostnames: hostnames, @@ -312,7 +327,9 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul randomString(32), randomString(32), randomString(32) - ] + ], + CPU_ARCH: API.CPU_ARCH, + CPU_AVX: API.CPU_AVX, }); const jsoned = JSON.parse(rendered); @@ -373,7 +390,6 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul if (jsoned.services[key].volumes && jsoned['cosmos-installer'] && jsoned['cosmos-installer']['frozen-volumes']) { jsoned['cosmos-installer']['frozen-volumes'].forEach((volumeName) => { const keyVolume = overrides[key].volumes.findIndex((v) => { - console.log(v) return v.source === volumeName; }); delete overrides[key].volumes[keyVolume]; @@ -424,7 +440,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul setYmlError(e.message); return; } - }, [openModal, dockerCompose, serviceName, hostnames, overrides]); + }, [openModal, dockerCompose, serviceName, hostnames, overrides, installer]); const openModalFunc = () => { setOpenModal(true); @@ -489,7 +505,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul setServiceName(e.target.value)} /> - {service['cosmos-installer'] && service['cosmos-installer'].form.map((formElement) => { + {service['cosmos-installer'] && service['cosmos-installer'].form && service['cosmos-installer'].form.map((formElement) => { return formElement.type === 'checkbox' ? { @@ -506,6 +522,24 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul setContext({ ...context, [formElement.name]: e.target.value }); } } /> + : (formElement.type === 'select') ? + {}, + handleChange: () => {} + }} + onChange={(e) => { + setContext({ ...context, [formElement.name]: e.target.value }); + }} + options={formElement.options} + /> : formElement.type === 'hostname' ? <> { - console.log(formElement['name-container'], name) - console.log(context) setContext({ ...context, [formElement['name-container']]: name }); }} /> diff --git a/client/src/utils/dns-challenge-comp.jsx b/client/src/utils/dns-challenge-comp.jsx index 83ba853..d1bdd51 100644 --- a/client/src/utils/dns-challenge-comp.jsx +++ b/client/src/utils/dns-challenge-comp.jsx @@ -51,7 +51,7 @@ export const DnsChallengeComp = ({ name, configName, style, multiline, type, pla onChange={(e) => { onChange && onChange(e); }} - options={dnsList.map((dns) => ([dns,dns]))} + options={[["", "DISABLE"], ...(dnsList).map((dns) => ([dns,dns]))]} /> @@ -71,7 +71,7 @@ export const DnsChallengeComp = ({ name, configName, style, multiline, type, pla {dnsVar}: { const newConfig = { ...formik.values[configName], diff --git a/package.json b/package.json index 69ce476..dcaa134 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.7.0-unstable9", + "version": "0.7.0-unstable10", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go index 296a79d..719ac83 100644 --- a/src/docker/api_blueprint.go +++ b/src/docker/api_blueprint.go @@ -448,12 +448,14 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) OnLog(fmt.Sprintf("Not found. Creating directory %s for bind mount\n", newSource)) err := os.MkdirAll(newSource, 0750) + 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) 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 } + if container.UID != 0 { // Change the ownership of the directory to the container.UID err = os.Chown(newSource, container.UID, container.GID) @@ -461,6 +463,15 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) utils.Error("CreateService: Unable to change ownership of directory", err) OnLog(fmt.Sprintf("[ERROR] Unable to change ownership of directory: " + err.Error())) } + } else if container.User != "" && strings.Contains(container.User, ":") { + uidgid := strings.Split(container.User, ":") + uid, _ := strconv.Atoi(uidgid[0]) + gid, _ := strconv.Atoi(uidgid[1]) + err = os.Chown(newSource, uid, gid) + if err != nil { + utils.Error("CreateService: Unable to change ownership of directory", err) + OnLog(fmt.Sprintf("[ERROR] Unable to change ownership of directory: " + err.Error())) + } } else if container.User != "" { // Change the ownership of the directory to the container.User userInfo, err := user.Lookup(container.User) @@ -530,13 +541,14 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) EndpointsConfig: make(map[string]*network.EndpointSettings), } - for netName, netConfig := range container.Networks { + // Stupid Docker does not want to connect to multiple networks at once + /*for netName, netConfig := range container.Networks { networkingConfig.EndpointsConfig[netName] = &network.EndpointSettings{ Aliases: netConfig.Aliases, IPAddress: netConfig.IPV4Address, GlobalIPv6Address: netConfig.IPV6Address, } - } + }*/ _, err = DockerClient.ContainerCreate(DockerContext, containerConfig, hostConfig, networkingConfig, nil, container.Name) @@ -552,6 +564,22 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) Type: "container", Name: container.Name, }) + + // connect to networks + for netName, netConfig := range container.Networks { + err = DockerClient.NetworkConnect(DockerContext, netName, container.Name, &network.EndpointSettings{ + Aliases: netConfig.Aliases, + IPAddress: netConfig.IPV4Address, + GlobalIPv6Address: netConfig.IPV6Address, + }) + if err != nil { + utils.Error("CreateService: Rolling back changes because of -- Network Connection -- ", err) + OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Network connection error: "+err.Error())) + Rollback(rollbackActions, OnLog) + return err + } + } + // add routes for _, route := range container.Routes { diff --git a/src/market/update.go b/src/market/update.go index 5ac3cba..e8038e6 100644 --- a/src/market/update.go +++ b/src/market/update.go @@ -18,6 +18,7 @@ type appDefinition struct { Screenshots []string `json:"screenshots"` Icon string `json:"icon"` Compose string `json:"compose"` + SupportedArchitectures []string `json:"supported_architectures"` } type marketDefinition struct { diff --git a/src/status.go b/src/status.go index be3553f..014e1b7 100644 --- a/src/status.go +++ b/src/status.go @@ -3,6 +3,8 @@ package main import ( "net/http" "encoding/json" + "runtime" + "golang.org/x/sys/cpu" "github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/docker" @@ -46,6 +48,8 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) { "needsRestart": utils.NeedsRestart, "newVersionAvailable": utils.NewVersionAvailable, "hostname": utils.GetMainConfig().HTTPConfig.Hostname, + "CPU": runtime.GOARCH, + "AVX": cpu.X86.HasAVX, }, }) } else { diff --git a/src/utils/types.go b/src/utils/types.go index a0038aa..470843c 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -105,7 +105,7 @@ type HTTPConfig struct { SSLEmail string `validate:"omitempty,email"` UseWildcardCertificate bool AcceptAllInsecureHostname bool - DNSChallengeConfig map[string]string + DNSChallengeConfig map[string]string `json:"dnsChallengeConfig,omitempty"` } const (