From b8bb26da75be6299375c6338ac082a9dd3681d3a Mon Sep 17 00:00:00 2001 From: alexion Date: Thu, 30 Jul 2026 08:31:11 -0400 Subject: [PATCH] feat(firefox): pin signed browser extensions --- AGENTS.md | 4 ++++ flake.lock | 23 +++++++++++++++++++++++ flake.nix | 6 ++++++ modules/desktop/firefox.nix | 37 +++++++++++++++++++------------------ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5dc6bdc..cce1e67 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla ## Gotchas +- ADR bodies are immutable records of decisions as they were made, while status frontmatter is mutable. + When a decision changes or its premise proves wrong, preserve the original body, update its status, and add a new ADR that supersedes it. - This repo pins no Nix formatter, and its committed `.nix` files are not clean under current `nixfmt-rfc-style`. Running `nixfmt` across a file reflows untouched code (for example `lib.nix`'s `deriveMac` list and multi-line assertion messages) and injects churn unrelated to the change. Format only the lines being written or changed, matching the surrounding style by hand. @@ -62,6 +64,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla Omission alone does not prune a built-in engine, since Firefox reconciles its app-provided engines back in, so remove one by listing it with `.metaData.hidden = true`. Engines are referenced by their current id, so the default is `default = "ddg"`, not `"DuckDuckGo"`. Decode the built file with `mozlz4a -d ` to check the result. +- Any non-empty Home Manager Firefox `profiles..extensions.settings..settings` causes Home Manager to set `extensions.webextensions.ExtensionStorageIDB.enabled = false` globally for that profile. + This repo's Stylix Firefox `colorTheme` settings trigger it, so every extension in the profile uses the legacy extension-storage backend regardless of how it is installed. - `home.sessionVariables` do **not** reach the Hyprland session, since UWSM does not source `hm-session-vars.sh`. The cursor is therefore set through Hyprland's own `env = KEY,VALUE` in `modules/desktop/hyprland/hyprland.nix`, sourced from `config.stylix.cursor`. Bibata ships XCursor format only (no `hyprcursor/` dir), rendered through Hyprland's XCursor fallback, so `XCURSOR_*` and `HYPRCURSOR_*` naming the same theme are both safe. diff --git a/flake.lock b/flake.lock index 71ccf76..4984c33 100644 --- a/flake.lock +++ b/flake.lock @@ -109,6 +109,28 @@ "type": "github" } }, + "firefox-addons": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "dir": "pkgs/firefox-addons", + "lastModified": 1785384175, + "narHash": "sha256-sWSJPXpQwKJstL4rdhpAQYCYlHK5wOkEHR8/lNHBVb4=", + "owner": "rycee", + "repo": "nur-expressions", + "rev": "db607f3d0afe811bcb3b16266f28b2fc5af4e74f", + "type": "gitlab" + }, + "original": { + "dir": "pkgs/firefox-addons", + "owner": "rycee", + "repo": "nur-expressions", + "type": "gitlab" + } + }, "firefox-gnome-theme": { "flake": false, "locked": { @@ -517,6 +539,7 @@ "inputs": { "chaotic": "chaotic", "disko": "disko", + "firefox-addons": "firefox-addons", "gitea-axi": "gitea-axi", "home-manager": "home-manager_3", "nixos-hardware": "nixos-hardware", diff --git a/flake.nix b/flake.nix index 7e5f297..410c00a 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,12 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + # Signed AMO extensions, pinned by version and hash. + firefox-addons = { + url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # Follows our nixpkgs so its plugins build against the same package set. nixvim = { url = "github:nix-community/nixvim"; diff --git a/modules/desktop/firefox.nix b/modules/desktop/firefox.nix index 89d8c9c..227dc82 100644 --- a/modules/desktop/firefox.nix +++ b/modules/desktop/firefox.nix @@ -1,15 +1,15 @@ -{ config, lib, ... }: +{ + config, + inputs, + lib, + pkgs, + ... +}: # Firefox as the desktop browser: stock mainline, hardened and de-monetized by policy. let cfg = config.modules.desktop.firefox; user = config.user.name; - - # A force-installed extension, keyed at the call site by the add-on's own id. - # Firefox fetches the signed add-on from Mozilla's site and enables it automatically. - forceInstalled = slug: { - install_url = "https://addons.mozilla.org/firefox/downloads/latest/${slug}/latest.xpi"; - installation_mode = "force_installed"; - }; + firefoxAddons = inputs.firefox-addons.packages.${pkgs.stdenv.hostPlatform.system}; in { options.modules.desktop.firefox.enable = lib.mkEnableOption "Firefox as the desktop browser"; @@ -34,21 +34,22 @@ in SponsoredPocket = false; Snippets = false; }; - - # An ad blocker, a password manager, and a video sponsor-skipper. - ExtensionSettings = { - "uBlock0@raymondhill.net" = forceInstalled "ublock-origin"; - "78272b6fa58f4a1abaac99321d503a20@proton.me" = forceInstalled "proton-pass"; - "sponsorBlocker@ajay.app" = forceInstalled "sponsorblock"; - }; }; profiles.default = { isDefault = true; - # The Nord chrome theme is a declared extension setting, so home-manager - # owns the extension-settings store, overwriting runtime changes to it. - extensions.force = true; + extensions = { + packages = with firefoxAddons; [ + ublock-origin + proton-pass + sponsorblock + ]; + + # The Nord chrome theme is a declared extension setting, so home-manager + # owns the extension-settings store, overwriting runtime changes to it. + force = true; + }; # Stylix's Nord mapping paints the selected address-bar result a # near-white grey, leaving its light text unreadable. Darken that one