Note: Can’t post to announcements so I posted it here. I hope that is okay
I’ve been using NixOS for a while and kept running into the same pattern: I want certain applications available in my launcher (Discord, Insomnia etc.), but I rarely use some of them. Installing them all means a larger closure, longer rebuilds, and more disk space for apps that might sit unused for weeks.
So I either had to open a terminal, run nix-shell -p <xyz> and hope I remembered the package name correctly or live with having them permanently installed.
Being annoyed by this I did the only sane thing (not really). I built “Deferred Apps” - a NixOS and Home Manager module that creates lightweight wrapper scripts (~1KB each) with proper .desktop files. The apps appear in your launcher immediately (with icons), but the actual packages only gets downloaded when you first click them (via nix shell).
GitHub: GitHub - WitteShadovv/deferred-apps
What it does
- Creates tiny (~1KB) wrapper scripts with proper desktop entries
- Auto-detects executable names from nixpkgs metadata (so
obs-studiocorrectly runsobs) - Resolves icons from Papirus theme at build time
- Supports both free packages (pure mode) and unfree packages (impure mode with explicit opt-in)
- Works with both NixOS and Home Manager
- Supports nested packages like
jetbrains.pycharm
Basic usage
{
inputs.deferred-apps.url = "github:WitteShadovv/deferred-apps/v0.2.0";
outputs = { nixpkgs, deferred-apps, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
deferred-apps.nixosModules.default
{
programs.deferredApps = {
enable = true;
apps = [ "spotify" "discord" "obs-studio" "blender" "gimp" ];
allowUnfree = true; # Required for spotify, discord
};
}
];
};
};
}
Tradeoffs to be aware of
- The first one is (hopefully) kinda obvious: First launch requires network. The package downloads on first use - no offline fallback unless it’s already cached in the Nix store.
- Unfree packages require
--impure. This is a Nix limitation and there is no good way around this afaik (let me know if there is). Free packages stay pure. - GC can remove cached packages. By default, running nix-collect-garbage will require re-downloading on next launch. You can enable
gcRoot = trueto prevent this, but then you need manual cleanup.
How it works
At build time, the module creates a derivation containing:
- A wrapper script in
/libexecthat callsnix shell <flakeRef>#<package> --command <exe> - A
.desktopfile pointing to that wrapper - An optional terminal command symlink in
/bin
The wrapper shows a notification on first launch, runs nix build to download and create a GC root (if enabled), then execs the actual application.
I’d appreciate any feedback, especially around:
- Edge cases I might have missed
- Better approaches to the unfree package handling
- Ideas for improving the security model
PRs and ideas for improvements are welcome.
Cheers