{ pkgs, lib, sLib, info, ... }: let submodule = { name, ... }: { options = { packages = lib.mkOption { type = sLib.compose [ lib.types.listOf (lib.types.either lib.types.package) (lib.types.submodule) ] { options = { name = lib.mkOption { type = lib.types.str; default = name; example = "ublock-origin"; description = "The short name of the extension. To find the short name look at the extension's download link."; }; guid = lib.mkOption { type = lib.types.str; example = "uBlock0@raymondhill.net"; description = "The guid of the extension. To find the guid go to https://addons.mozilla.org/api/v5/addons/addon/\${shortName}/"; }; }; }; default = [ ]; description = '' Extensions to install for this profile. Takes either a package or a description of an extension. * If a package is given, the extension will be preinstalled. * If a description is given then ${info.name} will download the extension on startup. ''; }; settings = lib.mkOption { type = let jsonFormat = pkgs.formats.json { }; in lib.types.attrsOf jsonFormat.type; default = { }; description = "Settings for this extension, accessible by the managed storage API. The extension must be referenced by id."; }; }; }; # (Package -> a) -> ({ name, guid } -> a) -> Extension -> a withExtension = f: g: x: if lib.isDerivation x then f x else g x; # ublock-origin wants its settings as a string, which is annoying # to merge. For convenience, you can keep ublock-origin's config # as a set and this function will turn it into a string at the # last minute. As a bonus, we can also do other magic for other # extensions if need be. magicInfo = { "uBlock0@raymondhill.net" = x: x // lib.optionalAttrs (x ? adminSettings) { adminSettings = sLib.compose [ builtins.toJSON ( y: y // lib.optionalAttrs (y ? userFilters) { userFilters = builtins.concatStringsSep "\n" y.userFilters; } ) ( y: y // lib.optionalAttrs (y ? urlFilteringString) { urlFilteringString = builtins.concatStringsSep "\n" y.urlFilteringString; } ) ( y: y // lib.optionalAttrs (y ? hostnameSwitchesString) { hostnameSwitchesString = builtins.concatStringsSep "\n" y.hostnameSwitchesString; } ) ( y: y // lib.optionalAttrs (y ? dynamicFilteringString) { dynamicFilteringString = builtins.concatStringsSep "\n" y.dynamicFilteringString; } ) ] x.adminSettings; }; }; magic = guid: magicInfo.${guid} or lib.trivial.id; in { options = { extensions = lib.mkOption { type = lib.types.submodule submodule; default = { }; # example = { # packages = [ # nur.repos.rycee.firefox-addons.ublock-origin # { name = "decentraleyes"; guid = "jid1-BoFifL9Vbdl2zQ@jetpack"; } # ]; # settings."uBlock0@raymondhill.net" = { # whitelist = [ "example.org" ]; # }; # }; description = "The extensions to install."; }; }; process = { extensions, ... }: { policies = { "3rdparty".Extensions = extensions.settings; ExtensionSettings = sLib.compose [ builtins.listToAttrs (builtins.map ( withExtension (x: { name = x.addonId; value = { installation_mode = "force_installed"; private_browsing = true; install_url = "file://${x}/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/${x.addonId}.xpi"; }; }) (x: { name = x.guid; value = { installation_mode = "force_installed"; private_browsing = true; install_url = "https://addons.mozilla.org/firefox/downloads/latest/${x.name}/latest.xpi"; }; }) )) ] extensions.packages; }; prefs."extensions.autoDisableScopes" = 0; }; postprocess = { policies, ... }: lib.optionalAttrs (policies ? "3rdparty".Extensions) { policies."3rdparty".Extensions = builtins.mapAttrs magic (policies."3rdparty".Extensions or { }); }; }