diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-10-07 01:50:56 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-10-07 04:24:45 +0900 |
| commit | 9e48d7f5626aa966a23754534f3120855d6a7c32 (patch) | |
| tree | ade6eb09abd52b4c64bb5eb9dfb5246ee93454fe /internal/app | |
| parent | f280994957bdc1c6defdd4bd9dcc44dd83a5cfd5 (diff) | |
hst/config: move container fields from toplevel
This change also moves pd behaviour to cmd/hakurei, as this does not belong in the hst API.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app')
| -rw-r--r-- | internal/app/app_test.go | 97 | ||||
| -rw-r--r-- | internal/app/dispatcher.go | 5 | ||||
| -rw-r--r-- | internal/app/dispatcher_test.go | 1 | ||||
| -rw-r--r-- | internal/app/finalise.go | 88 | ||||
| -rw-r--r-- | internal/app/spaccount.go | 37 | ||||
| -rw-r--r-- | internal/app/spcontainer.go | 15 |
6 files changed, 95 insertions, 148 deletions
diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 0c545023..a031d434 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -37,7 +37,35 @@ func TestApp(t *testing.T) { }{ { "nixos permissive defaults no enablements", new(stubNixOS), - &hst.Config{Username: "chronos", Home: m("/home/chronos")}, + &hst.Config{Container: &hst.ContainerConfig{ + Userns: true, HostNet: true, HostAbstract: true, Tty: true, + + Filesystem: []hst.FilesystemConfigJSON{ + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSRoot, + Source: container.AbsFHSRoot, + Write: true, + Special: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("kvm"), + Device: true, + Optional: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSEtc, + Source: container.AbsFHSEtc, + Special: true, + }}, + }, + + Username: "chronos", + Shell: m("/run/current-system/sw/bin/zsh"), + Home: m("/home/chronos"), + + Path: m("/run/current-system/sw/bin/zsh"), + Args: []string{"/run/current-system/sw/bin/zsh"}, + }}, state.ID{ 0x4a, 0x45, 0x0b, 0x65, 0x96, 0xd7, 0xbc, 0x15, @@ -70,7 +98,6 @@ func TestApp(t *testing.T) { DevWritable(m("/dev/"), true). Tmpfs(m("/dev/shm"), 0, 01777). Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional). - Readonly(m("/var/run/nscd"), 0755). Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac"). Tmpfs(m("/run/user/1971"), 8192, 0755). Tmpfs(m("/run/nscd"), 8192, 0755). @@ -93,11 +120,8 @@ func TestApp(t *testing.T) { "nixos permissive defaults chromium", new(stubNixOS), &hst.Config{ ID: "org.chromium.Chromium", - Args: []string{"zsh", "-c", "exec chromium "}, Identity: 9, Groups: []string{"video"}, - Username: "chronos", - Home: m("/home/chronos"), SessionBus: &dbus.Config{ Talk: []string{ "org.freedesktop.Notifications", @@ -130,6 +154,41 @@ func TestApp(t *testing.T) { Filter: true, }, Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse), + + Container: &hst.ContainerConfig{ + Userns: true, HostNet: true, HostAbstract: true, Tty: true, + + Filesystem: []hst.FilesystemConfigJSON{ + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSRoot, + Source: container.AbsFHSRoot, + Write: true, + Special: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("dri"), + Device: true, + Optional: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("kvm"), + Device: true, + Optional: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSEtc, + Source: container.AbsFHSEtc, + Special: true, + }}, + }, + + Username: "chronos", + Shell: m("/run/current-system/sw/bin/zsh"), + Home: m("/home/chronos"), + + Path: m("/run/current-system/sw/bin/zsh"), + Args: []string{"zsh", "-c", "exec chromium "}, + }, }, state.ID{ 0xeb, 0xf0, 0x83, 0xd1, @@ -207,7 +266,6 @@ func TestApp(t *testing.T) { Tmpfs(m("/dev/shm"), 0, 01777). Bind(m("/dev/dri"), m("/dev/dri"), container.BindWritable|container.BindDevice|container.BindOptional). Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional). - Readonly(m("/var/run/nscd"), 0755). Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c"). Tmpfs(m("/run/user/1971"), 8192, 0755). Tmpfs(m("/run/nscd"), 8192, 0755). @@ -236,10 +294,7 @@ func TestApp(t *testing.T) { "nixos chromium direct wayland", new(stubNixOS), &hst.Config{ ID: "org.chromium.Chromium", - Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse), - Shell: m("/run/current-system/sw/bin/zsh"), - Container: &hst.ContainerConfig{ Userns: true, HostNet: true, MapRealUID: true, Env: nil, Filesystem: []hst.FilesystemConfigJSON{ @@ -257,6 +312,12 @@ func TestApp(t *testing.T) { f(&hst.FSBind{Source: m("/etc/"), Target: m("/etc/"), Special: true}), f(&hst.FSBind{Source: m("/var/lib/persist/module/hakurei/0/1"), Write: true, Ensure: true}), }, + + Username: "u0_a1", + Shell: m("/run/current-system/sw/bin/zsh"), + Home: m("/var/lib/persist/module/hakurei/0/1"), + + Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), }, SystemBus: &dbus.Config{ Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"}, @@ -278,8 +339,6 @@ func TestApp(t *testing.T) { }, DirectWayland: true, - Username: "u0_a1", - Home: m("/var/lib/persist/module/hakurei/0/1"), Identity: 1, Groups: []string{}, }, state.ID{ @@ -461,7 +520,6 @@ func (s stubOsFileReadCloser) Write([]byte) (int, error) { panic("attempting to func (s stubOsFileReadCloser) Stat() (fs.FileInfo, error) { panic("attempting to call Stat") } type stubNixOS struct { - lookPathErr map[string]error usernameErr map[string]error } @@ -617,21 +675,6 @@ func (k *stubNixOS) evalSymlinks(path string) (string, error) { } } -func (k *stubNixOS) lookPath(file string) (string, error) { - if k.lookPathErr != nil { - if err, ok := k.lookPathErr[file]; ok { - return "", err - } - } - - switch file { - case "zsh": - return "/run/current-system/sw/bin/zsh", nil - default: - panic(fmt.Sprintf("attempted to look up unexpected executable %q", file)) - } -} - func (k *stubNixOS) lookupGroupId(name string) (string, error) { switch name { case "video": diff --git a/internal/app/dispatcher.go b/internal/app/dispatcher.go index 075b2add..3c5e25c4 100644 --- a/internal/app/dispatcher.go +++ b/internal/app/dispatcher.go @@ -45,9 +45,6 @@ type syscallDispatcher interface { // evalSymlinks provides [filepath.EvalSymlinks]. evalSymlinks(path string) (string, error) - // lookPath provides exec.LookPath. - lookPath(file string) (string, error) - // lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct. lookupGroupId(name string) (string, error) @@ -81,8 +78,6 @@ func (direct) tempdir() string { return os.TempDir() func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) } -func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) } - func (direct) lookupGroupId(name string) (gid string, err error) { var group *user.Group group, err = user.LookupGroup(name) diff --git a/internal/app/dispatcher_test.go b/internal/app/dispatcher_test.go index 3a7b21ba..934ab769 100644 --- a/internal/app/dispatcher_test.go +++ b/internal/app/dispatcher_test.go @@ -18,7 +18,6 @@ func (panicDispatcher) open(string) (osFile, error) { panic("unreachab func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") } func (panicDispatcher) tempdir() string { panic("unreachable") } func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") } -func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") } func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") } func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") } func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") } diff --git a/internal/app/finalise.go b/internal/app/finalise.go index 3d6ec38d..55554eb4 100644 --- a/internal/app/finalise.go +++ b/internal/app/finalise.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/fs" "maps" "os" "os/user" @@ -66,11 +65,8 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, } k.ctx = ctx - if config == nil { - return newWithMessage("invalid configuration") - } - if config.Home == nil { - return newWithMessage("invalid path to home directory") + if err := config.Validate(); err != nil { + return err } // TODO(ophestra): do not clobber during finalise @@ -102,6 +98,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, } } + // validation complete at this point s := outcomeState{ ID: id, Identity: config.Identity, @@ -110,81 +107,6 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, Container: config.Container, } - // permissive defaults - if s.Container == nil { - msg.Verbose("container configuration not supplied, PROCEED WITH CAUTION") - - if config.Shell == nil { - config.Shell = container.AbsFHSRoot.Append("bin", "sh") - shell, _ := k.lookupEnv("SHELL") - if a, err := container.NewAbs(shell); err == nil { - config.Shell = a - } - } - - // hsu clears the environment so resolve paths early - if config.Path == nil { - if len(config.Args) > 0 { - if p, err := k.lookPath(config.Args[0]); err != nil { - return &hst.AppError{Step: "look up executable file", Err: err} - } else if config.Path, err = container.NewAbs(p); err != nil { - return newWithMessageError(err.Error(), err) - } - } else { - config.Path = config.Shell - } - } - - conf := &hst.ContainerConfig{ - Userns: true, - HostNet: true, - HostAbstract: true, - Tty: true, - - Filesystem: []hst.FilesystemConfigJSON{ - // autoroot, includes the home directory - {FilesystemConfig: &hst.FSBind{ - Target: container.AbsFHSRoot, - Source: container.AbsFHSRoot, - Write: true, - Special: true, - }}, - }, - } - - // bind GPU stuff - if config.Enablements.Unwrap()&(hst.EX11|hst.EWayland) != 0 { - conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}}) - } - // opportunistically bind kvm - conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("kvm"), Device: true, Optional: true}}) - - // hide nscd from container if present - nscd := container.AbsFHSVar.Append("run/nscd") - if _, err := k.stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) { - conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}}) - } - - // do autoetc last - conf.Filesystem = append(conf.Filesystem, - hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ - Target: container.AbsFHSEtc, - Source: container.AbsFHSEtc, - Special: true, - }}, - ) - - s.Container = conf - } - - // late nil checks for pd behaviour - if config.Shell == nil { - return newWithMessage("invalid shell path") - } - if config.Path == nil { - return newWithMessage("invalid program path") - } - // enforce bounds and default early if s.Container.WaitDelay <= 0 { kp.waitDelay = hst.WaitDelayDefault @@ -210,14 +132,14 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, { ops := []outcomeOp{ // must run first - &spParamsOp{Path: config.Path, Args: config.Args}, + &spParamsOp{}, // TODO(ophestra): move this late for #8 and #9 spFilesystemOp{}, spRuntimeOp{}, spTmpdirOp{}, - &spAccountOp{Home: config.Home, Username: config.Username, Shell: config.Shell}, + spAccountOp{}, } et := config.Enablements.Unwrap() diff --git a/internal/app/spaccount.go b/internal/app/spaccount.go index dbe3b03c..71c7d7f3 100644 --- a/internal/app/spaccount.go +++ b/internal/app/spaccount.go @@ -9,45 +9,38 @@ import ( ) // spAccountOp sets up user account emulation inside the container. -type spAccountOp struct { - // Inner directory to use as the home directory of the emulated user. - Home *container.Absolute - // String matching the default NAME_REGEX value from adduser to use as the username of the emulated user. - Username string - // Pathname of shell to use for the emulated user. - Shell *container.Absolute -} +type spAccountOp struct{} -func (s *spAccountOp) toSystem(*outcomeStateSys, *hst.Config) error { +func (s spAccountOp) toSystem(state *outcomeStateSys, _ *hst.Config) error { const fallbackUsername = "chronos" // do checks here to fail before fork/exec - if s.Home == nil || s.Shell == nil { + if state.Container == nil || state.Container.Home == nil || state.Container.Shell == nil { // unreachable return syscall.ENOTRECOVERABLE } - if s.Username == "" { - s.Username = fallbackUsername - } else if !isValidUsername(s.Username) { - return newWithMessage(fmt.Sprintf("invalid user name %q", s.Username)) + if state.Container.Username == "" { + state.Container.Username = fallbackUsername + } else if !isValidUsername(state.Container.Username) { + return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username)) } return nil } -func (s *spAccountOp) toContainer(state *outcomeStateParams) error { - state.params.Dir = s.Home - state.env["HOME"] = s.Home.String() - state.env["USER"] = s.Username - state.env["SHELL"] = s.Shell.String() +func (s spAccountOp) toContainer(state *outcomeStateParams) error { + state.params.Dir = state.Container.Home + state.env["HOME"] = state.Container.Home.String() + state.env["USER"] = state.Container.Username + state.env["SHELL"] = state.Container.Shell.String() state.params. Place(container.AbsFHSEtc.Append("passwd"), - []byte(s.Username+":x:"+ + []byte(state.Container.Username+":x:"+ state.mapuid.String()+":"+ state.mapgid.String()+ ":Hakurei:"+ - s.Home.String()+":"+ - s.Shell.String()+"\n")). + state.Container.Home.String()+":"+ + state.Container.Shell.String()+"\n")). Place(container.AbsFHSEtc.Append("group"), []byte("hakurei:x:"+state.mapgid.String()+":\n")) diff --git a/internal/app/spcontainer.go b/internal/app/spcontainer.go index cb5d938f..4381fd72 100644 --- a/internal/app/spcontainer.go +++ b/internal/app/spcontainer.go @@ -18,11 +18,6 @@ const varRunNscd = container.FHSVar + "run/nscd" // spParamsOp initialises unordered fields of [container.Params] and the optional root filesystem. // This outcomeOp is hardcoded to always run first. type spParamsOp struct { - // Copied from the [hst.Config] field of the same name. - Path *container.Absolute `json:"path,omitempty"` - // Copied from the [hst.Config] field of the same name. - Args []string `json:"args"` - // Value of $TERM, stored during toSystem. Term string // Whether $TERM is set, stored during toSystem. @@ -49,15 +44,15 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error { state.params.HostNet = state.Container.HostNet state.params.HostAbstract = state.Container.HostAbstract - if s.Path == nil { + if state.Container.Path == nil { return newWithMessage("invalid program path") } - state.params.Path = s.Path + state.params.Path = state.Container.Path - if len(s.Args) == 0 { - state.params.Args = []string{s.Path.String()} + if len(state.Container.Args) == 0 { + state.params.Args = []string{state.Container.Path.String()} } else { - state.params.Args = s.Args + state.params.Args = state.Container.Args } // the container is canceled when shim is requested to exit or receives an interrupt or termination signal; |
