diff --git a/CHANGELOG.md b/CHANGELOG.md index b16ec7b..5fbd02e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,23 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant ### Deprecated ### Fixed +## [0.70.0] - 2025-11-26 + +### Added +\n +### Changed +- The HTTP Gateway is now embedded directly within each network node, simplifying deployment and removing the need for a separate gateway service. +- The configuration for the full API Gateway (including Auth, PubSub, and internal service routing) is now part of the main node configuration. +- Development environment setup no longer generates a separate `gateway.yaml` file or starts a standalone gateway process. +- Updated local environment descriptions and default gateway fallback to reflect the node-1 designation. + +### Deprecated + +### Removed + +### Fixed +- Updated the installation instructions in the README to reflect the correct APT repository URL. + ## [0.69.22] - 2025-11-26 ### Added diff --git a/Makefile b/Makefile index 63812d4..94334e1 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ test-e2e: .PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill -VERSION := 0.69.22 +VERSION := 0.70.0 COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' diff --git a/README.md b/README.md index 54a2f2a..cccd6b5 100644 --- a/README.md +++ b/README.md @@ -200,8 +200,8 @@ make build ```bash # Install via APT -curl -fsSL https://apt.orama.network/gpg | sudo gpg --dearmor -o /usr/share/keyrings/orama.gpg -echo "deb [signed-by=/usr/share/keyrings/orama.gpg] https://apt.orama.network stable main" | sudo tee /etc/apt/sources.list.d/orama.list +echo "deb https://debrosficial.github.io/network/apt stable main" | sudo tee /etc/apt/sources.list.d/debros.list + sudo apt update && sudo apt install orama # Interactive installation (recommended) diff --git a/pkg/cli/auth_commands.go b/pkg/cli/auth_commands.go index 9e4fade..6a159c1 100644 --- a/pkg/cli/auth_commands.go +++ b/pkg/cli/auth_commands.go @@ -174,6 +174,6 @@ func getGatewayURL() string { return env.GatewayURL } - // Fallback to default + // Fallback to default (node-1) return "http://localhost:6001" } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 44e78f4..b52fba6 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -28,7 +28,7 @@ var DefaultEnvironments = []Environment{ { Name: "local", GatewayURL: "http://localhost:6001", - Description: "Local development environment", + Description: "Local development environment (node-1)", IsActive: true, }, { diff --git a/pkg/config/config.go b/pkg/config/config.go index 3b1add3..2698c1c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -106,6 +106,15 @@ type HTTPGatewayConfig struct { Routes map[string]RouteConfig `yaml:"routes"` // Service routes HTTPS HTTPSConfig `yaml:"https"` // HTTPS/TLS configuration SNI SNIConfig `yaml:"sni"` // SNI-based TCP routing configuration + + // Full gateway configuration (for API, auth, pubsub) + ClientNamespace string `yaml:"client_namespace"` // Namespace for network client + RQLiteDSN string `yaml:"rqlite_dsn"` // RQLite database DSN + OlricServers []string `yaml:"olric_servers"` // List of Olric server addresses + OlricTimeout time.Duration `yaml:"olric_timeout"` // Timeout for Olric operations + IPFSClusterAPIURL string `yaml:"ipfs_cluster_api_url"` // IPFS Cluster API URL + IPFSAPIURL string `yaml:"ipfs_api_url"` // IPFS API URL + IPFSTimeout time.Duration `yaml:"ipfs_timeout"` // Timeout for IPFS operations } // HTTPSConfig contains HTTPS/TLS configuration for the gateway @@ -216,10 +225,17 @@ func DefaultConfig() *Config { Format: "console", }, HTTPGateway: HTTPGatewayConfig{ - Enabled: true, - ListenAddr: ":8080", - NodeName: "default", - Routes: make(map[string]RouteConfig), + Enabled: true, + ListenAddr: ":8080", + NodeName: "default", + Routes: make(map[string]RouteConfig), + ClientNamespace: "default", + RQLiteDSN: "http://localhost:5001", + OlricServers: []string{"localhost:3320"}, + OlricTimeout: 10 * time.Second, + IPFSClusterAPIURL: "http://localhost:9094", + IPFSAPIURL: "http://localhost:5001", + IPFSTimeout: 60 * time.Second, }, } } diff --git a/pkg/environments/development/config.go b/pkg/environments/development/config.go index 2ece6d4..e83ec8b 100644 --- a/pkg/environments/development/config.go +++ b/pkg/environments/development/config.go @@ -62,10 +62,8 @@ func (ce *ConfigEnsurer) EnsureAll() error { } } - // Ensure gateway config - if err := ce.ensureGateway(peerAddrs); err != nil { - return fmt.Errorf("failed to ensure gateway: %w", err) - } + // Gateway configuration is now embedded in each node's config + // No separate gateway.yaml needed anymore // Ensure Olric config if err := ce.ensureOlric(); err != nil { @@ -171,34 +169,8 @@ func (ce *ConfigEnsurer) ensureNodeConfig(nodeSpec NodeSpec, peerAddrs []string) return nil } -// ensureGateway creates gateway config -func (ce *ConfigEnsurer) ensureGateway(peerAddrs []string) error { - configPath := filepath.Join(ce.oramaDir, "gateway.yaml") - - // Get first node's cluster API port for default - topology := DefaultTopology() - firstNode := topology.GetFirstNode() - - data := templates.GatewayConfigData{ - ListenPort: topology.GatewayPort, - BootstrapPeers: peerAddrs, - OlricServers: []string{fmt.Sprintf("127.0.0.1:%d", topology.OlricHTTPPort)}, - ClusterAPIPort: firstNode.ClusterAPIPort, - IPFSAPIPort: firstNode.IPFSAPIPort, - } - - config, err := templates.RenderGatewayConfig(data) - if err != nil { - return fmt.Errorf("failed to render gateway config: %w", err) - } - - if err := os.WriteFile(configPath, []byte(config), 0644); err != nil { - return fmt.Errorf("failed to write gateway config: %w", err) - } - - fmt.Printf("✓ Generated gateway.yaml\n") - return nil -} +// Gateway configuration is now embedded in each node's config +// ensureGateway is no longer needed - each node runs its own embedded gateway // ensureOlric creates Olric config func (ce *ConfigEnsurer) ensureOlric() error { diff --git a/pkg/environments/development/runner.go b/pkg/environments/development/runner.go index a1c431b..8d97c05 100644 --- a/pkg/environments/development/runner.go +++ b/pkg/environments/development/runner.go @@ -67,7 +67,7 @@ func (pm *ProcessManager) StartAll(ctx context.Context) error { {"Olric", pm.startOlric}, {"Anon", pm.startAnon}, {"Nodes (Network)", pm.startNodes}, - {"Gateway", pm.startGateway}, + // Gateway is now per-node (embedded in each node) - no separate main gateway needed } for _, svc := range services { @@ -238,7 +238,7 @@ func (pm *ProcessManager) Status(ctx context.Context) { } fmt.Fprintf(pm.logWriter, "\nConfiguration files in %s:\n", pm.oramaDir) - configFiles := []string{"node-1.yaml", "node-2.yaml", "node-3.yaml", "node-4.yaml", "node-5.yaml", "gateway.yaml", "olric-config.yaml"} + configFiles := []string{"node-1.yaml", "node-2.yaml", "node-3.yaml", "node-4.yaml", "node-5.yaml", "olric-config.yaml"} for _, f := range configFiles { path := filepath.Join(pm.oramaDir, f) if _, err := os.Stat(path); err == nil { diff --git a/pkg/environments/production/orchestrator.go b/pkg/environments/production/orchestrator.go index 6355743..63ceaf2 100644 --- a/pkg/environments/production/orchestrator.go +++ b/pkg/environments/production/orchestrator.go @@ -3,7 +3,6 @@ package production import ( "fmt" "io" - "net" "os" "os/exec" "path/filepath" @@ -376,34 +375,8 @@ func (ps *ProductionSetup) Phase4GenerateConfigs(peerAddresses []string, vpsIP s } ps.logf(" ✓ Node config generated: %s", configFile) - // Determine Olric servers for gateway config - // Olric binds to localhost, gateway connects locally - var olricServers []string - - // Start with local Olric server - if vpsIP != "" { - olricServers = []string{net.JoinHostPort(vpsIP, "3320")} - } else { - olricServers = []string{"127.0.0.1:3320"} - } - - // If joining existing cluster, also include peer Olric servers - if len(peerAddresses) > 0 { - peerIP := inferPeerIP(peerAddresses, "") - if peerIP != "" && peerIP != vpsIP { - olricServers = append(olricServers, net.JoinHostPort(peerIP, "3320")) - } - } - - gatewayConfig, err := ps.configGenerator.GenerateGatewayConfig(peerAddresses, enableHTTPS, domain, olricServers) - if err != nil { - return fmt.Errorf("failed to generate gateway config: %w", err) - } - - if err := ps.secretGenerator.SaveConfig("gateway.yaml", gatewayConfig); err != nil { - return fmt.Errorf("failed to save gateway config: %w", err) - } - ps.logf(" ✓ Gateway config generated") + // Gateway configuration is now embedded in each node's config + // No separate gateway.yaml needed - each node runs its own embedded gateway // Olric config - bind to localhost for security // External access goes through the HTTP gateway @@ -472,19 +445,12 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices() error { } ps.logf(" ✓ Olric service created") - // Node service (unified) + // Node service (unified - includes embedded gateway) nodeUnit := ps.serviceGenerator.GenerateNodeService() if err := ps.serviceController.WriteServiceUnit("debros-node.service", nodeUnit); err != nil { return fmt.Errorf("failed to write Node service: %w", err) } - ps.logf(" ✓ Node service created: debros-node.service") - - // Gateway service - gatewayUnit := ps.serviceGenerator.GenerateGatewayService() - if err := ps.serviceController.WriteServiceUnit("debros-gateway.service", gatewayUnit); err != nil { - return fmt.Errorf("failed to write Gateway service: %w", err) - } - ps.logf(" ✓ Gateway service created") + ps.logf(" ✓ Node service created: debros-node.service (with embedded gateway)") // Anyone Client service (SOCKS5 proxy) anyoneUnit := ps.serviceGenerator.GenerateAnyoneClientService() @@ -500,7 +466,8 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices() error { ps.logf(" ✓ Systemd daemon reloaded") // Enable services (unified names - no bootstrap/node distinction) - services := []string{"debros-ipfs.service", "debros-ipfs-cluster.service", "debros-olric.service", "debros-node.service", "debros-gateway.service", "debros-anyone-client.service"} + // Note: debros-gateway.service is no longer needed - each node has an embedded gateway + services := []string{"debros-ipfs.service", "debros-ipfs-cluster.service", "debros-olric.service", "debros-node.service", "debros-anyone-client.service"} for _, svc := range services { if err := ps.serviceController.EnableService(svc); err != nil { ps.logf(" ⚠️ Failed to enable %s: %v", svc, err) diff --git a/pkg/environments/templates/node.yaml b/pkg/environments/templates/node.yaml index 7f28294..c761958 100644 --- a/pkg/environments/templates/node.yaml +++ b/pkg/environments/templates/node.yaml @@ -46,30 +46,16 @@ http_gateway: enabled: true listen_addr: ":{{.UnifiedGatewayPort}}" node_name: "{{.NodeID}}" - routes: - # Node internal services - accessible on unified gateway port - rqlite_http: - path_prefix: "/rqlite/http" - backend_url: "http://localhost:{{.RQLiteHTTPPort}}" - timeout: "30s" - websocket: false - rqlite_raft: - path_prefix: "/rqlite/raft" - backend_url: "http://localhost:{{.RQLiteRaftPort}}" - timeout: "30s" - websocket: false - ipfs_api: - path_prefix: "/ipfs/api" - backend_url: "http://localhost:{{.IPFSAPIPort}}" - timeout: "60s" - websocket: true - ipfs_swarm: - path_prefix: "/ipfs/swarm" - backend_url: "http://localhost:4102" - timeout: "30s" - websocket: false - cluster_api: - path_prefix: "/cluster" - backend_url: "http://localhost:{{.ClusterAPIPort}}" - timeout: "30s" - websocket: false + + # Full gateway configuration (for API, auth, pubsub, and internal service routing) + client_namespace: "default" + rqlite_dsn: "http://localhost:{{.RQLiteHTTPPort}}" + olric_servers: + - "127.0.0.1:3320" + olric_timeout: "10s" + ipfs_cluster_api_url: "http://localhost:{{.ClusterAPIPort}}" + ipfs_api_url: "http://localhost:{{.IPFSAPIPort}}" + ipfs_timeout: "60s" + + # Routes for internal service reverse proxy (kept for backwards compatibility but not used by full gateway) + routes: {} diff --git a/pkg/node/node.go b/pkg/node/node.go index 397805b..7d87c31 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -5,6 +5,7 @@ import ( "fmt" mathrand "math/rand" "net" + "net/http" "os" "path/filepath" "strings" @@ -52,8 +53,9 @@ type Node struct { // IPFS Cluster config manager clusterConfigManager *ipfs.ClusterConfigManager - // HTTP reverse proxy gateway - httpGateway *gateway.HTTPGateway + // Full gateway (for API, auth, pubsub, and internal service routing) + apiGateway *gateway.Gateway + apiGatewayServer *http.Server } // NewNode creates a new network node @@ -632,9 +634,16 @@ func (n *Node) stopPeerDiscovery() { func (n *Node) Stop() error { n.logger.ComponentInfo(logging.ComponentNode, "Stopping network node") - // Stop HTTP Gateway - if n.httpGateway != nil { - _ = n.httpGateway.Stop() + // Stop HTTP Gateway server + if n.apiGatewayServer != nil { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = n.apiGatewayServer.Shutdown(ctx) + } + + // Close Gateway client + if n.apiGateway != nil { + n.apiGateway.Close() } // Stop cluster discovery @@ -667,14 +676,14 @@ func (n *Node) Stop() error { return nil } -// startHTTPGateway initializes and starts the HTTP reverse proxy gateway +// startHTTPGateway initializes and starts the full API gateway with auth, pubsub, and API endpoints func (n *Node) startHTTPGateway(ctx context.Context) error { if !n.config.HTTPGateway.Enabled { n.logger.ComponentInfo(logging.ComponentNode, "HTTP Gateway disabled in config") return nil } - // Create separate logger for unified gateway + // Create separate logger for gateway logFile := filepath.Join(os.ExpandEnv(n.config.Node.DataDir), "..", "logs", fmt.Sprintf("gateway-%s.log", n.config.HTTPGateway.NodeName)) // Ensure logs directory exists @@ -683,21 +692,57 @@ func (n *Node) startHTTPGateway(ctx context.Context) error { return fmt.Errorf("failed to create logs directory: %w", err) } - httpGatewayLogger, err := logging.NewFileLogger(logging.ComponentGeneral, logFile, false) + gatewayLogger, err := logging.NewFileLogger(logging.ComponentGeneral, logFile, false) if err != nil { - return fmt.Errorf("failed to create HTTP gateway logger: %w", err) + return fmt.Errorf("failed to create gateway logger: %w", err) } - // Create and start HTTP gateway with its own logger - n.httpGateway, err = gateway.NewHTTPGateway(httpGatewayLogger, &n.config.HTTPGateway) - if err != nil { - return fmt.Errorf("failed to create HTTP gateway: %w", err) + // Create full API Gateway for auth, pubsub, rqlite, and API endpoints + // This replaces both the old reverse proxy gateway and the standalone gateway + gwCfg := &gateway.Config{ + ListenAddr: n.config.HTTPGateway.ListenAddr, + ClientNamespace: n.config.HTTPGateway.ClientNamespace, + BootstrapPeers: n.config.Discovery.BootstrapPeers, + RQLiteDSN: n.config.HTTPGateway.RQLiteDSN, + OlricServers: n.config.HTTPGateway.OlricServers, + OlricTimeout: n.config.HTTPGateway.OlricTimeout, + IPFSClusterAPIURL: n.config.HTTPGateway.IPFSClusterAPIURL, + IPFSAPIURL: n.config.HTTPGateway.IPFSAPIURL, + IPFSTimeout: n.config.HTTPGateway.IPFSTimeout, } - // Start gateway in a goroutine (it handles its own lifecycle) + apiGateway, err := gateway.New(gatewayLogger, gwCfg) + if err != nil { + return fmt.Errorf("failed to create full API gateway: %w", err) + } + + n.apiGateway = apiGateway + + // Start API Gateway in a goroutine go func() { - if err := n.httpGateway.Start(ctx); err != nil { - n.logger.ComponentError(logging.ComponentNode, "HTTP Gateway error", zap.Error(err)) + n.logger.ComponentInfo(logging.ComponentNode, "Starting full API gateway", + zap.String("listen_addr", gwCfg.ListenAddr), + ) + + server := &http.Server{ + Addr: gwCfg.ListenAddr, + Handler: apiGateway.Routes(), + } + + n.apiGatewayServer = server + + // Try to bind listener + ln, err := net.Listen("tcp", gwCfg.ListenAddr) + if err != nil { + n.logger.ComponentError(logging.ComponentNode, "failed to bind API gateway listener", zap.Error(err)) + return + } + + n.logger.ComponentInfo(logging.ComponentNode, "API gateway listener bound", zap.String("listen_addr", ln.Addr().String())) + + // Serve HTTP + if err := server.Serve(ln); err != nil && err != http.ErrServerClosed { + n.logger.ComponentError(logging.ComponentNode, "API Gateway error", zap.Error(err)) } }()