fixed more bugs and updated docs

This commit is contained in:
anonpenguin23 2026-02-01 15:58:28 +02:00
parent 73dfe22438
commit c401fdcd74
14 changed files with 87 additions and 51 deletions

View File

@ -452,15 +452,16 @@ make test-e2e # Run E2E tests
```bash
# First node (genesis — creates cluster)
sudo orama install --vps-ip <IP> --domain node1.example.com --nameserver
# Nameserver nodes use the base domain as --domain
sudo orama install --vps-ip <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 <TOKEN> --vps-ip <NEW_IP>
# Outputs: sudo orama install --join https://example.com --token <TOKEN> --vps-ip <NEW_IP>
# Additional nodes (join via invite token over HTTPS)
sudo orama install --join https://node1.example.com --token <TOKEN> \
--vps-ip <IP> --nameserver
# Additional nameserver nodes (join via invite token over HTTPS)
sudo orama install --join https://example.com --token <TOKEN> \
--vps-ip <IP> --domain example.com --base-domain example.com --nameserver
```
**Security:** Nodes join via single-use invite tokens over HTTPS. A WireGuard VPN tunnel

View File

@ -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 <ip>` | VPS public IP address (required) |
| `--domain <domain>` | Domain for HTTPS certificates |
| `--base-domain <domain>` | Base domain for deployment routing (e.g., dbrs.space) |
| `--domain <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 <domain>` | Base domain for deployment routing (e.g., example.com) |
| `--nameserver` | Configure this node as a nameserver (CoreDNS + Caddy) |
| `--join <url>` | Join existing cluster via HTTPS URL (e.g., `https://node1.dbrs.space`) |
| `--join <url>` | Join existing cluster via HTTPS URL (e.g., `https://node1.example.com`) |
| `--token <token>` | Invite token for joining (from `orama invite` on existing node) |
| `--branch <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 <TOKEN> --vps-ip <IP>
# Output: sudo orama install --join https://example.com --token <TOKEN> --vps-ip <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:

View File

@ -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"
}
}

View File

@ -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

View File

@ -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,

View File

@ -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}}"

View File

@ -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

View File

@ -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()),
}
}

View File

@ -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

View File

@ -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")
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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)

View File

@ -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")
}
}