[release] v0.8.3

This commit is contained in:
Yann Stepienik 2023-06-23 15:29:54 +01:00
parent e874e53cdb
commit a4c7eded55
11 changed files with 340 additions and 69 deletions

View file

@ -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 - Add installer option for hostname prefix/suffix
- Fix issue with inconsistent password when installing from the market - 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

View file

@ -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"

View file

@ -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>
)} )}

View file

@ -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={[ options={getHTTPSOptions(formik.values.Hostname && formik.values.Hostname)}
["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)"],
]}
/> />
{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' : '';
} }
}, },
{ {

View file

@ -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 {

View file

@ -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: '',

View file

@ -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: '',

View file

@ -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
src/docker/checkPorts.go Normal file
View file

@ -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
}

View file

@ -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) if config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"] {
cfg.CacheDir = "/config/certificates" cfg.CacheDir = "/config/certificates"
cfg.SSLEmail = config.HTTPConfig.SSLEmail cfg.SSLEmail = config.HTTPConfig.SSLEmail
cfg.HTTPAddress = "0.0.0.0:"+serverPortHTTP cfg.HTTPAddress = "0.0.0.0:"+serverPortHTTP
cfg.TLSAddress = "0.0.0.0:"+serverPortHTTPS cfg.TLSAddress = "0.0.0.0:"+serverPortHTTPS
if config.HTTPConfig.DNSChallengeProvider != "" { if config.HTTPConfig.DNSChallengeProvider != "" {
utils.Log("Using DNS Challenge with Provider: " + config.HTTPConfig.DNSChallengeProvider) utils.Log("Using DNS Challenge with Provider: " + config.HTTPConfig.DNSChallengeProvider)
cfg.DNSProvider = 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

View file

@ -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 {