mirror of
https://github.com/azukaar/Cosmos-Server.git
synced 2026-05-24 14:28:41 -05:00
[skip ci] you know it
This commit is contained in:
+5
-1
@@ -1,7 +1,11 @@
|
||||
## Version 0.21.0
|
||||
- UI refresh for most pages
|
||||
- Rclone rework, no more sub processes (increased performance and reliability)
|
||||
- Reworked mount managemenet logic for flexibility
|
||||
- Added "skip clean URL" option for apps that have invalid URLs like Synology
|
||||
- Support for tempFS
|
||||
- Improve support for 0.0.0.0 routes
|
||||
- Reduce SmartShield false positive on the server panel by having two level of strictness (UI/panel vs. login)
|
||||
- Rclone rework, no more sub processes (increased performance and reliability)
|
||||
- Reworked Constellation cluster synchronisation completely. Now there are no more "Master" server and each server is equally capable.
|
||||
- Any server can be used as DNS (add redundancy too)
|
||||
- Any server can tunnel another server's URL in any direction
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cosmos</title>
|
||||
<link rel="icon" type="image/x-icon" href="/src/assets/images/icons/cosmos.png">
|
||||
<link rel="icon" type="image/x-icon" href="/src/assets/images/icons/logo2.png">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
|
||||
@@ -5,7 +5,6 @@ import ThemeCustomization from './themes';
|
||||
import ScrollTop from './components/ScrollTop';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import {Alert, Box} from '@mui/material';
|
||||
import logo from './assets/images/icons/cosmos.png';
|
||||
|
||||
import * as API from './api';
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
@@ -31,7 +31,15 @@ const Logo = () => {
|
||||
: "brightness(0)" // black
|
||||
}}
|
||||
/>
|
||||
<span style={{fontWeight: 'bold', fontSize: '160%', paddingLeft:'10px'}}> Cosmos Cloud</span>
|
||||
<span style={{fontWeight: 500, fontSize: '160%', paddingLeft:'10px'}}>
|
||||
Cosmos{' '}
|
||||
<span style={{
|
||||
background: `linear-gradient(to right, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`,
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
}}>Cloud</span>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,10 +24,11 @@ const HostChip = ({route, settings, style}) => {
|
||||
|
||||
return <Chip
|
||||
label={<><StatusDot status={isOnline == null ? "unknown" : isOnline ? "success" : "error"} size={8} style={{ marginRight: 6 }} />{url}</>}
|
||||
color="secondary"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
style={{
|
||||
paddingRight: '4px',
|
||||
textDecoration: isOnline ? 'none' : 'underline wavy red',
|
||||
// textDecoration: isOnline ? 'none' : 'underline wavy red',
|
||||
...style
|
||||
}}
|
||||
onClick={() => {
|
||||
|
||||
@@ -9,37 +9,37 @@ let routeImages = {
|
||||
"TUNNEL": {
|
||||
label: "Tunnel",
|
||||
icon: "TU",
|
||||
backgroundColor: "#082452",
|
||||
background: "linear-gradient(135deg, #082452, #0a3a7e)",
|
||||
color: "white",
|
||||
},
|
||||
"SERVAPP": {
|
||||
label: "ServApp",
|
||||
icon: "SA",
|
||||
backgroundColor: "#0db7ed",
|
||||
background: "linear-gradient(135deg, #0db7ed, #0a8abf)",
|
||||
color: "black",
|
||||
},
|
||||
"STATIC": {
|
||||
label: "Static",
|
||||
icon: "ST",
|
||||
backgroundColor: "#f9d71c",
|
||||
background: "linear-gradient(135deg, #f9d71c, #d4b010)",
|
||||
color: "black",
|
||||
},
|
||||
"REDIRECT": {
|
||||
label: "Redir",
|
||||
icon: "RE",
|
||||
backgroundColor: "#2c3e50",
|
||||
background: "linear-gradient(135deg, #2c3e50, #1a252f)",
|
||||
color: "white",
|
||||
},
|
||||
"PROXY": {
|
||||
label: "Proxy",
|
||||
icon: "PR",
|
||||
backgroundColor: "#2ecc71",
|
||||
background: "linear-gradient(135deg, #2ecc71, #1fa85a)",
|
||||
color: "black",
|
||||
},
|
||||
"SPA": {
|
||||
label: "SPA",
|
||||
icon: "SP",
|
||||
backgroundColor: "#e74c3c",
|
||||
background: "linear-gradient(135deg, #e74c3c, #c0392b)",
|
||||
color: "black",
|
||||
},
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export const RouteMode = ({route}) => {
|
||||
icon={<span>{cicon}</span>}
|
||||
// label={c.label}
|
||||
sx={{
|
||||
backgroundColor: c.backgroundColor,
|
||||
background: c.background,
|
||||
paddingLeft: "5px",
|
||||
|
||||
'& .MuiChip-label': {
|
||||
|
||||
@@ -88,6 +88,7 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
||||
HideFromDashboard: routeConfig.HideFromDashboard,
|
||||
_SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false),
|
||||
RestrictToConstellation: routeConfig.RestrictToConstellation === true,
|
||||
SkipURLClean: routeConfig.SkipURLClean === true,
|
||||
OverwriteHostHeader: routeConfig.OverwriteHostHeader,
|
||||
Tunnel: routeConfig.Tunnel,
|
||||
TunneledHost: routeConfig.TunneledHost,
|
||||
@@ -383,6 +384,12 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
||||
label={t('mgmt.urls.edit.advancedSettings.overwriteHostHeaderInput.overwriteHostHeaderLabel')}
|
||||
placeholder={t('mgmt.urls.edit.advancedSettings.overwriteHostHeaderInput.overwriteHostHeaderPlaceholder')}
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCheckbox
|
||||
name="SkipURLClean"
|
||||
label={t('mgmt.urls.edit.advancedSettings.skipURLCleanCheckbox.skipURLCleanLabel')}
|
||||
formik={formik}
|
||||
/></>}
|
||||
|
||||
<Alert severity='warning'>
|
||||
|
||||
@@ -111,6 +111,9 @@ const ProxyManagement = () => {
|
||||
API.config.get().then((res) => {
|
||||
setConfig(res.data);
|
||||
});
|
||||
if (!isAdmin) {
|
||||
return;
|
||||
}
|
||||
API.constellation.tunnels().then((res) => {
|
||||
setTunnels((res.data || []).map(r => {
|
||||
let route = r.Route;
|
||||
|
||||
@@ -164,7 +164,7 @@ const AddDeviceModal = ({ users, config, refreshConfig, devices, canCreateManage
|
||||
disabled={!isAdmin}
|
||||
options={isAdmin ? [
|
||||
['client', t('mgmt.constellation.setup.deviceType.client')],
|
||||
['lighthouse', t('mgmt.constellation.setup.deviceType.lighthouse')],
|
||||
// ['lighthouse', t('mgmt.constellation.setup.deviceType.lighthouse')],
|
||||
['cosmos-agent', t('mgmt.constellation.setup.deviceType.cosmosAgent'), !canCreateAgent],
|
||||
['cosmos-manager', t('mgmt.constellation.setup.deviceType.cosmosManager'), !canCreateManager],
|
||||
] : [
|
||||
|
||||
@@ -93,6 +93,9 @@ const NewDockerServiceForm = () => {
|
||||
spacing={1}
|
||||
style={{
|
||||
maxWidth: '1000px',
|
||||
width: '100%',
|
||||
margin: 'auto',
|
||||
marginTop: '20px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -181,7 +184,7 @@ const NewDockerServiceForm = () => {
|
||||
title: 'URL',
|
||||
disabled: maxTab < 1,
|
||||
children: <Stack spacing={2}>
|
||||
<MainCard style={{ maxWidth: '1000px', width: '100%', margin: '', position: 'relative' }}>
|
||||
<MainCard style={{ maxWidth: '1000px', width: '100%', margin: 'auto', position: 'relative' }}>
|
||||
<Checkbox
|
||||
checked={containerInfo.CreateRoute}
|
||||
onChange={(e) => {
|
||||
@@ -304,7 +307,7 @@ const NewDockerServiceForm = () => {
|
||||
{
|
||||
title: t('mgmt.servApp.newContainer.reviewStartButton'),
|
||||
disabled: maxTab < 1,
|
||||
children: <Stack spacing={2}><NewDockerService service={service} />{nav()}</Stack>
|
||||
children: <Stack style={{ width: '100%', margin: 'auto', maxWidth: '1000px' }} spacing={2}><NewDockerService service={service} />{nav()}</Stack>
|
||||
}
|
||||
]} />}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ const VolumeContainerSetup = ({
|
||||
};
|
||||
|
||||
const formatSource = (mount) => {
|
||||
if (!mount) return null;
|
||||
if (mount.startsWith("/")) return mount;
|
||||
else return "/var/lib/docker/volumes/" + mount + "/_data";
|
||||
}
|
||||
@@ -213,6 +214,7 @@ const VolumeContainerSetup = ({
|
||||
>
|
||||
<MenuItem value="bind">{t('mgmt.servapps.newContainer.volumes.bindInput')}</MenuItem>
|
||||
<MenuItem value="volume">{t('global.volume')}</MenuItem>
|
||||
<MenuItem value="tmpfs">tmpfs</MenuItem>
|
||||
</TextField>
|
||||
</div>
|
||||
),
|
||||
@@ -248,6 +250,14 @@ const VolumeContainerSetup = ({
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</Stack>
|
||||
) : r.Type == "tmpfs" ? (
|
||||
<TextField
|
||||
className="px-2 my-2"
|
||||
variant="outlined"
|
||||
disabled
|
||||
style={{ minWidth: "200px" }}
|
||||
value="(memory)"
|
||||
/>
|
||||
) : (
|
||||
<TextField
|
||||
className="px-2 my-2"
|
||||
@@ -383,7 +393,7 @@ const VolumeContainerSetup = ({
|
||||
}}>
|
||||
{containerInfo && containerInfo.HostConfig && containerInfo.HostConfig.Mounts && <MainCard title={t('mgmt.backup.backups')}>
|
||||
<Backups pathFilters={
|
||||
containerInfo.HostConfig.Mounts.map((r) => formatSource(r.Source))
|
||||
containerInfo.HostConfig.Mounts.map((r) => formatSource(r.Source)).filter(Boolean)
|
||||
} />
|
||||
</MainCard>}
|
||||
</div>}
|
||||
|
||||
@@ -381,13 +381,14 @@ const ServApps = ({stack}) => {
|
||||
return <HostChip route={route} settings/>
|
||||
})}
|
||||
{app.networkSettings && app.networkSettings.Networks && app.networkSettings.Networks['host'] &&
|
||||
<Chip style={{ fontSize: '80%' }} label="Host Network" color="warning" />
|
||||
<Chip style={{ fontSize: '80%' }} label="Host Network" color="warning" variant='outlined' />
|
||||
}
|
||||
{app.ports && app.ports.filter(p => p && p.IP != '::' && p.PublicPort).map((port) => {
|
||||
return <Chip
|
||||
style={{ fontSize: '80%', cursor: 'pointer' }}
|
||||
label={port.PublicPort + ":" + port.PrivatePort}
|
||||
color="warning"
|
||||
variant='outlined'
|
||||
onClick={() => window.open(`${window.location.protocol}//${window.location.hostname}:${port.PublicPort}`, '_blank')}
|
||||
/>
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// project import
|
||||
import MainLayout from '../layout/MainLayout';
|
||||
import logo from '../assets/images/icons/cosmos.png';
|
||||
import logo from '../assets/images/icons/logo2.png';
|
||||
import { Navigate } from 'react-router';
|
||||
import UserManagement from '../pages/config/users/usermanagement';
|
||||
import ConfigManagement from '../pages/config/users/configman';
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
// ==============================|| OVERRIDES - BADGE ||============================== //
|
||||
|
||||
export default function Badge(theme) {
|
||||
const primaryMain = theme.palette.primary.main;
|
||||
const secondaryMain = theme.palette.secondary.main;
|
||||
|
||||
const colorGradient = (name) => {
|
||||
const c = theme.palette[name];
|
||||
return `linear-gradient(135deg, ${c.main}, ${c.dark})`;
|
||||
};
|
||||
|
||||
return {
|
||||
MuiBadge: {
|
||||
styleOverrides: {
|
||||
@@ -8,7 +16,25 @@ export default function Badge(theme) {
|
||||
minWidth: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
padding: theme.spacing(0.5)
|
||||
}
|
||||
},
|
||||
colorPrimary: {
|
||||
backgroundImage: `linear-gradient(135deg, ${primaryMain}, ${secondaryMain})`,
|
||||
},
|
||||
colorSecondary: {
|
||||
backgroundImage: colorGradient('secondary'),
|
||||
},
|
||||
colorError: {
|
||||
backgroundImage: colorGradient('error'),
|
||||
},
|
||||
colorSuccess: {
|
||||
backgroundImage: colorGradient('success'),
|
||||
},
|
||||
colorWarning: {
|
||||
backgroundImage: colorGradient('warning'),
|
||||
},
|
||||
colorInfo: {
|
||||
backgroundImage: colorGradient('info'),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,13 +5,60 @@ import { alpha } from '@mui/material/styles';
|
||||
|
||||
export default function Button(theme) {
|
||||
const primaryMain = theme.palette.primary.main;
|
||||
const secondaryMain = theme.palette.secondary.main;
|
||||
const paperBg = theme.palette.background.paper;
|
||||
|
||||
// Two-tone gradient: primary → secondary
|
||||
const primaryGradient = `linear-gradient(135deg, ${primaryMain}, ${secondaryMain})`;
|
||||
|
||||
// Per-color gradient: main → dark
|
||||
const colorGradient = (name) => {
|
||||
const c = theme.palette[name];
|
||||
return `linear-gradient(135deg, ${c.main}, ${c.dark})`;
|
||||
};
|
||||
|
||||
const disabledStyle = {
|
||||
'&.Mui-disabled': {
|
||||
backgroundColor: theme.palette.grey[400]
|
||||
backgroundColor: theme.palette.grey[400],
|
||||
backgroundImage: 'none',
|
||||
borderColor: theme.palette.grey[400],
|
||||
}
|
||||
};
|
||||
|
||||
// Build per-color contained + outlined overrides
|
||||
const colors = {
|
||||
Primary: { gradient: primaryGradient, main: primaryMain, shift: secondaryMain },
|
||||
Secondary: { gradient: colorGradient('secondary'), main: secondaryMain },
|
||||
Error: { gradient: colorGradient('error'), main: theme.palette.error.main },
|
||||
Success: { gradient: colorGradient('success'), main: theme.palette.success.main },
|
||||
Warning: { gradient: colorGradient('warning'), main: theme.palette.warning.main },
|
||||
Info: { gradient: colorGradient('info'), main: theme.palette.info.main },
|
||||
};
|
||||
|
||||
const containedOverrides = {};
|
||||
const outlinedOverrides = {};
|
||||
|
||||
Object.entries(colors).forEach(([name, { gradient }]) => {
|
||||
containedOverrides[`contained${name}`] = {
|
||||
backgroundImage: gradient,
|
||||
};
|
||||
|
||||
outlinedOverrides[`outlined${name}`] = {
|
||||
borderColor: 'transparent',
|
||||
backgroundImage: `linear-gradient(${paperBg}, ${paperBg}), ${gradient}`,
|
||||
backgroundOrigin: 'border-box',
|
||||
backgroundClip: 'padding-box, border-box',
|
||||
'&:hover': {
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: 'transparent',
|
||||
backgroundImage: gradient,
|
||||
backgroundOrigin: 'border-box',
|
||||
backgroundClip: 'border-box',
|
||||
color: '#fff',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
MuiButton: {
|
||||
defaultProps: {
|
||||
@@ -24,27 +71,40 @@ export default function Button(theme) {
|
||||
textTransform: 'none',
|
||||
transition: 'all 0.15s ease',
|
||||
},
|
||||
|
||||
// --- Contained: gradient backgrounds ---
|
||||
contained: {
|
||||
boxShadow: 'none',
|
||||
'&:hover': {
|
||||
boxShadow: 'none',
|
||||
filter: 'brightness(1.1)',
|
||||
filter: 'brightness(1.15)',
|
||||
},
|
||||
...disabledStyle
|
||||
},
|
||||
...containedOverrides,
|
||||
|
||||
// --- Outlined: gradient borders ---
|
||||
outlined: {
|
||||
borderWidth: '1.5px',
|
||||
'&:hover': {
|
||||
borderWidth: '1.5px',
|
||||
backgroundColor: alpha(primaryMain, 0.08),
|
||||
},
|
||||
...disabledStyle
|
||||
},
|
||||
...outlinedOverrides,
|
||||
|
||||
// --- Text: gradient tint on hover ---
|
||||
text: {
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(primaryMain, 0.06),
|
||||
},
|
||||
},
|
||||
textPrimary: {
|
||||
'&:hover': {
|
||||
backgroundImage: `linear-gradient(135deg, ${alpha(primaryMain, 0.06)}, ${alpha(secondaryMain, 0.06)})`,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
// ==============================|| OVERRIDES - CHIP ||============================== //
|
||||
|
||||
export default function Chip(theme) {
|
||||
const primaryMain = theme.palette.primary.main;
|
||||
const secondaryMain = theme.palette.secondary.main;
|
||||
|
||||
const colorGradient = (name) => {
|
||||
const c = theme.palette[name];
|
||||
return `linear-gradient(135deg, ${c.main}, ${c.dark})`;
|
||||
};
|
||||
|
||||
return {
|
||||
MuiChip: {
|
||||
styleOverrides: {
|
||||
@@ -10,13 +18,34 @@ export default function Chip(theme) {
|
||||
boxShadow: 'none'
|
||||
}
|
||||
},
|
||||
colorWarning: {
|
||||
color: '#000',
|
||||
outlinedWarning: {
|
||||
color: theme.palette.warning.dark,
|
||||
},
|
||||
sizeLarge: {
|
||||
fontSize: '1rem',
|
||||
height: 40
|
||||
},
|
||||
// --- Filled chips: gradient backgrounds ---
|
||||
filledPrimary: {
|
||||
backgroundImage: `linear-gradient(135deg, ${primaryMain}, ${secondaryMain})`,
|
||||
},
|
||||
filledSecondary: {
|
||||
backgroundImage: colorGradient('secondary'),
|
||||
},
|
||||
filledError: {
|
||||
backgroundImage: colorGradient('error'),
|
||||
},
|
||||
filledSuccess: {
|
||||
backgroundImage: colorGradient('success'),
|
||||
},
|
||||
filledWarning: {
|
||||
backgroundImage: colorGradient('warning'),
|
||||
color: '#000',
|
||||
},
|
||||
filledInfo: {
|
||||
backgroundImage: colorGradient('info'),
|
||||
},
|
||||
// --- Light variant (custom) ---
|
||||
light: {
|
||||
color: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.primary.lighter,
|
||||
|
||||
@@ -28,10 +28,10 @@ const Theme = (colors, darkMode) => {
|
||||
|
||||
return {
|
||||
primary: {
|
||||
main: purple[400],
|
||||
main: "#ff64c8",
|
||||
},
|
||||
secondary: {
|
||||
main: indigo[600],
|
||||
main: "#c864ff",
|
||||
},
|
||||
error: {
|
||||
lighter: red[0],
|
||||
|
||||
@@ -624,6 +624,7 @@
|
||||
"mgmt.urls.edit.advancedSettings.hideFromDashboardCheckbox.hideFromDashboardLabel": "Hide from Dashboard",
|
||||
"mgmt.urls.edit.advancedSettings.overwriteHostHeaderInput.overwriteHostHeaderLabel": "Overwrite Host Header (use this to chain resolve request from another server/ip)",
|
||||
"mgmt.urls.edit.advancedSettings.overwriteHostHeaderInput.overwriteHostHeaderPlaceholder": "Overwrite Host Header",
|
||||
"mgmt.urls.edit.advancedSettings.skipURLCleanCheckbox.skipURLCleanLabel": "Skip URL Cleaning (preserve double slashes in path)",
|
||||
"mgmt.urls.edit.advancedSettings.whitelistInboundIpInput.whitelistInboundIpLabel": "Whitelist Inbound IPs and/or IP ranges (comma separated)",
|
||||
"mgmt.urls.edit.advancedSettings.whitelistInboundIpInput.whitelistInboundIpPlaceholder": "Whitelist Inbound IPs and/or IP ranges (comma separated)",
|
||||
"mgmt.urls.edit.advancedSettingsTitle": "Advanced Settings",
|
||||
|
||||
@@ -165,6 +165,8 @@ func StartNATS() {
|
||||
utils.Debug("[NATS] Adding NATS user for device: " + devices.DeviceName + " With API Key: " + devices.APIKey)
|
||||
username := sanitizeNATSUsername(devices.DeviceName)
|
||||
|
||||
// TODO: Agent / users with less permissions
|
||||
|
||||
users = append(users, &server.User{
|
||||
Username: username,
|
||||
Password: devices.APIKey,
|
||||
@@ -268,8 +270,9 @@ func StartNATS() {
|
||||
retries++
|
||||
continue
|
||||
}
|
||||
|
||||
if NebulaFailedStarting {
|
||||
utils.Error("[NATS] Nebula failed to start, aborting NATS server setup", nil)
|
||||
utils.Error("[NATS] Nebula failed to start, aborting NATS server setup retry", nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -320,7 +323,7 @@ func InitNATSClient() {
|
||||
var err error
|
||||
retries := 0
|
||||
|
||||
if NebulaFailedStarting || !NebulaStarted {
|
||||
if NebulaFailedStarting {
|
||||
utils.Error("[NATS] Nebula failed to start, aborting NATS client connection", nil)
|
||||
return
|
||||
}
|
||||
@@ -365,12 +368,18 @@ func InitNATSClient() {
|
||||
}
|
||||
|
||||
if NebulaFailedStarting {
|
||||
utils.Error("[NATS] Nebula failed to start, aborting NATS client connection", nil)
|
||||
utils.Error("[NATS] Nebula failed to start, aborting NATS client connection retry", nil)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(2 * (retries + 1)) * time.Second)
|
||||
|
||||
if !NebulaStarted {
|
||||
retries++
|
||||
utils.Warn("[NATS] Nebula not started yet, delaying NATS client connection retry")
|
||||
continue
|
||||
}
|
||||
|
||||
nc, err = natsClient.Connect("nats://localhost:4222",
|
||||
nats.Secure(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
@@ -94,3 +96,67 @@ func DevicePublicList(w http.ResponseWriter, req *http.Request) {
|
||||
"data": publicDevices,
|
||||
})
|
||||
}
|
||||
|
||||
func PublicDeviceListNATS(m *nats.Msg) {
|
||||
// Connect to the collection
|
||||
c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices")
|
||||
defer closeDb()
|
||||
if errCo != nil {
|
||||
utils.Error("PublicDeviceListNATS: Database Connect", errCo)
|
||||
m.Respond([]byte(`{"status":"error","message":"Database error"}`))
|
||||
return
|
||||
}
|
||||
|
||||
utils.Debug("PublicDeviceListNATS: Fetching devices")
|
||||
|
||||
// Find all non-blocked, non-invisible devices
|
||||
cursor, err := c.Find(nil, map[string]interface{}{
|
||||
"Blocked": false,
|
||||
"Invisible": false,
|
||||
})
|
||||
|
||||
defer cursor.Close(nil)
|
||||
|
||||
if err != nil {
|
||||
utils.Error("PublicDeviceListNATS: Error fetching devices", err)
|
||||
m.Respond([]byte(`{"status":"error","message":"Error fetching devices"}`))
|
||||
return
|
||||
}
|
||||
|
||||
var devices []utils.ConstellationDevice
|
||||
if err = cursor.All(nil, &devices); err != nil {
|
||||
utils.Error("PublicDeviceListNATS: Error decoding devices", err)
|
||||
m.Respond([]byte(`{"status":"error","message":"Error decoding devices"}`))
|
||||
return
|
||||
}
|
||||
|
||||
// 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),
|
||||
IsLighthouse: device.IsLighthouse,
|
||||
CosmosNode: device.CosmosNode,
|
||||
IsRelay: device.IsRelay,
|
||||
IsExitNode: device.IsExitNode,
|
||||
PublicHostname: device.PublicHostname,
|
||||
Port: device.Port,
|
||||
}
|
||||
}
|
||||
|
||||
// Respond with the list of public device info
|
||||
response, err := json.Marshal(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": publicDevices,
|
||||
})
|
||||
if err != nil {
|
||||
utils.Error("PublicDeviceListNATS: Error marshalling response", err)
|
||||
m.Respond([]byte(`{"status":"error","message":"Error encoding response"}`))
|
||||
return
|
||||
}
|
||||
|
||||
m.Respond(response)
|
||||
}
|
||||
|
||||
@@ -544,7 +544,6 @@ func ExportLighthouseFromDB() error {
|
||||
var relays []string
|
||||
var Blocklist []string
|
||||
for _, lh := range ll {
|
||||
utils.Debug("[CACA PROUT] Lighthouse from DB: " + lh.IP + " - " + lh.PublicHostname)
|
||||
if lh.Blocked {
|
||||
Blocklist = append(Blocklist, lh.Fingerprint)
|
||||
} else {
|
||||
|
||||
@@ -289,6 +289,8 @@ func SyncNATSClientRouter(nc *nats.Conn) {
|
||||
m.Respond([]byte(response))
|
||||
}
|
||||
})
|
||||
|
||||
nc.Subscribe("cosmos._global_.constellation.public-devices", PublicDeviceListNATS)
|
||||
|
||||
nc.Subscribe("cosmos._global_.constellation.data.sync-receive", func(m *nats.Msg) {
|
||||
utils.Log("[NATS] Constellation data sync received")
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"strings"
|
||||
"strconv"
|
||||
"time"
|
||||
"sort"
|
||||
"github.com/nats-io/nats.go"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
@@ -263,6 +264,10 @@ func UpdateLocalTunnelCache() {
|
||||
}
|
||||
|
||||
for _, tunnelRoute := range heartbeat.Tunnels {
|
||||
// Skip tunnels from ourselves
|
||||
if heartbeat.DeviceName == currentDeviceName {
|
||||
continue
|
||||
}
|
||||
if tunnelRoute.Tunnel == "_ANY_" || tunnelRoute.Tunnel == currentDeviceName {
|
||||
if existing, ok := byName[tunnelRoute.Name]; ok {
|
||||
existing.From = append(existing.From, heartbeat.DeviceName)
|
||||
@@ -283,9 +288,23 @@ func UpdateLocalTunnelCache() {
|
||||
tunnels = append(tunnels, *t)
|
||||
}
|
||||
|
||||
// Compare old and new cache using JSON stringify
|
||||
oldJSON, _ := json.Marshal(localTunnelCache)
|
||||
newJSON, _ := json.Marshal(tunnels)
|
||||
// Compare old and new cache using sorted copies for consistent comparison
|
||||
sortTunnelsForComparison := func(t []utils.ConstellationTunnel) []utils.ConstellationTunnel {
|
||||
copied := make([]utils.ConstellationTunnel, len(t))
|
||||
for i, tunnel := range t {
|
||||
copied[i] = tunnel
|
||||
copied[i].From = make([]string, len(tunnel.From))
|
||||
copy(copied[i].From, tunnel.From)
|
||||
sort.Strings(copied[i].From)
|
||||
}
|
||||
sort.Slice(copied, func(i, j int) bool {
|
||||
return copied[i].Route.Name < copied[j].Route.Name
|
||||
})
|
||||
return copied
|
||||
}
|
||||
|
||||
oldJSON, _ := json.Marshal(sortTunnelsForComparison(localTunnelCache))
|
||||
newJSON, _ := json.Marshal(sortTunnelsForComparison(tunnels))
|
||||
|
||||
localTunnelCache = tunnels
|
||||
lastCacheUpdate = time.Now()
|
||||
|
||||
+53
-31
@@ -232,30 +232,44 @@ func tokenMiddleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func SecureAPI(userRouter *mux.Router, public bool, publicCors bool) {
|
||||
func SecureAPI(userRouter *mux.Router, public bool, publicCors bool, strict bool) {
|
||||
if(!public) {
|
||||
userRouter.Use(tokenMiddleware)
|
||||
}
|
||||
userRouter.Use(proxy.SmartShieldMiddleware(
|
||||
"__COSMOS",
|
||||
utils.ProxyRouteConfig{
|
||||
Name: "Cosmos-Internal",
|
||||
SmartShield: utils.SmartShieldPolicy{
|
||||
Enabled: true,
|
||||
PolicyStrictness: 1,
|
||||
PerUserRequestLimit: 12000,
|
||||
|
||||
if(strict) {
|
||||
userRouter.Use(proxy.SmartShieldMiddleware(
|
||||
"__COSMOS",
|
||||
utils.ProxyRouteConfig{
|
||||
Name: "Cosmos-Internal-login",
|
||||
SmartShield: utils.SmartShieldPolicy{
|
||||
Enabled: true,
|
||||
PolicyStrictness: 1,
|
||||
PerUserRequestLimit: 10000,
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
))
|
||||
} else {
|
||||
userRouter.Use(proxy.SmartShieldMiddleware(
|
||||
"__COSMOS",
|
||||
utils.ProxyRouteConfig{
|
||||
Name: "Cosmos-Internal",
|
||||
SmartShield: utils.SmartShieldPolicy{
|
||||
Enabled: true,
|
||||
PolicyStrictness: 2,
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
if(publicCors || public) {
|
||||
userRouter.Use(utils.PublicCORS)
|
||||
}
|
||||
|
||||
userRouter.Use(utils.MiddlewareTimeout(45 * time.Second))
|
||||
userRouter.Use(httprate.Limit(180, 1*time.Minute,
|
||||
userRouter.Use(httprate.Limit(500, 1*time.Minute,
|
||||
httprate.WithKeyFuncs(httprate.KeyByIP),
|
||||
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.Error("Too many requests. Throttling", nil)
|
||||
utils.HTTPError(w, "Too many requests",
|
||||
http.StatusTooManyRequests, "HTTP003")
|
||||
@@ -430,7 +444,7 @@ func InitServer() *mux.Router {
|
||||
|
||||
utils.Log("Initialising HTTP(S) Router and all routes")
|
||||
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
router := mux.NewRouter().StrictSlash(true).SkipClean(true)
|
||||
|
||||
router.Use(utils.BlockBannedIPs)
|
||||
|
||||
@@ -449,22 +463,26 @@ func InitServer() *mux.Router {
|
||||
}
|
||||
|
||||
logoAPI := router.PathPrefix("/logo").Subrouter()
|
||||
SecureAPI(logoAPI, true, true)
|
||||
SecureAPI(logoAPI, true, true, false)
|
||||
logoAPI.HandleFunc("/", SendLogo)
|
||||
|
||||
|
||||
srapi := router.PathPrefix("/cosmos").Subrouter()
|
||||
srapi.Use(utils.ContentTypeMiddleware("application/json"))
|
||||
|
||||
srapiStrict := router.PathPrefix("/cosmos").Subrouter()
|
||||
srapiStrict.Use(utils.ContentTypeMiddleware("application/json"))
|
||||
|
||||
|
||||
srapi.HandleFunc("/api/login", user.UserLogin)
|
||||
srapi.HandleFunc("/api/sudo", user.UserSudo)
|
||||
srapi.HandleFunc("/api/password-reset", user.ResetPassword)
|
||||
srapi.HandleFunc("/api/mfa", user.API2FA)
|
||||
srapiStrict.HandleFunc("/api/login", user.UserLogin)
|
||||
srapiStrict.HandleFunc("/api/sudo", user.UserSudo)
|
||||
srapiStrict.HandleFunc("/api/password-reset", user.ResetPassword)
|
||||
srapiStrict.HandleFunc("/api/mfa", user.API2FA)
|
||||
srapiStrict.HandleFunc("/api/register", user.UserRegister)
|
||||
srapiStrict.HandleFunc("/api/newInstall", NewInstallRoute)
|
||||
srapi.HandleFunc("/api/status", StatusRoute)
|
||||
srapi.HandleFunc("/api/can-send-email", CanSendEmail)
|
||||
srapi.HandleFunc("/api/newInstall", NewInstallRoute)
|
||||
srapi.HandleFunc("/api/logout", user.UserLogout)
|
||||
srapi.HandleFunc("/api/register", user.UserRegister)
|
||||
srapi.HandleFunc("/api/dns", GetDNSRoute)
|
||||
srapi.HandleFunc("/api/dns-check", CheckDNSRoute)
|
||||
srapi.HandleFunc("/api/favicon", GetFavicon)
|
||||
@@ -616,18 +634,22 @@ func InitServer() *mux.Router {
|
||||
srapiAdmin.Use(utils.Restrictions(config.AdminConstellationOnly, config.AdminWhitelistIPs))
|
||||
|
||||
srapi.Use(utils.SetSecurityHeaders)
|
||||
srapiStrict.Use(utils.SetSecurityHeaders)
|
||||
srapiAdmin.Use(utils.SetSecurityHeaders)
|
||||
|
||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||
srapi.Use(utils.EnsureHostname)
|
||||
srapiStrict.Use(utils.EnsureHostname)
|
||||
srapiAdmin.Use(utils.EnsureHostname)
|
||||
|
||||
srapi.Use(utils.EnsureHostnameCosmosAPI)
|
||||
srapiStrict.Use(utils.EnsureHostnameCosmosAPI)
|
||||
srapiAdmin.Use(utils.EnsureHostnameCosmosAPI)
|
||||
}
|
||||
|
||||
SecureAPI(srapi, false, false)
|
||||
SecureAPI(srapiAdmin, false, false)
|
||||
SecureAPI(srapiStrict, false, false, true)
|
||||
SecureAPI(srapi, false, false, false)
|
||||
SecureAPI(srapiAdmin, false, false, false)
|
||||
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
@@ -642,7 +664,7 @@ func InitServer() *mux.Router {
|
||||
// fs := http.FileServer(http.Dir(pwd + "/static"))
|
||||
uirouter := router.PathPrefix("/cosmos-ui").Subrouter()
|
||||
uirouter.Use(utils.SetSecurityHeaders)
|
||||
SecureAPI(uirouter, true, true)
|
||||
SecureAPI(uirouter, true, true, false)
|
||||
uirouter.PathPrefix("/").Handler(http.StripPrefix("/cosmos-ui", utils.SPAHandler(pwd + "/static")))
|
||||
|
||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||
@@ -650,28 +672,28 @@ func InitServer() *mux.Router {
|
||||
}
|
||||
|
||||
OpenIDDetect := router.PathPrefix("/").Subrouter()
|
||||
SecureAPI(OpenIDDetect, true, true)
|
||||
authorizationserver.RegisterHandlersDetect(OpenIDDetect, srapi)
|
||||
SecureAPI(OpenIDDetect, true, true, false)
|
||||
authorizationserver.RegisterHandlersDetect(OpenIDDetect, srapiStrict)
|
||||
|
||||
router = proxy.BuildFromConfig(router, HTTPConfig.ProxyConfig)
|
||||
|
||||
wellKnownRouter := router.PathPrefix("/").Subrouter()
|
||||
SecureAPI(wellKnownRouter, true, true)
|
||||
SecureAPI(wellKnownRouter, true, true, false)
|
||||
|
||||
userRouter := router.PathPrefix("/oauth2").Subrouter()
|
||||
SecureAPI(userRouter, false, true)
|
||||
SecureAPI(userRouter, false, true, true)
|
||||
|
||||
serverRouter := router.PathPrefix("/oauth2").Subrouter()
|
||||
SecureAPI(serverRouter, true, true)
|
||||
SecureAPI(serverRouter, true, true, true)
|
||||
|
||||
authorizationserver.RegisterHandlers(wellKnownRouter, userRouter, serverRouter)
|
||||
|
||||
router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/cosmos-ui/", http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, "/cosmos-ui/", http.StatusTemporaryRedirect)
|
||||
}))
|
||||
|
||||
router.HandleFunc("/cosmos-ui", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/cosmos-ui/", http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, "/cosmos-ui/", http.StatusTemporaryRedirect)
|
||||
}))
|
||||
|
||||
return router
|
||||
|
||||
+1
-1
@@ -331,7 +331,7 @@ func PingURL(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
func SendLogo(w http.ResponseWriter, req *http.Request) {
|
||||
pwd,_ := os.Getwd()
|
||||
imgsrc := "Logo.png"
|
||||
imgsrc := "Logo2.png"
|
||||
Logo, err := ioutil.ReadFile(pwd + "/" + imgsrc)
|
||||
if err != nil {
|
||||
utils.Error("Logo", err)
|
||||
|
||||
+28
-15
@@ -107,6 +107,17 @@ func startProxy(listenAddr string, target string, proxyInfo *ProxyInfo, isHTTPPr
|
||||
var listener net.Listener
|
||||
var packetConn net.PacketConn
|
||||
|
||||
// Ensure we always signal completion, even on early errors
|
||||
defer func() {
|
||||
if listener != nil {
|
||||
listener.Close()
|
||||
}
|
||||
if packetConn != nil {
|
||||
packetConn.Close()
|
||||
}
|
||||
close(proxyInfo.stopped)
|
||||
}()
|
||||
|
||||
switch listenProtocol {
|
||||
case "tcp":
|
||||
listener, err = net.Listen("tcp", listenAddr)
|
||||
@@ -122,16 +133,6 @@ func startProxy(listenAddr string, target string, proxyInfo *ProxyInfo, isHTTPPr
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if listener != nil {
|
||||
listener.Close()
|
||||
}
|
||||
if packetConn != nil {
|
||||
packetConn.Close()
|
||||
}
|
||||
close(proxyInfo.stopped)
|
||||
}()
|
||||
|
||||
utils.Log("[SocketProxy] Proxy listening on "+listenAddr+", forwarding to "+listenProtocol+"://"+target)
|
||||
|
||||
if listenProtocol == "tcp" {
|
||||
@@ -171,9 +172,9 @@ func handleTCPProxy(listener net.Listener, target string, proxyInfo *ProxyInfo,
|
||||
} else {
|
||||
shieldedClient = client
|
||||
}
|
||||
|
||||
|
||||
utils.Debug("[SocketProxy] New TCP connection accepted on " + listenAddr)
|
||||
|
||||
|
||||
server, err := net.Dial("tcp", target)
|
||||
if err != nil {
|
||||
utils.Error("[SocketProxy] Failed to connect to TCP server", err)
|
||||
@@ -393,12 +394,24 @@ func InitInternalSocketProxy() {
|
||||
portpair.To = "localhost:" + HTTPPort
|
||||
}
|
||||
|
||||
expectedPorts = append(expectedPorts, portpair)
|
||||
// Check if this port is already in the list (avoid duplicates)
|
||||
alreadyExists := false
|
||||
for _, existing := range expectedPorts {
|
||||
if existing.From == portpair.From {
|
||||
alreadyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if alreadyExists {
|
||||
utils.MajorError("[SocketProxy] Duplicate port detected: "+portpair.From+". Multiple routes are trying to use the same port.", nil)
|
||||
} else {
|
||||
expectedPorts = append(expectedPorts, portpair)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StopAllProxies()
|
||||
|
||||
go initInternalPortProxy(expectedPorts)
|
||||
|
||||
initInternalPortProxy(expectedPorts)
|
||||
}
|
||||
@@ -410,19 +410,19 @@ func TCPSmartShieldMiddleware(shieldID string, route utils.ProxyRouteConfig) fun
|
||||
policy.PerUserTimeBudget = 2 * 60 * 60 * 1000 // 2 hours
|
||||
}
|
||||
if(policy.PerUserRequestLimit == 0) {
|
||||
policy.PerUserRequestLimit = 12000 // 150 requests per minute
|
||||
policy.PerUserRequestLimit = 18000
|
||||
}
|
||||
if(policy.PerUserByteLimit == 0) {
|
||||
policy.PerUserByteLimit = 150 * 1024 * 1024 * 1024 // 150GB
|
||||
policy.PerUserByteLimit = 200 * 1024 * 1024 * 1024 // 200GB
|
||||
}
|
||||
if(policy.PolicyStrictness == 0) {
|
||||
policy.PolicyStrictness = 2 // NORMAL
|
||||
}
|
||||
if(policy.PerUserSimultaneous == 0) {
|
||||
policy.PerUserSimultaneous = 24
|
||||
policy.PerUserSimultaneous = 100
|
||||
}
|
||||
if(policy.MaxGlobalSimultaneous == 0) {
|
||||
policy.MaxGlobalSimultaneous = 250
|
||||
policy.MaxGlobalSimultaneous = 2000
|
||||
}
|
||||
if(policy.PrivilegedGroups == 0) {
|
||||
policy.PrivilegedGroups = utils.ADMIN
|
||||
|
||||
@@ -123,21 +123,24 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, DisableHeaderHa
|
||||
|
||||
targetIP, err := docker.GetContainerIPByName(targetHost)
|
||||
if err != nil {
|
||||
utils.Error("Create Route", err)
|
||||
utils.Error("Director Route", err)
|
||||
}
|
||||
utils.Debug("Dockerless Target IP: " + targetIP)
|
||||
req.URL.Host = targetIP + ":" + targetURL.Port()
|
||||
}
|
||||
|
||||
utils.Debug("Request to backend: " + req.URL.String())
|
||||
|
||||
req.URL.Path, req.URL.RawPath = joinURLPath(targetURL, req.URL)
|
||||
|
||||
|
||||
if urlQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = urlQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = urlQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
|
||||
utils.Debug("Request to backend: " + req.URL.String())
|
||||
|
||||
req.Header.Set("X-Forwarded-Proto", originalScheme)
|
||||
|
||||
if(originalScheme == "https") {
|
||||
|
||||
+40
-2
@@ -2,10 +2,12 @@ package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"net/url"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/user"
|
||||
"github.com/azukaar/cosmos-server/src/constellation"
|
||||
@@ -14,6 +16,32 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Borrowed from the net/http package. (Thanks mux!)
|
||||
func cleanPathMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
p := r.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
np := path.Clean(p)
|
||||
if p[len(p)-1] == '/' && np != "/" {
|
||||
np += "/"
|
||||
}
|
||||
|
||||
if np != r.URL.Path {
|
||||
url := *r.URL
|
||||
url.Path = np
|
||||
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func tokenMiddleware(route utils.ProxyRouteConfig) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -95,7 +123,13 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
|
||||
origin := router.NewRoute()
|
||||
|
||||
if route.UseHost {
|
||||
origin = origin.Host(route.Host)
|
||||
// If hostname is 0.0.0.0, treat it as a wildcard (match any host with that port)
|
||||
if strings.Contains(route.Host, ":") && (strings.Split(route.Host, ":")[0] == "0.0.0.0" || route.Host[0] == ":") {
|
||||
port := strings.Split(route.Host, ":")[1]
|
||||
origin = origin.Host("{host:[^:]+}:" + port)
|
||||
} else {
|
||||
origin = origin.Host(route.Host)
|
||||
}
|
||||
|
||||
if route.Mode == "SERVAPP" || route.Mode == "PROXY" || route.Mode == "REDIRECT" {
|
||||
urlRoute, err := url.Parse(route.Target)
|
||||
@@ -199,6 +233,10 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
|
||||
|
||||
destination = tokenMiddleware(route)(utils.SetCosmosHeader(destination))
|
||||
|
||||
if !route.SkipURLClean {
|
||||
destination = cleanPathMiddleware(destination)
|
||||
}
|
||||
|
||||
origin.Handler(destination)
|
||||
|
||||
utils.Log("Added route: [" + (string)(route.Mode) + "] " + route.Host + route.PathPrefix + " to " + route.Target + "")
|
||||
|
||||
+4
-4
@@ -325,19 +325,19 @@ func SmartShieldMiddleware(shieldID string, route utils.ProxyRouteConfig) func(h
|
||||
policy.PerUserTimeBudget = 2 * 60 * 60 * 1000 // 2 hours
|
||||
}
|
||||
if(policy.PerUserRequestLimit == 0) {
|
||||
policy.PerUserRequestLimit = 12000 // 150 requests per minute
|
||||
policy.PerUserRequestLimit = 18000 // 225 requests per minute
|
||||
}
|
||||
if(policy.PerUserByteLimit == 0) {
|
||||
policy.PerUserByteLimit = 150 * 1024 * 1024 * 1024 // 150GB
|
||||
policy.PerUserByteLimit = 200 * 1024 * 1024 * 1024 // 200GB
|
||||
}
|
||||
if(policy.PolicyStrictness == 0) {
|
||||
policy.PolicyStrictness = 2 // NORMAL
|
||||
}
|
||||
if(policy.PerUserSimultaneous == 0) {
|
||||
policy.PerUserSimultaneous = 24
|
||||
policy.PerUserSimultaneous = 100
|
||||
}
|
||||
if(policy.MaxGlobalSimultaneous == 0) {
|
||||
policy.MaxGlobalSimultaneous = 250
|
||||
policy.MaxGlobalSimultaneous = 2000
|
||||
}
|
||||
if(policy.PrivilegedGroups == 0) {
|
||||
policy.PrivilegedGroups = utils.ADMIN
|
||||
|
||||
@@ -98,6 +98,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
||||
"LicenceNumber": licenceNumber,
|
||||
"LicenceNodeNumber": licenceNodeNumber,
|
||||
"ConfigFolder": absoluteConfigPath,
|
||||
"ConstellationName": utils.GetMainConfig().ConstellationConfig.ThisDeviceName,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -257,6 +257,7 @@ type ProxyRouteConfig struct {
|
||||
Tunnel string `yaml:"tunnel,omitempty"`
|
||||
TunneledHost string `yaml:"tunneled_host,omitempty"`
|
||||
ExtraHeaders map[string]string `yaml:"extra_headers,omitempty"`
|
||||
SkipURLClean bool `yaml:"skip_url_clean"`
|
||||
Const_IsTunneled bool `yaml:"-", json:"-"`
|
||||
}
|
||||
|
||||
|
||||
+9
-8
@@ -453,14 +453,15 @@ func SoftRestartServer() {
|
||||
|
||||
RestartHTTPServer()
|
||||
|
||||
WaitForAllJobs()
|
||||
|
||||
RestartConstellation() // Constellation
|
||||
InitRemoteStorage() // rclone
|
||||
InitBackups() // restic
|
||||
InitSnapRAIDConfig() // snapraid
|
||||
|
||||
RestartCRON()
|
||||
go func() {
|
||||
WaitForAllJobs()
|
||||
RestartConstellation() // Constellation
|
||||
InitRemoteStorage() // rclone
|
||||
InitBackups() // restic
|
||||
InitSnapRAIDConfig() // snapraid
|
||||
|
||||
RestartCRON()
|
||||
}()
|
||||
}
|
||||
|
||||
func LetsEncryptValidOnly(hostnames []string, acceptWildcard bool) []string {
|
||||
|
||||
Reference in New Issue
Block a user