= nix-wrapper-manager-fds
:toc:


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 original 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]
----
nix-channel --add https://github.com/foo-dogsquared/wrapper-manager-fds/archive/master.tar.gz wrapper-manager-fds
nix-channel --update
----

Then in your environment configuration, you'll have to import it.
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]
----
npins add github foo-dogsquared wrapper-manager-fds
----

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 like in the following code.

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

let
  wrapper-manager-fds = builtins.fetchTarball "https://github.com/foo-dogsquared/wrapper-manager-fds/archive/master.tar.gz";
  wrapperManager = import wrapper-manager-fds { };
  wrapperManagerLib = import wrapperManagerLib.lib { 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]
----
{
  inputs.wrapper-manager-fds.url = "github:foo-dogsquared/wrapper-manager-fds";

  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`) 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.


[#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.

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 = "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.

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 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. footnote:[Most likely, you don't even need to interact with it for the most part 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 link:./tests[./tests] covering the library set nad 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 tests.
To make use of it, you can run it with 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 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] following software versioning of the upstream.
The unstable branches are basically deployed with development versions of this project.

* Only the current stable branch and the unstable branch of NixOS is supported.

* The preferred default nixpkgs branch at development is `nixos-unstable`.

* 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.


[#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.

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 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.
--


[#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 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 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.
