diff --git a/cmd/app/main.go b/cmd/app/main.go new file mode 100644 index 00000000..a10412e9 --- /dev/null +++ b/cmd/app/main.go @@ -0,0 +1,140 @@ +// The app program is a proof-of-concept frontend for cmd/hakurei. +// +// This program is not covered by the compatibility promise. The command line +// interface and configuration syntax may change at any time. +package main + +import ( + "context" + "log" + "os" + "os/signal" + "path/filepath" + "syscall" + + "hakurei.app/check" + "hakurei.app/command" + "hakurei.app/fhs" + "hakurei.app/hst" + "hakurei.app/message" +) + +func main() { + log.SetFlags(0) + log.SetPrefix("app: ") + msg := message.New(log.Default()) + + ctx, stop := signal.NotifyContext(context.Background(), + syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + defer stop() + + var ( + flagVerbose bool + flagBase string + + base, template, initial, upper, work *check.Absolute + ) + c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) { + msg.SwapVerbose(flagVerbose) + flagBase = os.ExpandEnv(flagBase) + if flagBase == "" { + flagBase = "state" + } + if flagBase, err = filepath.Abs(flagBase); err != nil { + return + } else if base, err = check.NewAbs(flagBase); err != nil { + return + } + + template = base.Append("template") + initial = template.Append("initial") + upper = template.Append("upper") + work = template.Append("work") + return + }).Flag( + &flagVerbose, + "v", command.BoolFlag(false), + "Increase log verbosity", + ).Flag( + &flagBase, + "d", command.StringFlag("$HAKUREI_APP_PATH"), + "Configuration and state directory", + ) + + { + var ( + flagShell string + flagHome string + ) + c.NewCommand( + "enter", "Enter mutable state template", + func([]string) error { + config := hst.Config{ + ID: "app.hakurei.mutable", + Container: &hst.ContainerConfig{ + Hostname: "mutable", + Filesystem: []hst.FilesystemConfigJSON{ + {FilesystemConfig: &hst.FSOverlay{ + Target: fhs.AbsRoot, + Lower: []*check.Absolute{initial}, + Upper: upper, + Work: work, + }}, + {FilesystemConfig: &hst.FSEphemeral{ + Target: fhs.AbsTmp, + Write: true, + Perm: 0755, + }}, + }, + Username: "chronos", + Flags: hst.FMultiarch | + hst.FDevel | + hst.FUserns | + hst.FHostNet | + hst.FTty, + }, + } + + if a, err := check.NewAbs(flagShell); err != nil { + return err + } else { + config.Container.Shell = a + config.Container.Path = a + config.Container.Args = []string{ + "-" + filepath.Base(flagShell), + } + } + + if a, err := check.NewAbs(flagHome); err != nil { + return err + } else { + config.Container.Home = a + } + + return run(ctx, msg, &config) + }, + ).Flag( + &flagShell, + "shell", command.StringFlag("/bin/zsh"), + "Shell program within container", + ).Flag( + &flagHome, + "home", command.StringFlag("/home/chronos"), + "Home directory within container", + ) + } + + c.MustParse(os.Args[1:], func(err error) { + if w, ok := err.(interface{ Unwrap() []error }); !ok { + log.Fatal(err) + } else { + errs := w.Unwrap() + for i, e := range errs { + if i == len(errs)-1 { + log.Fatal(e) + } + log.Println(e) + } + } + }) +} diff --git a/cmd/app/run.go b/cmd/app/run.go new file mode 100644 index 00000000..16f8b885 --- /dev/null +++ b/cmd/app/run.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "encoding/json" + "os" + "os/exec" + "syscall" + + "hakurei.app/hst" + "hakurei.app/message" +) + +// run starts a container via cmd/hakurei and returns after it terminates. +func run(ctx context.Context, msg message.Msg, config *hst.Config) error { + c, cancel := context.WithCancel(ctx) + defer cancel() + + cmd := exec.CommandContext(c, "hakurei") + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + cmd.Cancel = func() error { + return cmd.Process.Signal(syscall.SIGINT) + } + if msg.IsVerbose() { + cmd.Args = append(cmd.Args, "-v") + } + cmd.Args = append(cmd.Args, "run", "3") + + r, w, err := os.Pipe() + if err != nil { + return err + } + cmd.ExtraFiles = append(cmd.ExtraFiles, r) + + if err = cmd.Start(); err != nil { + _, _ = r.Close(), w.Close() + return err + } + + if err = r.Close(); err != nil { + _ = w.Close() + return err + } else if err = json.NewEncoder(w).Encode(&config); err != nil { + _ = w.Close() + return err + } else if err = w.Close(); err != nil { + return err + } + + return cmd.Wait() +}