mirror of
https://github.com/nix-community/home-manager.git
synced 2026-06-05 21:02:51 +00:00
mkFirefoxModule: make profile extensions extensible
Restore nested option extensibility for `programs.firefox.profiles.<name>.extensions` by keeping it as a plain submodule. This removes the deprecated `programs.firefox.profiles.<name>.extensions = [ ... ]` shorthand. Add-ons must now be declared with `programs.firefox.profiles.<name>.extensions.packages`, while declarative settings remain under `extensions.settings`. Add a regression test for downstream `extensions.*` option extension and a news entry documenting the migration.
This commit is contained in:
13
modules/misc/news/2026/04/2026-04-23_18-35-00.nix
Normal file
13
modules/misc/news/2026/04/2026-04-23_18-35-00.nix
Normal file
@@ -0,0 +1,13 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
time = "2026-04-23T18:35:00+00:00";
|
||||
condition = config.programs.firefox.enable;
|
||||
message = ''
|
||||
The deprecated `programs.firefox.profiles.<name>.extensions = [ ... ]`
|
||||
shorthand has been removed.
|
||||
|
||||
Use `programs.firefox.profiles.<name>.extensions.packages = [ ... ]`
|
||||
to install add-ons, and keep declarative extension settings under
|
||||
`programs.firefox.profiles.<name>.extensions.settings`.
|
||||
'';
|
||||
}
|
||||
@@ -661,152 +661,137 @@ in
|
||||
'';
|
||||
};
|
||||
extensions = mkOption {
|
||||
type =
|
||||
types.coercedTo (types.listOf types.package)
|
||||
(packages: {
|
||||
packages = mkIf (builtins.length packages > 0) (
|
||||
lib.warn ''
|
||||
In order to support declarative extension configuration,
|
||||
extension installation has been moved from
|
||||
${moduleName}.profiles.<profile>.extensions
|
||||
to
|
||||
${moduleName}.profiles.<profile>.extensions.packages
|
||||
'' packages
|
||||
type = types.submodule {
|
||||
options = {
|
||||
packages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
with pkgs.nur.repos.rycee.firefox-addons; [
|
||||
privacy-badger
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
List of ${name} add-on packages to install for this profile.
|
||||
Some pre-packaged add-ons are 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.
|
||||
|
||||
Note that it is necessary to manually enable these extensions
|
||||
inside ${name} after the first installation.
|
||||
|
||||
To automatically enable extensions add
|
||||
`"extensions.autoDisableScopes" = 0;`
|
||||
to
|
||||
[{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
|
||||
'';
|
||||
};
|
||||
|
||||
force = mkOption {
|
||||
description = ''
|
||||
Whether to override all previous firefox settings.
|
||||
|
||||
This is required when using `settings`.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
exhaustivePermissions = mkOption {
|
||||
description = ''
|
||||
When enabled, the user must authorize requested
|
||||
permissions for all extensions from
|
||||
{option}`${moduleName}.profiles.<profile>.extensions.packages`
|
||||
in
|
||||
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
exactPermissions = mkOption {
|
||||
description = ''
|
||||
When enabled,
|
||||
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
|
||||
must specify the exact set of permissions that the
|
||||
extension will request.
|
||||
|
||||
This means that if the authorized permissions are
|
||||
broader than what the extension requests, the
|
||||
assertion will fail.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
# Example with uBlock origin's extensionID
|
||||
"uBlock0@raymondhill.net".settings = {
|
||||
selectedFilterLists = [
|
||||
"ublock-filters"
|
||||
"ublock-badware"
|
||||
"ublock-privacy"
|
||||
"ublock-unbreak"
|
||||
"ublock-quick-fixes"
|
||||
];
|
||||
};
|
||||
|
||||
# Example with Stylus' UUID-form extensionID
|
||||
"{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}".settings = {
|
||||
dbInChromeStorage = true; # required for Stylus
|
||||
}
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Attribute set of options for each extension.
|
||||
The keys of the attribute set consist of the ID of the extension
|
||||
or its UUID wrapped in curly braces.
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
options = {
|
||||
settings = mkOption {
|
||||
type = types.attrsOf jsonFormat.type;
|
||||
default = { };
|
||||
description = "Json formatted options for this extension.";
|
||||
};
|
||||
permissions = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
example = [ "activeTab" ];
|
||||
defaultText = "Any permissions";
|
||||
description = ''
|
||||
Allowed permissions for this extension. See
|
||||
<https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions>
|
||||
for a list of relevant permissions.
|
||||
'';
|
||||
};
|
||||
force = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Forcibly override any existing configuration for
|
||||
this extension.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
})
|
||||
(
|
||||
types.submodule {
|
||||
options = {
|
||||
packages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
with pkgs.nur.repos.rycee.firefox-addons; [
|
||||
privacy-badger
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
List of ${name} add-on packages to install for this profile.
|
||||
Some pre-packaged add-ons are 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.
|
||||
|
||||
Note that it is necessary to manually enable these extensions
|
||||
inside ${name} after the first installation.
|
||||
|
||||
To automatically enable extensions add
|
||||
`"extensions.autoDisableScopes" = 0;`
|
||||
to
|
||||
[{option}`${moduleName}.profiles.<profile>.settings`](#opt-${moduleName}.profiles._name_.settings)
|
||||
'';
|
||||
};
|
||||
|
||||
force = mkOption {
|
||||
description = ''
|
||||
Whether to override all previous firefox settings.
|
||||
|
||||
This is required when using `settings`.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
exhaustivePermissions = mkOption {
|
||||
description = ''
|
||||
When enabled, the user must authorize requested
|
||||
permissions for all extensions from
|
||||
{option}`${moduleName}.profiles.<profile>.extensions.packages`
|
||||
in
|
||||
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
exactPermissions = mkOption {
|
||||
description = ''
|
||||
When enabled,
|
||||
{option}`${moduleName}.profiles.<profile>.extensions.settings.<extensionID>.permissions`
|
||||
must specify the exact set of permissions that the
|
||||
extension will request.
|
||||
|
||||
This means that if the authorized permissions are
|
||||
broader than what the extension requests, the
|
||||
assertion will fail.
|
||||
'';
|
||||
default = false;
|
||||
example = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
# Example with uBlock origin's extensionID
|
||||
"uBlock0@raymondhill.net".settings = {
|
||||
selectedFilterLists = [
|
||||
"ublock-filters"
|
||||
"ublock-badware"
|
||||
"ublock-privacy"
|
||||
"ublock-unbreak"
|
||||
"ublock-quick-fixes"
|
||||
];
|
||||
};
|
||||
|
||||
# Example with Stylus' UUID-form extensionID
|
||||
"{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}".settings = {
|
||||
dbInChromeStorage = true; # required for Stylus
|
||||
}
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Attribute set of options for each extension.
|
||||
The keys of the attribute set consist of the ID of the extension
|
||||
or its UUID wrapped in curly braces.
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
options = {
|
||||
settings = mkOption {
|
||||
type = types.attrsOf jsonFormat.type;
|
||||
default = { };
|
||||
description = "Json formatted options for this extension.";
|
||||
};
|
||||
permissions = mkOption {
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
example = [ "activeTab" ];
|
||||
defaultText = "Any permissions";
|
||||
description = ''
|
||||
Allowed permissions for this extension. See
|
||||
<https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions>
|
||||
for a list of relevant permissions.
|
||||
'';
|
||||
};
|
||||
force = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Forcibly override any existing configuration for
|
||||
this extension.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
description = ''
|
||||
Submodule for installing and configuring extensions.
|
||||
|
||||
@@ -21,6 +21,7 @@ builtins.mapAttrs
|
||||
"${name}-profiles-duplicate-ids" = ./profiles/duplicate-ids.nix;
|
||||
"${name}-profiles-extensions" = ./profiles/extensions;
|
||||
"${name}-profiles-extensions-assertions" = ./profiles/extensions/assertions.nix;
|
||||
"${name}-profiles-extensions-extensible" = ./profiles/extensions/extensible.nix;
|
||||
"${name}-profiles-extensions-exhaustive" = ./profiles/extensions/exhaustive.nix;
|
||||
"${name}-profiles-extensions-exact" = ./profiles/extensions/exact.nix;
|
||||
"${name}-profiles-handlers" = ./profiles/handlers;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
modulePath:
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
homeManagerModules = import ../../../../../../modules/modules.nix {
|
||||
inherit lib pkgs;
|
||||
check = false;
|
||||
};
|
||||
extensionsOptionPath = modulePath ++ [
|
||||
"profiles"
|
||||
"default"
|
||||
"extensions"
|
||||
];
|
||||
thirdPartyOptionPath = extensionsOptionPath ++ [ "thirdParty" ];
|
||||
packagesOptionPath = extensionsOptionPath ++ [ "packages" ];
|
||||
|
||||
evalResult = builtins.tryEval (
|
||||
let
|
||||
evaluated = lib.evalModules {
|
||||
specialArgs = { inherit pkgs; };
|
||||
|
||||
modules = homeManagerModules ++ [
|
||||
{
|
||||
home = {
|
||||
username = "hm-user";
|
||||
homeDirectory = "/home/hm-user";
|
||||
stateVersion = "25.05";
|
||||
};
|
||||
}
|
||||
{
|
||||
options = lib.setAttrByPath (modulePath ++ [ "profiles" ]) (
|
||||
lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options.extensions.thirdParty = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Third-party regression test option.";
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
config = lib.setAttrByPath thirdPartyOptionPath true;
|
||||
}
|
||||
{
|
||||
config = lib.setAttrByPath packagesOptionPath [ pkgs.hello ];
|
||||
}
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
thirdParty = lib.getAttrFromPath thirdPartyOptionPath evaluated.config;
|
||||
packageCount = builtins.length (lib.getAttrFromPath packagesOptionPath evaluated.config);
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
nmt.script = ''
|
||||
test '${builtins.toJSON evalResult.success}' = 'true'
|
||||
test '${
|
||||
builtins.toJSON (if evalResult.success then evalResult.value.thirdParty else null)
|
||||
}' = 'true'
|
||||
test '${builtins.toJSON (if evalResult.success then evalResult.value.packageCount else null)}' = '1'
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user