Browse Source

Update to 0.7.0-unstable5

Yann Stepienik 2 years ago
parent
commit
aa9a72b787

+ 7 - 0
changelog.md

@@ -1,3 +1,10 @@
+## Version 0.7.0
+ - Add Cosmos Market
+ - Fix issue with docker compose timeout healthcheck as string, and inverted ports
+
+## Version 0.6.0
+// todo
+
 ## Version 0.6.0
 ## Version 0.6.0
 - OpenID support!
 - OpenID support!
 - Add hostname check when adding new routes to Cosmos
 - Add hostname check when adding new routes to Cosmos

+ 14 - 0
client/src/index.css

@@ -73,4 +73,18 @@
 
 
 .loading-image {
 .loading-image {
   background: url('/assets/images/icons/cosmos_gray.png') no-repeat center center;
   background: url('/assets/images/icons/cosmos_gray.png') no-repeat center center;
+}
+
+.raw-table table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.raw-table table th {
+  text-align: left;
+}
+
+.raw-table table th, .raw-table table td {
+  padding: 5px;
+  border: 1px solid #ccc;
 }
 }

+ 1 - 1
client/src/pages/config/routes/routeman.jsx

@@ -183,7 +183,7 @@ const RouteManagement = ({ routeConfig, routeNames, TargetContainer, noControls
                       : <CosmosInputText
                       : <CosmosInputText
                         name="Target"
                         name="Target"
                         label={formik.values.Mode == "PROXY" ? "Target URL" : "Target Folder Path"}
                         label={formik.values.Mode == "PROXY" ? "Target URL" : "Target Folder Path"}
-                        placeholder={formik.values.Mode == "PROXY" ? "localhost:8080" : "/path/to/my/app"}
+                        placeholder={formik.values.Mode == "PROXY" ? "http://localhost:8080" : "/path/to/my/app"}
                         formik={formik}
                         formik={formik}
                       />
                       />
                   }
                   }

+ 18 - 14
client/src/pages/config/users/configman.jsx

@@ -30,6 +30,7 @@ import RestartModal from './restart';
 import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
 import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
 import { CosmosCheckbox, CosmosFormDivider, CosmosInputPassword, CosmosInputText, CosmosSelect } from './formShortcuts';
 import { CosmosCheckbox, CosmosFormDivider, CosmosInputPassword, CosmosInputText, CosmosSelect } from './formShortcuts';
 import CountrySelect, { countries } from '../../../components/countrySelect';
 import CountrySelect, { countries } from '../../../components/countrySelect';
