User guide
While the project overview should be enough to get you started, this document contain all of the information you may need to make full use of wrapper-manager.
What is wrapper-manager?
Simply put, this is a declarative interface built on top of makeWrapper
and company plus some other integrations as we’ll explore in this document.
It is comparable to NixOS and home-manager in a way that it compiles into an operating system and a home environment respectively, wrapper-manager compiles the module environment into a package.
Speaking of which, wrapper-manager is meant to be composed in larger-scoped environments such as NixOS and home-manager, mainly by including wrapper-manager packages in environment.systemPackages
and home.packages
but you could also make them as a standalone package.
Getting started
But first, we’ll have to have wrapper-manager in our configuration. There are multiple ways to do so as already shown from the Project overview so we’ll go ahead with what should you do after it. There are multiple ways to build a wrapper-manager package:
-
You can manually build one with
wrapperManagerLib.env.build
. It needs a nixpkgs instance (pkgs
) and a list of additional modules (modules
) with an optional attrset of static of module arguments (specialArgs
).Here’s an example of using the function.
{ pkgs ? import <nixpkgs> { } }: let wrapper-manager = import <wrapper-manager-fds> { }; wrapperManagerLib = import wrapper-manager.wrapperManagerLib { inherit pkgs; }; in wrapperManagerLib.env.build { inherit pkgs; modules = [ ./config/wrapper.nix ]; specialArgs = { hello = "WORLD"; }; }
-
There is also
wrapperManagerLib.env.eval
which is just evaluating a wrapper-manager package and nothing else. [1] Useful for creating your own integrations in other module environments. It should accept the same arguments as the build function. -
You can take advantage of the integration modules for home-manager and NixOS and it automatically adds them into their respective package list (i.e.,
home.packages
andenvironment.systemPackages
). They are mainly configured throughwrapper-manager.packages
which is an attribute set representing a set of package. For more details, you can see With NixOS and home-manager.
Using wrapper-manager
The module environment of wrapper-manager is the main interface of the project. In the following code, we’ll define two wrappers around yt-dlp.
{ lib, pkgs, ... }:
{
wrappers.yt-dlp-audio = {
arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
prependArgs = [
"--no-overwrite"
"--extract-audio"
"--format" "bestaudio"
"--audio-format" "opus"
"--output" "'%(album_artists.0,artists.0)s/%(album,playlist)s/%(track_number,playlist_index)d-%(track,title)s.%(ext)s'"
"--download-archive" "archive"
"--embed-thumbnail"
"--add-metadata"
];
};
# You could also lessen the code above by passing `--config-location` to
# yt-dlp and move them into a separate file. This is what wrapper-manager is
# made for, after all.
wrappers.yt-dlp-video = {
arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
prependArgs = [
"--config-location" ../../config/yt-dlp/video.conf
];
};
}
If we build the configuration, it should result in a derivation containing two executables.
$ ls ./result/bin
yt-dlp-audio yt-dlp-video
By default, these wrappers are compiled with makeBinaryWrapper
.
You could make into a shell-based wrapper by changing build.variant
value into shell
.
If you want to include the original yt-dlp package as part of the standalone package, just pass the package as part of basePackages
.
{ lib, pkgs, ... }:
{
# ...
basePackages = [ pkgs.yt-dlp ];
}
Another thing to keep in mind is wrapper-manager packages have the library set available as wrapperManagerLib
module argument.
This is mainly useful for setting values within the configuration.
{ config, lib, pkgs, wrapperManagerLib, ... }:
{
# It is used for setting values in certain modules options.
wrappers.yt-dlp-video = {
xdg.dataDirs = wrapperManagerLib.getXdgDataDirs [
pkgs.emacs
pkgs.neovim
];
pathAdd = wrapperManagerLib.getBin (with pkgs; [
yt-dlp
gallery-dl
]);
};
# Another nicety is to create a wraparound wrapper like in the following code
# where we wrap tmux to be used with boxxy.
wrappers.tmux = wrapperManagerLib.makeWraparound {
arg0 = lib.getExe' pkgs.tmux "tmux";
under = lib.getExe' pkgs.boxxy "boxxy";
underFlags = [ "--rule" "~/.tmux.conf:~/.config/tmux/tmux.conf" ];
underSeparator = "--";
};
}
One of the typical thing to set in a wrapper script is the environment variables.
You could set them from environment.variables
to set it for all of the wrappers.
For wrapper-specific values, just go for wrappers.<name>.env
.
{ config, lib, pkgs, wrapperManagerLib, ... }: {
# Set a envvar and its value.
environment.variables.LOG_STYLE.value = "systemd";
# By default, the values are forcibly set. You could set as the default value
# if unset by setting the action to `set-default`.
environment.variables.LOG_STYLE.action = "set-default";
# Unset an environment variable. Its value will be ignored.
environment.variables.MODS_DIR.action = "unset";
# Set a list of separator-delimited values, typically for PATH,
# XDG_CONFIG_DIRS, XDG_DATA_DIRS, and the like.
environment.variables.PATH = {
action = "prefix";
separator = ":";
value = wrapperManagerLib.getBin (with pkgs; [
yt-dlp
neofetch
]);
};
# For wrapper-specific values, it has the same interface, just different attribute.
wrappers.name.env.LOG_STYLE.value = "systemd";
}
XDG integration
This environment comes with various features for XDG desktop integrations.
These does not necessarily implements the feature itself but rather creates the files typically recognized with the wider-scoped list of packages (e.g., home.packages
for home-manager, environment.systemPackages
for NixOS).
As one of those features, you can create XDG desktop entries to be exported to $out/share/applications/$NAME.desktop
in the output path.
This uses the makeDesktopItem
builder from nixpkgs so the settings should be the same with those.
Here’s an example of creating a wrapper-manager package with a sole desktop entry for Firefox with the additional configuration to be opened within GNOME Shell.
{ config, lib, pkgs, ... }: {
xdg.desktopEntries.firefox = {
name = "Firefox";
genericName = "Web browser";
exec = "firefox %u";
terminal = false;
categories = [ "Application" "Network" "WebBrowser" ];
mimeTypes = [ "text/html" "text/xml" ];
extraConfig."X-GNOME-Autostart-Phase" = "WindowManager";
keywords = [ "Web" "Browser" ];
startupNotify = false;
startupWMClass = "MyOwnClass";
};
}
You could also automatically create a desktop entry for one of your wrappers by setting wrappers.<name>.xdg.desktopEntry.enable
to true
and configuring the entry with wrappers.<name>.xdg.desktopEntry.settings
.
It simply sets some of those settings automatically for you such as the Name=
, DesktopName=
, and Exec=
but you’ll have to set the rest of it yourself for full control what’s in there.
{ lib, pkgs, ... }: {
wrappers.nvim = {
arg0 = lib.getExe' pkgs.neovim "nvim";
xdg.desktopEntry = {
enable = true;
settings = {
terminal = true;
extraConfig."X-GNOME-Autostart-Phase" = "WindowManager";
keywords = [ "Text editor" ];
startupNotify = false;
startupWMClass = "MyOwnClass";
};
};
};
}
Another XDG-related feature for wrapper-manager is adding paths to a couple of XDG search paths including for XDG_CONFIG_DIRS
and XDG_DATA_DIRS
.
You can either add them for all wrappers or set them per-wrapper.
{ config, lib, pkgs, wrapperManagerLib, ... }: let
inherit (wrapperManagerLib) getXdgDataDirs getXdgConfigDirs;
searchPaths = with pkgs; [ yt-dlp neofetch ];
in {
xdg.configDirs = getXdgConfigDirs searchPaths;
xdg.dataDirs = getXdgDataDirs searchPaths;
wrappers.nvim.xdg.configDirs = getXdgConfigDirs searchPaths;
wrappers.emacs.xdg.dataDirs = getXdgDataDirs searchPaths;
}
Some more other integrations
Being a module environment specializing on creating wrappers, there are some other integrations that you could use.
One of them is setting arbitrary files within the output path of the derivation with files
.
The interface should be similar to NixOS' environment.etc
or home-manager’s home.file
module option.
{ config, lib, ... }: {
files."etc/xdg/custom-application".text = ''
HELLO=WORLD
LOCATION=Inside of your house
'';
# Just take note any files in `$out/bin` will be overridden by the wrappers
# if they have the same name.
files."bin/what" = {
text = "echo WHAT $@";
mode = "0755";
};
files."share/example".source = ./docs/example;
}
One of them is the setting the locale archive which is practically required for every Nix-built applications.
To enable them, you’ll have to set locale.enable
to true
to set it for all wrappers but you can specifically set them with wrappers.<name>.locale.enable
.
You could also change the locale archive package with locale.package
.
As a standalone package
wrapper-manager packages can be compiled as a standalone package to be included as part of the typical Nix operations (e.g., makeShell
, as part of packages
flake output, as part of environment.systemPackages
in NixOS).
That part is easy, just build it with wrapper-manager build
function located at its library set.
The following code listing shows an example of it including a wrapper-manager config as part of the devshell. Just remember that wrapper-manager configurations primarily ends as a package.
{ pkgs ? import <nixpkgs> { }, wrapperManager ? import <wrapper-manager-fds> { } }:
let
inherit (pkgs) lib;
gems = pkgs.bundlerEnv {
name = "wrapper-manager-fds-gem-env";
ruby = pkgs.ruby_3_1;
gemdir = ./.;
};
asciidoctorWrapped = wrapperManager.lib.build {
inherit pkgs;
modules = lib.singleton {
wrappers.asciidoctor = {
arg0 = lib.getExe' gems "asciidoctor";
prependArgs = [ "-r" "asciidoctor-diagram" "-T" ./templates ];
};
};
};
in
pkgs.mkShell {
packages = with pkgs; [
asciidoctorWrapped
treefmt
gems
gems.wrappedRuby
];
}
With NixOS and home-manager
wrapper-manager also comes with integrations for NixOS and home-manager. You’ll have to import the respective environment modules for them somewhere in your configuration. Here’s an example of importing it into a NixOS and home-manager config with flakes.
{
# ...
inputs.wrapper-manager.url = "github:foo-dogsquared/nix-wrapper-manager";
outputs = inputs:
let
inherit (inputs.nixpkgs) lib;
inherit (lib) nixosSystem;
inherit (inputs.home-manager.lib) homeManagerConfiguration;
in
{
nixosConfigurations.desktop = nixosSystem {
modules = [
inputs.wrapper-manager.nixosModules.wrapper-manager
];
};
homeConfigurations.user = homeManagerConfiguration {
modules = [
inputs.wrapper-manager.homeModules.wrapper-manager
];
};
};
}
For the most part, the integration modules are mostly the same.
As an example, you can create wrappers through wrapper-manager.packages
where it is expected to be an attribute set of wrapper-manager configurations.
{ lib, config, ... }:
{
wrapper-manager.packages.writing.imports = [
../configs/wrapper-manager/writing
];
wrapper-manager.packages.music-setup = {
wrappers.beets = {
arg0 = lib.getExe' pkgs.beets "beet";
prependArgs = [ "--config" ./config/beets/config.yml ];
};
};
wrapper-manager.packages.archive-setup = { lib, pkgs, ... }: {
wrappers.gallery-dl = {
arg0 = lib.getExe' pkgs.gallery-dl "gallery-dl";
prependArgs = [ ];
};
wrappers.yt-dlp-audio = {
arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
prependArgs = [
"--config-location" ./configs/yt-dlp/audio.conf
];
};
};
}
Aside from an easy way to create wrappers instead of manually invoking the building function from wrapper-manager, there’s also another nicety with the integration module.
The wrapper-manager configuration will have an additional module argument depending on the environment: nixosConfig
for NixOS and hmConfig
for home-manager.
This is useful for dynamic and conditional configurations with the wider-scoped environment.
Additionally, with documentation packages alongside the environment similar to NixOS and home-manager.
-
There is a manpage which you can install by setting
wrapper-manager.documentation.manpage.enable
totrue
. It is available to be viewed aswrapper-manager.nix(5)
(i.e.,man 5 wrapper-manager.nix
). -
An HTML manual can be brought over by setting
wrapper-manager.documentation.html.enable
totrue
. The HTML manual package has a desktop entry file titledwrapper-manager manual
in the common application launchers (e.g., rofi, GNOME Shell app launcher).
You can also set additional modules to be included with wrapper-manager.documentation.extraModules
in case you have custom wrapper-manager modules.
Differences from original wrapper-manager
Being a reimagining of wrapper-manager, there are some major differences between them.
The main difference is the way how the final output is built.
In the original version, each of the specified wrappers under wrappers
are individually built.
In the reimagined version, these are consolidated into one build step since makeWrapper
allows us to do so.
As a side effect, there’s no options that could require to be built individually such as wrappers.<name>.basePackage
, wrappers.<name>.renames
, wrappers.<name>.overrideAttrs
, and wrappers.<name>.extraPackages
.
Another difference is the original version also handles some cases of fixing XDG desktop entries in the final output. In wrapper-manager-fds, this case is absent since its maintainer at the time (foo-dogsquared) deemed it "a pain in the ass" to handle especially that…
-
There are more use cases to handle such as multiple desktop entries for multiple reasons.
-
Most desktop metadata is pretty much usable even with the custom wrapper without cleaning them.
-
This need is less emphasized since wrapper-manager-fds also allows you to make XDG desktop entries in the config itself anyways.
If you’re interested in migrating to this version, here’s a quicktable of individual differences that might interest you.
How arg0
is set per-wrapper
{ lib, pkgs, ... }:
{
wrappers.hello.basePackage = pkgs.hello;
}
{ lib, pkgs, ... }:
{
wrappers.hello.arg0 = lib.getExe' pkgs.hello "hello";
}
Renaming executables per-wrapper
{ lib, pkgs, ... }:
{
wrappers.hello.renames.hello = "hello-customized";
}
In wrapper-manager-fds, there’s no renaming step as we already let the user name the executable.
{ lib, pkgs, ... }:
{
wrappers.hello.executableName = "hello-customized";
# You could also change the attrname.
wrappers.hello-customized.arg0 = "${pkgs.hello}/bin/hello";
}
Setting (and unsetting) environment variables per-wrapper
{ lib, pkgs, ... }:
{
# The default action is to set the value if not yet set.
wrappers.hello.env.CUSTOM_ENV_VAR.value = "HELLO";
# You can force it with the following.
wrappers.hello.env.CUSTOM_ENV_VAR.force = true;
# You can also unset it by setting the value to null.
wrappers.hello.env.CUSTOM_ENV_VAR.value = lib.mkForce null;
}
{ lib, pkgs, ... }:
{
# On the other hand, wrapper-manager-fds forces it by default.
wrappers.hello.env.CUSTOM_ENV_VAR.value = "HELLO";
# But you can conditionally set it with...
wrappers.hello.env.CUSTOM_ENV_VAR.action = "set-default";
# If you want to unset it, set the following code.
wrappers.hello.env.CUSTOM_ENV_VAR.action = lib.mkForce "unset";
}
Adding PATH env values
{ config, lib, pkgs, ... }:
{
wrappers.hello.pathAdd = with pkgs; [
yt-dlp
gallery-dl
];
}
{ config, lib, pkgs, wrapperManagerLib, ... }:
{
wrappers.hello.pathAdd = wrapperManagerLib.getBin (with pkgs; [
yt-dlp
gallery-dl
]);
}
wrapperManagerLib.env.build
is just a wrapper around this function getting the toplevel package.