Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firefox nix addon support #91724

Merged
merged 8 commits into from Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 40 additions & 0 deletions doc/builders/packages/firefox.section.md
@@ -0,0 +1,40 @@
# Firefox

## Build wrapped Firefox with extensions and policies

The `wrapFirefox` function allows to pass policies, preferences and extension that are available to firefox. With the help of `fetchFirefoxAddon` this allows build a firefox version that already comes with addons pre-installed:

```nix
{
myFirefox = wrapFirefox firefox-unwrapped {
Qubasa marked this conversation as resolved.
Show resolved Hide resolved
extraExtensions = [
(fetchFirefoxAddon {
name = "ublock";
url = "https://addons.mozilla.org/firefox/downloads/file/3679754/ublock_origin-1.31.0-an+fx.xpi";
sha256 = "1h768ljlh3pi23l27qp961v1hd0nbj2vasgy11bmcrlqp40zgvnr";
})
];

extraPolicies = {
CaptivePortal = false;
DisableFirefoxStudies = true;
DisablePocket = true;
DisableTelemetry = true;
DisableFirefoxAccounts = true;
FirefoxHome = {
Pocket = false;
Snippets = false;
};
UserMessaging = {
ExtensionRecommendations = false;
SkipOnboarding = true;
};
};

extraPrefs = ''
// Show more ssl cert infos
lockPref("security.identityblock.show_extended_validation", true);
'';
};
}
```
1 change: 1 addition & 0 deletions doc/builders/packages/index.xml
Expand Up @@ -10,6 +10,7 @@
<xi:include href="eclipse.xml" />
<xi:include href="elm.xml" />
<xi:include href="emacs.section.xml" />
<xi:include href="firefox.section.xml" />
<xi:include href="ibus.xml" />
<xi:include href="kakoune.section.xml" />
<xi:include href="linux.section.xml" />
Expand Down
170 changes: 169 additions & 1 deletion pkgs/applications/networking/browsers/firefox/wrapper.nix
@@ -1,4 +1,5 @@
{ stdenv, lib, makeDesktopItem, makeWrapper, lndir, config
, replace, fetchurl, zip, unzip, jq

## various stuff that can be plugged in
, flashplayer, hal-flash
Expand Down Expand Up @@ -31,6 +32,16 @@ let
, forceWayland ? false
, useGlvnd ? true
, cfg ? config.${browserName} or {}

## Following options are needed for extra prefs & policies
# For more information about anti tracking (german website)
# visit https://wiki.kairaven.de/open/app/firefox
, extraPrefs ? ""
# For more information about policies visit
# https://github.com/mozilla/policy-templates#enterprisepoliciesenabled
, extraPolicies ? {}
, firefoxLibName ? "firefox" # Important for tor package or the like
, extraExtensions ? [ ]
}:

assert forceWayland -> (browser ? gtk3); # Can only use the wayland backend if gtk3 is being used
Expand Down Expand Up @@ -81,6 +92,61 @@ let
++ pkcs11Modules;
gtk_modules = [ libcanberra-gtk2 ];

#########################
# #
# EXTRA PREF CHANGES #
# #
#########################
policiesJson = builtins.toFile "policies.json"
(builtins.toJSON enterprisePolicies);

extensions = builtins.map (a:
if ! (builtins.hasAttr "extid" a) then
throw "extraExtensions has an invalid entry. Missing extid attribute. Please use fetchfirefoxaddon"
else
a
) extraExtensions;

enterprisePolicies =
{
policies = {
DisableAppUpdate = true;
} //
{
ExtensionSettings = {
"*" = {
blocked_install_message = "You can't have manual extension mixed with nix extensions";
installation_mode = "blocked";
};

} // lib.foldr (e: ret:
ret // {
"${e.extid}" = {
installation_mode = "allowed";
};
}
) {} extensions;
}
// extraPolicies;
};

mozillaCfg = builtins.toFile "mozilla.cfg" ''
// First line must be a comment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks indented incorrectly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not because toFile does not strip tabs away and this file format needs a comment without spaces at the beginning

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luis-hebendanz: toFile only takes strings, the stripping is caused by ''...''. I don't however see any tab characters involved here. Checking this quickly via nix repl results in:

nix-repl> builtins.readFile (builtins.toFile "mozilla.cfg" ''
                  // First line must be a comment
          
                  // Disables addon signature checking
                  // to be able to install addons that do not have an extid
                  // Security is maintained because only user whitelisted addons
                  // with a checksum can be installed
                  lockPref("xpinstall.signatures.required", false);
                  ...
          '')
"// First line must be a comment\n\n// Disables addon signature checking\n// to be able to install addons that do not have an extid\n// Security is maintained because only user whitelisted addons\n// with a checksum can be installed\nlockPref(\"xpinstall.signatures.required\", false);\n...\n"

nix-repl>


// Disables addon signature checking
// to be able to install addons that do not have an extid
// Security is maintained because only user whitelisted addons
// with a checksum can be installed
lockPref("xpinstall.signatures.required", false);
${extraPrefs}
'';

#############################
# #
# END EXTRA PREF CHANGES #
# #
#############################

