aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/outcome/shim.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-12-15 12:43:58 +0900
committerOphestra <cat@gensokyo.uk>2025-12-15 12:57:06 +0900
commit54610aaddcc01e335c34087bc0c3bc1de6d23cf8 (patch)
tree4e8ad54c21e4440ddd54a2be4089d7d4807f97ac /internal/outcome/shim.go
parent2e806601693e0262641386ce5430d7e8d84b2a77 (diff)
internal/outcome: expose pipewire via pipewire-pulse
This no longer exposes the pipewire socket to the container, and instead mediates access via pipewire-pulse. This makes insecure parts of the protocol inaccessible as explained in the doc comment in hst. Closes #29. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/outcome/shim.go')
-rw-r--r--internal/outcome/shim.go75
1 files changed, 75 insertions, 0 deletions
diff --git a/internal/outcome/shim.go b/internal/outcome/shim.go
index 59b1236c..a569e7a6 100644
--- a/internal/outcome/shim.go
+++ b/internal/outcome/shim.go
@@ -15,9 +15,11 @@ import (
"hakurei.app/container"
"hakurei.app/container/check"
+ "hakurei.app/container/fhs"
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/hst"
+ "hakurei.app/internal/pipewire"
"hakurei.app/message"
)
@@ -266,6 +268,79 @@ func shimEntrypoint(k syscallDispatcher) {
// bounds and default enforced in finalise.go
z.WaitDelay = state.Shim.WaitDelay
+ if stateParams.pipewirePulsePath != nil {
+ zpw := container.NewCommand(ctx, msg, stateParams.pipewirePulsePath, pipewirePulseName)
+ zpw.Hostname = "hakurei-" + pipewirePulseName
+ zpw.SeccompFlags |= seccomp.AllowMultiarch
+ zpw.SeccompPresets |= std.PresetStrict
+ zpw.Env = []string{
+ // pipewire SecurityContext socket path
+ pipewire.Remote + "=" + stateParams.instancePath().Append("pipewire").String(),
+ // pipewire-pulse socket directory path
+ envXDGRuntimeDir + "=" + sp.String(),
+ }
+ if msg.IsVerbose() {
+ zpw.Stdin, zpw.Stdout, zpw.Stderr = os.Stdin, os.Stdout, os.Stderr
+ }
+ zpw.
+ Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
+ Bind(sp.unwrap(), sp.unwrap(), std.BindWritable).
+ Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
+ socketPath := sp.unwrap().Append("pulse", "native")
+ innerSocketPath := stateParams.runtimeDir.Append("pulse", "native")
+
+ if err := k.containerStart(zpw); err != nil {
+ sp.destroy()
+ printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) },
+ "cannot start "+pipewirePulseName+" container:", err)
+ }
+ if err := k.containerServe(zpw); err != nil {
+ sp.destroy()
+ printMessageError(func(v ...any) { k.fatal(fmt.Sprintln(v...)) },
+ "cannot configure "+pipewirePulseName+" container:", err)
+ }
+
+ done := make(chan error, 1)
+ k.new(func(k syscallDispatcher, msg message.Msg) { done <- k.containerWait(zpw) })
+
+ socketTimer := time.NewTimer(shimPipeWireTimeout)
+ for {
+ select {
+ case <-socketTimer.C:
+ sp.destroy()
+ k.fatal(pipewirePulseName + " exceeded deadline before socket appeared")
+ break
+
+ case err := <-done:
+ var exitError *exec.ExitError
+ if !errors.As(err, &exitError) {
+ msg.Verbosef("cannot wait: %v", err)
+ k.exit(127)
+ }
+ sp.destroy()
+ k.fatal(pipewirePulseName + " " + exitError.ProcessState.String())
+ break
+
+ default:
+ if _, err := k.stat(socketPath.String()); err != nil {
+ if !errors.Is(err, os.ErrNotExist) {
+ sp.destroy()
+ k.fatal(err)
+ break
+ }
+
+ time.Sleep(500 * time.Microsecond)
+ continue
+ }
+ }
+
+ break
+ }
+
+ z.Bind(socketPath, innerSocketPath, 0)
+ z.Env = append(z.Env, "PULSE_SERVER=unix:"+innerSocketPath.String())
+ }
+
if err := k.containerStart(z); err != nil {
var f func(v ...any)
if logger := msg.GetLogger(); logger != nil {