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.
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].
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.
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
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.
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.
* 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].
* 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:
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.
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:
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.
* 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`).
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`]).
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`])