mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 10:26:57 +00:00
161 lines
4.0 KiB
Go
161 lines
4.0 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/inspector"
|
|
)
|
|
|
|
// List prints all sandbox clusters.
|
|
func List() error {
|
|
states, err := ListStates()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(states) == 0 {
|
|
fmt.Println("No sandboxes found.")
|
|
fmt.Println("Create one: orama sandbox create")
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("%-20s %-10s %-5s %-25s %s\n", "NAME", "STATUS", "NODES", "CREATED", "DOMAIN")
|
|
for _, s := range states {
|
|
fmt.Printf("%-20s %-10s %-5d %-25s %s\n",
|
|
s.Name, s.Status, len(s.Servers), s.CreatedAt.Format("2006-01-02 15:04"), s.Domain)
|
|
}
|
|
|
|
// Check for orphaned servers on Hetzner
|
|
cfg, err := LoadConfig()
|
|
if err != nil {
|
|
return nil // Config not set up, skip orphan check
|
|
}
|
|
|
|
client := NewHetznerClient(cfg.HetznerAPIToken)
|
|
hetznerServers, err := client.ListServersByLabel("orama-sandbox")
|
|
if err != nil {
|
|
return nil // API error, skip orphan check
|
|
}
|
|
|
|
// Build set of known server IDs
|
|
known := make(map[int64]bool)
|
|
for _, s := range states {
|
|
for _, srv := range s.Servers {
|
|
known[srv.ID] = true
|
|
}
|
|
}
|
|
|
|
var orphans []string
|
|
for _, srv := range hetznerServers {
|
|
if !known[srv.ID] {
|
|
orphans = append(orphans, fmt.Sprintf("%s (ID: %d, IP: %s)", srv.Name, srv.ID, srv.PublicNet.IPv4.IP))
|
|
}
|
|
}
|
|
|
|
if len(orphans) > 0 {
|
|
fmt.Printf("\nWarning: %d orphaned server(s) on Hetzner (no state file):\n", len(orphans))
|
|
for _, o := range orphans {
|
|
fmt.Printf(" %s\n", o)
|
|
}
|
|
fmt.Println("Delete manually at https://console.hetzner.cloud")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Status prints the health report for a sandbox cluster.
|
|
func Status(name string) error {
|
|
cfg, err := LoadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
state, err := resolveSandbox(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sshKeyPath := cfg.ExpandedPrivateKeyPath()
|
|
fmt.Printf("Sandbox: %s (status: %s)\n\n", state.Name, state.Status)
|
|
|
|
for _, srv := range state.Servers {
|
|
node := inspector.Node{User: "root", Host: srv.IP, SSHKey: sshKeyPath}
|
|
|
|
fmt.Printf("%s (%s) — %s\n", srv.Name, srv.IP, srv.Role)
|
|
|
|
// Get node report
|
|
out, err := runSSHOutput(node, "orama node report --json 2>/dev/null")
|
|
if err != nil {
|
|
fmt.Printf(" Status: UNREACHABLE (%v)\n", err)
|
|
fmt.Println()
|
|
continue
|
|
}
|
|
|
|
printNodeReport(out)
|
|
fmt.Println()
|
|
}
|
|
|
|
// Cluster summary
|
|
fmt.Println("Cluster Summary")
|
|
fmt.Println("---------------")
|
|
genesis := state.GenesisServer()
|
|
genesisNode := inspector.Node{User: "root", Host: genesis.IP, SSHKey: sshKeyPath}
|
|
|
|
out, err := runSSHOutput(genesisNode, "curl -sf http://localhost:5001/status 2>/dev/null")
|
|
if err != nil {
|
|
fmt.Println(" RQLite: UNREACHABLE")
|
|
} else {
|
|
var status map[string]interface{}
|
|
if err := json.Unmarshal([]byte(out), &status); err == nil {
|
|
if store, ok := status["store"].(map[string]interface{}); ok {
|
|
if raft, ok := store["raft"].(map[string]interface{}); ok {
|
|
fmt.Printf(" RQLite state: %v\n", raft["state"])
|
|
fmt.Printf(" Commit index: %v\n", raft["commit_index"])
|
|
if nodes, ok := raft["nodes"].([]interface{}); ok {
|
|
fmt.Printf(" Nodes: %d\n", len(nodes))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// printNodeReport parses and prints a node report JSON.
|
|
func printNodeReport(jsonStr string) {
|
|
var report map[string]interface{}
|
|
if err := json.Unmarshal([]byte(jsonStr), &report); err != nil {
|
|
fmt.Printf(" Report: (parse error)\n")
|
|
return
|
|
}
|
|
|
|
// Print key fields
|
|
if services, ok := report["services"].(map[string]interface{}); ok {
|
|
var active, inactive []string
|
|
for name, info := range services {
|
|
if svc, ok := info.(map[string]interface{}); ok {
|
|
if state, ok := svc["active"].(bool); ok && state {
|
|
active = append(active, name)
|
|
} else {
|
|
inactive = append(inactive, name)
|
|
}
|
|
}
|
|
}
|
|
if len(active) > 0 {
|
|
fmt.Printf(" Active: %s\n", strings.Join(active, ", "))
|
|
}
|
|
if len(inactive) > 0 {
|
|
fmt.Printf(" Inactive: %s\n", strings.Join(inactive, ", "))
|
|
}
|
|
}
|
|
|
|
if rqlite, ok := report["rqlite"].(map[string]interface{}); ok {
|
|
if state, ok := rqlite["state"].(string); ok {
|
|
fmt.Printf(" RQLite: %s\n", state)
|
|
}
|
|
}
|
|
}
|