Development
First of all, here's a very quick overview over the codebase:
examples/
each folder in here should contain aflake.nix
as a user would write it for their configuration. Each such example flake will automatically be evaluated and rendered in the documentation.icons/
contains all the service, device and interface icons. Placing a new file here will automatically register it.nixos/
contains anything related to the provided NixOS module. Mostly information extractors.options/
contains shared options. Shared means that all of these options will be present both in the global topology module and also in each NixOS module under thetopology
option, which will be merged into the global topology. If you need NixOS specific options (like extractors) or topology specific options (like renderers) have a look atnixos/module.nix
andtopology/default.nix
.pkgs/
contains our nixpkgs overlay and packages required to render the graphs.topology/
contains anything related to the global topology module. This is where the NixOS configurations are merged into the global topology and where the renderers are defined.
Criteria for new extractors and service extractors
I'm generally happy to accept any extractor, but please make sure they meet the following criteria:
-
It must provide an
enable
option so it can be disabled. It may be enabled by default, if it is a no-op for systems that don't use the relevant parts. The services extractor for example guards all assignments behindmkIf config.services.<service_name>.enable
to make sure that it works for systems that don't use a particular service. -
The default settings of an extractor (or any part of it) should try not to cause clutter in the generated cards. Take for example OpenSSH which is usually enabled on all machines. Showing it by default would cause a lot of unnecessary clutter, so it should be guarded behind an additional option that is disabled by default. Same goes for common local services like smartd, fwupd, ...
-
Extractors should be made for things available in nixpkgs or the broader list of community projects. So I'm happy to accept extractors for projects like (microvm.nix, disko, ...) but if you want to extract information from very niche or personal projects, it might be better to include the extractor in there.
If you write an extractor for such a third party dependency, make sure that it is either disabled by default, or guarded in a way so that it doesn't affect vanilla systems. Check the microvm extractor for an example which makes sure that the microvm module is acutually used on the target system.
Adding a new extractor
To add a whole new extractor, all you need to do is to create nixos/extractors/foo.nix
,
and add a new option for your extractor:
{ config, lib, ... }: let
inherit (lib) mkEnableOption mkIf;
in {
options.topology.extractors.foo.enable = mkEnableOption "topology foo extractor" // {default = true;};
config = mkIf (config.topology.extractors.foo.enable && /* additional checks if necessary */) {
topology = {
# Modify topology based on the information you gathered
};
};
}
The file will automatically be included by the main NixOS module.
Adding a service to the services extractor
To add a new service to the services extractor, all you need to do is to add the relevant attribute to the body of the extractor. Please keep them sorted alphabetically.
Imagine we want to add support for vaultwarden, we would start by extracting some information, while making sure there's a sensible fallback value at all times if the value isn't set. Don't depend on any value being set, only observe what the user has actually configured!
vaultwarden = let
# Extract domain, address and port if they were set
domain = config.services.vaultwarden.config.domain or config.services.vaultwarden.config.DOMAIN or null;
address = config.services.vaultwarden.config.rocketAddress or config.services.vaultwarden.config.ROCKET_ADDRESS or null;
port = config.services.vaultwarden.config.rocketPort or config.services.vaultwarden.config.ROCKET_PORT or null;
in
# Only assign anything if the service is enabled
mkIf config.services.vaultwarden.enable {
# The service's proper name.
name = "Vaultwarden";
# An icon from the icon registry. We will add a new icon for the service later.
icon = "services.vaultwarden";
# One line of information that should be shown below the service name.
# Usually this should be the hosted domain name (if it is known), or very very important information.
# For vaultwarden, we the domain in the config. If you are unsure for your service, just leave
# it out so users can set it manually via topology.self.services.<service>.info = "...";
info = mkIf (domain != null) domain;
# In details you can add more information specific to the service.
# Currently I tried to include a `listen` detail for any service listening on an address/port.
# Samba for example shows the configured shares and nginx the configured reverse proxies.
# If you are unsure what to do here, just leave it out.
details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";};
};
Now we still need to add an icon to the registry, but then the extractor is finished.
nix-topology supports svg, png and jpeg files, but we prefer SVG wherever possible.
Usually you should be able to find the original logo of any project in their main repository
on GitHub by searching for .svg
by pressing t.
But before we put it in icons/services/<service_name>.svg
, we should optimize the svg
and make sure it has a square viewBox="..."
property set. For this I recommend passing it through
scour
and svgo
once, this will do all of the work automatically, except for making sure that the
logo is square:
nix-shell -p nodePackages.svgo scour
scour --enable-viewboxing -i original.svg -o tmp.svg
svgo -i tmp.svg -o service_name.svg
If you open the file and see a viewBox like viewBox="0 0 100 100"
, then you are ready to go.
The actual numbers don't matter, but the last two (width and height) should be the same. If they
are not, open the file in any svg editor of your liking, for example Inkscape or boxy-svg.com
and make it square manually. (In theory you can also just increase the smaller number and x
or y
by half of that
in the viewBox by hand. Run it through svgo
another time and you are all set.
Adding an example
Examples are currently our tests. They are automatically built together with the documentation and the rendered result is also included in the docs. If you have made a complex extractor, you might want to test it by creating a new example.
All you need to do for that to happen is to create a flake.nix
in a new example folder,
for example examples/foo/flake.nix
. Just copy the simple example and start from there.
The flake should be a regular flake containing nixosConfigurations
, just like what a user would
write. Beware that you currently cannot add inputs to that flake, let me know if you need that.
To build the example, just build the documentation:
nix build .#docs`