[release] v0.22.0-unstable01

This commit is contained in:
Yann Stepienik
2026-03-06 18:52:33 +00:00
parent 409fe81d4c
commit ee4cc6427e
18 changed files with 145 additions and 25 deletions
+4
View File
@@ -1,5 +1,9 @@
## Version 0.22
- Support for mounting network disk
- Fix issue with Constellation sometime not connecting right away on new network
## Version 0.21.7
- Fix issue with Constellation routing HTTP tunnels to the wrong protocol
## Version 0.21.6
- Prevent potential double counting of server by wrong timing
+6 -2
View File
@@ -117,7 +117,8 @@ const ProxyManagement = () => {
API.constellation.tunnels().then((res) => {
setTunnels((res.data || []).map(r => {
let route = r.Route;
route._from = r.From;
route._from = (r.Targets || []).map(t => t.deviceName);
route._targets = r.Targets || [];
return route;
}));
});
@@ -276,7 +277,10 @@ const ProxyManagement = () => {
</div> : <div></div>
},
{ title: t('mgmt.config.proxy.originTitle'), screenMin: 'md', clickable:true, search: (r) => r.Host + ' ' + r.PathPrefix, field: (r) => <HostChip ellipsis route={r} /> },
{ title: t('global.target'), screenMin: 'md', search: (r) => r.Target, field: (r) => <><RouteMode route={r} /> <Chip label={r.Target} /></> },
{ title: t('global.target'), screenMin: 'md', search: (r) => r._IsTunnel ? (r._targets || []).map(t => t.targetURL).join(' ') : r.Target, field: (r) => r._IsTunnel && r._targets && r._targets.length > 0
? <><RouteMode route={r} /> {r._targets.map((t, i) => <Chip key={i} label={t.targetURL + ' (' + t.deviceName + ')'} style={{marginBottom: 2}} />)}</>
: <><RouteMode route={r} /> <Chip label={r.Target} /></>
},
{ title: t('global.securityTitle'), screenMin: 'lg', field: (r) => <RouteSecurity route={r} />,
style: {minWidth: '70px'} },
{ title: '', clickable:true, field: (r, k) => r._IsTunnel ? <Tooltip title={t('tooltip.route.tunnelWarn')}>
+6 -2
View File
@@ -438,12 +438,16 @@ export const ConstellationVPN = ({ freeVersion }) => {
</Tooltip>
</Stack>} />}
{!freeVersion && !config.ConstellationConfig.ThisDeviceName && <CosmosCheckbox disabled={!isAdmin} formik={formik} name="IsLighthouse" label={<Stack direction="row" spacing={0.5} alignItems="center" component="span">
{!freeVersion && !config.ConstellationConfig.ThisDeviceName && <>
<Alert severity="warning">
{t('mgmt.constellation.setup.lighthouseInfo')}
</Alert>
<CosmosCheckbox disabled={!isAdmin} formik={formik} name="IsLighthouse" label={<Stack direction="row" spacing={0.5} alignItems="center" component="span">
<span>{t('mgmt.constellation.setup.isLighthouse.label')}</span>
<Tooltip title={t('mgmt.constellation.setup.isLighthouse.tooltip')}>
<QuestionCircleOutlined style={{ fontSize: 14, cursor: 'help', opacity: 0.6 }} />
</Tooltip>
</Stack>} />}
</Stack>} /></>}
{constellationEnabled && formik.values.IsLighthouse && <>
<CosmosCheckbox disabled={!isAdmin} formik={formik} name="IsRelay" label={<Stack direction="row" spacing={0.5} alignItems="center" component="span">
<span>{t('mgmt.constellation.setup.relayRequests.label')}</span>
@@ -272,6 +272,7 @@
"mgmt.constellation.resetLabel": "Reset Network",
"mgmt.constellation.resetText": "This will completely reset the network, and disconnect all the clients. You will need to reconnect them. This cannot be undone.",
"mgmt.constellation.restartButton": "Restart VPN Service",
"mgmt.constellation.setup.lighthouseInfo": "This is important! If you are behind CGNAT or do not wish to open any port on your server, you have to uncheck this!",
"mgmt.constellation.setup.createConstellation": "Create Constellation",
"mgmt.constellation.setup.deviceName.validationError": "Device must only contain lowercase letters, numbers and hyphens",
"mgmt.constellation.setup.addDeviceSuccess": "Device added successfully! Download scan the QR Code from the Cosmos app or download the relevant files to your device along side the config and network certificate to connect:",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.21.7",
"version": "0.22.0-unstable01",
"description": "",
"main": "test-server.js",
"bugs": {
+1 -1
View File
@@ -80,7 +80,7 @@ func Init() {
oauth2 = compose.ComposeAllEnabled(foconfig, store, AuthPrivateKey)
routes := config.HTTPConfig.ProxyConfig.Routes
routes = append(routes, config.ConstellationConfig.Tunnels...)
routes = append(routes, utils.GetConstellationTunnelRoutes()...)
// Add proxy route clients
for _, route := range routes {
+13
View File
@@ -226,6 +226,11 @@ func StartNATS() {
utils.Error("[NATS] Failed to get cluster IPs", err)
}
utils.Debug("[NATS] Cluster IPs: ")
for _, cip := range cips {
utils.Debug("[NATS] Cluster IP: " + cip.String())
}
// if only one, abort
if (len(cips) == 0) {
utils.Warn("[NATS] No cluster IPs found, NATS server will not start")
@@ -296,6 +301,8 @@ func StartNATS() {
utils.Debug("[NATS] Retrying to start NATS server")
}
NATSStarted = true
if err != nil {
utils.MajorError("[NATS] Error starting NATS server", err)
} else {
@@ -312,6 +319,7 @@ func StopNATS() {
ns.WaitForShutdown()
ns = nil
}
NATSStarted = false
}
// sync lock - RWMutex allows multiple readers, single writer
@@ -320,6 +328,11 @@ var NATSClientTopic = ""
var nc *nats.Conn
var js nats.JetStreamContext
func InitNATSClient() error {
if !NATSStarted {
utils.Warn("[NATS] NATS server not started, cannot initialize client")
return errors.New("NATS server not started, cannot initialize client")
}
clientConfigLock.Lock()
defer clientConfigLock.Unlock()
+1 -1
View File
@@ -92,7 +92,7 @@ func DeviceBlock(w http.ResponseWriter, req *http.Request) {
}
go func() {
SendNewDBSyncMessage()
go SendNewDBSyncMessage()
time.Sleep(2 * time.Second)
RestartNebula()
}()
+1 -1
View File
@@ -168,7 +168,7 @@ func DeviceCreate_API(w http.ResponseWriter, req *http.Request) {
})
go func() {
SendNewDBSyncMessage()
go SendNewDBSyncMessage()
time.Sleep(2 * time.Second)
RestartNebula()
}()
+1 -1
View File
@@ -89,7 +89,7 @@ func DeviceEdit_API(w http.ResponseWriter, req *http.Request) {
})
go func() {
SendNewDBSyncMessage()
go SendNewDBSyncMessage()
time.Sleep(2 * time.Second)
RestartNebula()
}()
+12
View File
@@ -7,6 +7,7 @@ import (
var NebulaStarted = false
var NebulaHasStarted = false
var NATSStarted = false
var CachedDeviceNames = map[string]string{}
var CachedDevices = map[string]utils.ConstellationDevice{}
var needToSyncCA = false
@@ -15,6 +16,15 @@ func resyncConstellationNodes() {
SendNewDBSyncMessage()
}
func getConstellationTunnelRoutes() []utils.ProxyRouteConfig {
tunnels := GetLocalTunnelCache()
routes := make([]utils.ProxyRouteConfig, len(tunnels))
for i, t := range tunnels {
routes[i] = t.Route
}
return routes
}
func GetDefaultHostnames() []string {
hostnames, _ := utils.ListIps(true)
httpHostname := utils.GetMainConfig().HTTPConfig.Hostname
@@ -71,8 +81,10 @@ func Init() {
utils.IsConstellationIP = IsConstellationIP
utils.ResyncConstellationNodes = resyncConstellationNodes
utils.GetConstellationTunnelRoutes = getConstellationTunnelRoutes
NebulaStarted = false
NATSStarted = false
var err error
+7 -1
View File
@@ -81,7 +81,10 @@ func startNebula() error {
UpdateFirewallBlockedClients()
AdjustDNS(logBuffer)
ExportLighthouseFromDB()
e := ExportLighthouseFromDB()
if e != nil {
utils.Error("Failed to export lighthouse config from DB", e)
}
// Handle existing PID file
pidFile := utils.CONFIGFOLDER + "nebula.pid"
@@ -216,6 +219,7 @@ func monitorNebulaProcess(proc *exec.Cmd) {
defer ProcessMux.Unlock()
process = nil
NebulaStarted = false
NATSStarted = false
}
func stop() {
@@ -244,6 +248,7 @@ func stop() {
}
NebulaStarted = false
NATSStarted = false
cachedCurrentDevice = nil
CachedDevices = map[string]utils.ConstellationDevice{}
CachedDeviceNames = map[string]string{}
@@ -293,6 +298,7 @@ func ResetNebula() error {
config.ConstellationConfig.DNSDisabled = false
config.ConstellationConfig.FirewallBlockedClients = []string{}
config.ConstellationConfig.ThisDeviceName = ""
config.ConstellationConfig.IPRange = ""
config.ConstellationConfig.ConstellationHostname = strings.Join(GetDefaultHostnames(), ",")
if config.Licence == "" {
+63 -2
View File
@@ -54,7 +54,12 @@ func GetAllTunneledRoutes() []utils.ProxyRouteConfig {
protocol = "http://"
}
route.Target = protocol + thisIp + ":" + port
route.Target = protocol + thisIp
if port != "" {
route.Target += ":" + port
}
route.Mode = "PROXY"
if route.TunneledHost != "" {
@@ -126,7 +131,7 @@ func ClientHeartbeatInit() {
utils.Debug("[NATS] Key-Value store 'constellation-nodes' ready")
go UpdateLocalTunnelCache();
UpdateLocalTunnelCache()
heartbeatStopChan = make(chan struct{})
heartbeatTicker = time.NewTicker(2 * time.Second)
@@ -135,6 +140,55 @@ func ClientHeartbeatInit() {
stopChan := heartbeatStopChan
ticker := heartbeatTicker
// Watch KV for changes and refresh tunnel cache
go func() {
err := ClientConnectToJS()
if err != nil {
utils.Warn("[NATS] Error connecting to JetStream for KV watcher: " + err.Error())
return
}
clientConfigLock.RLock()
if js == nil {
clientConfigLock.RUnlock()
utils.Warn("[NATS] JetStream context is nil for KV watcher")
return
}
kv, err := js.KeyValue("constellation-nodes")
clientConfigLock.RUnlock()
if err != nil {
utils.Warn("[NATS] Error getting KV store for watcher: " + err.Error())
return
}
watcher, err := kv.WatchAll()
if err != nil {
utils.Warn("[NATS] Error creating KV watcher: " + err.Error())
return
}
defer watcher.Stop()
utils.Log("[NATS] KV watcher started for tunnel cache updates")
for {
select {
case <-stopChan:
utils.Log("[NATS] KV watcher stopped")
return
case entry, ok := <-watcher.Updates():
if !ok {
utils.Warn("[NATS] KV watcher channel closed")
return
}
if entry == nil {
// nil marks end of initial values
continue
}
GetLocalTunnelCache()
}
}
}()
go func() {
for {
select {
@@ -288,6 +342,13 @@ func UpdateLocalTunnelCache() {
tunnels := make([]utils.ConstellationTunnel, 0, len(byName))
for _, t := range byName {
// Ensure local node is always first in targets
for i, target := range t.Targets {
if target.DeviceName == currentDeviceName && i != 0 {
t.Targets[0], t.Targets[i] = t.Targets[i], t.Targets[0]
break
}
}
tunnels = append(tunnels, *t)
}
-1
View File
@@ -10,7 +10,6 @@ import (
)
func BuildFromConfig(router *mux.Router, config utils.ProxyConfig) *mux.Router {
router.HandleFunc("/_health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
+20 -7
View File
@@ -241,16 +241,29 @@ func TunnelRouteTo(tunnel utils.ConstellationTunnel, lb *TunnelLoadBalancer) htt
handler http.Handler
}
// For self-tunnel, use the original route handler to avoid proxy loop
currentDeviceName, _ := constellation.GetCurrentDeviceName()
proxies := make([]targetProxy, 0, len(tunnel.Targets))
for _, t := range tunnel.Targets {
route := tunnel.Route
route.Target = t.TargetURL
proxy, err := NewProxy(t.TargetURL, route.AcceptInsecureHTTPSTarget, route.DisableHeaderHardening, route)
if err != nil {
utils.Error("Create Tunnel Route for "+t.DeviceName, err)
continue
if t.DeviceName == currentDeviceName {
// Find the original config route and use RouteTo directly
for _, cfgRoute := range utils.GetMainConfig().HTTPConfig.ProxyConfig.Routes {
if cfgRoute.Name == tunnel.Route.Name {
proxies = append(proxies, targetProxy{t, RouteTo(cfgRoute)})
break
}
}
} else {
route := tunnel.Route
route.Target = t.TargetURL
proxy, err := NewProxy(t.TargetURL, route.AcceptInsecureHTTPSTarget, route.DisableHeaderHardening, route)
if err != nil {
utils.Error("Create Tunnel Route for "+t.DeviceName, err)
continue
}
proxies = append(proxies, targetProxy{t, proxy})
}
proxies = append(proxies, targetProxy{t, proxy})
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+2
View File
@@ -3,6 +3,7 @@ package proxy
import (
"sync"
"sync/atomic"
"strings"
"github.com/azukaar/cosmos-server/src/constellation"
"github.com/azukaar/cosmos-server/src/utils"
@@ -45,6 +46,7 @@ func (lb *TunnelLoadBalancer) SelectTarget(targets []utils.TunnelTarget, routeNa
// Mode selection
var selected *utils.TunnelTarget
mode = strings.ToLower(mode)
switch mode {
case "round_robin":
c := lb.getCounter(routeName)
+3 -3
View File
@@ -262,9 +262,9 @@ type ProxyRouteConfig struct {
ExtraHeaders map[string]string `yaml:"extra_headers,omitempty"`
SkipURLClean bool `yaml:"skip_url_clean"`
UseH2C bool `yaml:"use_h2c"`
LBMode string `yaml:"lb_mode" json:"lb_mode,omitempty"`
LBStickyMode bool `yaml:"lb_sticky_mode" json:"lb_sticky_mode,omitempty"`
Const_IsTunneled bool `yaml:"-", json:"-"`
LBMode string `yaml:"lb_mode" json:"LBMode,omitempty"`
LBStickyMode bool `yaml:"lb_sticky_mode" json:"LBStickyMode,omitempty"`
Const_IsTunneled bool `yaml:"-" json:"-"`
}
type EmailConfig struct {
+3 -2
View File
@@ -66,6 +66,7 @@ var StopAllRCloneProcess func(bool)
var InitPremiumFeatures func()
var ResyncConstellationNodes = func() {}
var GetConstellationTunnelRoutes = func() []ProxyRouteConfig { return []ProxyRouteConfig{} }
var LetsEncryptErrors = []string{}
@@ -560,8 +561,8 @@ func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
mainHostname,
}
proxies := GetMainConfig().ConstellationConfig.Tunnels
proxies = append(proxies, GetMainConfig().HTTPConfig.ProxyConfig.Routes...)
proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
proxies = append(proxies, GetConstellationTunnelRoutes()...)
for _, proxy := range proxies {
if proxy.UseHost && proxy.Host != "" && !strings.Contains(proxy.Host, ",") && !strings.Contains(proxy.Host, " ") {