197 lines
7.1 KiB
Nix
197 lines
7.1 KiB
Nix
{
|
|
stdenv, lib
|
|
, runCommandLocal, writeShellScriptBin, makeDesktopItem
|
|
, bubblewrap, coreutils, glibc, pkgsi686Linux
|
|
}:
|
|
|
|
{
|
|
name # Name of the sandboxed package, e.g. "package-wrapped"
|
|
, launchScriptName # Name of the script that will be placed in $out/bin
|
|
, binary # The binary to execute inside the sandbox
|
|
, extraArgs ? [] # Extra arguments to add to the argv
|
|
, desktopFileAttributes ? {} # Arguments to apply to the desktop file
|
|
, preDesktopFilePhase ? "" # Code to run during the install phase before building the desktop file
|
|
, enableDesktopFile ? false # Whether to build a desktop file
|
|
, copyIntoSandbox ? {} # Source -> destination relative to $out
|
|
, unshareUser ? true
|
|
, unshareIpc ? true
|
|
, unsharePid ? true
|
|
, unshareNet ? false
|
|
, unshareUts ? true
|
|
, unshareCgroup ? true
|
|
, dieWithParent ? true # Should bubblewrap exit when the parents exits
|
|
, mountInHome ? [] # Files, directories to mount inside the sandbox
|
|
, chdirTo ? "\"$(pwd)\"" # What should be the CWD when entering the sandbox
|
|
, additionalBlacklist ? [] # Root directories that should not be mounted
|
|
, additionalMounts ? [] # Files, directories outside the home directory to mount inside the sandbox
|
|
, extraEnv ? {} # Extra environment variables to set inside the sandbox
|
|
, enableAlsa ? true # Mount ALSA files from /etc/
|
|
, enableSudo ? true # Mount sudo files from /etc/
|
|
, enableShells ? true # Mount shell related files from /etc/
|
|
, enableNix ? true # Mount nix related files from /etc/
|
|
, enableOsInfo ? true # Mount /etc/{machine-id,os-release}
|
|
}:
|
|
|
|
let
|
|
mountHome = mountInHome == [];
|
|
|
|
etcBindFlags = let
|
|
files = [
|
|
# Users, Groups, NSS
|
|
"passwd"
|
|
"group"
|
|
"shadow"
|
|
"hosts"
|
|
"resolv.conf"
|
|
"nsswitch.conf"
|
|
# User profiles
|
|
"profiles"
|
|
# Time
|
|
"localtime"
|
|
"zoneinfo"
|
|
# PAM
|
|
"pam.d"
|
|
# Fonts
|
|
"fonts"
|
|
]
|
|
++ (lib.optionals enableNix [ "static" "nix" ])
|
|
++ (lib.optionals enableShells [ "bashrc" "zshenv" "zshrc" "zinputrc" "zprofile" ])
|
|
++ (lib.optionals enableSudo [ "login.defs" "sudoers" "sudoers.d" ])
|
|
++ (lib.optionals enableAlsa [ "alsa" "asound.conf" ])
|
|
++ (lib.optionals enableOsInfo [ "machine-id" "os-release" ])
|
|
# Only add SSL stuff when we have networking enabled
|
|
++ (lib.optionals (!unshareNet) [ "ssl/certs" "pki" ]);
|
|
in builtins.concatStringsSep "\n " (map (file: "--ro-bind-try /etc/${file} /etc/${file}") files);
|
|
|
|
# Create this on the fly instead of linking from /nix
|
|
# The container might have to modify it and re-run ldconfig if there are
|
|
# issues running some binary with LD_LIBRARY_PATH
|
|
createLdConfCache = ''
|
|
cat > /etc/ld.so.conf <<EOF
|
|
/lib
|
|
/lib/x86_64-linux-gnu
|
|
/lib64
|
|
/usr/lib
|
|
/usr/lib/x86_64-linux-gnu
|
|
/usr/lib64
|
|
/lib/i386-linux-gnu
|
|
/lib32
|
|
/usr/lib/i386-linux-gnu
|
|
/usr/lib32
|
|
EOF
|
|
ldconfig &> /dev/null
|
|
'';
|
|
sandboxInitScriptName = "${name}-wrapped-init.sh";
|
|
sandboxInitScript = writeShellScriptBin sandboxInitScriptName ''
|
|
source /etc/profile
|
|
${createLdConfCache}
|
|
exec ${binary} "$@"
|
|
'';
|
|
|
|
|
|
sandboxScriptName = "${name}-wrapped.sh";
|
|
sandboxScript = let
|
|
extraEnvString = lib.foldl (acc: val: acc + val + "\n") "" (lib.mapAttrsToList (name: value: "--setenv ${name} \"${value}\"") extraEnv);
|
|
stringify = x: "\"${x}\"";
|
|
safeAdditionalMounts = map stringify additionalMounts;
|
|
safeMountInHome = map stringify mountInHome;
|
|
in writeShellScriptBin sandboxScriptName ''
|
|
blacklist=(/nix /dev /proc /etc ${lib.optionalString (!mountHome) "/home"} ${builtins.toString additionalBlacklist})
|
|
ro_mounts=()
|
|
symlinks=()
|
|
|
|
declare -a auto_mounts
|
|
# loop through all directories in the root
|
|
for dir in /*; do
|
|
# if it is a directory and it is not in the blacklist
|
|
if [[ -d "$dir" ]] && [[ ! "''${blacklist[@]}" =~ "$dir" ]]; then
|
|
# add it to the mount list
|
|
auto_mounts+=(--bind "$dir" "$dir")
|
|
fi
|
|
done
|
|
|
|
if [[ "${lib.optionalString (!mountHome) "1"}" = "1" ]]; then
|
|
for entry in ${builtins.toString safeMountInHome}; do
|
|
auto_mounts+=(--bind "/home/$USER/$entry" "/home/$USER/$entry")
|
|
done
|
|
fi
|
|
|
|
if [[ ! -z "${builtins.toString additionalMounts}" ]]; then
|
|
for entry in ${builtins.toString safeAdditionalMounts}; do
|
|
auto_mounts+=(--bind "$entry" "$entry")
|
|
done
|
|
fi
|
|
|
|
cmd=(
|
|
${bubblewrap}/bin/bwrap
|
|
--dev-bind /dev /dev
|
|
--proc /proc
|
|
--chdir ${chdirTo}
|
|
${lib.optionalString unshareUser "--unshare-user"}
|
|
${lib.optionalString unshareIpc "--unshare-ipc"}
|
|
${lib.optionalString unsharePid "--unshare-pid"}
|
|
${lib.optionalString unshareNet "--unshare-net"}
|
|
${lib.optionalString unshareUts "--unshare-uts"}
|
|
${lib.optionalString unshareCgroup "--unshare-cgroup"}
|
|
${lib.optionalString dieWithParent "--die-with-parent"}
|
|
--ro-bind /nix /nix
|
|
# Our glibc will look for the cache in its own path in `/nix/store`.
|
|
# As such, we need a cache to exist there, because pressure-vessel
|
|
# depends on the existence of an ld cache. However, adding one
|
|
# globally proved to be a bad idea (see #100655), the solution we
|
|
# settled on being mounting one via bwrap.
|
|
# Also, the cache needs to go to both 32 and 64 bit glibcs, for games
|
|
# of both architectures to work.
|
|
--tmpfs ${glibc}/etc \
|
|
--symlink /etc/ld.so.conf ${glibc}/etc/ld.so.conf \
|
|
--symlink /etc/ld.so.cache ${glibc}/etc/ld.so.cache \
|
|
--ro-bind ${glibc}/etc/rpc ${glibc}/etc/rpc \
|
|
--remount-ro ${glibc}/etc \
|
|
--tmpfs ${pkgsi686Linux.glibc}/etc \
|
|
--symlink /etc/ld.so.conf ${pkgsi686Linux.glibc}/etc/ld.so.conf \
|
|
--symlink /etc/ld.so.cache ${pkgsi686Linux.glibc}/etc/ld.so.cache \
|
|
--ro-bind ${pkgsi686Linux.glibc}/etc/rpc ${pkgsi686Linux.glibc}/etc/rpc \
|
|
--remount-ro ${pkgsi686Linux.glibc}/etc \
|
|
${etcBindFlags}
|
|
"''${ro_mounts[@]}"
|
|
"''${symlinks[@]}"
|
|
"''${auto_mounts[@]}"
|
|
${extraEnvString}
|
|
${sandboxInitScript}/bin/${sandboxInitScriptName}
|
|
)
|
|
exec "''${cmd[@]}" $@
|
|
'';
|
|
in stdenv.mkDerivation {
|
|
pname = name;
|
|
version = "1.0.0";
|
|
|
|
unpackPhase = ":";
|
|
dontBuild = true;
|
|
|
|
installPhase = let
|
|
desktopFilePhase = let
|
|
desktopFile = makeDesktopItem (desktopFileAttributes // {
|
|
exec = "${sandboxScript}/bin/${sandboxScriptName}";
|
|
});
|
|
in ''
|
|
cp -r ${desktopFile}/share/applications $out/share
|
|
'';
|
|
copyIntoSandboxPhase = let
|
|
attrs = builtins.attrNames copyIntoSandbox;
|
|
cps = map (x: "cp -Lr ${x} $out/${copyIntoSandbox."${x}"}\n") attrs;
|
|
in lib.concatStrings cps;
|
|
in ''
|
|
mkdir -p $out/bin
|
|
ln -s ${sandboxScript}/bin/${sandboxScriptName} $out/bin/${launchScriptName}
|
|
|
|
|
|
# Generate the desktop file, if enabled
|
|
${lib.optionalString enableDesktopFile "mkdir -p $out/share"}
|
|
${lib.optionalString enableDesktopFile preDesktopFilePhase}
|
|
${lib.optionalString enableDesktopFile desktopFilePhase}
|
|
|
|
# Copy extra files if they are specified
|
|
${lib.optionalString (copyIntoSandbox != {}) copyIntoSandboxPhase}
|
|
'';
|
|
}
|