mkFirefox: add policy to accept added extensions

This commit adds extensions added via
profiles.<name>.globalExtensions to the policy.
In consequence the extensions are added without complaint and don't need
to be enabled manually.

Co-authored-by: khaneliman <khaneliman@users.noreply.github.com>
This commit is contained in:
Mikilio 2026-03-14 11:46:02 +01:00 committed by Austin Horstman
parent 8af17160d1
commit 9f9b23ae21
4 changed files with 171 additions and 14 deletions

View file

@ -254,6 +254,54 @@ in
description = "Upstream release version used to fetch from `releases.mozilla.org`.";
};
globalExtensions = mkOption {
type = types.listOf (
types.oneOf [
types.package
(types.submodule {
options = {
package = mkOption {
type = types.package;
};
settings = mkOption {
type = types.attrsOf jsonFormat.type;
default = { };
description = "Json formatted options for this extension.";
};
};
})
]
);
default = [ ];
example = literalExpression ''
with pkgs.nur.repos.rycee.firefox-addons; [
privacy-badger
{
package = ublock-origin;
settings = {
private_browsing = true;
};
}
]
'';
description = ''
Add-on package to install under policies.
For a package to work here it needs and addonId exposed in it's passthru.
This will be included in all add-ons accessible from the Nix User Repository.
Once you have NUR installed run
```console
$ nix-env -f '<nixpkgs>' -qaP -A nur.repos.rycee.firefox-addons
```
to list the available ${name} add-ons.
Installing extensions this way will automatically enable extensions
inside ${name} after the first installation.
'';
};
languagePacks = mkOption {
type = types.listOf types.str;
default = [ ];
@ -373,7 +421,11 @@ in
profiles = mkOption {
type = types.attrsOf (
types.submodule (
{ config, name, ... }:
{
config,
name,
...
}:
let
profilePath = modulePath ++ [
"profiles"
@ -905,7 +957,10 @@ in
]
) config.extensions.packages)
++ (builtins.concatMap (
{ name, value }:
{
name,
value,
}:
let
packages = builtins.filter (pkg: (pkg.addonId or pkg.name) == name) config.extensions.packages;
in
@ -1009,6 +1064,17 @@ in
'';
}
{
assertion = builtins.all (
elem:
let
package = elem.package or elem;
in
package ? addonId
) cfg.globalExtensions;
message = "${moduleName}.globalExtensions requires each package to expose addonId in passthru.";
}
(mkNoDuplicateAssertion cfg.profiles "profile")
]
++ (lib.concatMap (profile: profile.assertions) (attrValues cfg.profiles));
@ -1078,7 +1144,6 @@ in
);
targets.darwin.defaults = (
mkIf (cfg.darwinDefaultsId != null && isDarwin) {
${cfg.darwinDefaultsId} = {
EnterprisePoliciesEnabled = true;
}
@ -1185,17 +1250,38 @@ in
NoDefaultBookmarks = lib.mkIf (builtins.any (profile: profile.bookmarks.enable) (
builtins.attrValues cfg.profiles
)) false;
ExtensionSettings = lib.mkIf (cfg.languagePacks != [ ]) (
lib.listToAttrs (
map (
lang:
lib.nameValuePair "langpack-${lang}@firefox.mozilla.org" {
installation_mode = "normal_installed";
install_url = "https://releases.mozilla.org/pub/firefox/releases/${cfg.release}/linux-x86_64/xpi/${lang}.xpi";
}
) cfg.languagePacks
)
);
ExtensionSettings = mkMerge [
(lib.mkIf (cfg.languagePacks != [ ]) (
lib.listToAttrs (
map (
lang:
lib.nameValuePair "langpack-${lang}@firefox.mozilla.org" {
installation_mode = "normal_installed";
install_url = "https://releases.mozilla.org/pub/firefox/releases/${cfg.release}/linux-x86_64/xpi/${lang}.xpi";
}
) cfg.languagePacks
)
))
(lib.mkIf (cfg.globalExtensions != [ ]) (
lib.listToAttrs (
map (
elem:
let
package = elem.package or elem;
settings = elem.settings or { };
in
lib.nameValuePair package.addonId (
{
installation_mode = "force_installed";
install_url = "file://${package.outPath}/share/mozilla/${extensionPath}/${package.addonId}.xpi";
}
// settings
)
) (builtins.filter (elem: (elem.package or elem) ? addonId) cfg.globalExtensions)
)
))
];
};
}
);

