Portable version 13
Portable version 13 is here, and it comes with a full rewrite of the sandbox. The old bash-based implementation has been retired in favour of a shiny new Go version. The result is faster startup, better efficiency, and fewer creative ways for things to go wrong. The original goals remain unchanged: fast, private, and efficient. Now with less duct tape.
Changes
Unsafe mode dropped
During months of maintaining the legacy bash version, it became increasingly clear that unsafe mode was… mostly ignored. It was annoying to have a popup ask you whether Portable should enable sandbox. Very few people used it, and keeping it alive meant extra complexity for little benefit. For the Go rewrite, we decided to let it go.
For folks still using unsafe mode for whatever reason, the legacy branch and portable-legacy package has been pushed to the AUR.
Process tracking
In the legacy version, Portable didn’t really track processes. It simply counted how many were running inside the sandbox. This worked until some libraries decided to spawn long-lived background processes that refused to die when the main application exited. (I’m looking at you, Glycin). The option terminateImmediately was added as a workaround, but it was never ideal.
From version 13 onward, things have changed a bit. The init now tracks all processes it spawned, and shuts everything down cleanly once they’re all done. It was worth noting that the aforementioned knob still exists for broken apps that forks off and terminate immediately.
Actual concurrency
We have a semi-concurrent bash code base before. A function takes care of unblocking when the other end is ready. This allows us to do some concurrency: we can install the stub desktop file while calculating arguments for another thingy. But there’s a catch: bash subshells subshells can’t easily send data back to the main shell, which made true parallel argument computation impractical. We did implement a argument passing mechanic, but it was a racy mess.
With the rewrite in Go, we are able to take full advantage of global variables (scary!) and channels. Now, arguments are computed in a parallel way, with no race in between. The most expensive compute task runs first while others are dispatched later, reducing the block time further.
Multi instances
Portable now has 2 UDS, or Unix Domain Sockets. The control socket is listened by outside daemon, while auxStart is managed by the sandbox init itself. This enables a new, robust way to detect running sandboxes, while also removing the fragile inotify from dependencies list.
Each instance now has a numeric ID which appears in the unit names. Reducing the chance of a collision while prevents a failed instance from messing with future runs.
Arguments calculation
The new version still split arguments from launchTarget by spaces for compatibility. However, user-supplied arguments are now handled very differently: they are now parsed by Go’s os.Args into slices, encoded in JSON and sent to init for execution. The practical result is simple: quoted arguments now behave correctly. Spaces stay where they belong, and your command line stops playing guessing games.
More udev integration
Unlike most sandboxes, Portable makes heavy use of the /sys sysfs. Packages define whether input devices and all GPUs are exposed.
Traditionally, we do scanning to figure out how many input devices should be bound. That is, using realpath & readlink to find the root of a GPU / input device. This is messy, and could result in leaks because we doesn’t really know boundaries for a certain device. And not to mention manually parsing a filesystem is slow.
The Go rewrite not makes heavy use of udev. For example we query udev for drm_minor devices for actual GPUs, and input subsystem for your input devices. This works in a more robust way for extremely edge cases, like weird GPU names and non-standard device file location. Though unfortunately, this part of parsing will be mostly single-threaded per udev enumerator.
This integration also leads to more possibilities, like only exposing game controller if udev properly labels it.
Portable 13 is a quieter, cleaner, and more predictable foundation. Less shell trickery, more actual engineering, and fewer surprises lurking in the background.
