[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
- 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
- Custmizable homepage / theme colors

View file

@ -337,7 +337,7 @@ const ConfigManagement = () => {
<Grid container spacing={3}>
<Grid item xs={12}>
<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
id="Hostname-login"
type="text"

View file

@ -240,7 +240,7 @@ const HomePage = () => {
{coStatus && coStatus.domain && (
<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>
)}

View file

@ -47,6 +47,8 @@ const debounce = (func, wait) => {
}
}, 500)
const hostnameIsDomainReg = /^((?!localhost|\d+\.\d+\.\d+\.\d+)[a-zA-Z0-9\-]{1,63}\.)+[a-zA-Z]{2,63}$/
const NewInstall = () => {
const [activeStep, setActiveStep] = useState(0);
const [status, setStatus] = useState(null);
@ -83,6 +85,29 @@ const NewInstall = () => {
}
}, [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 = [
{
label: 'Welcome! 💖',
@ -252,26 +277,17 @@ const NewInstall = () => {
component: (<Stack item xs={12} spacing={2}>
<div>
<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.
</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>
<Formik
initialValues={{
HTTPSCertificateMode: "LETSENCRYPT",
HTTPSCertificateMode: "",
UseWildcardCertificate: false,
DNSChallengeProvider: '',
DNSChallengeConfig: {},
__success: false,
}}
validationSchema={Yup.object().shape({
SSLEmail: Yup.string().when('HTTPSCertificateMode', {
@ -289,7 +305,7 @@ const NewInstall = () => {
}),
Hostname: Yup.string().when('HTTPSCertificateMode', {
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()
}),
})}
@ -310,7 +326,9 @@ const NewInstall = () => {
if(res.status == "OK") {
setStatus({ success: true });
setHostname((values.HTTPSCertificateMode == "DISABLED" ? "http://" : "https://") + values.Hostname);
setActiveStep(4);
}
return res;
} catch (error) {
setStatus({ success: false });
setErrors({ submit: "Please check you have filled all the inputs properly" });
@ -320,16 +338,31 @@ const NewInstall = () => {
{(formik) => (
<form noValidate onSubmit={formik.handleSubmit}>
<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
name="HTTPSCertificateMode"
label="Select your choice"
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" && (
<>
@ -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}>
<Alert color='error'>{hostError}</Alert>
</Grid>}
@ -401,11 +425,12 @@ const NewInstall = () => {
</Alert>
)}
{(formik.values.HTTPSCertificateMode === "LETSENCRYPT" || formik.values.HTTPSCertificateMode === "SELFSIGNED") && formik.values.Hostname && formik.values.Hostname.match(hostnameIsDomainReg) && (
<CosmosCheckbox
label={"Use Wildcard Certificate for *." + (formik.values.Hostname || "")}
name="UseWildcardCertificate"
formik={formik}
/>
/>)}
{formik.errors.submit && (
<Grid item xs={12}>
@ -432,7 +457,7 @@ const NewInstall = () => {
</div>
</Stack>),
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 {version} from '../../../../../package.json';
import cmp from 'semver-compare';
import { HostnameChecker } from '../../../utils/routes';
import { HostnameChecker, getHostnameFromName } from '../../../utils/routes';
import { CosmosContainerPicker } from '../../config/users/containerPicker';
import { randomString } from '../../../utils/indexs';
@ -76,10 +76,6 @@ const isNewerVersion = (minver) => {
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 cleanDefaultName = defaultName && defaultName.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-]/g, '');
const [step, setStep] = useState(0);
@ -94,6 +90,19 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
const [context, setContext] = useState({});
const [installer, setInstaller] = useState(installerInit);
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([
randomString(24),
randomString(24),
@ -369,7 +378,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
if (route.useHost) {
let newRoute = Object.assign({}, route);
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] = {};
@ -722,7 +731,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
setHostnames({});
setOverrides({});
}}>Close</Button>
<Button disabled={!dockerCompose || ymlError} onClick={() => {
<Button disabled={!dockerCompose || ymlError || hostnameErrors()} onClick={() => {
if (step === 0) {
setStep(1);
} 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 HostChip from '../../../components/hostChip';
import { RouteMode, RouteSecurity } from '../../../components/routeComponents';
import { getFaviconURL } from '../../../utils/routes';
import { getFaviconURL, getHostnameFromName } from '../../../utils/routes';
import * as API from '../../../api';
import { ArrowLeftOutlined, ArrowRightOutlined, CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
import IsLoggedIn from '../../../isLoggedIn';
@ -20,13 +20,10 @@ import DockerTerminal from './terminal';
import NewDockerService from './newService';
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 [currentTab, setCurrentTab] = React.useState(0);
const [maxTab, setMaxTab] = React.useState(0);
const [config, setConfig] = React.useState(null);
const [containerInfo, setContainerInfo] = React.useState({
Name: '',
Names: [],
@ -55,6 +52,16 @@ const NewDockerServiceForm = () => {
},
});
const refreshConfig = () => {
API.config.get().then((res) => {
setConfig(res.data);
});
};
React.useEffect(() => {
refreshConfig();
}, []);
let service = {
services: {
container_name : {
@ -182,7 +189,7 @@ const NewDockerServiceForm = () => {
Name: containerInfo.Name.replace('/', ''),
Description: "Expose " + containerInfo.Name.replace('/', '') + " to the internet",
UseHost: true,
Host: getHostnameFromName(containerInfo.Name),
Host: getHostnameFromName(containerInfo.Name, null, config),
UsePathPrefix: false,
PathPrefix: '',
CORSOrigin: '',

View file

@ -2,13 +2,9 @@ import React, { useState } from 'react';
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack } from '@mui/material';
import { Alert } from '@mui/material';
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';
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 [submitErrors, setSubmitErrors] = useState([]);
const [newRoute, setNewRoute] = useState(null);
@ -36,7 +32,7 @@ const ExposeModal = ({ openModal, setOpenModal, config, updateRoutes, container
Name: containerName.replace('/', ''),
Description: "Expose " + containerName.replace('/', '') + " to the internet",
UseHost: true,
Host: getHostnameFromName(containerName),
Host: getHostnameFromName(containerName, null, config),
UsePathPrefix: false,
PathPrefix: '',
CORSOrigin: '',

View file

@ -37,6 +37,10 @@ const addProtocol = (url) => {
if (url.indexOf("http://") === 0 || url.indexOf("https://") === 0) {
return url;
}
// get current protocol
if (window.location.protocol === "http:") {
return "http://" + 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>}
</>
};
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) {
utils.Log("Listening to HTTP on : 0.0.0.0:" + serverPortHTTP)
docker.CheckPorts()
err := http.ListenAndServe("0.0.0.0:" + serverPortHTTP, router)
if err != nil {
@ -40,7 +42,7 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
cfg := simplecert.Default
cfg.Domains = utils.GetAllHostnames(true, false)
if config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"] {
cfg.CacheDir = "/config/certificates"
cfg.SSLEmail = config.HTTPConfig.SSLEmail
cfg.HTTPAddress = "0.0.0.0:"+serverPortHTTP
@ -49,6 +51,10 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
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) {
@ -74,6 +80,10 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
}
}
utils.IsHTTPS = true
// Redirect ports
docker.CheckPorts()
// redirect http to https
go (func () {
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 HTTPS on :" + serverPortHTTPS)
utils.IsHTTPS = true
tlsConf := tlsconfig.NewServerTLSConfig(tlsconfig.TLSModeServerStrict)
if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
@ -133,6 +141,7 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
DisableGeneralOptionsHandler: true,
}
// start https server
errServ := server.ListenAndServeTLS("", "")
@ -197,13 +206,13 @@ func StartServer() {
var tlsCert = HTTPConfig.TLSCert
var tlsKey= HTTPConfig.TLSKey
domains := utils.GetAllHostnames(true, true)
domains := utils.GetAllHostnames(true, false)
oldDomains := baseMainConfig.HTTPConfig.TLSKeyHostsCached
NeedsRefresh := (tlsCert == "" || tlsKey == "") || !utils.StringArrayEquals(domains, oldDomains)
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)
baseMainConfig.HTTPConfig.TLSCert = pub

View file

@ -307,6 +307,22 @@ func RestartServer() {
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 {
mainHostname := GetMainConfig().HTTPConfig.Hostname
hostnames := []string{
@ -315,7 +331,7 @@ func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
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 {
hostnames = append(hostnames, strings.Split(proxy.Host, ":")[0])
} else {