diff --git a/pkg/environments/production/config.go b/pkg/environments/production/config.go index c5167eb..a2fd99e 100644 --- a/pkg/environments/production/config.go +++ b/pkg/environments/production/config.go @@ -106,13 +106,15 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri } // Determine advertise addresses - use vpsIP if provided - // When HTTPS/SNI is enabled, use domain-based raft address for SNI routing + // When HTTPS is enabled, RQLite uses native TLS on port 7002 (not SNI gateway) + // This avoids conflicts between SNI gateway TLS termination and RQLite's native TLS var httpAdvAddr, raftAdvAddr string if vpsIP != "" { httpAdvAddr = net.JoinHostPort(vpsIP, "5001") - if enableHTTPS && domain != "" { - // Use SNI domain for Raft advertisement so other nodes connect via SNI gateway - raftAdvAddr = fmt.Sprintf("raft.%s:7001", domain) + if enableHTTPS { + // Use direct IP:7002 for Raft - RQLite handles TLS natively via -node-cert + // This bypasses the SNI gateway which would cause TLS termination conflicts + raftAdvAddr = net.JoinHostPort(vpsIP, "7002") } else { raftAdvAddr = net.JoinHostPort(vpsIP, "7001") } @@ -123,15 +125,26 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri } // Determine RQLite join address + // When HTTPS is enabled, use port 7002 (direct RQLite TLS) instead of 7001 (SNI gateway) + joinPort := "7001" + if enableHTTPS { + joinPort = "7002" + } + var rqliteJoinAddr string if joinAddress != "" { // Use explicitly provided join address - rqliteJoinAddr = joinAddress + // If it contains :7001 and HTTPS is enabled, update to :7002 + if enableHTTPS && strings.Contains(joinAddress, ":7001") { + rqliteJoinAddr = strings.Replace(joinAddress, ":7001", ":7002", 1) + } else { + rqliteJoinAddr = joinAddress + } } else if len(peerAddresses) > 0 { // Infer join address from peers peerIP := inferPeerIP(peerAddresses, "") if peerIP != "" { - rqliteJoinAddr = net.JoinHostPort(peerIP, "7001") + rqliteJoinAddr = net.JoinHostPort(peerIP, joinPort) // Validate that join address doesn't match this node's own raft address (would cause self-join) if rqliteJoinAddr == raftAdvAddr { rqliteJoinAddr = "" // Clear it - this is the first node @@ -176,14 +189,13 @@ func (cg *ConfigGenerator) GenerateNodeConfig(peerAddresses []string, vpsIP stri HTTPSPort: httpsPort, } - // When HTTPS/SNI is enabled, configure RQLite node-to-node TLS encryption - // This allows Raft traffic to be routed through the SNI gateway - // Uses the same certificates as the SNI gateway (Let's Encrypt or self-signed) + // When HTTPS is enabled, configure RQLite node-to-node TLS encryption + // RQLite handles TLS natively on port 7002, bypassing the SNI gateway + // This avoids TLS termination conflicts between SNI gateway and RQLite if enableHTTPS && domain != "" { data.NodeCert = filepath.Join(tlsCacheDir, domain+".crt") data.NodeKey = filepath.Join(tlsCacheDir, domain+".key") // Skip verification since nodes may have different domain certificates - // and we're routing through the SNI gateway which terminates TLS data.NodeNoVerify = true } diff --git a/pkg/environments/templates/node.yaml b/pkg/environments/templates/node.yaml index bd1cb5a..df8cc10 100644 --- a/pkg/environments/templates/node.yaml +++ b/pkg/environments/templates/node.yaml @@ -15,7 +15,7 @@ database: rqlite_port: {{.RQLiteHTTPPort}} rqlite_raft_port: {{.RQLiteRaftInternalPort}} rqlite_join_address: "{{.RQLiteJoinAddress}}" - {{if .NodeCert}}# Node-to-node TLS encryption for Raft communication (required for SNI gateway routing) + {{if .NodeCert}}# Node-to-node TLS encryption for Raft communication (direct RQLite TLS on port 7002) node_cert: "{{.NodeCert}}" node_key: "{{.NodeKey}}" {{if .NodeCACert}}node_ca_cert: "{{.NodeCACert}}" @@ -68,7 +68,7 @@ http_gateway: cert_file: "{{.TLSCacheDir}}/{{.Domain}}.crt" key_file: "{{.TLSCacheDir}}/{{.Domain}}.key" routes: - raft.{{.Domain}}: "localhost:{{.RQLiteRaftInternalPort}}" + # Note: Raft traffic bypasses SNI gateway - RQLite uses native TLS on port 7002 ipfs.{{.Domain}}: "localhost:4101" ipfs-cluster.{{.Domain}}: "localhost:9096" olric.{{.Domain}}: "localhost:3322" diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 50d76da..d659e28 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -25,7 +25,8 @@ type InstallerConfig struct { VpsIP string Domain string PeerDomain string // Domain of existing node to join - JoinAddress string // Auto-populated: raft.{PeerDomain}:7001 + PeerIP string // Resolved IP of peer domain (for Raft join) + JoinAddress string // Auto-populated: {PeerIP}:7002 (direct RQLite TLS) Peers []string // Auto-populated: /dns4/{PeerDomain}/tcp/4001/p2p/{PeerID} ClusterSecret string SwarmKeyHex string // 64-hex IPFS swarm key (for joining private network) @@ -280,8 +281,28 @@ func (m *Model) handleEnter() (tea.Model, tea.Cmd) { m.config.PeerDomain = peerDomain m.discoveredPeer = discovery.PeerID - // Auto-populate join address and bootstrap peers - m.config.JoinAddress = fmt.Sprintf("raft.%s:7001", peerDomain) + // Resolve peer domain to IP for direct RQLite TLS connection + // RQLite uses native TLS on port 7002 (not SNI gateway on 7001) + peerIPs, err := net.LookupIP(peerDomain) + if err != nil || len(peerIPs) == 0 { + m.err = fmt.Errorf("failed to resolve peer domain %s to IP: %w", peerDomain, err) + return m, nil + } + // Prefer IPv4 + var peerIP string + for _, ip := range peerIPs { + if ip.To4() != nil { + peerIP = ip.String() + break + } + } + if peerIP == "" { + peerIP = peerIPs[0].String() + } + m.config.PeerIP = peerIP + + // Auto-populate join address (direct RQLite TLS on port 7002) and bootstrap peers + m.config.JoinAddress = fmt.Sprintf("%s:7002", peerIP) m.config.Peers = []string{ fmt.Sprintf("/dns4/%s/tcp/4001/p2p/%s", peerDomain, discovery.PeerID), } @@ -836,12 +857,13 @@ func detectPublicIP() string { } // validateSNIDNSRecords checks if the required SNI DNS records exist -// It tries to resolve the key SNI hostnames for RQLite, IPFS, IPFS Cluster, and Olric +// It tries to resolve the key SNI hostnames for IPFS, IPFS Cluster, and Olric +// Note: Raft no longer uses SNI - it uses direct RQLite TLS on port 7002 // All should resolve to the same IP (the node's public IP or domain) func validateSNIDNSRecords(domain string) error { // List of SNI services that need DNS records + // Note: raft.domain is NOT included - RQLite uses direct TLS on port 7002 sniServices := []string{ - fmt.Sprintf("raft.%s", domain), fmt.Sprintf("ipfs.%s", domain), fmt.Sprintf("ipfs-cluster.%s", domain), fmt.Sprintf("olric.%s", domain),