[release] v0.8.3
This commit is contained in:
parent
e874e53cdb
commit
a4c7eded55
10
changelog.md
10
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
|
- 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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -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' : '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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: [],
|
||||||
|
@ -55,6 +52,16 @@ const NewDockerServiceForm = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const refreshConfig = () => {
|
||||||
|
API.config.get().then((res) => {
|
||||||
|
setConfig(res.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
refreshConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
let service = {
|
let service = {
|
||||||
services: {
|
services: {
|
||||||
container_name : {
|
container_name : {
|
||||||
|
@ -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,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,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,3 +158,35 @@ 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
169
src/docker/checkPorts.go
Normal 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
|
||||||
|
}
|
|
@ -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,7 +42,7 @@ 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
|
||||||
|
@ -49,6 +51,10 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
|
||||||
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) {
|
||||||
|
@ -74,6 +80,10 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils.IsHTTPS = true
|
||||||
|
// Redirect ports
|
||||||
|
docker.CheckPorts()
|
||||||
|
|
||||||
// redirect http to https
|
// redirect http to https
|
||||||
go (func () {
|
go (func () {
|
||||||
httpRouter := mux.NewRouter()
|
httpRouter := mux.NewRouter()
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue