{ lib, config, pkgs, ... }: let cfg = config.soispha.programs.firefox; mkAllowedExtension = extension: lib.attrsets.nameValuePair extension.addonId { installation_mode = "normal_installed"; updates_disabled = true; inherit (extension) default_area; install_url = "file://${builtins.fetchurl { inherit (extension) url sha256 ; }}"; }; allowedExtensions = builtins.listToAttrs (builtins.map mkAllowedExtension (builtins.attrValues cfg.extensions)); mkBlockedExtension = id: lib.attrsets.nameValuePair id { install_mode = "blocked"; }; blockedExtensions = builtins.listToAttrs (builtins.map mkBlockedExtension [ # these are the default search engines "addons-search-detection@mozilla.com" "amazon@search.mozilla.org" "bing@search.mozilla.org" "ddg@search.mozilla.org" "google@search.mozilla.org" "wikipedia@search.mozilla.org" ]); mkProfile = import ./profile.nix {inherit config pkgs;}; in { options.soispha.programs.firefox = { enable = lib.mkEnableOption "firefox"; profiles = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule { options = { id = lib.mkOption { type = lib.types.int; description = "The id of this profile."; }; name = lib.mkOption { type = lib.types.str; description = "The name of this profile"; }; }; }); description = "A list of profies to create besides the default `default` profile."; default = {}; apply = value: lib.attrsets.mapAttrs' (name: value: lib.attrsets.nameValuePair name (mkProfile value)) value; }; extensions = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { options = { addonId = lib.mkOption { type = lib.types.str; example = "addon@darkreader.org"; description = "The addon id of this extension"; }; default_area = lib.mkOption { type = lib.types.enum ["navbar" "menupanel"]; example = "navbar"; description = '' Where to put this extension by default. `navbar` means into the top-left bar as icon. `menupanel` means hidden behind a “all extensions” button. ''; }; pname = lib.mkOption { type = lib.types.str; example = "darkreader"; description = "The package name of this extension"; }; sha256 = lib.mkOption { type = lib.types.str; example = "sha256:f565b2263a71626a0310380915b7aef90be8cc6fe16ea43ac1a0846efedc2e4c"; description = "The fetchurl copatible hash of this extension"; }; url = lib.mkOption { type = lib.types.str; example = "https://addons.mozilla.org/firefox/downloads/file/4439735/darkreader-4.9.103.xpi"; description = "The download url of this extension."; }; version = lib.mkOption { type = lib.types.str; example = "4.9.103"; description = "The version of this extension"; }; }; } ); default = builtins.fromJSON (builtins.readFile ./extensions.json); description = '' A list of the extensions that should be installed. You can use a tool like `generate_extensions` to generate this config. ''; }; }; config = lib.mkIf cfg.enable { programs.firefox = { enable = true; preferencesStatus = "locked"; languagePacks = ["en-CA" "de" "sv-SE"]; nativeMessagingHosts.packages = [ pkgs.tridactyl-native pkgs.keepassxc ]; # NOTE: See https://mozilla.github.io/policy-templates for documentation <2023-10-21> policies = { # NixOS manages this already DisableAppUpdate = true; DisableFirefoxAccounts = true; DisableFirefoxScreenshots = true; # KeepassXC does this for me DisableMasterPasswordCreation = true; # I use a self-hosted services for that DisablePocket = true; # I don't want to lose my data DisableProfileRefresh = true; DisableDeveloperTools = false; DisplayBookmarksToolbar = "newtab"; DisplayMenuBar = "default-off"; DNSOverHTTPS = { Enabled = true; Locked = false; }; # The concept of a "default browser" does not apply to my NixOS config DontCheckDefaultBrowser = true; ExtensionSettings = { "*" = { # Blocking the extension install here, also blocks the 'about:debugging' page # blocked_install_message = '' # You can't install a extension manually, # please specify it in your NixOS configuration # ''; installation_mode = "allowed"; }; } // allowedExtensions // blockedExtensions; RequestedLocales = config.programs.firefox.languagePacks; ExtensionUpdate = false; HardwareAcceleration = true; # KeepassXC and such things OfferToSaveLogins = false; PasswordManagerEnabled = false; PDFjs = { Enabled = true; # Don't honor documents right to be un-copy-able EnablePermissions = false; }; SearchBar = "unified"; }; # Beware, that we already set them per-profile in the home-manager config. preferences = {}; }; home-manager.users.soispha = { home.sessionVariables = { # Improve touch input and make scrolling smother MOZ_USE_XINPUT2 = "1"; # Tell Firefox to use Wayland MOZ_ENABLE_WAYLAND = 1; # Tell GTK to use portals GTK_USE_PORTAL = 1; BROWSER = "firefox"; }; programs.firefox = { enable = true; arkenfox = { enable = true; version = "133.0"; }; # We use the NixOS module to provide us a package. # HACK: Extract the package from the system-path to get a version for # arkenfox-nixos to compare to. <2025-04-02> package = lib.lists.findSingle (x: builtins.hasAttr "pname" x && x.pname == "firefox") "none" "multiple" config.environment.systemPackages; profiles = { default = mkProfile { isDefault = true; id = 0; name = "default"; }; } // cfg.profiles; }; }; }; }