diff --git a/hosts/common/programs/firefox/addons.nix b/hosts/common/programs/firefox/addons.nix new file mode 100644 index 000000000..ffa23113b --- /dev/null +++ b/hosts/common/programs/firefox/addons.nix @@ -0,0 +1,128 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.sane.programs.firefox.config; +in +{ + sane.programs.firefox = { + config.addons = { + fxCast = { + # add a menu to cast to chromecast devices, but it doesn't seem to work very well. + # right click (or shift+rc) a video, then select "cast". + # - asciinema.org: icon appears, but glitches when clicked. + # - youtube.com: no icon appears, even when site is whitelisted. + # future: maybe better to have browser open all videos in mpv, and then use mpv for casting. + # see e.g. `ff2mpv`, `open-in-mpv` (both are packaged in nixpkgs) + package = pkgs.firefox-extensions.fx_cast; + enable = lib.mkDefault false; + }; + browserpass-extension = { + package = pkgs.firefox-extensions.browserpass-extension; + enable = lib.mkDefault true; + }; + bypass-paywalls-clean = { + package = pkgs.firefox-extensions.bypass-paywalls-clean; + enable = lib.mkDefault true; + }; + ctrl-shift-c-should-copy = { + package = pkgs.firefox-extensions.ctrl-shift-c-should-copy; + enable = lib.mkDefault false; # prefer patching firefox source code, so it works in more places + }; + ether-metamask = { + package = pkgs.firefox-extensions.ether-metamask; + enable = lib.mkDefault false; # until i can disable the first-run notification + }; + firefox-xdg-open = { + # test: `xdg-open xdg-open:https://uninsane.org` + package = pkgs.firefox-extensions.firefox-xdg-open; + enable = lib.mkDefault true; + }; + i2p-in-private-browsing = { + package = pkgs.firefox-extensions.i2p-in-private-browsing; + enable = lib.mkDefault config.services.i2p.enable; + }; + i-still-dont-care-about-cookies = { + package = pkgs.firefox-extensions.i-still-dont-care-about-cookies; + enable = lib.mkDefault false; #< obsoleted by uBlock Origin annoyances/cookies lists + }; + open-in-mpv = { + # test: `open-in-mpv 'mpv:///open?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ'` + package = pkgs.firefox-extensions.open-in-mpv; + enable = lib.mkDefault false; + }; + sidebery = { + package = pkgs.firefox-extensions.sidebery; + enable = lib.mkDefault true; + }; + sponsorblock = { + package = pkgs.firefox-extensions.sponsorblock; + enable = lib.mkDefault true; + }; + ublacklist = { + package = pkgs.firefox-extensions.ublacklist; + enable = lib.mkDefault false; + }; + ublock-origin = { + package = pkgs.firefox-extensions.ublock-origin; + enable = lib.mkDefault true; + }; + }; + + suggestedPrograms = lib.optionals cfg.addons.firefox-xdg-open.enable [ + "firefox-xdg-open" + ] ++ lib.optionals cfg.addons.open-in-mpv.enable [ + "open-in-mpv" + ]; + + sandbox.extraHomePaths = lib.optionals cfg.addons.browserpass-extension.enable [ + # browserpass needs these paths: + # - knowledge/secrets/accounts: where the encrypted account secrets live + # at least one of: + # - .config/sops: for the sops key which can decrypt account secrets + # - .ssh: to unlock the sops key, if not unlocked (`sane-secrets-unlock`) + # TODO: find a way to not expose ~/.ssh to firefox + # - unlock sops at login (or before firefox launch)? + # - see if ssh has a more formal type of subkey system? + # ".ssh/id_ed25519" + # ".config/sops" + "knowledge/secrets/accounts" + ]; + + fs.".config/sops".dir = lib.mkIf cfg.addons.browserpass-extension.enable {}; #< needs to be created, not *just* added to the sandbox + + # uBlock configuration: + fs.".mozilla/firefox/managed-storage/uBlock0@raymondhill.net.json".symlink.target = cfg.addons.ublock-origin.package.makeConfig { + # more filter lists are available here: + # - + # - + # - + filterFiles = let + getUasset = n: "${pkgs.uassets}/share/filters/${n}.txt"; + in [ + # default ublock filters: + (getUasset "ublock-filters") + (getUasset "ublock-badware") + (getUasset "ublock-privacy") + (getUasset "ublock-quick-fixes") + (getUasset "ublock-unbreak") + (getUasset "easylist") + (getUasset "easyprivacy") + # (getUasset "urlhaus-1") #< TODO: i think this is the same as urlhaus-filter-online + (getUasset "urlhaus-filter-online") + # (getUasset "plowe-0") #< TODO: where does this come from? + # (getUasset "ublock-cookies-adguard") #< TODO: where does this come from? + # filters i've added: + (getUasset "easylist-annoyances") #< blocks in-page popups, "social media content" (e.g. FB like button; improves loading time) + (getUasset "easylist-cookies") #< blocks GDPR cookie consent popovers (e.g. at stackoverflow.com) + # (getUasset "ublock-annoyances-others") + # (getUasset "ublock-annoyances-cookies") + ]; + }; + + env = lib.mkIf cfg.addons.browserpass-extension.enable { + # TODO: env.PASSWORD_STORE_DIR only needs to be present within the browser session. + # alternative to PASSWORD_STORE_DIR: + # fs.".password-store".symlink.target = lib.mkIf cfg.addons.browserpass-extension.enable "knowledge/secrets/accounts"; + PASSWORD_STORE_DIR = "/home/colin/knowledge/secrets/accounts"; + }; + }; +} diff --git a/hosts/common/programs/firefox/default.nix b/hosts/common/programs/firefox/default.nix index 5b47af0e9..b87507d5a 100644 --- a/hosts/common/programs/firefox/default.nix +++ b/hosts/common/programs/firefox/default.nix @@ -155,258 +155,144 @@ let runHook postFixup ''; }); - - addonOpts = types.submodule { - options = { - package = mkOption { - type = types.package; - }; - enable = mkOption { - type = types.bool; - }; - }; - }; - - configOpts = { - options = { - browser = mkOption { - default = defaultSettings; - type = types.anything; - }; - persistData = mkOption { - description = "optional store name to which persist browsing data (like history)"; - type = types.nullOr types.str; - default = null; - }; - persistCache = mkOption { - description = "optional store name to which persist browser cache"; - type = types.nullOr types.str; - default = "ephemeral"; - }; - addons = mkOption { - type = types.attrsOf addonOpts; - default = {}; - }; - }; - }; in { - config = mkMerge [ - { - sane.programs.firefox.configOption = mkOption { - type = types.submodule configOpts; - default = {}; - }; - sane.programs.firefox.config.addons = { - fxCast = { - # add a menu to cast to chromecast devices, but it doesn't seem to work very well. - # right click (or shift+rc) a video, then select "cast". - # - asciinema.org: icon appears, but glitches when clicked. - # - youtube.com: no icon appears, even when site is whitelisted. - # future: maybe better to have browser open all videos in mpv, and then use mpv for casting. - # see e.g. `ff2mpv`, `open-in-mpv` (both are packaged in nixpkgs) - package = pkgs.firefox-extensions.fx_cast; - enable = lib.mkDefault false; - }; - browserpass-extension = { - package = pkgs.firefox-extensions.browserpass-extension; - enable = lib.mkDefault true; - }; - bypass-paywalls-clean = { - package = pkgs.firefox-extensions.bypass-paywalls-clean; - enable = lib.mkDefault true; - }; - ctrl-shift-c-should-copy = { - package = pkgs.firefox-extensions.ctrl-shift-c-should-copy; - enable = lib.mkDefault false; # prefer patching firefox source code, so it works in more places - }; - ether-metamask = { - package = pkgs.firefox-extensions.ether-metamask; - enable = lib.mkDefault false; # until i can disable the first-run notification - }; - firefox-xdg-open = { - # test: `xdg-open xdg-open:https://uninsane.org` - package = pkgs.firefox-extensions.firefox-xdg-open; - enable = lib.mkDefault true; - }; - i2p-in-private-browsing = { - package = pkgs.firefox-extensions.i2p-in-private-browsing; - enable = lib.mkDefault config.services.i2p.enable; - }; - i-still-dont-care-about-cookies = { - package = pkgs.firefox-extensions.i-still-dont-care-about-cookies; - enable = lib.mkDefault false; #< obsoleted by uBlock Origin annoyances/cookies lists - }; - open-in-mpv = { - # test: `open-in-mpv 'mpv:///open?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ'` - package = pkgs.firefox-extensions.open-in-mpv; - enable = lib.mkDefault false; - }; - sidebery = { - package = pkgs.firefox-extensions.sidebery; - enable = lib.mkDefault true; - }; - sponsorblock = { - package = pkgs.firefox-extensions.sponsorblock; - enable = lib.mkDefault true; - }; - ublacklist = { - package = pkgs.firefox-extensions.ublacklist; - enable = lib.mkDefault false; - }; - ublock-origin = { - package = pkgs.firefox-extensions.ublock-origin; - enable = lib.mkDefault true; - }; - }; - } - { - sane.programs.firefox = { - inherit packageUnwrapped; - sandbox.method = "bwrap"; # landlock works, but requires all of /proc to be linked - sandbox.net = "all"; - sandbox.whitelistAudio = true; - sandbox.whitelistDbus = [ "user" ]; # mpris - sandbox.whitelistWayland = true; - sandbox.extraHomePaths = [ - "dev" # for developing anything web-related - # for uploads/downloads. - # it still needs these paths despite using the portal's file-chooser :? - "tmp" - "Pictures/albums" - "Pictures/cat" - "Pictures/from" - "Pictures/Photos" - "Pictures/Screenshots" - "Pictures/servo-macros" - ] ++ lib.optionals cfg.addons.browserpass-extension.enable [ - # browserpass needs these paths: - # - knowledge/secrets/accounts: where the encrypted account secrets live - # at least one of: - # - .config/sops: for the sops key which can decrypt account secrets - # - .ssh: to unlock the sops key, if not unlocked (`sane-secrets-unlock`) - # TODO: find a way to not expose ~/.ssh to firefox - # - unlock sops at login (or before firefox launch)? - # - see if ssh has a more formal type of subkey system? - # ".ssh/id_ed25519" - # ".config/sops" - "knowledge/secrets/accounts" - ]; - fs.".config/sops".dir = lib.mkIf cfg.addons.browserpass-extension.enable {}; #< needs to be created, not *just* added to the sandbox - - suggestedPrograms = lib.optionals cfg.addons.firefox-xdg-open.enable [ - "firefox-xdg-open" - ] ++ lib.optionals cfg.addons.open-in-mpv.enable [ - "open-in-mpv" - ]; - - mime.associations = let - inherit (cfg.browser) desktop; - in { - "text/html" = desktop; - "x-scheme-handler/http" = desktop; - "x-scheme-handler/https" = desktop; - "x-scheme-handler/about" = desktop; - "x-scheme-handler/unknown" = desktop; - }; - - # env.BROWSER = "${package}/bin/${cfg.browser.libName}"; - env.BROWSER = cfg.browser.libName; # used by misc tools like xdg-email, as fallback - - # uBlock configuration: - fs."${cfg.browser.dotDir}/managed-storage/uBlock0@raymondhill.net.json".symlink.target = cfg.addons.ublock-origin.package.makeConfig { - # more filter lists are available here: - # - - # - - # - - filterFiles = let - getUasset = n: "${pkgs.uassets}/share/filters/${n}.txt"; - in [ - # default ublock filters: - (getUasset "ublock-filters") - (getUasset "ublock-badware") - (getUasset "ublock-privacy") - (getUasset "ublock-quick-fixes") - (getUasset "ublock-unbreak") - (getUasset "easylist") - (getUasset "easyprivacy") - # (getUasset "urlhaus-1") #< TODO: i think this is the same as urlhaus-filter-online - (getUasset "urlhaus-filter-online") - # (getUasset "plowe-0") #< TODO: where does this come from? - # (getUasset "ublock-cookies-adguard") #< TODO: where does this come from? - # filters i've added: - (getUasset "easylist-annoyances") #< blocks in-page popups, "social media content" (e.g. FB like button; improves loading time) - (getUasset "easylist-cookies") #< blocks GDPR cookie consent popovers (e.g. at stackoverflow.com) - # (getUasset "ublock-annoyances-others") - # (getUasset "ublock-annoyances-cookies") - ]; - }; - - # TODO: this is better suited in `extraPrefs` during `wrapFirefox` call - fs."${cfg.browser.dotDir}/${cfg.browser.libName}.overrides.cfg".symlink.text = '' - // if we can't query the revocation status of a SSL cert because the issuer is offline, - // treat it as unrevoked. - // see: - defaultPref("security.OCSP.require", false); - - // scrollbar configuration, see: - // style=4 gives rectangular scrollbars - // could also enable "always show scrollbars" in about:preferences -- not sure what the actual pref name for that is - // note that too-large scrollbars (like 50px wide, even 20px) tend to obscure content (and make buttons unclickable) - defaultPref("widget.non-native-theme.scrollbar.size.override", 14); - defaultPref("widget.non-native-theme.scrollbar.style", 4); - - // disable inertial/kinetic/momentum scrolling because it just gets in the way on touchpads - // source: - defaultPref("apz.gtk.kinetic_scroll.enabled", false); - - // open external URIs/files via xdg-desktop-portal. - defaultPref("widget.use-xdg-desktop-portal.mime-handler", 1); - defaultPref("widget.use-xdg-desktop-portal.open-uri", 1); - - defaultPref("browser.toolbars.bookmarks.visibility", "never"); - // configure which extensions are visible by default (TODO: requires a lot of trial and error) - // defaultPref("browser.uiCustomization.state", ...); - - // auto-open specific URI schemes without prompting: - defaultPref("network.protocol-handler.external.xdg-open", true); // for firefox-xdg-open extension - defaultPref("network.protocol-handler.external.mpv", true); // for open-in-mpv extension - defaultPref("network.protocol-handler.external.element", true); // for Element matrix client - defaultPref("network.protocol-handler.external.matrix", true); // for Nheko matrix client - ''; - # instruct Firefox to put the profile in a predictable directory (so we can do things like persist just it). - # XXX: the directory *must* exist, even if empty; Firefox will not create the directory itself. - fs."${cfg.browser.dotDir}/profiles.ini".symlink.text = '' - [Profile0] - Name=default - IsRelative=1 - Path=default - Default=1 - - [General] - StartWithLastProfile=1 - ''; - - # TODO: env.PASSWORD_STORE_DIR only needs to be present within the browser session. - env.PASSWORD_STORE_DIR = "/home/colin/knowledge/secrets/accounts"; - # alternative to PASSWORD_STORE_DIR: - # fs.".password-store".symlink.target = lib.mkIf cfg.addons.browserpass-extension.enable "knowledge/secrets/accounts"; - - # flush the cache to disk to avoid it taking up too much tmp. - persist.byPath."${cfg.browser.cacheDir}".store = - if (cfg.persistData != null) then - cfg.persistData - else - "ephemeral" - ; - - persist.byPath."${cfg.browser.dotDir}/default".store = - if (cfg.persistData != null) then - cfg.persistData - else - "ephemeral" - ; - }; - } + imports = [ + ./addons.nix ]; + + sane.programs.firefox = { + configOption = mkOption { + default = {}; + type = types.submodule { + options = { + browser = mkOption { + default = defaultSettings; + type = types.anything; + }; + persistData = mkOption { + description = "optional store name to which persist browsing data (like history)"; + type = types.nullOr types.str; + default = null; + }; + persistCache = mkOption { + description = "optional store name to which persist browser cache"; + type = types.nullOr types.str; + default = "ephemeral"; + }; + addons = mkOption { + default = {}; + type = types.attrsOf (types.submodule { + options = { + package = mkOption { + type = types.package; + }; + enable = mkOption { + type = types.bool; + }; + }; + }); + }; + }; + }; + }; + + inherit packageUnwrapped; + + sandbox.method = "bwrap"; + sandbox.net = "all"; + sandbox.whitelistAudio = true; + sandbox.whitelistDbus = [ "user" ]; # mpris + sandbox.whitelistWayland = true; + sandbox.extraHomePaths = [ + "dev" # for developing anything web-related + # for uploads/downloads. + # it still needs these paths despite using the portal's file-chooser :? + "tmp" + "Pictures/albums" + "Pictures/cat" + "Pictures/from" + "Pictures/Photos" + "Pictures/Screenshots" + "Pictures/servo-macros" + ]; + + mime.associations = let + inherit (cfg.browser) desktop; + in { + "text/html" = desktop; + "x-scheme-handler/http" = desktop; + "x-scheme-handler/https" = desktop; + "x-scheme-handler/about" = desktop; + "x-scheme-handler/unknown" = desktop; + }; + + env.BROWSER = cfg.browser.libName; # used by misc tools like xdg-email, as fallback + + # redirect librewolf configs to the firefox configs; this way addons don't have to care about the firefox/librewolf distinction. + fs.".librewolf/librewolf.overrides.cfg".symlink.target = "../.mozilla/firefox/firefox.overrides.cfg"; + fs.".librewolf/profiles.ini".symlink.target = "../.mozilla/firefox/profiles.ini"; + fs.".librewolf/managed-storage".symlink.target = "../.mozilla/firefox/managed-storage"; + + # TODO: this is better suited in `extraPrefs` during `wrapFirefox` call + fs.".mozilla/firefox/firefox.overrides.cfg".symlink.text = '' + // if we can't query the revocation status of a SSL cert because the issuer is offline, + // treat it as unrevoked. + // see: + defaultPref("security.OCSP.require", false); + + // scrollbar configuration, see: + // style=4 gives rectangular scrollbars + // could also enable "always show scrollbars" in about:preferences -- not sure what the actual pref name for that is + // note that too-large scrollbars (like 50px wide, even 20px) tend to obscure content (and make buttons unclickable) + defaultPref("widget.non-native-theme.scrollbar.size.override", 14); + defaultPref("widget.non-native-theme.scrollbar.style", 4); + + // disable inertial/kinetic/momentum scrolling because it just gets in the way on touchpads + // source: + defaultPref("apz.gtk.kinetic_scroll.enabled", false); + + // open external URIs/files via xdg-desktop-portal. + defaultPref("widget.use-xdg-desktop-portal.mime-handler", 1); + defaultPref("widget.use-xdg-desktop-portal.open-uri", 1); + + defaultPref("browser.toolbars.bookmarks.visibility", "never"); + // configure which extensions are visible by default (TODO: requires a lot of trial and error) + // defaultPref("browser.uiCustomization.state", ...); + + // auto-open specific URI schemes without prompting: + defaultPref("network.protocol-handler.external.xdg-open", true); // for firefox-xdg-open extension + defaultPref("network.protocol-handler.external.mpv", true); // for open-in-mpv extension + defaultPref("network.protocol-handler.external.element", true); // for Element matrix client + defaultPref("network.protocol-handler.external.matrix", true); // for Nheko matrix client + ''; + + # instruct Firefox to put the profile in a predictable directory (so we can do things like persist just it). + # XXX: the directory *must* exist, even if empty; Firefox will not create the directory itself. + fs.".mozilla/firefox/profiles.ini".symlink.text = '' + [Profile0] + Name=default + IsRelative=1 + Path=default + Default=1 + + [General] + StartWithLastProfile=1 + ''; + + # flush the cache to disk to avoid it taking up too much tmp. + persist.byPath."${cfg.browser.cacheDir}".store = + if (cfg.persistData != null) then + cfg.persistData + else + "ephemeral" + ; + + persist.byPath."${cfg.browser.dotDir}/default".store = + if (cfg.persistData != null) then + cfg.persistData + else + "ephemeral" + ; + }; }