Files
hakurei/cmd/app/main.go
T
cat 3938e8bce5
Test / Create distribution (push) Successful in 52s
Test / Sandbox (push) Successful in 2m44s
Test / ShareFS (push) Successful in 3m53s
Test / Hakurei (push) Successful in 4m6s
Test / Sandbox (race detector) (push) Successful in 5m27s
Test / Hakurei (race detector) (push) Successful in 6m40s
Test / Flake checks (push) Successful in 1m12s
cmd/app: multiple template uppers
Having multiple environments is useful, and this was trivial to implement.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-18 03:51:49 +09:00

213 lines
4.4 KiB
Go

// 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"
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"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 *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 = base.Append("initial")
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(args []string) error {
if len(args) != 1 {
dents, err := os.ReadDir(template.String())
if err != nil {
return err
}
for _, dent := range dents {
if !dent.IsDir() {
continue
}
fmt.Println(dent.Name())
}
return nil
}
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: template.Append(args[0], "upper"),
Work: template.Append(args[0], "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.NewCommand(
"run", "Start the named application",
func(args []string) error {
if len(args) != 1 {
dents, err := os.ReadDir(base.Append("app").String())
if err != nil {
return err
}
for _, dent := range dents {
if dent.IsDir() {
continue
}
fmt.Println(dent.Name())
}
return nil
}
var config *hst.Config
var r io.Reader
f, err := os.Open(base.Append("app", args[0]).String())
if err != nil {
return err
}
r = f
var common *os.File
if common, err = os.Open(base.Append("common").String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
_ = f.Close()
return err
}
} else {
r = io.MultiReader(f, common)
}
config, err = parse(args[0], base, r)
if closeErr := f.Close(); err == nil {
err = closeErr
}
if common != nil {
if closeErr := common.Close(); err == nil {
err = closeErr
}
}
if err != nil {
return err
}
return run(ctx, msg, config)
},
)
c.MustParse(os.Args[1:], func(err error) {
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
os.Exit(e.ExitCode())
}
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)
}
}
})
}