diff --git a/modules/home-manager/default.nix b/modules/home-manager/default.nix index 65e4f6f..7de4750 100644 --- a/modules/home-manager/default.nix +++ b/modules/home-manager/default.nix @@ -1,2 +1,9 @@ -sLib: { +sLib: +let + mkGeckoBrowser = import ./gecko-browser/make-module.nix sLib; +in +{ + firefox = mkGeckoBrowser "firefox"; + floorp = mkGeckoBrowser "floorp"; + librewolf = mkGeckoBrowser "librewolf"; } diff --git a/modules/home-manager/gecko-browser/gecko-lib/bookmarks.nix b/modules/home-manager/gecko-browser/gecko-lib/bookmarks.nix new file mode 100644 index 0000000..84bb8b3 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/bookmarks.nix @@ -0,0 +1,104 @@ +{ + pkgs, + lib, + info, + ... +}: + +let + ## BookmarkConfig -> BookmarkJSON + updateBookmarkJSON = + data: + builtins.removeAttrs + ( + data + // ( + if (data ? children) then + { + type = "text/x-moz-place-container"; + children = builtins.map updateBookmarkJSON data.children; + } + else + { + type = "text/x-moz-place"; + } + ) + // lib.optionalAttrs (data ? addTime) { dateAdded = data.addTime; } + // lib.optionalAttrs (data ? modTime) { lastModified = data.modTime; } + ) + [ + "addTime" + "modTime" + ]; +in +{ + options = { + bookmarks = lib.mkOption { + type = lib.types.submodule { + options = { + menu = lib.mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.anything); + default = [ ]; + description = "Bookmarks to be placed in the menu."; + }; + toolbar = lib.mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.anything); + default = [ ]; + description = "Bookmarks to be placed in the toolbar"; + }; + unfiled = lib.mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.anything); + default = [ ]; + description = "Bookmarks to be placed in the unsorted bookmarks folder."; + }; + mobile = lib.mkOption { + type = lib.types.listOf (lib.types.attrsOf lib.types.anything); + default = [ ]; + description = "Bookmarks to be placed in the mobile bookmarks folder. This folder is not visible on PC, but the bookmarks can still be searched for."; + }; + }; + }; + default = { }; + description = "Declarative bookmarks for this profile."; + }; + }; + + files = + { + name, + path, + bookmarks, + ... + }: + [ + ({ + name = "${info.configPath}/${path}/bookmarkbackups/bookmarks-1970-01-01.json"; + value.text = builtins.toJSON (updateBookmarkJSON { + title = ""; + root = "placesRoot"; + children = [ + { + title = "menu"; + root = "bookmarksMenuFolder"; + children = bookmarks.menu; + } + { + title = "toolbar"; + root = "toolbarFolder"; + children = bookmarks.toolbar; + } + { + title = "unfiled"; + root = "unfiledBookmarksFolder"; + children = bookmarks.unfiled; + } + { + title = "mobile"; + root = "mobileFolder"; + children = bookmarks.mobile; + } + ]; + }); + }) + ]; +} diff --git a/modules/home-manager/gecko-browser/gecko-lib/default.nix b/modules/home-manager/gecko-browser/gecko-lib/default.nix new file mode 100644 index 0000000..4aafb10 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/default.nix @@ -0,0 +1,18 @@ +args: + +let + args' = args // { + inherit geckoLib; + }; + + geckoLib = { + bookmarks = import ./bookmarks.nix args'; + extensions = import ./extensions.nix args'; + policies = import ./policies.nix args'; + prefs = import ./prefs.nix args'; + profiles = import ./profiles.nix args'; + search = import ./search.nix args'; + site-settings = import ./site-settings.nix args'; + }; +in +geckoLib diff --git a/modules/home-manager/gecko-browser/gecko-lib/extensions.nix b/modules/home-manager/gecko-browser/gecko-lib/extensions.nix new file mode 100644 index 0000000..8e32fe6 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/extensions.nix @@ -0,0 +1,157 @@ +{ + pkgs, + lib, + info, + ... +}: + +let + submodule = + { name, ... }: + { + options = { + packages = lib.mkOption { + type = + 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 = + 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 = + 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 { }; + }; +} diff --git a/modules/home-manager/gecko-browser/gecko-lib/policies.nix b/modules/home-manager/gecko-browser/gecko-lib/policies.nix new file mode 100644 index 0000000..939d965 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/policies.nix @@ -0,0 +1,32 @@ +{ pkgs, lib, ... }: + +let + type = + let + jsonFormat = pkgs.formats.json { }; + in + jsonFormat.type; +in +{ + inherit type; + + options = { + policies = lib.mkOption { + inherit type; + default = { }; + description = "[See list of policies](https://mozilla.github.io/policy-templates/)."; + example = { + DefaultDownloadDirectory = "\${home}/Downloads"; + BlockAboutConfig = true; + ExtensionSettings = { + "uBlock0@raymondhill.net" = { + install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"; + installation_mode = "force_installed"; + default_area = "menupanel"; + private_browsing = true; + }; + }; + }; + }; + }; +} diff --git a/modules/home-manager/gecko-browser/gecko-lib/prefs.nix b/modules/home-manager/gecko-browser/gecko-lib/prefs.nix new file mode 100644 index 0000000..4980cde --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/prefs.nix @@ -0,0 +1,49 @@ +{ + pkgs, + lib, + info, + ... +}: + +let + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.int + lib.types.bool + lib.types.str + ] + ); +in +{ + inherit type; + + options = { + prefs = lib.mkOption { + inherit type; + default = { }; + description = "User preferences."; + }; + + lockPref = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "When adding user preferences, determine whether to use lockPref over user_pref"; + }; + }; + + postprocess = + { lockPref, prefs, ... }: + { + prefs = + builtins.concatStringsSep "\n" + <| (x: [ "// Generated by ${info.name} module" ] ++ x) + <| lib.mapAttrsToList ( + let + prefFunc = if lockPref then "lockPref" else "user_pref"; + in + key: val: ''${prefFunc}("${key}", ${builtins.toJSON val});'' + ) + <| prefs; + }; +} diff --git a/modules/home-manager/gecko-browser/gecko-lib/profiles.nix b/modules/home-manager/gecko-browser/gecko-lib/profiles.nix new file mode 100644 index 0000000..7722c57 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/profiles.nix @@ -0,0 +1,175 @@ +{ + lib, + pkgs, + sLib, + info, + geckoLib, + ... +}@args: + +let + # (a -> b -> c) -> [a] -> [b] -> [c] + mapProduct = + f: xs: ys: + builtins.concatMap (y: builtins.concatMap (x: f x y) xs) ys; + + # (Lib -> Profile -> c) -> ProfileSettings -> [c] + mapLibAndProfile = f: x: mapProduct f allLibs <| builtins.attrValues <| x; + + allLibs = builtins.attrValues <| lib.filterAttrs (k: _: k != "profiles") <| geckoLib; + allLibs' = allLibs ++ [ geckoLib.profiles ]; + + submodule = + { config, name, ... }: + { + options = { + id = lib.mkOption { + type = lib.types.ints.unsigned; + default = 0; + description = "Profile ID"; + }; + + name = lib.mkOption { + type = lib.types.str; + default = name; + description = "Profile name"; + }; + + path = lib.mkOption { + type = lib.types.str; + default = name; + description = "Profile path"; + }; + + isDefault = lib.mkOption { + type = lib.types.bool; + default = config.id == 0; + defaultText = "config.id == 0"; + description = "Is profile the default?"; + }; + + languagePacks = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "en-GB" + "de" + ]; + description = "The language packs to install."; + }; + } + // builtins.foldl' (x: y: x // y.options) { } allLibs; + }; + + writeProfilesIni = configPath: profileSettings: { + name = "${configPath}/profiles.ini"; + value.text = + lib.generators.toINI { } + <| + lib.mapAttrs' ( + _: v: + lib.nameValuePair "Profile${builtins.toString v.id}" { + Name = v.name; + Path = v.path; + IsRelative = 1; + Default = if v.isDefault then 1 else 0; + } + ) profileSettings + // { + General = { + StartWithLastProfile = 1; + Version = 2; + }; + }; + }; +in +{ + options = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule submodule); + default = { }; + description = "Sets browser settings on a per-profile basis."; + }; + + getDefaultProfileName = + profileSettings: + builtins.head + <| builtins.filter (x: profileSettings.${x}.isDefault) + <| builtins.attrNames + <| profileSettings; + + process = + { languagePacks, ... }: + let + extractVersion = + (x: if x == [ ] then info.base.version else builtins.head x) + <| builtins.match "([^-]*)-.*" + <| info.base.version; + in + { + policies.ExtensionSettings = + builtins.listToAttrs + <| builtins.map (lang: { + name = "langpack-${lang}@firefox.mozilla.org"; + value = { + installation_mode = "force_installed"; + install_url = "https://releases.mozilla.org/pub/firefox/releases/${extractVersion}/linux-x86_64/xpi/${lang}.xpi"; + }; + }) + <| languagePacks; + }; + + assertions = profiles: [ + { + assertion = profiles != { }; + message = "No profiles are defined."; + } + { + assertion = + (x: x == 1) + <| builtins.length + <| builtins.filter (x: profiles.${x}.isDefault) + <| builtins.attrNames + <| profiles; + message = "One, and only one, profile must be marked as 'isDefault'. If you have not set this option on any profile, then one and only one profile must have id '0'."; + } + ]; + + warnings = _: [ ]; + + evaluateAllProfiles = + profiles: + let + processLevel = + level: userConfig: + sLib.recursiveMergeAll + <| (x: [ userConfig ] ++ x) + <| builtins.map (myLib: (myLib.${level} or (_: { })) userConfig) + <| allLibs'; + + # Internal libraries can create their own configuration that needs to be merged + # into the user configuration (the user's takes priority). Generate that config. + finalConfig = builtins.mapAttrs ( + _: v: processLevel "postprocess" <| processLevel "process" <| processLevel "preprocess" <| v + ) profiles; + in + { + assertions = + mapLibAndProfile (x: x.assertions or (_: [ ])) finalConfig + ++ geckoLib.profiles.assertions finalConfig; + + warnings = + mapLibAndProfile (x: x.warnings or (_: [ ])) finalConfig ++ geckoLib.profiles.warnings finalConfig; + + profiles = lib.mapAttrs (_: v: { + policies = v.policies; + prefs = v.prefs; + files = builtins.concatMap (x: (x.files or (_: [ ])) v) allLibs'; + extFiles = builtins.concatMap (x: (x.extFiles or (_: [ ])) v) allLibs'; + }) finalConfig; + + files = [ + (writeProfilesIni info.configPath finalConfig) + ] + ++ mapLibAndProfile (x: x.files or (_: [ ])) finalConfig; + }; +} diff --git a/modules/home-manager/gecko-browser/gecko-lib/search.nix b/modules/home-manager/gecko-browser/gecko-lib/search.nix new file mode 100644 index 0000000..50292d6 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/search.nix @@ -0,0 +1,118 @@ +{ lib, ... }: + +let + submodule = + { config, ... }: + { + options = { + default = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "ddg"; + description = "The default search engine used in the address bar and search bar."; + }; + + engines = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule engineSubmodule); + default = { }; + }; + }; + }; + + engineSubmodule = + { name, ... }: + { + options = { + name = lib.mkOption { + type = lib.types.str; + default = name; + }; + + searchUri = { + base = lib.mkOption { + type = lib.types.strMatching "[a-z]+://[-A-Za-z0-9.]+(:[0-9]+)?(/[^?]*)?"; + }; + + params = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + key = lib.mkOption { type = lib.types.str; }; + value = lib.mkOption { type = lib.types.str; }; + }; + } + ); + default = [ ]; + }; + }; + + method = lib.mkOption { + type = lib.types.enum [ + "GET" + "POST" + ]; + default = "GET"; + }; + + alias = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + + icon = lib.mkOption { + type = lib.types.nullOr ( + lib.types.oneOf [ + lib.types.package + lib.types.str + ] + ); + default = null; + }; + }; + }; + + buildPolicyFromEngine = + { + name, + searchUri, + method, + alias, + icon, + }: + let + params = + builtins.concatStringsSep "&" <| builtins.map (x: "${x.key}=${x.value}") <| searchUri.params; + in + { + Name = name; + URLTemplate = + if method == "POST" then + searchUri.base + else if params == "" then + searchUri.base + else + "${searchUri.base}?${params}"; + Method = method; + ${if method == "POST" then "PostData" else null} = params; + ${if icon != null then "IconURL" else null} = "file://${builtins.toString icon}"; + ${if alias != null then "Alias" else null} = alias; + }; +in +{ + options = { + search = lib.mkOption { + type = lib.types.submodule submodule; + default = { }; + description = "Declarative search engine configuration."; + }; + }; + + process = + { search, ... }: + { + policies.SearchEngines = { + ${if search.default == null then null else "Default"} = search.default; + Add = lib.mapAttrsToList (_: buildPolicyFromEngine) search.engines; + }; + }; +} diff --git a/modules/home-manager/gecko-browser/gecko-lib/site-settings.nix b/modules/home-manager/gecko-browser/gecko-lib/site-settings.nix new file mode 100644 index 0000000..b9ff736 --- /dev/null +++ b/modules/home-manager/gecko-browser/gecko-lib/site-settings.nix @@ -0,0 +1,210 @@ +{ + lib, + sLib, + info, + ... +}: + +let + submodule = isDefault: { + options = { + https-only = lib.mkOption { + type = lib.types.bool; + default = false; # if isDefault then false else config.default.https-only; + example = true; + description = "Whether ${ + if isDefault then "all domains" else "this domain" + } should be accessed over https only."; + }; + + cookies = lib.mkOption { + type = lib.types.enum ( + [ + "forever" + "session" + "never" + true + false + ] + ++ lib.optional (!isDefault) null + ); + default = if isDefault then "forever" else null; + example = "session"; + description = '' + Determines how cookies are handled on this domain: + forever: Cookies are permitted + session: Cookies are permitted, but they are deleted when the session ends + never: Cookies are denied + true: Equivalent to "session" + false: Equivalent to "never" + ${if isDefault then "" else "null: Use the default settings"} + ''; + }; + + ${if isDefault then null else "ubo"} = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = "Whether ublock-origin should be enabled on this domain."; + }; + + scripts = lib.mkOption { + type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool; + default = if isDefault then true else null; + example = false; + description = "Whether to allow JavaScript to run on websites.${ + if isDefault then "" else " If null, use the default settings." + }"; + }; + + fonts = lib.mkOption { + type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool; + default = if isDefault then true else null; + example = false; + description = "Whether to allow websites to use remote fonts."; + }; + + popups = lib.mkOption { + type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool; + default = if isDefault then true else null; + example = false; + description = "Whether to allow popups."; + }; + + largeMedia = lib.mkOption { + type = (if isDefault then (x: x) else lib.types.nullOr) lib.types.bool; + default = if isDefault then true else null; + example = false; + description = "Whether to allow large media."; + }; + + subdomain = { + allow = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "google.com" ]; + description = "List of subdomains that ${ + if isDefault then "any" else "this" + } domain can load resources from."; + }; + block = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "google.com" ]; + description = "List of subdomains that ${ + if isDefault then "any" else "this" + } domain cannot load resources from."; + }; + noop = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "google.com" ]; + description = "List of subdomains that ${ + if isDefault then "any" else "this" + } domain can load resources from, if a static filter does not override this rule."; + }; + }; + + ${if isDefault then null else "filters"} = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "#irritating-popup" ]; + description = "List of ublock filters to apply to this domain."; + }; + }; + }; +in +{ + options = { + per-site = { + default = lib.mkOption { + type = lib.types.submodule (submodule true); + default = { }; + description = "Sets options for all websites."; + }; + + site = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule (submodule false)); + default = { }; + description = "Sets options on a per-website basis."; + }; + }; + }; + + preprocess = + { per-site, ... }: + let + inherit (per-site) default site; + + behavior = + if + builtins.elem default.cookies [ + "forever" + "session" + true + ] + then + "accept" + else + "reject"; + + filterCookies = + x: + builtins.concatLists + <| lib.mapAttrsToList ( + k: v: + if builtins.elem v.cookies x then + [ "https://${k}/" ] ++ lib.optional (!v.https-only) "http://${k}/" + else + [ ] + ) + <| site; + + makeUboConf = domain: config: { + adminSettings = { + whitelist = lib.optional (!(config.ubo or true)) domain; + hostnameSwitchesString = + lib.optional (config.scripts != null) "no-scripting: ${domain} ${builtins.toJSON (!config.scripts)}" + ++ + lib.optional (config.fonts != null) + "no-remote-fonts: ${domain} ${builtins.toJSON (!config.fonts)}" + ++ lib.optional (config.popups != null) "no-popups: ${domain} ${builtins.toJSON (!config.popups)}" + ++ + lib.optional (config.largeMedia != null) + "no-large-media: ${domain} ${builtins.toJSON (!config.largeMedia)}"; + dynamicFilteringString = + builtins.map (x: "${domain} ${x} * allow") config.subdomain.allow + ++ builtins.map (x: "${domain} ${x} * block") config.subdomain.block + ++ builtins.map (x: "${domain} ${x} * noop") config.subdomain.noop; + userFilters = builtins.map (x: "${domain}##${x}") (config.filters or [ ]); + }; + # toOverwrite = { + # trustedSiteDirectives = lib.optional (!(config.ubo or true)) domain; + # filters = builtins.map (x: "${domain}##${x}") (config.filters or []); + # }; + }; + in + { + policies = { + Cookies = { + Allow = filterCookies [ "forever" ]; + AllowSession = filterCookies [ + "session" + true + ]; + Block = filterCookies [ + "never" + false + ]; + Behavior = behavior; + BehaviorPrivateBrowsing = behavior; + }; + SanitizeOnShutdown.Cookies = builtins.elem default.cookies [ + "session" + true + ]; + }; + extensions.settings."uBlock0@raymondhill.net" = + sLib.recursiveMergeAll <| [ (makeUboConf "*" default) ] ++ lib.mapAttrsToList makeUboConf site; + }; +} diff --git a/modules/home-manager/gecko-browser/make-module.nix b/modules/home-manager/gecko-browser/make-module.nix new file mode 100644 index 0000000..f628a87 --- /dev/null +++ b/modules/home-manager/gecko-browser/make-module.nix @@ -0,0 +1,91 @@ +sLib: fork: +{ + config, + lib, + pkgs, + ... +}: + +let + info = + { + "firefox" = { + name = "firefox"; + base = pkgs.firefox-unwrapped; + binName = "firefox"; + configPath = ".mozilla/firefox"; + }; + "librewolf" = { + name = "librewolf"; + base = pkgs.librewolf-unwrapped; + binName = "librewolf"; + configPath = ".librewolf"; + }; + "floorp" = { + name = "floorp"; + base = pkgs.floorp-bin-unwrapped; + binName = "floorp"; # TODO: test + configPath = ".floorp"; # TODO: test + }; + } + .${fork}; + + cfg = config.hylonix.${info.name}; + geckoLib = import ./gecko-lib { + inherit + geckoLib + info + lib + pkgs + sLib + ; + }; + + finalConfig = geckoLib.profiles.evaluateAllProfiles cfg.profiles; + + buildFinal = + profile: settings: + let + pkg = ( + pkgs.wrapFirefox info.base { + extraPolicies = settings.policies; + extraPrefs = settings.prefs; + } + ); + in + { + package = pkg; + command = "${pkg}/bin/${info.binName} -P ${profile}"; + }; +in +{ + options.hylonix.${info.name} = { + enable = lib.mkEnableOption fork; + + final = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + package = lib.mkOption { type = lib.types.package; }; + command = lib.mkOption { type = lib.types.str; }; + }; + } + ); + visible = false; + readOnly = true; + description = "Resulting browser information for this profile."; + }; + + profiles = geckoLib.profiles.options; + }; # end options + + config = lib.mkIf cfg.enable { + inherit (finalConfig) assertions warnings; + + hylonix.${info.name}.final = lib.mapAttrs buildFinal finalConfig.profiles; + + home.file = builtins.listToAttrs finalConfig.files; + + home.packages = [ cfg.final.${geckoLib.profiles.getDefaultProfileName cfg.profiles}.package ]; + }; # end config +}