diff --git a/build-dev.sh b/build-dev.sh index 671ccdf..a171be3 100644 --- a/build-dev.sh +++ b/build-dev.sh @@ -2,6 +2,10 @@ echo " ---- Build Cosmos ----" +# Set target architecture for ARM64 +# export GOOS=linux +# export GOARCH=arm64 + rm -rf build cp src/update.go src/launcher/update.go diff --git a/changelog.md b/changelog.md index 19e5d83..5310a04 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,14 @@ +## Version 0.19.0 + - Constellation allows nodes to see and ping each others + - Constellation now has a firewall! + - Constellation now has exit nodes + - Improve docker image cleanup efficiency + - Improve support for container network modes in import/export + - Fixed the annoying "user unauthenticated" error when opening the homepage after the admin token expired + - Fixed issue with exporting hostname when it would be incompatible to re-importing it + - Updating network mode now also updates the network-mode label + - Default storage path is now /cosmos-storage instead of /usr + ## Version 0.18.4 - Fix issue with DB credentials dissapearing - Remove expired discount diff --git a/client/src/api/constellation.demo.tsx b/client/src/api/constellation.demo.tsx index 5782c3a..54b9bc7 100644 --- a/client/src/api/constellation.demo.tsx +++ b/client/src/api/constellation.demo.tsx @@ -128,6 +128,15 @@ function ping() { }); } +function pingDevice() { + return new Promise((resolve, reject) => { + resolve({ + "status": "ok", + "data": 1 + }) + }); +} + export { list, addDevice, @@ -138,4 +147,5 @@ export { connect, block, ping, + pingDevice }; \ No newline at end of file diff --git a/client/src/api/constellation.tsx b/client/src/api/constellation.tsx index ffa1561..73f788b 100644 --- a/client/src/api/constellation.tsx +++ b/client/src/api/constellation.tsx @@ -18,6 +18,15 @@ function ping() { })) } +function pingDevice(deviceId) { + return wrap(fetch(`/cosmos/api/constellation/devices/${deviceId}/ping`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + })) +} + function addDevice(device) { return wrap(fetch('/cosmos/api/constellation/devices', { method: 'POST', @@ -128,4 +137,5 @@ export { connect, block, ping, + pingDevice, }; \ No newline at end of file diff --git a/client/src/pages/config/users/configman.jsx b/client/src/pages/config/users/configman.jsx index 95ea500..2ce6c37 100644 --- a/client/src/pages/config/users/configman.jsx +++ b/client/src/pages/config/users/configman.jsx @@ -139,7 +139,7 @@ const ConfigManagement = () => { SkipPruneNetwork: config.DockerConfig.SkipPruneNetwork, SkipPruneImages: config.DockerConfig.SkipPruneImages, - DefaultDataPath: config.DockerConfig.DefaultDataPath || "/usr", + DefaultDataPath: config.DockerConfig.DefaultDataPath || "/cosmos-storage", Background: config && config.HomepageConfig && config.HomepageConfig.Background, Expanded: config && config.HomepageConfig && config.HomepageConfig.Expanded, @@ -817,7 +817,7 @@ const ConfigManagement = () => { label={t('mgmt.config.docker.defaultDatapathInput.defaultDatapathLabel')} name="DefaultDataPath" formik={formik} - placeholder={'/usr'} + placeholder={'/cosmos-storage'} /> diff --git a/client/src/pages/config/users/containerPicker.jsx b/client/src/pages/config/users/containerPicker.jsx index 2fb3ba0..d99f67f 100644 --- a/client/src/pages/config/users/containerPicker.jsx +++ b/client/src/pages/config/users/containerPicker.jsx @@ -34,7 +34,7 @@ import defaultport from '../../servapps/defaultport.json'; import * as API from '../../../api'; -export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetContainer, onTargetChange, label = "Container Name", name = "Target"}) { +export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetContainer, onTargetChange, label, name = "Target"}) { const { t } = useTranslation(); const [open, setOpen] = React.useState(false); const [containers, setContainers] = React.useState([]); @@ -179,7 +179,7 @@ export function CosmosContainerPicker({formik, nameOnly, lockTarget, TargetConta return ( - {t('mgmt.config.containerPicker.containerNameSelection.containerNameLabel')} + {label || t('mgmt.config.containerPicker.containerNameSelection.containerNameLabel')} {!loading && { PublicHostname: '', IsRelay: true, isLighthouse: false, + invisible: false, }} validationSchema={yup.object({ @@ -185,6 +186,12 @@ const AddDeviceModal = ({ users, config, refreshConfig, devices }) => { formik={formik} /> */} + + {formik.values.isLighthouse && <> diff --git a/client/src/pages/constellation/vpn.jsx b/client/src/pages/constellation/vpn.jsx index be2ad09..a12f1bc 100644 --- a/client/src/pages/constellation/vpn.jsx +++ b/client/src/pages/constellation/vpn.jsx @@ -1,11 +1,11 @@ import React from "react"; import { useEffect, useState } from "react"; -import * as API from "../../api"; +import * as API from "../../api"; import AddDeviceModal from "./addDevice"; import PrettyTableView from "../../components/tableView/prettyTableView"; import { DeleteButton } from "../../components/delete"; -import { CloudOutlined, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, SyncOutlined, TabletOutlined } from "@ant-design/icons"; -import { Alert, Button, CircularProgress, IconButton, Stack, Tooltip } from "@mui/material"; +import { CloudOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, SyncOutlined, TabletOutlined } from "@ant-design/icons"; +import { Alert, Button, Chip, CircularProgress, IconButton, Stack, Switch, Tooltip } from "@mui/material"; import { CosmosCheckbox, CosmosFormDivider, CosmosInputText } from "../config/users/formShortcuts"; import MainCard from "../../components/MainCard"; import { Formik } from "formik"; @@ -22,23 +22,25 @@ import { autoBatchEnhancer } from "@reduxjs/toolkit"; const getDefaultConstellationHostname = (config) => { // if domain is set, use it - if(isDomain(config.HTTPConfig.Hostname)) { + if (isDomain(config.HTTPConfig.Hostname)) { return "vpn." + config.HTTPConfig.Hostname; } else { return config.HTTPConfig.Hostname; } } -export const ConstellationVPN = ({freeVersion}) => { +export const ConstellationVPN = ({ freeVersion }) => { const { t } = useTranslation(); const [config, setConfig] = useState(null); const [users, setUsers] = useState(null); const [devices, setDevices] = useState(null); const [resynDevice, setResyncDevice] = useState(null); // [nickname, deviceName] - const {role} = useClientInfos(); + const { role } = useClientInfos(); const isAdmin = role === "2"; const [ping, setPing] = useState(0); const [coStatus, setCoStatus] = React.useState(null); + const [devicePingStatus, setDevicePingStatus] = useState({}); // {deviceName: 'loading' | 'success' | 'error'} + const [firewallLoading, setFirewallLoading] = useState(null); // deviceName being toggled const refreshStatus = () => { API.getStatus().then((res) => { @@ -46,6 +48,69 @@ export const ConstellationVPN = ({freeVersion}) => { }); } + const pingDevices = async (deviceList) => { + if (!deviceList || deviceList.length === 0) return; + + // Initialize all devices as loading + const initialStatus = {}; + deviceList.forEach(device => { + initialStatus[device.deviceName] = 'loading'; + }); + setDevicePingStatus(initialStatus); + + // Ping devices 5 at a time + const batchSize = 5; + for (let i = 0; i < deviceList.length; i += batchSize) { + const batch = deviceList.slice(i, i + batchSize); + const pingPromises = batch.map(device => + API.constellation.pingDevice(device.deviceName) + .then(res => { + setDevicePingStatus(prev => ({ + ...prev, + [device.deviceName]: res.data.reachable ? 'success' : 'error' + })); + }) + .catch(() => { + setDevicePingStatus(prev => ({ + ...prev, + [device.deviceName]: 'error' + })); + }) + ); + await Promise.all(pingPromises); + } + }; + + const isFirewallBlocked = (deviceName) => { + if (!config?.ConstellationConfig?.FirewallBlockedClients) { + return false; + } + return config.ConstellationConfig.FirewallBlockedClients.includes(deviceName); + }; + + const toggleFirewallBlock = async (deviceName, isBlocked) => { + setFirewallLoading(deviceName); + + let newConfig = { ...config }; + if (!newConfig.ConstellationConfig.FirewallBlockedClients) { + newConfig.ConstellationConfig.FirewallBlockedClients = []; + } + + if (isBlocked) { + // Remove from blocked list + newConfig.ConstellationConfig.FirewallBlockedClients = + newConfig.ConstellationConfig.FirewallBlockedClients.filter(d => d !== deviceName); + } else { + // Add to blocked list + if (!newConfig.ConstellationConfig.FirewallBlockedClients.includes(deviceName)) { + newConfig.ConstellationConfig.FirewallBlockedClients.push(deviceName); + } + } + + await API.config.set(newConfig); + setFirewallLoading(null); + }; + let constellationEnabled = config && config.ConstellationConfig.Enabled; const refreshConfig = async () => { @@ -53,14 +118,17 @@ export const ConstellationVPN = ({freeVersion}) => { refreshStatus(); let configAsync = await API.config.get(); setConfig(configAsync.data); - setDevices((await API.constellation.list()).data || []); - if(isAdmin) + const deviceList = (await API.constellation.list()).data || []; + setDevices(deviceList); + if (isAdmin) setUsers((await API.users.list()).data || []); - else + else setUsers([]); - if(configAsync.data.ConstellationConfig.Enabled) { + if (configAsync.data.ConstellationConfig.Enabled) { setPing((await API.constellation.ping()).data ? 2 : 1); + // Ping devices after loading + pingDevices(deviceList.filter((d) => !d.blocked)); } }; @@ -92,177 +160,255 @@ export const ConstellationVPN = ({freeVersion}) => { () => setResyncDevice(null) } /> } - -
- {constellationEnabled && coStatus.ConstellationSlaveIPWarning && - {coStatus.ConstellationSlaveIPWarning} - } + +
+ {constellationEnabled && coStatus && coStatus.ConstellationSlaveIPWarning && + {coStatus.ConstellationSlaveIPWarning} + } - {!freeVersion && - , ]} - /> - } - - - {constellationEnabled && config.ConstellationConfig.SlaveMode && isAdmin && <> - - {t('mgmt.constellation.externalTextSlaveNoAdmin')} - - } - {constellationEnabled && config.ConstellationConfig.SlaveMode && isAdmin && <> - - {t('mgmt.constellation.externalText')} - - } - {!constellationEnabled && !isAdmin && <> - - {t('mgmt.constellation.setupTextNoAdmin')} - - } - {(isAdmin || constellationEnabled) && { - let newConfig = { ...config }; - newConfig.ConstellationConfig.Enabled = values.Enabled; - newConfig.ConstellationConfig.PrivateNode = values.PrivateNode; - newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay; - newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname; - newConfig.ConstellationConfig.DoNotSyncNodes = !values.SyncNodes; - setTimeout(() => { - refreshConfig(); - }, 1500); - return API.config.set(newConfig); - }} - > - {(formik) => ( -
- - {isAdmin && constellationEnabled && - - - - { - await API.constellation.reset(); - refreshConfig(); - }} - /> - } - - {constellationEnabled &&
- {t('mgmt.constellation.constStatus')}: {[ - , - {t('mgmt.constellation.constStatusDown')}, - {t('mgmt.constellation.constStatusConnected')}, - ][ping]} + {!freeVersion && + , ]} + /> + } + + + {constellationEnabled && config.ConstellationConfig.SlaveMode && isAdmin && <> + + {t('mgmt.constellation.externalTextSlaveNoAdmin')} + + } + {constellationEnabled && config.ConstellationConfig.SlaveMode && isAdmin && <> + + {t('mgmt.constellation.externalText')} + + } + {!constellationEnabled && !isAdmin && <> + + {t('mgmt.constellation.setupTextNoAdmin')} + + } + {(isAdmin || constellationEnabled) && { + let newConfig = { ...config }; + newConfig.ConstellationConfig.Enabled = values.Enabled; + newConfig.ConstellationConfig.PrivateNode = values.PrivateNode; + newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay; + newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname; + newConfig.ConstellationConfig.DoNotSyncNodes = !values.SyncNodes; + setTimeout(() => { + refreshConfig(); + }, 1500); + return API.config.set(newConfig); + }} + > + {(formik) => ( + + + {isAdmin && constellationEnabled && + + + + { + await API.constellation.reset(); + refreshConfig(); + }} + /> + } - { - setPing(0); - setPing((await API.constellation.ping()).data ? 2 : 1); - }}> - - -
} + {constellationEnabled &&
+ {t('mgmt.constellation.constStatus')}: {[ + , + {t('mgmt.constellation.constStatusDown')}, + {t('mgmt.constellation.constStatusConnected')}, + ][ping]} - {!freeVersion && <> - - - {constellationEnabled && !config.ConstellationConfig.SlaveMode && <> - {formik.values.Enabled && <> - - - - {!formik.values.PrivateNode && <> - - + { + setPing(0); + setPing((await API.constellation.ping()).data ? 2 : 1); + }}> + + +
} + + {!freeVersion && <> + + + {constellationEnabled && !config.ConstellationConfig.SlaveMode && <> + {formik.values.Enabled && <> + + {devices.length > 0 && {t('mgmt.constellation.setup.deviceConnectedWarn')}} + 0} formik={formik} name="IsRelay" label={t('mgmt.constellation.setup.relayRequests.label')} /> + 0} formik={formik} name="PrivateNode" label={t('mgmt.constellation.setup.privNode.label')} /> + {!formik.values.PrivateNode && <> + + 0} formik={formik} name="ConstellationHostname" label={'Constellation ' + t('global.hostname')} /> + } + } + } + + {isAdmin && <> + {t('global.saveAction')} + + } } - } - } + {isAdmin && <> { + let file = e.target.files[0]; + await API.constellation.connect(file); + setTimeout(() => { + refreshConfig(); + }, 1000); + }} + />} +
+
+ )} +
} +
+
+
+ {config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <> + + !d.blocked)} + getKey={(r) => r.deviceName} + buttons={[ + , + + ]} + columns={[ + { + title: '', + field: getIcon, + }, + { + title: t('mgmt.constellation.setup.deviceName.label'), + field: (r) => { - {isAdmin && <> - {t('global.saveAction')} - - } - } - {isAdmin && <> { - let file = e.target.files[0]; - await API.constellation.connect(file); - setTimeout(() => { - refreshConfig(); - }, 1000); - }} - />} -
- - )} - } - - -
- {config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <> - - !d.blocked)} - getKey={(r) => r.deviceName} - buttons={[ - , - ]} - columns={[ - { - title: '', - field: getIcon, + const status = devicePingStatus[r.deviceName]; + let res = ""; + + if (status === 'loading') { + res = ; + } else if (status === 'success') { + res = "🟢"; + } else if (status === 'error') { + res = "🔴"; + } + + return {res} {r.deviceName}; + } }, { - title: t('mgmt.constellation.setup.deviceName.label'), - field: (r) => {r.deviceName}, + title: t('mgmt.constellation.setup.owner.label'), + field: (r) => {r.nickname}, }, { - title: t('mgmt.constellation.setup.owner.label'), - field: (r) => {r.nickname}, + title: t('mgmt.storage.typeTitle'), + field: (r) => {r.isLighthouse ? "Lighthouse" : "Client"}, }, { - title: t('mgmt.storage.typeTitle'), - field: (r) => {r.isLighthouse ? "Lighthouse" : "Client"}, + title: t('mgmt.constellation.setup.ipTitle'), + screenMin: 'md', + field: (r) => r.ip, }, { - title: t('mgmt.constellation.setup.ipTitle'), - screenMin: 'md', - field: (r) => r.ip, + title: t('mgmt.constellation.setup.firewallStatus'), + field: (r) => { + const blocked = isFirewallBlocked(r.deviceName); + const isLoading = firewallLoading === r.deviceName; + + if (isLoading) { + return + + Updating... + + } + color="default" + />; + } + + return blocked ? + + Blocked + + } + color="error" + onClick={() => toggleFirewallBlock(r.deviceName, blocked)} + style={{ cursor: 'pointer' }} + /> : + + Allowed + + } + color="success" + onClick={() => toggleFirewallBlock(r.deviceName, blocked)} + style={{ cursor: 'pointer' }} + />; + }, }, { title: '', @@ -281,14 +427,14 @@ export const ConstellationVPN = ({freeVersion}) => { } } - ]} - /> - } -
+ ]} + /> + } +
:
} - {freeVersion && config && !constellationEnabled && } + {freeVersion && config && !constellationEnabled && } }; \ No newline at end of file diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index de31d94..fdd2cc7 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -124,7 +124,7 @@ const convertDockerCompose = (config, serviceName, dockerCompose, setYmlError) = if(doc.services[key].volumes) Object.values(doc.services[key].volumes).forEach((volume) => { if (volume.source && volume.source[0] === '.') { - let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr" + let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/cosmos-storage" volume.source = defaultPath + volume.source.replace('.', ''); } }); @@ -324,7 +324,7 @@ const convertDockerCompose = (config, serviceName, dockerCompose, setYmlError) = // for each network mode that are container, add a label and remove hostname Object.keys(doc.services).forEach((key) => { - if (doc.services[key].network_mode && doc.services[key].network_mode.startsWith('container:')) { + if (doc.services[key].network_mode && (doc.services[key].network_mode.startsWith('service:') || doc.services[key].network_mode.startsWith('container:'))) { doc.services[key].labels = doc.services[key].labels || {}; doc.services[key].labels['cosmos-force-network-mode'] = doc.services[key].network_mode; @@ -549,7 +549,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul Passwords: passwords, CPU_ARCH: API.CPU_ARCH, CPU_AVX: API.CPU_AVX, - DefaultDataPath: (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr", + DefaultDataPath: (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/cosmos-storage", }); let jsoned; diff --git a/client/src/pages/servapps/containers/network.jsx b/client/src/pages/servapps/containers/network.jsx index c90008f..06320ba 100644 --- a/client/src/pages/servapps/containers/network.jsx +++ b/client/src/pages/servapps/containers/network.jsx @@ -12,6 +12,7 @@ import { NetworksColumns } from '../networks'; import NewNetworkButton from '../createNetwork'; import LinkContainersButton from '../linkContainersButton'; import { useTranslation } from 'react-i18next'; +import { CosmosContainerPicker } from '../../config/users/containerPicker'; const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, OnChange, OnConnect, OnDisconnect }) => { const { t } = useTranslation(); @@ -93,6 +94,11 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O initialValues={{ networkMode: containerInfo.HostConfig.NetworkMode, ports: getPortBindings(), + Container: (() => { + if(!containerInfo || !containerInfo.HostConfig || !containerInfo.HostConfig.NetworkMode) return ""; + if(!containerInfo.HostConfig.NetworkMode.startsWith("container:")) return ""; + return containerInfo.HostConfig.NetworkMode.replace("container:", ""); + })() }} validate={(values) => { const errors = {}; @@ -142,16 +148,29 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O {t('mgmt.servApps.networks.containerotRunningWarning')} )} - {isForceSecure && ( + {/* {isForceSecure && ( {t('mgmt.servApps.networks.forcedSecurityWarning')} - )} + )} */} { + formik.setFieldValue('Container', ''); + }} + /> + { + if(!name) return; + formik.setFieldValue('networkMode', `container:${name}`); + }} + name='Container' + label={t('mgmt.servApps.networks.useAsVPN')} + nameOnly />
diff --git a/client/src/pages/servapps/servapps.jsx b/client/src/pages/servapps/servapps.jsx index 9dc7196..9e4eda7 100644 --- a/client/src/pages/servapps/servapps.jsx +++ b/client/src/pages/servapps/servapps.jsx @@ -354,11 +354,12 @@ const ServApps = ({stack}) => { Ports - {app.ports.filter(p => p.IP != '::').map((port) => { + {console.log(app.ports)} + {app.ports && (app.ports.filter(p => p && p.IP != '::').map((port) => { return - })} + }))} diff --git a/client/src/utils/hooks.js b/client/src/utils/hooks.js index 6a7ee26..38a9394 100644 --- a/client/src/utils/hooks.js +++ b/client/src/utils/hooks.js @@ -19,15 +19,26 @@ function useClientInfos() { // Try to parse the cookie into a JavaScript object clientInfos = cookies['client-infos'].split(','); - if(clientInfos.length !== 3) { + if(clientInfos.length <= 3) { window.location.href = '/cosmos-ui/logout'; } - return { + let res = { nickname: clientInfos[0], userRole: clientInfos[1], role: clientInfos[2] }; + + if(clientInfos.length > 3) { + let roleUntil = new Date(parseInt(clientInfos[3], 10) * 1000); + let currentDate = new Date(); + + if(roleUntil < currentDate && res.userRole == "2") { + res.userRole = "1"; + } + } + + return res; } catch (error) { console.error('Error parsing client-infos cookie:', error); return { diff --git a/client/src/utils/locales/en/translation.json b/client/src/utils/locales/en/translation.json index 680ca17..ec4e825 100644 --- a/client/src/utils/locales/en/translation.json +++ b/client/src/utils/locales/en/translation.json @@ -273,6 +273,9 @@ "mgmt.constellation.setup.relayRequests.label": "Relay requests via this Node", "mgmt.constellation.setup.unsafeRoutesText": "Coming soon. This feature will allow you to tunnel your traffic through your devices to things outside of your constellation.", "mgmt.constellation.setup.unsafeRoutesTitle": "Unsafe Routes", + "mgmt.constellation.setup.repingAll": "Ping All", + "mgmt.constellation.setup.deviceConnectedWarn": "The following settings cannot be changed once clients have been created. Reset your network to change them.", + "mgmt.constellation.setup.firewallStatus": "Firewall", "mgmt.constellation.setupText": "Constellation is a VPN that runs inside your Cosmos network. It automatically connects all your devices together, and allows you to access them from anywhere. Please refer to the <0>documentation for more information. In order to connect, please use the <1>Constellation App", "mgmt.constellation.setupTitle": "Constellation Setup", "mgmt.constellation.setuplighthouseTitle": "Lighthouse Setup", @@ -363,6 +366,7 @@ "mgmt.servApps.networks.removedNetConnectedEitherRecreate": "Either re-create it or", "mgmt.servApps.networks.removedNetConnectedError": "You are connected to a network that has been removed:", "mgmt.servApps.networks.updatePortsButton": "Update Ports", + "mgmt.servApps.networks.useAsVPN": "Use this container as VPN or Network controller", "mgmt.servApps.newChip.newLabel": "New", "mgmt.servApps.newContainer.chooseUrl": "Choose URL for", "mgmt.servApps.newContainer.cosmosOutdatedError": "This service requires a newer version of Cosmos. Please update Cosmos to install this service.", diff --git a/package-lock.json b/package-lock.json index ddb6c10..502827b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cosmos-server", - "version": "0.18.0-unstable8", + "version": "0.19.0-unstable1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cosmos-server", - "version": "0.18.0-unstable8", + "version": "0.19.0-unstable1", "dependencies": { "@ant-design/colors": "^6.0.0", "@ant-design/icons": "^4.7.0", diff --git a/package.json b/package.json index 4a3a6e2..499dadb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cosmos-server", - "version": "0.18.4", + "version": "0.19.0-unstable1", "description": "", "main": "test-server.js", "bugs": { diff --git a/src/constellation/api_devices_config.go b/src/constellation/api_devices_config.go index 5ececec..9507ffc 100644 --- a/src/constellation/api_devices_config.go +++ b/src/constellation/api_devices_config.go @@ -396,14 +396,17 @@ func SlaveConfigSync(newConfig string) (bool, error) { return false, err } - endpoint := configMap["cstln_config_endpoint"] + rawEndpoint := configMap["cstln_config_endpoint"] apiKey := configMap["cstln_api_key"] - if endpoint == nil || apiKey == nil { + if rawEndpoint == nil || apiKey == nil { utils.Error("SlaveConfigSync: Invalid slave config file for resync", nil) return false, errors.New("Invalid slave config file for resync") } + endpoint := rawEndpoint.(string) + endpoint += "cosmos/api/constellation/config-sync" + // utils.Log("SlaveConfigSync: Fetching config from " + endpoint.(string)) // fetch the config from the endpoint with Authorization header diff --git a/src/constellation/api_devices_create.go b/src/constellation/api_devices_create.go index 5b76498..a703367 100644 --- a/src/constellation/api_devices_create.go +++ b/src/constellation/api_devices_create.go @@ -21,6 +21,7 @@ type DeviceCreateRequestJSON struct { IsRelay bool `json:"isRelay",omitempty` PublicHostname string `json:"PublicHostname",omitempty` Port string `json:"port",omitempty` + Invisible bool `json:"invisible",omitempty` } func DeviceCreate(w http.ResponseWriter, req *http.Request) { @@ -114,6 +115,7 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) { "Fingerprint": fingerprint, "APIKey": APIKey, "Blocked": false, + "Invisible": request.Invisible, }) if err3 != nil { @@ -185,6 +187,7 @@ func DeviceCreate(w http.ResponseWriter, req *http.Request) { "PublicHostname": request.PublicHostname, "Port": request.Port, "LighthousesList": lightHousesList, + "Invisible": request.Invisible, }, }) diff --git a/src/constellation/api_devices_ping.go b/src/constellation/api_devices_ping.go new file mode 100644 index 0000000..aaad32c --- /dev/null +++ b/src/constellation/api_devices_ping.go @@ -0,0 +1,94 @@ +package constellation + +import ( + "net/http" + "encoding/json" + "os/exec" + "runtime" + + "github.com/gorilla/mux" + "github.com/azukaar/cosmos-server/src/utils" +) + +func DevicePing(w http.ResponseWriter, req *http.Request) { + if req.Method != "GET" { + utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP002") + return + } + + if utils.LoggedInOnly(w, req) != nil { + return + } + + // Extract device ID from URL parameters + vars := mux.Vars(req) + deviceID := vars["id"] + + if deviceID == "" { + utils.HTTPError(w, "Device ID is required", http.StatusBadRequest, "DP001") + return + } + + // Connect to the collection + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() + if errCo != nil { + utils.Error("Database Connect", errCo) + utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") + return + } + + var device utils.ConstellationDevice + + // Find the device by DeviceName + err := c.FindOne(nil, map[string]interface{}{ + "DeviceName": deviceID, + }).Decode(&device) + + if err != nil { + utils.Error("DevicePing: Device not found", err) + utils.HTTPError(w, "Device not found", http.StatusNotFound, "DP002") + return + } + + // Check if the device IP exists + if device.IP == "" { + utils.HTTPError(w, "Device has no IP address", http.StatusBadRequest, "DP003") + return + } + + // Ping the device IP + pingResult := pingIP(device.IP) + + // Respond with the ping result + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + "data": map[string]interface{}{ + "deviceName": device.DeviceName, + "ip": device.IP, + "reachable": pingResult, + }, + }) +} + +func pingIP(ip string) bool { + var cmd *exec.Cmd + + // remove the CIDR suffix if present + if len(ip) > 3 && ip[len(ip)-3] == '/' { + ip = ip[:len(ip)-3] + } + + // Use platform-specific ping command + if runtime.GOOS == "windows" { + cmd = exec.Command("ping", "-n", "1", "-w", "10000", ip) + } else { + cmd = exec.Command("ping", "-c", "1", "-W", "10", ip) + } + + output, err := cmd.CombinedOutput() + + utils.Debug("Ping - Pinging IP "+ip+" - Reachable: \nOutput:\n"+string(output)) + + return err == nil +} diff --git a/src/constellation/api_devices_public.go b/src/constellation/api_devices_public.go new file mode 100644 index 0000000..ae5e76e --- /dev/null +++ b/src/constellation/api_devices_public.go @@ -0,0 +1,92 @@ +package constellation + +import ( + "net/http" + "encoding/json" + "strings" + + "github.com/azukaar/cosmos-server/src/utils" +) + +// PublicDeviceInfo represents the limited device information exposed to the public API +type PublicDeviceInfo struct { + DeviceID string `json:"id"` + DeviceName string `json:"name"` + User string `json:"user"` + IP string `json:"ip"` +} + +func DevicePublicList(w http.ResponseWriter, req *http.Request) { + // Check for GET method + if req.Method != "GET" { + utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP002") + return + } + + // Get authorization header + auth := req.Header.Get("Authorization") + if auth == "" { + http.Error(w, "Unauthorized [1]", http.StatusUnauthorized) + return + } + + // Remove "Bearer " from auth header + auth = strings.Replace(auth, "Bearer ", "", 1) + + // Connect to the collection + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + defer closeDb() + if errCo != nil { + utils.Error("Database Connect", errCo) + utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") + return + } + + utils.Log("DevicePublicList: Fetching devices with API key") + + // Find all non-blocked devices that match the API key + cursor, err := c.Find(nil, map[string]interface{}{ + "Blocked": false, + "Invisible": false, + }) + + defer cursor.Close(nil) + + if err != nil { + utils.Error("DevicePublicList: Error fetching devices", err) + utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DPL001") + return + } + + var devices []utils.ConstellationDevice + if err = cursor.All(nil, &devices); err != nil { + utils.Error("DevicePublicList: Error decoding devices", err) + utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DPL002") + return + } + + // Always add the cosmos lighthouse device + cosmosDevice := utils.ConstellationDevice{ + DeviceName: "cosmos", + Nickname: "cosmos", + IP: "192.168.201.1", + } + devices = append([]utils.ConstellationDevice{cosmosDevice}, devices...) + + // Convert to public device info with limited fields + publicDevices := make([]PublicDeviceInfo, len(devices)) + for i, device := range devices { + publicDevices[i] = PublicDeviceInfo{ + DeviceID: device.DeviceName, + DeviceName: device.DeviceName, + User: device.Nickname, + IP: cleanIp(device.IP), + } + } + + // Respond with the list of public device info + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "OK", + "data": publicDevices, + }) +} diff --git a/src/constellation/nebula.go b/src/constellation/nebula.go index 1d5a8e5..d4d9b02 100644 --- a/src/constellation/nebula.go +++ b/src/constellation/nebula.go @@ -40,6 +40,8 @@ var NebulaFailedStarting = false func startNebulaInBackground() error { ProcessMux.Lock() defer ProcessMux.Unlock() + + UpdateFirewallBlockedClients() NebulaFailedStarting = false if process != nil { @@ -249,6 +251,7 @@ func ResetNebula() error { config.ConstellationConfig.Enabled = false config.ConstellationConfig.SlaveMode = false config.ConstellationConfig.DNSDisabled = false + config.ConstellationConfig.FirewallBlockedClients = []string{} utils.SetBaseMainConfig(config) @@ -356,7 +359,7 @@ func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath st for _, d := range blockedDevices { finalConfig.PKI.Blocklist = append(finalConfig.PKI.Blocklist, d.Fingerprint) } - + finalConfig.Lighthouse.AMLighthouse = !overwriteConfig.PrivateNode finalConfig.Lighthouse.Hosts = []string{} @@ -402,6 +405,136 @@ func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath st return nil } +func UpdateFirewallBlockedClients() error { + nebulaYmlPath := utils.CONFIGFOLDER + "nebula.yml" + + // Read the existing nebula.yml file + yamlData, err := ioutil.ReadFile(nebulaYmlPath) + if err != nil { + return fmt.Errorf("failed to read nebula.yml: %w", err) + } + + // Unmarshal the YAML data into a map + var configMap map[string]interface{} + err = yaml.Unmarshal(yamlData, &configMap) + if err != nil { + return fmt.Errorf("failed to unmarshal nebula.yml: %w", err) + } + + // Get the firewall configuration + firewallMap, ok := configMap["firewall"].(map[interface{}]interface{}) + if !ok { + return errors.New("firewall not found in nebula.yml") + } + + // Get all devices from the database to map names to IPs + c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") + if errCo != nil { + return errCo + } + defer closeDb() + + var devices []utils.ConstellationDevice + cursor, err := c.Find(nil, map[string]interface{}{}) + if err != nil { + return err + } + defer cursor.Close(nil) + cursor.All(nil, &devices) + + // Always add the cosmos lighthouse device + cosmosDevice := utils.ConstellationDevice{ + DeviceName: "cosmos", + Nickname: "cosmos", + IP: "192.168.201.1", + } + devices = append([]utils.ConstellationDevice{cosmosDevice}, devices...) + + // Create a map of device names to IPs + deviceIPs := make(map[string]string) + for _, device := range devices { + deviceIPs[device.DeviceName] = cleanIp(device.IP) + } + + // Get the blocked clients list from config + blockedClients := utils.GetMainConfig().ConstellationConfig.FirewallBlockedClients + blockedIPs := make(map[string]bool) + for _, clientName := range blockedClients { + if ip, exists := deviceIPs[clientName]; exists && ip != "" { + blockedIPs[ip] = true + utils.Log("Constellation: Blocking device " + clientName + " (" + ip + ") in firewall") + } + } + + // Build new firewall rules + newInboundRules := []interface{}{} + newOutboundRules := []interface{}{} + + // Always allow ICMP (ping) from any + newInboundRules = append(newInboundRules, map[interface{}]interface{}{ + "port": "any", + "proto": "icmp", + "host": "any", + }) + + // Always allow port 4222 (NATS) from any + newInboundRules = append(newInboundRules, map[interface{}]interface{}{ + "port": 4222, + "proto": "any", + "host": "any", + }) + + // Always allow port 53 (DNS) from any + newInboundRules = append(newInboundRules, map[interface{}]interface{}{ + "port": 53, + "proto": "any", + "host": "any", + }) + + // Always allow lighthouse (cosmos) + newInboundRules = append(newInboundRules, map[interface{}]interface{}{ + "port": "any", + "proto": "any", + "host": "cosmos", + }) + + // Allow outbound to any + newOutboundRules = append(newOutboundRules, map[interface{}]interface{}{ + "port": "any", + "proto": "any", + "host": "any", + }) + + for _, device := range devices { + if device.DeviceName != "" && !blockedIPs[cleanIp(device.IP)] && device.DeviceName != "cosmos" { + // Allow inbound from this device using its hostname + newInboundRules = append(newInboundRules, map[interface{}]interface{}{ + "port": "any", + "proto": "any", + "host": device.DeviceName, + }) + } + } + + firewallMap["inbound"] = newInboundRules + firewallMap["outbound"] = newOutboundRules + + // Marshal back to YAML + updatedYaml, err := yaml.Marshal(configMap) + if err != nil { + return fmt.Errorf("failed to marshal updated config: %w", err) + } + + // Write back to nebula.yml + err = ioutil.WriteFile(nebulaYmlPath, updatedYaml, 0644) + if err != nil { + return fmt.Errorf("failed to write nebula.yml: %w", err) + } + + utils.Log("Updated firewall rules in nebula.yml") + return nil +} + func getYAMLClientConfig(name, configPath, capki, cert, key, APIKey string, device utils.ConstellationDevice, lite bool, getLicence bool) (string, error) { utils.Log("Exporting YAML config for " + name + " with file " + configPath) @@ -525,7 +658,7 @@ func getYAMLClientConfig(name, configPath, capki, cert, key, APIKey string, devi configHost += "/" } - configEndpoint := configHost + "cosmos/api/constellation/config-sync" + configEndpoint := configHost configHostname := strings.Split(configHost, "://")[1] configHostname = strings.Split(configHostname, ":")[0] @@ -772,6 +905,7 @@ func generateNebulaCert(name, ip, PK string, saveToFile bool) (string, string, s "sign", "-ca-crt", utils.CONFIGFOLDER + "ca.crt", "-ca-key", utils.CONFIGFOLDER + "ca.key", + "-subnets", "0.0.0.0/0", "-name", name, "-ip", ip, ) @@ -785,6 +919,7 @@ func generateNebulaCert(name, ip, PK string, saveToFile bool) (string, string, s "sign", "-ca-crt", utils.CONFIGFOLDER + "ca.crt", "-ca-key", utils.CONFIGFOLDER + "ca.key", + "-subnets", "0.0.0.0/0", "-name", name, "-ip", ip, "-in-pub", "./temp.key", diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go index cc708a1..a4bace7 100644 --- a/src/docker/api_blueprint.go +++ b/src/docker/api_blueprint.go @@ -423,6 +423,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) // Create containers + tempServiceList := make(map[string]ContainerCreateRequestContainer) for serviceName, container := range serviceRequest.Services { utils.Log(fmt.Sprintf("Checking service %s...", serviceName)) OnLog(fmt.Sprintf("Checking service %s...\n", serviceName)) @@ -491,10 +492,6 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) OpenStdin: container.StdinOpen, } - if container.StopGracePeriod == 0 { - containerConfig.StopTimeout = nil - } - // check if there's an empty TZ env, if so, replace it with the host's TZ if containerConfig.Env != nil { for i, env := range containerConfig.Env { @@ -699,6 +696,18 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) CapDrop: container.CapDrop, } + // cosmos-force-network-mode logic + if containerConfig.Labels["cosmos-force-network-mode"] == "" { + if (strings.HasPrefix(string(hostConfig.NetworkMode), "service:") || + strings.HasPrefix(string(hostConfig.NetworkMode), "container:")) { + containerConfig.Labels["cosmos-force-network-mode"] = string(hostConfig.NetworkMode) + } + } else { + hostConfig.NetworkMode = conttype.NetworkMode(containerConfig.Labels["cosmos-force-network-mode"]) + utils.Debug("Forcing network mode to " + string(hostConfig.NetworkMode)) + } + + if container.Runtime != "" { hostConfig.Runtime = strings.Join(strings.Fields(container.Runtime), " ") } @@ -903,10 +912,16 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) // Write a response to the client utils.Log(fmt.Sprintf("Container %s created", container.Name)) OnLog(fmt.Sprintf("Container %s created", container.Name)) + + tempServiceList[serviceName] = ContainerCreateRequestContainer{ + Name: container.Name, + DependsOn: container.DependsOn, + NetworkMode: string(hostConfig.NetworkMode), + } } // re-order containers dpeneding on depends_on - startOrder, mustStart, err := ReOrderServices(serviceRequest.Services) + startOrder, mustStart, err := ReOrderServices(tempServiceList) if err != nil { utils.Error("CreateService: Rolling back changes because of -- Container", err) OnLog(utils.DoErr("Rolling back changes because of -- Container creation error: "+err.Error())) @@ -1044,9 +1059,23 @@ func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]C changed := false for name, service := range serviceMap { + dependencies := service.DependsOn + if dependencies == nil { + dependencies = make(map[string]ContainerCreateRequestContainerDependsOnCont) + } + + // if network_mode is container: then we need to add a dependency + if strings.HasPrefix(string(service.NetworkMode), "container:") { + depService := strings.TrimPrefix(string(service.NetworkMode), "container:") + dependencies[depService] = ContainerCreateRequestContainerDependsOnCont{ + Condition: "service_started", + } + } + + // If there are no dependencies, we can add this service to startOrder // Check if all dependencies are already in startOrder allDependenciesStarted := true - for dependency, dependencyDetails := range service.DependsOn { + for dependency, dependencyDetails := range dependencies { dependencyStarted := false for _, startedService := range startOrder { if startedService.Name == dependency { @@ -1086,6 +1115,13 @@ func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]C for name, _ := range serviceMap { errorMessage += "Could not start service: " + name + "\n" errorMessage += "Unsatisfied dependencies:\n" + + // if network_mode is container: then we need to add a dependency + if strings.HasPrefix(string(serviceMap[name].NetworkMode), "container:") { + depService := strings.TrimPrefix(string(serviceMap[name].NetworkMode), "container:") + errorMessage += depService + " (network_mode)\n" + } + for dependency, _ := range serviceMap[name].DependsOn { _, ok := serviceMap[dependency] if ok { diff --git a/src/docker/api_updateContainer.go b/src/docker/api_updateContainer.go index 5189aaf..85798ac 100644 --- a/src/docker/api_updateContainer.go +++ b/src/docker/api_updateContainer.go @@ -125,6 +125,11 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) { form.NetworkMode != "default" { container.Config.MacAddress = "" } + // update cosmos-force-network-mode label + if container.Config.Labels == nil { + container.Config.Labels = make(map[string]string) + } + container.Config.Labels["cosmos-force-network-mode"] = form.NetworkMode } _, err = EditContainer(container.ID, container, false) diff --git a/src/docker/docker.go b/src/docker/docker.go index a84a3f0..3c81b0a 100644 --- a/src/docker/docker.go +++ b/src/docker/docker.go @@ -130,15 +130,13 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock } } - if(newConfig.HostConfig.NetworkMode != "bridge" && - newConfig.HostConfig.NetworkMode != "default" && - newConfig.HostConfig.NetworkMode != "host" && - newConfig.HostConfig.NetworkMode != "none") { - if(!HasLabel(newConfig, "cosmos-force-network-mode")) { + if !HasLabel(newConfig, "cosmos-force-network-mode") { + if (strings.HasPrefix(string(newConfig.HostConfig.NetworkMode), "service:") || + strings.HasPrefix(string(newConfig.HostConfig.NetworkMode), "container:")) { AddLabels(newConfig, map[string]string{"cosmos-force-network-mode": string(newConfig.HostConfig.NetworkMode)}) - } else { - newConfig.HostConfig.NetworkMode = container.NetworkMode(GetLabel(newConfig, "cosmos-force-network-mode")) - } + } + } else { + newConfig.HostConfig.NetworkMode = container.NetworkMode(GetLabel(newConfig, "cosmos-force-network-mode")) } newName := newConfig.Name diff --git a/src/docker/export.go b/src/docker/export.go index ffd5a6b..9be9675 100644 --- a/src/docker/export.go +++ b/src/docker/export.go @@ -41,7 +41,12 @@ func ExportContainer(containerID string) (ContainerCreateRequestContainer, error User: detailedInfo.Config.User, Tty: detailedInfo.Config.Tty, StdinOpen: detailedInfo.Config.OpenStdin, - Hostname: detailedInfo.Config.Hostname, + Hostname: func () string { + if string(detailedInfo.HostConfig.NetworkMode) == "bridge" || string(detailedInfo.HostConfig.NetworkMode) == "default" { + return detailedInfo.Config.Hostname + } + return "" + }(), Domainname: detailedInfo.Config.Domainname, MacAddress: detailedInfo.NetworkSettings.MacAddress, NetworkMode: string(detailedInfo.HostConfig.NetworkMode), diff --git a/src/docker/network.go b/src/docker/network.go index 477f13f..987facf 100644 --- a/src/docker/network.go +++ b/src/docker/network.go @@ -494,6 +494,7 @@ func NetworkCleanUp() { if(!config.DockerConfig.SkipPruneImages) { pruneFilters := filters.NewArgs() + pruneFilters.Add("dangling", "false") report, err := DockerClient.ImagesPrune(DockerContext, pruneFilters) if err != nil { utils.Error("[DOCKER] Error pruning images", err) diff --git a/src/httpServer.go b/src/httpServer.go index 805d151..4ff116a 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -542,6 +542,8 @@ func InitServer() *mux.Router { srapiAdmin.HandleFunc("/api/get-backup", configapi.BackupFileApiGet) srapiAdmin.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices) + srapiAdmin.HandleFunc("/api/constellation/public-devices", constellation.DevicePublicList) + srapiAdmin.HandleFunc("/api/constellation/devices/{id}/ping", constellation.DevicePing) srapiAdmin.HandleFunc("/api/constellation/restart", constellation.API_Restart) srapiAdmin.HandleFunc("/api/constellation/reset", constellation.API_Reset) srapiAdmin.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting) diff --git a/src/market/init.go b/src/market/init.go index b203317..209df42 100644 --- a/src/market/init.go +++ b/src/market/init.go @@ -46,6 +46,13 @@ func Init() { Name: "cosmos-cloud", } + if config.Mpdu_ != "" && config.Mpdn_ != "" { + defaultMarket = utils.MarketSource{ + Url: config.Mpdu_, + Name: config.Mpdn_, + } + } + sources = append([]utils.MarketSource{defaultMarket}, sources...) for _, marketDef := range sources { diff --git a/src/user/token.go b/src/user/token.go index cb2c513..d19f3f8 100644 --- a/src/user/token.go +++ b/src/user/token.go @@ -281,9 +281,11 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf claims["mfaDone"] = mfaDone claims["forDomain"] = reqHostNoPort + sudoUntil := time.Now().Add(time.Hour * 2).Unix() + // if role is ADMIN, add a timeout if tokenRole == utils.ADMIN { - claims["sudo-until"] = time.Now().Add(time.Hour * 2).Unix() + claims["sudo-until"] = sudoUntil } key, err5 := jwt.ParseEdPrivateKeyFromPEM([]byte(utils.GetPrivateAuthKey())) @@ -313,7 +315,7 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf clientCookie := http.Cookie{ Name: "client-infos", - Value: user.Nickname + "," + strconv.Itoa(int(user.Role)) + "," + strconv.Itoa(int(tokenRole)), + Value: user.Nickname + "," + strconv.Itoa(int(user.Role)) + "," + strconv.Itoa(int(tokenRole)) + "," + strconv.Itoa(int(sudoUntil - 1)), Expires: expiration, Path: "/", Secure: shouldCookieBeSecured(req.RemoteAddr), diff --git a/src/utils/types.go b/src/utils/types.go index a694ce8..4ae08c7 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -111,6 +111,8 @@ type Config struct { RemoteStorage RemoteStorageConfig DisableOpenIDDirect bool Backup BackupConfig + Mpdu_ string + Mpdn_ string } @@ -297,6 +299,7 @@ type ConstellationConfig struct { NebulaConfig NebulaConfig ConstellationHostname string Tunnels []ProxyRouteConfig + FirewallBlockedClients []string `json:"FirewallBlockedClients" bson:"FirewallBlockedClients"` } type ConstellationDNSEntry struct { @@ -317,6 +320,7 @@ type ConstellationDevice struct { Blocked bool `json:"blocked" bson:"Blocked"` Fingerprint string `json:"fingerprint" bson:"Fingerprint"` APIKey string `json:"-" bson:"APIKey"` + Invisible bool `json:"invisible" bson:"Invisible"` } type NebulaFirewallRule struct { diff --git a/src/utils/utils.go b/src/utils/utils.go index 567ba39..e85c5e7 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -89,7 +89,7 @@ var DefaultConfig = Config{ }, }, DockerConfig: DockerConfig{ - DefaultDataPath: "/usr", + DefaultDataPath: "/cosmos-storage", }, MarketConfig: MarketConfig{ Sources: []MarketSource{