Questions about package management

Hello,

I want to optimize my nixos build as I am running it of a flash medium and the less it weighs, the longer it’ll live.

Querying packages

I have started with querying all installed packages. However, I get different counts depending on which method I use:

[root@S720:/etc/nixos]# nix-store -q --requisites /run/current-system | wc -l
973
[root@S720:/etc/nixos]# neofetch  | grep Pack
                                              Packages: 641 (nix-system) 

Why does that happen?

Debloat

I wanted to remove all wireless capabilities and the sound server.
For the benchmarks I am using bluez, wpa_supplicant and pipewire.

I have tried playing around with the config and I cannot seem to turn them off…
I do not get any conficting statements in my config, the default is <i-dont-want-this>.enable = false; and still I have the unnescessary packages:

# nix-store -q --requisites /run/current-system | grep -P "wpa|bluez|pipe"
/nix/store/j6mwswpa6zqhdm1lm2lv9iix3arn774g-glibc-2.38-27
/nix/store/dp9qpfqm080qhisnfrmwbhv34g1azys6-bluez-5.70
/nix/store/34p1ibgfjihkf5gbfbrq62qgyaf8pmq7-pipewire-1.0.1
/nix/store/qxv3raf90syc7v0av1q6h6jhxhnh8v3c-wpa_supplicant-2.10
/nix/store/llg80dgdc9bnx15i36iyx7ivp598vx7g-libpipeline-1.5.7
/nix/store/ahv1bgr56k1jmjzak8cijyrfvq5q5q14-lesspipe-2.10

Querying package details

On Arch-based distros there is pacman --query --info <package>, which work something like this:

$ pacman -Qi bluez   
Name            : bluez
Version         : 5.72-2
Description     : Daemons for the bluetooth protocol stack
Architecture    : x86_64
URL             : http://www.bluez.org/
Licenses        : GPL-2.0-only
Groups          : None
Provides        : None
Depends On      : dbus  glib2  alsa-lib  glibc
Optional Deps   : None
Required By     : fwupd
Optional For    : networkmanager
Conflicts With  : None
Replaces        : None

Here I can clearly see the dependencies and what requires this package.
I have read man nix-store-query and I still am not sure what flags will accomplish that.
Can somebody help me figure this out?

On Nix you can also use nix why-depends to get the dependency chain between e.g. bluez and /run/current-system or explore the dependency tree with GitHub - utdemir/nix-tree: Interactively browse dependency graphs of Nix derivations.

3 Likes

neofetch shows you only the subset of store paths that are “packages”: https://github.com/dylanaraps/neofetch/blob/ccd5d9f52609bbdcd5d8fa78c4fdb0f12954125f/neofetch#L1612

A usual system closure will contain some store paths that are not really “packages” in the traditional sense, like the activation script, configuration files, fstab related things, oneshot systemd services, …

That said, the neofetch command seems to also exclude kernel & kernel modules, which would be included on traditional distros. Either way, the point is it tries to get a definition of “package” closer to what you would expect, unlike nix-store which uses the technical definition that is actually useful in nix land.

1 Like

I have felt like that’s the case. Thanks for the detailed explanation.

nix-tree

Daaaamn, this is so neat!

I’ll play around with it for a while :dd

Before I understand it though, I would like to grasp the nix why-depends.

# nix why-depends bluez /run/current-system
error: experimental Nix feature 'nix-command' is disabled; use '--extra-experimental-features nix-command' to override
# nix why-depends bluez /run/current-system --extra-experimental-features nix-command
error: experimental Nix feature 'flakes' is disabled; use '--extra-experimental-features flakes' to override
# nix why-depends bluez /run/current-system --extra-experimental-features nix-command --extra-experimental-features flakes
error: cannot find flake 'flake:bluez' in the flake registries

Why do I need to enable so many experimental stuff for (imo) a basic use case?

nix why-depends wants a store path. You’re giving it bluez, which isn’t a path, so nix assumes it’s a flake. If you give it a store path instead you will only need to enable the experimental nix-command feature.

You also have your arguments reversed, if you want to know why the current system profile depends on (some specific) bluez.

1 Like

Okay, I see.

Now, comparing two outputs

Pacman

$ pactree -r bluez
bluez
└─fwupd

I know that if I remove fwupd I can remove bluez:

# pacman -Rsu fwupd

checking dependencies...
:: networkmanager optionally requires bluez: Bluetooth support

Package (10)  Old Version  Net Change

