Browse Source

[release] v0.8.3

Yann Stepienik 2 years ago
parent
commit
a4c7eded55

+ 7 - 3
changelog.md

@@ -1,7 +1,11 @@
-## version 0.8.1 -> 0.8.2
+## version 0.8.1 -> 0.8.3
+ - Added new automatic Docker mapping feature (for people not using (sub)domains)
  - App store image size issue
  - App store image size issue
- - Add installer hostname prefix/suffix
- - Fix issue with inconsistent password when installing from the market
+ - Add installer option for hostname prefix/suffix
+ - Fix minor issue with inconsistent password on market installer
+ - Fixed issue where home page was https:// links on http only servers
+ - Improved setup flow for setting up hostname and HTTPS
+ - Added a new port range system for people not using a domain name
 
 
 ## Version 0.8.0
 ## Version 0.8.0
  - Custmizable homepage / theme colors
  - Custmizable homepage / theme colors

+ 1 - 1
client/src/pages/config/users/configman.jsx

@@ -337,7 +337,7 @@ const ConfigManagement = () => {
                 <Grid container spacing={3}>
                 <Grid container spacing={3}>
                   <Grid item xs={12}>
                   <Grid item xs={12}>
                     <Stack spacing={1}>
                     <Stack spacing={1}>
-                      <InputLabel htmlFor="Hostname-login">Hostname: This will be used to restrict access to your Cosmos Server (Default: 0.0.0.0)</InputLabel>
+                      <InputLabel htmlFor="Hostname-login">Hostname: This will be used to restrict access to your Cosmos Server (Your IP, or your domain name)</InputLabel>
                       <OutlinedInput
                       <OutlinedInput
                         id="Hostname-login"
                         id="Hostname-login"
                         type="text"
                         type="text"

+ 1 - 1
client/src/pages/home/index.jsx

@@ -240,7 +240,7 @@ const HomePage = () => {
 
 
             {coStatus && coStatus.domain && (
             {coStatus && coStatus.domain && (
                 <Alert severity="error">
                 <Alert severity="error">
-                    You are using localhost or 0.0.0.0 as a hostname in the configuration. It is recommended that you use a domain name instead.
+                    You are using localhost or 0.0.0.0 as a hostname in the configuration. It is recommended that you use a domain name or an IP instead.
                 </Alert>
                 </Alert>
             )}
             )}
 
 

+ 55 - 30
client/src/pages/newInstall/newInstall.jsx

@@ -47,6 +47,8 @@ const debounce = (func, wait) => {
     }
     }
   }, 500)
   }, 500)
 
 
+const hostnameIsDomainReg = /^((?!localhost|\d+\.\d+\.\d+\.\d+)[a-zA-Z0-9\-]{1,63}\.)+[a-zA-Z]{2,63}$/
+
 const NewInstall = () => {
 const NewInstall = () => {
     const [activeStep, setActiveStep] = useState(0);
     const [activeStep, setActiveStep] = useState(0);
     const [status, setStatus] = useState(null);
     const [status, setStatus] = useState(null);
@@ -83,6 +85,29 @@ const NewInstall = () => {
         }
         }
     }, [activeStep, status]);
     }, [activeStep, status]);
 
 
