184 lines
8.7 KiB
Plaintext
184 lines
8.7 KiB
Plaintext
@document.meta
|
|
title: Cool shit you can do with nix
|
|
description: Nix is surprisingly versatile
|
|
authors: [
|
|
Adumh00man
|
|
]
|
|
categories: [
|
|
nix
|
|
linux
|
|
blog
|
|
]
|
|
created: 2026-03-28T19:03:27+00:00
|
|
updated: 2026-03-28T19:59:57+0100
|
|
draft: true
|
|
layout: post
|
|
version: 1.1.1
|
|
@end
|
|
|
|
* Nix is hard
|
|
Nix is supposed to be a solve-all to the "it runs on my machine" problem. And it is. If it didn't do that, then there would be no point in
|
|
it existing. But, the syntax is a pain in the ass, and the documentation is half baked at best. For reference, everything I talk about
|
|
here is coming from my {https://git.voidarc.co.uk/voidarc/nixos}[nixos config repo], so if you need some context, then everything in
|
|
there should be functional enough, at least to serve as some kind of guide.
|
|
|
|
+html.class note
|
|
> I am not a professional. If you want to get your info from someone that knows what they're talking about, look on youtube, idiot.
|
|
|
|
** Starting out
|
|
This whole guide is coming from the perspective of someone who has never used nix outside of nixos. I decided to switch one day and had
|
|
to learn as I went ({:/posts/nix:}[/If only there was a post about that/]). Of course, at the beginning, my config was very simple. I
|
|
basically tried to replicate my arch config, which I already managed the dotfiles for through git. I managed, but my main issue was that
|
|
my nixos config had to be edited by the root user, because it was in the `/etc/nixos` directory. This is the default location, which makes
|
|
sense, because most linux systems need to be oriented toward having multiple users, and you don't want every person on the system to be
|
|
able to change the underlying programs. However, this is irritating, because you have to manage a git repo as the root user, which *sucks*.
|
|
There are some workarounds, mostly involving symlinks, that, if broken, will brick your system, so they aren't recommended. The real way
|
|
you're supposed to do it is with flakes.
|
|
|
|
*** Flakes
|
|
Flakes aren't real. They can't hurt you. But, they can let you manage your config outside of the `/etc` directory.
|
|
|
|
@code nix
|
|
{
|
|
description = "My example flake";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
|
};
|
|
|
|
outputs = { self, nixpkgs }: {
|
|
packages.x86_64-linux = {
|
|
default = self.packages.x86_64-linux.hello;
|
|
hello = nixpkgs.legacyPackages.x86_64-linux.hello;
|
|
};
|
|
};
|
|
}
|
|
@end
|
|
|
|
This is the default flake that you get upon running `nix flake init` in a project. What you'll immediately notice is that there are only
|
|
2 sections: inputs and outputs (there are more but idk how to use them, except the description). These are not as explanatory as you
|
|
might first expect, so I tend to think about it like this:
|
|
|
|
- Inputs
|
|
-- External links / projects
|
|
-- Immutable (for the most part)
|
|
- Outputs
|
|
-- Generated / built code
|
|
-- Changable
|
|
-- Where you would put anything of interest
|
|
|
|
I am aware this clears up nothing, so let me explain slightly further.
|
|
|
|
**** Inputs
|
|
Without inputs, you basically can't build anything outside of a hello world with no dependancies. They are the main reason that flakes
|
|
were made in the first place. This is because every input is stored in the lock file as a hash of the git repo it is a part of. I went
|
|
more in depth in the article I linked to at the start, but for now I'll give you some examples.
|
|
|
|
The main input that you would need to
|
|
know about is the `nixpkgs` input, which provides a fixed version of the nixpkgs git repo that you can use to access whatever packages
|
|
you need. This can be unstable, which, as the name suggests, is newer and less supported, a fixed hash, like
|
|
`nixpkgs.url = "github:nixos/nixpkgs?ref=f5da6d7f24b8565882487ce7f45c2a7d9d8afdeb";`, or just a branch, like the one in my config:
|
|
`nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";`. All have different use cases, which I may or may not get to.
|
|
|
|
Another common form of input is a repo input. Nixpkgs uses this kind, so it follows the same rules.
|
|
@code nix
|
|
omnisearch = {
|
|
url = "git+https://git.voidarc.co.uk/voidarc/omnisearch";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
@end
|
|
The url is slightly different because the repo isn't hosted on github, but the `github:` from before is just a shortcut that expands
|
|
into `git+https://github.com/` when you build the flake. Notice that there is another line in the input. This tells nix to use the
|
|
version of nixpkgs that the whole system is running on, the one in this repo's flake.lock, instead of the one in the called flake's
|
|
repo. This saves storage and prevents you from installing needless copies of apps that are basically the same. Using this syntax is
|
|
best practice unless there is a specific version of a dependancy that the app needs.
|
|
|
|
The last kind of input you're probably going to see is the local input. These come in the form of local paths, relative to the current flake.
|
|
These are used when you have submodules that aren't large enough to deserve a whole repo, or modules that you need to reload on the fly.
|
|
@code nix
|
|
chataigne.url = "./modules/chataigne";
|
|
nvim-wrapped = {
|
|
url = "git+file:///home/user01/.dotfiles/.config/nvim";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
@end
|
|
The first input here is just a relative path, with a specific set of dependancies, hence it doesn't follow nixpkgs like the other.
|
|
The second input is still a local module, but it isn't relative. The flake is located in a path above the system flake, that I want
|
|
to call it from, so I needed to use a workaround and use the repo input type with a `file://` prefix. This lets me test my nvim config
|
|
without pushing to git beforehand, which is useful when I've only added one dependancy and don't want to make a whole commit for 3
|
|
lines of code. (don't worry, we'll get back to why it's called nvim-wrapped later)
|
|
|
|
**** Outputs
|
|
Outputs, as I said, are what's produced when the flake is run. There are different types of these, too, so let's start simple.
|
|
|
|
Before you can even think about compiling an app, you have to allow the outputs access to the inputs. This can be seen as the
|
|
function call at the beginning of the output section:
|
|
@code nix
|
|
outputs =
|
|
{ self, nixpkgs }:
|
|
{
|
|
# Whatever app
|
|
};
|
|
@end
|
|
However, this only allows the outputs to access nixpkgs, which is ok for the first type of output: The humble binary.
|
|
|
|
Binaries are defined by system. This allows you to have different build instructions for x64 and arm systems in the same flake, but with
|
|
modern programming languages that isn't much of an issue. As an example, this flake output would provide fastfetch
|
|
@code nix
|
|
packages.${system}.default = pkgs.fastfetch;
|
|
@end
|
|
Simple, right? the `nixpkgs` input provides `pkgs` as a callable parameter, so that would be all you need. This is on display with
|
|
the example flake from earlier, providing the `hello` package for x86_64-linux.
|
|
This can be added to, by providing a devshell with this package, although that requires slightly more boilerplate.
|
|
@code nix
|
|
devShells.${system}.default = pkgs.mkShell {
|
|
# Packages you want available in your shell
|
|
buildInputs = [
|
|
pkgs.fastfetch
|
|
];
|
|
|
|
# Environmental variables or shell hooks that you want to run
|
|
# when the shell starts
|
|
shellHook = ''
|
|
export SOME_VAR=foo
|
|
echo "Shell started"
|
|
'';
|
|
};
|
|
@end
|
|
Of course, a whole shell for fastfetch is a solved issue, with `nix shell` and everything, but for explanatory purposes that's all you
|
|
would need to read flakes, and understand what you would need to call in order to get the right package from a repo. There are obviously
|
|
building tools, but I have no idea how those work. This is supposed to be from a fully nixos user perspective, and I don't tend to write
|
|
too much code.
|
|
|
|
Finally, there's the main thing that you would need to manage your system, `pkgs.lib.nixosSystem`. Of course, you could use the
|
|
function directly, but that's boring, so I made a function to make a system instead:
|
|
@code nix
|
|
outputs =
|
|
{
|
|
self,
|
|
nixpkgs,
|
|
...
|
|
}@inputs:
|
|
let
|
|
mkSystem =
|
|
extraModules:
|
|
nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
specialArgs = { inherit inputs; };
|
|
modules = [
|
|
common
|
|
hardwareConfig
|
|
]
|
|
++ extraModules;
|
|
};
|
|
in
|
|
{
|
|
nixosConfigurations = {
|
|
mobile02 = mkSystem [ ./configs/configuration-laptop.nix ];
|
|
};
|
|
@end
|
|
There are a few things to note here. This is from my system flake, I've just omitted my pc config, because you only really need one to
|
|
see what's going on. Starting from the top, there is an extra `@inputs` after the output function. This allows for the `inherit inputs`
|
|
inside the mkSystem function, so that you can dynamically access any input from whatever module you're calling for your system.
|
|
|