[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 - Add Wilcard certificates support
- Auto switch to Mongo 4 if CPU has no ADX - Auto switch to Mongo 4 if CPU has no ADX
- Improve setup for certificates on new install - Improve setup for certificates on new install

View file

@ -46,6 +46,7 @@ const RouteManagement = ({ routeConfig, routeNames, TargetContainer, noControls
Target: routeConfig.Target, Target: routeConfig.Target,
UseHost: routeConfig.UseHost, UseHost: routeConfig.UseHost,
Host: routeConfig.Host, Host: routeConfig.Host,
AcceptInsecureHTTPSTarget: routeConfig.AcceptInsecureHTTPSTarget === true,
UsePathPrefix: routeConfig.UsePathPrefix, UsePathPrefix: routeConfig.UsePathPrefix,
PathPrefix: routeConfig.PathPrefix, PathPrefix: routeConfig.PathPrefix,
StripPathPrefix: routeConfig.StripPathPrefix, 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'} /> <CosmosFormDivider title={'Source'} />
<Grid item xs={12}> <Grid item xs={12}>

View file

@ -1,7 +1,7 @@
// material-ui // material-ui
import * as React from 'react'; import * as React from 'react';
import { Alert, Button, Stack, Typography } from '@mui/material'; import { Alert, Button, Stack, Typography } from '@mui/material';
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined, ArrowUpOutlined, FileZipOutlined } from '@ant-design/icons'; import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined, SyncOutlined, UserOutlined, KeyOutlined, ArrowUpOutlined, FileZipOutlined } from '@ant-design/icons';
import Table from '@mui/material/Table'; import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody'; import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell'; import TableCell from '@mui/material/TableCell';
@ -28,13 +28,13 @@ import NewDockerService from './newService';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
function checkIsOnline() { function checkIsOnline() {
API.isOnline().then((res) => { API.isOnline().then((res) => {
window.location.reload(); window.location.reload();
}).catch((err) => { }).catch((err) => {
setTimeout(() => { setTimeout(() => {
checkIsOnline(); checkIsOnline();
}, 1000); }, 1000);
}); });
} }
const preStyle = { const preStyle = {
@ -63,30 +63,25 @@ const preStyle = {
marginRight: '0', marginRight: '0',
} }
const DockerComposeImport = ({refresh}) => { const DockerComposeImport = ({ refresh }) => {
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const [dockerCompose, setDockerCompose] = useState(''); const [dockerCompose, setDockerCompose] = useState('');
const [service, setService] = useState({}); const [service, setService] = useState({});
const [ymlError, setYmlError] = useState(''); const [ymlError, setYmlError] = useState('');
useEffect(() => { useEffect(() => {
if(dockerCompose === '') { if (dockerCompose === '') {
return; return;
} }
setYmlError('');
let doc;
let newService = {};
try {
doc = yaml.load(dockerCompose);
setYmlError('');
let doc;
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 && if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 &&
!doc.services && !doc.networks && !doc.volumes) { !doc.services && !doc.networks && !doc.volumes) {
doc = { doc = {
@ -94,52 +89,66 @@ const DockerComposeImport = ({refresh}) => {
} }
} }
// convert to the proper format // convert to the proper format
if(doc.services) { if (doc.services) {
Object.keys(doc.services).forEach((key) => { Object.keys(doc.services).forEach((key) => {
// convert volumes // convert volumes
if(doc.services[key].volumes) { if (doc.services[key].volumes) {
let volumes = []; if(Array.isArray(doc.services[key].volumes)) {
doc.services[key].volumes.forEach((volume) => { let volumes = [];
let volumeSplit = volume.split(':'); doc.services[key].volumes.forEach((volume) => {
let volumeObj = { if (typeof volume === 'object') {
Source: volumeSplit[0], volumes.push(volume);
Target: volumeSplit[1], } else {
Type: volume[0] === '/' ? 'bind' : 'volume', let volumeSplit = volume.split(':');
}; let volumeObj = {
volumes.push(volumeObj); Source: volumeSplit[0],
}); Target: volumeSplit[1],
doc.services[key].volumes = volumes; Type: volume[0] === '/' ? 'bind' : 'volume',
};
volumes.push(volumeObj);
}
});
doc.services[key].volumes = volumes;
}
} }
// convert expose // convert expose
if(doc.services[key].expose) { if (doc.services[key].expose) {
doc.services[key].expose = doc.services[key].expose.map((port) => { doc.services[key].expose = doc.services[key].expose.map((port) => {
return ''+port; return '' + port;
}) })
} }
//convert user //convert user
if(doc.services[key].user) { if (doc.services[key].user) {
doc.services[key].user = '' + doc.services[key].user; doc.services[key].user = '' + doc.services[key].user;
} }
// convert labels: // convert labels:
if(doc.services[key].labels) { if (doc.services[key].labels) {
if(Array.isArray(doc.services[key].labels)) { if (Array.isArray(doc.services[key].labels)) {
let labels = {}; let labels = {};
doc.services[key].labels.forEach((label) => { doc.services[key].labels.forEach((label) => {
const [key, value] = label.split('='); const [key, value] = label.split('=');
labels[''+key] = ''+value; labels['' + key] = '' + value;
});
doc.services[key].labels = labels;
}
if (typeof doc.services[key].labels == 'object') {
let labels = {};
Object.keys(doc.services[key].labels).forEach((keylabel) => {
labels['' + keylabel] = '' + doc.services[key].labels[keylabel];
}); });
doc.services[key].labels = labels; doc.services[key].labels = labels;
} }
} }
// convert environment // convert environment
if(doc.services[key].environment) { if (doc.services[key].environment) {
if(!Array.isArray(doc.services[key].environment)) { if (!Array.isArray(doc.services[key].environment)) {
let environment = []; let environment = [];
Object.keys(doc.services[key].environment).forEach((keyenv) => { Object.keys(doc.services[key].environment).forEach((keyenv) => {
environment.push(keyenv + '=' + doc.services[key].environment[keyenv]); environment.push(keyenv + '=' + doc.services[key].environment[keyenv]);
@ -149,100 +158,155 @@ const DockerComposeImport = ({refresh}) => {
} }
// convert network // convert network
if(doc.services[key].networks) { if (doc.services[key].networks) {
if(Array.isArray(doc.services[key].networks)) { if (Array.isArray(doc.services[key].networks)) {
let networks = {}; let networks = {};
doc.services[key].networks.forEach((network) => { doc.services[key].networks.forEach((network) => {
networks[''+network] = {}; if (typeof network === 'object') {
networks['' + network.name] = network;
}
else
networks['' + network] = {};
}); });
doc.services[key].networks = networks; doc.services[key].networks = networks;
} }
} }
// ensure container_name // ensure container_name
if(!doc.services[key].container_name) { if (!doc.services[key].container_name) {
doc.services[key].container_name = key; doc.services[key].container_name = key;
} }
}); });
} }
setService(doc); // convert networks
}, [dockerCompose]); 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;
}
}
return <> // convert volumes
<Dialog open={openModal} onClose={() => setOpenModal(false)}> if (doc.volumes) {
<DialogTitle>Import Docker Compose</DialogTitle> if (Array.isArray(doc.volumes)) {
<DialogContent> let volumes = {};
<DialogContentText> doc.volumes.forEach((volume) => {
{step === 0 && <Stack spacing={2}> if(!volume) {
<Alert severity="warning" icon={<WarningOutlined />}> volume = {};
This is a highly experimental feature. It is recommended to use with caution. }
</Alert> 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;
}
}
<UploadButtons } catch (e) {
accept='.yml,.yaml' console.log(e);
OnChange={(e) => { setYmlError(e.message);
const file = e.target.files[0]; return;
const reader = new FileReader(); }
reader.onload = (e) => {
setDockerCompose(e.target.result);
};
reader.readAsText(file);
}}
/>
<div style={{color: 'red'}}> setService(doc);
{ymlError} }, [dockerCompose]);
</div>
<TextField
multiline
placeholder='Paste your docker-compose.yml here or use the file upload button.'
fullWidth
value={dockerCompose}
onChange={(e) => setDockerCompose(e.target.value)}
sx={preStyle}
InputProps={{
sx: {
color: '#EEE',
}
}}
rows={20}></TextField>
</Stack>}
{step === 1 && <Stack spacing={2}>
<NewDockerService service={service} refresh={refresh}/>
</Stack>}
</DialogContentText>
</DialogContent>
{!isLoading && <DialogActions>
<Button onClick={() => {
setOpenModal(false);
setStep(0);
setDockerCompose('');
setYmlError('');
}}>Close</Button>
<Button onClick={() => {
if(step === 0) {
setStep(1);
} else {
setStep(0);
}
}}>
{step === 0 && 'Next'}
{step === 1 && 'Back'}
</Button>
</DialogActions>}
</Dialog>
<ResponsiveButton return <>
color="primary" <Dialog open={openModal} onClose={() => setOpenModal(false)}>
onClick={() => setOpenModal(true)} <DialogTitle>Import Docker Compose</DialogTitle>
variant="outlined" <DialogContent>
startIcon={<ArrowUpOutlined />} <DialogContentText>
> {step === 0 && <Stack spacing={2}>
Import Docker Compose <Alert severity="warning" icon={<WarningOutlined />}>
</ResponsiveButton> This is a highly experimental feature. It is recommended to use with caution.
</>; </Alert>
<UploadButtons
accept='.yml,.yaml'
OnChange={(e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
setDockerCompose(e.target.result);
};
reader.readAsText(file);
}}
/>
<div style={{ color: 'red' }}>
{ymlError}
</div>
<TextField
multiline
placeholder='Paste your docker-compose.yml here or use the file upload button.'
fullWidth
value={dockerCompose}
onChange={(e) => setDockerCompose(e.target.value)}
sx={preStyle}
InputProps={{
sx: {
color: '#EEE',
}
}}
rows={20}></TextField>
</Stack>}
{step === 1 && <Stack spacing={2}>
<NewDockerService service={service} refresh={refresh} />
</Stack>}
</DialogContentText>
</DialogContent>
{!isLoading && <DialogActions>
<Button onClick={() => {
setOpenModal(false);
setStep(0);
setDockerCompose('');
setYmlError('');
}}>Close</Button>
<Button onClick={() => {
if (step === 0) {
setStep(1);
} else {
setStep(0);
}
}}>
{step === 0 && 'Next'}
{step === 1 && 'Back'}
</Button>
</DialogActions>}
</Dialog>
<ResponsiveButton
color="primary"
onClick={() => setOpenModal(true)}
variant="outlined"
startIcon={<ArrowUpOutlined />}
>
Import Docker Compose
</ResponsiveButton>
</>;
}; };
export default DockerComposeImport; export default DockerComposeImport;

4
package-lock.json generated
View file

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

View file

@ -1,6 +1,6 @@
{ {
"name": "cosmos-server", "name": "cosmos-server",
"version": "0.5.10", "version": "0.5.11",
"description": "", "description": "",
"main": "test-server.js", "main": "test-server.js",
"bugs": { "bugs": {
@ -56,10 +56,10 @@
"client": "vite", "client": "vite",
"client-build": "vite build --base=/ui/", "client-build": "vite build --base=/ui/",
"start": "env COSMOS_HOSTNAME=localhost CONFIG_FILE=./config_dev.json EZ=UTC build/cosmos", "start": "env COSMOS_HOSTNAME=localhost CONFIG_FILE=./config_dev.json EZ=UTC build/cosmos",
"build": " sh build.sh", "build": "sh build.sh",
"dev": "npm run build && npm run start", "dev": "npm run build && npm run start",
"dockerdevbuild": "sh build.sh && npm run client-build && docker build --tag 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 --restart=unless-stopped -h cosmos-dev --name cosmos-dev 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", "dockerdev": "npm run dockerdevbuild && npm run dockerdevrun",
"demo": "vite build --base=/ui/ --mode demo", "demo": "vite build --base=/ui/ --mode demo",
"devdemo": "vite --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: 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. 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" "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 { type ContainerCreateRequestContainer struct {
Name string `json:"container_name"` Name string `json:"container_name"`
Image string `json:"image"` Image string `json:"image"`
@ -29,12 +35,8 @@ type ContainerCreateRequestContainer struct {
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
Ports []string `json:"ports"` Ports []string `json:"ports"`
Volumes []mount.Mount `json:"volumes"` Volumes []mount.Mount `json:"volumes"`
Networks map[string]struct { Networks map[string]ContainerCreateRequestServiceNetwork `json:"networks"`
Aliases []string `json:"aliases,omitempty"` Routes []utils.ProxyRouteConfig `json:"routes"`
IPV4Address string `json:"ipv4_address,omitempty"`
IPV6Address string `json:"ipv6_address,omitempty"`
} `json:"networks"`
Routes []utils.ProxyRouteConfig `json:"routes"`
RestartPolicy string `json:"restart,omitempty"` RestartPolicy string `json:"restart,omitempty"`
Devices []string `json:"devices"` Devices []string `json:"devices"`
@ -100,7 +102,7 @@ type ContainerCreateRequestNetwork struct {
type DockerServiceCreateRequest struct { type DockerServiceCreateRequest struct {
Services map[string]ContainerCreateRequestContainer `json:"services"` Services map[string]ContainerCreateRequestContainer `json:"services"`
Volumes []ContainerCreateRequestVolume `json:"volumes"` Volumes map[string]ContainerCreateRequestVolume `json:"volumes"`
Networks map[string]ContainerCreateRequestNetwork `json:"networks"` Networks map[string]ContainerCreateRequestNetwork `json:"networks"`
} }
@ -137,6 +139,9 @@ func Rollback(actions []DockerServiceCreateRollback , w http.ResponseWriter, flu
flusher.Flush() flusher.Flush()
} }
case "network": case "network":
if os.Getenv("HOSTNAME") != "" {
DockerClient.NetworkDisconnect(DockerContext, action.Name, os.Getenv("HOSTNAME"), true)
}
err := DockerClient.NetworkRemove(DockerContext, action.Name) err := DockerClient.NetworkRemove(DockerContext, action.Name)
if err != nil { if err != nil {
utils.Error("Rollback: Network", err) utils.Error("Rollback: Network", err)
@ -220,6 +225,48 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
var rollbackActions []DockerServiceCreateRollback var rollbackActions []DockerServiceCreateRollback
var err error 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 // Create networks
for networkToCreateName, networkToCreate := range serviceRequest.Networks { for networkToCreateName, networkToCreate := range serviceRequest.Networks {
utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName)) 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 // Create missing folders for bind mounts
for _, newmount := range container.Volumes { for _, newmount := range container.Volumes {
if newmount.Type == mount.TypeBind { if newmount.Type == mount.TypeBind {
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) { newSource := newmount.Source
err := os.MkdirAll(newmount.Source, 0755)
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 { if err != nil {
utils.Error("CreateService: Unable to create directory for bind mount", err) 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: "+err.Error()) 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() flusher.Flush()
Rollback(rollbackActions, w, flusher) Rollback(rollbackActions, w, flusher)
return err return err
@ -411,7 +479,7 @@ func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest Dock
} else { } else {
uid, _ := strconv.Atoi(userInfo.Uid) uid, _ := strconv.Atoi(userInfo.Uid)
gid, _ := strconv.Atoi(userInfo.Gid) gid, _ := strconv.Atoi(userInfo.Gid)
err = os.Chown(newmount.Source, uid, gid) err = os.Chown(newSource, uid, gid)
if err != nil { if err != nil {
utils.Error("CreateService: Unable to change ownership of directory", err) utils.Error("CreateService: Unable to change ownership of directory", err)
fmt.Fprintf(w, "[ERROR] Unable to change ownership of directory: "+err.Error()) fmt.Fprintf(w, "[ERROR] Unable to change ownership of directory: "+err.Error())

View file

@ -4,8 +4,6 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"os" "os"
"os/user"
"strconv"
"github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/utils"
containerType "github.com/docker/docker/api/types/container" containerType "github.com/docker/docker/api/types/container"
@ -86,33 +84,6 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
} }
} }
if(form.Volumes != nil) { 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.Mounts = form.Volumes
container.HostConfig.Binds = []string{} container.HostConfig.Binds = []string{}
} }

View file

@ -5,12 +5,17 @@ import (
"errors" "errors"
"time" "time"
"bufio" "bufio"
"os"
"os/user"
"fmt"
"strings" "strings"
"strconv"
"github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/utils"
"github.com/docker/docker/client" "github.com/docker/docker/client"
// natting "github.com/docker/go-connections/nat" // natting "github.com/docker/go-connections/nat"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
mountType "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
) )
@ -111,6 +116,49 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock
oldContainer := newConfig oldContainer := newConfig
if(oldContainerID != "") { 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) utils.Log("EditContainer - Container updating. Retriveing currently running " + oldContainerID)
var err error var err error

View file

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

View file

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

View file

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