+    const getHTTPSOptions = (hostname) => {
+        if(!hostname) {
+            return [["", "Set your hostname first"]];
+        }
+
+        if(hostname.match(hostnameIsDomainReg)) {
+            return [
+                ["", "Select an option"],
+                ["LETSENCRYPT", "Use Let's Encrypt automatic HTTPS (recommended)"],
+                ["SELFSIGNED", "Generate self-signed certificate"],
+                ["PROVIDED", "Supply my own HTTPS certificate"],
+                ["DISABLED", "Use HTTP only (not recommended)"],
+            ]
+        } else {
+            return [
+                ["", "Select an option"],
+                ["SELFSIGNED", "Generate self-signed certificate (recommended)"],
+                ["PROVIDED", "Supply my own HTTPS certificate"],
+                ["DISABLED", "Use HTTP only (not recommended)"],
+            ] 
+        }
+    }
+
     const steps = [
     const steps = [
         {
         {
             label: 'Welcome! 💖',
             label: 'Welcome! 💖',
@@ -252,26 +277,17 @@ const NewInstall = () => {
             component: (<Stack item xs={12} spacing={2}>
             component: (<Stack item xs={12} spacing={2}>
             <div>
             <div>
                 <QuestionCircleOutlined /> It is recommended to use Let's Encrypt to automatically provide HTTPS Certificates.
                 <QuestionCircleOutlined /> It is recommended to use Let's Encrypt to automatically provide HTTPS Certificates.
-                This requires a valid domain name pointing to this server. If you don't have one, you can use a self-signed certificate. 
+                This requires a valid domain name pointing to this server. If you don't have one, <strong>you can select "Generate self-signed certificate" in the dropdown. </strong> 
                 If you enable HTTPS, it will be effective after the next restart.
                 If you enable HTTPS, it will be effective after the next restart.
             </div>
             </div>
-            <div>
-                {status && <>
-                    <div>
-                        HTTPS Certificate Mode is currently: <b>{status.HTTPSCertificateMode}</b>
-                    </div>
-                    <div>
-                        Hostname is currently: <b>{status.hostname}</b>
-                    </div>
-                </>}
-            </div>
             <div>
             <div>
             <Formik
             <Formik
                 initialValues={{
                 initialValues={{
-                    HTTPSCertificateMode: "LETSENCRYPT",
+                    HTTPSCertificateMode: "",
                     UseWildcardCertificate: false,
                     UseWildcardCertificate: false,
                     DNSChallengeProvider: '',
                     DNSChallengeProvider: '',
                     DNSChallengeConfig: {},
                     DNSChallengeConfig: {},
+                    __success: false,
                 }}
                 }}
                 validationSchema={Yup.object().shape({
                 validationSchema={Yup.object().shape({
                         SSLEmail: Yup.string().when('HTTPSCertificateMode', {
                         SSLEmail: Yup.string().when('HTTPSCertificateMode', {
@@ -289,7 +305,7 @@ const NewInstall = () => {
                         }),
                         }),
                         Hostname: Yup.string().when('HTTPSCertificateMode', {
                         Hostname: Yup.string().when('HTTPSCertificateMode', {
                             is: "LETSENCRYPT",
                             is: "LETSENCRYPT",
-                            then: Yup.string().required().matches(/^((?!localhost|\d+\.\d+\.\d+\.\d+)[a-zA-Z0-9\-]{1,63}\.)+[a-zA-Z]{2,63}$/, 'Let\'s Encrypt only accepts domain names'),
+                            then: Yup.string().required().matches(hostnameIsDomainReg, 'Let\'s Encrypt only accepts domain names'),
                             otherwise: Yup.string().required()
                             otherwise: Yup.string().required()
                         }),
                         }),
                 })}
                 })}
@@ -310,7 +326,9 @@ const NewInstall = () => {
                         if(res.status == "OK") {
                         if(res.status == "OK") {
                             setStatus({ success: true });
                             setStatus({ success: true });
                             setHostname((values.HTTPSCertificateMode == "DISABLED" ? "http://" : "https://") + values.Hostname);
                             setHostname((values.HTTPSCertificateMode == "DISABLED" ? "http://" : "https://") + values.Hostname);
+                            setActiveStep(4);
                         }
                         }
+                        return res;
                     } catch (error) {
                     } catch (error) {
                         setStatus({ success: false });
                         setStatus({ success: false });
                         setErrors({ submit: "Please check you have filled all the inputs properly" });
                         setErrors({ submit: "Please check you have filled all the inputs properly" });
@@ -320,16 +338,31 @@ const NewInstall = () => {
                 {(formik) => (
                 {(formik) => (
                     <form noValidate onSubmit={formik.handleSubmit}>
                     <form noValidate onSubmit={formik.handleSubmit}>
                         <Stack item xs={12} spacing={2}>
                         <Stack item xs={12} spacing={2}>
+                        <CosmosInputText
+                            name="Hostname"
+                            label="Hostname (Domain required for Let's Encrypt)"
+                            placeholder="yourdomain.com, your ip, or localhost"
+                            formik={formik}
+                            onChange={(e) => {
+                              checkHost(e.target.value, setHostError, setHostIp);
+                            }}
+                        />
+                        {formik.values.Hostname && (formik.values.Hostname.match(hostnameIsDomainReg) ? 
+                            <Alert severity="info">
+                                You seem to be using a domain name. <br />
+                                Let's Encrypt can automatically generate a certificate for you.
+                            </Alert>
+                            :
+                            <Alert severity="info">
+                                You seem to be using an IP address or local domain. <br />
+                                You can use automatic Self-Signed certificates.
+                            </Alert>)
+                        }
                         <CosmosSelect
                         <CosmosSelect
                             name="HTTPSCertificateMode"
                             name="HTTPSCertificateMode"
                             label="Select your choice"
                             label="Select your choice"
                             formik={formik}
                             formik={formik}
-                            options={[
-                                ["LETSENCRYPT", "Use Let's Encrypt automatic HTTPS (recommended)"],
-                                ["PROVIDED", "Supply my own HTTPS certificate"],
-                                ["SELFSIGNED", "Generate a self-signed certificate"],
-                                ["DISABLED", "Use HTTP only (not recommended)"],
-                            ]}
+                            options={getHTTPSOptions(formik.values.Hostname && formik.values.Hostname)}
                         />
                         />
                         {formik.values.HTTPSCertificateMode === "LETSENCRYPT" && (
                         {formik.values.HTTPSCertificateMode === "LETSENCRYPT" && (
                             <>
                             <>
@@ -378,15 +411,6 @@ const NewInstall = () => {
                             </>
                             </>
                         )}
                         )}
                         
                         
-                        <CosmosInputText
-                            name="Hostname"
-                            label="Hostname (Domain required for Let's Encrypt)"
-                            placeholder="yourdomain.com, your ip, or localhost"
-                            formik={formik}
-                            onChange={(e) => {
-                              checkHost(e.target.value, setHostError, setHostIp);
-                            }}
-                        />
                         {hostError && <Grid item xs={12}>
                         {hostError && <Grid item xs={12}>
                           <Alert color='error'>{hostError}</Alert>
                           <Alert color='error'>{hostError}</Alert>
                         </Grid>}
                         </Grid>}
@@ -401,11 +425,12 @@ const NewInstall = () => {
                             </Alert>
                             </Alert>
                         )}
                         )}
                         
                         
+                        {(formik.values.HTTPSCertificateMode === "LETSENCRYPT" || formik.values.HTTPSCertificateMode === "SELFSIGNED") && formik.values.Hostname && formik.values.Hostname.match(hostnameIsDomainReg) && (
                         <CosmosCheckbox
                         <CosmosCheckbox
                             label={"Use Wildcard Certificate for *." + (formik.values.Hostname ||  "")}
                             label={"Use Wildcard Certificate for *." + (formik.values.Hostname ||  "")}
                             name="UseWildcardCertificate"
                             name="UseWildcardCertificate"
                             formik={formik}
                             formik={formik}
-                        />
+                        />)}
 
 
                         {formik.errors.submit && (
                         {formik.errors.submit && (
                           <Grid item xs={12}>
                           <Grid item xs={12}>
@@ -432,7 +457,7 @@ const NewInstall = () => {
             </div>
             </div>
             </Stack>),
             </Stack>),
             nextButtonLabel: () => {
             nextButtonLabel: () => {
-                return status ? 'Next' : 'Skip';
+                return (status && status.hostname != '0.0.0.0') ? 'Next' : '';
             }
             }
         },
         },
         {
         {

+ 16 - 7
client/src/pages/servapps/containers/docker-compose.jsx

@@ -32,7 +32,7 @@ import DockerContainerSetup from './setup';
 import whiskers from 'whiskers';
 import whiskers from 'whiskers';
 import {version} from '../../../../../package.json';
 import {version} from '../../../../../package.json';
 import cmp from 'semver-compare';
 import cmp from 'semver-compare';
-import { HostnameChecker } from '../../../utils/routes';
+import { HostnameChecker, getHostnameFromName } from '../../../utils/routes';
 import { CosmosContainerPicker } from '../../config/users/containerPicker';
 import { CosmosContainerPicker } from '../../config/users/containerPicker';
 import { randomString } from '../../../utils/indexs';
 import { randomString } from '../../../utils/indexs';
 
 
@@ -76,10 +76,6 @@ const isNewerVersion = (minver) => {
   return cmp(version, minver) === -1;
   return cmp(version, minver) === -1;
 }
 }
 
 
-const getHostnameFromName = (name) => {
-  return name.replace('/', '').replace(/_/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase().replace(/\s/g, '-') + '.' + window.location.origin.split('://')[1]
-}
-
 const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaultName }) => {
 const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaultName }) => {
   const cleanDefaultName = defaultName && defaultName.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-]/g, '');
   const cleanDefaultName = defaultName && defaultName.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-]/g, '');
   const [step, setStep] = useState(0);
   const [step, setStep] = useState(0);
@@ -94,6 +90,19 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
   const [context, setContext] = useState({});
   const [context, setContext] = useState({});
   const [installer, setInstaller] = useState(installerInit);
   const [installer, setInstaller] = useState(installerInit);
   const [config, setConfig] = useState({});
   const [config, setConfig] = useState({});
+
+  let hostnameErrors = () => {
+    let broken = false;
+    Object.values(hostnames).forEach((service) => {
+      Object.values(service).forEach((route) => {
+        if(!route.host || route.host.match(/\s/g)) {
+          broken = true;
+        }
+      });
+    });
+    return broken;
+  }
+
   const [passwords, setPasswords] = useState([
   const [passwords, setPasswords] = useState([
     randomString(24),
     randomString(24),
     randomString(24),
     randomString(24),
@@ -369,7 +378,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
                 if (route.useHost) {
                 if (route.useHost) {
                   let newRoute = Object.assign({}, route);
                   let newRoute = Object.assign({}, route);
                   if (route.useHost === true) {
                   if (route.useHost === true) {
-                    newRoute.host = (newRoute.hostPrefix || '') + getHostnameFromName(key + (routeId > 0 ? '-' + routeId : '')) + (newRoute.hostSuffix || '');
+                    newRoute.host = getHostnameFromName(key + (routeId > 0 ? '-' + routeId : ''), newRoute, config);
                   }
                   }
                   
                   
                   if(!newHostnames[key]) newHostnames[key] = {};
                   if(!newHostnames[key]) newHostnames[key] = {};
@@ -722,7 +731,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
           setHostnames({});
           setHostnames({});
           setOverrides({});
           setOverrides({});
         }}>Close</Button>
         }}>Close</Button>
-        <Button disabled={!dockerCompose || ymlError} onClick={() => {
+        <Button disabled={!dockerCompose || ymlError || hostnameErrors()} onClick={() => {
           if (step === 0) {
           if (step === 0) {
             setStep(1);
             setStep(1);
           } else {
           } else {

+ 13 - 6
client/src/pages/servapps/containers/newServiceForm.jsx

@@ -4,7 +4,7 @@ import RestartModal from '../../config/users/restart';
 import { Alert, Button, Checkbox, Chip, Divider, Stack, useMediaQuery } from '@mui/material';
 import { Alert, Button, Checkbox, Chip, Divider, Stack, useMediaQuery } from '@mui/material';
 import HostChip from '../../../components/hostChip';
 import HostChip from '../../../components/hostChip';
 import { RouteMode, RouteSecurity } from '../../../components/routeComponents';
 import { RouteMode, RouteSecurity } from '../../../components/routeComponents';
-import { getFaviconURL } from '../../../utils/routes';
+import { getFaviconURL, getHostnameFromName } from '../../../utils/routes';
 import * as API from '../../../api';
 import * as API from '../../../api';
 import { ArrowLeftOutlined, ArrowRightOutlined, CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
 import { ArrowLeftOutlined, ArrowRightOutlined, CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
 import IsLoggedIn from '../../../isLoggedIn';
 import IsLoggedIn from '../../../isLoggedIn';
@@ -20,13 +20,10 @@ import DockerTerminal from './terminal';
 import NewDockerService from './newService';
 import NewDockerService from './newService';
 import RouteManagement from '../../config/routes/routeman';
 import RouteManagement from '../../config/routes/routeman';
 
 
-const getHostnameFromName = (name) => {
-  return name.replace('/', '').replace(/_/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase().replace(/\s/g, '-') + '.' + window.location.origin.split('://')[1]
-}
-
 const NewDockerServiceForm = () => {
 const NewDockerServiceForm = () => {
   const [currentTab, setCurrentTab] = React.useState(0);
   const [currentTab, setCurrentTab] = React.useState(0);
   const [maxTab, setMaxTab] = React.useState(0);
   const [maxTab, setMaxTab] = React.useState(0);
+  const [config, setConfig] = React.useState(null);
   const [containerInfo, setContainerInfo] = React.useState({
   const [containerInfo, setContainerInfo] = React.useState({
     Name: '',
     Name: '',
     Names: [],
     Names: [],
@@ -54,6 +51,16 @@ const NewDockerServiceForm = () => {
       Ports: [],
       Ports: [],
     },
     },
   });
   });
+  
+  const refreshConfig = () => {
+    API.config.get().then((res) => {
+      setConfig(res.data);
+    });
+  };
+
+  React.useEffect(() => {
+    refreshConfig();
+  }, []);
 
 
   let service = {
   let service = {
     services: {
     services: {
@@ -182,7 +189,7 @@ const NewDockerServiceForm = () => {
                 Name: containerInfo.Name.replace('/', ''),
                 Name: containerInfo.Name.replace('/', ''),
                 Description: "Expose " + containerInfo.Name.replace('/', '') + " to the internet",
                 Description: "Expose " + containerInfo.Name.replace('/', '') + " to the internet",
                 UseHost: true,
                 UseHost: true,
-                Host: getHostnameFromName(containerInfo.Name),
+                Host: getHostnameFromName(containerInfo.Name, null, config),
                 UsePathPrefix: false,
                 UsePathPrefix: false,
                 PathPrefix: '',
                 PathPrefix: '',
                 CORSOrigin: '',
                 CORSOrigin: '',

+ 2 - 6
client/src/pages/servapps/exposeModal.jsx

@@ -2,13 +2,9 @@ import React, { useState } from 'react';
 import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack } from '@mui/material';
 import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack } from '@mui/material';
 import { Alert } from '@mui/material';
 import { Alert } from '@mui/material';
 import RouteManagement from '../config/routes/routeman';
 import RouteManagement from '../config/routes/routeman';
-import { ValidateRoute, getFaviconURL, sanitizeRoute, getContainersRoutes } from '../../utils/routes';
+import { ValidateRoute, getFaviconURL, sanitizeRoute, getContainersRoutes, getHostnameFromName } from '../../utils/routes';
 import * as API from '../../api';
 import * as API from '../../api';
 
 
-const getHostnameFromName = (name) => {
-  return name.replace('/', '').replace(/_/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase().replace(/\s/g, '-') + '.' + window.location.origin.split('://')[1]
-}
-
 const ExposeModal = ({ openModal, setOpenModal, config, updateRoutes, container }) => {
 const ExposeModal = ({ openModal, setOpenModal, config, updateRoutes, container }) => {
   const [submitErrors, setSubmitErrors] = useState([]);
   const [submitErrors, setSubmitErrors] = useState([]);
   const [newRoute, setNewRoute] = useState(null);
   const [newRoute, setNewRoute] = useState(null);
@@ -36,7 +32,7 @@ const ExposeModal = ({ openModal, setOpenModal, config, updateRoutes, container
                         Name: containerName.replace('/', ''),
                         Name: containerName.replace('/', ''),
                         Description: "Expose " + containerName.replace('/', '') + " to the internet",
                         Description: "Expose " + containerName.replace('/', '') + " to the internet",
                         UseHost: true,
                         UseHost: true,
-                        Host: getHostnameFromName(containerName),
+                        Host: getHostnameFromName(containerName, null, config),
                         UsePathPrefix: false,
                         UsePathPrefix: false,
                         PathPrefix: '',
                         PathPrefix: '',
                         CORSOrigin: '',
                         CORSOrigin: '',

+ 37 - 1
client/src/utils/routes.jsx

@@ -37,6 +37,10 @@ const addProtocol = (url) => {
   if (url.indexOf("http://") === 0 || url.indexOf("https://") === 0) {
   if (url.indexOf("http://") === 0 || url.indexOf("https://") === 0) {
     return url;
     return url;
   }
   }
+  // get current protocol
+  if (window.location.protocol === "http:") {
+    return "http://" + url;
+  }
   return "https://" + url;
   return "https://" + url;
 }
 }
 
 
@@ -153,4 +157,36 @@ export const HostnameChecker = ({hostname}) => {
 
 
     {hostIp && <Alert color='info'>This hostname is pointing to <strong>{hostIp}</strong>, make sure it is your server IP!</Alert>}
     {hostIp && <Alert color='info'>This hostname is pointing to <strong>{hostIp}</strong>, make sure it is your server IP!</Alert>}
   </>
   </>
-};
+};
+
+const hostnameIsDomainReg = /^((?!localhost|\d+\.\d+\.\d+\.\d+)[a-zA-Z0-9\-]{1,63}\.)+[a-zA-Z]{2,63}$/
+
+export const getHostnameFromName = (name, route, config) => {
+  let origin = window.location.origin.split('://')[1];
+  let protocol = window.location.origin.split('://')[0];
+  let res;
+
+  if(origin.match(hostnameIsDomainReg)) {
+    res = name.replace('/', '').replace(/_/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase().replace(/\s/g, '-') + '.' + origin
+    if(route)
+      res = (route.hostPrefix || '') + res + (route.hostSuffix || '');
+  } else {
+    const existingRoutes = Object.values(config.HTTPConfig.ProxyConfig.Routes);
+    origin = origin.split(':')[0];
+
+    // find first port available in range 7200-7400
+    let port = protocol == "https" ? 7200 : 7351;
+    let endPort = protocol == "https" ? 7350 : 7500;
+    while(port < endPort) {
+      if(!existingRoutes.find((exiroute) => exiroute.Host == (origin + ":" + port))) {
+        res = origin + ":" + port;
+        return res;
+      }
+      else
+        port++;
+      }
+      
+    return "NO MORE PORT AVAILABLE. PLEASE CLEAN YOUR URLS!";
+  }
+  return res;
+}

+ 169 - 0
src/docker/checkPorts.go

@@ -0,0 +1,169 @@
+package docker
+
+import (
+	"github.com/azukaar/cosmos-server/src/utils"
+
+	"os"
+	"net"
+	"strings"
+	"errors"
+	"runtime"
+	mountType "github.com/docker/docker/api/types/mount"
+)
+
+func isPortAvailable(port string) bool {
+	ln, err := net.Listen("tcp", ":"+port)
+	if err != nil {
+		return false
+	}
+
+	ln.Close()
+	return true
+}
+
+func CheckPorts() error {
+	utils.Log("Setup: Checking Docker port mapping ")
+	expectedPorts := []string{}
+	isHTTPS := utils.IsHTTPS
+	config := utils.GetMainConfig()
+	HTTPPort := config.HTTPConfig.HTTPPort
+	HTTPSPort := config.HTTPConfig.HTTPSPort
+	routes := config.HTTPConfig.ProxyConfig.Routes
+	expectedPort := HTTPPort
+	if isHTTPS {
+		expectedPort = HTTPSPort
+	}
+
+	for _, route := range routes {
+		if route.UseHost && strings.Contains(route.Host, ":") {
+			hostname := route.Host
+			port := strings.Split(hostname, ":")[1]
+			expectedPorts = append(expectedPorts, port)
+		}
+	}
+
+	// append hostname port 
+	hostname := config.HTTPConfig.Hostname
+	if strings.Contains(hostname, ":") {
+		hostnameport := strings.Split(hostname, ":")[1]
+		expectedPorts = append(expectedPorts, hostnameport)
+	}
+
+	errD := Connect()
+	if errD != nil {
+		return errD
+	}
+
+	// Get container ID from HOSTNAME environment variable
+	containerID := os.Getenv("HOSTNAME")
+	if containerID == "" {
+		utils.Warn("Not in docker. Skipping port auto-mapping")
+		return nil
+	}
+
+	// Inspect the container
+	inspect, err := DockerClient.ContainerInspect(DockerContext, containerID)
+	if err != nil {
+		return err
+	}
+
+	// Get the ports
+	ports := map[string]struct{}{}
+	for containerPort, hostConfig := range inspect.NetworkSettings.Ports {
+		utils.Debug("Container port: " + containerPort.Port())
+		
+		for _, hostPort := range hostConfig {
+			utils.Debug("Host port: " + hostPort.HostPort)
+			ports[hostPort.HostPort] = struct{}{}
+		}
+	}
+
+	finalPorts := []string{}
+
+	hasChanged := false
+
+	utils.Debug("Expected ports: " + strings.Join(expectedPorts, ", "))
+
+	for _, port := range expectedPorts {
+		utils.Debug("Checking port : " + string(port))
+		if _, ok := ports[port]; !ok {
+			if !isPortAvailable(port) {
+				utils.Error("Port "+port+" is added to a URL but it is not available. Skipping for now", nil)
+			} else {
+				utils.Debug("Port "+port+" is not mapped. Adding it.")
+				finalPorts = append(finalPorts, port + ":" + expectedPort)
+				hasChanged = true
+			}
+		} else {
+			finalPorts = append(finalPorts, port + ":" + expectedPort)
+		}
+	}
+
+	if hasChanged {
+		finalPorts = append(finalPorts, config.HTTPConfig.HTTPPort + ":" + config.HTTPConfig.HTTPPort)
+		finalPorts = append(finalPorts, config.HTTPConfig.HTTPSPort + ":" + config.HTTPConfig.HTTPSPort)
+
+		utils.Log("Port mapping changed. Needs update.")
+		utils.Log("New ports: " + strings.Join(finalPorts, ", "))
+
+		UpdatePorts(finalPorts)
+	}
+
+	return nil
+}
+
+
+func UpdatePorts(finalPorts []string) error {
+	utils.Log("SelUpdatePorts - Starting...")
+
+	if os.Getenv("HOSTNAME") == "" {
+		utils.Error("SelUpdatePorts - not using Docker", nil)
+		return errors.New("SelUpdatePorts - 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",
+		SecurityOpt: []string{
+			"label:disable",
+		},
+		Environment: []string{
+			"CONTAINER_NAME=" + containerName,
+			"ACTION=ports",
+			"DOCKER_HOST=" + os.Getenv("DOCKER_HOST"),
+			"PORTS=" + strings.Join(finalPorts, ","),
+		},
+		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
+}

+ 22 - 13
src/httpServer.go

@@ -28,6 +28,8 @@ var serverPortHTTPS = ""
 func startHTTPServer(router *mux.Router) {
 func startHTTPServer(router *mux.Router) {
 	utils.Log("Listening to HTTP on : 0.0.0.0:" + serverPortHTTP)
 	utils.Log("Listening to HTTP on : 0.0.0.0:" + serverPortHTTP)
 
 
+	docker.CheckPorts()
+
 	err := http.ListenAndServe("0.0.0.0:" + serverPortHTTP, router)
 	err := http.ListenAndServe("0.0.0.0:" + serverPortHTTP, router)
 
 
 	if err != nil {
 	if err != nil {
@@ -40,16 +42,20 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
 
 
 	cfg := simplecert.Default
 	cfg := simplecert.Default
 
 
-	cfg.Domains = utils.GetAllHostnames(true, false)
-	cfg.CacheDir = "/config/certificates"
-	cfg.SSLEmail = config.HTTPConfig.SSLEmail
-	cfg.HTTPAddress = "0.0.0.0:"+serverPortHTTP
-	cfg.TLSAddress = "0.0.0.0:"+serverPortHTTPS
+	if config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"] {
+		cfg.CacheDir = "/config/certificates"
+		cfg.SSLEmail = config.HTTPConfig.SSLEmail
+		cfg.HTTPAddress = "0.0.0.0:"+serverPortHTTP
+		cfg.TLSAddress = "0.0.0.0:"+serverPortHTTPS
 	
 	
-	if config.HTTPConfig.DNSChallengeProvider != "" {
-		utils.Log("Using DNS Challenge with Provider: " + config.HTTPConfig.DNSChallengeProvider)
-		cfg.DNSProvider  = config.HTTPConfig.DNSChallengeProvider
-	}
+		if config.HTTPConfig.DNSChallengeProvider != "" {
+			utils.Log("Using DNS Challenge with Provider: " + config.HTTPConfig.DNSChallengeProvider)
+			cfg.DNSProvider  = config.HTTPConfig.DNSChallengeProvider
+			cfg.Domains = utils.GetAllHostnames(true, false)
+		} else {
+			cfg.Domains = utils.LetsEncryptValidOnly(utils.GetAllHostnames(true, false))
+		}
+	}	
 
 
 	cfg.FailedToRenewCertificate = func(err error) {
 	cfg.FailedToRenewCertificate = func(err error) {
 		utils.Error("Failed to renew certificate", err)
 		utils.Error("Failed to renew certificate", err)
@@ -73,6 +79,10 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
 				return
 				return
 		}
 		}
 	}
 	}
+	
+	utils.IsHTTPS = true
+	// Redirect ports 
+	docker.CheckPorts()
 		
 		
 	// redirect http to https
 	// redirect http to https
 	go (func () {
 	go (func () {
@@ -107,8 +117,6 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
 	utils.Log("Listening to HTTP on :" + serverPortHTTP)
 	utils.Log("Listening to HTTP on :" + serverPortHTTP)
 	utils.Log("Listening to HTTPS on :" + serverPortHTTPS)
 	utils.Log("Listening to HTTPS on :" + serverPortHTTPS)
 
 
-	utils.IsHTTPS = true
-
 	tlsConf := tlsconfig.NewServerTLSConfig(tlsconfig.TLSModeServerStrict)
 	tlsConf := tlsconfig.NewServerTLSConfig(tlsconfig.TLSModeServerStrict)
 
 
 	if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
 	if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
@@ -133,6 +141,7 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
 		DisableGeneralOptionsHandler: true,
 		DisableGeneralOptionsHandler: true,
 	}
 	}
 
 
+
 	// start https server
 	// start https server
 	errServ := server.ListenAndServeTLS("", "")
 	errServ := server.ListenAndServeTLS("", "")
 
 
@@ -197,13 +206,13 @@ func StartServer() {
 	var tlsCert = HTTPConfig.TLSCert
 	var tlsCert = HTTPConfig.TLSCert
 	var tlsKey= HTTPConfig.TLSKey
 	var tlsKey= HTTPConfig.TLSKey
 
 
-	domains := utils.GetAllHostnames(true, true)
+	domains := utils.GetAllHostnames(true, false)
 	oldDomains := baseMainConfig.HTTPConfig.TLSKeyHostsCached
 	oldDomains := baseMainConfig.HTTPConfig.TLSKeyHostsCached
 
 
 	NeedsRefresh := (tlsCert == "" || tlsKey == "") || !utils.StringArrayEquals(domains, oldDomains)
 	NeedsRefresh := (tlsCert == "" || tlsKey == "") || !utils.StringArrayEquals(domains, oldDomains)
 
 
 	if(NeedsRefresh && HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["SELFSIGNED"]) {
 	if(NeedsRefresh && HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["SELFSIGNED"]) {
-		utils.Log("Generating new TLS certificate")
+		utils.Log("Generating new TLS certificate for domains: " + strings.Join(domains, ", "))
 		pub, priv := utils.GenerateRSAWebCertificates(domains)
 		pub, priv := utils.GenerateRSAWebCertificates(domains)
 		
 		
 		baseMainConfig.HTTPConfig.TLSCert = pub
 		baseMainConfig.HTTPConfig.TLSCert = pub

+ 17 - 1
src/utils/utils.go

@@ -307,6 +307,22 @@ func RestartServer() {
 	os.Exit(0)
 	os.Exit(0)
 }
 }
 
 
+func LetsEncryptValidOnly(hostnames []string) []string {
+	wrongPattern := `^(localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|.*\.local)$`
+	re, _ := regexp.Compile(wrongPattern)
+
+	var validDomains []string
+	for _, domain := range hostnames {
+		if !re.MatchString(domain) && !strings.Contains(domain, "*") && !strings.Contains(domain, " ") && !strings.Contains(domain, ",") {
+			validDomains = append(validDomains, domain)
+		} else {
+			Error("Invalid domain found in URLs: " + domain + " it was removed from the certificate to not break Let's Encrypt", nil)
+		}
+	}
+
+	return validDomains
+}
+
 func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
 func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
 	mainHostname := GetMainConfig().HTTPConfig.Hostname
 	mainHostname := GetMainConfig().HTTPConfig.Hostname
 	hostnames := []string{
 	hostnames := []string{
@@ -315,7 +331,7 @@ func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
 
 
 	proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
 	proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
 	for _, proxy := range proxies {
 	for _, proxy := range proxies {
-		if proxy.UseHost && proxy.Host != "" && strings.Contains(proxy.Host, ".") && !strings.Contains(proxy.Host, ",") && !strings.Contains(proxy.Host, " ") {
+		if proxy.UseHost && proxy.Host != "" && !strings.Contains(proxy.Host, ",") && !strings.Contains(proxy.Host, " ") {
 			if removePorts {
 			if removePorts {
 				hostnames = append(hostnames, strings.Split(proxy.Host, ":")[0])
 				hostnames = append(hostnames, strings.Split(proxy.Host, ":")[0])
 			} else {
 			} else {