Werkdock skeleton: doctor, load, run over the bwrap engine (Go)

The minimal build-capable CLI decided in RFC 0002's outcome: stdlib-only
Go module, one static binary.

- engine: RunSpec behind the Engine interface (RFC 0001); the Bwrap
  engine ports Werkator's hardened invocation — uid-0 mapping, read-only
  rootfs at /, proc/dev/tmp/root before the user binds so binds below
  them land inside, mountpoint pre-creation in the rootfs including file
  mountpoints, and a guard against binds escaping the rootfs. --clearenv
  gives docker-style clean environments (HOME/PATH set explicitly).
- store: images under $WERKDOCK_HOME (default ~/.werkdock), load
  unpacks via the tar CLI into a tmp dir and renames atomically.
- cli: docker-shaped run flags (-v/-e/-w/--rm); refused docker flags
  (-p, --network, --memory, --cpus, --user, -d) fail loudly with the
  reason; exit codes follow docker (125 CLI errors, child code through).
- doctor: port of werkator-build-prerequisites.sh — userns probe with
  the three signals, tar/zstd, free space and group-quota headroom via
  testable df/quota parsers, same PASS/FAIL output.
- tests: argv golden test, mountpoint and escape tests, flag refusals,
  store round trip, doctor parsers — plus real-sandbox integration
  tests that skip where bwrap or userns are unavailable.

Also records in step 21: RFC 0002 levels 2/3 deferred; next goal is
sandbox builds of Werkator, Werkbaum, and Werkdock itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-09-01 07:01:21 +02:00
co-authored by Claude Fable 5
parent de79210a7b
commit e4bfeacf5a
17 changed files with 1628 additions and 2 deletions
+68
View File
@@ -0,0 +1,68 @@
// Package cli parses werkdock's docker-shaped command line (RFC 0002)
// and dispatches to the internal packages. Exit codes follow docker:
// 125 for werkdock's own errors, otherwise the sandboxed command's code
// is passed through.
package cli
import (
"fmt"
"io"
"os"
)
// Version is replaced at release time; the dev default marks unreleased
// builds.
var Version = "0.1.0-dev"
const exitCLIError = 125
// Main runs the CLI and returns the process exit code.
func Main(args []string) int {
if len(args) == 0 {
usage(os.Stderr)
return exitCLIError
}
switch args[0] {
case "run":
return runCmd(args[1:])
case "load":
return loadCmd(args[1:])
case "doctor":
return doctorCmd(args[1:])
case "version", "--version":
fmt.Printf("werkdock %s\n", Version)
return 0
case "help", "--help", "-h":
usage(os.Stdout)
return 0
default:
fmt.Fprintf(os.Stderr, "werkdock: unknown command %q\n\n", args[0])
usage(os.Stderr)
return exitCLIError
}
}
func usage(w io.Writer) {
fmt.Fprint(w, `werkdock — a docker-like sandbox CLI over bwrap, filesystem isolation only.
Network, uid, /proc, /dev, and /tmp come from the host by contract.
Usage:
werkdock run [flags] IMAGE COMMAND [ARG...] run a command in a sandbox
werkdock load -i ARCHIVE [--name NAME] import a rootfs archive as an image
werkdock doctor [TARGET_DIR] check whether this host can run sandboxes
werkdock version print the version
Run flags:
-v, --volume SRC:DEST[:ro] bind mount (repeatable, applied in order)
-e, --env KEY=VALUE set an environment variable (KEY alone copies it from the host)
-w, --workdir DIR working directory inside the sandbox (default /)
--rm remove the instance afterwards (currently required)
The store lives in $WERKDOCK_HOME (default ~/.werkdock).
`)
}
func fail(err error) int {
fmt.Fprintf(os.Stderr, "werkdock: %v\n", err)
return exitCLIError
}