[release] v0.5.11

This commit is contained in:
Yann Stepienik 2023-05-27 13:58:33 +01:00
parent caddef01d1
commit c97ebed936
12 changed files with 356 additions and 182 deletions

View file

@ -1,4 +1,9 @@
## version 0.5.1 -> 0.5.7
## versiom 0.5.11
- Improve docker-compose import support for alternative syntaxes
- Improve docker service creation when using force secure label (fixes few containers not liking restarting too fast when created)
- Add toggle for using insecure HTTPS targets (fixes Unifi controller)
## version 0.5.1 -> 0.5.10
- Add Wilcard certificates support
- Auto switch to Mongo 4 if CPU has no ADX
- Improve setup for certificates on new install

View file

@ -46,6 +46,7 @@ const RouteManagement = ({ routeConfig, routeNames, TargetContainer, noControls
Target: routeConfig.Target,
UseHost: routeConfig.UseHost,
Host: routeConfig.Host,
AcceptInsecureHTTPSTarget: routeConfig.AcceptInsecureHTTPSTarget === true,
UsePathPrefix: routeConfig.UsePathPrefix,
PathPrefix: routeConfig.PathPrefix,
StripPathPrefix: routeConfig.StripPathPrefix,
@ -167,6 +168,12 @@ const RouteManagement = ({ routeConfig, routeNames, TargetContainer, noControls
/>
}
{formik.values.Target.startsWith('https://') && <CosmosCheckbox
name="AcceptInsecureHTTPSTarget"
label="Accept Insecure HTTPS Target (not recommended)"
formik={formik}
/>}
<CosmosFormDivider title={'Source'} />
<Grid item xs={12}>

View file

@ -81,11 +81,6 @@ const DockerComposeImport = ({refresh}) => {
let newService = {};
try {
doc = yaml.load(dockerCompose);
} catch (e) {
console.log(e);
setYmlError(e.message);
return;
}
if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 &&
!doc.services && !doc.networks && !doc.volumes) {
@ -98,10 +93,15 @@ const DockerComposeImport = ({refresh}) => {
// 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],
@ -109,9 +109,11 @@ const DockerComposeImport = ({refresh}) => {
Type: volume[0] === '/' ? 'bind' : 'volume',
};
volumes.push(volumeObj);
}
});
doc.services[key].volumes = volumes;
}
}
// convert expose
if (doc.services[key].expose) {
@ -135,6 +137,13 @@ const DockerComposeImport = ({refresh}) => {
});
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
@ -153,6 +162,10 @@ const DockerComposeImport = ({refresh}) => {
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;
@ -166,6 +179,57 @@ const DockerComposeImport = ({refresh}) => {
});
}
// 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) {
console.log(e);
setYmlError(e.message);
return;
}
setService(doc);
}, [dockerCompose]);

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "cosmos-server",
"version": "0.5.0-unstable",
"version": "0.5.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cosmos-server",
"version": "0.5.0-unstable",
"version": "0.5.10",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",

View file

