cmd/hakurei: rename app to run
All checks were successful
Test / Create distribution (push) Successful in 1m15s
Test / Sandbox (push) Successful in 3m7s
Test / Hakurei (push) Successful in 4m21s
Test / ShareFS (push) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 6m36s
Test / Flake checks (push) Successful in 1m24s

The run command was a legacy holdover from very early days and is only useful for testing and demonstration these days. This change also renames it to exec.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-03-28 16:43:02 +09:00
parent 2c254c70b8
commit b1ea3b4acf
8 changed files with 50 additions and 54 deletions

View File

@@ -34,6 +34,8 @@ func optionalErrorUnwrap(err error) error {
return err return err
} }
var errSuccess = errors.New("success")
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command { func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
var ( var (
flagVerbose bool flagVerbose bool
@@ -63,9 +65,9 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
var ( var (
flagIdentifierFile int flagIdentifierFile int
) )
c.NewCommand("app", "Load and start container from configuration file", func(args []string) error { c.NewCommand("run", "Load and start container from configuration file", func(args []string) error {
if len(args) < 1 { if len(args) < 1 {
log.Fatal("app requires at least 1 argument") log.Fatal("run requires at least 1 argument")
} }
config := tryPath(msg, args[0]) config := tryPath(msg, args[0])
@@ -101,7 +103,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool flagWayland, flagX11, flagDBus, flagPipeWire, flagPulse bool
) )
c.NewCommand("run", "Configure and start a permissive container", func(args []string) error { c.NewCommand("exec", "Configure and start a permissive container", func(args []string) error {
if flagIdentity < hst.IdentityStart || flagIdentity > hst.IdentityEnd { if flagIdentity < hst.IdentityStart || flagIdentity > hst.IdentityEnd {
log.Fatalf("identity %d out of range", flagIdentity) log.Fatalf("identity %d out of range", flagIdentity)
} }
@@ -326,7 +328,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
flagShort bool flagShort bool
flagNoStore bool flagNoStore bool
) )
c.NewCommand("show", "Show live or local app configuration", func(args []string) error { c.NewCommand("show", "Show live or local instance configuration", func(args []string) error {
switch len(args) { switch len(args) {
case 0: // system case 0: // system
printShowSystem(os.Stdout, flagShort, flagJSON) printShowSystem(os.Stdout, flagShort, flagJSON)

View File

@@ -23,9 +23,9 @@ func TestHelp(t *testing.T) {
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS] Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
Commands: Commands:
app Load and start container from configuration file run Load and start container from configuration file
run Configure and start a permissive container exec Configure and start a permissive container
show Show live or local app configuration show Show live or local instance configuration
ps List active instances ps List active instances
version Display version information version Display version information
license Show full license text license Show full license text
@@ -35,8 +35,8 @@ Commands:
`, `,
}, },
{ {
"run", []string{"run", "-h"}, ` "exec", []string{"exec", "-h"}, `
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS] Usage: hakurei exec [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--policy <value>] [--priority <int>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
Flags: Flags:
-X Enable direct connection to X11 -X Enable direct connection to X11

View File

@@ -1,8 +1,5 @@
package main package main
// this works around go:embed '..' limitation
//go:generate cp ../../LICENSE .
import ( import (
"context" "context"
_ "embed" _ "embed"
@@ -17,12 +14,9 @@ import (
"hakurei.app/message" "hakurei.app/message"
) )
var ( //go:generate cp ../../LICENSE .
errSuccess = errors.New("success") //go:embed LICENSE
var license string
//go:embed LICENSE
license string
)
// earlyHardeningErrs are errors collected while setting up early hardening feature. // earlyHardeningErrs are errors collected while setting up early hardening feature.
type earlyHardeningErrs struct{ yamaLSM, dumpable error } type earlyHardeningErrs struct{ yamaLSM, dumpable error }

10
dist/comp/_hakurei vendored
View File

@@ -1,11 +1,11 @@
#compdef hakurei #compdef hakurei
_hakurei_app() { _hakurei_run() {
__hakurei_files __hakurei_files
return $? return $?
} }
_hakurei_run() { _hakurei_exec() {
_arguments \ _arguments \
'--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \ '--id[Reverse-DNS style Application identifier, leave empty to inherit instance identifier]:id' \
'-a[Application identity]: :_numbers' \ '-a[Application identity]: :_numbers' \
@@ -57,9 +57,9 @@ __hakurei_instances() {
{ {
local -a _hakurei_cmds local -a _hakurei_cmds
_hakurei_cmds=( _hakurei_cmds=(
"app:Load and start container from configuration file" "run:Load and start container from configuration file"
"run:Configure and start a permissive container" "exec:Configure and start a permissive container"
"show:Show live or local app configuration" "show:Show live or local instance configuration"
"ps:List active instances" "ps:List active instances"
"version:Display version information" "version:Display version information"
"license:Show full license text" "license:Show full license text"

View File

@@ -265,7 +265,7 @@ in
''; '';
in in
pkgs.writeShellScriptBin app.name '' pkgs.writeShellScriptBin app.name ''
exec hakurei${if app.verbose then " -v" else ""} app ${checkedConfig "hakurei-app-${app.name}.json" conf} $@ exec hakurei${if app.verbose then " -v" else ""} run ${checkedConfig "hakurei-app-${app.name}.json" conf} $@
'' ''
) )
] ]

View File

@@ -30,7 +30,7 @@ in
# For checking pd outcome: # For checking pd outcome:
(pkgs.writeShellScriptBin "check-sandbox-pd" '' (pkgs.writeShellScriptBin "check-sandbox-pd" ''
hakurei -v run hakurei-test \ hakurei -v exec hakurei-test \
-p "/var/tmp/.hakurei-check-ok.0" \ -p "/var/tmp/.hakurei-check-ok.0" \
-t ${toString (builtins.toFile "hakurei-pd-want.json" (builtins.toJSON testCases.pd.want))} \ -t ${toString (builtins.toFile "hakurei-pd-want.json" (builtins.toJSON testCases.pd.want))} \
-s ${testCases.pd.expectedFilter.${pkgs.stdenv.hostPlatform.system}} "$@" -s ${testCases.pd.expectedFilter.${pkgs.stdenv.hostPlatform.system}} "$@"

View File

@@ -42,23 +42,23 @@ machine.wait_for_file("/run/user/1000/wayland-1")
machine.wait_for_file("/tmp/sway-ipc.sock") machine.wait_for_file("/tmp/sway-ipc.sock")
# Check pd seccomp outcome: # Check pd seccomp outcome:
swaymsg("exec hakurei run cat") swaymsg("exec hakurei exec cat")
check_filter(0, "pdlike", "cat") check_filter(0, "pdlike", "cat")
# Check fd leak: # Check fd leak:
swaymsg("exec exec 127</proc/cmdline && hakurei -v run sleep infinity") swaymsg("exec exec 127</proc/cmdline && hakurei -v exec sleep infinity")
pd_identity0_sleep_pid = int(machine.wait_until_succeeds("pgrep -U 10000 -x sleep", timeout=60)) pd_identity0_sleep_pid = int(machine.wait_until_succeeds("pgrep -U 10000 -x sleep", timeout=60))
print(machine.succeed(f"hakurei-test fd {pd_identity0_sleep_pid}")) print(machine.succeed(f"hakurei-test fd {pd_identity0_sleep_pid}"))
machine.succeed(f"kill -INT {pd_identity0_sleep_pid}") machine.succeed(f"kill -INT {pd_identity0_sleep_pid}")
# Verify capabilities/securebits in user namespace: # Verify capabilities/securebits in user namespace:
print(machine.succeed("sudo -u alice -i hakurei run capsh --print")) print(machine.succeed("sudo -u alice -i hakurei exec capsh --print"))
print(machine.succeed("sudo -u alice -i hakurei run capsh --has-no-new-privs")) print(machine.succeed("sudo -u alice -i hakurei exec capsh --has-no-new-privs"))
print(machine.fail("sudo -u alice -i hakurei run capsh --has-a=CAP_SYS_ADMIN")) print(machine.fail("sudo -u alice -i hakurei exec capsh --has-a=CAP_SYS_ADMIN"))
print(machine.fail("sudo -u alice -i hakurei run capsh --has-b=CAP_SYS_ADMIN")) print(machine.fail("sudo -u alice -i hakurei exec capsh --has-b=CAP_SYS_ADMIN"))
print(machine.fail("sudo -u alice -i hakurei run capsh --has-i=CAP_SYS_ADMIN")) print(machine.fail("sudo -u alice -i hakurei exec capsh --has-i=CAP_SYS_ADMIN"))
print(machine.fail("sudo -u alice -i hakurei run capsh --has-p=CAP_SYS_ADMIN")) print(machine.fail("sudo -u alice -i hakurei exec capsh --has-p=CAP_SYS_ADMIN"))
print(machine.fail("sudo -u alice -i hakurei run umount -R /dev")) print(machine.fail("sudo -u alice -i hakurei exec umount -R /dev"))
# Check sandbox outcome: # Check sandbox outcome:
machine.succeed("install -dm0777 /tmp/.hakurei-store-rw/{upper,work}") machine.succeed("install -dm0777 /tmp/.hakurei-store-rw/{upper,work}")

View File

@@ -87,9 +87,9 @@ machine.wait_for_file("/tmp/sway-ipc.sock")
swaymsg("exec hakurei-test") swaymsg("exec hakurei-test")
# Deny unmapped uid: # Deny unmapped uid:
denyOutput = machine.fail("sudo -u untrusted -i hakurei run &>/dev/stdout") denyOutput = machine.fail("sudo -u untrusted -i hakurei exec &>/dev/stdout")
print(denyOutput) print(denyOutput)
denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v run &>/dev/stdout") denyOutputVerbose = machine.fail("sudo -u untrusted -i hakurei -v exec &>/dev/stdout")
print(denyOutputVerbose) print(denyOutputVerbose)
# Fail direct hsu call: # Fail direct hsu call:
@@ -118,11 +118,11 @@ def hakurei_identity(offset):
# Start hakurei permissive defaults outside Wayland session: # Start hakurei permissive defaults outside Wayland session:
print(machine.succeed("sudo -u alice -i hakurei -v run -a 0 touch /tmp/pd-bare-ok")) print(machine.succeed("sudo -u alice -i hakurei -v exec -a 0 touch /tmp/pd-bare-ok"))
machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok", timeout=5) machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-bare-ok", timeout=5)
# Verify silent output permissive defaults: # Verify silent output permissive defaults:
output = machine.succeed("sudo -u alice -i hakurei run -a 0 true &>/dev/stdout") output = machine.succeed("sudo -u alice -i hakurei exec -a 0 true &>/dev/stdout")
if output != "": if output != "":
raise Exception(f"unexpected output\n{output}") raise Exception(f"unexpected output\n{output}")
@@ -131,12 +131,12 @@ def silent_output_interrupt(flags):
swaymsg("exec foot") swaymsg("exec foot")
wait_for_window("alice@machine") wait_for_window("alice@machine")
# identity 0 does not have home-manager # identity 0 does not have home-manager
machine.send_chars(f"exec hakurei run {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n") machine.send_chars(f"exec hakurei exec {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n")
machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready", timeout=15) machine.wait_for_file("/tmp/hakurei.0/tmpdir/0/pd-silent-ready", timeout=15)
machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready") machine.succeed("rm /tmp/hakurei.0/tmpdir/0/pd-silent-ready")
machine.send_key("ctrl-c") machine.send_key("ctrl-c")
machine.wait_until_fails("pgrep foot", timeout=5) machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_until_fails(f"pgrep -u alice -f 'hakurei run {flags}-a 0 '", timeout=5) machine.wait_until_fails(f"pgrep -u alice -f 'hakurei exec {flags}-a 0 '", timeout=5)
output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent") output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent")
if output != "": if output != "":
raise Exception(f"unexpected output\n{output}") raise Exception(f"unexpected output\n{output}")
@@ -147,10 +147,10 @@ silent_output_interrupt("--dbus ") # this one is especially painful as it mainta
silent_output_interrupt("--wayland -X --dbus --pulse ") silent_output_interrupt("--wayland -X --dbus --pulse ")
# Verify graceful failure on bad Wayland display name: # Verify graceful failure on bad Wayland display name:
print(machine.fail("sudo -u alice -i hakurei -v run --wayland true")) print(machine.fail("sudo -u alice -i hakurei -v exec --wayland true"))
# Start hakurei permissive defaults within Wayland session: # Start hakurei permissive defaults within Wayland session:
hakurei('-v run --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok') hakurei('-v exec --wayland --dbus --dbus-log notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-ok')
machine.wait_for_file("/tmp/dbus-ok", timeout=15) machine.wait_for_file("/tmp/dbus-ok", timeout=15)
collect_state_ui("dbus_notify_exited") collect_state_ui("dbus_notify_exited")
# not in pid namespace, verify termination # not in pid namespace, verify termination
@@ -158,10 +158,10 @@ machine.wait_until_fails("pgrep xdg-dbus-proxy")
machine.succeed("pkill -9 mako") machine.succeed("pkill -9 mako")
# Check revert type selection: # Check revert type selection:
hakurei("-v run --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok") hakurei("-v exec --wayland -X --dbus --pulse -u p0 foot && touch /tmp/p0-exit-ok")
wait_for_window("p0@machine") wait_for_window("p0@machine")
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
hakurei("-v run --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok") hakurei("-v exec --wayland -X --dbus --pulse -u p1 foot && touch /tmp/p1-exit-ok")
wait_for_window("p1@machine") wait_for_window("p1@machine")
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")) print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000"))
machine.send_chars("exit\n") machine.send_chars("exit\n")
@@ -173,14 +173,14 @@ machine.wait_for_file("/tmp/p0-exit-ok", timeout=15)
machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000") machine.fail("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10000")
# Check invalid identifier fd behaviour: # Check invalid identifier fd behaviour:
machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v app --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd') machine.fail('echo \'{"container":{"shell":"/proc/nonexistent","home":"/proc/nonexistent","path":"/proc/nonexistent"}}\' | sudo -u alice -i hakurei -v run --identifier-fd 32767 - 2>&1 | tee > /tmp/invalid-identifier-fd')
machine.wait_for_file("/tmp/invalid-identifier-fd") machine.wait_for_file("/tmp/invalid-identifier-fd")
print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd')) print(machine.succeed('grep "^hakurei: cannot write identifier: bad file descriptor$" /tmp/invalid-identifier-fd'))
# Check interrupt shim behaviour: # Check interrupt shim behaviour:
swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'") swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'")
wait_for_window(f"u0_a{hakurei_identity(0)}@machine") wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
machine.succeed("pkill -INT -f 'hakurei -v app '") machine.succeed("pkill -INT -f 'hakurei -v run '")
machine.wait_until_fails("pgrep foot", timeout=5) machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_for_file("/tmp/monitor-exit-code") machine.wait_for_file("/tmp/monitor-exit-code")
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
@@ -190,7 +190,7 @@ if interrupt_exit_code != 230:
# Check interrupt shim behaviour immediate termination: # Check interrupt shim behaviour immediate termination:
swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'") swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'")
wait_for_window(f"u0_a{hakurei_identity(0)}@machine") wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
machine.succeed("pkill -INT -f 'hakurei -v app '") machine.succeed("pkill -INT -f 'hakurei -v run '")
machine.wait_until_fails("pgrep foot", timeout=5) machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_for_file("/tmp/monitor-exit-code") machine.wait_for_file("/tmp/monitor-exit-code")
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
@@ -201,19 +201,19 @@ if interrupt_exit_code != 254:
swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'") swaymsg("exec sh -c 'ne-foot &> /tmp/shim-cont-unexpected-pid'")
wait_for_window(f"u0_a{hakurei_identity(0)}@machine") wait_for_window(f"u0_a{hakurei_identity(0)}@machine")
machine.succeed("pkill -CONT -f 'hakurei shim'") machine.succeed("pkill -CONT -f 'hakurei shim'")
machine.succeed("pkill -INT -f 'hakurei -v app '") machine.succeed("pkill -INT -f 'hakurei -v run '")
machine.wait_until_fails("pgrep foot", timeout=5) machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_for_file("/tmp/shim-cont-unexpected-pid") machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid')) print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
# Check setscheduler: # Check setscheduler:
sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v run cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v exec cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_unset != 0: if sched_unset != 0:
raise Exception(f"unexpected unset policy: {sched_unset}") raise Exception(f"unexpected unset policy: {sched_unset}")
sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v run --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_idle != 5: if sched_idle != 5:
raise Exception(f"unexpected idle policy: {sched_idle}") raise Exception(f"unexpected idle policy: {sched_idle}")
sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v run --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2")) sched_rr = int(machine.succeed("sudo -u alice -i hakurei -v exec --policy=rr cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_rr != 2: if sched_rr != 2:
raise Exception(f"unexpected round-robin policy: {sched_idle}") raise Exception(f"unexpected round-robin policy: {sched_idle}")
@@ -243,11 +243,11 @@ machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_until_fails("pgrep -x hakurei", timeout=5) machine.wait_until_fails("pgrep -x hakurei", timeout=5)
machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +") machine.succeed("find /tmp -maxdepth 1 -type d -name '.hakurei-shim-*' -print -exec false '{}' +")
# Test PipeWire SecurityContext: # Test PipeWire SecurityContext:
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl info") machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl info")
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle") machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pulse pactl set-sink-mute @DEFAULT_SINK@ toggle")
# Test PipeWire direct access: # Test PipeWire direct access:
machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump") machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 pw-dump")
machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v run --pipewire pw-dump") machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei -v exec --pipewire pw-dump")
# Test XWayland (foot does not support X): # Test XWayland (foot does not support X):
swaymsg("exec x11-alacritty") swaymsg("exec x11-alacritty")