From b63b2e0d9cb63e81bf270aa1154d711e98ad17cc Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Tue, 26 Apr 2022 17:14:08 +0200 Subject: [PATCH] lib: Improve the sandbox derivation --- hosts/miku.nix | 6 +- lib/sandbox.nix | 136 ++++++++++++----------- overlays/sandbox.nix | 159 +++++++++++++++++++-------- packages/default.nix | 2 + packages/scripts/zoom-vm/default.nix | 46 ++++++++ 5 files changed, 240 insertions(+), 109 deletions(-) create mode 100644 packages/scripts/zoom-vm/default.nix diff --git a/hosts/miku.nix b/hosts/miku.nix index eb33bbc..87fc2a3 100644 --- a/hosts/miku.nix +++ b/hosts/miku.nix @@ -41,12 +41,16 @@ sublime-music anki gnome-podcasts + gimp #psst evolution # Proprietary stuff (yikes) - discord-wrapped discord-app-wrapped spotify-wrapped + discord-wrapped #discord-app-wrapped + spotify-wrapped + vortex-wrapped + zoom-vm ]; ptw = { diff --git a/lib/sandbox.nix b/lib/sandbox.nix index 3acfa4d..9ac75bb 100644 --- a/lib/sandbox.nix +++ b/lib/sandbox.nix @@ -5,38 +5,38 @@ }: { - name -, package -, binaryName -, version ? "1.0.0" -, desktopFileArgs ? null -, copyIntoSandbox ? null + 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 -, mountInHome ? [] -, chdirTo ? "\"$(pwd)\"" -, additionalBlacklist ? [] -, additionalMounts ? [] -, extraEnv ? {} +, 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 = [ - # NixOS Compatibility - "static" - "nix" # mainly for nixUnstable users, but also for access to nix/netrc - # Shells - "bashrc" - "zshenv" - "zshrc" - "zinputrc" - "zprofile" # Users, Groups, NSS "passwd" "group" @@ -46,30 +46,23 @@ let "nsswitch.conf" # User profiles "profiles" - # Sudo & Su - "login.defs" - "sudoers" - "sudoers.d" # Time "localtime" "zoneinfo" - # Other Core Stuff - "machine-id" - "os-release" # PAM "pam.d" # Fonts "fonts" - # ALSA - "alsa" - "asound.conf" - # SSL - "ssl/certs" - "pki" - ]; - in builtins.concatStringsSep "\n " - (map (file: "--ro-bind-try /etc/${file} /etc/${file}") files); - + ] + ++ (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 @@ -88,15 +81,21 @@ let EOF ldconfig &> /dev/null ''; - init = run: writeShellScriptBin "${binaryName}-init" '' + sandboxInitScriptName = "${name}-wrapped-init.sh"; + sandboxInitScript = writeShellScriptBin sandboxInitScriptName '' source /etc/profile ${createLdConfCache} - exec ${run} "$@" + exec ${binary} "$@" ''; - extraEnvString = lib.foldl (acc: val: acc + val + "\n") "" (lib.mapAttrsToList (name: value: "--setenv ${name} \"${value}\"") extraEnv); - mountHome = mountInHome == []; - initStr = init "${package}/bin/${binaryName}"; - bwrapCmd = { initArgs ? "" }: '' + + + 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=() @@ -112,13 +111,13 @@ let done if [[ "${lib.optionalString (!mountHome) "1"}" = "1" ]]; then - for entry in ${builtins.toString mountInHome}; do + 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 additionalMounts}; do + for entry in ${builtins.toString safeAdditionalMounts}; do auto_mounts+=(--bind "$entry" "$entry") done fi @@ -158,27 +157,40 @@ let "''${symlinks[@]}" "''${auto_mounts[@]}" ${extraEnvString} - ${initStr}/bin/${name}-init ${initArgs} + ${sandboxInitScript}/bin/${sandboxInitScriptName} ) - exec "''${cmd[@]}" + exec "''${cmd[@]}" $@ ''; - bin = writeShellScriptBin name (bwrapCmd { initArgs = ''"$@"''; }); - desktopItem = makeDesktopItem (desktopFileArgs // { - exec = "${bin}/bin/${name}"; - }); - copyIntoSandboxString = lib.concatStrings (map (x: "cp -Lr ${package}/${x} $out/${x}\n") copyIntoSandbox); in stdenv.mkDerivation { - pname = "${name}-sandboxed"; - version = version; + pname = name; + version = "1.0.0"; unpackPhase = ":"; dontBuild = true; - - installPhase = '' + + 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 ${bin}/bin/${name} $out/bin/${name} - '' + lib.strings.optionalString (!(builtins.isNull desktopFileArgs)) '' - mkdir -p $out/share/ - cp -r ${desktopItem}/share/applications $out/share - '' + lib.strings.optionalString (!(builtins.isNull copyIntoSandbox)) copyIntoSandboxString; + 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} + ''; } diff --git a/overlays/sandbox.nix b/overlays/sandbox.nix index 1dfadac..e6a2d60 100644 --- a/overlays/sandbox.nix +++ b/overlays/sandbox.nix @@ -3,27 +3,7 @@ final: prev: let wrapInSandbox = prev.callPackage ../lib/sandbox.nix {}; in { - discord-wrapped = wrapInSandbox { - name = "discord"; - package = prev.discord; - #package = discord-system-electron; - binaryName = "discord"; - mountInHome = [ ".config/discord" ".config/BetterDiscord" ]; - additionalBlacklist = [ "/mnt" ]; - chdirTo = "/home/$USER"; - - version = "1.0.1"; - copyIntoSandbox = [ "share/pixmaps" ]; - desktopFileArgs = { - name = "discord-wrapped"; - #exec = "discord"; - icon = "discord"; - desktopName = "Discord (wrapped)"; - genericName = "Instant Messenger"; - categories = [ "Network" "InstantMessaging" ]; - }; - }; - + /* discord-app-wrapped = wrapInSandbox { name = "discord-app"; package = prev.discord-app; @@ -50,48 +30,135 @@ in { mountInHome = [ ".local/share/minecraft" ".minecraft" ]; additionalBlacklist = [ "/mnt" ]; chdirTo = "/home/$USER"; - }; + };*/ steam-wrapped = wrapInSandbox { - name = "steam"; - package = prev.steam; - binaryName = "steam"; + name = "steam-wrapped"; + binary = "${prev.steam}/bin/steam"; + launchScriptName = "steam"; mountInHome = [ ".steam" ".local/share/Steam" ]; additionalBlacklist = [ "/mnt" ]; additionalMounts = [ "/mnt/Storage/Games/SteamLibrary" ]; chdirTo = "/home/$USER"; - extraEnv = { - DRI_PRIME = "1"; + + preDesktopFilePhase = '' + cp -Lr ${prev.steam}/share/icons $out/share/icons + ''; + desktopFileAttributes = { + name = "steam-wrapped"; + icon = "steam"; + desktopName = "Steam (wrapped)"; }; - - copyIntoSandbox = [ "/share" ]; - }; - - android-studio-wrapped = wrapInSandbox { - name = "android-studio"; - package = prev.android-studio; - binaryName = "android-studio"; - additionalBlacklist = [ "/mnt" ]; - mountInHome = [ "Development/Personal/Android" ".android" ]; - chdirTo = "/home/$USER"; + enableDesktopFile = true; }; spotify-wrapped = wrapInSandbox { - name = "spotify"; - package = prev.spotify; - binaryName = "spotify"; + name = "spotify-wrapped"; + launchScriptName = "spotify"; + binary = "${prev.spotify}/bin/spotify"; additionalBlacklist = [ "/mnt" ]; mountInHome = [ ".config/spotify" ]; chdirTo = "/home/$USER"; + + preDesktopFilePhase = '' + cp -Lr ${prev.spotify}/share/icons $out/share/icons + ''; + desktopFileAttributes = { + name = "spotify-wrapped"; + icon = "spotify"; + desktopName = "Spotify (wrapped)"; + }; + enableDesktopFile = true; }; lutris-free-wrapped = wrapInSandbox { - name = "lutris-free"; - package = prev.lutris-free; - binaryName = "lutris"; + name = "lutris-free-wrapped"; + launchScriptName = "lutris"; + binary = "${prev.lutris-free}/bin/lutris"; additionalBlacklist = [ "/mnt" ]; - additionalMounts = [ "/mnt/Storage/Games/UPlay" ]; - mountInHome = [ ".local/share/lutris" ".config/lutris" ]; + additionalMounts = [ "/mnt/Storage/Games/" ]; + mountInHome = [ ".local/share/lutris" ".config/lutris" "Games" ]; chdirTo = "/home/$USER"; + + preDesktopFilePhase = '' + mkdir -p $out/share/ + cp -Lr ${prev.lutris-free}/share/icons $out/share/icons/ + ''; + desktopFileAttributes = { + name = "lutris-wrapped"; + icon = "lutris"; + desktopName = "Lutris (wrapped)"; + }; + enableDesktopFile = true; + }; + + discord-wrapped = let + discordPkg = prev.discord; + in wrapInSandbox { + name = "discord-wrapped"; + launchScriptName = "discord"; + binary = "${discordPkg}/bin/discord"; + mountInHome = [ ".config/discord" ".config/BetterDiscord" ]; + additionalBlacklist = [ "/mnt" ]; + chdirTo = "/home/$USER"; + + preDesktopFilePhase = '' + mkdir -p $out/share/icons + cp -L ${discordPkg}/share/pixmaps/discord.png $out/share/icons/discord.png + ''; + desktopFileAttributes = { + name = "discord-wrapped"; + #exec = "discord"; + icon = "discord"; + desktopName = "Discord (wrapped)"; + genericName = "Instant Messenger"; + #categories = [ "Network" "InstantMessaging" ]; + }; + enableDesktopFile = true; + }; + + vortex-wrapped = let + # The path to the wine prefix Vortex is installed in + prefixPath = "/mnt/Storage/Games/NewVortex"; + + vortexStartScript = prev.writeShellScriptBin "start-vortex.sh" '' + WINEARCH=win64 \ + WINEPREFIX=${prefixPath} \ + ${prev.wineWowPackages.staging}/bin/wine "C:\Program Files\Black Tree Gaming Ltd\Vortex\Vortex.exe" + ''; + vortexIcon = builtins.fetchurl { + url = "https://www.nexusmods.com/bootstrap/images/vortex/vortex-logomark.svg"; + sha256 = "0237wbbyvgapmmjsq5xab0izzaciqjx1si163r75wa2g7xvz4s22"; + }; + in wrapInSandbox { + name = "vortex-wrapped"; + launchScriptName = "vortex"; + binary = "${vortexStartScript}/bin/start-vortex.sh"; + + chdirTo = "/mnt/Storage/Games/"; + additionalBlacklist = [ "/mnt" ]; + additionalMounts = [ + # Wine prefix + "/mnt/Storage/Games/NewVortex" + # Mod cache + "/mnt/Storage/Vortex Mods" + # Only access Skyrim: SE + "/mnt/Storage/Games/SteamLibrary/SteamLibrary/steamapps/common/Skyrim\ Special\ Edition/" + ]; + mountInHome = [ + # Mod downloads + "Downloads/Skyrim Mods" + ]; + + preDesktopFilePhase = '' + mkdir -p $out/share/icons + cp ${vortexIcon} $out/share/icons/vortex.svg + ''; + desktopFileAttributes = { + name = "vortex-wrapped"; + icon = "vortex"; + desktopName = "Vortex Mod Manager (wrapped)"; + }; + enableDesktopFile = true; }; } diff --git a/packages/default.nix b/packages/default.nix index f00cf45..19e662c 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -66,4 +66,6 @@ in { discord-app = prev.callPackage applications/networking/instant-messengers/discord-app { chromium = prev.ungoogled-chromium; }; + + zoom-vm = prev.callPackage scripts/zoom-vm {}; } diff --git a/packages/scripts/zoom-vm/default.nix b/packages/scripts/zoom-vm/default.nix new file mode 100644 index 0000000..7e90894 --- /dev/null +++ b/packages/scripts/zoom-vm/default.nix @@ -0,0 +1,46 @@ +{ + stdenv, lib +, makeDesktopItem, writeShellScriptBin +, virt-manager, libvirt, notify-desktop +}: + +let + zoomVmScript = writeShellScriptBin "start-zoom-vm.sh" '' + ${notify-desktop}/bin/notify-desktop "Zoom VM" "Starting VM..." + output=$(/run/wrappers/bin/pkexec virsh start zoom 2>&1) + if [[ ! "$?" = "0" ]]; then + ${notify-desktop}/bin/notify-desktop -u critical "Zoom VM" "$output" + exit 1 + fi + + ${virt-manager}/bin/virt-manager --connect qemu:///system --show-domain-console zoom + ''; + zoomIcon = builtins.fetchurl { + url = "https://dl.flathub.org/repo/appstream/x86_64/icons/128x128/us.zoom.Zoom.png"; + sha256 = "0d94vhjna2196s4wnq0vkga345ad5lpb7b5qp6cymwf1hnpg9lyh"; + }; +in stdenv.mkDerivation rec { + pname = "zoom-vm"; + version = "1.0.0"; + + unpackPhase = ":"; + dontBuild = true; + + desktopItem = makeDesktopItem { + name = "zoom-vm"; + desktopName = "Zoom (VM)"; + exec = "start-zoom-vm"; + icon = "us.zoom.Zoom"; + }; + + installPhase = '' + # Install the script + mkdir -p $out/bin + cp ${zoomVmScript}/bin/start-zoom-vm.sh $out/bin/start-zoom-vm + + # Install the desktop file + mkdir -p $out/share/icons + cp ${zoomIcon} $out/share/icons/us.zoom.Zoom.png + cp -r ${desktopItem}/share/applications $out/share + ''; +}