@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.5.10",
"version": "0.5.11",
"description": "",
"main": "test-server.js",
"bugs": {
@ -58,8 +58,8 @@
"start": "env COSMOS_HOSTNAME=localhost CONFIG_FILE=./config_dev.json EZ=UTC build/cosmos",
"build": "sh build.sh",
"dev": "npm run build && npm run start",
"dockerdevbuild": "sh build.sh && npm run client-build && 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 --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
"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",
"demo": "vite build --base=/ui/ --mode demo",
"devdemo": "vite --mode demo"

View file

@ -110,7 +110,7 @@ Authentication is very hard (how do you check the password match? What encryptio
Installation is simple using Docker:
```
docker run -d -p 80:80 -p 443:443 --name cosmos-server -h cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/cosmos:/config azukaar/cosmos-server:latest
docker run -d -p 80:80 -p 443:443 --name cosmos-server -h cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /:/mnt/host -v /var/lib/cosmos:/config azukaar/cosmos-server:latest
```
Once installed, simply go to `http://your-server-ip` and follow the instructions of the setup wizard.

View file

@ -22,6 +22,12 @@ import (
"github.com/azukaar/cosmos-server/src/utils"
)
type ContainerCreateRequestServiceNetwork struct {
Aliases []string `json:"aliases,omitempty"`
IPV4Address string `json:"ipv4_address,omitempty"`
IPV6Address string `json:"ipv6_address,omitempty"`
}
type ContainerCreateRequestContainer struct {
Name string `json:"container_name"`
Image string `json:"image"`
@ -29,11 +35,7 @@ type ContainerCreateRequestContainer struct {
Labels map[string]string `json:"labels"`
Ports []string `json:"ports"`
Volumes []mount.Mount `json:"volumes"`
Networks map[string]struct {
Aliases []string `json:"aliases,omitempty"`
IPV4Address string `json:"ipv4_address,omitempty"`
IPV6Address string `json:"ipv6_address,omitempty"`
} `json:"networks"`
Networks map[string]ContainerCreateRequestServiceNetwork `json:"networks"`
Routes []utils.ProxyRouteConfig `json:"routes"`
RestartPolicy string `json:"restart,omitempty"`
@ -100,7 +102,7 @@ type ContainerCreateRequestNetwork struct {
type DockerServiceCreateRequest struct {
Services map[string]ContainerCreateRequestContainer `json:"services"`
Volumes []ContainerCreateRequestVolume `json:"volumes"`
Volumes map[string]ContainerCreateRequestVolume `json:"volumes"`
Networks map[string]ContainerCreateRequestNetwork `json:"networks"`
}
@ -137,6 +139,9 @@ func Rollback(actions []DockerServiceCreateRollback , w http.ResponseWriter, flu
flusher.Flush()
}
case "network":
if os.Getenv("HOSTNAME") != "" {
DockerClient.NetworkDisconnect(DockerContext, action.Name, os.Getenv("HOSTNAME"), true)
}
err := DockerClient.NetworkRemove(DockerContext, action.Name)
if err != nil {
utils.Error("Rollback: Network", err)
@ -220,6 +225,48 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
var rollbackActions []DockerServiceCreateRollback
var err error
// 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()
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()
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)
return err
}
service.Labels["cosmos-network-name"] = newNetwork
AttachNetworkToCosmos(newNetwork)
if service.Networks == nil {
service.Networks = make(map[string]ContainerCreateRequestServiceNetwork)
}
service.Networks[newNetwork] = ContainerCreateRequestServiceNetwork{}
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
Action: "remove",
Type: "network",
Name: newNetwork,
})
utils.Log(fmt.Sprintf("Created secure network %s", newNetwork))
fmt.Fprintf(w, "Created secure network %s\n", newNetwork)
flusher.Flush()
}
}
// Create networks
for networkToCreateName, networkToCreate := range serviceRequest.Networks {
utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName))
@ -391,11 +438,32 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
// Create missing folders for bind mounts
for _, newmount := range container.Volumes {
if newmount.Type == mount.TypeBind {
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) {
err := os.MkdirAll(newmount.Source, 0755)
newSource := newmount.Source
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)
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()
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()
err := os.MkdirAll(newSource, 0755)
if err != nil {
utils.Error("CreateService: Unable to create directory for bind mount", err)
fmt.Fprintf(w, "[ERROR] Unable to create directory for bind mount: "+err.Error())
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)
return err
@ -411,7 +479,7 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
} else {
uid, _ := strconv.Atoi(userInfo.Uid)
gid, _ := strconv.Atoi(userInfo.Gid)
err = os.Chown(newmount.Source, uid, 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())

View file

@ -4,8 +4,6 @@ import (
"encoding/json"
"net/http"
"os"
"os/user"
"strconv"
"github.com/azukaar/cosmos-server/src/utils"
containerType "github.com/docker/docker/api/types/container"
@ -86,33 +84,6 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
}
}
if(form.Volumes != nil) {
// Create missing folders for bind mounts
for _, newmount := range form.Volumes {
if newmount.Type == mount.TypeBind {
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) {
err := os.MkdirAll(newmount.Source, 0755)
if err != nil {
utils.Error("UpdateService: Unable to create directory for bind mount", err)
utils.HTTPError(w, "Unable to create directory for bind mount: "+err.Error(), http.StatusInternalServerError, "DS004")
return
}
// Change the ownership of the directory to the container.User
userInfo, err := user.Lookup(container.Config.User)
if err != nil {
utils.Error("UpdateService: Unable to lookup user", err)
} else {
uid, _ := strconv.Atoi(userInfo.Uid)
gid, _ := strconv.Atoi(userInfo.Gid)
err = os.Chown(newmount.Source, uid, gid)
if err != nil {
utils.Error("UpdateService: Unable to change ownership of directory", err)
}
}
}
}
}
container.HostConfig.Mounts = form.Volumes
container.HostConfig.Binds = []string{}
}

