13 , extraInstallCommands ? ""
16 , extraPreBwrapCmds ? ""
23 , unshareCgroup ? false
25 , dieWithParent ? true
29 assert (!args ? pname || !args ? version) -> (args ? name); # You must provide name if pname or version (preferred) is missing.
41 inherit (lib.attrsets) removeAttrs;
43 name = args.name or "${args.pname}-${args.version}";
44 executableName = args.pname or args.name;
45 # we don't know which have been supplied, and want to avoid defaulting missing attrs to null. Passed into runCommandLocal
46 nameAttrs = lib.filterAttrs (key: value: builtins.elem key [ "name" "pname" "version" ]) args;
48 buildFHSEnv = callPackage ./buildFHSEnv.nix { };
50 fhsenv = buildFHSEnv (removeAttrs args [
51 "runScript" "extraInstallCommands" "meta" "passthru" "extraPreBwrapCmds" "extraBwrapArgs" "dieWithParent"
52 "unshareUser" "unshareCgroup" "unshareUts" "unshareNet" "unsharePid" "unshareIpc" "privateTmp"
59 "nix" # mainly for nixUnstable users, but also for access to nix/netrc
98 in map (path: "/etc/${path}") files;
100 # Create this on the fly instead of linking from /nix
101 # The container might have to modify it and re-run ldconfig if there are
102 # issues running some binary with LD_LIBRARY_PATH
103 createLdConfCache = ''
104 cat > /etc/ld.so.conf <<EOF
106 /lib/x86_64-linux-gnu
109 /usr/lib/x86_64-linux-gnu
113 /usr/lib/i386-linux-gnu
115 /run/opengl-driver/lib
116 /run/opengl-driver-32/lib
118 ldconfig &> /dev/null
120 init = run: writeShellScript "${name}-init" ''
126 indentLines = str: concatLines (map (s: " " + s) (filter (s: s != "") (splitString "\n" str)));
127 bwrapCmd = { initArgs ? "" }: ''
129 ignored=(/nix /dev /proc /etc ${optionalString privateTmp "/tmp"})
134 # loop through all entries of root in the fhs environment, except its /etc.
135 for i in ${fhsenv}/*; do
137 if [[ $path == '/etc' ]]; then
139 elif [[ -L $i ]]; then
140 symlinks+=(--symlink "$(${coreutils}/bin/readlink "$i")" "$path")
143 ro_mounts+=(--ro-bind "$i" "$path")
148 # loop through the entries of /etc in the fhs environment.
149 if [[ -d ${fhsenv}/etc ]]; then
150 for i in ${fhsenv}/etc/*; do
152 # NOTE: we're binding /etc/fonts and /etc/ssl/certs from the host so we
153 # don't want to override it with a path from the FHS environment.
154 if [[ $path == '/fonts' || $path == '/ssl' ]]; then
158 symlinks+=(--symlink "$i" "/etc$path")
160 ro_mounts+=(--ro-bind "$i" "/etc$path")
162 etc_ignored+=("/etc$path")
166 # propagate /etc from the actual host if nested
167 if [[ -e /.host-etc ]]; then
168 ro_mounts+=(--ro-bind /.host-etc /.host-etc)
170 ro_mounts+=(--ro-bind /etc /.host-etc)
173 # link selected etc entries from the actual root
174 for i in ${escapeShellArgs etcBindEntries}; do
175 if [[ "''${etc_ignored[@]}" =~ "$i" ]]; then
179 symlinks+=(--symlink "/.host-etc/''${i#/etc/}" "$i")
183 declare -a auto_mounts
184 # loop through all directories in the root
186 # if it is a directory and it is not ignored
187 if [[ -d "$dir" ]] && [[ ! "''${ignored[@]}" =~ "$dir" ]]; then
188 # add it to the mount list
189 auto_mounts+=(--bind "$dir" "$dir")
194 # Always mount a tmpfs on /tmp/.X11-unix
195 # Rationale: https://github.com/flatpak/flatpak/blob/be2de97e862e5ca223da40a895e54e7bf24dbfb9/common/flatpak-run.c#L277
196 x11_args+=(--tmpfs /tmp/.X11-unix)
198 # Try to guess X socket path. This doesn't cover _everything_, but it covers some things.
199 if [[ "$DISPLAY" == :* ]]; then
200 display_nr=''${DISPLAY#?}
201 local_socket=/tmp/.X11-unix/X$display_nr
202 x11_args+=(--ro-bind-try "$local_socket" "$local_socket")
205 ${optionalString privateTmp ''
206 # sddm places XAUTHORITY in /tmp
207 if [[ "$XAUTHORITY" == /tmp/* ]]; then
208 x11_args+=(--ro-bind-try "$XAUTHORITY" "$XAUTHORITY")
211 # dbus-run-session puts the socket in /tmp
212 IFS=";" read -ra addrs <<<"$DBUS_SESSION_BUS_ADDRESS"
213 for addr in "''${addrs[@]}"; do
214 [[ "$addr" == unix:* ]] || continue
215 IFS="," read -ra parts <<<"''${addr#unix:}"
216 for part in "''${parts[@]}"; do
217 printf -v part '%s' "''${part//\\/\\\\}"
218 printf -v part '%b' "''${part//%/\\x}"
219 [[ "$part" == path=/tmp/* ]] || continue
220 x11_args+=(--ro-bind-try "''${part#path=}" "''${part#path=}")
226 ${bubblewrap}/bin/bwrap
230 ${optionalString unshareUser "--unshare-user"}
231 ${optionalString unshareIpc "--unshare-ipc"}
232 ${optionalString unsharePid "--unshare-pid"}
233 ${optionalString unshareNet "--unshare-net"}
234 ${optionalString unshareUts "--unshare-uts"}
235 ${optionalString unshareCgroup "--unshare-cgroup"}
236 ${optionalString dieWithParent "--die-with-parent"}
238 ${optionalString privateTmp "--tmpfs /tmp"}
239 # Our glibc will look for the cache in its own path in `/nix/store`.
240 # As such, we need a cache to exist there, because pressure-vessel
241 # depends on the existence of an ld cache. However, adding one
242 # globally proved to be a bad idea (see #100655), the solution we
243 # settled on being mounting one via bwrap.
244 # Also, the cache needs to go to both 32 and 64 bit glibcs, for games
245 # of both architectures to work.
246 --tmpfs ${glibc}/etc \
248 --symlink /etc/ld.so.conf ${glibc}/etc/ld.so.conf \
249 --symlink /etc/ld.so.cache ${glibc}/etc/ld.so.cache \
250 --ro-bind ${glibc}/etc/rpc ${glibc}/etc/rpc \
251 --remount-ro ${glibc}/etc \
252 '' + optionalString fhsenv.isMultiBuild (indentLines ''
253 --tmpfs ${pkgsi686Linux.glibc}/etc \
254 --symlink /etc/ld.so.conf ${pkgsi686Linux.glibc}/etc/ld.so.conf \
255 --symlink /etc/ld.so.cache ${pkgsi686Linux.glibc}/etc/ld.so.cache \
256 --ro-bind ${pkgsi686Linux.glibc}/etc/rpc ${pkgsi686Linux.glibc}/etc/rpc \
257 --remount-ro ${pkgsi686Linux.glibc}/etc \
261 "''${auto_mounts[@]}"
263 ${concatStringsSep "\n " extraBwrapArgs}
264 ${init runScript} ${initArgs}
269 bin = writeShellScript "${name}-bwrap" (bwrapCmd { initArgs = ''"$@"''; });
270 in runCommandLocal name (nameAttrs // {
273 passthru = passthru // {
274 env = runCommandLocal "${name}-shell-env" {
275 shellHook = bwrapCmd {};
278 echo >&2 "*** User chroot 'env' attributes are intended for interactive nix-shell sessions, not for building! ***"
286 ln -s ${bin} $out/bin/${executableName}
288 ${extraInstallCommands}