+import { DnsChallengeComp } from '../../../utils/dns-challenge-comp';
 
 
 
 
 const ConfigManagement = () => {
 const ConfigManagement = () => {
@@ -70,6 +71,7 @@ const ConfigManagement = () => {
           UseWildcardCertificate: config.HTTPConfig.UseWildcardCertificate,
           UseWildcardCertificate: config.HTTPConfig.UseWildcardCertificate,
           HTTPSCertificateMode: config.HTTPConfig.HTTPSCertificateMode,
           HTTPSCertificateMode: config.HTTPConfig.HTTPSCertificateMode,
           DNSChallengeProvider: config.HTTPConfig.DNSChallengeProvider,
           DNSChallengeProvider: config.HTTPConfig.DNSChallengeProvider,
+          DNSChallengeConfig: config.HTTPConfig.DNSChallengeConfig,
 
 
           Email_Enabled: config.EmailConfig.Enabled,
           Email_Enabled: config.EmailConfig.Enabled,
           Email_Host: config.EmailConfig.Host,
           Email_Host: config.EmailConfig.Host,
@@ -103,6 +105,7 @@ const ConfigManagement = () => {
                 UseWildcardCertificate: values.UseWildcardCertificate,
                 UseWildcardCertificate: values.UseWildcardCertificate,
                 HTTPSCertificateMode: values.HTTPSCertificateMode,
                 HTTPSCertificateMode: values.HTTPSCertificateMode,
                 DNSChallengeProvider: values.DNSChallengeProvider,
                 DNSChallengeProvider: values.DNSChallengeProvider,
+                DNSChallengeConfig: values.DNSChallengeConfig,
               },
               },
               EmailConfig: {
               EmailConfig: {
                 ...config.EmailConfig,
                 ...config.EmailConfig,
@@ -371,6 +374,18 @@ const ConfigManagement = () => {
                     <Alert severity="info">For security reasons, It is not possible to remotely change the Private keys of any certificates on your instance. It is advised to manually edit the config file, or better, use Environment Variables to store them.</Alert>
                     <Alert severity="info">For security reasons, It is not possible to remotely change the Private keys of any certificates on your instance. It is advised to manually edit the config file, or better, use Environment Variables to store them.</Alert>
                   </Grid>
                   </Grid>
 
 
+                  <Grid item xs={12}>
+                    <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
+                      <Field
+                        type="checkbox"
+                        name="GenerateMissingAuthCert"
+                        as={FormControlLabel}
+                        control={<Checkbox size="large" />}
+                        label="Generate missing Authentication Certificates automatically (Default: true)"
+                      />
+                    </Stack>
+                  </Grid>
+
                   <CosmosSelect
                   <CosmosSelect
                     name="HTTPSCertificateMode"
                     name="HTTPSCertificateMode"
                     label="HTTPS Certificates"
                     label="HTTPS Certificates"
@@ -400,26 +415,15 @@ const ConfigManagement = () => {
                   
                   
                   {
                   {
                     formik.values.HTTPSCertificateMode === "LETSENCRYPT" && (
                     formik.values.HTTPSCertificateMode === "LETSENCRYPT" && (
-                      <CosmosInputText
+                      <DnsChallengeComp 
+                        label="Pick a DNS provider (if you are using a DNS Challenge, otherwise leave empty)"
                         name="DNSChallengeProvider"
                         name="DNSChallengeProvider"
-                        label="DNS provider (if you are using a DNS Challenge)"
+                        configName="DNSChallengeConfig"
                         formik={formik}
                         formik={formik}
                       />
                       />
                     )
                     )
                   }
                   }
 
 
-                  <Grid item xs={12}>
-                    <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
-                      <Field
-                        type="checkbox"
-                        name="GenerateMissingAuthCert"
-                        as={FormControlLabel}
-                        control={<Checkbox size="large" />}
-                        label="Generate missing Authentication Certificates automatically (Default: true)"
-                      />
-                    </Stack>
-                  </Grid>
-
                   <Grid item xs={12}>
                   <Grid item xs={12}>
                     <h4>Authentication Public Key</h4>
                     <h4>Authentication Public Key</h4>
                     <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
                     <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>

+ 2 - 3
client/src/pages/config/users/containerPicker.jsx

@@ -33,7 +33,7 @@ import defaultport from '../../servapps/defaultport.json';
 
 
 import * as API  from '../../../api';
 import * as API  from '../../../api';
 
 
-export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetContainer, onTargetChange}) {
+export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetContainer, onTargetChange, label = "Container Name", name = "Target"}) {
   const [open, setOpen] = React.useState(false);
   const [open, setOpen] = React.useState(false);
   const [containers, setContainers] = React.useState([]);
   const [containers, setContainers] = React.useState([]);
   const [hasPublicPorts, setHasPublicPorts] = React.useState(false);
   const [hasPublicPorts, setHasPublicPorts] = React.useState(false);
@@ -42,8 +42,6 @@ export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetConta
   const [portsOptions, setPortsOptions] = React.useState(null);
   const [portsOptions, setPortsOptions] = React.useState(null);
   const loading = options === null;
   const loading = options === null;
 
 
-  const name = "Target"
-  const label = "Container Name"
   let targetResult = {
   let targetResult = {
     container: 'null',
     container: 'null',
     port: "",
     port: "",
@@ -51,6 +49,7 @@ export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetConta
   }
   }
 
 
   let preview = formik.values[name];
   let preview = formik.values[name];
+  console.log(name)
 
 
   if(preview) {
   if(preview) {
     let protocols = preview.split("://")
     let protocols = preview.split("://")

+ 3 - 5
client/src/pages/market/listing.jsx

@@ -151,8 +151,8 @@ const MarketPage = () => {
         height: '100%',
         height: '100%',
       }}></Link>
       }}></Link>
 
 
-      <Stack direction="row" spacing={2} style={{ height: '100%', overflow: 'hidden' }} justifyContent="flex-end">
-        <Stack direction="column" spacing={3} style={{ height: '100%' }} sx={{
+      <Stack direction="row" spacing={2} style={{ height: '100%'}} justifyContent="flex-end">
+        <Stack direction="column" spacing={3} style={{ height: '100%', overflow: "auto"}} sx={{
           backgroundColor: isDark ? '#1A2027' : '#fff',
           backgroundColor: isDark ? '#1A2027' : '#fff',
           padding: '80px 80px',
           padding: '80px 80px',
           width: '100%',
           width: '100%',
@@ -193,9 +193,7 @@ const MarketPage = () => {
             <div><strong>compose:</strong> <LinkMUI href={openedApp.compose}>{openedApp.compose}</LinkMUI></div>
             <div><strong>compose:</strong> <LinkMUI href={openedApp.compose}>{openedApp.compose}</LinkMUI></div>
           </div>  
           </div>  
 
 
-          <div dangerouslySetInnerHTML={{ __html: openedApp.longDescription }} style={{
-            overflow: 'hidden',
-          }}></div>
+          <div dangerouslySetInnerHTML={{ __html: openedApp.longDescription }}></div>
 
 
           <div>
           <div>
           <DockerComposeImport installerInit defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />
           <DockerComposeImport installerInit defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />

+ 6 - 3
client/src/pages/newInstall/newInstall.jsx

@@ -20,6 +20,7 @@ import AnimateButton from '../../components/@extended/AnimateButton';
 import { Box } from '@mui/system';
 import { Box } from '@mui/system';
 import { pull } from 'lodash';
 import { pull } from 'lodash';
 import { isDomain } from '../../utils/indexs';
 import { isDomain } from '../../utils/indexs';
+import { DnsChallengeComp } from '../../utils/dns-challenge-comp';
 // ================================|| LOGIN ||================================ //
 // ================================|| LOGIN ||================================ //
 
 
 const debounce = (func, wait) => {
 const debounce = (func, wait) => {
@@ -272,6 +273,7 @@ const NewInstall = () => {
                     HTTPSCertificateMode: "LETSENCRYPT",
                     HTTPSCertificateMode: "LETSENCRYPT",
                     UseWildcardCertificate: false,
                     UseWildcardCertificate: false,
                     DNSChallengeProvider: '',
                     DNSChallengeProvider: '',
+                    DNSChallengeConfig: {},
                 }}
                 }}
                 validationSchema={Yup.object().shape({
                 validationSchema={Yup.object().shape({
                         SSLEmail: Yup.string().when('HTTPSCertificateMode', {
                         SSLEmail: Yup.string().when('HTTPSCertificateMode', {
@@ -305,6 +307,7 @@ const NewInstall = () => {
                             TLSCert: values.HTTPSCertificateMode === "PROVIDED" ? values.TLSCert : '',
                             TLSCert: values.HTTPSCertificateMode === "PROVIDED" ? values.TLSCert : '',
                             Hostname: values.Hostname,
                             Hostname: values.Hostname,
                             DNSChallengeProvider: values.DNSChallengeProvider,
                             DNSChallengeProvider: values.DNSChallengeProvider,
+                            DNSChallengeConfig: values.DNSChallengeConfig,
                         });
                         });
                         if(res.status == "OK") {
                         if(res.status == "OK") {
                             setStatus({ success: true });
                             setStatus({ success: true });
@@ -350,10 +353,10 @@ const NewInstall = () => {
                                     Cosmos after this installer. See doc here: <a target="_blank" href="https://go-acme.github.io/lego/dns/">https://go-acme.github.io/lego/dns/</a>
                                     Cosmos after this installer. See doc here: <a target="_blank" href="https://go-acme.github.io/lego/dns/">https://go-acme.github.io/lego/dns/</a>
                                 </Alert>
                                 </Alert>
                             )}
                             )}
-                            <CosmosInputText
-                                label={"DNS Provider (only set if you want to use the DNS challenge)"}
+                            <DnsChallengeComp 
+                                label="Pick a DNS provider (if you are using a DNS Challenge, otherwise leave empty)"
                                 name="DNSChallengeProvider"
                                 name="DNSChallengeProvider"
-                                placeholder={"provider"}
+                                configName="DNSChallengeConfig"
                                 formik={formik}
                                 formik={formik}
                             />
                             />
                             </>
                             </>

+ 114 - 30
client/src/pages/servapps/containers/docker-compose.jsx

@@ -26,10 +26,14 @@ import ResponsiveButton from '../../../components/responseiveButton';
 import UploadButtons from '../../../components/fileUpload';
 import UploadButtons from '../../../components/fileUpload';
 import NewDockerService from './newService';
 import NewDockerService from './newService';
 import yaml from 'js-yaml';
 import yaml from 'js-yaml';
-import { CosmosCollapse, CosmosFormDivider, CosmosInputText } from '../../config/users/formShortcuts';
+import { CosmosCollapse, CosmosFormDivider, CosmosInputPassword, CosmosInputText } from '../../config/users/formShortcuts';
 import VolumeContainerSetup from './volumes';
 import VolumeContainerSetup from './volumes';
 import DockerContainerSetup from './setup';
 import DockerContainerSetup from './setup';
 import whiskers from 'whiskers';
 import whiskers from 'whiskers';
+import {version} from '../../../../../package.json';
+import cmp from 'semver-compare';
+import { HostnameChecker } from '../../../utils/routes';
+import { CosmosContainerPicker } from '../../config/users/containerPicker';
 
 
 function checkIsOnline() {
 function checkIsOnline() {
   API.isOnline().then((res) => {
   API.isOnline().then((res) => {
@@ -67,8 +71,8 @@ const preStyle = {
   marginRight: '0',
   marginRight: '0',
 }
 }
 
 
-const isNewerVersion = (v1, v2) => {
-  return false;
+const isNewerVersion = (minver) => {
+  return cmp(version, minver) === -1;
 }
 }
 
 
 const getHostnameFromName = (name) => {
 const getHostnameFromName = (name) => {
@@ -224,6 +228,11 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
             if (!doc.services[key].container_name) {
             if (!doc.services[key].container_name) {
               doc.services[key].container_name = key;
               doc.services[key].container_name = key;
             }
             }
+
+            // convert healthcheck
+            if (doc.services[key].healthcheck && typeof doc.services[key].healthcheck.timeout === 'string') {
+              doc.services[key].healthcheck.timeout = parseInt(doc.services[key].healthcheck.timeout);
+            }
           });
           });
         }
         }
 
 
@@ -293,7 +302,8 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
 
 
     try {
     try {
       if (installer) {
       if (installer) {
-        const rendered = whiskers.render(dockerCompose, {
+        console.log(hostnames)
+        const rendered = whiskers.render(dockerCompose.replace(/{StaticServiceName}/ig, serviceName), {
           ServiceName: serviceName,
           ServiceName: serviceName,
           Hostnames: hostnames,
           Hostnames: hostnames,
           Context: context,
           Context: context,
@@ -333,13 +343,34 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
 
 
           let newVolumes = [];
           let newVolumes = [];
 
 
+          // SET DEFAULT CONTEXT
+          let newContext = {};
+          if (jsoned['cosmos-installer'] && jsoned['cosmos-installer'].form) {
+            jsoned['cosmos-installer'].form.forEach((field) => {
+              [field.name, field['name-container']].forEach((fieldName) => {
+                if(typeof context[fieldName] === "undefined" && typeof field.initialValue !== "undefined") {
+                  newContext[fieldName] = field.initialValue;
+                } else if (typeof context[fieldName] !== "undefined") {
+                  newContext[fieldName] = context[fieldName];
+                }
+              });
+            });
+          }
+          
+          if(JSON.stringify(Object.keys(newContext)) !== JSON.stringify(Object.keys(context)))
+            setContext({...newContext});
+
           Object.keys(jsoned.services).forEach((key) => {
           Object.keys(jsoned.services).forEach((key) => {
             // APPLY OVERRIDE
             // APPLY OVERRIDE
             if (overrides[key]) {
             if (overrides[key]) {
               // prevent customizing static volumes
               // prevent customizing static volumes
               if (jsoned.services[key].volumes && jsoned['cosmos-installer'] && jsoned['cosmos-installer']['frozen-volumes']) {
               if (jsoned.services[key].volumes && jsoned['cosmos-installer'] && jsoned['cosmos-installer']['frozen-volumes']) {
-                jsoned['cosmos-installer']['frozen-volumes'].forEach((volume) => {
-                  delete overrides[key].volumes;
+                jsoned['cosmos-installer']['frozen-volumes'].forEach((volumeName) => {
+                  const keyVolume = overrides[key].volumes.findIndex((v) => {
+                    console.log(v)
+                    return v.source === volumeName;
+                  });
+                  delete overrides[key].volumes[keyVolume];
                 });
                 });
               }
               }
 
 
@@ -363,7 +394,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
             // CREATE NEW VOLUMES
             // CREATE NEW VOLUMES
             if (jsoned.services[key].volumes) {
             if (jsoned.services[key].volumes) {
               jsoned.services[key].volumes.forEach((volume) => {
               jsoned.services[key].volumes.forEach((volume) => {
-                if (typeof volume === 'object' && !volume.existing) {
+                if (typeof volume === 'object' && !volume.source.startsWith('/') && !volume.existing) {
                   newVolumes.push(volume);
                   newVolumes.push(volume);
                 } else if (typeof volume === 'object' && volume.existing) {
                 } else if (typeof volume === 'object' && volume.existing) {
                   delete volume.existing;
                   delete volume.existing;
@@ -396,6 +427,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
     setYmlError(null);
     setYmlError(null);
     setOverrides({});
     setOverrides({});
     setHostnames({});
     setHostnames({});
+    setContext({});
     setDockerCompose('');
     setDockerCompose('');
     setInstaller(installerInit);
     setInstaller(installerInit);
     setServiceName(defaultName || 'default-name');
     setServiceName(defaultName || 'default-name');
@@ -452,15 +484,61 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
               <TextField label="Service Name" value={serviceName} onChange={(e) => setServiceName(e.target.value)} />
               <TextField label="Service Name" value={serviceName} onChange={(e) => setServiceName(e.target.value)} />
 
 
               {service['cosmos-installer'] && service['cosmos-installer'].form.map((formElement) => {
               {service['cosmos-installer'] && service['cosmos-installer'].form.map((formElement) => {
-                return formElement.type === 'checkbox' ? <FormControlLabel
-                  control={<Checkbox checked={context[formElement.name] || formElement.initialValue} onChange={(e) => {
+                return formElement.type === 'checkbox' ?
+                <FormControlLabel
+                  control={<Checkbox checked={context[formElement.name]} onChange={(e) => {
                     setContext({ ...context, [formElement.name]: e.target.checked });
                     setContext({ ...context, [formElement.name]: e.target.checked });
                   }
                   }
                   } />}
                   } />}
                   label={formElement.label}
                   label={formElement.label}
-                /> : <TextField
+                /> : (formElement.type === 'password' || formElement.type === 'email') ?
+                  <TextField
                   label={formElement.label}
                   label={formElement.label}
-                  value={context[formElement.name] || formElement.initialValue}
+                  value={context[formElement.name]}
+                  type={formElement.type}
+                  onChange={(e) => {
+                    setContext({ ...context, [formElement.name]: e.target.value });
+                  }
+                  } />
+                : formElement.type === 'hostname' ? 
+                  <>
+                    <TextField
+                      label={formElement.label}
+                      value={context[formElement.name]}
+                      onChange={(e) => {
+                        setContext({ ...context, [formElement.name]: e.target.value });
+                      }
+                      } />
+                    <HostnameChecker hostname={context[formElement.name]} />
+                  </>
+                : formElement.type === 'container' || formElement.type === 'container-full' ? 
+                  <CosmosContainerPicker
+                      name={formElement.name} 
+                      formik={{
+                        values: {
+                          [formElement.name] : context[formElement.name]
+                        },
+                        errors: {},
+                        setFieldValue: (name, value) => {
+                          setContext({ ...context, [formElement.name]: value });
+                        },
+                      }}
+                      nameOnly={formElement.type === 'container'}
+                      label={formElement.label}
+                      onTargetChange={(_, name) => {
+                        console.log(formElement['name-container'], name)
+                        console.log(context)
+                        setContext({ ...context, [formElement['name-container']]: name });
+                      }}
+                  />
+                : formElement.type === 'error' || formElement.type === 'info' || formElement.type === 'warning' ?
+                  <Alert severity={formElement.type}>
+                    {formElement.label}
+                  </Alert>
+                
+                : <TextField
+                  label={formElement.label}
+                  value={context[formElement.name]}
                   onChange={(e) => {
                   onChange={(e) => {
                     setContext({ ...context, [formElement.name]: e.target.value });
                     setContext({ ...context, [formElement.name]: e.target.value });
                   }
                   }
@@ -479,6 +557,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
                       hostnames[serviceIndex][hostname.name].host = e.target.value;
                       hostnames[serviceIndex][hostname.name].host = e.target.value;
                       setHostnames({...hostnames});
                       setHostnames({...hostnames});
                     }} />
                     }} />
+                    <HostnameChecker hostname={hostname.host} />
                   </>
                   </>
                 })
                 })
               })}
               })}
@@ -518,7 +597,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
                       containerInfo={{
                       containerInfo={{
                         HostConfig: {
                         HostConfig: {
                           Binds: [],
                           Binds: [],
-                          Mounts: Object.keys(value.volumes).map(k => {
+                          Mounts: value.volumes && Object.keys(value.volumes).map(k => {
                             return {
                             return {
                               Type: value.volumes[k].type || (k.startsWith('/') ? 'bind' : 'volume'),
                               Type: value.volumes[k].type || (k.startsWith('/') ? 'bind' : 'volume'),
                               Source: value.volumes[k].source || "",
                               Source: value.volumes[k].source || "",
@@ -560,14 +639,23 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
           </Stack>}
           </Stack>}
         </DialogContentText>
         </DialogContentText>
       </DialogContent>
       </DialogContent>
-      {!isLoading && <DialogActions>
+      {(installerInit && service.minVersion && isNewerVersion(service.minVersion)) ?
+      <Alert severity="error" icon={<WarningOutlined />}>
+        This service requires a newer version of Cosmos. Please update Cosmos to install this service.
+      </Alert>
+      : 
+      (!isLoading && <DialogActions>
         <Button onClick={() => {
         <Button onClick={() => {
           setOpenModal(false);
           setOpenModal(false);
           setStep(0);
           setStep(0);
           setDockerCompose('');
           setDockerCompose('');
           setYmlError('');
           setYmlError('');
           setInstaller(false);
           setInstaller(false);
-        }}>Cancel</Button>
+          setServiceName('');
+          setContext({});
+          setHostnames({});
+          setOverrides({});
+        }}>Close</Button>
         <Button disabled={!dockerCompose || ymlError} onClick={() => {
         <Button disabled={!dockerCompose || ymlError} onClick={() => {
           if (step === 0) {
           if (step === 0) {
             setStep(1);
             setStep(1);
@@ -578,24 +666,20 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
           {step === 0 && 'Next'}
           {step === 0 && 'Next'}
           {step === 1 && 'Back'}
           {step === 1 && 'Back'}
         </Button>
         </Button>
-      </DialogActions>}
+      </DialogActions>)}
     </Dialog>
     </Dialog>
 
 
-    {(installerInit && service.minVersion && isNewerVersion(service.minVersion)) ?
-      <Alert severity="error" icon={<WarningOutlined />}>
-        This service requires a newer version of Cosmos. Please update Cosmos to install this service.
-      </Alert>
-      : <ResponsiveButton
-        color="primary"
-        onClick={() => {
-          openModalFunc();
-        }}
-        variant={(installerInit ? "contained" : "outlined")}
-        startIcon={(installerInit ? <ArrowDownOutlined /> : <ArrowUpOutlined />)}
-      >
-        {installerInit ? 'Install' : 'Import Compose File'}
-      </ResponsiveButton>
-    }
+    <ResponsiveButton
+      color="primary"
+      onClick={() => {
+        openModalFunc();
+      }}
+      variant={(installerInit ? "contained" : "outlined")}
+      startIcon={(installerInit ? <ArrowDownOutlined /> : <ArrowUpOutlined />)}
+    >
+      {installerInit ? 'Install' : 'Import Compose File'}
+    </ResponsiveButton>
+    
   </>;
   </>;
 };
 };
 
 

+ 6 - 6
client/src/pages/servapps/containers/network.jsx

@@ -148,24 +148,24 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O
                       <Grid container key={idx}>
                       <Grid container key={idx}>
                         <Grid item xs={4} style={{ padding }}>
                         <Grid item xs={4} style={{ padding }}>
                           <TextField
                           <TextField
-                            label="Container Port"
                             fullWidth
                             fullWidth
-                            value={port.port}
+                            label="Host port"
+                            value={'' + port.hostPort}
                             onChange={(e) => {
                             onChange={(e) => {
                               const newports = [...formik.values.ports];
                               const newports = [...formik.values.ports];
-                              newports[idx].port = e.target.value;
+                              newports[idx].hostPort = e.target.value;
                               formik.setFieldValue('ports', newports);
                               formik.setFieldValue('ports', newports);
                             }}
                             }}
                           />
                           />
                         </Grid>
                         </Grid>
                         <Grid item xs={4} style={{ padding }}>
                         <Grid item xs={4} style={{ padding }}>
                           <TextField
                           <TextField
+                            label="Container Port"
                             fullWidth
                             fullWidth
-                            label="Host port"
-                            value={'' + port.hostPort}
+                            value={port.port}
                             onChange={(e) => {
                             onChange={(e) => {
                               const newports = [...formik.values.ports];
                               const newports = [...formik.values.ports];
-                              newports[idx].hostPort = e.target.value;
+                              newports[idx].port = e.target.value;
                               formik.setFieldValue('ports', newports);
                               formik.setFieldValue('ports', newports);
                             }}
                             }}
                           />
                           />

+ 5 - 0
client/src/pages/servapps/containers/newService.jsx

@@ -60,6 +60,8 @@ const NewDockerService = ({service, refresh}) => {
   const [openModal, setOpenModal] = React.useState(false);
   const [openModal, setOpenModal] = React.useState(false);
   const preRef = React.useRef(null);
   const preRef = React.useRef(null);
 
 
+  const installer = {...service['cosmos-installer']};
+  service = {...service};
   delete service['cosmos-installer'];
   delete service['cosmos-installer'];
 
 
   React.useEffect(() => {
   React.useEffect(() => {
@@ -99,6 +101,9 @@ const NewDockerService = ({service, refresh}) => {
       >Create</LoadingButton>}
       >Create</LoadingButton>}
       {isDone && <Stack spacing={1}>
       {isDone && <Stack spacing={1}>
         <Alert severity="success">Service Created!</Alert>
         <Alert severity="success">Service Created!</Alert>
+        {installer && installer['post-install'] && installer['post-install'].map(m =>{
+          return <Alert severity={m.type}>{m.label}</Alert>
+        })}
         {needsRestart && <Alert severity="warning">Cosmos needs to be restarted to apply changes to the URLs</Alert>}
         {needsRestart && <Alert severity="warning">Cosmos needs to be restarted to apply changes to the URLs</Alert>}
         {needsRestart &&
         {needsRestart &&
           <Button
           <Button

+ 84 - 0
client/src/utils/dns-challenge-comp.jsx

@@ -0,0 +1,84 @@
+import dnsList from './dns-list.json';
+import dnsConfig from './dns-config.json';
+
+  import * as React from 'react';
+  import {
+    Checkbox,
+    Divider,
+    FormControlLabel,
+    Grid,
+    InputLabel,
+    OutlinedInput,
+    Stack,
+    Typography,
+    FormHelperText,
+    TextField,
+    MenuItem,
+    AccordionSummary,
+    AccordionDetails,
+    Accordion,
+    Chip,
+    Box,
+    FormControl,
+    IconButton,
+    InputAdornment,
+    Alert,
+  
+  } from '@mui/material';
+  import { Field } from 'formik';
+  import { DownOutlined, UpOutlined } from '@ant-design/icons';
+
+  import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
+import { useState } from 'react';
+import { CosmosCollapse, CosmosSelect } from '../pages/config/users/formShortcuts';
+  
+
+export const DnsChallengeComp = ({ name, configName, style, multiline, type, placeholder, onChange, label, formik }) => {
+    const [dnsSetConfig, setDnsSetConfig] = useState({});
+
+    return <><CosmosSelect
+      name={name}
+      label={label}
+      formik={formik}
+      onChange={(e) => {
+        onChange && onChange(e);
+      }}
+      options={dnsList.map((dns) => ([dns,dns]))}
+    />
+
+      <Grid item xs={12}>
+        <Stack spacing={2}>
+        {formik.values[name] && dnsConfig[formik.values[name]] &&<>
+          {dnsConfig[formik.values[name]].vars.length > 0 && <CosmosCollapse title="DNS Challenge setup">
+            
+          <Stack spacing={2}>
+          <Alert severity="info">
+            Please be careful you are filling the correct values. Check the doc if unsure. Leave blank unused variables. <br />
+            Doc link: <a href={dnsConfig[formik.values[name]].url} target="_blank">{dnsConfig[formik.values[name]].url}</a>
+          </Alert>
+          <div className="raw-table">
+            <div dangerouslySetInnerHTML={{__html: dnsConfig[formik.values[name]].docs}}></div>
+          </div>
+          {dnsConfig[formik.values[name]].vars.map((dnsVar) => <div>
+            {dnsVar}:
+              <OutlinedInput
+                type={type ? type : 'text'}
+                value={formik.values[configName][dnsVar] || ''}
+                onChange={(...ar) => {
+                  const newConfig = {
+                    ...formik.values[configName],
+                    [dnsVar]: ar[0].target.value
+                  };
+                  formik.setFieldValue(configName, newConfig);
+                }}
+                placeholder={"leave blank if unused"}
+                fullWidth
+              />
+          </div>)}
+          </Stack>
+          </CosmosCollapse>}
+        </>}
+        </Stack>
+      </Grid>
+    </>;
+  }

+ 1072 - 0
client/src/utils/dns-config.json

@@ -0,0 +1,1072 @@
+{
+  "acme-dns": {
+    "name": "acme-dns",
+    "url": "https://go-acme.github.io/lego/dns/acme-dns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ACME_DNS_API_BASE</code></td>\n<td>The ACME-DNS API address</td>\n</tr>\n<tr>\n<td><code>ACME_DNS_STORAGE_PATH</code></td>\n<td>The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates.</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ACME_DNS_API_BASE",
+      "ACME_DNS_STORAGE_PATH"
+    ]
+  },
+  "alidns": {
+    "name": "alidns",
+    "url": "https://go-acme.github.io/lego/dns/alidns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ALICLOUD_ACCESS_KEY</code></td>\n<td>Access key ID</td>\n</tr>\n<tr>\n<td><code>ALICLOUD_RAM_ROLE</code></td>\n<td>Your instance RAM role (<a href=\"https://www.alibabacloud.com/help/doc-detail/54579.htm\">https://www.alibabacloud.com/help/doc-detail/54579.htm</a>)</td>\n</tr>\n<tr>\n<td><code>ALICLOUD_SECRET_KEY</code></td>\n<td>Access Key secret</td>\n</tr>\n<tr>\n<td><code>ALICLOUD_SECURITY_TOKEN</code></td>\n<td>STS Security Token (optional)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ALICLOUD_ACCESS_KEY",
+      "ALICLOUD_RAM_ROLE",
+      "ALICLOUD_SECRET_KEY",
+      "ALICLOUD_SECURITY_TOKEN"
+    ]
+  },
+  "allinkl": {
+    "name": "allinkl",
+    "url": "https://go-acme.github.io/lego/dns/allinkl/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ALL_INKL_LOGIN</code></td>\n<td>KAS login</td>\n</tr>\n<tr>\n<td><code>ALL_INKL_PASSWORD</code></td>\n<td>KAS password</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ALL_INKL_LOGIN",
+      "ALL_INKL_PASSWORD"
+    ]
+  },
+  "arvancloud": {
+    "name": "arvancloud",
+    "url": "https://go-acme.github.io/lego/dns/arvancloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ARVANCLOUD_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ARVANCLOUD_API_KEY"
+    ]
+  },
+  "azure": {
+    "name": "azure",
+    "url": "https://go-acme.github.io/lego/dns/azure/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>AZURE_CLIENT_ID</code></td>\n<td>Client ID</td>\n</tr>\n<tr>\n<td><code>AZURE_CLIENT_SECRET</code></td>\n<td>Client secret</td>\n</tr>\n<tr>\n<td><code>AZURE_ENVIRONMENT</code></td>\n<td>Azure environment, one of: public, usgovernment, german, and china</td>\n</tr>\n<tr>\n<td><code>AZURE_RESOURCE_GROUP</code></td>\n<td>Resource group</td>\n</tr>\n<tr>\n<td><code>AZURE_SUBSCRIPTION_ID</code></td>\n<td>Subscription ID</td>\n</tr>\n<tr>\n<td><code>AZURE_TENANT_ID</code></td>\n<td>Tenant ID</td>\n</tr>\n<tr>\n<td><code>instance metadata service</code></td>\n<td>If the credentials are <strong>not</strong> set via the environment, then it will attempt to get a bearer token via the <a href=\"https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service\">instance metadata service</a>.</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "AZURE_CLIENT_ID",
+      "AZURE_CLIENT_SECRET",
+      "AZURE_ENVIRONMENT",
+      "AZURE_RESOURCE_GROUP",
+      "AZURE_SUBSCRIPTION_ID",
+      "AZURE_TENANT_ID"
+    ]
+  },
+  "auroradns": {
+    "name": "auroradns",
+    "url": "https://go-acme.github.io/lego/dns/auroradns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>AURORA_API_KEY</code></td>\n<td>API key or username to used</td>\n</tr>\n<tr>\n<td><code>AURORA_SECRET</code></td>\n<td>Secret password to be used</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "AURORA_API_KEY",
+      "AURORA_SECRET"
+    ]
+  },
+  "autodns": {
+    "name": "autodns",
+    "url": "https://go-acme.github.io/lego/dns/autodns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>AUTODNS_API_PASSWORD</code></td>\n<td>User Password</td>\n</tr>\n<tr>\n<td><code>AUTODNS_API_USER</code></td>\n<td>Username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "AUTODNS_API_PASSWORD",
+      "AUTODNS_API_USER"
+    ]
+  },
+  "bindman": {
+    "name": "bindman",
+    "url": "https://go-acme.github.io/lego/dns/bindman/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>BINDMAN_MANAGER_ADDRESS</code></td>\n<td>The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "BINDMAN_MANAGER_ADDRESS"
+    ]
+  },
+  "bluecat": {
+    "name": "bluecat",
+    "url": "https://go-acme.github.io/lego/dns/bluecat/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>BLUECAT_CONFIG_NAME</code></td>\n<td>Configuration name</td>\n</tr>\n<tr>\n<td><code>BLUECAT_DNS_VIEW</code></td>\n<td>External DNS View Name</td>\n</tr>\n<tr>\n<td><code>BLUECAT_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>BLUECAT_SERVER_URL</code></td>\n<td>The server URL, should have scheme, hostname, and port (if required) of the authoritative Bluecat BAM serve</td>\n</tr>\n<tr>\n<td><code>BLUECAT_USER_NAME</code></td>\n<td>API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "BLUECAT_CONFIG_NAME",
+      "BLUECAT_DNS_VIEW",
+      "BLUECAT_PASSWORD",
+      "BLUECAT_SERVER_URL",
+      "BLUECAT_USER_NAME"
+    ]
+  },
+  "brandit": {
+    "name": "brandit",
+    "url": "https://go-acme.github.io/lego/dns/brandit/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>BRANDIT_API_KEY</code></td>\n<td>The API key</td>\n</tr>\n<tr>\n<td><code>BRANDIT_API_USERNAME</code></td>\n<td>The API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "BRANDIT_API_KEY",
+      "BRANDIT_API_USERNAME"
+    ]
+  },
+  "bunny": {
+    "name": "bunny",
+    "url": "https://go-acme.github.io/lego/dns/bunny/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>BUNNY_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "BUNNY_API_KEY"
+    ]
+  },
+  "checkdomain": {
+    "name": "checkdomain",
+    "url": "https://go-acme.github.io/lego/dns/checkdomain/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CHECKDOMAIN_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CHECKDOMAIN_TOKEN"
+    ]
+  },
+  "civo": {
+    "name": "civo",
+    "url": "https://go-acme.github.io/lego/dns/civo/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CIVO_TOKEN</code></td>\n<td>Authentication token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CIVO_TOKEN"
+    ]
+  },
+  "clouddns": {
+    "name": "clouddns",
+    "url": "https://go-acme.github.io/lego/dns/clouddns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CLOUDDNS_CLIENT_ID</code></td>\n<td>Client ID</td>\n</tr>\n<tr>\n<td><code>CLOUDDNS_EMAIL</code></td>\n<td>Account email</td>\n</tr>\n<tr>\n<td><code>CLOUDDNS_PASSWORD</code></td>\n<td>Account password</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CLOUDDNS_CLIENT_ID",
+      "CLOUDDNS_EMAIL",
+      "CLOUDDNS_PASSWORD"
+    ]
+  },
+  "cloudflare": {
+    "name": "cloudflare",
+    "url": "https://go-acme.github.io/lego/dns/cloudflare/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CF_API_EMAIL</code></td>\n<td>Account email</td>\n</tr>\n<tr>\n<td><code>CF_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>CF_DNS_API_TOKEN</code></td>\n<td>API token with DNS:Edit permission (since v3.1.0)</td>\n</tr>\n<tr>\n<td><code>CF_ZONE_API_TOKEN</code></td>\n<td>API token with Zone:Read permission (since v3.1.0)</td>\n</tr>\n<tr>\n<td><code>CLOUDFLARE_API_KEY</code></td>\n<td>Alias to CF_API_KEY</td>\n</tr>\n<tr>\n<td><code>CLOUDFLARE_DNS_API_TOKEN</code></td>\n<td>Alias to CF_DNS_API_TOKEN</td>\n</tr>\n<tr>\n<td><code>CLOUDFLARE_EMAIL</code></td>\n<td>Alias to CF_API_EMAIL</td>\n</tr>\n<tr>\n<td><code>CLOUDFLARE_ZONE_API_TOKEN</code></td>\n<td>Alias to CF_ZONE_API_TOKEN</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CLOUDFLARE_EMAIL",
+      "CLOUDFLARE_API_KEY",
+      "CF_API_EMAIL",
+      "CF_API_KEY",
+      "CF_DNS_API_TOKEN",
+      "CF_ZONE_API_TOKEN",
+      "CLOUDFLARE_DNS_API_TOKEN",
+      "CLOUDFLARE_ZONE_API_TOKEN"
+    ]
+  },
+  "cloudns": {
+    "name": "cloudns",
+    "url": "https://go-acme.github.io/lego/dns/cloudns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CLOUDNS_AUTH_ID</code></td>\n<td>The API user ID</td>\n</tr>\n<tr>\n<td><code>CLOUDNS_AUTH_PASSWORD</code></td>\n<td>The password for API user ID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CLOUDNS_AUTH_ID",
+      "CLOUDNS_AUTH_PASSWORD"
+    ]
+  },
+  "cloudxns": {
+    "name": "cloudxns",
+    "url": "https://go-acme.github.io/lego/dns/cloudxns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CLOUDXNS_API_KEY</code></td>\n<td>The API key</td>\n</tr>\n<tr>\n<td><code>CLOUDXNS_SECRET_KEY</code></td>\n<td>The API secret key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CLOUDXNS_API_KEY",
+      "CLOUDXNS_SECRET_KEY"
+    ]
+  },
+  "conoha": {
+    "name": "conoha",
+    "url": "https://go-acme.github.io/lego/dns/conoha/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CONOHA_API_PASSWORD</code></td>\n<td>The API password</td>\n</tr>\n<tr>\n<td><code>CONOHA_API_USERNAME</code></td>\n<td>The API username</td>\n</tr>\n<tr>\n<td><code>CONOHA_TENANT_ID</code></td>\n<td>Tenant ID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CONOHA_API_PASSWORD",
+      "CONOHA_API_USERNAME",
+      "CONOHA_TENANT_ID"
+    ]
+  },
+  "constellix": {
+    "name": "constellix",
+    "url": "https://go-acme.github.io/lego/dns/constellix/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>CONSTELLIX_API_KEY</code></td>\n<td>User API key</td>\n</tr>\n<tr>\n<td><code>CONSTELLIX_SECRET_KEY</code></td>\n<td>User secret key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "CONSTELLIX_API_KEY",
+      "CONSTELLIX_SECRET_KEY"
+    ]
+  },
+  "derak": {
+    "name": "derak",
+    "url": "https://go-acme.github.io/lego/dns/derak/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DERAK_API_KEY</code></td>\n<td>The API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DERAK_API_KEY"
+    ]
+  },
+  "desec": {
+    "name": "desec",
+    "url": "https://go-acme.github.io/lego/dns/desec/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DESEC_TOKEN</code></td>\n<td>Domain token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DESEC_TOKEN"
+    ]
+  },
+  "designate": {
+    "name": "designate",
+    "url": "https://go-acme.github.io/lego/dns/designate/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>OS_APPLICATION_CREDENTIAL_ID</code></td>\n<td>Application credential ID</td>\n</tr>\n<tr>\n<td><code>OS_APPLICATION_CREDENTIAL_NAME</code></td>\n<td>Application credential name</td>\n</tr>\n<tr>\n<td><code>OS_APPLICATION_CREDENTIAL_SECRET</code></td>\n<td>Application credential secret</td>\n</tr>\n<tr>\n<td><code>OS_AUTH_URL</code></td>\n<td>Identity endpoint URL</td>\n</tr>\n<tr>\n<td><code>OS_PASSWORD</code></td>\n<td>Password</td>\n</tr>\n<tr>\n<td><code>OS_PROJECT_NAME</code></td>\n<td>Project name</td>\n</tr>\n<tr>\n<td><code>OS_REGION_NAME</code></td>\n<td>Region name</td>\n</tr>\n<tr>\n<td><code>OS_USERNAME</code></td>\n<td>Username</td>\n</tr>\n<tr>\n<td><code>OS_USER_ID</code></td>\n<td>User ID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "OS_APPLICATION_CREDENTIAL_ID",
+      "OS_APPLICATION_CREDENTIAL_NAME",
+      "OS_APPLICATION_CREDENTIAL_SECRET",
+      "OS_AUTH_URL",
+      "OS_PASSWORD",
+      "OS_PROJECT_NAME",
+      "OS_REGION_NAME",
+      "OS_USERNAME",
+      "OS_USER_ID"
+    ]
+  },
+  "digitalocean": {
+    "name": "digitalocean",
+    "url": "https://go-acme.github.io/lego/dns/digitalocean/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DO_AUTH_TOKEN</code></td>\n<td>Authentication token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DO_AUTH_TOKEN"
+    ]
+  },
+  "dnshomede": {
+    "name": "dnshomede",
+    "url": "https://go-acme.github.io/lego/dns/dnshomede/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DNSHOMEDE_CREDENTIALS</code></td>\n<td>Comma-separated list of domain:password credential pairs</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DNSHOMEDE_CREDENTIALS"
+    ]
+  },
+  "dnsimple": {
+    "name": "dnsimple",
+    "url": "https://go-acme.github.io/lego/dns/dnsimple/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DNSIMPLE_OAUTH_TOKEN</code></td>\n<td>OAuth token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DNSIMPLE_OAUTH_TOKEN"
+    ]
+  },
+  "dnsmadeeasy": {
+    "name": "dnsmadeeasy",
+    "url": "https://go-acme.github.io/lego/dns/dnsmadeeasy/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DNSMADEEASY_API_KEY</code></td>\n<td>The API key</td>\n</tr>\n<tr>\n<td><code>DNSMADEEASY_API_SECRET</code></td>\n<td>The API Secret key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DNSMADEEASY_API_KEY",
+      "DNSMADEEASY_API_SECRET"
+    ]
+  },
+  "dnspod": {
+    "name": "dnspod",
+    "url": "https://go-acme.github.io/lego/dns/dnspod/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DNSPOD_API_KEY</code></td>\n<td>The user token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DNSPOD_API_KEY"
+    ]
+  },
+  "dode": {
+    "name": "dode",
+    "url": "https://go-acme.github.io/lego/dns/dode/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DODE_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DODE_TOKEN"
+    ]
+  },
+  "domeneshop": {
+    "name": "domeneshop",
+    "url": "https://go-acme.github.io/lego/dns/domeneshop/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DOMENESHOP_API_SECRET</code></td>\n<td>API secret</td>\n</tr>\n<tr>\n<td><code>DOMENESHOP_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DOMENESHOP_API_SECRET",
+      "DOMENESHOP_API_TOKEN"
+    ]
+  },
+  "dreamhost": {
+    "name": "dreamhost",
+    "url": "https://go-acme.github.io/lego/dns/dreamhost/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DREAMHOST_API_KEY</code></td>\n<td>The API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DREAMHOST_API_KEY"
+    ]
+  },
+  "duckdns": {
+    "name": "duckdns",
+    "url": "https://go-acme.github.io/lego/dns/duckdns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DUCKDNS_TOKEN</code></td>\n<td>Account token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DUCKDNS_TOKEN"
+    ]
+  },
+  "dyn": {
+    "name": "dyn",
+    "url": "https://go-acme.github.io/lego/dns/dyn/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DYN_CUSTOMER_NAME</code></td>\n<td>Customer name</td>\n</tr>\n<tr>\n<td><code>DYN_PASSWORD</code></td>\n<td>Password</td>\n</tr>\n<tr>\n<td><code>DYN_USER_NAME</code></td>\n<td>User name</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DYN_CUSTOMER_NAME",
+      "DYN_PASSWORD",
+      "DYN_USER_NAME"
+    ]
+  },
+  "dynu": {
+    "name": "dynu",
+    "url": "https://go-acme.github.io/lego/dns/dynu/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>DYNU_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "DYNU_API_KEY"
+    ]
+  },
+  "easydns": {
+    "name": "easydns",
+    "url": "https://go-acme.github.io/lego/dns/easydns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>EASYDNS_KEY</code></td>\n<td>API Key</td>\n</tr>\n<tr>\n<td><code>EASYDNS_TOKEN</code></td>\n<td>API Token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "EASYDNS_KEY",
+      "EASYDNS_TOKEN"
+    ]
+  },
+  "edgedns": {
+    "name": "edgedns",
+    "url": "https://go-acme.github.io/lego/dns/edgedns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>AKAMAI_ACCESS_TOKEN</code></td>\n<td>Access token, managed by the Akamai EdgeGrid client</td>\n</tr>\n<tr>\n<td><code>AKAMAI_CLIENT_SECRET</code></td>\n<td>Client secret, managed by the Akamai EdgeGrid client</td>\n</tr>\n<tr>\n<td><code>AKAMAI_CLIENT_TOKEN</code></td>\n<td>Client token, managed by the Akamai EdgeGrid client</td>\n</tr>\n<tr>\n<td><code>AKAMAI_EDGERC</code></td>\n<td>Path to the .edgerc file, managed by the Akamai EdgeGrid client</td>\n</tr>\n<tr>\n<td><code>AKAMAI_EDGERC_SECTION</code></td>\n<td>Configuration section, managed by the Akamai EdgeGrid client</td>\n</tr>\n<tr>\n<td><code>AKAMAI_HOST</code></td>\n<td>API host, managed by the Akamai EdgeGrid client</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "AKAMAI_ACCESS_TOKEN",
+      "AKAMAI_CLIENT_SECRET",
+      "AKAMAI_CLIENT_TOKEN",
+      "AKAMAI_EDGERC",
+      "AKAMAI_EDGERC_SECTION",
+      "AKAMAI_HOST"
+    ]
+  },
+  "epik": {
+    "name": "epik",
+    "url": "https://go-acme.github.io/lego/dns/epik/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>EPIK_SIGNATURE</code></td>\n<td>Epik API signature (<a href=\"https://registrar.epik.com/account/api-settings/\">https://registrar.epik.com/account/api-settings/</a>)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "EPIK_SIGNATURE"
+    ]
+  },
+  "exoscale": {
+    "name": "exoscale",
+    "url": "https://go-acme.github.io/lego/dns/exoscale/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>EXOSCALE_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>EXOSCALE_API_SECRET</code></td>\n<td>API secret</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "EXOSCALE_API_KEY",
+      "EXOSCALE_API_SECRET"
+    ]
+  },
+  "freemyip": {
+    "name": "freemyip",
+    "url": "https://go-acme.github.io/lego/dns/freemyip/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>FREEMYIP_TOKEN</code></td>\n<td>Account token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "FREEMYIP_TOKEN"
+    ]
+  },
+  "gandi": {
+    "name": "gandi",
+    "url": "https://go-acme.github.io/lego/dns/gandi/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GANDI_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GANDI_API_KEY"
+    ]
+  },
+  "gandiv5": {
+    "name": "gandiv5",
+    "url": "https://go-acme.github.io/lego/dns/gandiv5/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GANDIV5_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GANDIV5_API_KEY"
+    ]
+  },
+  "gcloud": {
+    "name": "gcloud",
+    "url": "https://go-acme.github.io/lego/dns/gcloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Application Default Credentials</code></td>\n<td><a href=\"https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application\">Documentation</a></td>\n</tr>\n<tr>\n<td><code>GCE_PROJECT</code></td>\n<td>Project name (by default, the project name is auto-detected by using the metadata service)</td>\n</tr>\n<tr>\n<td><code>GCE_SERVICE_ACCOUNT</code></td>\n<td>Account</td>\n</tr>\n<tr>\n<td><code>GCE_SERVICE_ACCOUNT_FILE</code></td>\n<td>Account file path</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GCE_PROJECT",
+      "GCE_SERVICE_ACCOUNT",
+      "GCE_SERVICE_ACCOUNT_FILE"
+    ]
+  },
+  "gcore": {
+    "name": "gcore",
+    "url": "https://go-acme.github.io/lego/dns/gcore/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GCORE_PERMANENT_API_TOKEN</code></td>\n<td>Permanent API tokene (<a href=\"https://gcorelabs.com/blog/permanent-api-token-explained/\">https://gcorelabs.com/blog/permanent-api-token-explained/</a>)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GCORE_PERMANENT_API_TOKEN"
+    ]
+  },
+  "glesys": {
+    "name": "glesys",
+    "url": "https://go-acme.github.io/lego/dns/glesys/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GLESYS_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>GLESYS_API_USER</code></td>\n<td>API user</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GLESYS_API_KEY",
+      "GLESYS_API_USER"
+    ]
+  },
+  "godaddy": {
+    "name": "godaddy",
+    "url": "https://go-acme.github.io/lego/dns/godaddy/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GODADDY_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>GODADDY_API_SECRET</code></td>\n<td>API secret</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GODADDY_API_KEY",
+      "GODADDY_API_SECRET"
+    ]
+  },
+  "googledomains": {
+    "name": "googledomains",
+    "url": "https://go-acme.github.io/lego/dns/googledomains/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GOOGLE_DOMAINS_ACCESS_TOKEN</code></td>\n<td>Access token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "GOOGLE_DOMAINS_ACCESS_TOKEN"
+    ]
+  },
+  "hetzner": {
+    "name": "hetzner",
+    "url": "https://go-acme.github.io/lego/dns/hetzner/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>HETZNER_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "HETZNER_API_KEY"
+    ]
+  },
+  "hostingde": {
+    "name": "hostingde",
+    "url": "https://go-acme.github.io/lego/dns/hostingde/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>HOSTINGDE_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "HOSTINGDE_API_KEY"
+    ]
+  },
+  "hosttech": {
+    "name": "hosttech",
+    "url": "https://go-acme.github.io/lego/dns/hosttech/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>HOSTTECH_API_KEY</code></td>\n<td>API login</td>\n</tr>\n<tr>\n<td><code>HOSTTECH_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "HOSTTECH_API_KEY",
+      "HOSTTECH_PASSWORD"
+    ]
+  },
+  "httpreq": {
+    "name": "httpreq",
+    "url": "https://go-acme.github.io/lego/dns/httpreq/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>HTTPREQ_ENDPOINT</code></td>\n<td>The URL of the server</td>\n</tr>\n<tr>\n<td><code>HTTPREQ_MODE</code></td>\n<td><code>RAW</code>, none</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "HTTPREQ_ENDPOINT",
+      "HTTPREQ_MODE",
+      "RAW"
+    ]
+  },
+  "hurricane": {
+    "name": "hurricane",
+    "url": "https://go-acme.github.io/lego/dns/hurricane/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>HURRICANE_TOKENS</code></td>\n<td>TXT record names and tokens</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "HURRICANE_TOKENS"
+    ]
+  },
+  "ibmcloud": {
+    "name": "ibmcloud",
+    "url": "https://go-acme.github.io/lego/dns/ibmcloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SOFTLAYER_API_KEY</code></td>\n<td>Classic Infrastructure API key</td>\n</tr>\n<tr>\n<td><code>SOFTLAYER_USERNAME</code></td>\n<td>User name (IBM Cloud is <!-- raw HTML omitted -->_<!-- raw HTML omitted -->)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SOFTLAYER_API_KEY",
+      "SOFTLAYER_USERNAME"
+    ]
+  },
+  "iij": {
+    "name": "iij",
+    "url": "https://go-acme.github.io/lego/dns/iij/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>IIJ_API_ACCESS_KEY</code></td>\n<td>API access key</td>\n</tr>\n<tr>\n<td><code>IIJ_API_SECRET_KEY</code></td>\n<td>API secret key</td>\n</tr>\n<tr>\n<td><code>IIJ_DO_SERVICE_CODE</code></td>\n<td>DO service code</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "IIJ_API_ACCESS_KEY",
+      "IIJ_API_SECRET_KEY",
+      "IIJ_DO_SERVICE_CODE"
+    ]
+  },
+  "iijdpf": {
+    "name": "iijdpf",
+    "url": "https://go-acme.github.io/lego/dns/iijdpf/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>IIJ_DPF_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n<tr>\n<td><code>IIJ_DPF_DPM_SERVICE_CODE</code></td>\n<td>IIJ Managed DNS Service&rsquo;s service code</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "IIJ_DPF_API_TOKEN",
+      "IIJ_DPF_DPM_SERVICE_CODE"
+    ]
+  },
+  "infoblox": {
+    "name": "infoblox",
+    "url": "https://go-acme.github.io/lego/dns/infoblox/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>INFOBLOX_HOST</code></td>\n<td>Host URI</td>\n</tr>\n<tr>\n<td><code>INFOBLOX_PASSWORD</code></td>\n<td>Account Password</td>\n</tr>\n<tr>\n<td><code>INFOBLOX_USERNAME</code></td>\n<td>Account Username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "INFOBLOX_HOST",
+      "INFOBLOX_PASSWORD",
+      "INFOBLOX_USERNAME"
+    ]
+  },
+  "infomaniak": {
+    "name": "infomaniak",
+    "url": "https://go-acme.github.io/lego/dns/infomaniak/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>INFOMANIAK_ACCESS_TOKEN</code></td>\n<td>Access token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "INFOMANIAK_ACCESS_TOKEN"
+    ]
+  },
+  "internetbs": {
+    "name": "internetbs",
+    "url": "https://go-acme.github.io/lego/dns/internetbs/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>INTERNET_BS_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>INTERNET_BS_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "INTERNET_BS_API_KEY",
+      "INTERNET_BS_PASSWORD"
+    ]
+  },
+  "inwx": {
+    "name": "inwx",
+    "url": "https://go-acme.github.io/lego/dns/inwx/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>INWX_PASSWORD</code></td>\n<td>Password</td>\n</tr>\n<tr>\n<td><code>INWX_USERNAME</code></td>\n<td>Username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "INWX_PASSWORD",
+      "INWX_USERNAME"
+    ]
+  },
+  "ionos": {
+    "name": "ionos",
+    "url": "https://go-acme.github.io/lego/dns/ionos/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>IONOS_API_KEY</code></td>\n<td>API key <code>&lt;prefix&gt;.&lt;secret&gt;</code> <a href=\"https://developer.hosting.ionos.com/docs/getstarted\">https://developer.hosting.ionos.com/docs/getstarted</a></td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "IONOS_API_KEY"
+    ]
+  },
+  "iwantmyname": {
+    "name": "iwantmyname",
+    "url": "https://go-acme.github.io/lego/dns/iwantmyname/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>IWANTMYNAME_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>IWANTMYNAME_USERNAME</code></td>\n<td>API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "IWANTMYNAME_PASSWORD",
+      "IWANTMYNAME_USERNAME"
+    ]
+  },
+  "joker": {
+    "name": "joker",
+    "url": "https://go-acme.github.io/lego/dns/joker/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>JOKER_API_KEY</code></td>\n<td>API key (only with DMAPI mode)</td>\n</tr>\n<tr>\n<td><code>JOKER_API_MODE</code></td>\n<td>&lsquo;DMAPI&rsquo; or &lsquo;SVC&rsquo;. DMAPI is for resellers accounts. (Default: DMAPI)</td>\n</tr>\n<tr>\n<td><code>JOKER_PASSWORD</code></td>\n<td>Joker.com password</td>\n</tr>\n<tr>\n<td><code>JOKER_USERNAME</code></td>\n<td>Joker.com username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "JOKER_API_KEY",
+      "JOKER_API_MODE",
+      "JOKER_PASSWORD",
+      "JOKER_USERNAME"
+    ]
+  },
+  "liara": {
+    "name": "liara",
+    "url": "https://go-acme.github.io/lego/dns/liara/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>LIARA_API_KEY</code></td>\n<td>The API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "LIARA_API_KEY"
+    ]
+  },
+  "lightsail": {
+    "name": "lightsail",
+    "url": "https://go-acme.github.io/lego/dns/lightsail/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>AWS_ACCESS_KEY_ID</code></td>\n<td>Managed by the AWS client. Access key ID (<code>AWS_ACCESS_KEY_ID_FILE</code> is not supported, use <code>AWS_SHARED_CREDENTIALS_FILE</code> instead)</td>\n</tr>\n<tr>\n<td><code>AWS_SECRET_ACCESS_KEY</code></td>\n<td>Managed by the AWS client. Secret access key (<code>AWS_SECRET_ACCESS_KEY_FILE</code> is not supported, use <code>AWS_SHARED_CREDENTIALS_FILE</code> instead)</td>\n</tr>\n<tr>\n<td><code>DNS_ZONE</code></td>\n<td>Domain name of the DNS zone</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "AWS_ACCESS_KEY_ID",
+      "AWS_ACCESS_KEY_ID_FILE",
+      "AWS_SHARED_CREDENTIALS_FILE",
+      "AWS_SECRET_ACCESS_KEY",
+      "AWS_SECRET_ACCESS_KEY_FILE",
+      "AWS_SHARED_CREDENTIALS_FILE",
+      "DNS_ZONE"
+    ]
+  },
+  "linode": {
+    "name": "linode",
+    "url": "https://go-acme.github.io/lego/dns/linode/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>LINODE_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "LINODE_TOKEN"
+    ]
+  },
+  "liquidweb": {
+    "name": "liquidweb",
+    "url": "https://go-acme.github.io/lego/dns/liquidweb/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>LIQUID_WEB_PASSWORD</code></td>\n<td>Storm API Password</td>\n</tr>\n<tr>\n<td><code>LIQUID_WEB_USERNAME</code></td>\n<td>Storm API Username</td>\n</tr>\n<tr>\n<td><code>LIQUID_WEB_ZONE</code></td>\n<td>DNS Zone</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "LIQUID_WEB_PASSWORD",
+      "LIQUID_WEB_USERNAME",
+      "LIQUID_WEB_ZONE"
+    ]
+  },
+  "luadns": {
+    "name": "luadns",
+    "url": "https://go-acme.github.io/lego/dns/luadns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>LUADNS_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n<tr>\n<td><code>LUADNS_API_USERNAME</code></td>\n<td>Username (your email)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "LUADNS_API_TOKEN",
+      "LUADNS_API_USERNAME"
+    ]
+  },
+  "loopia": {
+    "name": "loopia",
+    "url": "https://go-acme.github.io/lego/dns/loopia/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>LOOPIA_API_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>LOOPIA_API_USER</code></td>\n<td>API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "LOOPIA_API_PASSWORD",
+      "LOOPIA_API_USER"
+    ]
+  },
+  "mydnsjp": {
+    "name": "mydnsjp",
+    "url": "https://go-acme.github.io/lego/dns/mydnsjp/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>MYDNSJP_MASTER_ID</code></td>\n<td>Master ID</td>\n</tr>\n<tr>\n<td><code>MYDNSJP_PASSWORD</code></td>\n<td>Password</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "MYDNSJP_MASTER_ID",
+      "MYDNSJP_PASSWORD"
+    ]
+  },
+  "mythicbeasts": {
+    "name": "mythicbeasts",
+    "url": "https://go-acme.github.io/lego/dns/mythicbeasts/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>MYTHICBEASTS_PASSWORD</code></td>\n<td>Password</td>\n</tr>\n<tr>\n<td><code>MYTHICBEASTS_USERNAME</code></td>\n<td>User name</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "MYTHICBEASTS_PASSWORD",
+      "MYTHICBEASTS_USERNAME"
+    ]
+  },
+  "namecheap": {
+    "name": "namecheap",
+    "url": "https://go-acme.github.io/lego/dns/namecheap/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NAMECHEAP_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>NAMECHEAP_API_USER</code></td>\n<td>API user</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NAMECHEAP_API_KEY",
+      "NAMECHEAP_API_USER"
+    ]
+  },
+  "namedotcom": {
+    "name": "namedotcom",
+    "url": "https://go-acme.github.io/lego/dns/namedotcom/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NAMECOM_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n<tr>\n<td><code>NAMECOM_USERNAME</code></td>\n<td>Username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NAMECOM_API_TOKEN",
+      "NAMECOM_USERNAME"
+    ]
+  },
+  "namesilo": {
+    "name": "namesilo",
+    "url": "https://go-acme.github.io/lego/dns/namesilo/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NAMESILO_API_KEY</code></td>\n<td>Client ID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NAMESILO_API_KEY"
+    ]
+  },
+  "nearlyfreespeech": {
+    "name": "nearlyfreespeech",
+    "url": "https://go-acme.github.io/lego/dns/nearlyfreespeech/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NEARLYFREESPEECH_API_KEY</code></td>\n<td>API Key for API requests</td>\n</tr>\n<tr>\n<td><code>NEARLYFREESPEECH_LOGIN</code></td>\n<td>Username for API requests</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NEARLYFREESPEECH_API_KEY",
+      "NEARLYFREESPEECH_LOGIN"
+    ]
+  },
+  "netcup": {
+    "name": "netcup",
+    "url": "https://go-acme.github.io/lego/dns/netcup/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NETCUP_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>NETCUP_API_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>NETCUP_CUSTOMER_NUMBER</code></td>\n<td>Customer number</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NETCUP_API_KEY",
+      "NETCUP_API_PASSWORD",
+      "NETCUP_CUSTOMER_NUMBER"
+    ]
+  },
+  "netlify": {
+    "name": "netlify",
+    "url": "https://go-acme.github.io/lego/dns/netlify/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NETLIFY_TOKEN</code></td>\n<td>Token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NETLIFY_TOKEN"
+    ]
+  },
+  "nicmanager": {
+    "name": "nicmanager",
+    "url": "https://go-acme.github.io/lego/dns/nicmanager/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NICMANAGER_API_EMAIL</code></td>\n<td>Email-based login</td>\n</tr>\n<tr>\n<td><code>NICMANAGER_API_LOGIN</code></td>\n<td>Login, used for Username-based login</td>\n</tr>\n<tr>\n<td><code>NICMANAGER_API_PASSWORD</code></td>\n<td>Password, always required</td>\n</tr>\n<tr>\n<td><code>NICMANAGER_API_USERNAME</code></td>\n<td>Username, used for Username-based login</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NICMANAGER_API_EMAIL",
+      "NICMANAGER_API_LOGIN",
+      "NICMANAGER_API_PASSWORD",
+      "NICMANAGER_API_USERNAME"
+    ]
+  },
+  "nifcloud": {
+    "name": "nifcloud",
+    "url": "https://go-acme.github.io/lego/dns/nifcloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NIFCLOUD_ACCESS_KEY_ID</code></td>\n<td>Access key</td>\n</tr>\n<tr>\n<td><code>NIFCLOUD_SECRET_ACCESS_KEY</code></td>\n<td>Secret access key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NIFCLOUD_ACCESS_KEY_ID",
+      "NIFCLOUD_SECRET_ACCESS_KEY"
+    ]
+  },
+  "njalla": {
+    "name": "njalla",
+    "url": "https://go-acme.github.io/lego/dns/njalla/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NJALLA_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NJALLA_TOKEN"
+    ]
+  },
+  "nodion": {
+    "name": "nodion",
+    "url": "https://go-acme.github.io/lego/dns/nodion/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NODION_API_TOKEN</code></td>\n<td>The API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NODION_API_TOKEN"
+    ]
+  },
+  "ns1": {
+    "name": "ns1",
+    "url": "https://go-acme.github.io/lego/dns/ns1/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>NS1_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "NS1_API_KEY"
+    ]
+  },
+  "oraclecloud": {
+    "name": "oraclecloud",
+    "url": "https://go-acme.github.io/lego/dns/oraclecloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>OCI_COMPARTMENT_OCID</code></td>\n<td>Compartment OCID</td>\n</tr>\n<tr>\n<td><code>OCI_PRIVKEY_FILE</code></td>\n<td>Private key file</td>\n</tr>\n<tr>\n<td><code>OCI_PRIVKEY_PASS</code></td>\n<td>Private key password</td>\n</tr>\n<tr>\n<td><code>OCI_PUBKEY_FINGERPRINT</code></td>\n<td>Public key fingerprint</td>\n</tr>\n<tr>\n<td><code>OCI_REGION</code></td>\n<td>Region</td>\n</tr>\n<tr>\n<td><code>OCI_TENANCY_OCID</code></td>\n<td>Tenancy OCID</td>\n</tr>\n<tr>\n<td><code>OCI_USER_OCID</code></td>\n<td>User OCID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "OCI_COMPARTMENT_OCID",
+      "OCI_PRIVKEY_FILE",
+      "OCI_PRIVKEY_PASS",
+      "OCI_PUBKEY_FINGERPRINT",
+      "OCI_REGION",
+      "OCI_TENANCY_OCID",
+      "OCI_USER_OCID"
+    ]
+  },
+  "otc": {
+    "name": "otc",
+    "url": "https://go-acme.github.io/lego/dns/otc/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>OTC_DOMAIN_NAME</code></td>\n<td>Domain name</td>\n</tr>\n<tr>\n<td><code>OTC_IDENTITY_ENDPOINT</code></td>\n<td>Identity endpoint URL</td>\n</tr>\n<tr>\n<td><code>OTC_PASSWORD</code></td>\n<td>Password</td>\n</tr>\n<tr>\n<td><code>OTC_PROJECT_NAME</code></td>\n<td>Project name</td>\n</tr>\n<tr>\n<td><code>OTC_USER_NAME</code></td>\n<td>User name</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "OTC_DOMAIN_NAME",
+      "OTC_IDENTITY_ENDPOINT",
+      "OTC_PASSWORD",
+      "OTC_PROJECT_NAME",
+      "OTC_USER_NAME"
+    ]
+  },
+  "ovh": {
+    "name": "ovh",
+    "url": "https://go-acme.github.io/lego/dns/ovh/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>OVH_APPLICATION_KEY</code></td>\n<td>Application key</td>\n</tr>\n<tr>\n<td><code>OVH_APPLICATION_SECRET</code></td>\n<td>Application secret</td>\n</tr>\n<tr>\n<td><code>OVH_CONSUMER_KEY</code></td>\n<td>Consumer key</td>\n</tr>\n<tr>\n<td><code>OVH_ENDPOINT</code></td>\n<td>Endpoint URL (ovh-eu or ovh-ca)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "OVH_APPLICATION_KEY",
+      "OVH_APPLICATION_SECRET",
+      "OVH_CONSUMER_KEY",
+      "OVH_ENDPOINT"
+    ]
+  },
+  "pdns": {
+    "name": "pdns",
+    "url": "https://go-acme.github.io/lego/dns/pdns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>PDNS_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>PDNS_API_URL</code></td>\n<td>API URL</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "PDNS_API_KEY",
+      "PDNS_API_URL"
+    ]
+  },
+  "plesk": {
+    "name": "plesk",
+    "url": "https://go-acme.github.io/lego/dns/plesk/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>PLESK_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>PLESK_SERVER_BASE_URL</code></td>\n<td>Base URL of the server (ex: <a href=\"https://plesk.myserver.com:8443\">https://plesk.myserver.com:8443</a>)</td>\n</tr>\n<tr>\n<td><code>PLESK_USERNAME</code></td>\n<td>API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "PLESK_PASSWORD",
+      "PLESK_SERVER_BASE_URL",
+      "PLESK_USERNAME"
+    ]
+  },
+  "porkbun": {
+    "name": "porkbun",
+    "url": "https://go-acme.github.io/lego/dns/porkbun/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>PORKBUN_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>PORKBUN_SECRET_API_KEY</code></td>\n<td>secret API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "PORKBUN_API_KEY",
+      "PORKBUN_SECRET_API_KEY"
+    ]
+  },
+  "rackspace": {
+    "name": "rackspace",
+    "url": "https://go-acme.github.io/lego/dns/rackspace/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>RACKSPACE_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>RACKSPACE_USER</code></td>\n<td>API user</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "RACKSPACE_API_KEY",
+      "RACKSPACE_USER"
+    ]
+  },
+  "regru": {
+    "name": "regru",
+    "url": "https://go-acme.github.io/lego/dns/regru/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>REGRU_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>REGRU_USERNAME</code></td>\n<td>API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "REGRU_PASSWORD",
+      "REGRU_USERNAME"
+    ]
+  },
+  "rfc2136": {
+    "name": "rfc2136",
+    "url": "https://go-acme.github.io/lego/dns/rfc2136/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>RFC2136_NAMESERVER</code></td>\n<td>Network address in the form &ldquo;host&rdquo; or &ldquo;host:port&rdquo;</td>\n</tr>\n<tr>\n<td><code>RFC2136_TSIG_ALGORITHM</code></td>\n<td>TSIG algorithm. See <a href=\"https://github.com/miekg/dns/blob/master/tsig.go\">miekg/dns#tsig.go</a> for supported values. To disable TSIG authentication, leave the <code>RFC2136_TSIG*</code> variables unset.</td>\n</tr>\n<tr>\n<td><code>RFC2136_TSIG_KEY</code></td>\n<td>Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the <code>RFC2136_TSIG*</code> variables unset.</td>\n</tr>\n<tr>\n<td><code>RFC2136_TSIG_SECRET</code></td>\n<td>Secret key payload. To disable TSIG authentication, leave the<code> RFC2136_TSIG*</code> variables unset.</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "RFC2136_NAMESERVER",
+      "RFC2136_TSIG_ALGORITHM",
+      "RFC2136_TSIG_KEY",
+      "RFC2136_TSIG_SECRET"
+    ]
+  },
+  "rimuhosting": {
+    "name": "rimuhosting",
+    "url": "https://go-acme.github.io/lego/dns/rimuhosting/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>RIMUHOSTING_API_KEY</code></td>\n<td>User API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "RIMUHOSTING_API_KEY"
+    ]
+  },
+  "route53": {
+    "name": "route53",
+    "url": "https://go-acme.github.io/lego/dns/route53/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>AWS_ACCESS_KEY_ID</code></td>\n<td>Managed by the AWS client. Access key ID (<code>AWS_ACCESS_KEY_ID_FILE</code> is not supported, use <code>AWS_SHARED_CREDENTIALS_FILE</code> instead)</td>\n</tr>\n<tr>\n<td><code>AWS_ASSUME_ROLE_ARN</code></td>\n<td>Managed by the AWS Role ARN (<code>AWS_ASSUME_ROLE_ARN_FILE</code> is not supported)</td>\n</tr>\n<tr>\n<td><code>AWS_EXTERNAL_ID</code></td>\n<td>Managed by STS AssumeRole API operation (<code>AWS_EXTERNAL_ID_FILE</code> is not supported)</td>\n</tr>\n<tr>\n<td><code>AWS_HOSTED_ZONE_ID</code></td>\n<td>Override the hosted zone ID.</td>\n</tr>\n<tr>\n<td><code>AWS_PROFILE</code></td>\n<td>Managed by the AWS client (<code>AWS_PROFILE_FILE</code> is not supported)</td>\n</tr>\n<tr>\n<td><code>AWS_REGION</code></td>\n<td>Managed by the AWS client (<code>AWS_REGION_FILE</code> is not supported)</td>\n</tr>\n<tr>\n<td><code>AWS_SDK_LOAD_CONFIG</code></td>\n<td>Managed by the AWS client. Retrieve the region from the CLI config file (<code>AWS_SDK_LOAD_CONFIG_FILE</code> is not supported)</td>\n</tr>\n<tr>\n<td><code>AWS_SECRET_ACCESS_KEY</code></td>\n<td>Managed by the AWS client. Secret access key (<code>AWS_SECRET_ACCESS_KEY_FILE</code> is not supported, use <code>AWS_SHARED_CREDENTIALS_FILE</code> instead)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "AWS_ACCESS_KEY_ID",
+      "AWS_ACCESS_KEY_ID_FILE",
+      "AWS_SHARED_CREDENTIALS_FILE",
+      "AWS_ASSUME_ROLE_ARN",
+      "AWS_ASSUME_ROLE_ARN_FILE",
+      "AWS_EXTERNAL_ID",
+      "AWS_EXTERNAL_ID_FILE",
+      "AWS_HOSTED_ZONE_ID",
+      "AWS_PROFILE",
+      "AWS_PROFILE_FILE",
+      "AWS_REGION",
+      "AWS_REGION_FILE",
+      "AWS_SDK_LOAD_CONFIG",
+      "AWS_SDK_LOAD_CONFIG_FILE",
+      "AWS_SECRET_ACCESS_KEY",
+      "AWS_SECRET_ACCESS_KEY_FILE",
+      "AWS_SHARED_CREDENTIALS_FILE"
+    ]
+  },
+  "safedns": {
+    "name": "safedns",
+    "url": "https://go-acme.github.io/lego/dns/safedns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SAFEDNS_AUTH_TOKEN</code></td>\n<td>Authentication token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SAFEDNS_AUTH_TOKEN"
+    ]
+  },
+  "sakuracloud": {
+    "name": "sakuracloud",
+    "url": "https://go-acme.github.io/lego/dns/sakuracloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SAKURACLOUD_ACCESS_TOKEN</code></td>\n<td>Access token</td>\n</tr>\n<tr>\n<td><code>SAKURACLOUD_ACCESS_TOKEN_SECRET</code></td>\n<td>Access token secret</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SAKURACLOUD_ACCESS_TOKEN",
+      "SAKURACLOUD_ACCESS_TOKEN_SECRET"
+    ]
+  },
+  "scaleway": {
+    "name": "scaleway",
+    "url": "https://go-acme.github.io/lego/dns/scaleway/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SCALEWAY_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n<tr>\n<td><code>SCALEWAY_PROJECT_ID</code></td>\n<td>Project to use (optional)</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SCALEWAY_API_TOKEN",
+      "SCALEWAY_PROJECT_ID"
+    ]
+  },
+  "selectel": {
+    "name": "selectel",
+    "url": "https://go-acme.github.io/lego/dns/selectel/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SELECTEL_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SELECTEL_API_TOKEN"
+    ]
+  },
+  "servercow": {
+    "name": "servercow",
+    "url": "https://go-acme.github.io/lego/dns/servercow/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SERVERCOW_PASSWORD</code></td>\n<td>API password</td>\n</tr>\n<tr>\n<td><code>SERVERCOW_USERNAME</code></td>\n<td>API username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SERVERCOW_PASSWORD",
+      "SERVERCOW_USERNAME"
+    ]
+  },
+  "simply": {
+    "name": "simply",
+    "url": "https://go-acme.github.io/lego/dns/simply/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SIMPLY_ACCOUNT_NAME</code></td>\n<td>Account name</td>\n</tr>\n<tr>\n<td><code>SIMPLY_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SIMPLY_ACCOUNT_NAME",
+      "SIMPLY_API_KEY"
+    ]
+  },
+  "sonic": {
+    "name": "sonic",
+    "url": "https://go-acme.github.io/lego/dns/sonic/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SONIC_API_KEY</code></td>\n<td>API Key</td>\n</tr>\n<tr>\n<td><code>SONIC_USER_ID</code></td>\n<td>User ID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SONIC_API_KEY",
+      "SONIC_USER_ID"
+    ]
+  },
+  "stackpath": {
+    "name": "stackpath",
+    "url": "https://go-acme.github.io/lego/dns/stackpath/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>STACKPATH_CLIENT_ID</code></td>\n<td>Client ID</td>\n</tr>\n<tr>\n<td><code>STACKPATH_CLIENT_SECRET</code></td>\n<td>Client secret</td>\n</tr>\n<tr>\n<td><code>STACKPATH_STACK_ID</code></td>\n<td>Stack ID</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "STACKPATH_CLIENT_ID",
+      "STACKPATH_CLIENT_SECRET",
+      "STACKPATH_STACK_ID"
+    ]
+  },
+  "tencentcloud": {
+    "name": "tencentcloud",
+    "url": "https://go-acme.github.io/lego/dns/tencentcloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>TENCENTCLOUD_SECRET_ID</code></td>\n<td>Access key ID</td>\n</tr>\n<tr>\n<td><code>TENCENTCLOUD_SECRET_KEY</code></td>\n<td>Access Key secret</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "TENCENTCLOUD_SECRET_ID",
+      "TENCENTCLOUD_SECRET_KEY"
+    ]
+  },
+  "transip": {
+    "name": "transip",
+    "url": "https://go-acme.github.io/lego/dns/transip/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>TRANSIP_ACCOUNT_NAME</code></td>\n<td>Account name</td>\n</tr>\n<tr>\n<td><code>TRANSIP_PRIVATE_KEY_PATH</code></td>\n<td>Private key path</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "TRANSIP_ACCOUNT_NAME",
+      "TRANSIP_PRIVATE_KEY_PATH"
+    ]
+  },
+  "ultradns": {
+    "name": "ultradns",
+    "url": "https://go-acme.github.io/lego/dns/ultradns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ULTRADNS_PASSWORD</code></td>\n<td>API Password</td>\n</tr>\n<tr>\n<td><code>ULTRADNS_USERNAME</code></td>\n<td>API Username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ULTRADNS_PASSWORD",
+      "ULTRADNS_USERNAME"
+    ]
+  },
+  "variomedia": {
+    "name": "variomedia",
+    "url": "https://go-acme.github.io/lego/dns/variomedia/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VARIOMEDIA_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VARIOMEDIA_API_TOKEN"
+    ]
+  },
+  "vegadns": {
+    "name": "vegadns",
+    "url": "https://go-acme.github.io/lego/dns/vegadns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SECRET_VEGADNS_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>SECRET_VEGADNS_SECRET</code></td>\n<td>API secret</td>\n</tr>\n<tr>\n<td><code>VEGADNS_URL</code></td>\n<td>API endpoint URL</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "SECRET_VEGADNS_KEY",
+      "SECRET_VEGADNS_SECRET",
+      "VEGADNS_URL"
+    ]
+  },
+  "vercel": {
+    "name": "vercel",
+    "url": "https://go-acme.github.io/lego/dns/vercel/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VERCEL_API_TOKEN</code></td>\n<td>Authentication token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VERCEL_API_TOKEN"
+    ]
+  },
+  "versio": {
+    "name": "versio",
+    "url": "https://go-acme.github.io/lego/dns/versio/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VERSIO_PASSWORD</code></td>\n<td>Basic authentication password</td>\n</tr>\n<tr>\n<td><code>VERSIO_USERNAME</code></td>\n<td>Basic authentication username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VERSIO_PASSWORD",
+      "VERSIO_USERNAME"
+    ]
+  },
+  "vinyldns": {
+    "name": "vinyldns",
+    "url": "https://go-acme.github.io/lego/dns/vinyldns/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VINYLDNS_ACCESS_KEY</code></td>\n<td>The VinylDNS API key</td>\n</tr>\n<tr>\n<td><code>VINYLDNS_HOST</code></td>\n<td>The VinylDNS API URL</td>\n</tr>\n<tr>\n<td><code>VINYLDNS_SECRET_KEY</code></td>\n<td>The VinylDNS API Secret key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VINYLDNS_ACCESS_KEY",
+      "VINYLDNS_HOST",
+      "VINYLDNS_SECRET_KEY"
+    ]
+  },
+  "vkcloud": {
+    "name": "vkcloud",
+    "url": "https://go-acme.github.io/lego/dns/vkcloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VK_CLOUD_PASSWORD</code></td>\n<td>Password for VK Cloud account</td>\n</tr>\n<tr>\n<td><code>VK_CLOUD_PROJECT_ID</code></td>\n<td>String ID of project in VK Cloud</td>\n</tr>\n<tr>\n<td><code>VK_CLOUD_USERNAME</code></td>\n<td>Email of VK Cloud account</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VK_CLOUD_PASSWORD",
+      "VK_CLOUD_PROJECT_ID",
+      "VK_CLOUD_USERNAME"
+    ]
+  },
+  "vscale": {
+    "name": "vscale",
+    "url": "https://go-acme.github.io/lego/dns/vscale/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VSCALE_API_TOKEN</code></td>\n<td>API token</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VSCALE_API_TOKEN"
+    ]
+  },
+  "vultr": {
+    "name": "vultr",
+    "url": "https://go-acme.github.io/lego/dns/vultr/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>VULTR_API_KEY</code></td>\n<td>API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "VULTR_API_KEY"
+    ]
+  },
+  "websupport": {
+    "name": "websupport",
+    "url": "https://go-acme.github.io/lego/dns/websupport/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>WEBSUPPORT_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>WEBSUPPORT_SECRET</code></td>\n<td>API secret</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "WEBSUPPORT_API_KEY",
+      "WEBSUPPORT_SECRET"
+    ]
+  },
+  "wedos": {
+    "name": "wedos",
+    "url": "https://go-acme.github.io/lego/dns/wedos/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>WEDOS_USERNAME</code></td>\n<td>Username is the same as for the admin account</td>\n</tr>\n<tr>\n<td><code>WEDOS_WAPI_PASSWORD</code></td>\n<td>Password needs to be generated and IP allowed in the admin interface</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "WEDOS_USERNAME",
+      "WEDOS_WAPI_PASSWORD"
+    ]
+  },
+  "yandex": {
+    "name": "yandex",
+    "url": "https://go-acme.github.io/lego/dns/yandex/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>YANDEX_PDD_TOKEN</code></td>\n<td>Basic authentication username</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "YANDEX_PDD_TOKEN"
+    ]
+  },
+  "yandexcloud": {
+    "name": "yandexcloud",
+    "url": "https://go-acme.github.io/lego/dns/yandexcloud/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>YANDEX_CLOUD_FOLDER_ID</code></td>\n<td>The string id of folder (aka project) in Yandex Cloud</td>\n</tr>\n<tr>\n<td><code>YANDEX_CLOUD_IAM_TOKEN</code></td>\n<td>The base64 encoded json which contains inforamtion about iam token of serivce account with <code>dns.admin</code> permissions</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "YANDEX_CLOUD_FOLDER_ID",
+      "YANDEX_CLOUD_IAM_TOKEN",
+      "dns.admin"
+    ]
+  },
+  "zoneee": {
+    "name": "zoneee",
+    "url": "https://go-acme.github.io/lego/dns/zoneee/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ZONEEE_API_KEY</code></td>\n<td>API key</td>\n</tr>\n<tr>\n<td><code>ZONEEE_API_USER</code></td>\n<td>API user</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ZONEEE_API_KEY",
+      "ZONEEE_API_USER"
+    ]
+  },
+  "zonomi": {
+    "name": "zonomi",
+    "url": "https://go-acme.github.io/lego/dns/zonomi/#credentials",
+    "docs": "\n<table>\n<thead>\n<tr>\n<th>Environment Variable Name</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>ZONOMI_API_KEY</code></td>\n<td>User API key</td>\n</tr>\n</tbody>\n</table>",
+    "vars": [
+      "ZONOMI_API_KEY"
+    ]
+  }
+}

