sorcery-go/cmd/sorcery/main.go

223 lines
7.8 KiB
Go
Executable File

// Package main is the sorcery CLI — the unified entry point for the Coven.
//
// This is the real CLI. Every subcommand wires together the actual pkg/*
// implementations: config.Default() builds the runtime paths, state.Open
// opens the bbolt DB, grimoire.IndexAll walks the spell tree, cast.Pipeline
// runs the forge, warding.Ward guards the result, web.Server serves the UI.
//
// Drop-in usage in an existing Source Mage chroot:
//
// sudo install -m 755 sorcery /usr/local/sbin/sorcery-go
// sudo sorcery-go init
// sudo sorcery-go cast busybox --static --default
// sorcery-go gaze install busybox
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"dcos.net/sorcery-go/pkg/config"
"dcos.net/sorcery-go/pkg/eventbus"
"dcos.net/sorcery-go/pkg/grimoire"
"dcos.net/sorcery-go/pkg/state"
"dcos.net/sorcery-go/pkg/tomb"
)
// Version is set at build time via -ldflags.
var Version = "dev"
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "init":
cmdInit(args)
case "cast":
cmdCast(args)
case "reanimate":
cmdReanimate(args)
case "dispel":
cmdDispel(args)
case "coven":
cmdCoven(args)
case "tomb":
cmdTomb(args)
case "ward":
cmdWard(args)
case "legal":
cmdLegal(args)
case "web":
cmdWeb(args)
case "gaze":
cmdGaze(args)
case "version", "--version", "-v":
fmt.Printf("sorcery %s (Coven Edition)\n", Version)
case "help", "--help", "-h":
usage()
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd)
usage()
os.Exit(2)
}
}
func usage() {
fmt.Print(`sorcery — Sovereign Coven CLI
Usage:
sorcery init Initialise state DB and Tomb
sorcery cast <spell> [--target arch] [--static] [-r] [-d] [-m arches]
sorcery reanimate <essence> --sanctum <id> [--runtime <type>]
sorcery dispel <spell>
sorcery coven spawn|destroy|list|status <name> [--runtime <type>]
sorcery coven join <master-ip>
sorcery coven pulse
sorcery tomb list|verify|purge|inspect
sorcery ward status|banish|reinforce|watch|thaw
sorcery legal audit|sbom|set-posture
sorcery web --port 8080
sorcery gaze install|tablet|depends|essence|whereis|sbom
sorcery version
Flags:
--target <arch> Cross-compile for arch (x86_64, aarch64)
--static Produce a portable static ELF (musl)
-r, --reconfigure Force ICE y/n prompts even if Tablet has answers
-d, --default Accept all defaults (HPC-friendly)
-m, --matrix Build across all maintained arches simultaneously
Environment:
SORCERY_GO_ROOT State root (default /var/lib/sorcery-go)
SORCERY_GO_GRIMOIRE Grimoire path (default /var/lib/sorcery/codex/grimoire)
SORCERY_GO_RUNTIME Container runtime (lxc|podman|firecracker|baremetal|auto)
SORCERY_GO_POSTURE Legal posture (strict_copyleft | corporate_lite | lawless)
SORCERY_GO_PGP_KEYRING GnuPG keyring for DETAILS attestation
SORCERY_GO_EBPF_ENFORCE Enable eBPF enforcing mode (true/false)
SORCERY_GO_NETWORK_BRIDGE Bridge interface for containers (default br0)
Runtimes:
lxc System containers (lxc-tools)
podman OCI containers (rootless capable)
firecracker Lightweight microVMs (VM-level isolation)
baremetal Direct filesystem deployment
auto Auto-detect first available runtime
Coven Edition · "The Ley-Lines are humming. The Tomb is secure (eBPF-enforced)."
`)
}
// --- engine helpers ---
// openEngine wires up the standard set of pkg/* objects every command needs.
// Returns them all so callers can use what they want.
func openEngine() (*config.Config, *state.Manager, *tomb.Tomb, *eventbus.Bus) {
cfg := config.Default()
if err := cfg.EnsureDirs(); err != nil {
fmt.Fprintf(os.Stderr, "init: %v\n", err)
os.Exit(1)
}
stateMgr, err := state.Open(cfg.StateDB)
if err != nil {
fmt.Fprintf(os.Stderr, "state: %v\n", err)
os.Exit(1)
}
t := tomb.New(cfg.TombRoot)
bus := eventbus.New()
return cfg, stateMgr, t, bus
}
// indexGrimoire walks the configured grimoire path and returns the in-memory
// spell index. Used by cast, gaze, and web.
func indexGrimoire(cfg *config.Config) map[string]*grimoire.Spell {
spells, err := grimoire.IndexAll(cfg.GrimoirePath)
if err != nil {
fmt.Fprintf(os.Stderr, "grimoire: %v (continuing with empty index)\n", err)
return map[string]*grimoire.Spell{}
}
return spells
}
// installSignalHandler returns a context that's cancelled on Ctrl+C / SIGTERM.
func installSignalHandler() context.Context {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-ch
fmt.Fprintln(os.Stderr, "\n⚡ Interrupt received — winding down...")
cancel()
}()
return ctx
}
// --- init ---
func cmdInit(args []string) {
fs := flag.NewFlagSet("init", flag.ExitOnError)
force := fs.Bool("force", false, "rebuild indexes even if state.db exists")
_ = fs.Parse(args)
cfg := config.Default()
if err := cfg.EnsureDirs(); err != nil {
fmt.Fprintf(os.Stderr, "init: %v\n", err)
os.Exit(1)
}
stateMgr, err := state.Open(cfg.StateDB)
if err != nil {
fmt.Fprintf(os.Stderr, "state: %v\n", err)
os.Exit(1)
}
defer stateMgr.Close()
fmt.Printf("⚡ Sorcery-Go %s initialised\n", Version)
fmt.Printf(" State DB: %s\n", cfg.StateDB)
fmt.Printf(" Tomb: %s\n", cfg.TombRoot)
fmt.Printf(" Grimoire: %s\n", cfg.GrimoirePath)
fmt.Printf(" Spool: %s\n", cfg.SpoolDir)
fmt.Printf(" Host arch: %s\n", cfg.HostArch)
fmt.Printf(" Posture: %s\n", cfg.ActivePosture)
if *force {
spells := indexGrimoire(cfg)
fmt.Printf(" Indexed %d spells from grimoire.\n", len(spells))
} else {
fmt.Println(" (run `sorcery gaze install <spell>` to query the grimoire)")
}
fmt.Println("\nNext: `sorcery cast busybox --static --default`")
}
// streamTaskToStdout subscribes to the bus under taskID and prints every
// event to stdout. Returns when the task completes or fails.
func streamTaskToStdout(bus *eventbus.Bus, taskID string) {
ch, unsub := bus.Subscribe(taskID)
defer unsub()
defer func() { for range ch {} }()
for ev := range ch {
switch ev.Type {
case eventbus.EventLog:
fmt.Println(ev.Data)
case eventbus.EventPhase:
fmt.Printf("─── %s ───\n", ev.Data)
case eventbus.EventProgress:
fmt.Printf(" [%d/%d]\n", ev.Done, ev.Total)
case eventbus.EventComplete:
fmt.Printf("✓ Essence sealed: %s\n", ev.Data)
return
case eventbus.EventFailed:
fmt.Fprintf(os.Stderr, "✗ FAILED: %s\n", ev.Data)
os.Exit(1)
}
}
}
// pause briefly so the goroutine in cmdCast can subscribe before events fire.
var pause = func() { time.Sleep(10 * time.Millisecond) }