aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-07 01:50:56 +0900
committerOphestra <cat@gensokyo.uk>2025-10-07 04:24:45 +0900
commit9e48d7f5626aa966a23754534f3120855d6a7c32 (patch)
treeade6eb09abd52b4c64bb5eb9dfb5246ee93454fe /internal
parentf280994957bdc1c6defdd4bd9dcc44dd83a5cfd5 (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')
-rw-r--r--internal/app/app_test.go97
-rw-r--r--internal/app/dispatcher.go5
-rw-r--r--internal/app/dispatcher_test.go1
-rw-r--r--internal/app/finalise.go88
-rw-r--r--internal/app/spaccount.go37
-rw-r--r--internal/app/spcontainer.go15
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;