hst/config: move container fields from toplevel
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m7s
Test / Hpkg (push) Successful in 3m54s
Test / Hakurei (race detector) (push) Successful in 5m18s
Test / Sandbox (race detector) (push) Successful in 2m10s
Test / Hakurei (push) Successful in 2m13s
Test / Flake checks (push) Successful in 1m33s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m7s
Test / Hpkg (push) Successful in 3m54s
Test / Hakurei (race detector) (push) Successful in 5m18s
Test / Sandbox (race detector) (push) Successful in 2m10s
Test / Hakurei (push) Successful in 2m13s
Test / Flake checks (push) Successful in 1m33s
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>
This commit is contained in:
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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") }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user