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>
69 lines
1.9 KiB
Go
69 lines
1.9 KiB
Go
// 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
|
|
}
|