aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-06-16 23:26:55 +0900
committerOphestra <cat@gensokyo.uk>2026-06-16 23:27:50 +0900
commitf46a0370a723f3920fc3be5edc8ca630d9039dc5 (patch)
treea9bf694eb563211f1ca76550279baa3d6dbadad5
parent5ee66a86eea424f05ee88baba3c5c81d820555c7 (diff)
cmd/app: initial container template command
This program is an experimental frontend for cmd/hakurei. This serves as a short-term replacement of the nixos module and a testing environment to implement API useful to planterette. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--cmd/app/main.go140
-rw-r--r--cmd/app/run.go51
2 files changed, 191 insertions, 0 deletions
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()
+}