orama/pkg/cli/namespace_commands.go
anonpenguin23 8ee606bfb1 feat: implement SFU and TURN server functionality
- Add signaling package with message types and structures for SFU communication.
- Implement client and server message serialization/deserialization tests.
- Enhance systemd manager to handle SFU and TURN services, including start/stop logic.
- Create TURN server configuration and main server logic with HMAC-SHA1 authentication.
- Add tests for TURN server credential generation and validation.
- Define systemd service files for SFU and TURN services.
2026-02-21 11:17:13 +02:00

458 lines
14 KiB
Go

package cli
import (
"bufio"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"strings"
"github.com/DeBrosOfficial/network/pkg/auth"
"github.com/DeBrosOfficial/network/pkg/constants"
)
// HandleNamespaceCommand handles namespace management commands
func HandleNamespaceCommand(args []string) {
if len(args) == 0 {
showNamespaceHelp()
return
}
subcommand := args[0]
switch subcommand {
case "delete":
var force bool
fs := flag.NewFlagSet("namespace delete", flag.ExitOnError)
fs.BoolVar(&force, "force", false, "Skip confirmation prompt")
_ = fs.Parse(args[1:])
handleNamespaceDelete(force)
case "list":
handleNamespaceList()
case "repair":
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: orama namespace repair <namespace_name>\n")
os.Exit(1)
}
handleNamespaceRepair(args[1])
case "enable":
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: orama namespace enable <feature> --namespace <name>\n")
fmt.Fprintf(os.Stderr, "Features: webrtc\n")
os.Exit(1)
}
handleNamespaceEnable(args[1:])
case "disable":
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: orama namespace disable <feature> --namespace <name>\n")
fmt.Fprintf(os.Stderr, "Features: webrtc\n")
os.Exit(1)
}
handleNamespaceDisable(args[1:])
case "webrtc-status":
var ns string
fs := flag.NewFlagSet("namespace webrtc-status", flag.ExitOnError)
fs.StringVar(&ns, "namespace", "", "Namespace name")
_ = fs.Parse(args[1:])
if ns == "" {
fmt.Fprintf(os.Stderr, "Usage: orama namespace webrtc-status --namespace <name>\n")
os.Exit(1)
}
handleNamespaceWebRTCStatus(ns)
case "help":
showNamespaceHelp()
default:
fmt.Fprintf(os.Stderr, "Unknown namespace command: %s\n", subcommand)
showNamespaceHelp()
os.Exit(1)
}
}
func showNamespaceHelp() {
fmt.Printf("Namespace Management Commands\n\n")
fmt.Printf("Usage: orama namespace <subcommand>\n\n")
fmt.Printf("Subcommands:\n")
fmt.Printf(" list - List namespaces owned by the current wallet\n")
fmt.Printf(" delete - Delete the current namespace and all its resources\n")
fmt.Printf(" repair <namespace> - Repair an under-provisioned namespace cluster\n")
fmt.Printf(" enable webrtc --namespace NS - Enable WebRTC (SFU + TURN) for a namespace\n")
fmt.Printf(" disable webrtc --namespace NS - Disable WebRTC for a namespace\n")
fmt.Printf(" webrtc-status --namespace NS - Show WebRTC service status\n")
fmt.Printf(" help - Show this help message\n\n")
fmt.Printf("Flags:\n")
fmt.Printf(" --force - Skip confirmation prompt (delete only)\n")
fmt.Printf(" --namespace - Namespace name (enable/disable/webrtc-status)\n\n")
fmt.Printf("Examples:\n")
fmt.Printf(" orama namespace list\n")
fmt.Printf(" orama namespace delete\n")
fmt.Printf(" orama namespace delete --force\n")
fmt.Printf(" orama namespace repair anchat\n")
fmt.Printf(" orama namespace enable webrtc --namespace myapp\n")
fmt.Printf(" orama namespace disable webrtc --namespace myapp\n")
fmt.Printf(" orama namespace webrtc-status --namespace myapp\n")
}
func handleNamespaceRepair(namespaceName string) {
fmt.Printf("Repairing namespace cluster '%s'...\n", namespaceName)
// Call the internal repair endpoint on the local gateway
url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/repair?namespace=%s", constants.GatewayAPIPort, namespaceName)
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Repair failed: %s\n", errMsg)
os.Exit(1)
}
fmt.Printf("Namespace '%s' cluster repaired successfully.\n", namespaceName)
if msg, ok := result["message"].(string); ok {
fmt.Printf(" %s\n", msg)
}
}
func handleNamespaceDelete(force bool) {
// Load credentials
store, err := auth.LoadEnhancedCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load credentials: %v\n", err)
os.Exit(1)
}
gatewayURL := getGatewayURL()
creds := store.GetDefaultCredential(gatewayURL)
if creds == nil || !creds.IsValid() {
fmt.Fprintf(os.Stderr, "Not authenticated. Run 'orama auth login' first.\n")
os.Exit(1)
}
namespace := creds.Namespace
if namespace == "" || namespace == "default" {
fmt.Fprintf(os.Stderr, "Cannot delete default namespace.\n")
os.Exit(1)
}
// Confirm deletion
if !force {
fmt.Printf("This will permanently delete namespace '%s' and all its resources:\n", namespace)
fmt.Printf(" - All deployments and their processes\n")
fmt.Printf(" - RQLite cluster (3 nodes)\n")
fmt.Printf(" - Olric cache cluster (3 nodes)\n")
fmt.Printf(" - Gateway instances\n")
fmt.Printf(" - API keys and credentials\n")
fmt.Printf(" - IPFS content and DNS records\n\n")
fmt.Printf("Type the namespace name to confirm: ")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
input := strings.TrimSpace(scanner.Text())
if input != namespace {
fmt.Println("Aborted - namespace name did not match.")
os.Exit(1)
}
}
fmt.Printf("Deleting namespace '%s'...\n", namespace)
// Make DELETE request to gateway
url := fmt.Sprintf("%s/v1/namespace/delete", gatewayURL)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+creds.APIKey)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Failed to delete namespace: %s\n", errMsg)
os.Exit(1)
}
fmt.Printf("Namespace '%s' deleted successfully.\n", namespace)
// Clean up local credentials for the deleted namespace
if store.RemoveCredentialByNamespace(gatewayURL, namespace) {
if err := store.Save(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to clean up local credentials: %v\n", err)
} else {
fmt.Printf("Local credentials for '%s' cleared.\n", namespace)
}
}
fmt.Printf("Run 'orama auth login' to create a new namespace.\n")
}
func handleNamespaceEnable(args []string) {
feature := args[0]
if feature != "webrtc" {
fmt.Fprintf(os.Stderr, "Unknown feature: %s\nSupported features: webrtc\n", feature)
os.Exit(1)
}
var ns string
fs := flag.NewFlagSet("namespace enable webrtc", flag.ExitOnError)
fs.StringVar(&ns, "namespace", "", "Namespace name")
_ = fs.Parse(args[1:])
if ns == "" {
fmt.Fprintf(os.Stderr, "Usage: orama namespace enable webrtc --namespace <name>\n")
os.Exit(1)
}
fmt.Printf("Enabling WebRTC for namespace '%s'...\n", ns)
fmt.Printf("This will provision SFU (3 nodes) and TURN (2 nodes) services.\n")
url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/webrtc/enable?namespace=%s", constants.GatewayAPIPort, ns)
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Failed to enable WebRTC: %s\n", errMsg)
os.Exit(1)
}
fmt.Printf("WebRTC enabled for namespace '%s'.\n", ns)
fmt.Printf(" SFU instances: 3 nodes (signaling via WireGuard)\n")
fmt.Printf(" TURN instances: 2 nodes (relay on public IPs)\n")
}
func handleNamespaceDisable(args []string) {
feature := args[0]
if feature != "webrtc" {
fmt.Fprintf(os.Stderr, "Unknown feature: %s\nSupported features: webrtc\n", feature)
os.Exit(1)
}
var ns string
fs := flag.NewFlagSet("namespace disable webrtc", flag.ExitOnError)
fs.StringVar(&ns, "namespace", "", "Namespace name")
_ = fs.Parse(args[1:])
if ns == "" {
fmt.Fprintf(os.Stderr, "Usage: orama namespace disable webrtc --namespace <name>\n")
os.Exit(1)
}
fmt.Printf("Disabling WebRTC for namespace '%s'...\n", ns)
url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/webrtc/disable?namespace=%s", constants.GatewayAPIPort, ns)
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Failed to disable WebRTC: %s\n", errMsg)
os.Exit(1)
}
fmt.Printf("WebRTC disabled for namespace '%s'.\n", ns)
fmt.Printf(" SFU and TURN services stopped, ports deallocated, DNS records removed.\n")
}
func handleNamespaceWebRTCStatus(ns string) {
url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/webrtc/status?namespace=%s", constants.GatewayAPIPort, ns)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Failed to get WebRTC status: %s\n", errMsg)
os.Exit(1)
}
enabled, _ := result["enabled"].(bool)
if !enabled {
fmt.Printf("WebRTC is not enabled for namespace '%s'.\n", ns)
fmt.Printf(" Enable with: orama namespace enable webrtc --namespace %s\n", ns)
return
}
fmt.Printf("WebRTC Status for namespace '%s'\n\n", ns)
fmt.Printf(" Enabled: yes\n")
if sfuCount, ok := result["sfu_node_count"].(float64); ok {
fmt.Printf(" SFU nodes: %.0f\n", sfuCount)
}
if turnCount, ok := result["turn_node_count"].(float64); ok {
fmt.Printf(" TURN nodes: %.0f\n", turnCount)
}
if ttl, ok := result["turn_credential_ttl"].(float64); ok {
fmt.Printf(" TURN cred TTL: %.0fs\n", ttl)
}
if enabledBy, ok := result["enabled_by"].(string); ok {
fmt.Printf(" Enabled by: %s\n", enabledBy)
}
if enabledAt, ok := result["enabled_at"].(string); ok {
fmt.Printf(" Enabled at: %s\n", enabledAt)
}
}
func handleNamespaceList() {
// Load credentials
store, err := auth.LoadEnhancedCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load credentials: %v\n", err)
os.Exit(1)
}
gatewayURL := getGatewayURL()
creds := store.GetDefaultCredential(gatewayURL)
if creds == nil || !creds.IsValid() {
fmt.Fprintf(os.Stderr, "Not authenticated. Run 'orama auth login' first.\n")
os.Exit(1)
}
// Make GET request to namespace list endpoint
url := fmt.Sprintf("%s/v1/namespace/list", gatewayURL)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+creds.APIKey)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Failed to list namespaces: %s\n", errMsg)
os.Exit(1)
}
namespaces, _ := result["namespaces"].([]interface{})
if len(namespaces) == 0 {
fmt.Println("No namespaces found.")
return
}
activeNS := creds.Namespace
fmt.Printf("Namespaces (%d):\n\n", len(namespaces))
for _, ns := range namespaces {
nsMap, _ := ns.(map[string]interface{})
name, _ := nsMap["name"].(string)
status, _ := nsMap["cluster_status"].(string)
marker := " "
if name == activeNS {
marker = "* "
}
fmt.Printf("%s%-20s cluster: %s\n", marker, name, status)
}
fmt.Printf("\n* = active namespace\n")
}