bluez         5.72-2        -1,62 MiB
flashrom      1.2-4         -1,72 MiB
fwupd-efi     1.4-1         -0,06 MiB
gcab          1.6-1         -0,40 MiB
libcbor       0.10.2-1      -0,16 MiB
libgusb       0.4.8-1       -2,64 MiB
libjcat       0.2.1-1       -0,55 MiB
libsmbios     2.4.3-6       -1,03 MiB
passim        0.1.5-1       -0,32 MiB
fwupd         1.9.12-1     -12,40 MiB

Total Removed Size:  20,89 MiB

Nix

# nix why-depends /run/current-system /nix/store/dp9qpfqm080qhisnfrmwbhv34g1azys6-bluez-5.70 --extra-experimental-features nix-command
/nix/store/33nsrxry5hjz0l77p9cnw404i9qga9qs-nixos-system-S720-23.11.4030.9f2ee8c91ac4
└───/nix/store/fs16dlbgl808zb48r7xal8icbhm1386i-system-path
    └───/nix/store/6k6m43c17wyijsgq2hq5i7xl99vh0frr-NetworkManager-openconnect-1.2.10
        └───/nix/store/2ifw89cp6yjiv3n49y3wlkcf55ddnhya-webkitgtk-2.42.4+abi=4.1
            └───/nix/store/6nh5lbkd06b6i0r1rkan9i1lv4v60p02-gst-plugins-bad-1.22.8
                └───/nix/store/il9f8kd6m42k20ah3a8p2dnwqcai2i2c-openal-soft-1.23.1
                    └───/nix/store/34p1ibgfjihkf5gbfbrq62qgyaf8pmq7-pipewire-1.0.1
                        └───/nix/store/dp9qpfqm080qhisnfrmwbhv34g1azys6-bluez-5.70

Now I see that pipewire requires bluez. Swapping /run/current-system for /nix/store/[...]-pipewire-[...] only cuts the tree.

Now, should I assume that bluez is a hard dependency of pipewire?
Is there a thing as an optional dependency with nix?

Also, what is the /nix/store/[...]-system-path? Is it a collection of stuff required for the system to run?

I can see that system-path requires NetworkManager’s openconnect & openvpn?
This seems nuts! Why is that the case and how is that connected to bluez?

EDIT

I have disabled NetworkManager which seems to have removed like 300 packages from my system (a third XD). Everything seems to work after a restart.

Now I wonder why do nixos-system requires 3 perls and 4 are in store…

# nix-store -qR /run/current-system | grep perl-5.38.2-env
/nix/store/6yzim4nvw51h36v4mx6lv7xm2y1qlbg0-perl-5.38.2-env
/nix/store/d6cg56822bwv32y1q14j2mal3c9qmyhh-perl-5.38.2-env
/nix/store/c0lcncwk0wwyh1hvdmkhk35h0aq8f2bw-perl-5.38.2-env
/nix/store/rvkwj81mbq4agigvx9m7rxrp3ngqkl0v-perl-5.38.2-env
# nix why-depends /run/current-system /nix/store/6yzim4nvw51h36v4mx6lv7xm2y1qlbg0-perl-5.38.2-env
/nix/store/h38zzh9hbf02x8sq488i7a3183bybh3l-nixos-system-S720-23.11.4030.9f2ee8c91ac4
└───/nix/store/6yzim4nvw51h36v4mx6lv7xm2y1qlbg0-perl-5.38.2-env

I mean… I see that system requires it, but why?

These are not full Perls. It is Perls with certain packages available, but different set of packages for each, tailored for their respective consumers. With hardlinking in the store enabled, the storage overhead should be minimal.

Regarding optional dependencies: Yes, there are ways for runtime detection of dependencies, but those are reserved for very special cases, such as OpenGL. Otherwise all dependencies are declared by the configuration and package derivations and during evaluation they are converted into hard dependencies to ensure that each configuration can be rebuilt and always delivers the same system.

1 Like

Packages often have arguments to disable features you don’t want. Pipewire has one for bluez:

services.pipewire.package = pkgs.pipewire.override { bluezSupport = false; };

Usually you need to look at the source to see this, but there’s a convenient link from search.nixos.org, so usually not a big deal.

It’s everything NixOS adds to $PATH, i.e., all packages on your system that put something in their /bin. Most likely the packages are added to environment.systemPackages.

It can be hard to tell where exactly that happens - modules can do it, and why-depends only looks at store paths, so it cannot deduce how your configuration added something to the path.

Your best bet is removing some modules you suspect and seeing if they disappear - note you can use the result symlink of a nixos-rebuild build instead of /run/current-system for experiments like this.

Networkmanager likely is the culprit indeed, it has bluez and vpn support by default, and no way to disable them. Maybe there’s a way to disable that with .overrideAttrs, but you’d need to figure out how to build networkmanager to do that.

2 Likes

On dependencies

That is a great help. Thanks!

On system-path