in stdenv.mkDerivation {
inherit pname version;

Expand All @@ -106,6 +172,7 @@ let
nativeBuildInputs = [ makeWrapper lndir ];
buildInputs = lib.optional (browser ? gtk3) browser.gtk3;


buildCommand = lib.optionalString stdenv.isDarwin ''
mkdir -p $out/Applications
cp -R --no-preserve=mode,ownership ${browser}/Applications/${browserName}.app $out/Applications
Expand All @@ -117,7 +184,66 @@ let
exit 1
fi

makeWrapper "$(readlink -v --canonicalize-existing "${browser}${browser.execdir or "/bin"}/${browserName}")" \
#########################
# #
# EXTRA PREF CHANGES #
# #
#########################
# Link the runtime. The executable itself has to be copied,
# because it will resolve paths relative to its true location.
# Any symbolic links have to be replicated as well.
cd "${browser}"
find . -type d -exec mkdir -p "$out"/{} \;

find . -type f \( -not -name "${browserName}" \) -exec ln -sT "${browser}"/{} "$out"/{} \;

find . -type f -name "${browserName}" -print0 | while read -d $'\0' f; do
cp -P --no-preserve=mode,ownership "${browser}/$f" "$out/$f"
chmod a+rwx "$out/$f"
done

# fix links and absolute references
cd "${browser}"

find . -type l -print0 | while read -d $'\0' l; do
target="$(readlink "$l" | ${replace}/bin/replace-literal -es -- "${browser}" "$out")"
ln -sfT "$target" "$out/$l"
done

# This will not patch binaries, only "text" files.
# Its there for the wrapper mostly.
cd "$out"
${replace}/bin/replace-literal -esfR -- "${browser}" "$out"

# create the wrapper

executablePrefix="$out${browser.execdir or "/bin"}"
executablePath="$executablePrefix/${browserName}"

if [ ! -x "$executablePath" ]
then
echo "cannot find executable file \`${browser}${browser.execdir or "/bin"}/${browserName}'"
exit 1
fi

if [ ! -L "$executablePath" ]
then
# Careful here, the file at executablePath may already be
# a wrapper. That is why we postfix it with -old instead
# of -wrapped.
oldExe="$executablePrefix"/".${browserName}"-old
mv "$executablePath" "$oldExe"
else
oldExe="$(readlink -v --canonicalize-existing "$executablePath")"
fi

if [ ! -x "${browser}${browser.execdir or "/bin"}/${browserName}" ]
then
echo "cannot find executable file \`${browser}${browser.execdir or "/bin"}/${browserName}'"
exit 1
fi

makeWrapper "$oldExe" \
"$out${browser.execdir or "/bin"}/${browserName}${nameSuffix}" \
--suffix-each MOZ_PLUGIN_PATH ':' "$plugins" \
--suffix LD_LIBRARY_PATH ':' "$libs" \
Expand All @@ -137,6 +263,11 @@ let
--suffix XDG_DATA_DIRS : '${gnome3.adwaita-icon-theme}/share'
''
}
#############################
# #
# END EXTRA PREF CHANGES #
# #
#############################

if [ -e "${browser}/share/icons" ]; then
mkdir -p "$out/share"
Expand Down Expand Up @@ -166,6 +297,43 @@ let
# For manpages, in case the program supplies them
mkdir -p $out/nix-support
echo ${browser} > $out/nix-support/propagated-user-env-packages


#########################
# #
# EXTRA PREF CHANGES #
# #
#########################
# user customization
mkdir -p $out/lib/${firefoxLibName}

# creating policies.json
mkdir -p "$out/lib/${firefoxLibName}/distribution"

POL_PATH="$out/lib/${firefoxLibName}/distribution/policies.json"
rm -f "$POL_PATH"
cat ${policiesJson} >> "$POL_PATH"

# preparing for autoconfig
mkdir -p "$out/lib/${firefoxLibName}/defaults/pref"

cat > "$out/lib/${firefoxLibName}/defaults/pref/autoconfig.js" <<EOF
pref("general.config.filename", "mozilla.cfg");
pref("general.config.obscure_value", 0);
EOF

cat > "$out/lib/${firefoxLibName}/mozilla.cfg" < ${mozillaCfg}

mkdir -p $out/lib/${firefoxLibName}/distribution/extensions

for i in ${toString extensions}; do
ln -s -t $out/lib/${firefoxLibName}/distribution/extensions $i/*
done
#############################
# #
# END EXTRA PREF CHANGES #
# #
#############################
'';

preferLocalBuild = true;
Expand Down
37 changes: 37 additions & 0 deletions pkgs/build-support/fetchfirefoxaddon/default.nix
@@ -0,0 +1,37 @@
{stdenv, lib, coreutils, unzip, jq, zip, fetchurl,writeScript, ...}:
{ name
, url
, md5 ? ""
, sha1 ? ""
, sha256 ? ""
, sha512 ? ""
}:
stdenv.mkDerivation rec {

inherit name;
extid = "${src.outputHash}@${name}";
passthru = {
exitd=extid;
};

builder = writeScript "xpibuilder" ''
source $stdenv/setup

header "firefox addon $name into $out"

UUID="${extid}"
mkdir -p "$out/$UUID"
unzip -q ${src} -d "$out/$UUID"
NEW_MANIFEST=$(jq '. + {"applications": { "gecko": { "id": "${extid}" }}, "browser_specific_settings":{"gecko":{"id": "${extid}"}}}' "$out/$UUID/manifest.json")
echo "$NEW_MANIFEST" > "$out/$UUID/manifest.json"
cd "$out/$UUID"
zip -r -q -FS "$out/$UUID.xpi" *
rm -r "$out/$UUID"
'';
src = fetchurl {
url = url;
inherit md5 sha1 sha256 sha512;
};
nativeBuildInputs = [ coreutils unzip zip jq ];
}

2 changes: 2 additions & 0 deletions pkgs/top-level/all-packages.nix
Expand Up @@ -365,6 +365,8 @@ in

fetchhg = callPackage ../build-support/fetchhg { };

fetchFirefoxAddon = callPackage ../build-support/fetchfirefoxaddon {};

# `fetchurl' downloads a file from the network.
fetchurl = if stdenv.buildPlatform != stdenv.hostPlatform
then buildPackages.fetchurl # No need to do special overrides twice,
Expand Down