// BareMetal runtime adapter for Sorcery-Go. // // BareMetal deploys Essences directly to the host filesystem without // any container or VM isolation. This is the simplest mode and is // useful for: // - CI environments where isolation is handled externally // - Single-node development setups // - Systems where container runtimes are unavailable // // Security enforcement still applies: the eBPF Tomb Guard LSM hook // protects the Tomb regardless of whether containers are used. package runtime import ( "context" "fmt" "os" "path/filepath" "strings" "syscall" "time" ) // BareMetalRuntime deploys Essences directly to the host filesystem. type BareMetalRuntime struct { deployRoot string } // NewBareMetalRuntime creates a baremetal runtime adapter. func NewBareMetalRuntime() *BareMetalRuntime { return &BareMetalRuntime{ deployRoot: "/opt/sorcery/sanctums", } } func (r *BareMetalRuntime) Type() Type { return RuntimeBareMetal } func (r *BareMetalRuntime) Name() string { return "Bare Metal (no container)" } // Probe always succeeds — baremetal is always available. func (r *BareMetalRuntime) Probe() error { return nil } // Create creates a directory for the sanctum deployment. func (r *BareMetalRuntime) Create(ctx context.Context, opts CreateOpts) (string, error) { sanctumPath := filepath.Join(r.deployRoot, opts.Name) if err := os.MkdirAll(sanctumPath, 0755); err != nil { return "", err } // TODO: persist sanctum metadata (name, runtime, arch, created) to a // JSON sidecar file under sanctumPath/metadata.json for status reporting. return opts.Name, nil } // Start is a no-op for baremetal (the files are already on disk). func (r *BareMetalRuntime) Start(ctx context.Context, sanctumID string) error { return nil } // Stop is a no-op for baremetal. func (r *BareMetalRuntime) Stop(ctx context.Context, sanctumID string) error { return nil } // Freeze sends SIGSTOP to all processes running from the sanctum path. func (r *BareMetalRuntime) Freeze(ctx context.Context, sanctumID string) error { // For baremetal, we can try to freeze via the cgroup of any process // running from the sanctum directory. This is a best-effort approach. cgPath := r.CgroupPath(sanctumID) if cgPath != "" { freezeFile := cgPath + "/cgroup.freeze" if _, err := os.Stat(freezeFile); err == nil { return os.WriteFile(freezeFile, []byte("1"), 0644) } } return nil } // Thaw resumes a frozen baremetal sanctum. func (r *BareMetalRuntime) Thaw(ctx context.Context, sanctumID string) error { cgPath := r.CgroupPath(sanctumID) if cgPath != "" { freezeFile := cgPath + "/cgroup.freeze" if _, err := os.Stat(freezeFile); err == nil { return os.WriteFile(freezeFile, []byte("0"), 0644) } } return nil } // Destroy removes the sanctum directory. func (r *BareMetalRuntime) Destroy(ctx context.Context, sanctumID string) error { sanctumPath := filepath.Join(r.deployRoot, sanctumID) return os.RemoveAll(sanctumPath) } // Exec runs a command in the sanctum's chroot using chroot(2). func (r *BareMetalRuntime) Exec(ctx context.Context, sanctumID string, command []string, stdin []byte) (*ExecResult, error) { sanctumPath := filepath.Join(r.deployRoot, sanctumID) if len(command) == 0 { return &ExecResult{}, nil } // Use chroot to execute the command in the sanctum environment. // This requires CAP_SYS_CHROOT. cmd := exec.CommandContext(ctx, "chroot", sanctumPath, command[0]) cmd.Args = append([]string{command[0]}, command[1:]...) if len(stdin) > 0 { cmd.Stdin = strings.NewReader(string(stdin)) } var stdout, stderr strings.Builder cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() return &ExecResult{ ExitCode: exitCode(err), Stdout: []byte(stdout.String()), Stderr: []byte(stderr.String()), }, err } // Status returns the status of a baremetal sanctum. func (r *BareMetalRuntime) Status(ctx context.Context, sanctumID string) (*SanctumInfo, error) { sanctumPath := filepath.Join(r.deployRoot, sanctumID) info := &SanctumInfo{ ID: sanctumID, Name: sanctumID, Runtime: RuntimeBareMetal, Status: StatusStopped, RootFS: sanctumPath, } st, err := os.Stat(sanctumPath) if err != nil { if os.IsNotExist(err) { return nil, fmt.Errorf("baremetal: sanctum %s does not exist", sanctumID) } return nil, err } // Check if any processes are running from this directory (heuristic). info.Created = time.Unix(st.Sys().(*syscall.Stat_t).Ctim.Unix(), 0) info.Status = StatusRunning // baremetal is always "running" if it exists info.Cgroup = r.CgroupPath(sanctumID) return info, nil } // List returns all baremetal sanctums. func (r *BareMetalRuntime) List(ctx context.Context) ([]*SanctumInfo, error) { entries, err := os.ReadDir(r.deployRoot) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } var infos []*SanctumInfo for _, e := range entries { if !e.IsDir() { continue } info, err := r.Status(ctx, e.Name()) if err != nil { continue } infos = append(infos, info) } return infos, nil } // CgroupPath returns the cgroup path for baremetal processes. // For baremetal, this typically falls back to the system.slice. func (r *BareMetalRuntime) CgroupPath(sanctumID string) string { // Baremetal doesn't have a dedicated cgroup. // Return empty — the eBPF LSM hook still protects the Tomb at the // kernel level regardless of cgroup attachment. return "" } // --- shared cgroup helpers --- // cgroupV2PathByPID tries to find the cgroup v2 path for a container // by inspecting /proc//cgroup. This is a fallback when the // runtime-specific cgroup path detection fails. func cgroupV2PathByPID(sanctumID string) string { // This is a simplified implementation. A production version would: // 1. Find the init PID of the container (via runtime-specific methods) // 2. Read /proc//cgroup // 3. Parse the cgroup v2 hierarchy path // For now, return empty — the runtime-specific paths should handle // the common cases. return "" }