// Package config holds the runtime configuration for a Sorcery-Go process. // // One Config is constructed at startup (from CLI flags + env vars + a small // /etc/sorcery-go/config.yaml if present) and threaded through every pkg/* // call site. This replaces the global Bash variables that the original // Sorcery used. // // Paths default to /var/lib/sorcery-go so a Sorcery-Go binary can be // dropped into an existing Source Mage chroot WITHOUT overwriting the // legacy Bash state under /var/lib/sorcery. Both tools can coexist while // the migration is in progress. package config import ( "fmt" "log" "net/http" "os" "path/filepath" "runtime" "time" ) // Config is the runtime configuration. type Config struct { // RootDir is the engine's state root. Defaults to /var/lib/sorcery-go // so we never clobber the legacy /var/lib/sorcery tree. RootDir string // GrimoirePath is the directory containing the spell tree. We default // to the real Source Mage location so an existing chroot "just works". GrimoirePath string // StateDB is the bbolt file path. StateDB string // TombRoot is the content-addressable blob store root. TombRoot string // BuildRoot is where sandbox overlays are mounted. BuildRoot string // SpoolDir is where downloaded source tarballs land. SpoolDir string // LogDir is where per-spell build logs are persisted. LogDir string // Concurrency is the worker-pool size for parallel casts. Concurrency int // HostArch is the architecture of the running host. HostArch string // ActivePosture is the active legal profile name // (strict_copyleft | corporate_lite | lawless). ActivePosture string // Firewall is which network gatekeeper is active // (opensnitch | portmaster | none). Firewall string // PGPKeyring is the GnuPG keyring used to verify signed DETAILS files. // Empty disables PGP attestation (the Warding will warn but not block). PGPKeyring string // Runtime is the container runtime to use for Sanctum management. // (lxc | podman | firecracker | baremetal | auto) // "auto" probes for available runtimes in order. Runtime string // EBPFEnforce controls whether the eBPF Tomb Guard operates in // enforcing mode (true) or permissive/log-only mode (false). EBPFEnforce bool // FirecrackerBin is the path to the firecracker VMM binary. // Used only when Runtime == "firecracker". FirecrackerBin string // FirecrackerKernel is the path to the kernel image for Firecracker VMs. FirecrackerKernel string // NetworkBridge is the host bridge interface for container networking. // Defaults to "br0". NetworkBridge string // FesterURL is the URL of the Fester cluster controller. // When set, distributed build scheduling and node telemetry are // delegated to Fester. Empty means standalone mode. // Example: "http://192.168.1.100:8787" FesterURL string // Toolchain selects which compiler toolchain to use. // "gcc" (default) uses the host system GCC. // "btc" uses a BTC.sh sovereign-forged toolchain. Toolchain string // BTCPath is the path to the BTC.sh script. BTCPath string // BTCRoot is the BTC persistent archive root directory. BTCRoot string // BTCSYSLabel is the pre-detected SYS_LABEL for the BTC toolchain. // When empty, sorcery-go will auto-detect it from the golden image // filename at startup. BTCSYSLabel string } // Default returns a Config wired for an existing Source Mage chroot. // Every path is overridable via env var (SORCERY_GO_) so the same // binary can run in CI, in a chroot, or in any supported container runtime. func Default() *Config { cfg := &Config{ RootDir: envOr("SORCERY_GO_ROOT", "/var/lib/sorcery-go"), GrimoirePath: envOr("SORCERY_GO_GRIMOIRE", "/var/lib/sorcery/codex/grimoire"), Concurrency: runtime.NumCPU(), HostArch: hostArch(), ActivePosture: envOr("SORCERY_GO_POSTURE", "strict_copyleft"), Firewall: envOr("SORCERY_GO_FIREWALL", "none"), PGPKeyring: envOr("SORCERY_GO_PGP_KEYRING", "/etc/sorcery-go/keyring.gpg"), Runtime: envOr("SORCERY_GO_RUNTIME", "auto"), EBPFEnforce: envBool("SORCERY_GO_EBPF_ENFORCE", false), FirecrackerBin: envOr("SORCERY_GO_FIRECRACKER_BIN", "firecracker"), FirecrackerKernel: envOr("SORCERY_GO_FIRECRACKER_KERNEL", ""), NetworkBridge: envOr("SORCERY_GO_NETWORK_BRIDGE", "br0"), FesterURL: envOr("SORCERY_GO_FESTER_URL", ""), Toolchain: envOr("SORCERY_GO_TOOLCHAIN", "gcc"), BTCPath: envOr("SORCERY_GO_BTC_PATH", "/opt/BTC/BTC.sh"), BTCRoot: envOr("SORCERY_GO_BTC_ROOT", "/opt/BTC"), BTCSYSLabel: envOr("SORCERY_GO_BTC_SYS_LABEL", ""), } cfg.StateDB = filepath.Join(cfg.RootDir, "state", "state.db") cfg.TombRoot = filepath.Join(cfg.RootDir, "tomb") cfg.BuildRoot = filepath.Join(cfg.RootDir, "build") cfg.SpoolDir = envOr("SORCERY_GO_SPOOL", "/var/spool/sorcery-go") cfg.LogDir = filepath.Join(cfg.RootDir, "log") return cfg } // EnsureDirs creates every state directory with the right mode. Idempotent. // Must be called as root (or with write access to RootDir). func (c *Config) EnsureDirs() error { dirs := []struct { path string mode os.FileMode }{ {c.RootDir, 0755}, {filepath.Dir(c.StateDB), 0700}, {c.TombRoot, 0700}, {filepath.Join(c.TombRoot, "epitaphs"), 0700}, {filepath.Join(c.TombRoot, "blobs"), 0700}, {c.BuildRoot, 0755}, {c.SpoolDir, 0755}, {c.LogDir, 0755}, } for _, d := range dirs { if err := os.MkdirAll(d.path, d.mode); err != nil { return fmt.Errorf("config: mkdir %s: %w", d.path, err) } } return nil } // IsRoot reports whether the process is running as UID 0. Required for // OverlayFS mounts and atomic commits to the live root. func IsRoot() bool { return os.Geteuid() == 0 } // ValidateFesterURL checks whether the configured FesterURL is reachable. // It logs a warning if the URL is set but the host does not respond. // This is a non-blocking best-effort check — failure does not prevent startup. func (c *Config) ValidateFesterURL() { if c.FesterURL == "" { return } client := &http.Client{Timeout: 3 * time.Second} resp, err := client.Get(c.FesterURL + "/api/health") if err != nil { log.Printf("config: warning: FesterURL %s is not reachable: %v", c.FesterURL, err) log.Printf("config: distributed build features will be unavailable until Fester is accessible") return } resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("config: warning: FesterURL %s returned status %d", c.FesterURL, resp.StatusCode) } else { log.Printf("config: FesterURL %s is reachable", c.FesterURL) } } // ValidatePaths checks that critical paths exist and logs warnings // for any that are missing. This gives operators clear error messages // instead of cryptic "file not found" errors later. func (c *Config) ValidatePaths() { if _, err := os.Stat(c.GrimoirePath); os.IsNotExist(err) { log.Printf("config: warning: GrimoirePath %s does not exist", c.GrimoirePath) log.Printf("config: no spell definitions will be available. Set SORCERY_GO_GRIMOIRE or install a grimoire.") } if c.Toolchain == "btc" { if _, err := os.Stat(c.BTCRoot); os.IsNotExist(err) { log.Printf("config: warning: BTCRoot %s does not exist", c.BTCRoot) log.Printf("config: BTC toolchain will not be available. Run BTC.sh first or set SORCERY_GO_BTC_ROOT.") } } } func envOr(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } func envBool(key string, def bool) bool { v := os.Getenv(key) if v == "" { return def } return v == "1" || v == "true" || v == "yes" } // archMap maps runtime.GOARCH to Source Mage canonical arch names. var archMap = map[string]string{ "arm64": "aarch64", "amd64": "x86_64", "386": "i686", } func hostArch() string { if arch, ok := archMap[runtime.GOARCH]; ok { return arch } return runtime.GOARCH }