---
title: Project overview
---
= nix-wrapper-manager-fds
:toc:

:current-version: 0.1.0
:github-repo: nix-module-wrapper-manager-fds
:github-full: foo-dogsquared/{github-repo}
:remote-git-repo: https://github.com/{github-full}
:docs-site: https://foo-dogsquared.github.io/{github-repo}


wrapper-manager-fds is foodogsquared's reimagining of https://github.com/viperML/wrapper-manager/[wrapper-manager].

[CAUTION]
====
wrapper-manager-fds is considered unstable and the author is still freely experimenting with various aspects of the project including the module options, library set API, and ease of use for third-partyuse.
Expect constant breakages here and put on a hard hat before exploring this part of the town.
====

As a recap, the aim of wrapper-manager is to pave a way of configuring Nix environments through wrappers.
There has been https://discourse.nixos.org/t/declarative-wrappers/1775[interest on] https://github.com/NixOS/rfcs/pull/75[this feature] footnote:[I mean, a part of the nixpkgs package set has dedicated wrappers for some packages such as GIMP, Inkscape, and Blender.] and I sometimes configure programs through wrappers instead of the typical module system.
It has its use cases such as quickly creating variants of the same program for specific reasons (for example, creating specialized versions of yt-dlp for downloading audio, video, and more).
But why not create something more than that?
And that's how wrapper-manager-fds came to be.

