background grid image
Image for post declarative-gnome-configuration-with-nixos
Jun 1, 2023 by Ana Hobden

Declarative GNOME configuration with NixOS

I adore tinkering with my machine, trying new tools, extensions, themes, and ideas. When I was younger, it was just a way to learn. Now, it’s a way for me to refine my workspace and bring myself small joys.

While tinkering can be fun, it can be a chore to set up a new machine, keep configurations up to date between machines, or even just remember to keep up to date backups.

What about when we want to configure a whole desktop environment? While NixOS offers configuration settings like services.gnome.gnome-keyring.enable for system-wide features, there’s a lack of knobs when you want to set things like user-specific GNOME ‘Favorite Apps’ or extensions.

Let’s explore a useful addition to your NixOS configuration: Home Manager and its dconf module.

This article uses Nix flakes which is an experimental feature. You may need to set this in your configuration:

1
nix.settings.experimental-features = [ "flakes" "nix-command" ];

Getting Home Manager set up

Home manager a tool from the Nix ecosystem that helps you take the declarative ideals of Nix/NixOS and apply them to your user’s home directory ($HOME). It plugs in (as a NixOS module) into an existing NixOS configuration, or can be installed on different Linux as a user service.

In order to make some parts of your configuration declarative, Home Manager might take control of certain file paths, or set various options in things like dconf.

Because of its job, Home Manager can make updating your configuration feel more error-prone, but don’t fear: Use a VCS like git to store your configuration. If your Home Manager setup breaks or acts strange for any reason, check the service status via systemctl status home-manager-$USER and journalctl -u home-manager-$USER.service. When in doubt, roll back your configuration and delete any files the errors are complaining about.

In our example, the user is named ana, and the machine is named gizmo.

In your Nix flake, add the input for Home Manager and ensure it follows the nixpkgs you’re using:

flake.nix
1
{
2
inputs = {
3
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
4
home-manager = {
5
url = "github:nix-community/home-manager";
6
inputs.nixpkgs.follows = "nixpkgs";
7
};
8
# ...
9
};
10
# ...
11
}

If you don’t already have GNOME configured, you can do that via a nixosModule like this:

flake.nix
1
{
2
# ...
3
outputs = { self, nixpkgs, home-manager }:
4
let
5
# ...
6
in {
7
# ...
8
nixosModules = {
9
# ...
10
gnome = { pkgs, ... }: {
11
config = {
12
services.xserver.enable = true;
13
services.xserver.displayManager.gdm.enable = true;
14
services.xserver.desktopManager.gnome.enable = true;
15
environment.gnome.excludePackages = (with pkgs; [
16
gnome-photos
17
gnome-tour
18
]) ++ (with pkgs.gnome; [
19
cheese # webcam tool
20
gnome-music
21
gedit # text editor
22
epiphany # web browser
23
geary # email reader
24
gnome-characters
25
tali # poker game
26
iagno # go game
27
hitori # sudoku game
28
atomix # puzzle game
29
yelp # Help view
30
gnome-contacts
31
gnome-initial-setup
32
]);
33
programs.dconf.enable = true;
34
environment.systemPackages = with pkgs; [
35
gnome.gnome-tweaks
36
]
37
};
38
};
39
};
40
};
41
}

Next, add a nixosModule that enables home-manager:

flake.nix
1
{
2
# ...
3
outputs = { self, nixpkgs, home-manager }:
4
let
5
# ...
6
in {
7
# ...
8
nixosModules = {
9
# ...
10
declarativeHome = { ... }: {
11
config = {
12
home-manager.useGlobalPkgs = true;
13
home-manager.useUserPackages = true;
14
};
15
};
16
};
17
};
18
}

I create a nixosModule for each of my users (you may have another way, feel free to do that):

flake.nix
1
{
2
# ...
3
outputs = { self, nixpkgs, home-manager }:
4
let
5
# ...
6
in {
7
# ...
8
nixosModules = {
9
# ...
10
users-ana = ./users/ana;
11
};
12
};
13
}

