diff --git a/changelog.md b/changelog.md
index a59d1559..ab3cedb2 100644
--- a/changelog.md
+++ b/changelog.md
@@ -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
diff --git a/client/src/pages/config/users/proxyman.jsx b/client/src/pages/config/users/proxyman.jsx
index 4c54ed86..0a78c297 100644
--- a/client/src/pages/config/users/proxyman.jsx
+++ b/client/src/pages/config/users/proxyman.jsx
@@ -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 = () => {
:
},
{ title: t('mgmt.config.proxy.originTitle'), screenMin: 'md', clickable:true, search: (r) => r.Host + ' ' + r.PathPrefix, field: (r) => },
- { title: t('global.target'), screenMin: 'md', search: (r) => r.Target, field: (r) => <> > },
+ { 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
+ ? <> {r._targets.map((t, i) => )}>
+ : <> >
+ },
{ title: t('global.securityTitle'), screenMin: 'lg', field: (r) => ,
style: {minWidth: '70px'} },
{ title: '', clickable:true, field: (r, k) => r._IsTunnel ?
diff --git a/client/src/pages/constellation/vpn.jsx b/client/src/pages/constellation/vpn.jsx
index a5515b7e..d242889f 100644
--- a/client/src/pages/constellation/vpn.jsx
+++ b/client/src/pages/constellation/vpn.jsx
@@ -438,12 +438,16 @@ export const ConstellationVPN = ({ freeVersion }) => {
} />}
- {!freeVersion && !config.ConstellationConfig.ThisDeviceName &&
+ {!freeVersion && !config.ConstellationConfig.ThisDeviceName && <>
+
+ {t('mgmt.constellation.setup.lighthouseInfo')}
+
+
{t('mgmt.constellation.setup.isLighthouse.label')}
- } />}
+ } />>}
{constellationEnabled && formik.values.IsLighthouse && <>
{t('mgmt.constellation.setup.relayRequests.label')}
diff --git a/client/src/utils/locales/en/translation.json b/client/src/utils/locales/en/translation.json
index e3fedc74..4a0683eb 100644
--- a/client/src/utils/locales/en/translation.json
+++ b/client/src/utils/locales/en/translation.json
@@ -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:",
diff --git a/package.json b/package.json
index 9fac7c6f..319561c4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cosmos-server",
- "version": "0.21.7",
+ "version": "0.22.0-unstable01",
"description": "",
"main": "test-server.js",
"bugs": {
diff --git a/src/authorizationserver/oauth2.go b/src/authorizationserver/oauth2.go
index 7b3a8e34..af591e1f 100644
--- a/src/authorizationserver/oauth2.go
+++ b/src/authorizationserver/oauth2.go
@@ -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 {
diff --git a/src/constellation/NATS.go b/src/constellation/NATS.go
index 9ad5c7b5..1382c03f 100644
--- a/src/constellation/NATS.go
+++ b/src/constellation/NATS.go
@@ -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()
diff --git a/src/constellation/api_devices_block.go b/src/constellation/api_devices_block.go
index 972799df..b90930b6 100644
--- a/src/constellation/api_devices_block.go
+++ b/src/constellation/api_devices_block.go
@@ -92,7 +92,7 @@ func DeviceBlock(w http.ResponseWriter, req *http.Request) {
}
go func() {
- SendNewDBSyncMessage()
+ go SendNewDBSyncMessage()
time.Sleep(2 * time.Second)
RestartNebula()
}()
diff --git a/src/constellation/api_devices_create.go b/src/constellation/api_devices_create.go
index d15453c3..52f12ea3 100644
--- a/src/constellation/api_devices_create.go
+++ b/src/constellation/api_devices_create.go
@@ -168,7 +168,7 @@ func DeviceCreate_API(w http.ResponseWriter, req *http.Request) {
})
go func() {
- SendNewDBSyncMessage()
+ go SendNewDBSyncMessage()
time.Sleep(2 * time.Second)
RestartNebula()
}()
diff --git a/src/constellation/api_devices_edit.go b/src/constellation/api_devices_edit.go
index 7ec34e97..750ab307 100644
--- a/src/constellation/api_devices_edit.go
+++ b/src/constellation/api_devices_edit.go
@@ -89,7 +89,7 @@ func DeviceEdit_API(w http.ResponseWriter, req *http.Request) {
})
go func() {
- SendNewDBSyncMessage()
+ go SendNewDBSyncMessage()
time.Sleep(2 * time.Second)
RestartNebula()
}()
diff --git a/src/constellation/index.go b/src/constellation/index.go
index e41a91c8..ccfd7b2e 100644
--- a/src/constellation/index.go
+++ b/src/constellation/index.go
@@ -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
diff --git a/src/constellation/nebula.go b/src/constellation/nebula.go
index 54fc3f08..4bf1d153 100644
--- a/src/constellation/nebula.go
+++ b/src/constellation/nebula.go
@@ -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 == "" {
diff --git a/src/constellation/tunnels.go b/src/constellation/tunnels.go
index 11bb095f..0c3ab1ed 100644
--- a/src/constellation/tunnels.go
+++ b/src/constellation/tunnels.go
@@ -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)
}
diff --git a/src/proxy/buildFromConfig.go b/src/proxy/buildFromConfig.go
index fc3ef547..658a9893 100644
--- a/src/proxy/buildFromConfig.go
+++ b/src/proxy/buildFromConfig.go
@@ -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"))
diff --git a/src/proxy/routeTo.go b/src/proxy/routeTo.go
index c0b6f097..330ef067 100644
--- a/src/proxy/routeTo.go
+++ b/src/proxy/routeTo.go
@@ -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) {
diff --git a/src/proxy/tunnel_lb.go b/src/proxy/tunnel_lb.go
index e597dceb..93e98704 100644
--- a/src/proxy/tunnel_lb.go
+++ b/src/proxy/tunnel_lb.go
@@ -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)
diff --git a/src/utils/types.go b/src/utils/types.go
index 32a88bac..ca5c38a2 100644
--- a/src/utils/types.go
+++ b/src/utils/types.go
@@ -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 {
diff --git a/src/utils/utils.go b/src/utils/utils.go
index 4065a47b..b6a902c6 100644
--- a/src/utils/utils.go
+++ b/src/utils/utils.go
@@ -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, " ") {