+ 117 - 0
client/src/utils/dns-list.json

@@ -0,0 +1,117 @@
+[
+  "acme-dns",
+  "alidns",
+  "allinkl",
+  "arvancloud",
+  "azure",
+  "auroradns",
+  "autodns",
+  "bindman",
+  "bluecat",
+  "brandit",
+  "bunny",
+  "checkdomain",
+  "civo",
+  "clouddns",
+  "cloudflare",
+  "cloudns",
+  "cloudxns",
+  "conoha",
+  "constellix",
+  "derak",
+  "desec",
+  "designate",
+  "digitalocean",
+  "dnshomede",
+  "dnsimple",
+  "dnsmadeeasy",
+  "dnspod",
+  "dode",
+  "domeneshop",
+  "dreamhost",
+  "duckdns",
+  "dyn",
+  "dynu",
+  "easydns",
+  "edgedns",
+  "epik",
+  "exoscale",
+  "freemyip",
+  "gandi",
+  "gandiv5",
+  "gcloud",
+  "gcore",
+  "glesys",
+  "godaddy",
+  "googledomains",
+  "hetzner",
+  "hostingde",
+  "hosttech",
+  "httpreq",
+  "hurricane",
+  "ibmcloud",
+  "iij",
+  "iijdpf",
+  "infoblox",
+  "infomaniak",
+  "internetbs",
+  "inwx",
+  "ionos",
+  "iwantmyname",
+  "joker",
+  "liara",
+  "lightsail",
+  "linode",
+  "liquidweb",
+  "luadns",
+  "loopia",
+  "mydnsjp",
+  "mythicbeasts",
+  "namecheap",
+  "namedotcom",
+  "namesilo",
+  "nearlyfreespeech",
+  "netcup",
+  "netlify",
+  "nicmanager",
+  "nifcloud",
+  "njalla",
+  "nodion",
+  "ns1",
+  "oraclecloud",
+  "otc",
+  "ovh",
+  "pdns",
+  "plesk",
+  "porkbun",
+  "rackspace",
+  "regru",
+  "rfc2136",
+  "rimuhosting",
+  "route53",
+  "safedns",
+  "sakuracloud",
+  "scaleway",
+  "selectel",
+  "servercow",
+  "simply",
+  "sonic",
+  "stackpath",
+  "tencentcloud",
+  "transip",
+  "ultradns",
+  "variomedia",
+  "vegadns",
+  "vercel",
+  "versio",
+  "vinyldns",
+  "vkcloud",
+  "vscale",
+  "vultr",
+  "websupport",
+  "wedos",
+  "yandex",
+  "yandexcloud",
+  "zoneee",
+  "zonomi"
+]

