223 lines
7.8 KiB
Go
Executable File
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) }
|