View file

@ -5,12 +5,17 @@ import (
"errors"
"time"
"bufio"
"os"
"os/user"
"fmt"
"strings"
"strconv"
"github.com/azukaar/cosmos-server/src/utils"
"github.com/docker/docker/client"
// natting "github.com/docker/go-connections/nat"
"github.com/docker/docker/api/types/container"
mountType "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types"
)
@ -111,6 +116,49 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock
oldContainer := newConfig
if(oldContainerID != "") {
// create missing folders
for _, newmount := range newConfig.HostConfig.Mounts {
if newmount.Type == mountType.TypeBind {
newSource := newmount.Source
if os.Getenv("HOSTNAME") != "" {
if _, err := os.Stat("/mnt/host"); os.IsNotExist(err) {
utils.Error("EditContainer: 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)
return "", errors.New("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")
}
newSource = "/mnt/host" + newSource
}
utils.Log(fmt.Sprintf("Checking directory %s for bind mount", newSource))
if _, err := os.Stat(newSource); os.IsNotExist(err) {
utils.Log(fmt.Sprintf("Not found. Creating directory %s for bind mount", newSource))
err := os.MkdirAll(newSource, 0755)
if err != nil {
utils.Error("EditContainer: Unable to create directory for bind mount", err)
return "", errors.New("Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory")
}
if newConfig.Config.User != "" {
// Change the ownership of the directory to the container.User
userInfo, err := user.Lookup(newConfig.Config.User)
if err != nil {
utils.Error("EditContainer: Unable to lookup user", err)
} else {
uid, _ := strconv.Atoi(userInfo.Uid)
gid, _ := strconv.Atoi(userInfo.Gid)
err = os.Chown(newSource, uid, gid)
if err != nil {
utils.Error("EditContainer: Unable to change ownership of directory", err)
}
}
}
}
}
}
utils.Log("EditContainer - Container updating. Retriveing currently running " + oldContainerID)
var err error

View file

@ -4,12 +4,13 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"crypto/tls"
spa "github.com/roberthodgen/spa-server"
"github.com/azukaar/cosmos-server/src/utils"
)
// NewProxy takes target host and creates a reverse proxy
func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
return nil, err
@ -17,6 +18,12 @@ func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
proxy := httputil.NewSingleHostReverseProxy(url)
if AcceptInsecureHTTPSTarget && url.Scheme == "https" {
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
proxy.ModifyResponse = func(resp *http.Response) error {
utils.Debug("Response from backend: " + resp.Status)
utils.Debug("URL was " + resp.Request.URL.String())
@ -35,7 +42,7 @@ func RouteTo(route utils.ProxyRouteConfig) http.Handler {
routeType := route.Mode
if(routeType == "SERVAPP" || routeType == "PROXY") {
proxy, err := NewProxy(destination)
proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget)
if err != nil {
utils.Error("Create Route", err)
}

View file

@ -148,6 +148,7 @@ type ProxyRouteConfig struct {
Mode ProxyMode
BlockCommonBots bool
BlockAPIAbuse bool
AcceptInsecureHTTPSTarget bool
}
type EmailConfig struct {

View file

@ -8,7 +8,6 @@ export default defineConfig({
build: {
outDir: '../static',
},
// base: '/ui',
server: {
proxy: {
'/cosmos/api': {
@ -16,6 +15,10 @@ export default defineConfig({
secure: false,
ws: true,
}
},
watch: {
usePolling: true
}
}
})