It’s everything NixOS adds to $PATH, i.e., all packages on your system that put something in their /bin. Most likely the packages are added to environment.systemPackages.

It can be hard to tell where exactly that happens - modules can do it, and why-depends only looks at store paths, so it cannot deduce how your configuration added something to the path.

Makes sense.

Your best bet is removing some modules you suspect and seeing if they disappear - note you can use the result symlink of a nixos-rebuild build instead of /run/current-system for experiments like this.

I did not know what was the use case for it. Now I do. Thanks!

Networkmanager likely is the culprit indeed

Yep. I have the luxury of removing it.
The output of nix-store -qR /run/current-system | wc -l changed from ~980 to ~670!

These are not full Perls. It is Perls with certain packages available, but different set of packages for each, tailored for their respective consumers. With hardlinking in the store enabled, the storage overhead should be minimal.

Okay… they seem to not differ a lot though.

I was thinking if I could tidy up the second level of nix-tree and merging those would do that. However with your explanation I understand that it would go against the Nix way of doing things.

Do you think that it makes sense to put bash under bash-interactive?
(as in pacman --database --asdeps bash)

Regarding optional dependencies: Yes, there are ways for runtime detection of dependencies, but those are reserved for very special cases, such as OpenGL. Otherwise all dependencies are declared by the configuration and package derivations and during evaluation they are converted into hard dependencies to ensure that each configuration can be rebuilt and always delivers the same system.

So all the dependencies in the pacman -Qi are runtime detected?

Nix’ store is very different from what pacman’s database does. Everything in /nix/store are the actual files you are interacting with while using NixOS, either because a symlink points at them from a more typical location (e.g. config files in /etc), or because they are directly executed (through some systemd unit, loaded as shared libraries by a binary on $PATH, etc.).

Touching any paths in /nix/store without nix tools will immediately make those packages no longer function. Aggressively using nix-store to delete or move paths without the higher level functions will also break your system, though at least it won’t break the store’s consistency (and require hours of debugging the store).

Don’t touch the store.

What @wamserma is referring to by “hard links” is the actual Linux feature that allows multiple paths to point to the same file. If you have nix store optimization enabled, nix will automatically ensure that any duplicates are only represented once on the underlying file system (kind of like a symlink, but instead of creating a file that contains a bit of text defining another path Linux will just increase a reference counter on your filesystem).

So, if you see any paths that seemingly look the same, they are probably the same on disk, and if they are not the differences are significant enough that trying to merge them would result in something breaking.

Nix’ principle is ultimately that while, yes, ideally we build systems where everything depends on just one version of a dependency, often enough that isn’t possible, so sometimes you either need to not package something or you need a way to have two copies of a dependency. NixOS does its best to reduce occurrences of that.

In the case of bash vs bash-interactive, pretty sure the only difference is that one installs readline, so the actual storage increase will be negligible.

Don’t touch the store!

No, but the way optional dependencies work on other distros is that when you run the package, it has some code inside of itself that turns on functionality if another package happens to be installed. Other distros list such dependencies as “optional”.

NixOS doesn’t have this concept at all, it just has very convenient methods for changing the package completely (e.g. .override { bluezSupport = false; };.

All of this is kind of an implementation detail, though, so I get that this is a confusing statement.

2 Likes

Right.
I get the feeling that benchmarking package optimalization by the package count might not be a good solution for Nix.
In that case, how can I benchmark how optimal my system is storage wise?
du -hs /nix/store/ obviously does not make sense.

Not at all. I like learning and this thread brought me some good info!

Why not? du is smart about multiple hard links to a single file, if that’s what you’re worried about.

I would like to measure the size of my running system.
If I understand things correctly, /nix/store is where all the files required for generating systems are, so I would need to collect garbage and optimize each time before the check.
This seems unhandy to me so I thought that there must be a better way to do it.

If you want just the size of a particular closure (like /run/current-system), nix-du.

Note that nix-du tries to take store optimization into account, but you should read the relevant README section for caveats.

Or if you want something less graphical,

nix-store --query --requisites /run/current-system | tr '\n' '\0' | du -bcs --files0-from=- | tail -1
2 Likes

:hushed: :face_with_open_eyes_and_hand_over_mouth:
This made my jaw drop.

It seems like not the ideal tool for my use case.

The commands that you have suggested suit my needs better, but I will look into the nix-du later.

Many thanks!

I think I can consider this thread closed now.

There is also nix path-info -Sh /run/current-system which will yield the closure size of your current system (or any other path in the nix store).

1 Like

I had forgotten about nix path-info! Though I’ll note that it gives somewhat larger results on my system than the du-based pipeline I used; I suspect it’s measuring NAR size or something rather than literal disk space.