Files
werkdock/internal/engine/engine.go
6d6060bb07 RFC 0005 milestone 2: instance lifecycle — run -d, ps, stop, logs, rm, exec (#4)
An instance is a directory under instances/<id>/ (config.json, state.json,
stdout, stderr, run/, volumes/) written by package instance, the service
behind the verbs. run -d and start fork this binary as a supervisor
(werkdock supervise ID) that records the pids and the exit code; stop,
kill, wait, logs, ps, rm, inspect read the files. exec is a second bwrap
over the same instance tree. The command is pid 1 of its sandbox
(bwrap --as-pid-1) so stop's SIGTERM reaches it, as in docker; /run and
the anonymous volumes are instance directories, kept across stop/start
and shared with exec. Version 0.5.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #4
2026-09-10 20:55:59 +02:00

77 lines
2.4 KiB
Go

// Package engine executes sandboxed commands. The CLI verbs are thin
// frontends over this package, so a later daemon can expose the same
// logic without duplicating it (RFC 0002).
package engine
import "os"
// MountMode distinguishes the mount kinds a RunSpec can carry.
type MountMode int
const (
// MountBind is a read-write bind mount.
MountBind MountMode = iota
// MountRoBind is a read-only bind mount.
MountRoBind
// MountTmpfs is an empty tmpfs at Dest; Source is unused.
MountTmpfs
)
// Mount is one mount, applied in order; later mounts shadow earlier
// ones at their own path, exactly as bwrap layers them — the order of
// -v and --tmpfs flags is therefore significant and preserved.
type Mount struct {
Mode MountMode
Source string
Dest string
}
// EnvVar is one environment variable; order is preserved.
type EnvVar struct {
Key string
Value string
}
// RunSpec describes one sandboxed command, independent of the engine
// that executes it.
type RunSpec struct {
// RootFS is the absolute path to the unpacked image rootfs,
// bound read-only at /.
RootFS string
Mounts []Mount
Env []EnvVar
Workdir string
Command []string
// UID and GID are what the calling user appears as inside the
// sandbox — the single mapping an unprivileged user namespace has.
// Zero, the default, is root as before (RFC 0005).
UID, GID int
// Home is HOME inside the sandbox; empty means /root, which is a
// tmpfs and therefore writable whoever the uid is.
Home string
}
// Process is a started sandbox: the command runs as pid 1 of its own
// pid namespace, and ChildPID is that process as the host sees it —
// the address a stop signal goes to (RFC 0005).
type Process interface {
// ChildPID is the host pid of the sandboxed command.
ChildPID() int
// Signal delivers sig to the sandboxed command. Like docker's
// pid 1, the command only receives signals it handles, except
// SIGKILL.
Signal(sig os.Signal) error
// Wait blocks until the command has exited and returns its code.
Wait() (int, error)
}
// Engine runs a RunSpec and reports the command's exit code.
// bwrap is the first engine; native namespaces may become a second
// (RFC 0001).
type Engine interface {
Run(spec RunSpec) (int, error)
// Start launches spec and returns without waiting; the instance
// supervisor uses it to record the pid and to stop on request.
Start(spec RunSpec) (Process, error)
}