Managing Firefox on macOS with Nix
By Calum MacRae
May 9, 2020
In this post I'll talk about how I manage Firefox and mimick some of the behaviours/aesthetics I'd come to enjoy from using other browsers over the years.
Note: I'm in no way affiliated with Worldwide. It's a cool project and the site looks great, so figured I'd use it for a screenshot
Last winter, like many others, my experience with Chrome/Chromium had been a little sour for some time. I decided to make the jump to Firefox. Seeing as the browser was the one component of my system that I'd left out of my declarative configuration, I thought it'd be a good time to tackle that. The final piece.
Defining a package
The Firefox package in nixpkgs has been broken for platforms.darwin
for quite a while.
I wasn't about to set out on trying to tackle that, as I simply didn't have the time.
So instead, I wrapped up fetching the release .dmg
in a simple overlay. Here's what it looks
like currently:
{ stdenv, fetchurl, undmg }:
stdenv.mkDerivation rec {
pname = "Firefox";
version = "76.0.1";
buildInputs = [ undmg ];
sourceRoot = ".";
phases = [ "unpackPhase" "installPhase" ];
installPhase = ''
mkdir -p "$out/Applications"
cp -r Firefox.app "$out/Applications/Firefox.app"
'';
src = fetchurl {
name = "Firefox-${version}.dmg";
url = "https://download-installer.cdn.mozilla.net/pub/firefox/releases/${version}/mac/en-GB/Firefox%20${version}.dmg";
sha256 = "1kvmyn375pb7mkcy410m8p1j550kv4g6dyx646igw68h1gz830va";
};
meta = with stdenv.lib; {
description = "The Firefox web browser";
homepage = "https://www.mozilla.org/en-GB/firefox";
maintainers = [ maintainers.cmacrae ];
platforms = platforms.darwin;
};
}
As you can see, it's a simple derivation that just fetches & unpacks the .dmg
, then moves it to an
Applications/
directory in the resulting $out
. Using this overlay, you can simply add it to your
user or system packages list and it'll end up in ~/.nix-profile/Applications/
or ~/Applications/
Tying it in with home-manager
The ever awesome nix-community/home-manager has a really nice module for configuring Firefox.
The above overlay can be configured as the package by simply setting
programs.firefox.package = Firefox;
(if Firefox
is the name you bound the overlay to).
But the module lets you do much more than that.
Extensions from rycee's NUR repo
The programs.firefox
module has an extensions
option
programs.firefox.extensions
List of Firefox add-on packages to install. Note, it is necessary to manually enable these extensions inside Firefox after the first installation.
Type: list of packages
So, if you can produce package derivations whos result represents a Firefox extension, you can add it to this list and they'll be installed. Fortunately rycee has a bunch available in his NUR expressions repo.
For anyone unfamilar with the NUR, here's an excerpt from the nix-community/NUR README
The Nix User Repository (NUR) is community-driven meta repository for Nix packages. It provides access to user repositories that contain package descriptions (Nix expressions) and allows you to install packages by referencing them via attributes. In contrast to Nixpkgs, packages are built from source and are not reviewed by any Nixpkgs member.
With this and rycee's firefox-addons
packages (automatically generated & updated)
we have declarative management of extensions:
nixpkgs.config.packageOverrides = pkgs: {
nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
inherit pkgs;
};
};
home-manager.users.cmacrae.programs.firefox.extensions =
with pkgs.nur.repos.rycee.firefox-addons; [
ublock-origin
browserpass
vimium
];
Fine grained configuration & mimicking Chrome profiles
Another great feature of the programs.firefox
module from home-manager: you can configure user
profiles along with any settings available in about:config
(stored in userprefs.js
).
I was excited to see this, because my experience with Firefox's profile management so far had been lacklustre. I thought I could perhaps cook up something to mimick the profile management of Chrome. There are a few extensions out there for Firefox that offer session management based on profiles, but I really like the simplistic approach of having each instance/window of the browser run under a profile session. I'd gotten used to the pattern of having my work profile open on one workspace, and my personal profile open on another.
I've managed to achieve this fairly elegantly, I think. Here's what I've been using:
home-manager.users.cmacrae.programs.firefox.profiles =
let defaultSettings = {
"app.update.auto" = false;
"browser.startup.homepage" = "https://lobste.rs";
# *snip* no need to splurge all my settings, you get the idea...
"identity.fxaccounts.account.device.name" = config.networking.hostName;
"signon.rememberSignons" = false;
};
in {
home = {
id = 0;
settings = defaultSettings // {
"browser.urlbar.placeholderName" = "DuckDuckGo";
"toolkit.legacyUserProfileCustomizations.stylesheets" = true;
};
userChrome = builtins.readFile ../conf.d/userChrome.css;
};
work = {
id = 1;
settings = defaultSettings // {
"browser.startup.homepage" = "about:blank";
};
};
With this, I'm setting up a bunch of common configuration I'd like to use for any profiles by let
'ing defaultSettings
.
Then, for each profile I'm doing a merge operation with //
(for those unfamilar, attributes on the right are merged over matching
attributes on the left).
This yields a home
profile with some styling (we'll get to that) and DuckDuckGo as the search engine, and a work
profile
whos only differing setting from the default configuration is an about:blank
home page.
I haven't taken the time yet to configure it, but it'd be dead simple to implement a different
styled/coloured work
profile so the browser windows differ in appearance, which would be nice.
To launch these different profiles in the manner I prefer, I have the following skhd
bindings:
cmd + ctrl - f : open -n ~/.nix-profile/Applications/Firefox.app --args -P home
cmd + shift + ctrl - f : open -n ~/.nix-profile/Applications/Firefox.app --args -P work
The firefox
binary accepts a -P
flag to set the profile. We can pass this using open
's --args
flag.
This really nicely mimicks the behaviour of Chrome I mentioned. All browser data from each profile is kept entirely
separate.
Changing appearance with userChrome
And to go along with userprefs.js
, the module also has a userChrome
option.
I only recently decided to take advantage of this. I've been using yabai for a while and have managed
to configure my desktop appearance/functionality to closely resemble that of the tiling WMs I'd used on Linux/BSD
over the years. I have fond memories of xombrero on OpenBSD, and found myself longing for a somewhat similar
appearance. Since I'd configured Alacritty and patched Emacs to drop their title bars to fit nicely
with the nature of the environment, I figured it was probably possible with Firefox via userChrome
too.
Some readers may have noticed the this line earlier in my profile expression
userChrome = builtins.readFile ../conf.d/userChrome.css;
I decided to break out the CSS for Firefox into its own file, as I knew how unwieldy it'd be. I've managed to conjure up something that hides all the elements I'm not interested in displaying, as I operate the browser mostly with the keyboard (thanks to Vimium).
Here's what it configures:
- Hides window controls (macOS title bar)
- Hides all "clutter" in the nav bar (including all extensions except browserpass)
- Hide tabs if only one tab is open
- Minimal appearance with a solarized-light inspired theme
- Move the hamburger menu to the left of the URL bar
- Move tabs inline to the right of the URL bar
Here's the full configuration for anyone who may want to use it. I've tried to keep it clean and well commented.
The home-manager module also supports userContent
, but I'm yet to play around with that.
Closing words
So with all this, my entire we browsing environment is declarative - just as the rest of my configuration. It's nice being able to set up on a new machine in a matter of minutes and have everything exactly as you like it - even all the intricacies.
This concludes the write-up. If you have any questions, please don't hesitate to comment or reach out to me on Twitter (@calumacrae). Until next time, happy hacking :)
- Posted on:
- May 9, 2020
- Length:
- 6 minute read, 1262 words