View file

@ -22,6 +22,7 @@ builtins.mapAttrs
"${name}-deprecated-native-messenger" = ./deprecated-native-messenger.nix;
"${name}-null-package" = ./null-package.nix;
"${name}-final-package" = ./final-package.nix;
"${name}-global-extensions-assertions" = ./global-extensions-assertions.nix;
"${name}-policies" = ./policies.nix;
"${name}-profiles-bookmarks" = ./profiles/bookmarks;
"${name}-profiles-bookmarks-attrset" = ./profiles/bookmarks/attrset.nix;

View file

@ -0,0 +1,25 @@
modulePath:
{ config, lib, ... }:
let
firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath;
extensionWithoutAddonId = config.lib.test.mkStubPackage {
name = "extension-without-addon-id";
};
in
{
imports = [ firefoxMockOverlay ];
config = lib.mkIf config.test.enableBig (
lib.setAttrByPath modulePath {
enable = true;
globalExtensions = [ extensionWithoutAddonId ];
}
// {
test.asserts.assertions.expected = [
"${lib.showOption modulePath}.globalExtensions requires each package to expose addonId in passthru."
];
}
);
}

View file

@ -11,6 +11,16 @@ let
firefoxMockOverlay = import ./setup-firefox-mock-overlay.nix modulePath;
darwinPath = "Applications/${cfg.darwinAppName}.app/Contents/Resources";
uBlockStubPkg = config.lib.test.mkStubPackage {
name = "ublock-origin-dummy";
extraAttrs.addonId = "uBlock0@raymondhill.net";
};
privacyBadgerStubPkg = config.lib.test.mkStubPackage {
name = "privacy-badger-dummy";
extraAttrs.addonId = "jid1-MnnxcxisBPnSXQ@jetpack";
};
in
{
imports = [ firefoxMockOverlay ];
@ -22,6 +32,16 @@ in
policies = {
BlockAboutConfig = true;
};
globalExtensions = [
uBlockStubPkg
{
package = privacyBadgerStubPkg;
settings = {
default_area = "menupanel";
private_browsing = true;
};
}
];
package = pkgs.${cfg.wrappedPackageName}.override {
extraPolicies = {
DownloadDirectory = "/foo";
@ -62,6 +82,31 @@ in
if [[ $downloadDirectory_actual_value != "\"/foo\"" ]]; then
fail "Expected '${config_file}' to set 'policies.DownloadDirectory' to \"/foo\""
fi
ublock_installation_mode="$($jq --arg extid "${uBlockStubPkg.addonId}" ".policies.ExtensionSettings[\$extid].installation_mode" ${config_file})"
if [[ $ublock_installation_mode != "\"force_installed\"" ]]; then
fail "Expected '${config_file}' to force-install ${uBlockStubPkg.addonId}"
fi
ublock_install_url="$($jq --arg extid "${uBlockStubPkg.addonId}" ".policies.ExtensionSettings[\$extid].install_url" ${config_file})"
expected_ublock_install_url="\"file://${uBlockStubPkg}/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/${uBlockStubPkg.addonId}.xpi\""
if [[ $ublock_install_url != "$expected_ublock_install_url" ]]; then
fail "Expected '${config_file}' to set install_url for ${uBlockStubPkg.addonId}"
fi
privacy_badger_private_browsing="$($jq --arg extid "${privacyBadgerStubPkg.addonId}" ".policies.ExtensionSettings[\$extid].private_browsing" ${config_file})"
if [[ $privacy_badger_private_browsing != "true" ]]; then
fail "Expected '${config_file}' to preserve custom settings for ${privacyBadgerStubPkg.addonId}"
fi
privacy_badger_default_area="$($jq --arg extid "${privacyBadgerStubPkg.addonId}" ".policies.ExtensionSettings[\$extid].default_area" ${config_file})"
if [[ $privacy_badger_default_area != "\"menupanel\"" ]]; then
fail "Expected '${config_file}' to preserve default_area for ${privacyBadgerStubPkg.addonId}"
fi
'';
}
);