I hit this issue when I was trying to declaratively turn on uBlock Origin’s Medium Mode via Home Manager’s module programs.librewolf.profiles.<my-profile-name>.extensions.settings."uBlock0@raymondhill.net".settings. To ensure the Nix code is actually doing what it should be, impermanence is wiping ~/.librewolf every time the computer reboots.
I also started with making a backup file through uBlock Origin’s “Back up to file” button in the extension’s GUI dashboard and tried simply translating the resulting JSON file settings to Nix and found userSettings.advancedUserEnabled was not sticking.
I initially solved the weirdness with userSettings by directly observing where uBlock Origin settings are stored. The Home Manager module for Librewolf (and similarly for Firefox and other Firefox derivatives) puts them in a JSON file at ~/.librewolf/<my-profile-name>/browser-extension-data/uBlock0@raymondhill.net/storage.js (apparently this storage location and method is Firefox legacy behavior that the Home Manager module explicitly enables, and this may stop working due to Firefox changes in the future).
In short, you can first save storage.js to another path, ignoring certain uninteresting cache values and third-party lists values; then make a change to uBlock Origin settings (e.g. use element picker mode to select something to block on the current webpage and thus generate a user filter); then see what exact JSON values change by diffing the current storage.js with the previously-saved storage.js. The JSON objects that changed are exactly what you should put into programs.librewolf.profiles.<my-profile-name>.extensions.settings.
To directly answer the question about user filters, the solution is again placing a differently-named key in the correct position. “user-filters” go in the top level:
extensions = {
force = true;
settings = {
"uBlock0@raymondhill.net".settings = {
# Do not do this
# userSettings = rec {
# uiTheme = "dark";
# };
#
# Instead, move the contents up a level
uiTheme = "dark";
# All other values (not under `userSettings`) stay the same
selectedFilterLists = [
"JPN-1"
];
# User filters go here
# These appear in uBlock Origin's dashboard in "My filters" pane
"user-filters" = "noai.duckduckgo.com##.set-browser-promo\nnoai.duckduckgo.com##.home-callouts-module-scss-module___U5DDa__callouts";
};
};
}
I’ll post my rabbit hole dive to potentially help someone else achieve declarative nirvana.
Versions:
- Librewolf: 154.0.1-3
- uBlock Origin: 1.74.0
(<my-profile-name> is a placeholder for the Librewolf profile name)
The goal is to have turn on Medium mode in uBlock Origin, and have that persist across reboots in the presence of Impermanece wiping ~/.librewolf at every reboot.
- Realize uBlock Origin has a “Back up to file” button whose output unfortunately does not directly map to
programs.librewolf.profiles.<my-profile-name>.extensions.settings
I turned on Medium Mode and got this “backup” file from uBlock Origin by clicking the “Back up to file” button:
{
"timeStamp": 1788134331842,
"version": "1.74.0",
"userSettings": {
"advancedUserEnabled": true,
"importedLists": [],
"popupPanelSections": 31
},
"selectedFilterLists": [
"user-filters",
"ublock-filters",
"ublock-badware",
"ublock-privacy",
"ublock-unbreak",
"ublock-quick-fixes",
"easylist",
"adguard-spyware-url",
"easyprivacy",
"urlhaus-1",
"curben-phishing",
"plowe-0",
"LegitimateURLShortener"
],
"hiddenSettings": {},
"whitelist": [
"chrome-extension-scheme",
"moz-extension-scheme"
],
"dynamicFilteringString": "behind-the-scene * * noop\nbehind-the-scene * inline-script noop\nbehind-the-scene * 1p-script noop\nbehind-the-scene * 3p-script noop\nbehind-the-scene * 3p-frame noop\nbehind-the-scene * image noop\nbehind-the-scene * 3p noop\n* * 3p-script block\n* * 3p-frame block",
"urlFilteringString": "",
"hostnameSwitchesString": "no-large-media: behind-the-scene false\nno-csp-reports: * true",
"userFilters": ""
}
The next step is to translate these settings into a Nix config using the Home Manager module for Librewolf.
At this point, `timestamp` and `version` do not look particularly useful for my purposes, so I left those out.
# uBlock Origin extension settings, Attempt 1
# Default uBlock Origin settings, except with medium mode
programs.librewolf.profiles."<my-profile-name>".extensions = {
force = true;
settings."uBlock0@raymondhill.net".settings = {
"userSettings" = {
"advancedUserEnabled" = true;
"importedLists" = [];
"popupPanelSections" = 31;
};
"selectedFilterLists" = [
"user-filters"
"ublock-filters"
"ublock-badware"
"ublock-privacy"
"ublock-unbreak"
"ublock-quick-fixes"
"easylist"
"adguard-spyware-url"
"easyprivacy"
"urlhaus-1"
"curben-phishing"
"plowe-0"
"LegitimateURLShortener"
];
"hiddenSettings" = {};
"whitelist" = [
"chrome-extension-scheme"
"moz-extension-scheme"
];
"dynamicFilteringString" = "behind-the-scene * * noop\nbehind-the-scene * inline-script noop\nbehind-the-scene * 1p-script noop\nbehind-the-scene * 3p-script noop\nbehind-the-scene * 3p-frame noop\nbehind-the-scene * image noop\nbehind-the-scene * 3p noop\n* * 3p-script block\n* * 3p-frame block";
"urlFilteringString" = "";
"hostnameSwitchesString" = "no-large-media: behind-the-scene false\nno-csp-reports: * true";
"userFilters" = "";
};
};
Run nixos-rebuild boot, reboot the machine to wipe ~/.librewolf, launch Librewolf, and… find out medium mode is no longer turned on.
Opening uBlock Origin’s dashboard immediately reveals “I am an advanced user” box is not checked anymore. However, I can see the two dynamic filters (under “My rules” pane) are still present.
(This is the first hint that all settings under userSettings in the Nix code do nothing, even though the backup file that uBlock Origin generates has userSettings).
- Realize uBlock Origin has a “Support” tab in its dashboard with “Troubleshooting Information” that also does not directly map to
programs.librewolf.profiles.<my-profile-name>.extensions.settings
Essentially the same problem as “Back up to file” where the “Troubleshooting Information” values are formatted like JSON but directly translating these to Nix in the Home Manager module for uBlock Origin does not work. userSettings appears here too, and if I check “I am an advanced user” again, advancedUserEnabled: true shows up as a subkey to userSettings here as well. But this is exactly the same non-working situation as attempt 1.
With no other ideas, I decided to look at how Home Manager implements the Librewolf module.
- Inspect the Home Manager module for Librewolf
This file itself is quite sparse; notice that most of the heavy lifting is in the function mkFirefoxModule defined in another file.
In this massive module I searched for “extension”, backwards because I am looking for the implementation details of how the module translates Nix code to uBlock Origin settings and Nix modules usually put those at the end of modules. Line 1308 "${cfg.profilesPath}/${profile.path}/browser-extension-data/${name}/storage.js" = { has the crucial hint that this storage.js might contain extension settings (which ends up to be true).
- Inspect
~/.librewolf/<my-profile-name>/browser-extension-data/uBlock0@raymondhill.net/storage.js
This is a rather large JSON file. Take a look at its contents with:
cat ~/.librewolf/<my-profile-name>/browser-extension-data/uBlock0@raymondhill.net/storage.js | jq
This spits out loads of data, including what looks like the contents of third-party block lists like Easylist.
# Read extension store JSON and pipe to `jq`
# `--sort-keys` helps with the later step of diffing this JSON object with another, similar JSON object...
# The `jq` filter removes uninteresting cache data and massive third-party "Filter lists" that uBlock Origin automatically downloads. These are the user-selected lists under "Filter lists" tab in uBlock Origin's settings GUI
# Unfortunately, the result still has some uninteresting data.
cat ~/.librewolf/<my-profile-name>/browser-extension-data/uBlock0@raymondhill.net/storage.js \
| jq --sort-keys 'with_entries(select( .key | startswith("cache/") | not ))'
I won’t copy the complete output of this command here because it is quite large and still contains data I do not care about (and my jq skills are too lacking to figure out how to filter those data out).
Inspecting the output manually, I find an interesting value…
"userSettings": {
"advancedUserEnabled": true,
"importedLists": [],
"popupPanelSections": 31
},
So this is evidence that the Nix code is indeed setting advancedUserEnabled: true but it does not have the desired effect; “I am an advanced user” is still unchecked.
Regardless, storage.js does seem to be where uBlock Origin settings exist. Let’s see if checking “I am an advanced user” will change anything in storage.js…
Save storage.js as it is now:
mkdir /tmp/ublock-origin-settings
cd /tmp/ublock-origin-settings
cat ~/.librewolf/<my-profile-name>/browser-extension-data/uBlock0@raymondhill.net/storage.js \
| jq --sort-keys 'with_entries(select( .key | startswith("cache/") | not ))' > storage-before.js
Then, in uBlock Origin’s dashboard in Librewolf, check “I am an advanced user” and save storage.js again…
cat ~/.librewolf/<my-profile-name>/browser-extension-data/uBlock0@raymondhill.net/storage.js \
| jq --sort-keys 'with_entries(select( .key | startswith("cache/") | not ))' > storage-after.js
Then, look at the diff with diff storage-before.js storage-after.js. There is a lot of data that appears to be related to modification times and updates to block lists that I don’t care about, but right at the top of the diff…
> "advancedUserEnabled": true,
…I see advancedUserEnabled by itself at the top level, not under “userSettings”. This is the big hint that all values under userSettings need to be hoisted to the top level.
Test the idea by changing the Home Manager module settings:
# uBlock Origin extension settings, final attempt
# Default uBlock Origin settings, except with medium mode
programs.librewolf.profiles."<my-profile-name>".extensions = {
force = true;
settings."uBlock0@raymondhill.net".settings = {
# Do not do this
# "userSettings" = {
# "advancedUserEnabled" = true;
# "importedLists" = [];
# "popupPanelSections" = 31;
# };
#
# Instead, hoist all values under `userSettings` to the top level
"advancedUserEnable" = true;
"importredLists" = [];
"popupPanelSections" = 31;
"selectedFilterLists" = [
"user-filters"
"ublock-filters"
"ublock-badware"
"ublock-privacy"
"ublock-unbreak"
"ublock-quick-fixes"
"easylist"
"adguard-spyware-url"
"easyprivacy"
"urlhaus-1"
"curben-phishing"
"plowe-0"
"LegitimateURLShortener"
];
"hiddenSettings" = {};
"whitelist" = [
"chrome-extension-scheme"
"moz-extension-scheme"
];
"dynamicFilteringString" = "behind-the-scene * * noop\nbehind-the-scene * inline-script noop\nbehind-the-scene * 1p-script noop\nbehind-the-scene * 3p-script noop\nbehind-the-scene * 3p-frame noop\nbehind-the-scene * image noop\nbehind-the-scene * 3p noop\n* * 3p-script block\n* * 3p-frame block";
"urlFilteringString" = "";
"hostnameSwitchesString" = "no-large-media: behind-the-scene false\nno-csp-reports: * true";
"userFilters" = "";
};
};
Run nixos-rebuild boot, wipe the runtime Librewolf profile folder by rebooting the machine, launch Librewolf, and I can see that medium mode is now persistently turned on.
I did the same thing to discover user-filters is the correct attr to use for user filters (the stuff under “My filters” pane in uBlock Origin dashboard).
There is one thing left to do. Using medium mode means I often add dynamic filtering rules in order to unbreak many (almost all) websites. So let’s script the entire process!
(I wrote the script in Nushell because Bash scripting has been a thorn on my behind for far too long. The script should be simple enough to port to Bash if that is what you want.)
programs.librewolf.profiles."<my-profile-name>".extensions = {
force = true;
settings."uBlock0@raymondhill.net".settings = {
# The settings for uBlock Origin are designed to be modified through uBlock Origin's GUI within the browser and then exposed to Nix through a JSON file generated by an external script.
settings =
let
ublockOriginSettingsJSONFile = ublock-origin_settings.json;
in if (builtins.pathExists ublockOriginSettingsJSONFile) then {
"uBlock0@raymondhill.net".settings = builtins.fromJSON (builtins.readFile ublockOriginSettingsJSONFile);
} else {};
};
};
#! /usr/bin/env nu
#
# From the current user's Librewolf installation, get its uBlock Origin settings and update the equivalent declarative settings
#
# Currently supports only the Librewolf profile name "default"
#
# NOTE: This script's shebang line is deliberately not `#! /usr/bin/env nix-shell`. Although using a `nix-shell` shebang will ensure the running machine will have the necessary interpreter and dependencies, `nix-shell` was found to be significantly slower to launch than a plain `#! /usr/bin/env <interpreter>`.
const pathSelfDir = path self | path dirname
let runtimeUBlockSettings = $env.HOME | path join ".librewolf/default/browser-extension-data/uBlock0@raymondhill.net/storage.js"
let UBlockSettingsJSON = $pathSelfDir | path join $"ublock-origin_settings.json"
# This implementation uses a whitelist of values to get the interesting values from `storage.js`, and some desirable keys may be missing.
# Those missing keys will need to be manually found and added to this list.
open $runtimeUBlockSettings
| jq --sort-keys '{
# The following key-value pairs are hoisted from `userSettings`
advancedUserEnabled,
importedLists,
popupPanelSections,
dynamicFilteringString,
hiddenSettings,
hostnameSwitchesString,
selectedFilterLists,
urlFilteringString,
"user-filters",
userFilters,
whitelist,
}'
| save --force $UBlockSettingsJSON
Run the script whenever you change uBlock Origin settings, and the updated settings should appear in ublock-origin_settings.json and automatically picked up by the Nix config.