[#installation]
== Installation

[#installation-channels]
=== Channels

You can install wrapper-manager-fds with https://zero-to-nix.com/concepts/channels[Nix channels].

[source, shell, subs=attributes]
----
nix-channel --add {remote-git-repo}/archive/master.tar.gz wrapper-manager-fds
nix-channel --update
----

Then in your environment configuration, you'll have to import the user entrypoint.
For more details, see <<user-entrypoint>>.

[#installation-pinning-tool]
=== Pinning tool

A recommended (non-flakes) way to install Nix dependencies is to use a pinning tool.
There are a handful of pinning tool out there but in this case, we'll use https://github.com/andir/npins[npins] as our tool of choice.
Assuming you have already initialized npins, you can simply add wrapper-manager-fds to your Nix project with the following command.

[source, shell, subs="attributes+"]
----
npins add --name wrapper-manager github foo-dogsquared {github-repo} --at {current-version}
----

Similar to channels installation, you'll have to import the <<user-entrypoint>> object.

[#installation-manual-pinning]
=== Manual pinning

Though not recommended, you could manually pin the Nix library yourself and then import the <<user-entrypoint, user entrypoint>> like in the following code.

[source, nix, subs="attributes+"]
----
{ pkgs, ... }:

let
  wrapper-manager-fds-src = builtins.fetchTarball "{remote-git-repo}/archive/{current-version}.tar.gz";
  wrapper-manager = import wrapper-manager-fds-src { };
  wrapperManagerLib = import wrapper-manager.wrapperManagerLib { inherit pkgs; }
in
wrapperManagerLib.env.build { }
----

[#installation-flakes]
=== Flakes

This project also has a https://zero-to-nix.com/concepts/flakes[flake object].
In your flake definition, just import it as one of the inputs.
Unlike the other methods, the flake output is the user entrypoint so no need to import it or anything.

[source, nix, subs="attributes+"]
----
{
  inputs.wrapper-manager-fds.url = "github:{github-full}/{current-version}";

  outputs = { nixpkgs, ... }@inputs: {
    nixosConfigurations.desktop = nixpkgs.lib.nixosSystem {
      modules = [
        ./hosts/desktop.nix
        inputs.wrapper-manager-fds.nixosModules.default
      ];
    };
  };
}
----

[#user-entrypoint]
=== User entrypoint

Most of the things you need from the project are all retrieveable from the entrypoint of this project (`default.nix` in project root) which is an attribute set closely structured from a typical Nix flake that you see.
For example, if you want the NixOS module for wrapper-manager, you could refer to `nixosModules.default`.
Here's the breakdown of the attrset entries in the entrypoint.

* `lib` is an attribute set containing the main functions: `eval` and `build`.
(More details to use them are in <<as-a-library>>.)

* `nixosModules` contains a set of NixOS modules for wrapper-manager-fds integration.
You could get the main module with the `default` attribute.

* `homeModules` are for home-manager modules and similarly structured to `nixosModules` attribute.

* `wrapperManagerLib` contains a path intended to be imported within your Nix code.
It simply contains the library set that can be used outside of the wrapper-manager-fds module environment.
To use it, it only requires a nixpkgs instance like in the following code.
+
[source, nix, subs="attributes+"]
----
{ pkgs, ... }:

let
  wrapper-manager-fds-src = builtins.fetchTarball "{remote-git-repo}/archive/master.tar.gz";
  wrapper-manager = import wrapper-manager-fds-src { };
  wrapperManagerLib = import wrapper-manager.wrapperManagerLib { inherit pkgs; }
in
wrapperManagerLib.env.build { }
----

* `overlays` is a set of nixpkgs overlays to be applied by the user.
So far, there's only one overlay called `default` containing the wrapper-manager library set.



[#getting-started]
== Getting started

Now that you have wrapper-manager-fds on the go, let's have a breakdown of what it is, exactly.
The project itself is made of different parts which you can use for different purposes.

[#the-module-environment]
=== The module environment

One part of the project is the module environment.
Just like https://github.com/nix-community/home-manager[home-manager] and https://github.com/nix-community/disko[disko] and https://github.com/viperML/wrapper-manager[the original source], wrapper-manager-fds comes with its own module environment.
Instead of a home environment from home-manager or an installation script from disko, wrapper-manager-fds module environment evaluates to a derivation containing a wrapper script. footnote:[While the original source also evaluates similar to that, it typically involves a set of wrappers inside of the same configuration environment rather than a single wrapper.]
This can be thought of as a declarative layer over `makeWrapper` build hook from nixpkgs.

If you want to view the module options, you can see it in
ifdef::env-hugo[link:./wrapper-manager-env-options/[wrapper-manager module options].]
ifndef::env-hugo[{docs-site}/wrapper-manager-env-options[wrapper-manager documentation site].]

Here's a very simple example of a wrapper for Neofetch.

[source, nix]
----
{ lib, pkgs, ... }:

{
  wrappers.neofetch = {
    arg0 = lib.getExe' pkgs.neofetch "neofetch";
    appendArgs = [
      "--ascii_distro" "guix"
      "--title_fqdn" "off"
      "--os_arch" "off"
    ];
  };
}
----

Or if you want fastfetch...

[source, nix]
----
{ lib, pkgs, ... }:

{
  wrappers.fastfetch = {
    arg0 = lib.getExe' pkgs.fastfetch "fastfetch";
    appendArgs = [ "--logo" "Guix" ];
    env.NO_COLOR.value = "1";
  };
}
----

Or even both in the same configuration (which you can do).

[source, nix]
----
{
  imports = [
    ./fastfetch.nix
    ./neofetch.nix
  ];
}
----

You could even create https://specifications.freedesktop.org/desktop-entry-spec/latest/[XDG desktop entry] files useful for the application to be launched through an application launcher/menu.
For example, you could create an executable and a desktop entry to launch a custom Firefox profile in your home-manager configuration.

.Creating a custom Firefox desktop entry launching a custom profile
[source, nix]
----
{ config, lib, pkgs, ... }:

{
  programs.firefox.profiles.custom-profile = {
    # Put some profile-specific settings here.
  };

  wrapper-manager.packages.browsers = {
    wrappers.firefox-custom-profile = {
      arg0 = lib.getExe' config.programs.firefox.package "firefox";
      prependArgs = [
        "-P" "custom-profile"
      ];
      xdg.desktopEntry = {
        enable = true;
        settings = {
          desktopName = "Firefox (custom-profile)";
          startupNotify = true;
          startupWMClass = "firefox";
          icon = "firefox";
          mimeTypes = [
            "text/html"
            "application/xhtml+xml"
            "application/vnd.mozilla.xul+xml"
            "x-scheme-handler/http"
            "x-scheme-handler/https"
          ];
        };
      };
    };
  };
}
----

[#as-a-library]
=== As a library

wrapper-manager also comes with a library set which you can use to evaluate and build wrapper-manager packages yourself.
This is found in the `wrapperManagerLib` attribute from the user entrypoint where it needs an attribute set containing a nixpkgs instance in `pkgs`.

[#src:example-lib-build]
.An example of importing wrapper-manager library
[source, nix]
----
{ pkgs }:

let
  wrapper-manager = import (builtins.fetchgit { }) { };

  wmLib = import wrapper-manager.wrapperManagerLib { inherit pkgs; };
in
wmLib.env.build {
  inherit pkgs;
  modules = [ ./fastfetch.nix ];
  specialArgs.yourMomName = "Joe Mama";
}
----

Here's a quick rundown of what you can do with the library.

* Evaluate a wrapper-manager module with `env.eval` where it accepts an attrset similar to the <<src:example-lib-build, previous code listing>> containing a list of additional modules, the nixpkgs instance to be used, and `specialArgs` to be passed on to the `lib.evalModules` from nixpkgs.

* Build a wrapper through `env.build` returning a derivation of the wrapper.
It accepts the same arguments as `env.eval`.

There is also `lib` attribute if all you want to do is to build and/or evaluate a wrapper-manager configuration.
It only contains the function from `env` subset which contains `build` and `eval`.

[#as-a-composable-module]
=== As a composable module

The most user-friendly way of using wrapper-manager would be as a composable nixpkgs module of an existing environment.
wrapper-manager provides a Nix module specifically for NixOS and home-manager environment. footnote:[Any other environments are basically unsupported and if you like to use it outside of NixOS and home-manager, you're on your own.]
You can import them through the `{nixos,home}Modules.default` from the user entrypoint of the project.

You can view the module options for each environment.

* For NixOS, you can view it in
ifdef::env-hugo[link:./wrapper-manager-nixos-module/[NixOS module integration options].]
ifndef::env-hugo[{docs-site}/wrapper-manager-nixos-module/[documentation site].]

* For home-manager, you can view it in
ifdef::env-hugo[link:./wrapper-manager-home-manager-module/[home-manager module integration options].]
ifndef::env-hugo[{docs-site}/wrapper-manager-home-manager-module/[documentation site].]

Most of the things set up here are implemented to make declaring wrappers ergonomic with the environment.
For a start, wrapper-manager-fds sets up a module namespace in `wrapper-manager`.
Here's a quick breakdown of the features that the module has.

* Passes the wrapper-manager library through `wrapperManagerLib` module argument.
This is nice if you want to only use wrapper-manager to quickly create wrappers inside of the configuration without using the wrapper-manager NixOS/home-manager integration module.

* You could declare wrappers through `wrapper-manager.packages.<name>` where each of the attribute value is expected to be a wrapper-manager configuration to be added in its respective wider-scope environment.

* You could include other modules through `wrapper-manager.sharedModules`.
This is useful for extending wrapper-manager inside of the configuration environment.

Here's an example of adding wrappers through wrapper-manager inside of a home-manager configuration.
The following configuration will create a wrapped package for yt-dlp with an additional wrapper script named `yt-dlp-audio` and `yt-dlp-video`.

.Installing yt-dlp with custom variants of it inside of a home-manager configuration
[source, nix]
----
{ config, lib, pkgs, ... }:

{
  home.packages = with pkgs; [
    flowtime
    blanket
  ];

  wrapper-manager.packages = {
    music-setup = {
      basePackages = [ pkgs.yt-dlp ];
      wrappers.yt-dlp-audio = {
        arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
        prependArgs = [
          "--config-location" ./config/yt-dlp/audio.conf
        ];
      };
      wrappers.yt-dlp-video = {
        arg0 = lib.getExe' pkgs.yt-dlp "yt-dlp";
        prependArgs = [
          "--config-location" ./config/yt-dlp/video.conf
        ];
      };
    };
  };
}
----


[#development]
== Development

If you want to hack this hack, you'll need either Nix with flakes enabled (`experimental-features = nix-command flakes` in `nix.conf`) or not.
Either way, this should be enough to cater to both flake- and non-flake users.

This project supports the current stable and unstable version of NixOS.
Specifically, we're looking out for the nixpkgs instance both of these versions has.
As an implementation detail, we pin these branches through https://github.com/andir/npins[npins] which both flakes- and non-flake-based setups uses.
Just be familiar with it and you'll be fine for the most part.
Most likely, you don't even need to interact with it since handling update cadence is handled automatically through the remote CI.

Setting up the development environment should be easy enough.

* For flake users, you can just reproduce the development environment with `nix develop`.
* For non-flake users, you can do the same with `nix-develop`.

As an additional note, it is recommended to use something like direnv with `use flake` or `use nix` depending on your personal preferences to use flake or not.

Take note there is a `Makefile` full of commands intended for easily interacting with the project but it is heavily assumed you're in the development environment of the project.

[#development-library-set-and-modules]
=== Library set and modules

This Nix project has a test infrastructure set up at
ifdef::env-hugo[github:{github-full}[test directory, rev=main, path=tests]]
ifndef::env-hugo[link:./tests[`./tests`]]
covering the library set and the wrapper-manager module environment.
For its library set, it makes use of the nixpkgs library and a JSON schema to validate if it passes the whole test suite.
To make use of it, just run the following commands.

* For flake users, you can run `nix flake check`.
* For non-flake users, you can do the same with `nix-build tests/ -A libTestPkg` or `nix build -f tests/ libTestPkg`.

The derivation output should be successfully built if all of the tests in the suite passes.
Otherwise, it should fail and you'll have to see the build log containing all of the tests that failed.

On another note, there is a quicker way of checking the test suite with `nix eval -f tests lib` (or `nix-instantiate --eval --strict tests/ -A lib`) where it contains the raw test data which is useful if you don't want to essentially build a new derivation each time.
It is also quicker to eyeball results in this way especially if you're always working with the tests anyways.

[#development-website]
=== Website

This project also has a website set up with https://gohugo.io/[Hugo].
The files that you need to see are in
ifdef::env-hugo[github:{github-full}[`docs`, rev=main, path=docs]]
ifndef::env-hugo[link:./docs[`./docs`]]
directory.

* For flake users, you can build the website with `nix build .#website`.
* For non-flake users, you can do the same with `nix-build docs/`.

There is also a dedicated development environment placed in `docs/shell.nix` but this should be a part of the primary development environment already.
You can enter it with `nix develop .#website` or `nix-shell docs/`.

Just take note that the website also requires the NixOS options which comes in a JSON file.
This should be already taken care of in the package definition of the website but otherwise it is something that you'll have to be aware of.

The more important task to developing this part of the project is continuously getting feedback from it.
You can do so simply with the following commands:

* For flake users, `nix develop --command hugo -s ./docs serve`.
* For non-flake users, `nix-shell docs --command hugo -s ./docs serve`.
* If you're using `Makefile` of this project, `make docs-serve`.

[#development-nix]
=== Nix environment

As for developing the environment with Nix itself, it is very much preferred to make wrapper-manager-fds work with non-flake setups.
This also includes the workflow of the development itself for the purpose of easier time bootstrapping wrapper-manager-fds.

Due to the unfortunate situation with flakes as an experimental feature, it is more like a second-class citizen in terms of support.
This is because it is pretty easy to make a flake with non-flake tools compared to vice versa. footnote:[flake-compat is great and all but it holds back wrapper-manager-fds in making it easy to bootstrap if we rely on it.]

Here's an exhaustive guidelines that you have to keep in mind when developing related files within the project:

* This project uses https://calver.org/[calendar versioning].

* Only the unstable branch of NixOS is currently supported.
Support for the stable versions are unfortunately secondary and more incidental (at least at the moment).

* There shouldn't be any user consumables that requires anything from the npins sources.


[#goals-and-non-goals]
== Goals and non-goals

As a Nix project, wrapper-manager-fds aims for the following goals.

* Create an ecosystem of creating them wrappers, mainly through its library set and the module environment.

* Make creating wrappers ergonomic for its users.
Not necessarily user-friendly but it should easy enough to get started while allowing some flexibility, yeah?

* Make a nice environment for creating custom wrappers which is already quite possible thanks to the heavy lifting of the nixpkgs module system.

For now, wrapper-manager-fds does not focus on the following ideas;
the main focus for now (as of 2024-07-31) is the core attributes needed to make wrapper-manager extensible for third-party module authors.
Take note, these are all ideas that are considered but may or may not be out of the blacklisted ideas at some point in the future for a variety of reasons.
Think of them as a list of possibilities for what may come within wrapper-manager-fds.

* Create an environment similar to NixOS and home-manager.
wrapper-manager-fds' endgoal is to create a derivation typically composed as part of an environment (e.g., `mkShell` for devshells, `environment.systemPackages` for NixOS, `home.packages` for home-manager).
Otherwise, we're creating a poor man's version of them and it'll quickly creep in scope.

* Support for multiple nixpkgs releases.
Up until I put some elbow grease for release engineering and to make testing between multiple branches easy, only the unstable branch of nixpkgs is officially supported for now.

* Integrating with sandboxing frameworks such as https://github.com/containers/bubblewrap[Bubblewrap] and https://github.com/queer/boxxy[Boxxy]. footnote:[That said, the author does have custom wrapper-manager modules that does exactly that so this being ruled out may be ruled out in the future ;p]
This is too big of a task so it isn't considered for now.
Plus, having this would now require creating additional support which the author does not have time for it.

* Create an ecosystem of modules that would allow to create quick configurations for different programs similarly found on other module environments such as in NixOS and home-manager.
Specifically, we're talking about modules in `programs` namespace (e.g., `programs.kitty`, `programs.alacritty`, `programs.nixvim`).
This would also require having a support cadence so not much is going to happen here.
Instead, I would encourage to have a separately-maintained project containing those for now.

* Focus on hardware-related configuration for the wrappers.
For now, it isn't possible within wrapper-manager (or Nix, really).
Some possible ideas include creating our own version of nixpkgs' `makeWrapper`, creating a specialized launcher for it, or something in the middle.
Would be a fun idea to make though. :)


[#faq]
== Frequently asked questions (FAQ)

[qanda]
Is this compatible with the original wrapper-manager?::
Nope.
It is a reimagining with a completely different way of using it so it won't be fully compatible with it from the start.

Why reimplement this anyways?::
For funsies and also because there are some things I find not so great with using the project.
https://github.com/viperML/wrapper-manager/tree/307eb5c38c8b5102c39617a59b63929efac7b1a7[As of this writing], using wrapper-manager to simply create wrappers anywhere is a pain.

Why not just incorporate the wanted changes into the original implementation?::
While it could be done, there will be some unwanted major changes into the project which would cause inconvenience to its users anyways so it isn't a good idea.
Plus it also justifies me implementing a bunch of features that would otherwise be deemed inconsistent with the project.

Can't you just create a wrapper with `pkgs.makeWrapper` and such from nixpkgs?::
Yeah, you can.
There's nobody stopping you from doing so and surely there's no hitman preparing to assissinate right behind you as you about to deny wrapper-manager-fds and smugly type `make` in `makeWrapper`.
In fact, wrapper-manager uses `makeWrapper` as the main ingredient.
Just think of wrapper-manager as a declarative version of that among the bajillion ways of making wrappers in the Nix ecosystem.
+
As an additional point, there are still use cases for it even with a simple `pkgs.writeShellScriptBin`.
In fact, if you have a situation like say having to create a one-off wrapper script to be added in a NixOS system, you can simply do the following:
+
[source, nix]
----
let
  ytdlpAudio = pkgs.writeScriptBin "yt-dlp-audio" ''
    ${pkgs.yt-dlp}/bin/yt-dlp --config-location "${../../config/yt-dlp/audio.conf}" $@
  '';
in
{
  environment.systemPackages = [ ytdlpAudio ];
}
----
+
BAM!
No need for wrapper-manager!

Why use the module system?::
Because screw you, that's why!!!
Am I stupid and lazy for basically using a battle-hardened configuration system library such as nixpkgs module system? footnote:[The answer is yes to both!]
+
Seriously though, the main reason is pretty simple: it is quite established and a battle-hardened part in the Nix ecosystem.
It has gone through the test of time and the numerous 339 users of the entire Nix ecosystem are quite adamant in the declarative aspect of the Nix thingy.
So... why not use it.

Any problems (and impending explosions) when using this project?::
As far as I can tell, not much (especially explosions) but there are a few caveats you need to know.
Just know this is something the author is trying to resolve.
+
--
* wrapper-manager-fds is not great at handling double wrappers.
It just naively wraps a package and goes on its merry way.

* wrapper-manager-fds is strongly biased towards Linux (and Unix-adjacent) ecosystem.

* wrapper-manager-fds doesn't handle any replacement for the related files very well.
This is especially noticeable in large desktop-adjacent packages such as Inkscape, Firefox, and Blender with a bunch of plugins and whatnot where they have their own wrappers.
This means you cannot set `programs.NAME.package` or something similar with it.

* The build step isn't enough to completely let the user replace the arguments found in `programs.<name>.package` (e.g., `programs.kitty.package = wrapperManagerLib.env.build { }`).
For now, the project focuses on making a nice declarative environment allowing the user to create a wrapper meant to work without adding configuration files into arbitrary locations in the filesystem (e.g., `$XDG_CONFIG_HOME`).
--


[#acknowledgements]
== Acknowledgements

I found a bunch of things for inspiration (READ: to steal ideas from).
Here's a list of resources I've found.

* The original source of the reimagining, of course: https://github.com/viperML/wrapper-manager[wrapper-manager].

* https://github.com/NixOS/rfcs/pull/75[Nix RFC 75] which also comes https://github.com/NixOS/nixpkgs/pull/85103[with its implementation and discussion around what works and whatnot].

* https://discourse.nixos.org/t/pre-rfc-module-system-for-wrappers-in-nixpkgs/42281[This NixOS Discourse post loudly thinking about the same idea.]


[#copyright]
== Copyright

This project is licensed under MIT License (SPDX identifier: https://spdx.org/licenses/MIT.html[`MIT`]).
Just see
ifdef::env-hugo[github:{github-full}[license file, rev=main, path=LICENSE]]
ifndef::env-hugo[link:./LICENSE[`./LICENSE`]]
for full text and details and whatnot.

The documentation (except for the code examples), on the other hand, is licensed under https://www.gnu.org/licenses/fdl-1.3.txt[GNU Free Documentation License] v1.3 only with no "Invariants" section (SPDX identifier: https://spdx.org/licenses/GFDL-1.3-no-invariants-only[`GFDL-1.3-no-invariants-only`])
You can see either the link or
ifdef::env-hugo[github:{github-full}[license file, rev=main, path=docs/LICENSE]]
ifndef::env-hugo[link:./docs/LICENSE[`./docs/LICENSE`]]
for more info.
The code examples, similar to the project codebase, are licensed under MIT with the same conditions apply and all that jazz.
