Nix? Flatpak? Why not both?
NixPak is essentially a fancy declarative wrapper around bwrap and xdg-dbus-proxy. You can use it to sandbox all sorts of Nix-packaged applications, including graphical ones.
- Bind-mount any host path, rw and ro
- Bind-mount devices
- Full network isolation
- D-Bus access control
- Flatpak Shim
- Fools xdg-desktop-portal into thinking your Nix application is a Flatpak!
- Enables use of the Document Portal, so you don't need to bind-mount your entire home directory but can still open and save files in arbitrary locations.
Also see the examples directory
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpak = {
url = "github:nixpak/nixpak";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, nixpak }: {
packages.x86_64-linux = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
mkNixPak = nixpak.lib.nixpak {
inherit (pkgs) lib;
inherit pkgs;
};
sandboxed-hello = mkNixPak {
config = { sloth, ... }: {
# the application to isolate
app.package = pkgs.hello;
# path to the executable to be wrapped
# this is usually autodetected but
# can be set explicitly nonetheless
app.binPath = "bin/hello";
# enabled by default, flip to disable
# and to remove dependency on xdg-dbus-proxy
dbus.enable = true;
# same usage as --see, --talk, --own
dbus.policies = {
"org.freedesktop.DBus" = "talk";
"ca.desrt.dconf" = "talk";
};
# needs to be set for Flatpak emulation
# defaults to com.nixpak.${name}
# where ${name} is generated from the drv name like:
# hello -> Hello
# my-app -> MyApp
flatpak.appId = "org.myself.HelloApp";
bubblewrap = {
# disable all network access
network = false;
# lists of paths to be mounted inside the sandbox
# supports runtime resolution of environment variables
# see "Sloth values" below
bind.rw = [
(sloth.concat' sloth.homeDir "/Documents")
(sloth.env "XDG_RUNTIME_DIR")
# a nested list represents a src -> dest mapping
# where src != dest
[
(sloth.concat' sloth.homeDir "/.local/state/nixpak/hello/config")
(sloth.concat' sloth.homeDir "/.config")
]
];
bind.ro = [
(sloth.concat' sloth.homeDir "/Downloads")
];
bind.dev = [
"/dev/dri"
];
};
};
};
in {
# Just the wrapped /bin/${mainProgram} binary
hello = sandboxed-hello.config.script;
# A symlinkJoin that resembles the original package,
# except the main binary is swapped for the
# wrapper script, as are textual references
# to the binary, like in D-Bus service files.
# Useful for GUI apps.
hello-env = sandboxed-hello.config.env;
};
};
}
Sandbox tools often need to deal with dynamic paths. Hardcoding a specific user's home directory in a sandbox configuration is not very useful. To deal with this, NixPak configuration supports values whose evaluation is delayed until runtime, called "sloth values" for their extraordinary laziness.
Sloth values are constructed using attributes from the sloth
attribute set, which is available
via module arguments.
{ sloth, ... }:
{
bubblewrap.bind.ro = [ sloth.homeDir ];
}
The following entries use Haskell-style type annotations. The Sloth
type refers to a sloth value,
which can be a string or a rich sloth value as created by sloth.*
functions.
Takes an environment variable name and resolves it to its value at runtime.
sloth.env "HOME" # results in "/home/user" at runtime
Ensures the presence of a directory. If it does not exist, creates it with permisions 0700
,
including all its parent components (like mkdir -p
). Returns the resolved sloth value afterwards.
sloth.mkdir (sloth.env "MY_CACHE_DIRECTORY")
Concatenates sloth values. Useful for combining an environment variable with a string.
sloth.concat [
sloth.homeDir
"/.config"
]
Concatenates two sloth values, with the convenience of not having to create a list.
sloth.concat' sloth.homeDir "/.config"
A unique alphanumeric string derived from the launcher's PID. Used to differentiate between multiple simultaneously active sandbox instances.
sloth.concat [
(sloth.env "XDG_RUNTIME_DIR")
"/my-app-"
sloth.instanceId
]
# looks something like "/run/user/1000/my-app-jim1rivq0gblz0vn6k32wgv7aq"