aboutsummaryrefslogtreecommitdiffhomepage
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
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>
-rw-r--r--cmd/hakurei/command.go132
-rw-r--r--cmd/hakurei/print.go31
-rw-r--r--cmd/hakurei/print_test.go113
-rw-r--r--cmd/hpkg/app.go14
-rw-r--r--cmd/hpkg/test/test.py6
-rw-r--r--cmd/hpkg/with.go50
-rw-r--r--hst/config.go55
-rw-r--r--hst/config_test.go37
-rw-r--r--hst/hst.go25
-rw-r--r--hst/hst_test.go22
-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
-rw-r--r--nixos.nix32
-rw-r--r--test/sandbox/case/pd.nix1
-rw-r--r--test/test.py8
19 files changed, 434 insertions, 335 deletions
diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go
index d284e075..45755521 100644
--- a/cmd/hakurei/command.go
+++ b/cmd/hakurei/command.go
@@ -2,10 +2,12 @@ package main
import (
"context"
+ "errors"
"fmt"
"io"
"log"
"os"
+ "os/exec"
"os/user"
"strconv"
"sync"
@@ -52,7 +54,9 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE
// config extraArgs...
config := tryPath(msg, args[0])
- config.Args = append(config.Args, args[1:]...)
+ if config != nil && config.Container != nil {
+ config.Container.Args = append(config.Container.Args, args[1:]...)
+ }
app.Main(ctx, msg, config)
panic("unreachable")
@@ -75,12 +79,6 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE
)
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error {
- // initialise config from flags
- config := &hst.Config{
- ID: flagID,
- Args: args,
- }
-
if flagIdentity < hst.IdentityMin || flagIdentity > hst.IdentityMax {
log.Fatalf("identity %d out of range", flagIdentity)
}
@@ -106,41 +104,109 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE
}
)
- if flagHomeDir == "os" {
- passwdOnce.Do(passwdFunc)
- flagHomeDir = passwd.HomeDir
- }
-
- if flagUserName == "chronos" {
- passwdOnce.Do(passwdFunc)
- flagUserName = passwd.Username
+ // paths are identical, resolve inner shell and program path
+ shell := container.AbsFHSRoot.Append("bin", "sh")
+ if a, err := container.NewAbs(os.Getenv("SHELL")); err == nil {
+ shell = a
}
-
- config.Identity = flagIdentity
- config.Groups = flagGroups
- config.Username = flagUserName
-
- if a, err := container.NewAbs(flagHomeDir); err != nil {
- log.Fatal(err.Error())
- return err
- } else {
- config.Home = a
+ progPath := shell
+ if len(args) > 0 {
+ if p, err := exec.LookPath(args[0]); err != nil {
+ log.Fatal(errors.Unwrap(err))
+ return err
+ } else if progPath, err = container.NewAbs(p); err != nil {
+ log.Fatal(err.Error())
+ return err
+ }
}
- var e hst.Enablement
+ var et hst.Enablement
if flagWayland {
- e |= hst.EWayland
+ et |= hst.EWayland
}
if flagX11 {
- e |= hst.EX11
+ et |= hst.EX11
}
if flagDBus {
- e |= hst.EDBus
+ et |= hst.EDBus
}
if flagPulse {
- e |= hst.EPulse
+ et |= hst.EPulse
+ }
+
+ config := &hst.Config{
+ ID: flagID,
+ Identity: flagIdentity,
+ Groups: flagGroups,
+ Enablements: hst.NewEnablements(et),
+
+ Container: &hst.ContainerConfig{
+ Userns: true,
+ HostNet: true,
+ Tty: true,
+ HostAbstract: true,
+
+ Filesystem: []hst.FilesystemConfigJSON{
+ // autoroot, includes the home directory
+ {FilesystemConfig: &hst.FSBind{
+ Target: container.AbsFHSRoot,
+ Source: container.AbsFHSRoot,
+ Write: true,
+ Special: true,
+ }},
+ },
+
+ Username: flagUserName,
+ Shell: shell,
+
+ Path: progPath,
+ Args: args,
+ },
+ }
+
+ // bind GPU stuff
+ if et&(hst.EX11|hst.EWayland) != 0 {
+ config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
+ Source: container.AbsFHSDev.Append("dri"),
+ Device: true,
+ Optional: true,
+ }})
+ }
+
+ config.Container.Filesystem = append(config.Container.Filesystem,
+ // opportunistically bind kvm
+ hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
+ Source: container.AbsFHSDev.Append("kvm"),
+ Device: true,
+ Optional: true,
+ }},
+
+ // do autoetc last
+ hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
+ Target: container.AbsFHSEtc,
+ Source: container.AbsFHSEtc,
+ Special: true,
+ }},
+ )
+
+ if config.Container.Username == "chronos" {
+ passwdOnce.Do(passwdFunc)
+ config.Container.Username = passwd.Username
+ }
+
+ {
+ homeDir := flagHomeDir
+ if homeDir == "os" {
+ passwdOnce.Do(passwdFunc)
+ homeDir = passwd.HomeDir
+ }
+ if a, err := container.NewAbs(homeDir); err != nil {
+ log.Fatal(err.Error())
+ return err
+ } else {
+ config.Container.Home = a
+ }
}
- config.Enablements = hst.NewEnablements(e)
// parse D-Bus config file from flags if applicable
if flagDBus {
@@ -218,7 +284,9 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE
if config == nil {
config = tryPath(msg, name)
}
- printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON)
+ if !printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON) {
+ os.Exit(1)
+ }
default:
log.Fatal("show requires 1 argument")
diff --git a/cmd/hakurei/print.go b/cmd/hakurei/print.go
index eefe1345..c9084a58 100644
--- a/cmd/hakurei/print.go
+++ b/cmd/hakurei/print.go
@@ -11,6 +11,7 @@ import (
"text/tabwriter"
"time"
+ "hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal/app"
"hakurei.app/internal/app/state"
@@ -39,7 +40,9 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
func printShowInstance(
output io.Writer, now time.Time,
instance *state.State, config *hst.Config,
- short, flagJSON bool) {
+ short, flagJSON bool) (valid bool) {
+ valid = true
+
if flagJSON {
if instance != nil {
printJSON(output, short, instance)
@@ -52,8 +55,11 @@ func printShowInstance(
t := newPrinter(output)
defer t.MustFlush()
- if config.Container == nil {
- mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n")
+ if err := config.Validate(); err != nil {
+ valid = false
+ if m, ok := container.GetErrorMessage(err); ok {
+ mustPrint(output, "Error: "+m+"!\n\n")
+ }
}
if instance != nil {
@@ -73,11 +79,11 @@ func printShowInstance(
if len(config.Groups) > 0 {
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
}
- if config.Home != nil {
- t.Printf(" Home:\t%s\n", config.Home)
- }
if config.Container != nil {
params := config.Container
+ if params.Home != nil {
+ t.Printf(" Home:\t%s\n", params.Home)
+ }
if params.Hostname != "" {
t.Printf(" Hostname:\t%s\n", params.Hostname)
}
@@ -100,12 +106,12 @@ func printShowInstance(
}
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
- if config.Path != nil {
- t.Printf(" Path:\t%s\n", config.Path)
+ if params.Path != nil {
+ t.Printf(" Path:\t%s\n", params.Path)
+ }
+ if len(params.Args) > 0 {
+ t.Printf(" Arguments:\t%s\n", strings.Join(params.Args, " "))
}
- }
- if len(config.Args) > 0 {
- t.Printf(" Arguments:\t%s\n", strings.Join(config.Args, " "))
}
t.Printf("\n")
@@ -114,6 +120,7 @@ func printShowInstance(
t.Printf("Filesystem\n")
for _, f := range config.Container.Filesystem {
if !f.Valid() {
+ valid = false
t.Println(" <invalid>")
continue
}
@@ -161,6 +168,8 @@ func printShowInstance(
printDBus(config.SystemBus)
t.Printf("\n")
}
+
+ return
}
func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) {
diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go
index 49579a82..a666d251 100644
--- a/cmd/hakurei/print_test.go
+++ b/cmd/hakurei/print_test.go
@@ -27,13 +27,14 @@ var (
testAppTime = time.Unix(0, 9).UTC()
)
-func Test_printShowInstance(t *testing.T) {
+func TestPrintShowInstance(t *testing.T) {
testCases := []struct {
name string
instance *state.State
config *hst.Config
short, json bool
want string
+ valid bool
}{
{"config", nil, hst.Template(), false, false, `App
Identity: 9 (org.chromium.Chromium)
@@ -71,21 +72,25 @@ System bus
Filter: true
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
-`},
- {"config pd", nil, new(hst.Config), false, false, `Warning: this configuration uses permissive defaults!
+`, true},
+ {"config pd", nil, new(hst.Config), false, false, `Error: configuration missing container state!
App
Identity: 0
Enablements: (no enablements)
-`},
- {"config flag none", nil, &hst.Config{Container: new(hst.ContainerConfig)}, false, false, `App
+`, false},
+ {"config flag none", nil, &hst.Config{Container: new(hst.ContainerConfig)}, false, false, `Error: container configuration missing path to home directory!
+
+App
Identity: 0
Enablements: (no enablements)
Flags: none
-`},
- {"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `App
+`, false},
+ {"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `Error: container configuration missing path to home directory!
+
+App
Identity: 0
Enablements: (no enablements)
Flags: none
@@ -95,8 +100,8 @@ Filesystem
Extra ACL
-`},
- {"config pd dbus see", nil, &hst.Config{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}, false, false, `Warning: this configuration uses permissive defaults!
+`, false},
+ {"config pd dbus see", nil, &hst.Config{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}, false, false, `Error: configuration missing container state!
App
Identity: 0
@@ -106,7 +111,7 @@ Session bus
Filter: false
See: ["org.example.test"]
-`},
+`, false},
{"instance", testState, hst.Template(), false, false, `State
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
@@ -148,8 +153,8 @@ System bus
Filter: true
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
-`},
- {"instance pd", testState, new(hst.Config), false, false, `Warning: this configuration uses permissive defaults!
+`, true},
+ {"instance pd", testState, new(hst.Config), false, false, `Error: configuration missing container state!
State
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
@@ -159,10 +164,10 @@ App
Identity: 0
Enablements: (no enablements)
-`},
+`, false},
{"json nil", nil, nil, false, true, `null
-`},
+`, true},
{"json instance", testState, nil, false, true, `{
"instance": [
142,
@@ -185,14 +190,6 @@ App
"pid": 3735928559,
"config": {
"id": "org.chromium.Chromium",
- "path": "/run/current-system/sw/bin/chromium",
- "args": [
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland"
- ],
"enablements": {
"wayland": true,
"dbus": true,
@@ -234,9 +231,6 @@ App
"broadcast": null,
"filter": true
},
- "username": "chronos",
- "shell": "/run/current-system/sw/bin/zsh",
- "home": "/data/data/org.chromium.Chromium",
"extra_perms": [
{
"ensure": true,
@@ -331,22 +325,25 @@ App
"dev": true,
"optional": true
}
+ ],
+ "username": "chronos",
+ "shell": "/run/current-system/sw/bin/zsh",
+ "home": "/data/data/org.chromium.Chromium",
+ "path": "/run/current-system/sw/bin/chromium",
+ "args": [
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland"
]
}
},
"time": "1970-01-01T00:00:00.000000009Z"
}
-`},
+`, true},
{"json config", nil, hst.Template(), false, true, `{
"id": "org.chromium.Chromium",
- "path": "/run/current-system/sw/bin/chromium",
- "args": [
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland"
- ],
"enablements": {
"wayland": true,
"dbus": true,
@@ -388,9 +385,6 @@ App
"broadcast": null,
"filter": true
},
- "username": "chronos",
- "shell": "/run/current-system/sw/bin/zsh",
- "home": "/data/data/org.chromium.Chromium",
"extra_perms": [
{
"ensure": true,
@@ -485,26 +479,39 @@ App
"dev": true,
"optional": true
}
+ ],
+ "username": "chronos",
+ "shell": "/run/current-system/sw/bin/zsh",
+ "home": "/data/data/org.chromium.Chromium",
+ "path": "/run/current-system/sw/bin/chromium",
+ "args": [
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland"
]
}
}
-`},
+`, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
output := new(strings.Builder)
- printShowInstance(output, testTime, tc.instance, tc.config, tc.short, tc.json)
+ gotValid := printShowInstance(output, testTime, tc.instance, tc.config, tc.short, tc.json)
if got := output.String(); got != tc.want {
- t.Errorf("printShowInstance: got\n%s\nwant\n%s",
- got, tc.want)
+ t.Errorf("printShowInstance: \n%s\nwant\n%s", got, tc.want)
return
}
+ if gotValid != tc.valid {
+ t.Errorf("printShowInstance: valid = %v, want %v", gotValid, tc.valid)
+ }
})
}
}
-func Test_printPs(t *testing.T) {
+func TestPrintPs(t *testing.T) {
testCases := []struct {
name string
entries state.Entries
@@ -547,14 +554,6 @@ func Test_printPs(t *testing.T) {
"pid": 3735928559,
"config": {
"id": "org.chromium.Chromium",
- "path": "/run/current-system/sw/bin/chromium",
- "args": [
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland"
- ],
"enablements": {
"wayland": true,
"dbus": true,
@@ -596,9 +595,6 @@ func Test_printPs(t *testing.T) {
"broadcast": null,
"filter": true
},
- "username": "chronos",
- "shell": "/run/current-system/sw/bin/zsh",
- "home": "/data/data/org.chromium.Chromium",
"extra_perms": [
{
"ensure": true,
@@ -693,6 +689,17 @@ func Test_printPs(t *testing.T) {
"dev": true,
"optional": true
}
+ ],
+ "username": "chronos",
+ "shell": "/run/current-system/sw/bin/zsh",
+ "home": "/data/data/org.chromium.Chromium",
+ "path": "/run/current-system/sw/bin/chromium",
+ "args": [
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland"
]
}
},
diff --git a/cmd/hpkg/app.go b/cmd/hpkg/app.go
index 1a1437ff..a4910aae 100644
--- a/cmd/hpkg/app.go
+++ b/cmd/hpkg/app.go
@@ -66,19 +66,12 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg
config := &hst.Config{
ID: app.ID,
- Path: pathname,
- Args: argv,
-
Enablements: app.Enablements,
SystemBus: app.SystemBus,
SessionBus: app.SessionBus,
DirectWayland: app.DirectWayland,
- Username: "hakurei",
- Shell: pathShell,
- Home: pathDataData.Append(app.ID),
-
Identity: app.Identity,
Groups: app.Groups,
@@ -107,6 +100,13 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg
{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("devices"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
},
+
+ Username: "hakurei",
+ Shell: pathShell,
+ Home: pathDataData.Append(app.ID),
+
+ Path: pathname,
+ Args: argv,
},
ExtraPerms: []*hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
diff --git a/cmd/hpkg/test/test.py b/cmd/hpkg/test/test.py
index d491def3..e2a002d6 100644
--- a/cmd/hpkg/test/test.py
+++ b/cmd/hpkg/test/test.py
@@ -62,11 +62,11 @@ def check_state(name, enablements):
config = instance['config']
- if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['args'][0]):
- raise Exception(f"unexpected args {instance['config']['args']}")
+ if len(config['container']['args']) != 1 or not (config['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['container']['args'][0]):
+ raise Exception(f"unexpected args {config['container']['args']}")
if config['enablements'] != enablements:
- raise Exception(f"unexpected enablements {instance['config']['enablements']}")
+ raise Exception(f"unexpected enablements {config['enablements']}")
start_all()
diff --git a/cmd/hpkg/with.go b/cmd/hpkg/with.go
index 59545c62..b3ccd47a 100644
--- a/cmd/hpkg/with.go
+++ b/cmd/hpkg/with.go
@@ -18,22 +18,6 @@ func withNixDaemon(
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
ID: app.ID,
- Path: pathShell,
- Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
- // start nix-daemon
- "nix-daemon --store / & " +
- // wait for socket to appear
- "(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
- // create directory so nix stops complaining
- "mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
- strings.Join(command, " && ") +
- // terminate nix-daemon
- " && pkill nix-daemon",
- },
-
- Username: "hakurei",
- Shell: pathShell,
- Home: pathDataData.Append(app.ID),
ExtraPerms: []*hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
@@ -55,6 +39,23 @@ func withNixDaemon(
{FilesystemConfig: &hst.FSLink{Target: container.AbsFHSUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
},
+
+ Username: "hakurei",
+ Shell: pathShell,
+ Home: pathDataData.Append(app.ID),
+
+ Path: pathShell,
+ Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
+ // start nix-daemon
+ "nix-daemon --store / & " +
+ // wait for socket to appear
+ "(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
+ // create directory so nix stops complaining
+ "mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
+ strings.Join(command, " && ") +
+ // terminate nix-daemon
+ " && pkill nix-daemon",
+ },
},
}), dropShell, beforeFail)
}
@@ -67,12 +68,6 @@ func withCacheDir(
mustRunAppDropShell(ctx, msg, &hst.Config{
ID: app.ID,
- Path: pathShell,
- Args: []string{bash, "-lc", strings.Join(command, " && ")},
-
- Username: "nixos",
- Shell: pathShell,
- Home: pathDataData.Append(app.ID, "cache"),
ExtraPerms: []*hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
@@ -94,13 +89,22 @@ func withCacheDir(
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsTmp.Append("bundle")}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}},
},
+
+ Username: "nixos",
+ Shell: pathShell,
+ Home: pathDataData.Append(app.ID, "cache"),
+
+ Path: pathShell,
+ Args: []string{bash, "-lc", strings.Join(command, " && ")},
},
}, dropShell, beforeFail)
}
func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
if dropShell {
- config.Args = []string{bash, "-l"}
+ if config.Container != nil {
+ config.Container.Args = []string{bash, "-l"}
+ }
mustRunApp(ctx, msg, config, beforeFail)
beforeFail()
msg.BeforeExit()
diff --git a/hst/config.go b/hst/config.go
index 7f6b079c..8581bbcf 100644
--- a/hst/config.go
+++ b/hst/config.go
@@ -1,6 +1,7 @@
package hst
import (
+ "errors"
"time"
"hakurei.app/container"
@@ -35,11 +36,6 @@ type (
// Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy.
ID string `json:"id"`
- // Pathname to executable file in the container filesystem.
- Path *container.Absolute `json:"path,omitempty"`
- // Final args passed to the initial program.
- Args []string `json:"args"`
-
// System services to make available in the container.
Enablements *Enablements `json:"enablements,omitempty"`
@@ -53,14 +49,6 @@ type (
// and the bare socket is made available to the container.
DirectWayland bool `json:"direct_wayland,omitempty"`
- // String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
- // Defaults to passwd name of target uid or chronos.
- Username string `json:"username,omitempty"`
- // Pathname of shell in the container filesystem to use for the emulated user.
- Shell *container.Absolute `json:"shell"`
- // Directory in the container filesystem to enter and use as the home directory of the emulated user.
- Home *container.Absolute `json:"home"`
-
// Extra acl update ops to perform before setuid.
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
@@ -114,9 +102,50 @@ type (
If the first element targets /, it is inserted early and excluded from path hiding. */
Filesystem []FilesystemConfigJSON `json:"filesystem"`
+
+ // String used as the username of the emulated user, validated against the default NAME_REGEX from adduser.
+ // Defaults to passwd name of target uid or chronos.
+ Username string `json:"username,omitempty"`
+ // Pathname of shell in the container filesystem to use for the emulated user.
+ Shell *container.Absolute `json:"shell"`
+ // Directory in the container filesystem to enter and use as the home directory of the emulated user.
+ Home *container.Absolute `json:"home"`
+
+ // Pathname to executable file in the container filesystem.
+ Path *container.Absolute `json:"path,omitempty"`
+ // Final args passed to the initial program.
+ Args []string `json:"args"`
}
)
+// ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any
+// field that must not be null.
+var ErrConfigNull = errors.New("unexpected null in config")
+
+func (config *Config) Validate() error {
+ if config == nil {
+ return &AppError{Step: "validate configuration", Err: ErrConfigNull,
+ Msg: "invalid configuration"}
+ }
+ if config.Container == nil {
+ return &AppError{Step: "validate configuration", Err: ErrConfigNull,
+ Msg: "configuration missing container state"}
+ }
+ if config.Container.Home == nil {
+ return &AppError{Step: "validate configuration", Err: ErrConfigNull,
+ Msg: "container configuration missing path to home directory"}
+ }
+ if config.Container.Shell == nil {
+ return &AppError{Step: "validate configuration", Err: ErrConfigNull,
+ Msg: "container configuration missing path to shell"}
+ }
+ if config.Container.Path == nil {
+ return &AppError{Step: "validate configuration", Err: ErrConfigNull,
+ Msg: "container configuration missing path to initial program"}
+ }
+ return nil
+}
+
// ExtraPermConfig describes an acl update op.
type ExtraPermConfig struct {
Ensure bool `json:"ensure,omitempty"`
diff --git a/hst/config_test.go b/hst/config_test.go
index c8da8a25..98321c73 100644
--- a/hst/config_test.go
+++ b/hst/config_test.go
@@ -1,12 +1,49 @@
package hst_test
import (
+ "reflect"
"testing"
"hakurei.app/container"
"hakurei.app/hst"
)
+func TestConfigValidate(t *testing.T) {
+ testCases := []struct {
+ name string
+ config *hst.Config
+ wantErr error
+ }{
+ {"nil", nil, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
+ Msg: "invalid configuration"}},
+ {"container", &hst.Config{}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
+ Msg: "configuration missing container state"}},
+ {"home", &hst.Config{Container: &hst.ContainerConfig{}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
+ Msg: "container configuration missing path to home directory"}},
+ {"shell", &hst.Config{Container: &hst.ContainerConfig{
+ Home: container.AbsFHSTmp,
+ }}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
+ Msg: "container configuration missing path to shell"}},
+ {"path", &hst.Config{Container: &hst.ContainerConfig{
+ Home: container.AbsFHSTmp,
+ Shell: container.AbsFHSTmp,
+ }}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
+ Msg: "container configuration missing path to initial program"}},
+ {"valid", &hst.Config{Container: &hst.ContainerConfig{
+ Home: container.AbsFHSTmp,
+ Shell: container.AbsFHSTmp,
+ Path: container.AbsFHSTmp,
+ }}, nil},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) {
+ t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
+ }
+ })
+ }
+}
+
func TestExtraPermConfig(t *testing.T) {
testCases := []struct {
name string
diff --git a/hst/hst.go b/hst/hst.go
index 8a72b509..14c18458 100644
--- a/hst/hst.go
+++ b/hst/hst.go
@@ -60,15 +60,6 @@ func Template() *Config {
return &Config{
ID: "org.chromium.Chromium",
- Path: container.AbsFHSRun.Append("current-system/sw/bin/chromium"),
- Args: []string{
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland",
- },
-
Enablements: NewEnablements(EWayland | EDBus | EPulse),
SessionBus: &dbus.Config{
@@ -93,9 +84,6 @@ func Template() *Config {
},
DirectWayland: false,
- Username: "chronos",
- Shell: container.AbsFHSRun.Append("current-system/sw/bin/zsh"),
- Home: container.MustAbs("/data/data/org.chromium.Chromium"),
ExtraPerms: []*ExtraPermConfig{
{Path: container.AbsFHSVarLib.Append("hakurei/u0"), Ensure: true, Execute: true},
{Path: container.AbsFHSVarLib.Append("hakurei/u0/org.chromium.Chromium"), Read: true, Write: true, Execute: true},
@@ -140,6 +128,19 @@ func Template() *Config {
Target: container.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}},
{&FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}},
},
+
+ Username: "chronos",
+ Shell: container.AbsFHSRun.Append("current-system/sw/bin/zsh"),
+ Home: container.MustAbs("/data/data/org.chromium.Chromium"),
+
+ Path: container.AbsFHSRun.Append("current-system/sw/bin/chromium"),
+ Args: []string{
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland",
+ },
},
}
}
diff --git a/hst/hst_test.go b/hst/hst_test.go
index 0c25fa7c..90380e07 100644
--- a/hst/hst_test.go
+++ b/hst/hst_test.go
@@ -92,14 +92,6 @@ func TestAppError(t *testing.T) {
func TestTemplate(t *testing.T) {
const want = `{
"id": "org.chromium.Chromium",
- "path": "/run/current-system/sw/bin/chromium",
- "args": [
- "chromium",
- "--ignore-gpu-blocklist",
- "--disable-smooth-scrolling",
- "--enable-features=UseOzonePlatform",
- "--ozone-platform=wayland"
- ],
"enablements": {
"wayland": true,
"dbus": true,
@@ -141,9 +133,6 @@ func TestTemplate(t *testing.T) {
"broadcast": null,
"filter": true
},
- "username": "chronos",
- "shell": "/run/current-system/sw/bin/zsh",
- "home": "/data/data/org.chromium.Chromium",
"extra_perms": [
{
"ensure": true,
@@ -238,6 +227,17 @@ func TestTemplate(t *testing.T) {
"dev": true,
"optional": true
}
+ ],
+ "username": "chronos",
+ "shell": "/run/current-system/sw/bin/zsh",
+ "home": "/data/data/org.chromium.Chromium",
+ "path": "/run/current-system/sw/bin/chromium",
+ "args": [
+ "chromium",
+ "--ignore-gpu-blocklist",
+ "--disable-smooth-scrolling",
+ "--enable-features=UseOzonePlatform",
+ "--ozone-platform=wayland"
]
}
}`
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;
diff --git a/nixos.nix b/nixos.nix
index e2382bcb..6dd51807 100644
--- a/nixos.nix
+++ b/nixos.nix
@@ -105,27 +105,11 @@ in
isGraphical = if app.gpu != null then app.gpu else app.enablements.wayland || app.enablements.x11;
conf = {
- path =
- if app.path == null then
- pkgs.writeScript "${app.name}-start" ''
- #!${pkgs.zsh}${pkgs.zsh.shellPath}
- ${script}
- ''
- else
- app.path;
- args = if app.args == null then [ "${app.name}-start" ] else app.args;
-
inherit id;
-
+ inherit (app) identity groups enablements;
inherit (dbusConfig) session_bus system_bus;
direct_wayland = app.insecureWayland;
- username = getsubname fid app.identity;
- home = getsubhome fid app.identity;
-
- inherit (cfg) shell;
- inherit (app) identity groups enablements;
-
container = {
inherit (app)
wait_delay
@@ -219,6 +203,20 @@ in
ensure = true;
}
];
+
+ username = getsubname fid app.identity;
+ inherit (cfg) shell;
+ home = getsubhome fid app.identity;
+
+ path =
+ if app.path == null then
+ pkgs.writeScript "${app.name}-start" ''
+ #!${pkgs.zsh}${pkgs.zsh.shellPath}
+ ${script}
+ ''
+ else
+ app.path;
+ args = if app.args == null then [ "${app.name}-start" ] else app.args;
};
};
diff --git a/test/sandbox/case/pd.nix b/test/sandbox/case/pd.nix
index ad97c996..140398e8 100644
--- a/test/sandbox/case/pd.nix
+++ b/test/sandbox/case/pd.nix
@@ -182,7 +182,6 @@
(ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw")
(ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000000,gid=1000000")
(ent "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
- (ent "/" "/run/nscd" "ro,nosuid,nodev,relatime" "tmpfs" "readonly" "ro,mode=755,uid=1000000,gid=1000000")
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
(ent "/" "/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000")
diff --git a/test/test.py b/test/test.py
index a1e6b0f5..f4e34934 100644
--- a/test/test.py
+++ b/test/test.py
@@ -61,14 +61,14 @@ def check_state(name, enablements):
config = instance['config']
command = f"{name}-start"
- if not (config['path'].startswith("/nix/store/")) or not (config['path'].endswith(command)):
+ if not (config['container']['path'].startswith("/nix/store/")) or not (config['container']['path'].endswith(command)):
raise Exception(f"unexpected path {config['path']}")
- if len(config['args']) != 1 or config['args'][0] != command:
+ if len(config['container']['args']) != 1 or config['container']['args'][0] != command:
raise Exception(f"unexpected args {config['args']}")
if config['enablements'] != enablements:
- raise Exception(f"unexpected enablements {instance['config']['enablements']}")
+ raise Exception(f"unexpected enablements {config['enablements']['enablements']}")
def hakurei(command):
@@ -104,7 +104,7 @@ if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *canno
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
# Verify timeout behaviour:
-machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr')
+machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr || (cat /var/tmp/linger-stderr; false)')
linger_stdout = machine.succeed("cat /var/tmp/linger-stdout")
linger_stderr = machine.succeed("cat /var/tmp/linger-stderr")
if linger_stdout != "":