Welcome, this repository houses my personal configuration files for my computers running NixOS.
Warning
This readme is very likely out-dated at at any given time. This is for two reasons. The first is that Nix, as a language, suffers from an apparent need to be frequently refractored. The second reason is that this is my personal configuration, and it is under constant revision.
As with all Nix code (and with software in general), the ultimate form of documentation is the source code itself.
There are may modules and packages that some people may find useful to utilize in their own NixOS or Home Manager environments.
Important
The following command, which most flakes recommend, does not work currently.
nix flake show 'github:spikespaz/dotfiles'
This is because the formatter
output points to a package
which uses IFD. This means that running the above command on a system that has
allow-import-from-derivation
disabled will result in a failure.
Please use the provided nix eval
commands or browse the repository to
explore its features until this is rectified.
Warning
All commands assume that you are at the root of the cloned repository, unless specified otherwise.
To use components from my flake in your own configurations,
add it as an input in your flake.nix
:
{
# Assumes you already have `nixpkgs` as an input.
inputs.birdos.url = "github:spikespaz/dotfiles/master";
inputs.birdos.inputs.nixpkgs.follows = "nixpkgs";
}
Note
Q: What does the second line with follows
accomplish?
A: The first time something from a flake is built (or some other command is run),
the inputs are locked in a file named flake.lock
.
This is a JSON file that contains a long registry of inputs--
as well as the inputs of your inputs--
and each entry is associated with a Git hash which determines what revision
or "version" of that input your flake will use.
Now, when you use something like input.A.inputs.B.follows = "C"
,
this allows you to override input B
of flake A
to instead use
another input from your flake, C
, where C
is an attribute name from your
own inputs
, which is locked in your own flake.lock
.
In the code block above, this mechanism demonstrates the ability to override
the flake.lock
which is cloned from this repository and instead replace the
Nixpkgs input to use the revision that you have locked instead. This means you don't
have to wait on me to run nix flake update
or nix flake lock --update-input nixpkgs
before you can compile birdos
packages with the latest dependencies from Nixpkgs.
This flake has several modules for both Home Manager and NixOS that you will probably find useful. My two favorites are the swayidle module (example config), and the Hyprland module (example config).
Note
To see all the modules made available, run the command(s):
nix eval 'github:spikespaz/dotfiles#nixosModules' --apply 'builtins.attrNames'
# and
nix eval 'github:spikespaz/dotfiles#homeManagerModules' --apply 'builtins.attrNames'
Assuming that you have this flake added as an input to your own (described above under Usage):
- Make sure your flake's
inputs
are passed tospecialArgs
orextraSpecialArgs
wherever you callnixpkgs.lib.nixosSystem
orhome-manager.lib.homeManagerConfiguration
. - Create a new
*.nix
file in your flake, wherever you like. - Make sure that the file you created is imported somewhere.
- In the
modules
attribute of a call tonixpkgs.lib.nixosSystem
, - In the
modules
attribute of a call tohome-manager.lib.homeManagerConfiguration
, - In the
imports
list of another module.
- In the
- Make sure that the file is a lambda including
inputs
as an argument. - Add the module you want to the
imports
list of the file you created.- This is a recommendation. There are other ways, detailed later.
- Set the options you want from the module you used.
You may organize your flake however you want. Following is a minimal example with that satisfies my personal preferences.
Here is an example of a directory structure where certain files use specific modules. Not every file shown in the tree is relevant to the example, but each is presented with the intent to represent an average setup.
.
├── flake.lock
├── flake.nix
├── hosts
│  └── intrepid
│  ├── bootloader.nix
│  ├── configuration.nix
│  ├── filesystems.nix
│  └── powerplan.nix # Imports the `amdctl` module.
└── users
└── jacob
├── desktops
│  ├── hyprland
│  │  ├── default.nix # Imports the `hyprland` module...
│  │  ├── config.nix # which is used here,
│  │  └── windowrules.nix # and here.
│  └── wayland
│  ├── default.nix # Imports the `timeous.nix` file.
│  └── timeouts.nix # Imports the `swayidle` module.
└── profile.nix
This is a larger example that shows usage of both NixOS and Home Manager modules.
It also shows several different ways of using the imports
attribute
that are specific to the circumstance.
Note
I think that it is best-practice to keep imports of modules to the scopes in which they are used. In the long run, if a file is included in your module tree somewhere, the options defined by it are made globally available. Regardless of this fact, keeping imports to narrow scopes allows for greater portability.
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
birdos.url = "github:spikespaz/dotfiles";
birdos.inputs.nixpkgs.follows = "nixpkgs";
};
# The `outputs` attribute is a lambda that receives the `inputs`
# attributes which are defined above.
#
# Here that attribute set is destructured to expose `nixpkgs`
# and `home-manager` to the entire scope of of `outputs`,
# but is also bound to `inputs` using the `@` syntax so that it
# may be passed along to your modules further.
outputs = inputs@{ self, nixpkgs, home-manager, ... }:
let
inherit (nixpkgs) lib;
# ...
in {
nixosConfigurations = {
intrepid = nixpkgs.lib.nixosSystem {
# Note that this is not the recommended way to specify the host platform.
# The relevant changes have not really reached the docs as of 7/24/2023,
# but if you browse the source code of this function there is a warning.
system = "x86_64-linux";
# Add your own modules to this list,
# as well as any that you may want to use in your configuration.
#
# Note that including modules from `inputs` here is just one way to do it,
# and not my personal preference.
modules = [
# These three are irrelevant to the example and are just
# here to indicate a typical configuration.
./hosts/intrepid/bootloader.nix
./hosts/intrepid/configuration.nix
./hosts/intrepid/filesystems.nix
# This one needs `inputs` to pull in `amdctl` module options.
./hosts/intrepid/powerplan.nix
# You could include all the modules you need to use here,
# but I recommend seeing the rest of the example files first.
#
# Omit the line below if you trust me.
inputs.birdos.nixosModules.amdctl
];
specialArgs = {
# Add this to ensure that modules above have access to the `inputs`
# attribute set, so that you can use `imports` later.
inherit inputs;
# ...
};
};
# ...
};
# This assumes that you will use Home Manager as "standalone",
# see the HM documentation for details.
homeConfigurations = {
jacob = home-manager.lib.homeManagerConfiguration {
# This is also not the recommended way of passing `nixpkgs`,
# for reasons (similar to `system` above) that are out-of-scope of this example.
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = [
# Before you ask why this is the only module--
# when there are clearly others in the file tree--
# please see the rest of the example files.
./users/jacob/profile.nix
# Just like the `nixosSystem` example above,
# this is my least preferred method of including
# modules from `inputs.birdos`.
#
# Listing them here is possible, but wait until you see the other files.
inputs.birdos.homeManagerModules.swayidle
inputs.birdos.homeManagerModules.hyprland
];
# Just like `specialArgs` above...
extraSpecialArgs = {
inherit inputs;
# ...
};
};
# ...
};
# ...
};
}
This file is included in the modules
list passed to nixpkgs.lib.nixosSystem
as shown in the example flake.nix
.
{ inputs, ... }: {
# Add the `amdctl` module to the `imports` list.
imports = [ inputs.birdos.nixosModules.amdctl ];
# Use the options defined by that module.
services.undervolt.amdctl = {
enable = true;
mode = "undervolt";
pstateVoltages = [ 150 100 100 ];
};
# This is here just to show that all of the default modules from
# Home Manager still work here.
services.upower = {
enable = true;
percentageLow = 15;
percentageCritical = 7;
percentageAction = 5;
criticalPowerAction = "Hibernate";
};
# ...
}
This is how I prefer to organize my Home Manager configuration.
Instead of adding all the modules to modules
in the arguments to
home-manager.lib.homeManagerConfiguration
, I prefer a more granular approach;
I only include the modules in the files in which they are used.
I use a file named profile.nix
in the root of my user configuration,
in order to make it easy to comment out certain imports when I am experimenting.
{ config, inputs, ... }: {
imports = [
# Both of these relative paths are directories that contain a file called
# `default.nix`, which is what will actually be imported.
./desktops/wayland
./desktops/hyprland
# While I do not include modules from `inputs.birdos` here,
# I do add `homeage` here (not shown in the example `flake.nix`).
# This is because I directly use options from that module *in this file*.
#
# This has nothing to do with `birdos` modules, and is only here for
# illustrative purposes.
inputs.homeage.homeManagerModules.homeage
# ...
];
# You'd typically have some other commonplace
# options from Home Manager itself defined in here too.
home.username = "jacob";
home.homeDirectory = "/home/jacob";
programs.home-manager.enable = true;
# ...
# This is to show usage of the `homeage` module (another flake)
# which is imported above.
homeage.mount = "${config.home.homeDirectory}/.secrets";
homeage.identityPaths = [ "~/.ssh/id_ed25519" ];
# ...
}
{ ... }: {
imports = [
./timeouts.nix
# ...
];
# ...
}
{ inputs, ... }: {
imports = [
# These two modules from my flake pair nicely together.
# They are imported *in this file* because they are used *only in this file*.
inputs.birdos.homeManagerModules.swayidle
inputs.birdos.homeManagerModules.idlehack
];
# Enabling the service from the `idlehack` module.
services.idlehack.enable = true;
# Usage of many options from the `swayidle` module.
services.swayidle = {
enable = true;
# Only use the `systemd` targets for the desktops that you have configured.
systemdTarget = [ "sway-session.target" "hyprland-session.target" ];
# Actual configurations are elided for brevity.
events = {
# ...
};
batteryTimeouts = {
# ...
};
pluggedInTimeouts = {
# ...
};
};
}
If the above examples are insufficient, please open an issue and I will write out more examples.
If you want to use the extended lib
provided by this flake, you can either
use inputs.birdos.lib
(assuming birdos
is what you named
the input), or you can extend Nixpkgs' lib with lib.extend
.
For example, in a let
block before your flake's output attributes:
let
lib = nixpkgs.lib.extend (import "${inputs.birdos}/lib");
tree = lib.birdos.mkFlakeTree ./.; # example usage of lib
# ...
in
Some lib
functions added by this flake are top-level, but some
that are not generally useful to the bulk of configuration are hidden
behind the birdos
attribute (such as flake utilities).
Note
You can learn what is inherited at the top-level lib
(when you extend nixpkgs.lib
as shown above)
by printing out lib.birdos.prelude
.
nix eval 'github:spikespaz/dotfiles#lib.birdos.prelude' --apply 'builtins.attrNames'
For packages, you have two options. Either use the flake's packages
output
or the overlays
output (read more about this).
Note
Run this command to print out all of the available package names:
# Replace `x86_64-linux` with the system-double of the host you're using.
nix eval 'github:spikespaz/dotfiles#packages.x86_64-linux' --apply 'builtins.attrNames'
Or this one to see the overlays:
nix eval 'github:spikespaz/dotfiles#overlays' --apply 'builtins.attrNames'
Make sure you have added inputs
to specialArgs
in the attribute set passed
to lib.nixos.nixosSystem
, or extraSpecialArgs
for home-manager.lib.homeManagerConfiguration
:
{
outputs = inputs@{ nixpkgs, home-manager, ... }:
let
# ...
in {
homeConfigurations = {
jacob = home-manager.lib.homeManagerConfiguration {
pkgs = pkgsFor.x86_64-linux;
extraSpecialArgs = { inherit nixpkgs inputs; };
};
};
# ...
};
# ...
}
Then you can use it in modules which take inputs
as an attribute argument,
for example to install fastfetch
to your user session:
{ pkgs, lib, inputs, ... }: {
home.packages = [
inputs.birdos.packages.${pkgs.system}.fastfetch
# ...
];
# ...
}
For those of you who like to use overlays, use something similar to this when importing Nixpkgs:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
birdos.url = "github:spikespaz/dotfiles";
birdos.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ nixpkgs, ... }:
let
inherit (nixpkgs) lib;
systems = [ "x86_64-linux" "aarch64-linux" "arm64-linux" ];
pkgsFor = builtins.genAttrs systems (system:
import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [
# All packages from BirdOS dotfiles.
inputs.birdos.overlays.default
# If you use packages from this flake that have an unfree license,
# you need to include this if `nixpkgs.config.allowUnfree`
# and `nixpkgs.config.allowUnfreePredicate` don't work for you.
# Last checked, Nixpkgs applies the unfree predicate before merging
# overlays, so packages from overlays with unfree licenses will not
# care about the policy set by your Nixpkgs options.
inputs.birdos.overlays.allowUnfree
# The overlay for OracleJDK 8 to get around Oracle's sign-in page.
inputs.birdos.overlays.oraclejdk
];
});
in {
packages = lib.genAttrs systems (system:
let pkgs = pkgsFor.${system};
in {
# ...
});
nixosConfigurations = {
# ...
};
homeConfigurations = {
# ...
};
};
}
Then you can use the packages from this flake directly from the pkgs
attribute argument to your modules.
For example, installing fastfetch
as a system package:
{ pkgs, lib, ... }: {
environment.systemPackages = [
pkgs.fastfetch
# ...
];
# ...
}
Warning
You might want to use the default
overlay if you use multiple packages
from this flake, or if you want to compile them with dependencies provided by your
locked revision of Nixpkgs.
Do note however that if you do not use the default
overlay,
packages are (nearly) guaranteed to build; if you do use the
overlay, Nix will try to build packages using newer dependencies from
Nixpkgs instead of using the ones decreed by this flake's flake.lock
,
which might result in build errors.
In the event that you are using the default
overlay and it causes build errors,
please consider using the method shown in the
previous subsection
for that specific package.
This mechanism accomplishes much the same goal as using inputs.follows
where you list this flake as an input, but the two approaches are not identical.
This section is mostly for my personal reference, but it is also good for the newbies so I will make it extensive. Some of these are untested because I am writing them down when I feel clever, if any are wrong, please open an issue.
NixOS system package for the current hostname:
nix build "path:.#nixosConfigurations.$(hostname).config.system.build.toplevel"
Home Manager package for the current user:
nix build "path:.#homeConfigurations.$USER.activationPackage"
Prepare the disk for installation.
- Partition the disk.
- Format the disk partitions.
- Mount partitions relative to
/mnt
. Ensure that volumes are mounted with the same options you want to use even after your installation.
Install the configuration by hostname.
Usage of --no-root-password
assumes
that you are using a configuration that specifies
user.users.root.hashedPassword = "!"
, which effectively disables root login.
Do not use this option if you have no users configured.
Note
The following command tells nix to run four jobs at a time, each job with access to a quarter of your CPU cores. For example, with a 6-core, 12-thread CPU, each job would be allocated 3 threads, and with an 8-core, 16-thread CPU each job gets 4 threads.
nixos-install --flake "path:.#$(hostname)" --no-root-password --cores "$(($(nproc)/4))" -j 4
This requires that the user in question is logged in and has an active shell.
home-manager switch --flake "path:.#$(whoami)@$(hostname)"
Or perhaps more explicitly,
nix --extra-experimental-features nix-command --extra-experimental-features flakes \
run 'github:nix-community/home-manager/master' -- switch --flake "path:.#$USER"
This assumes that the hostname of the system matches with the name of the system configuration that you would like to switch to.
nixos-rebuild switch --flake "path:.#$(hostname)"
If you have a problem with any of the modules or packages provided by this flake, please open an issue and let me know so that others can benefit.
As for the hosts
and users
configuration, no support will be provided if
you copy/adapt code from these directories. If you use them as an example or
basis for your own setup, and need help understanding something, don't
hesitate to ask for my help, but if you attempt to use a large section of
code without studying it, just know that I don't fish for charity.
It would have been an impossibility to set everything and learn how this crazy software works up without the support of many people.
I would like to specifically thank @NobbZ for his continued critique, and for fielding may of the questions asked by new users in the community.
Others noteworthy fellows would be @tejing1, @viperML, @fufexan.
I thank them for both their conversational guidance and graciously sharing their personal configurations for me to read and learn from.
- https://github.com/NobbZ/nixos-config
- https://github.com/tejing1/nixos-config
- https://github.com/fufexan/dotfiles
- https://github.com/viperML/dotfiles
- https://github.com/hlissner/dotfiles
Important
If you came here on your own, and would like to find help with Nix or NixOS, I encourage you to join this small Discord server, mostly led by @NobbZ.