biome: 1.9.2 -> 1.9.3 (#349335)
[NixPkgs.git] / pkgs / build-support / build-fhsenv-bubblewrap / default.nix
blob8bf5aebeb5166fbb1b72ae0575ce6ba003484903
1 { lib
2 , stdenv
3 , callPackage
4 , runCommandLocal
5 , writeShellScript
6 , glibc
7 , pkgsi686Linux
8 , runCommandCC
9 , coreutils
10 , bubblewrap
13 { runScript ? "bash"
14 , nativeBuildInputs ? []
15 , extraInstallCommands ? ""
16 , meta ? {}
17 , passthru ? {}
18 , extraPreBwrapCmds ? ""
19 , extraBwrapArgs ? []
20 , unshareUser ? false
21 , unshareIpc ? false
22 , unsharePid ? false
23 , unshareNet ? false
24 , unshareUts ? false
25 , unshareCgroup ? false
26 , privateTmp ? false
27 , dieWithParent ? true
28 , ...
29 } @ args:
31 assert (!args ? pname || !args ? version) -> (args ? name); # You must provide name if pname or version (preferred) is missing.
33 let
34   inherit (lib)
35     concatLines
36     concatStringsSep
37     escapeShellArgs
38     filter
39     optionalString
40     splitString
41     ;
43   inherit (lib.attrsets) removeAttrs;
45   name = args.name or "${args.pname}-${args.version}";
46   executableName = args.pname or args.name;
47   # we don't know which have been supplied, and want to avoid defaulting missing attrs to null. Passed into runCommandLocal
48   nameAttrs = lib.filterAttrs (key: value: builtins.elem key [ "name" "pname" "version" ]) args;
50   buildFHSEnv = callPackage ./buildFHSEnv.nix { };
52   fhsenv = buildFHSEnv (removeAttrs args [
53     "runScript" "extraInstallCommands" "meta" "passthru" "extraPreBwrapCmds" "extraBwrapArgs" "dieWithParent"
54     "unshareUser" "unshareCgroup" "unshareUts" "unshareNet" "unsharePid" "unshareIpc" "privateTmp"
55   ]);
57   etcBindEntries = let
58     files = [
59       # NixOS Compatibility
60       "static"
61       "nix" # mainly for nixUnstable users, but also for access to nix/netrc
62       # Shells
63       "shells"
64       "bashrc"
65       "zshenv"
66       "zshrc"
67       "zinputrc"
68       "zprofile"
69       # Users, Groups, NSS
70       "passwd"
71       "group"
72       "shadow"
73       "hosts"
74       "resolv.conf"
75       "nsswitch.conf"
76       # User profiles
77       "profiles"
78       # Sudo & Su
79       "login.defs"
80       "sudoers"
81       "sudoers.d"
82       # Time
83       "localtime"
84       "zoneinfo"
85       # Other Core Stuff
86       "machine-id"
87       "os-release"
88       # PAM
89       "pam.d"
90       # Fonts
91       "fonts"
92       # ALSA
93       "alsa"
94       "asound.conf"
95       # SSL
96       "ssl/certs"
97       "ca-certificates"
98       "pki"
99     ];
100   in map (path: "/etc/${path}") files;
102   # Here's the problem case:
103   # - we need to run bash to run the init script
104   # - LD_PRELOAD may be set to another dynamic library, requiring us to discover its dependencies
105   # - oops! ldconfig is part of the init script, and it hasn't run yet
106   # - everything explodes
107   #
108   # In particular, this happens with fhsenvs in fhsenvs, e.g. when running
109   # a wrapped game from Steam.
110   #
111   # So, instead of doing that, we build a tiny static (important!) shim
112   # that executes ldconfig in a completely clean environment to generate
113   # the initial cache, and then execs into the "real" init, which is the
114   # first time we see anything dynamically linked at all.
115   #
116   # Also, the real init is placed strategically at /init, so we don't
117   # have to recompile this every time.
118   containerInit = runCommandCC "container-init" {
119     buildInputs = [ stdenv.cc.libc.static or null ];
120   } ''
121     $CXX -static -s -o $out ${./container-init.cc}
122   '';
124   realInit = run: writeShellScript "${name}-init" ''
125     source /etc/profile
126     exec ${run} "$@"
127   '';
129   indentLines = str: concatLines (map (s: "  " + s) (filter (s: s != "") (splitString "\n" str)));
130   bwrapCmd = { initArgs ? "" }: ''
131     ignored=(/nix /dev /proc /etc ${optionalString privateTmp "/tmp"})
132     ro_mounts=()
133     symlinks=()
134     etc_ignored=()
136     ${extraPreBwrapCmds}
138     # loop through all entries of root in the fhs environment, except its /etc.
139     for i in ${fhsenv}/*; do
140       path="/''${i##*/}"
141       if [[ $path == '/etc' ]]; then
142         :
143       elif [[ -L $i ]]; then
144         symlinks+=(--symlink "$(${coreutils}/bin/readlink "$i")" "$path")
145         ignored+=("$path")
146       else
147         ro_mounts+=(--ro-bind "$i" "$path")
148         ignored+=("$path")
149       fi
150     done
152     # loop through the entries of /etc in the fhs environment.
153     if [[ -d ${fhsenv}/etc ]]; then
154       for i in ${fhsenv}/etc/*; do
155         path="/''${i##*/}"
156         # NOTE: we're binding /etc/fonts and /etc/ssl/certs from the host so we
157         # don't want to override it with a path from the FHS environment.
158         if [[ $path == '/fonts' || $path == '/ssl' ]]; then
159           continue
160         fi
161         if [[ -L $i ]]; then
162           symlinks+=(--symlink "$i" "/etc$path")
163         else
164           ro_mounts+=(--ro-bind "$i" "/etc$path")
165         fi
166         etc_ignored+=("/etc$path")
167       done
168     fi
170     # propagate /etc from the actual host if nested
171     if [[ -e /.host-etc ]]; then
172       ro_mounts+=(--ro-bind /.host-etc /.host-etc)
173     else
174       ro_mounts+=(--ro-bind /etc /.host-etc)
175     fi
177     # link selected etc entries from the actual root
178     for i in ${escapeShellArgs etcBindEntries}; do
179       if [[ "''${etc_ignored[@]}" =~ "$i" ]]; then
180         continue
181       fi
182       if [[ -e $i ]]; then
183         symlinks+=(--symlink "/.host-etc/''${i#/etc/}" "$i")
184       fi
185     done
187     declare -a auto_mounts
188     # loop through all directories in the root
189     for dir in /*; do
190       # if it is a directory and it is not ignored
191       if [[ -d "$dir" ]] && [[ ! "''${ignored[@]}" =~ "$dir" ]]; then
192         # add it to the mount list
193         auto_mounts+=(--bind "$dir" "$dir")
194       fi
195     done
197     declare -a x11_args
198     # Always mount a tmpfs on /tmp/.X11-unix
199     # Rationale: https://github.com/flatpak/flatpak/blob/be2de97e862e5ca223da40a895e54e7bf24dbfb9/common/flatpak-run.c#L277
200     x11_args+=(--tmpfs /tmp/.X11-unix)
202     # Try to guess X socket path. This doesn't cover _everything_, but it covers some things.
203     if [[ "$DISPLAY" == *:* ]]; then
204       # recover display number from $DISPLAY formatted [host]:num[.screen]
205       display_nr=''${DISPLAY/#*:} # strip host
206       display_nr=''${display_nr/%.*} # strip screen
207       local_socket=/tmp/.X11-unix/X$display_nr
208       x11_args+=(--ro-bind-try "$local_socket" "$local_socket")
209     fi
211     ${optionalString privateTmp ''
212     # sddm places XAUTHORITY in /tmp
213     if [[ "$XAUTHORITY" == /tmp/* ]]; then
214       x11_args+=(--ro-bind-try "$XAUTHORITY" "$XAUTHORITY")
215     fi
217     # dbus-run-session puts the socket in /tmp
218     IFS=";" read -ra addrs <<<"$DBUS_SESSION_BUS_ADDRESS"
219     for addr in "''${addrs[@]}"; do
220       [[ "$addr" == unix:* ]] || continue
221       IFS="," read -ra parts <<<"''${addr#unix:}"
222       for part in "''${parts[@]}"; do
223         printf -v part '%s' "''${part//\\/\\\\}"
224         printf -v part '%b' "''${part//%/\\x}"
225         [[ "$part" == path=/tmp/* ]] || continue
226         x11_args+=(--ro-bind-try "''${part#path=}" "''${part#path=}")
227       done
228     done
229     ''}
231     cmd=(
232       ${bubblewrap}/bin/bwrap
233       --dev-bind /dev /dev
234       --proc /proc
235       --chdir "$(pwd)"
236       ${optionalString unshareUser "--unshare-user"}
237       ${optionalString unshareIpc "--unshare-ipc"}
238       ${optionalString unsharePid "--unshare-pid"}
239       ${optionalString unshareNet "--unshare-net"}
240       ${optionalString unshareUts "--unshare-uts"}
241       ${optionalString unshareCgroup "--unshare-cgroup"}
242       ${optionalString dieWithParent "--die-with-parent"}
243       --ro-bind /nix /nix
244       ${optionalString privateTmp "--tmpfs /tmp"}
245       # Our glibc will look for the cache in its own path in `/nix/store`.
246       # As such, we need a cache to exist there, because pressure-vessel
247       # depends on the existence of an ld cache. However, adding one
248       # globally proved to be a bad idea (see #100655), the solution we
249       # settled on being mounting one via bwrap.
250       # Also, the cache needs to go to both 32 and 64 bit glibcs, for games
251       # of both architectures to work.
252       --tmpfs ${glibc}/etc \
253       --tmpfs /etc \
254       --symlink /etc/ld.so.conf ${glibc}/etc/ld.so.conf \
255       --symlink /etc/ld.so.cache ${glibc}/etc/ld.so.cache \
256       --ro-bind ${glibc}/etc/rpc ${glibc}/etc/rpc \
257       --remount-ro ${glibc}/etc \
258       --symlink ${realInit runScript} /init \
259   '' + optionalString fhsenv.isMultiBuild (indentLines ''
260       --tmpfs ${pkgsi686Linux.glibc}/etc \
261       --symlink /etc/ld.so.conf ${pkgsi686Linux.glibc}/etc/ld.so.conf \
262       --symlink /etc/ld.so.cache ${pkgsi686Linux.glibc}/etc/ld.so.cache \
263       --ro-bind ${pkgsi686Linux.glibc}/etc/rpc ${pkgsi686Linux.glibc}/etc/rpc \
264       --remount-ro ${pkgsi686Linux.glibc}/etc \
265   '') + ''
266       "''${ro_mounts[@]}"
267       "''${symlinks[@]}"
268       "''${auto_mounts[@]}"
269       "''${x11_args[@]}"
270       ${concatStringsSep "\n  " extraBwrapArgs}
271       ${containerInit} ${initArgs}
272     )
273     exec "''${cmd[@]}"
274   '';
276   bin = writeShellScript "${name}-bwrap" (bwrapCmd { initArgs = ''"$@"''; });
277 in runCommandLocal name (nameAttrs // {
278   inherit nativeBuildInputs meta;
280   passthru = passthru // {
281     env = runCommandLocal "${name}-shell-env" {
282       shellHook = bwrapCmd {};
283     } ''
284       echo >&2 ""
285       echo >&2 "*** User chroot 'env' attributes are intended for interactive nix-shell sessions, not for building! ***"
286       echo >&2 ""
287       exit 1
288     '';
289     inherit args fhsenv;
290   };
291 }) ''
292   mkdir -p $out/bin
293   ln -s ${bin} $out/bin/${executableName}
295   ${extraInstallCommands}