From c401fdcd74d8b373e9a631d3c0159e253d2970d4 Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Sun, 1 Feb 2026 15:58:28 +0200 Subject: [PATCH] fixed more bugs and updated docs --- docs/ARCHITECTURE.md | 11 +++++---- docs/DEV_DEPLOY.md | 24 ++++++++++--------- pkg/cli/production/install/orchestrator.go | 26 ++++++++++----------- pkg/environments/production/config.go | 1 + pkg/environments/production/orchestrator.go | 2 +- pkg/environments/templates/node.yaml | 4 ++++ pkg/environments/templates/render.go | 1 + pkg/gateway/handlers/join/handler.go | 26 +++++++++++++++++++-- pkg/gateway/instance_spawner.go | 2 +- pkg/namespace/cluster_manager.go | 2 +- pkg/namespace/cluster_manager_test.go | 12 +++++----- pkg/namespace/dns_manager_test.go | 20 ++++++++-------- pkg/rqlite/cluster_discovery_membership.go | 5 ++++ pkg/rqlite/process.go | 2 +- 14 files changed, 87 insertions(+), 51 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0e8a04e..d690af1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -452,15 +452,16 @@ make test-e2e # Run E2E tests ```bash # First node (genesis — creates cluster) -sudo orama install --vps-ip --domain node1.example.com --nameserver +# Nameserver nodes use the base domain as --domain +sudo orama install --vps-ip --domain example.com --base-domain example.com --nameserver # On the genesis node, generate an invite for a new node orama invite -# Outputs: sudo orama install --join https://node1.example.com --token --vps-ip +# Outputs: sudo orama install --join https://example.com --token --vps-ip -# Additional nodes (join via invite token over HTTPS) -sudo orama install --join https://node1.example.com --token \ - --vps-ip --nameserver +# Additional nameserver nodes (join via invite token over HTTPS) +sudo orama install --join https://example.com --token \ + --vps-ip --domain example.com --base-domain example.com --nameserver ``` **Security:** Nodes join via single-use invite tokens over HTTPS. A WireGuard VPN tunnel diff --git a/docs/DEV_DEPLOY.md b/docs/DEV_DEPLOY.md index 088e63e..e0f2555 100644 --- a/docs/DEV_DEPLOY.md +++ b/docs/DEV_DEPLOY.md @@ -100,10 +100,10 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS | Flag | Description | |------|-------------| | `--vps-ip ` | VPS public IP address (required) | -| `--domain ` | Domain for HTTPS certificates | -| `--base-domain ` | Base domain for deployment routing (e.g., dbrs.space) | +| `--domain ` | Domain for HTTPS certificates. Nameserver nodes use the base domain (e.g., `example.com`); non-nameserver nodes use a subdomain (e.g., `node-4.example.com`) | +| `--base-domain ` | Base domain for deployment routing (e.g., example.com) | | `--nameserver` | Configure this node as a nameserver (CoreDNS + Caddy) | -| `--join ` | Join existing cluster via HTTPS URL (e.g., `https://node1.dbrs.space`) | +| `--join ` | Join existing cluster via HTTPS URL (e.g., `https://node1.example.com`) | | `--token ` | Invite token for joining (from `orama invite` on existing node) | | `--branch ` | Git branch to use (default: main) | | `--no-pull` | Skip git clone/pull, use existing `/home/debros/src` | @@ -143,16 +143,18 @@ To deploy to all nodes, repeat steps 3-5 (dev) or 3-4 (production) for each VPS ```bash # 1. Genesis node (first node, creates cluster) -sudo orama install --vps-ip 1.2.3.4 --domain node1.dbrs.space \ - --base-domain dbrs.space --nameserver +# Nameserver nodes use the base domain as --domain +sudo orama install --vps-ip 1.2.3.4 --domain example.com \ + --base-domain example.com --nameserver # 2. On genesis node, generate an invite orama invite -# Output: sudo orama install --join https://node1.dbrs.space --token --vps-ip +# Output: sudo orama install --join https://example.com --token --vps-ip # 3. On the new node, run the printed command -sudo orama install --join https://node1.dbrs.space --token abc123... \ - --vps-ip 5.6.7.8 --nameserver +# Nameserver nodes use the base domain; non-nameserver nodes use subdomains (e.g., node-4.example.com) +sudo orama install --join https://example.com --token abc123... \ + --vps-ip 5.6.7.8 --domain example.com --base-domain example.com --nameserver ``` The join flow establishes a WireGuard VPN tunnel before starting cluster services. @@ -161,9 +163,9 @@ No cluster ports are ever exposed publicly. #### DNS Prerequisite -The `--join` URL should use the HTTPS domain of the genesis node (e.g., `https://node1.dbrs.space`). -For this to work, the domain registrar for `dbrs.space` must have NS records pointing to the genesis -node's IP so that `node1.dbrs.space` resolves publicly. +The `--join` URL should use the HTTPS domain of the genesis node (e.g., `https://node1.example.com`). +For this to work, the domain registrar for `example.com` must have NS records pointing to the genesis +node's IP so that `node1.example.com` resolves publicly. **If DNS is not yet configured**, you can use the genesis node's public IP with HTTP as a fallback: diff --git a/pkg/cli/production/install/orchestrator.go b/pkg/cli/production/install/orchestrator.go index e1f6c45..392c247 100644 --- a/pkg/cli/production/install/orchestrator.go +++ b/pkg/cli/production/install/orchestrator.go @@ -484,9 +484,9 @@ func promptForBaseDomain() string { fmt.Println("=================================") fmt.Println("Select the network environment for this node:") fmt.Println() - fmt.Println(" 1. devnet-orama.network (Development - for testing)") - fmt.Println(" 2. testnet-orama.network (Testnet - pre-production)") - fmt.Println(" 3. mainnet-orama.network (Mainnet - production)") + fmt.Println(" 1. orama-devnet.network (Development - for testing)") + fmt.Println(" 2. orama-testnet.network (Testnet - pre-production)") + fmt.Println(" 3. orama-mainnet.network (Mainnet - production)") fmt.Println(" 4. Custom domain...") fmt.Println() fmt.Print("Select option [1-4] (default: 1): ") @@ -496,21 +496,21 @@ func promptForBaseDomain() string { switch choice { case "", "1": - fmt.Println("✓ Selected: devnet-orama.network") - return "devnet-orama.network" + fmt.Println("✓ Selected: orama-devnet.network") + return "orama-devnet.network" case "2": - fmt.Println("✓ Selected: testnet-orama.network") - return "testnet-orama.network" + fmt.Println("✓ Selected: orama-testnet.network") + return "orama-testnet.network" case "3": - fmt.Println("✓ Selected: mainnet-orama.network") - return "mainnet-orama.network" + fmt.Println("✓ Selected: orama-mainnet.network") + return "orama-mainnet.network" case "4": fmt.Print("Enter custom base domain (e.g., example.com): ") customDomain, _ := reader.ReadString('\n') customDomain = strings.TrimSpace(customDomain) if customDomain == "" { - fmt.Println("⚠️ No domain entered, using devnet-orama.network") - return "devnet-orama.network" + fmt.Println("⚠️ No domain entered, using orama-devnet.network") + return "orama-devnet.network" } // Remove any protocol prefix if user included it customDomain = strings.TrimPrefix(customDomain, "https://") @@ -519,7 +519,7 @@ func promptForBaseDomain() string { fmt.Printf("✓ Selected: %s\n", customDomain) return customDomain default: - fmt.Println("⚠️ Invalid option, using devnet-orama.network") - return "devnet-orama.network" + fmt.Println("⚠️ Invalid option, using orama-devnet.network") + return "orama-devnet.network" } } diff --git a/pkg/environments/production/config.go b/pkg/environments/production/config.go index 6561427..435ed93 100644 --- a/pkg/environments/production/config.go +++ b/pkg/environments/production/config.go @@ -176,6 +176,7 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri TLSCacheDir: tlsCacheDir, HTTPPort: httpPort, HTTPSPort: httpsPort, + WGIP: vpsIP, } // RQLite node-to-node TLS encryption is disabled by default diff --git a/pkg/environments/production/orchestrator.go b/pkg/environments/production/orchestrator.go index a844eae..f091feb 100644 --- a/pkg/environments/production/orchestrator.go +++ b/pkg/environments/production/orchestrator.go @@ -504,7 +504,7 @@ func (ps *ProductionSetup) Phase4GenerateConfigs(peerAddresses []string, vpsIP s olricSeedPeers = olricPeers[0] } olricConfig, err := ps.configGenerator.GenerateOlricConfig( - "127.0.0.1", // HTTP API on localhost + vpsIP, // HTTP API on WG IP (unique per node, avoids memberlist name conflict) 3320, vpsIP, // Memberlist on WG IP for clustering 3322, diff --git a/pkg/environments/templates/node.yaml b/pkg/environments/templates/node.yaml index 7a894d9..33d627b 100644 --- a/pkg/environments/templates/node.yaml +++ b/pkg/environments/templates/node.yaml @@ -70,7 +70,11 @@ http_gateway: client_namespace: "default" rqlite_dsn: "http://localhost:{{.RQLiteHTTPPort}}" olric_servers: +{{- if .WGIP}} + - "{{.WGIP}}:3320" +{{- else}} - "127.0.0.1:3320" +{{- end}} olric_timeout: "10s" ipfs_cluster_api_url: "http://localhost:{{.ClusterAPIPort}}" ipfs_api_url: "http://localhost:{{.IPFSAPIPort}}" diff --git a/pkg/environments/templates/render.go b/pkg/environments/templates/render.go index 74b1de0..9f4b9c3 100644 --- a/pkg/environments/templates/render.go +++ b/pkg/environments/templates/render.go @@ -32,6 +32,7 @@ type NodeConfigData struct { TLSCacheDir string // Directory for ACME certificate cache HTTPPort int // HTTP port for ACME challenges (usually 80) HTTPSPort int // HTTPS port (usually 443) + WGIP string // WireGuard IP address (e.g., 10.0.0.1) // Node-to-node TLS encryption for RQLite Raft communication // Required when using SNI gateway for Raft traffic routing diff --git a/pkg/gateway/handlers/join/handler.go b/pkg/gateway/handlers/join/handler.go index ddaf3a3..d59b56d 100644 --- a/pkg/gateway/handlers/join/handler.go +++ b/pkg/gateway/handlers/join/handler.go @@ -10,7 +10,11 @@ import ( "strings" "time" + "path/filepath" + "github.com/DeBrosOfficial/network/pkg/rqlite" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" "go.uber.org/zap" ) @@ -406,12 +410,30 @@ func (h *Handler) queryIPFSClusterPeerInfo(myWGIP string) PeerInfo { } // buildBootstrapPeers constructs bootstrap peer multiaddrs using WG IPs +// Uses the node's LibP2P peer ID (port 4001), NOT the IPFS peer ID (port 4101) func (h *Handler) buildBootstrapPeers(myWGIP, ipfsPeerID string) []string { - if ipfsPeerID == "" { + // Read the node's LibP2P identity from disk + keyPath := filepath.Join(h.oramaDir, "data", "identity.key") + keyData, err := os.ReadFile(keyPath) + if err != nil { + h.logger.Warn("Failed to read node identity for bootstrap peers", zap.Error(err)) return nil } + + priv, err := crypto.UnmarshalPrivateKey(keyData) + if err != nil { + h.logger.Warn("Failed to unmarshal node identity key", zap.Error(err)) + return nil + } + + peerID, err := peer.IDFromPublicKey(priv.GetPublic()) + if err != nil { + h.logger.Warn("Failed to derive peer ID from identity key", zap.Error(err)) + return nil + } + return []string{ - fmt.Sprintf("/ip4/%s/tcp/4101/p2p/%s", myWGIP, ipfsPeerID), + fmt.Sprintf("/ip4/%s/tcp/4001/p2p/%s", myWGIP, peerID.String()), } } diff --git a/pkg/gateway/instance_spawner.go b/pkg/gateway/instance_spawner.go index f32d5b0..1dce319 100644 --- a/pkg/gateway/instance_spawner.go +++ b/pkg/gateway/instance_spawner.go @@ -75,7 +75,7 @@ type InstanceConfig struct { Namespace string // Namespace name (e.g., "alice") NodeID string // Physical node ID HTTPPort int // HTTP API port - BaseDomain string // Base domain (e.g., "devnet-orama.network") + BaseDomain string // Base domain (e.g., "orama-devnet.network") RQLiteDSN string // RQLite connection DSN (e.g., "http://localhost:10000") OlricServers []string // Olric server addresses NodePeerID string // Physical node's peer ID for home node management diff --git a/pkg/namespace/cluster_manager.go b/pkg/namespace/cluster_manager.go index 1f27000..03d90a8 100644 --- a/pkg/namespace/cluster_manager.go +++ b/pkg/namespace/cluster_manager.go @@ -20,7 +20,7 @@ import ( // ClusterManagerConfig contains configuration for the cluster manager type ClusterManagerConfig struct { - BaseDomain string // Base domain for namespace gateways (e.g., "devnet-orama.network") + BaseDomain string // Base domain for namespace gateways (e.g., "orama-devnet.network") BaseDataDir string // Base directory for namespace data (e.g., "~/.orama/data/namespaces") } diff --git a/pkg/namespace/cluster_manager_test.go b/pkg/namespace/cluster_manager_test.go index 2a40370..6588235 100644 --- a/pkg/namespace/cluster_manager_test.go +++ b/pkg/namespace/cluster_manager_test.go @@ -9,12 +9,12 @@ import ( func TestClusterManagerConfig(t *testing.T) { cfg := ClusterManagerConfig{ - BaseDomain: "devnet-orama.network", + BaseDomain: "orama-devnet.network", BaseDataDir: "~/.orama/data/namespaces", } - if cfg.BaseDomain != "devnet-orama.network" { - t.Errorf("BaseDomain = %s, want devnet-orama.network", cfg.BaseDomain) + if cfg.BaseDomain != "orama-devnet.network" { + t.Errorf("BaseDomain = %s, want orama-devnet.network", cfg.BaseDomain) } if cfg.BaseDataDir != "~/.orama/data/namespaces" { t.Errorf("BaseDataDir = %s, want ~/.orama/data/namespaces", cfg.BaseDataDir) @@ -25,7 +25,7 @@ func TestNewClusterManager(t *testing.T) { mockDB := newMockRQLiteClient() logger := zap.NewNop() cfg := ClusterManagerConfig{ - BaseDomain: "devnet-orama.network", + BaseDomain: "orama-devnet.network", BaseDataDir: "/tmp/test-namespaces", } @@ -288,10 +288,10 @@ func TestClusterManager_PortAllocationOrder(t *testing.T) { func TestClusterManager_DNSFormat(t *testing.T) { // Test the DNS domain format for namespace gateways - baseDomain := "devnet-orama.network" + baseDomain := "orama-devnet.network" namespaceName := "alice" - expectedDomain := "ns-alice.devnet-orama.network" + expectedDomain := "ns-alice.orama-devnet.network" actualDomain := "ns-" + namespaceName + "." + baseDomain if actualDomain != expectedDomain { diff --git a/pkg/namespace/dns_manager_test.go b/pkg/namespace/dns_manager_test.go index e518359..3fe682d 100644 --- a/pkg/namespace/dns_manager_test.go +++ b/pkg/namespace/dns_manager_test.go @@ -14,9 +14,9 @@ func TestDNSRecordManager_FQDNFormat(t *testing.T) { baseDomain string expected string }{ - {"alice", "devnet-orama.network", "ns-alice.devnet-orama.network."}, - {"bob", "testnet-orama.network", "ns-bob.testnet-orama.network."}, - {"my-namespace", "mainnet-orama.network", "ns-my-namespace.mainnet-orama.network."}, + {"alice", "orama-devnet.network", "ns-alice.orama-devnet.network."}, + {"bob", "orama-testnet.network", "ns-bob.orama-testnet.network."}, + {"my-namespace", "orama-mainnet.network", "ns-my-namespace.orama-mainnet.network."}, {"test123", "example.com", "ns-test123.example.com."}, } @@ -37,8 +37,8 @@ func TestDNSRecordManager_WildcardFQDNFormat(t *testing.T) { baseDomain string expected string }{ - {"alice", "devnet-orama.network", "*.ns-alice.devnet-orama.network."}, - {"bob", "testnet-orama.network", "*.ns-bob.testnet-orama.network."}, + {"alice", "orama-devnet.network", "*.ns-alice.orama-devnet.network."}, + {"bob", "orama-testnet.network", "*.ns-bob.orama-testnet.network."}, } for _, tt := range tests { @@ -54,7 +54,7 @@ func TestDNSRecordManager_WildcardFQDNFormat(t *testing.T) { func TestNewDNSRecordManager(t *testing.T) { mockDB := newMockRQLiteClient() logger := zap.NewNop() - baseDomain := "devnet-orama.network" + baseDomain := "orama-devnet.network" manager := NewDNSRecordManager(mockDB, baseDomain, logger) @@ -88,9 +88,9 @@ func TestDNSRecordTTL(t *testing.T) { func TestDNSRecordManager_MultipleDomainFormats(t *testing.T) { // Test support for different domain formats baseDomains := []string{ - "devnet-orama.network", - "testnet-orama.network", - "mainnet-orama.network", + "orama-devnet.network", + "orama-testnet.network", + "orama-mainnet.network", "custom.example.com", "subdomain.custom.example.com", } @@ -203,7 +203,7 @@ func TestDNSRecordManager_FQDNWithTrailingDot(t *testing.T) { input string expected string }{ - {"ns-alice.devnet-orama.network", "ns-alice.devnet-orama.network."}, + {"ns-alice.orama-devnet.network", "ns-alice.orama-devnet.network."}, } for _, tt := range tests { diff --git a/pkg/rqlite/cluster_discovery_membership.go b/pkg/rqlite/cluster_discovery_membership.go index ec5bf08..7f2ff83 100644 --- a/pkg/rqlite/cluster_discovery_membership.go +++ b/pkg/rqlite/cluster_discovery_membership.go @@ -306,6 +306,11 @@ func extractIPForSort(raftAddr string) string { // IsVoter returns true if the given raft address is in the voter set // based on the current known peers. Must be called with c.mu held. func (c *ClusterDiscoveryService) IsVoterLocked(raftAddress string) bool { + // If we don't know enough peers yet, default to voter. + // Non-voter demotion only kicks in once we see more than MaxDefaultVoters peers. + if len(c.knownPeers) <= MaxDefaultVoters { + return true + } raftAddrs := make([]string, 0, len(c.knownPeers)) for _, peer := range c.knownPeers { raftAddrs = append(raftAddrs, peer.RaftAddress) diff --git a/pkg/rqlite/process.go b/pkg/rqlite/process.go index b8edeef..f938776 100644 --- a/pkg/rqlite/process.go +++ b/pkg/rqlite/process.go @@ -122,7 +122,7 @@ func (r *RQLiteManager) launchProcess(ctx context.Context, rqliteDataDir string) if r.discoveryService != nil && !r.discoveryService.IsVoter(r.discoverConfig.RaftAdvAddress) { r.logger.Info("Joining as non-voter (read replica)", zap.String("raft_address", r.discoverConfig.RaftAdvAddress)) - args = append(args, "-non-voter") + args = append(args, "-raft-non-voter") } }