Date:
I run an ephemeral NixOS system (previous post), based
on the “Erase your
darlings” idea of erasing your system on every boot, and
selectively persisting state. My Firefox profile
(~/.mozilla) was a prime candidate for this
treatment: I’ll have it wiped on every boot. This post details my
two-year journey of managing customised ephemeral Firefox
profiles.
Firefox offers enterprise/distribution customisation through
policy.json (documented here). We can
use this for personal configuration. Nix provides excellent tools
for generating this JSON.
Let’s set our preferred about:config
settings.
programs.firefox = {
enable = true;
preferences = {
# Reduce visual noise
"browser.tabs.firefox-view" = false;
"dom.webnotifications.enabled" = false;
# Improve unlinkability
"privacy.firstparty.isolate" = true;
"privacy.resistFingerprinting" = true;
# Disable DOH, since I prefer my local resolver.
# https://wiki.mozilla.org/Trusted_Recursive_Resolver
"network.trr.mode" = 0;
};
};
Bookmarks are managed through the Bookmarks field
in policy.json. I store my bookmarks in
bookmarks.json:
[
{"title": "Groovesalad", "uri": "https://ice4.somafm.com/groovesalad-128-aac"},
{"title": "Raspberry Pi Pinout", "uri": "https://pinout.xyz/"}
]
And then have Nix read that and dump it into the
Bookmarks field.
programs.firefox = {
policies = {
Bookmarks = map (b: {
Folder = "Managed Bookmarks";
Title = b.title;
URL = b.uri;
}) (builtins.fromJSON (builtins.readFile ./bookmarks.json));
};
};
For many years before I had ephemeral Firefox profiles, I had “Delete history and cache” on quit, so losing this on Firefox is fine.
I preserve this behaviour with:
programs.firefox = {
policies = {
SanitizeOnShutdown = {
Cache = true;
Cookies = true;
Downloads = true;
FormData = true;
History = true;
Sessions = true;
SiteSettings = true;
OfflineApps = true;
};
};
};
While I’m here, let’s disable a bunch of others that I don’t use:
policies = {
AppAutoUpdate = false;
CaptivePortal = false;
DisableFirefoxAccounts = true;
DisableFirefoxStudies = true;
DisablePocket = true;
DisableTelemetry = true;
EnableTrackingProtection.Value = true;
ExtensionUpdate = false;
NetworkPrediction = false;
NoDefaultBookmarks = true;
OfferToSaveLogins = false;
PasswordManagerEnabled = false;
SanitizeOnShutdown = {
Cache = true;
Cookies = true;
Downloads = true;
FormData = true;
History = true;
Sessions = true;
SiteSettings = true;
OfflineApps = true;
};
SearchSuggestEnabled = false;
};
Extensions are managed via the ExtensionSettings
field:
let
# Set of extension IDs to rycee package names: https://gitlab.com/rycee/nur-expressions/-/blob/master/pkgs/firefox-addons/generated-firefox-addons.nix
ryceeExtensions = {
# https://addons.mozilla.org/en-US/firefox/addon/boring-rss/
"{45d4d1a3-4faa-42b7-9747-bcf2153310cd}" = {
name = "boring-rss";
permissions = [];
};
# https://addons.mozilla.org/en-US/firefox/addon/disable-facebook-news-feed/
"{85cd2b5d-b3bd-4037-8335-ced996a95092}" = {
name = "disable-facebook-news-feed";
permissions = [
"*://*.facebook.com/*"
];
};
# https://addons.mozilla.org/en-US/firefox/addon/old-reddit-redirect/
"{9063c2e9-e07c-4c2c-9646-cfe7ca8d0498}" = {
name = "old-reddit-redirect";
permissions = [
"webRequest"
"webRequestBlocking"
"*://reddit.com/*"
"*://www.reddit.com/*"
"*://np.reddit.com/*"
"*://amp.reddit.com/*"
"*://i.reddit.com/*"
"*://i.redd.it/*"
"*://preview.redd.it/*"
"*://old.reddit.com/*"
];
};
in {
config = {
programs.firefox = {
policies = {
ExtensionSettings =
(builtins.mapAttrs (k: v: {
"installation_mode" = "normal_installed";
# https://gitlab.com/rycee/nur-expressions/-/blob/5dce5ca68c1fcd569b8edd8b64033a9e62aa57c5/pkgs/firefox-addons/default.nix#L19
"install_url" = "file://${config.nur.repos.rycee.firefox-addons.${v.name}}/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/${k}.xpi";
}) ryceeExtensions);
};
};
};
}
This relies on having the great work rycee has done in making Firefox extensions available at nix build time.
I use permission allowlists (as described here) to control extension permissions and prevent unwanted permissions on upgrades.
Each boot provides a fresh Firefox instance, configured according to my preferences. Changes are managed through Nix, benefiting from version control, upgrades, rollbacks, and safe experimentation.
Ephemeral Firefox profiles have drawbacks:
Extension configuration: Many extensions
rely on internal configuration, which is lost on reboot. Only
extensions configurable via the 3rdparty field
of policy.json are fully supported. Few
extensions do.
Session management: No persistent sessions means re-logging in on every boot. Passkeys mitigate this somewhat.
Bookmark management: Adding bookmarks via
the UI doesn’t persist them. Bookmarks must be added to
bookmarks.json.