You can enable Home Manager for your user like this:

users/ana/default.nix
1
{ ... }:
2
3
{
4
config = {
5
home-manager.users.ana = ./home.nix;
6
users.users.ana = {
7
# ...
8
};
9
};
10
}

Now create the home.nix referenced above:

users/ana/home.nix
1
{ ... }:
2
3
{
4
home.username = "ana";
5
home.homeDirectory = "/home/ana";
6
# ...
7
programs.home-manager.enable = true;
8
home.stateVersion = "22.05";
9
}

Before enabling, ensure your nixosConfiguration has these modules, as well as home-manager.nixosModules.home-manager:

flake.nix
1
{
2
# ...
3
outputs = { self, nixpkgs, home-manager }:
4
let
5
# ...
6
in {
7
# ...
8
nixosConfigurations = {
9
gizmo = {
10
system = "aarch64-linux";
11
modules = with self.nixosModules; [
12
({ config = { nix.registry.nixpkgs.flake = nixpkgs; }; })
13
# ...
14
home-manager.nixosModules.home-manager
15
gnome
16
declarativeHome
17
users-ana
18
];
19
};
20
# ...
21
}
22
};
23
}

With that, you should be able to switch into the new configuration:

Terminal window
nixos-rebuild switch --flake .#gizmo

Validate it worked by reviewing the output:

Terminal window
$ systemctl status home-manager-ana.service
home-manager-ana.service - Home Manager environment for ana
Loaded: loaded (/etc/systemd/system/home-manager-ana.service; enabled; preset: enabled)
Active: active (exited) since Mon 2022-09-26 22:15:32 PDT; 2s ago
Process: 54958 ExecStart=/nix/store/nbhk58wgzvm2w8npi18qzjnn0xjcs3aw-hm-setup-env /nix/store/ig2vhy0pa4rvlkkdc511vyb6plp89x5a-home-manager-generation (code=exited, status=0/SU>
Main PID: 54958 (code=exited, status=0/SUCCESS)
IP: 0B in, 0B out
CPU: 377ms

If you see errors, dig deeper via journalctl -u home-manager-ana.service.

Declaratively configuring GNOME

There are a lot of knobs to set in GNOME.

GNOME breaks down into having GTK3/4 (which has UI, icon, and cursor themes), as well as an group of fairly tightly integrated components which are primarily configured by dconf, which most folks configure via gnome-settings or the settings panels of the relevant applications.

If you’re curious and wanted to watch how/if various dconf settings get changed when doing things, you can ‘watch’ while you click around an application:

Terminal window
$ dconf watch /
/org/gnome/control-center/last-panel
'network'
/system/proxy/mode
'none'
/org/gnome/control-center/last-panel
'background'
/org/gnome/desktop/interface/color-scheme
'default'
/org/gnome/desktop/interface/color-scheme
'prefer-dark'

If it’s too noisy, you can limit what you see by changing the / to a selector, for example /org/gnome/desktop/.

Home Manager offers a dconf module, which we can use to declaratively set these values.

GTK3/GTK4 cursor, icon, and window themes

To set the GTK icon theme, first search for a theme. this search or this one can help you find already packaged themes. You can click the “Source” button on any of those packages to see the expression used to package it, just in case you end up needing to make your own.

Here are the relevant settings, with some examples of what I’ve found that I like:

users/ana/home.nix
1
{ pkgs, ... }:
2
3
{
4
# ...
5
gtk = {
6
enable = true;
7
8
iconTheme = {
9
name = "Papirus-Dark";
10
package = pkgs.papirus-icon-theme;
11
};
12
13
theme = {
14
name = "palenight";
15
package = pkgs.palenight-theme;
16
};
17
18
cursorTheme = {
19
name = "Numix-Cursor";
20
package = pkgs.numix-cursor-theme;
21
};
22
23
gtk3.extraConfig = {
24
Settings = ''
25
gtk-application-prefer-dark-theme=1
26
'';
27
};
28
29
gtk4.extraConfig = {
30
Settings = ''
31
gtk-application-prefer-dark-theme=1
32
'';
33
};
34
};
35
36
home.sessionVariables.GTK_THEME = "palenight";
37
# ...
38
}

Want to set the GNOME Shell theme? We do this below after discussing dconf a bit more.

Finding the name field for a given package can be a bit inconsistent. 😔

Most of the time, you can guess it, or copy it from what shows up when you check with gnome-tweaks. Usually, you can find the package repository via the “Homepage” link on the searches listed above, or in the expression via the meta.homepage (example) or src fields (example). From there you can usually find some listing of the theme name (example).

To use a theme not already packaged, you’ll need to take a good starting point, edit it, then add your custom package to your flake:

flake.nix
1
{
2
# ...
3
outputs = { self, nixpkgs, home-manager }:
4
let
5
supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
6
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
7
# ...
8
in {
9
# ...
10
overlays.default = final: prev: {
11
my-artistanal-theme = final.callPackage ./packages/my-artisanal-theme { };
12
# ...
13
};
14
15
packages = forAllSystems
16
(system:
17
let
18
pkgs = import nixpkgs {
19
inherit system;
20
overlays = [ self.overlays.default ];
21
# ...
22
};
23
in
24
{
25
inherit (pkgs) my-artisanal-theme;
26
};
27
}

Once done, you should be able to use it by setting, for example, gtk.theme.package = pkgs.my-artisanal-theme.

Setting GNOME options

As mentioned above, most GNOME settings exist in dconf. Run dconf watch / and set whatever option you’re looking to declaratively persist, and observe the output:

Here’s what I see when I run gnome-settings and visit the ‘Appearance’ pane, then in ‘Style’ click between ‘Light’ and ‘Dark’.

Terminal window
$ dconf watch /
# ...
/org/gnome/desktop/interface/color-scheme
'default'
/org/gnome/desktop/interface/color-scheme
'prefer-dark'

Let’s try setting those from the command line an observing the gnome-settings window change:

Terminal window
dconf write /org/gnome/desktop/interface/color-scheme "'default'"
# Observe `gnome-settings` being light
dconf write /org/gnome/desktop/interface/color-scheme "'prefer-dark'"
# Observe `gnome-settings` being dark

Using this information, we can add this to the user configuration:

users/ana/home.nix
1
{ pkgs, ... }:
2
3
{
4
# ...
5
# Use `dconf watch /` to track stateful changes you are doing, then set them here.
6
dconf.settings = {
7
"org/gnome/desktop/interface" = {
8
color-scheme = "prefer-dark";
9
};
10
};
11
}

After a bit of tweaking, you might end up with something like this:

users/ana/home.nix
1
{ pkgs, ... }:
2
3
{
4
# ...
5
dconf.settings = {
6
# ...
7
"org/gnome/shell" = {
8
favorite-apps = [
9
"firefox.desktop"
10
"code.desktop"
11
"org.gnome.Terminal.desktop"
12
"spotify.desktop"
13
"virt-manager.desktop"
14
"org.gnome.Nautilus.desktop"
15
];
16
};
17
"org/gnome/desktop/interface" = {
18
color-scheme = "prefer-dark";
19
enable-hot-corners = false;
20
};
21
"org/gnome/desktop/wm/preferences" = {
22
workspace-names = [ "Main" ];
23
};
24
"org/gnome/desktop/background" = {
25
picture-uri = "file:///run/current-system/sw/share/backgrounds/gnome/vnc-l.png";
26
picture-uri-dark = "file:///run/current-system/sw/share/backgrounds/gnome/vnc-d.png";
27
};
28
"org/gnome/desktop/screensaver" = {
29
picture-uri = "file:///run/current-system/sw/share/backgrounds/gnome/vnc-d.png";
30
primary-color = "#3465a4";
31
secondary-color = "#000000";
32
};
33
};
34
}

GNOME Extensions

Once user extensions are enabled, extensions can be added to the home.packages set then enabled in dconf.settings."org/gnome/shell".enabled-extensions. After, they can be configured just as any other GNOME option as described just above.

users/ana/home.nix
1
{ pkgs, ... }:
2
3
{
4
# ...
5
dconf.settings = {
6
# ...
7
"org/gnome/shell" = {
8
disable-user-extensions = false;
9
10
# `gnome-extensions list` for a list
11
enabled-extensions = [
12
"user-theme@gnome-shell-extensions.gcampax.github.com"
13
"trayIconsReloaded@selfmade.pl"
14
"Vitals@CoreCoding.com"
15
"dash-to-panel@jderose9.github.com"
16
"sound-output-device-chooser@kgshank.net"
17
"space-bar@luchrioh"
18
];
19
};
20
};
21
22
home.packages = with pkgs; [
23
# ...
24
gnomeExtensions.user-themes
25
gnomeExtensions.tray-icons-reloaded
26
gnomeExtensions.vitals
27
gnomeExtensions.dash-to-panel
28
gnomeExtensions.sound-output-device-chooser
29
gnomeExtensions.space-bar
30
];
31
}

A GNOME Shell theme can be picked like this:

users/ana/home.nix
1
{ pkgs, ... }:
2
3
{
4
# ...
5
dconf.settings = {
6
# ...
7
"org/gnome/shell" = {
8
disable-user-extensions = false;
9
10
enabled-extensions = [
11
"user-theme@gnome-shell-extensions.gcampax.github.com"
12
];
13
14
"org/gnome/shell/extensions/user-theme" = {
15
name = "palenight";
16
};
17
};
18
};
19
20
home.packages = with pkgs; [
21
# ...
22
gnomeExtensions.user-themes
23
palenight-theme
24
];
25
}

Troubleshooting

I made a bunch of changes then ran nixos-rebuild switch, but some things didn’t change?

Log out and log back in. I found some things just didn’t set themselves until you did!

I accidentally altered GNOME settings which I’d set via Home Manager, and they aren’t changing back on a nixos-rebuild switch?

It is possible to change dconf values once set via Home Manager, if this happens and nixos-rebuild switch isn’t causing a change, you may need to restart the home-manager service with systemctl restart home-manager-$USER. If that fails, make a change (for example touch) your user home configuration first.

Some changes I made are not reflecting when I nixos-rebuild switch?

Check systemctl status home-manager-$USER and ensure the service started successfully, if not, dig in with journalctl -u home-manager-$USER and make sure to carefully read the error.

The setting I want isn’t tracked by Home Manager or dconf?

It might be complicated. You can try seeing if the program creates an entry in your XDG directories, such as ~/.config or ~/.cache. If so, you can often provision content into the file with the home.file option. Please note this will make the file unwritable, which may impact some programs.

Conclusion

Once various all of your preferred settings are persisted, it becomes straightforward to share key settings and extensions between multiple machines, or even architectures. By taking a little bit more time when configuring our system, we can avoid having to do it again.

Using these strategies I was able to have 3 different machines (an x86_64 desktop workstation, an x86_64 laptop, and a headed aarch64 server) with the same settings, and keep them all consistent so I can spend more time doing what I enjoy (tinkering) instead of what I don’t (re-configuring machines).

If you’re looking for a complete home configuration, you can check out mine here.

Here’s what it looks like:


Share
Avatar for Ana Hobden
Written by Ana Hobden

Ana is a hacker working in the Rust and Nix ecosystems. She's from Lək̓ʷəŋən territory in the Pacific Northwest, and holds a B.Sc. in Computer Science from the University of Victoria. She takes care of a golden retriever named Nami with her partner.