1{
2 stdenv,
3 lib,
4 makeDesktopItem,
5 makeWrapper,
6 lndir,
7 config,
8 buildPackages,
9 jq,
10 xdg-utils,
11 writeText,
12
13 ## various stuff that can be plugged in
14 ffmpeg,
15 xorg,
16 alsa-lib,
17 libpulseaudio,
18 libcanberra-gtk3,
19 libglvnd,
20 libnotify,
21 opensc,
22 adwaita-icon-theme,
23 pipewire,
24 udev,
25 libkrb5,
26 libva,
27 libgbm,
28 cups,
29 pciutils,
30 vulkan-loader,
31 sndio,
32 libjack2,
33 speechd-minimal,
34}:
35
36## configurability of the wrapper itself
37
38browser:
39
40let
41 isDarwin = stdenv.hostPlatform.isDarwin;
42 wrapper =
43 {
44 applicationName ? browser.binaryName or (lib.getName browser), # Note: this is actually *binary* name and is different from browser.applicationName, which is *app* name!
45 pname ? applicationName,
46 version ? lib.getVersion browser,
47 nameSuffix ? "",
48 icon ? applicationName,
49 wmClass ? applicationName,
50 nativeMessagingHosts ? [ ],
51 pkcs11Modules ? [ ],
52 useGlvnd ? (!isDarwin),
53 cfg ? config.${applicationName} or { },
54
55 ## Following options are needed for extra prefs & policies
56 # For more information about anti tracking (german website)
57 # visit https://wiki.kairaven.de/open/app/firefox
58 extraPrefs ? "",
59 extraPrefsFiles ? [ ],
60 # For more information about policies visit
61 # https://mozilla.github.io/policy-templates/
62 extraPolicies ? { },
63 extraPoliciesFiles ? [ ],
64 libName ? browser.libName or applicationName, # Important for tor package or the like
65 nixExtensions ? null,
66 hasMozSystemDirPatch ? (lib.hasPrefix "firefox" pname && !lib.hasSuffix "-bin" pname),
67 }:
68
69 let
70 ffmpegSupport = browser.ffmpegSupport or false;
71 gssSupport = browser.gssSupport or false;
72 alsaSupport = browser.alsaSupport or false;
73 pipewireSupport = browser.pipewireSupport or false;
74 sndioSupport = browser.sndioSupport or false;
75 jackSupport = browser.jackSupport or false;
76 # PCSC-Lite daemon (services.pcscd) also must be enabled for firefox to access smartcards
77 smartcardSupport = cfg.smartcardSupport or false;
78
79 allNativeMessagingHosts = builtins.map lib.getBin nativeMessagingHosts;
80
81 libs =
82 lib.optionals stdenv.hostPlatform.isLinux (
83 [
84 udev
85 libva
86 libgbm
87 libnotify
88 xorg.libXScrnSaver
89 cups
90 pciutils
91 vulkan-loader
92 ]
93 ++ lib.optional (cfg.speechSynthesisSupport or true) speechd-minimal
94 )
95 ++ lib.optional pipewireSupport pipewire
96 ++ lib.optional ffmpegSupport ffmpeg
97 ++ lib.optional gssSupport libkrb5
98 ++ lib.optional useGlvnd libglvnd
99 ++ lib.optionals (cfg.enableQuakeLive or false) (
100 with xorg;
101 [
102 stdenv.cc
103 libX11
104 libXxf86dga
105 libXxf86vm
106 libXext
107 libXt
108 alsa-lib
109 zlib
110 ]
111 )
112 ++ lib.optional (config.pulseaudio or (!isDarwin)) libpulseaudio
113 ++ lib.optional alsaSupport alsa-lib
114 ++ lib.optional sndioSupport sndio
115 ++ lib.optional jackSupport libjack2
116 ++ lib.optional smartcardSupport opensc
117 ++ pkcs11Modules
118 ++ lib.optionals (!isDarwin) gtk_modules;
119 gtk_modules = [ libcanberra-gtk3 ];
120
121 # Darwin does not rename bundled binaries
122 launcherName = "${applicationName}${lib.optionalString (!isDarwin) nameSuffix}";
123
124 #########################
125 # #
126 # EXTRA PREF CHANGES #
127 # #
128 #########################
129 policiesJson = writeText "policies.json" (builtins.toJSON enterprisePolicies);
130
131 usesNixExtensions = nixExtensions != null;
132
133 nameArray = builtins.map (a: a.name) (lib.optionals usesNixExtensions nixExtensions);
134
135 # Check that every extension has a unique .name attribute
136 # and an extid attribute
137 extensions =
138 if nameArray != (lib.unique nameArray) then
139 throw "Firefox addon name needs to be unique"
140 else if browser.requireSigning || !browser.allowAddonSideload then
141 throw "Nix addons are only supported with signature enforcement disabled and addon sideloading enabled (eg. LibreWolf)"
142 else
143 builtins.map (
144 a:
145 if !(builtins.hasAttr "extid" a) then
146 throw "nixExtensions has an invalid entry. Missing extid attribute. Please use fetchFirefoxAddon"
147 else
148 a
149 ) (lib.optionals usesNixExtensions nixExtensions);
150
151 enterprisePolicies = {
152 policies = {
153 DisableAppUpdate = true;
154 }
155 // lib.optionalAttrs usesNixExtensions {
156 ExtensionSettings = {
157 "*" = {
158 blocked_install_message = "You can't have manual extension mixed with nix extensions";
159 installation_mode = "blocked";
160 };
161 }
162 // lib.foldr (
163 e: ret:
164 ret
165 // {
166 "${e.extid}" = {
167 installation_mode = "allowed";
168 };
169 }
170 ) { } extensions;
171
172 Extensions = {
173 Install = lib.foldr (e: ret: ret ++ [ "${e.outPath}/${e.extid}.xpi" ]) [ ] extensions;
174 };
175 }
176 // lib.optionalAttrs smartcardSupport {
177 SecurityDevices = {
178 "OpenSC PKCS#11 Module" = "opensc-pkcs11.so";
179 };
180 }
181 // extraPolicies;
182 };
183
184 mozillaCfg = ''
185 // First line must be a comment
186
187 // Disables addon signature checking
188 // to be able to install addons that do not have an extid
189 // Security is maintained because only user whitelisted addons
190 // with a checksum can be installed
191 ${lib.optionalString usesNixExtensions ''lockPref("xpinstall.signatures.required", false);''}
192 '';
193
194 #############################
195 # #
196 # END EXTRA PREF CHANGES #
197 # #
198 #############################
199
200 in
201 stdenv.mkDerivation (finalAttrs: {
202 __structuredAttrs = true;
203 inherit pname version;
204
205 desktopItem = makeDesktopItem (
206 {
207 name = launcherName;
208 exec = "${launcherName} --name ${wmClass} %U";
209 inherit icon;
210 desktopName = browser.applicationName;
211 startupNotify = true;
212 startupWMClass = wmClass;
213 terminal = false;
214 }
215 // (
216 if libName == "thunderbird" then
217 {
218 genericName = "Email Client";
219 comment = "Read and write e-mails or RSS feeds, or manage tasks on calendars.";
220 categories = [
221 "Network"
222 "Chat"
223 "Email"
224 "Feed"
225 "GTK"
226 "News"
227 ];
228 keywords = [
229 "mail"
230 "email"
231 "e-mail"
232 "messages"
233 "rss"
234 "calendar"
235 "address book"
236 "addressbook"
237 "chat"
238 ];
239 mimeTypes = [
240 "message/rfc822"
241 "x-scheme-handler/mailto"
242 "text/calendar"
243 "text/x-vcard"
244 ];
245 actions = {
246 profile-manager-window = {
247 name = "Profile Manager";
248 exec = "${launcherName} --ProfileManager";
249 };
250 };
251 }
252 else
253 {
254 genericName = "Web Browser";
255 categories = [
256 "Network"
257 "WebBrowser"
258 ];
259 mimeTypes = [
260 "text/html"
261 "text/xml"
262 "application/xhtml+xml"
263 "application/vnd.mozilla.xul+xml"
264 "x-scheme-handler/http"
265 "x-scheme-handler/https"
266 ];
267 actions = {
268 new-window = {
269 name = "New Window";
270 exec = "${launcherName} --new-window %U";
271 };
272 new-private-window = {
273 name = "New Private Window";
274 exec = "${launcherName} --private-window %U";
275 };
276 profile-manager-window = {
277 name = "Profile Manager";
278 exec = "${launcherName} --ProfileManager";
279 };
280 };
281 }
282 )
283 );
284
285 nativeBuildInputs = [
286 makeWrapper
287 lndir
288 jq
289 ];
290 buildInputs = lib.optionals (!isDarwin) [ browser.gtk3 ];
291
292 makeWrapperArgs = [
293 "--prefix"
294 "LD_LIBRARY_PATH"
295 ":"
296 "${finalAttrs.libs}"
297
298 "--suffix"
299 "PATH"
300 ":"
301 "${placeholder "out"}/bin"
302
303 "--set"
304 "MOZ_APP_LAUNCHER"
305 launcherName
306
307 "--set"
308 "MOZ_LEGACY_PROFILES"
309 "1"
310
311 "--set"
312 "MOZ_ALLOW_DOWNGRADE"
313 "1"
314 ]
315 ++ lib.optionals (!isDarwin) [
316 "--suffix"
317 "GTK_PATH"
318 ":"
319 "${lib.concatStringsSep ":" finalAttrs.gtk_modules}"
320
321 "--suffix"
322 "XDG_DATA_DIRS"
323 ":"
324 "${adwaita-icon-theme}/share"
325
326 "--set-default"
327 "MOZ_ENABLE_WAYLAND"
328 "1"
329
330 ]
331 ++ lib.optionals (!xdg-utils.meta.broken && !isDarwin) [
332 # make xdg-open overridable at runtime
333 "--suffix"
334 "PATH"
335 ":"
336 "${lib.makeBinPath [ xdg-utils ]}"
337
338 ]
339 ++ lib.optionals hasMozSystemDirPatch [
340 "--set"
341 "MOZ_SYSTEM_DIR"
342 "${placeholder "out"}/lib/mozilla"
343
344 ]
345 ++ lib.optionals (!hasMozSystemDirPatch && allNativeMessagingHosts != [ ]) [
346 "--run"
347 ''mkdir -p ''${MOZ_HOME:-~/.mozilla}/native-messaging-hosts''
348
349 ]
350 ++ lib.optionals (!hasMozSystemDirPatch) (
351 lib.concatMap (ext: [
352 "--run"
353 ''ln -sfLt ''${MOZ_HOME:-~/.mozilla}/native-messaging-hosts ${ext}/lib/mozilla/native-messaging-hosts/*''
354 ]) allNativeMessagingHosts
355 );
356
357 buildCommand =
358 let
359 appPath = "Applications/${browser.applicationName}.app";
360 executablePrefix = if isDarwin then "${appPath}/Contents/MacOS" else "bin";
361 executablePath = "${executablePrefix}/${applicationName}";
362 finalBinaryPath = "${executablePath}" + lib.optionalString (!isDarwin) "${nameSuffix}";
363 sourceBinary = "${browser}/${executablePath}";
364 libDir = if isDarwin then "${appPath}/Contents/Resources" else "lib/${libName}";
365 prefsDir = if isDarwin then "${libDir}/browser/defaults/preferences" else "${libDir}/defaults/pref";
366 in
367 ''
368 if [ ! -x "${sourceBinary}" ]
369 then
370 echo "cannot find executable file \`${sourceBinary}'"
371 exit 1
372 fi
373
374 #########################
375 # #
376 # EXTRA PREF CHANGES #
377 # #
378 #########################
379 # Link the runtime. The executable itself has to be copied,
380 # because it will resolve paths relative to its true location.
381 # Any symbolic links have to be replicated as well.
382 cd "${browser}"
383 find . -type d -exec mkdir -p "$out"/{} \;
384
385 find . -type f \( -not -name "${applicationName}" \) -exec ln -sT "${browser}"/{} "$out"/{} \;
386
387 find . -type f \( -name "${applicationName}" -o -name "${applicationName}-bin" \) -print0 | while read -d $'\0' f; do
388 cp -P --no-preserve=mode,ownership --remove-destination "${browser}/$f" "$out/$f"
389 chmod a+rwx "$out/$f"
390 done
391
392 # fix links and absolute references
393
394 find . -type l -print0 | while read -d $'\0' l; do
395 target="$(readlink "$l")"
396 target=''${target/#"${browser}"/"$out"}
397 ln -sfT "$target" "$out/$l"
398 done
399
400 cd "$out"
401
402 ''
403 + lib.optionalString isDarwin ''
404 cd "${appPath}"
405
406 # The omni.ja files have to be copied and not symlinked, otherwise tabs crash.
407 # Maybe related to how omni.ja file is mmapped into memory. See:
408 # https://github.com/mozilla/gecko-dev/blob/b1662b447f306e6554647914090d4b73ac8e1664/modules/libjar/nsZipArchive.cpp#L204
409 #
410 # The *.dylib files are copied, otherwise some basic functionality, e.g. Crypto API, is broken.
411 for file in $(find . -name "omni.ja" -o -name "*.dylib"); do
412 rm "$file"
413 cp "${browser}/${appPath}/$file" "$file"
414 done
415
416 # Copy any embedded .app directories; plugin-container fails to start otherwise.
417 for dir in $(find . -type d -name '*.app'); do
418 rm -r "$dir"
419 cp -r "${browser}/${appPath}/$dir" "$dir"
420 done
421
422 cd ..
423
424 ''
425 + ''
426
427 # create the wrapper
428
429 executablePrefix="$out/${executablePrefix}"
430 executablePath="$out/${executablePath}"
431 oldWrapperArgs=()
432
433 if [[ -L $executablePath ]]; then
434 # Symbolic link: wrap the link's target.
435 oldExe="$(readlink -v --canonicalize-existing "$executablePath")"
436 rm "$executablePath"
437 elif wrapperCmd=$(${buildPackages.makeBinaryWrapper.extractCmd} "$executablePath"); [[ $wrapperCmd ]]; then
438 # If the executable is a binary wrapper, we need to update its target to
439 # point to $out, but we can't just edit the binary in-place because of length
440 # issues. So we extract the command used to create the wrapper and add the
441 # arguments to our wrapper.
442 parseMakeCWrapperCall() {
443 shift # makeCWrapper
444 oldExe=$1; shift
445 oldWrapperArgs=("$@")
446 }
447 eval "parseMakeCWrapperCall ''${wrapperCmd//"${browser}"/"$out"}"
448 rm "$executablePath"
449 else
450 if read -rn2 shebang < "$executablePath" && [[ $shebang == '#!' ]]; then
451 # Shell wrapper: patch in place to point to $out.
452 sed -i "s@${browser}@$out@g" "$executablePath"
453 fi
454 # Suffix the executable with -old, because -wrapped might already be used by the old wrapper.
455 oldExe="$executablePrefix/.${applicationName}"-old
456 mv "$executablePath" "$oldExe"
457 fi
458 ''
459 + lib.optionalString (!isDarwin) ''
460 appendToVar makeWrapperArgs --prefix XDG_DATA_DIRS : "$GSETTINGS_SCHEMAS_PATH"
461 ''
462 + ''
463 concatTo makeWrapperArgs oldWrapperArgs
464
465 makeWrapper "$oldExe" "$out/${finalBinaryPath}" "''${makeWrapperArgs[@]}"
466
467 #############################
468 # #
469 # END EXTRA PREF CHANGES #
470 # #
471 #############################
472 ''
473 + lib.optionalString (!isDarwin) ''
474 if [ -e "${browser}/share/icons" ]; then
475 mkdir -p "$out/share"
476 ln -s "${browser}/share/icons" "$out/share/icons"
477 else
478 for res in 16 32 48 64 128; do
479 mkdir -p "$out/share/icons/hicolor/''${res}x''${res}/apps"
480 icon=$( find "${browser}/lib/" -name "default''${res}.png" )
481 if [ -e "$icon" ]; then ln -s "$icon" \
482 "$out/share/icons/hicolor/''${res}x''${res}/apps/${icon}.png"
483 fi
484 done
485 fi
486
487 install -m 644 -D -t $out/share/applications $desktopItem/share/applications/*
488
489 ''
490 + lib.optionalString hasMozSystemDirPatch ''
491 mkdir -p $out/lib/mozilla/native-messaging-hosts
492 for ext in ${toString allNativeMessagingHosts}; do
493 ln -sLt $out/lib/mozilla/native-messaging-hosts $ext/lib/mozilla/native-messaging-hosts/*
494 done
495 ''
496 + ''
497
498 mkdir -p $out/lib/mozilla/pkcs11-modules
499 for ext in ${toString pkcs11Modules}; do
500 ln -sLt $out/lib/mozilla/pkcs11-modules $ext/lib/mozilla/pkcs11-modules/*
501 done
502
503
504 #########################
505 # #
506 # EXTRA PREF CHANGES #
507 # #
508 #########################
509 # user customization
510 libDir="$out/${libDir}"
511
512 # creating policies.json
513 mkdir -p "$libDir/distribution"
514
515 POL_PATH="$libDir/distribution/policies.json"
516 rm -f "$POL_PATH"
517 cat ${policiesJson} >> "$POL_PATH"
518
519 extraPoliciesFiles=(${builtins.toString extraPoliciesFiles})
520 for extraPoliciesFile in "''${extraPoliciesFiles[@]}"; do
521 jq -s '.[0] * .[1]' $extraPoliciesFile "$POL_PATH" > .tmp.json
522 mv .tmp.json "$POL_PATH"
523 done
524
525 # preparing for autoconfig
526 prefsDir="$out/${prefsDir}"
527 mkdir -p "$prefsDir"
528
529 echo 'pref("general.config.filename", "mozilla.cfg");' > "$prefsDir/autoconfig.js"
530 echo 'pref("general.config.obscure_value", 0);' >> "$prefsDir/autoconfig.js"
531
532 cat > "$libDir/mozilla.cfg" << EOF
533 ${mozillaCfg}
534 EOF
535
536 extraPrefsFiles=(${builtins.toString extraPrefsFiles})
537 for extraPrefsFile in "''${extraPrefsFiles[@]}"; do
538 cat "$extraPrefsFile" >> "$libDir/mozilla.cfg"
539 done
540
541 cat >> "$libDir/mozilla.cfg" << EOF
542 ${extraPrefs}
543 EOF
544
545 mkdir -p "$libDir/distribution/extensions"
546
547 #############################
548 # #
549 # END EXTRA PREF CHANGES #
550 # #
551 #############################
552 '';
553
554 preferLocalBuild = true;
555
556 libs = lib.makeLibraryPath libs + ":" + lib.makeSearchPathOutput "lib" "lib64" libs;
557 gtk_modules = map (x: x + x.gtkModule) gtk_modules;
558
559 passthru = {
560 unwrapped = browser;
561 };
562
563 disallowedRequisites = [ stdenv.cc ];
564 meta = browser.meta // {
565 inherit (browser.meta) description;
566 mainProgram = launcherName;
567 hydraPlatforms = [ ];
568 priority = (browser.meta.priority or lib.meta.defaultPriority) - 1; # prefer wrapper over the package
569 };
570 });
571in
572lib.makeOverridable wrapper