+ 7 - 19
client/src/utils/indexs.js

@@ -27,23 +27,11 @@ export function isDomain(hostname) {
   return true;
   return true;
 }
 }
 
 
-export function debounce(func, wait, immediate) {
-  var timeout;
-
-  return () => {
-      var context = this, args = arguments;
-
-      var later = () => {
-          timeout = null;
-          if (!immediate) func.apply(context, args);
-      };
-
-      var callNow = immediate && !timeout;
-
+export const debounce = (func, wait) => {
+    let timeout;
+    return function (...args) {
+      const context = this;
       clearTimeout(timeout);
       clearTimeout(timeout);
-
-      timeout = setTimeout(later, wait);
-
-      if (callNow) func.apply(context, args);
-  };
-};
+      timeout = setTimeout(() => func.apply(context, args), wait);
+    };
+  };

+ 49 - 9
client/src/utils/routes.jsx

@@ -3,9 +3,14 @@ import demoicons from './icons.demo.json';
 import logogray from '../assets/images/icons/cosmos_gray.png';
 import logogray from '../assets/images/icons/cosmos_gray.png';
 
 
 import * as Yup from 'yup';
 import * as Yup from 'yup';
+import { Alert, Grid } from '@mui/material';
+import { debounce, isDomain } from './indexs';
+
+import * as API from '../api';
+import { useEffect, useState } from 'react';
 
 
 export const sanitizeRoute = (_route) => {
 export const sanitizeRoute = (_route) => {
-  let route = {..._route};
+  let route = { ..._route };
 
 
   if (!route.UseHost) {
   if (!route.UseHost) {
     route.Host = "";
     route.Host = "";
@@ -13,14 +18,14 @@ export const sanitizeRoute = (_route) => {
   if (!route.UsePathPrefix) {
   if (!route.UsePathPrefix) {
     route.PathPrefix = "";
     route.PathPrefix = "";
   }
   }
-  
+
   route.Name = route.Name.trim();
   route.Name = route.Name.trim();
 
 
-  if(!route.SmartShield) {
+  if (!route.SmartShield) {
     route.SmartShield = {};
     route.SmartShield = {};
   }
   }
 
 
-  if(typeof route._SmartShield_Enabled !== "undefined") {
+  if (typeof route._SmartShield_Enabled !== "undefined") {
     route.SmartShield.Enabled = route._SmartShield_Enabled;
     route.SmartShield.Enabled = route._SmartShield_Enabled;
     delete route._SmartShield_Enabled;
     delete route._SmartShield_Enabled;
   }
   }
@@ -46,13 +51,13 @@ export const getFullOrigin = (route) => {
 const isDemo = import.meta.env.MODE === 'demo';
 const isDemo = import.meta.env.MODE === 'demo';
 
 
 export const getFaviconURL = (route) => {
 export const getFaviconURL = (route) => {
-  if(isDemo) {
+  if (isDemo) {
     if (route.Mode == "STATIC")
     if (route.Mode == "STATIC")
       return Folder;
       return Folder;
     return demoicons[route.Name] || logogray;
     return demoicons[route.Name] || logogray;
   }
   }
 
 
-  if(!route) {
+  if (!route) {
     return logogray;
     return logogray;
   }
   }
 
 
@@ -60,7 +65,7 @@ export const getFaviconURL = (route) => {
     return '/cosmos/api/favicon?q=' + encodeURIComponent(url)
     return '/cosmos/api/favicon?q=' + encodeURIComponent(url)
   }
   }
 
 
-  if(route.Mode == "SERVAPP" || route.Mode == "PROXY") {
+  if (route.Mode == "SERVAPP" || route.Mode == "PROXY") {
     return addRemote(route.Target)
     return addRemote(route.Target)
   } else if (route.Mode == "STATIC") {
   } else if (route.Mode == "STATIC") {
     return Folder;
     return Folder;
@@ -75,6 +80,9 @@ export const ValidateRouteSchema = Yup.object().shape({
   Target: Yup.string().required('Target is required').when('Mode', {
   Target: Yup.string().required('Target is required').when('Mode', {
     is: 'SERVAPP',
     is: 'SERVAPP',
     then: Yup.string().matches(/:[0-9]+$/, 'Invalid Target, must have a port'),
     then: Yup.string().matches(/:[0-9]+$/, 'Invalid Target, must have a port'),
+  }).when('Mode', {
+    is: 'PROXY',
+    then: Yup.string().matches(/^(https?:\/\/)/, 'Invalid Target, must start with http:// or https://'),
   }),
   }),
 
 
   Host: Yup.string().when('UseHost', {
   Host: Yup.string().when('UseHost', {
@@ -96,7 +104,7 @@ export const ValidateRouteSchema = Yup.object().shape({
 })
 })
 
 
 export const ValidateRoute = (routeConfig, config) => {
 export const ValidateRoute = (routeConfig, config) => {
-  let routeNames= config.HTTPConfig.ProxyConfig.Routes.map((r) => r.Name);
+  let routeNames = config.HTTPConfig.ProxyConfig.Routes.map((r) => r.Name);
 
 
   try {
   try {
     ValidateRouteSchema.validateSync(routeConfig);
     ValidateRouteSchema.validateSync(routeConfig);
@@ -114,4 +122,36 @@ export const getContainersRoutes = (config, containerName) => {
     let reg = new RegExp(`^(([a-z]+):\/\/)?${containerName}(:?[0-9]+)?$`, 'i');
     let reg = new RegExp(`^(([a-z]+):\/\/)?${containerName}(:?[0-9]+)?$`, 'i');
     return route.Mode == "SERVAPP" && reg.test(route.Target)
     return route.Mode == "SERVAPP" && reg.test(route.Target)
   })) || [];
   })) || [];
-}
+}
+
+const checkHost = debounce((host, setHostError, setHostIp) => {
+  console.log(host)
+  if (isDomain(host)) {
+    API.getDNS(host).then((data) => {
+      setHostError(null)
+      setHostIp(data.data)
+    }).catch((err) => {
+      setHostError(err.message)
+      setHostIp(null)
+    });
+  } else {
+    setHostError(null);
+    setHostIp(null);
+  }
+}, 500)
+
+export const HostnameChecker = ({hostname}) => {
+  const [hostError, setHostError] = useState(null);
+  const [hostIp, setHostIp] = useState(null);
+
+  useEffect(() => {
+    if (hostname) {
+      checkHost(hostname, setHostError, setHostIp);
+    }
+  }, [hostname]);
+
+  return <>{hostError && <Alert color='error'>{hostError}</Alert>}
+
+    {hostIp && <Alert color='info'>This hostname is pointing to <strong>{hostIp}</strong>, make sure it is your server IP!</Alert>}
+  </>
+};

+ 32 - 0
gen-doc-dns.js

@@ -0,0 +1,32 @@
+const dnsList = require('./client/src/utils/dns-list.json');
+
+console.log(dnsList);
+
+// for each make a request to https://go-acme.github.io/lego/dns/{dns}/#credentials
+
+let finalList = {};
+
+let i = 0;
+(async () => {
+  for(const dns of dnsList) {
+    console.log(`Fetching ${dns} infos`)
+    let result = (await (await fetch(`https://go-acme.github.io/lego/dns/${dns}/#credentials`)).text());
+    result = result.split(`<h2 id="credentials">Credentials</h2>`)[1];
+    result = result.split(`</table>`)[0] + `</table>`;
+    let vars = result.match(/<code>(.*?)<\/code>/g);
+    vars = vars.map(v => v.replace(/<\/?code>/g, ''));
+
+    finalList[dns] = {
+      name: dns,
+      url: `https://go-acme.github.io/lego/dns/${dns}/#credentials`,
+      docs: result,
+      vars: vars
+    }
+
+    console.log(`${i++}/${dnsList.length} done`)
+  }
+
+  // save to file
+  const fs = require('fs');
+  fs.writeFileSync('./client/src/utils/dns-config.json', JSON.stringify(finalList, null, 2));
+})();

+ 8 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
 {
   "name": "cosmos-server",
   "name": "cosmos-server",
-  "version": "0.7.0-unstable2",
+  "version": "0.7.0-unstable4",
   "lockfileVersion": 3,
   "lockfileVersion": 3,
   "requires": true,
   "requires": true,
   "packages": {
   "packages": {
     "": {
     "": {
       "name": "cosmos-server",
       "name": "cosmos-server",
-      "version": "0.7.0-unstable2",
+      "version": "0.7.0-unstable4",
       "dependencies": {
       "dependencies": {
         "@ant-design/colors": "^6.0.0",
         "@ant-design/colors": "^6.0.0",
         "@ant-design/icons": "^4.7.0",
         "@ant-design/icons": "^4.7.0",
@@ -48,6 +48,7 @@
         "react-syntax-highlighter": "^15.5.0",
         "react-syntax-highlighter": "^15.5.0",
         "react-window": "^1.8.7",
         "react-window": "^1.8.7",
         "redux": "^4.2.0",
         "redux": "^4.2.0",
+        "semver-compare": "^1.0.0",
         "simplebar": "^5.3.8",
         "simplebar": "^5.3.8",
         "simplebar-react": "^2.4.1",
         "simplebar-react": "^2.4.1",
         "typescript": "4.8.3",
         "typescript": "4.8.3",
@@ -9160,6 +9161,11 @@
         "semver": "bin/semver.js"
         "semver": "bin/semver.js"
       }
       }
     },
     },
+    "node_modules/semver-compare": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+      "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
+    },
     "node_modules/set-blocking": {
     "node_modules/set-blocking": {
       "version": "2.0.0",
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "cosmos-server",
   "name": "cosmos-server",
-  "version": "0.7.0-unstable4",
+  "version": "0.7.0-unstable5",
   "description": "",
   "description": "",
   "main": "test-server.js",
   "main": "test-server.js",
   "bugs": {
   "bugs": {
@@ -48,6 +48,7 @@
     "react-syntax-highlighter": "^15.5.0",
     "react-syntax-highlighter": "^15.5.0",
     "react-window": "^1.8.7",
     "react-window": "^1.8.7",
     "redux": "^4.2.0",
     "redux": "^4.2.0",
+    "semver-compare": "^1.0.0",
     "simplebar": "^5.3.8",
     "simplebar": "^5.3.8",
     "simplebar-react": "^2.4.1",
     "simplebar-react": "^2.4.1",
     "typescript": "4.8.3",
     "typescript": "4.8.3",

+ 2 - 0
readme.md

@@ -5,6 +5,8 @@
 <!-- sponsors -->
 <!-- sponsors -->
 <h3 align="center">Thanks to the sponsors:</h3></br>
 <h3 align="center">Thanks to the sponsors:</h3></br>
 <p align="center"><a href="https://github.com/zarevskaya"><img src="https://avatars.githubusercontent.com/zarevskaya" style="border-radius:48px" width="48" height="48" alt="zarev" title="zarev" /></a>
 <p align="center"><a href="https://github.com/zarevskaya"><img src="https://avatars.githubusercontent.com/zarevskaya" style="border-radius:48px" width="48" height="48" alt="zarev" title="zarev" /></a>
+<a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/DrMxrcy" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
+<a href="https://github.com/JustDalek"><img src="https://avatars.githubusercontent.com/JustDalek" style="border-radius:48px" width="48" height="48" alt="JustDalek" title="JustDalek" /></a>
 </p><!-- /sponsors -->
 </p><!-- /sponsors -->
 
 
 ---
 ---

+ 35 - 8
src/docker/api_blueprint.go

@@ -37,6 +37,7 @@ type ContainerCreateRequestContainer struct {
 	Volumes     []mount.Mount          `json:"volumes"`
 	Volumes     []mount.Mount          `json:"volumes"`
 	Networks    map[string]ContainerCreateRequestServiceNetwork `json:"networks"`
 	Networks    map[string]ContainerCreateRequestServiceNetwork `json:"networks"`
 	Routes 			[]utils.ProxyRouteConfig          `json:"routes"`
 	Routes 			[]utils.ProxyRouteConfig          `json:"routes"`
+	Links       []string  `json:"links,omitempty"`
 
 
 	RestartPolicy  string            `json:"restart,omitempty"`
 	RestartPolicy  string            `json:"restart,omitempty"`
 	Devices        []string          `json:"devices"`
 	Devices        []string          `json:"devices"`
@@ -49,6 +50,8 @@ type ContainerCreateRequestContainer struct {
 	Entrypoint string `json:"entrypoint,omitempty"`
 	Entrypoint string `json:"entrypoint,omitempty"`
 	WorkingDir string `json:"working_dir,omitempty"`
 	WorkingDir string `json:"working_dir,omitempty"`
 	User string `json:"user,omitempty"`
 	User string `json:"user,omitempty"`
+	UID int `json:"uid,omitempty"`
+	GID int `json:"gid,omitempty"`
 	Hostname string `json:"hostname,omitempty"`
 	Hostname string `json:"hostname,omitempty"`
 	Domainname string `json:"domainname,omitempty"`
 	Domainname string `json:"domainname,omitempty"`
 	MacAddress string `json:"mac_address,omitempty"`
 	MacAddress string `json:"mac_address,omitempty"`
@@ -66,7 +69,6 @@ type ContainerCreateRequestContainer struct {
 	DNS []string `json:"dns,omitempty"`
 	DNS []string `json:"dns,omitempty"`
 	DNSSearch []string `json:"dns_search,omitempty"`
 	DNSSearch []string `json:"dns_search,omitempty"`
 	ExtraHosts []string `json:"extra_hosts,omitempty"`
 	ExtraHosts []string `json:"extra_hosts,omitempty"`
-	Links []string `json:"links,omitempty"`
 	SecurityOpt []string `json:"security_opt,omitempty"`
 	SecurityOpt []string `json:"security_opt,omitempty"`
 	StorageOpt map[string]string `json:"storage_opt,omitempty"`
 	StorageOpt map[string]string `json:"storage_opt,omitempty"`
 	Sysctls map[string]string `json:"sysctls,omitempty"`
 	Sysctls map[string]string `json:"sysctls,omitempty"`
@@ -412,8 +414,8 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 		PortBindings := nat.PortMap{}
 		PortBindings := nat.PortMap{}
 
 
 		for _, port := range container.Ports {
 		for _, port := range container.Ports {
-			portContainer := strings.Split(port, ":")[0]
-			portHost := strings.Split(port, ":")[1]			
+			portHost := strings.Split(port, ":")[0]
+			portContainer := strings.Split(port, ":")[1]
 
 
 			containerConfig.ExposedPorts[nat.Port(portContainer)] = struct{}{}
 			containerConfig.ExposedPorts[nat.Port(portContainer)] = struct{}{}
 			PortBindings[nat.Port(portContainer)] = []nat.PortBinding{
 			PortBindings[nat.Port(portContainer)] = []nat.PortBinding{
@@ -445,20 +447,26 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 					utils.Log(fmt.Sprintf("Not found. Creating directory %s for bind mount", newSource))
 					utils.Log(fmt.Sprintf("Not found. Creating directory %s for bind mount", newSource))
 					OnLog(fmt.Sprintf("Not found. Creating directory %s for bind mount\n", newSource))
 					OnLog(fmt.Sprintf("Not found. Creating directory %s for bind mount\n", newSource))
 	
 	
-					err := os.MkdirAll(newSource, 0755)
+					err := os.MkdirAll(newSource, 0750)
 					if err != nil {
 					if err != nil {
 						utils.Error("CreateService: Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory", err)
 						utils.Error("CreateService: Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory", err)
 						OnLog(fmt.Sprintf("[ERROR] Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory: %s\n", err.Error()))
 						OnLog(fmt.Sprintf("[ERROR] Unable to create directory for bind mount. Make sure parent directories exist, and that Cosmos has permissions to create directories in the host directory: %s\n", err.Error()))
 						Rollback(rollbackActions, OnLog)
 						Rollback(rollbackActions, OnLog)
 						return err
 						return err
 					}
 					}
-		
-					if container.User != "" {
+					if container.UID != 0 {
+						// Change the ownership of the directory to the container.UID
+						err = os.Chown(newSource, container.UID, container.GID)
+						if err != nil {
+							utils.Error("CreateService: Unable to change ownership of directory", err)
+							OnLog(fmt.Sprintf("[ERROR] Unable to change ownership of directory: " + err.Error()))
+						}
+					} else if container.User != "" {
 						// Change the ownership of the directory to the container.User
 						// Change the ownership of the directory to the container.User
 						userInfo, err := user.Lookup(container.User)
 						userInfo, err := user.Lookup(container.User)
 						if err != nil {
 						if err != nil {
 							utils.Error("CreateService: Unable to lookup user", err)
 							utils.Error("CreateService: Unable to lookup user", err)
-							OnLog(fmt.Sprintf("[ERROR] Unable to lookup user " + container.User + "." +err.Error()))
+							OnLog(fmt.Sprintf("[ERROR] Unable to lookup user " + container.User + ". " +err.Error()))
 						} else {
 						} else {
 							uid, _ := strconv.Atoi(userInfo.Uid)
 							uid, _ := strconv.Atoi(userInfo.Uid)
 							gid, _ := strconv.Atoi(userInfo.Gid)
 							gid, _ := strconv.Atoi(userInfo.Gid)
@@ -484,7 +492,6 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 			DNS:         container.DNS,
 			DNS:         container.DNS,
 			DNSSearch:   container.DNSSearch,
 			DNSSearch:   container.DNSSearch,
 			ExtraHosts:  container.ExtraHosts,
 			ExtraHosts:  container.ExtraHosts,
-			Links:       container.Links,
 			SecurityOpt: container.SecurityOpt,
 			SecurityOpt: container.SecurityOpt,
 			StorageOpt:  container.StorageOpt,
 			StorageOpt:  container.StorageOpt,
 			Sysctls:     container.Sysctls,
 			Sysctls:     container.Sysctls,
@@ -566,6 +573,26 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 			}
 			}
 		}
 		}
 		
 		
+
+		// Create the networks for links
+		for _, targetContainer := range container.Links {
+			if strings.Contains(targetContainer, ":") {
+				err = errors.New("Link network cannot contain ':' please use container name only")
+				utils.Error("CreateService: Rolling back changes because of -- Link network", err)
+				OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Link network creation error: "+err.Error()))
+				Rollback(rollbackActions, OnLog)
+				return err
+			}
+
+			err = CreateLinkNetwork(container.Name, targetContainer)
+			if err != nil {
+				utils.Error("CreateService: Rolling back changes because of -- Link network", err)
+				OnLog(fmt.Sprintf("[ERROR] Rolling back changes because of -- Link network creation error: "+err.Error()))
+				Rollback(rollbackActions, OnLog)
+				return err
+			}
+		}
+		
 		// Write a response to the client
 		// Write a response to the client
 		utils.Log(fmt.Sprintf("Container %s created", container.Name))
 		utils.Log(fmt.Sprintf("Container %s created", container.Name))
 		OnLog(fmt.Sprintf("Container %s created", container.Name))
 		OnLog(fmt.Sprintf("Container %s created", container.Name))

+ 35 - 0
src/docker/network.go

@@ -246,6 +246,41 @@ func _debounceNetworkCleanUp() func(string) {
 	}
 	}
 }
 }
 
 
+func CreateLinkNetwork(containerName string, container2Name string) error {
+	// create network
+	networkName := "cosmos-link-" + containerName + "-" + container2Name + "-" + utils.GenerateRandomString(2)
+	_, err := DockerClient.NetworkCreate(DockerContext, networkName, types.NetworkCreate{
+		CheckDuplicate: true,
+		Labels: map[string]string{
+			"cosmos-link": "true",
+			"cosmos-link-name": networkName,
+			"cosmos-link-container1": containerName,
+			"cosmos-link-container2": container2Name,
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	// connect containers to network
+	err = ConnectToNetworkSync(networkName, containerName)
+	if err != nil {
+		return err
+	}
+
+	err = ConnectToNetworkSync(networkName, container2Name)
+	if err != nil {
+		// disconnect first container
+		DockerClient.NetworkDisconnect(DockerContext, networkName, containerName, true)
+		// destroy network
+		DockerClient.NetworkRemove(DockerContext, networkName)
+		return err
+	}
+
+	return nil
+}
+
 var DebouncedNetworkCleanUp = _debounceNetworkCleanUp()
 var DebouncedNetworkCleanUp = _debounceNetworkCleanUp()
 
 
 func NetworkCleanUp() {
 func NetworkCleanUp() {

+ 7 - 0
src/httpServer.go

@@ -57,6 +57,13 @@ func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
 	var certReloader *simplecert.CertReloader 
 	var certReloader *simplecert.CertReloader 
 	var errSimCert error
 	var errSimCert error
 	if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
 	if(config.HTTPConfig.HTTPSCertificateMode == utils.HTTPSCertModeList["LETSENCRYPT"]) {
+		if(config.HTTPConfig.DNSChallengeProvider != "") {
+			newEnv := config.HTTPConfig.DNSChallengeConfig
+			for key, value := range newEnv {
+				os.Setenv(key, value)
+			}
+		}
+
 		certReloader, errSimCert = simplecert.Init(cfg, nil)
 		certReloader, errSimCert = simplecert.Init(cfg, nil)
 		if errSimCert != nil {
 		if errSimCert != nil {
 			  // Temporary before we have a better way to handle this
 			  // Temporary before we have a better way to handle this

+ 2 - 0
src/newInstall.go

@@ -34,6 +34,7 @@ type NewInstallJSON struct {
 	SSLEmail string `json:"sslEmail",validate:"omitempty,email"`
 	SSLEmail string `json:"sslEmail",validate:"omitempty,email"`
 	UseWildcardCertificate bool `json:"useWildcardCertificate",validate:"omitempty"`
 	UseWildcardCertificate bool `json:"useWildcardCertificate",validate:"omitempty"`
 	DNSChallengeProvider string `json:"dnsChallengeProvider",validate:"omitempty"`
 	DNSChallengeProvider string `json:"dnsChallengeProvider",validate:"omitempty"`
+	DNSChallengeConfig map[string]string
 }
 }
 
 
 type AdminJSON struct {
 type AdminJSON struct {
@@ -110,6 +111,7 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) {
 			newConfig.HTTPConfig.SSLEmail = request.SSLEmail
 			newConfig.HTTPConfig.SSLEmail = request.SSLEmail
 			newConfig.HTTPConfig.UseWildcardCertificate = request.UseWildcardCertificate
 			newConfig.HTTPConfig.UseWildcardCertificate = request.UseWildcardCertificate
 			newConfig.HTTPConfig.DNSChallengeProvider = request.DNSChallengeProvider
 			newConfig.HTTPConfig.DNSChallengeProvider = request.DNSChallengeProvider
+			newConfig.HTTPConfig.DNSChallengeConfig = request.DNSChallengeConfig
 			newConfig.HTTPConfig.TLSCert = request.TLSCert
 			newConfig.HTTPConfig.TLSCert = request.TLSCert
 			newConfig.HTTPConfig.TLSKey = request.TLSKey
 			newConfig.HTTPConfig.TLSKey = request.TLSKey
 
 

+ 1 - 1
src/user/login.go

@@ -32,7 +32,7 @@ func UserLogin(w http.ResponseWriter, req *http.Request) {
 		c, errCo := utils.GetCollection(utils.GetRootAppId(), "users")
 		c, errCo := utils.GetCollection(utils.GetRootAppId(), "users")
 		if errCo != nil {
 		if errCo != nil {
 				utils.Error("Database Connect", errCo)
 				utils.Error("Database Connect", errCo)
-				utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001")
+				utils.HTTPError(w, "Database Error", http.StatusInternalServerError, "DB001")
 				return
 				return
 		}
 		}
 
 

+ 1 - 0
src/utils/types.go

@@ -105,6 +105,7 @@ type HTTPConfig struct {
 	SSLEmail string `validate:"omitempty,email"`
 	SSLEmail string `validate:"omitempty,email"`
 	UseWildcardCertificate bool
 	UseWildcardCertificate bool
 	AcceptAllInsecureHostname bool
 	AcceptAllInsecureHostname bool
+	DNSChallengeConfig map[string]string
 } 
 } 
 
 
 const (
 const (