nixos-rebuild should be ready for editing
This commit is contained in:
parent
e2af9b7bd6
commit
6f86cf9bea
13
_config.yml
13
_config.yml
|
@ -1,13 +1,18 @@
|
|||
title: artemist
|
||||
email: blog@artem.ist
|
||||
author:
|
||||
name: Artemis Tosini
|
||||
email: blog@artem.ist
|
||||
github_username: artemist
|
||||
description: >-
|
||||
Artemis Tosini's experiments
|
||||
baseurl: "/blog" # the subpath of your site, e.g. /blog
|
||||
url: "https://artem.ist" # the base hostname & protocol for your site, e.g. http://example.com
|
||||
github_username: artemist
|
||||
baseurl: "/blog"
|
||||
url: "https://artem.ist"
|
||||
|
||||
theme: minima
|
||||
minima:
|
||||
skin: auto
|
||||
social_links:
|
||||
- { platform: mastodon, user_url: "https://social.mildlyfunctional.gay/@artemist" }
|
||||
- { platform: github, user_url: "https://github.com/artemist" }
|
||||
plugins:
|
||||
- jekyll-feed
|
||||
|
|
19
_drafts/_nixos-rebuild-2.md
Normal file
19
_drafts/_nixos-rebuild-2.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## Wait what if you're building remotely?
|
||||
If you pass `--build-host` to `nixos-rebuild`
|
||||
|
||||
Building a NixOS configuration is secretly two steps: evaluation and realisation.
|
||||
Evaluation turns your nix code into a derivation: a file with build instructions
|
||||
(environment variables, a build script, and arguments), a list of other derivations its needs before it can build,
|
||||
and the outputs it will create when you run it.
|
||||
Evaluation is always done on the local machine (where you run `nixos-rebuild`), because the build host might have different channels (for non-flake builds) or no access to the source of some inputs (for flake builds)[^flake-eval].
|
||||
|
||||
Realisation (you can think of it as building) takes a derivation and its tree of dependencies then creates the output paths by running each
|
||||
build script in its own sandbox (or downloading the result from a trusted substituter, often [cache.nixos.org](https://cache.nixos.org/)). Most of the hard work happens here.
|
||||
|
||||
In order to get the derivations from the local machine to the build host,
|
||||
`nixos-rebuild` uses `nix copy --derivation --to`,
|
||||
which works just like `nix-copy-closure` but copies derivations instead of the entire closure.
|
||||
Than the hard work can happen on the destination without needing to copy all the nix source code and dependencies.
|
||||
|
||||
[^flake-eval]: It is actually possible to evaluate flakes on the remote machine, but this isn't supported. The `nix flake archive` command, which copies a flake and all of its inputs to the nix store, can copy to another machine with the `--to` argument. Building this way works but I haven't bothered writing a patch.
|
||||
|
23
_drafts/draft-the-trouble-with-time.md
Normal file
23
_drafts/draft-the-trouble-with-time.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
layout: post
|
||||
title: The Trouble with Time
|
||||
date: 2022-03-01
|
||||
---
|
||||
|
||||
We are all bound by an oppressive force. One which declares what we must say and when we must say it. I am, of course,
|
||||
referring to tzdata.
|
||||
|
||||
The Time Zone Database, or tzdata, is a list of time zones. Each entry includes information about UTC offsets, when
|
||||
daylight savings time begins and ends, and historical information about time changes. For example, if you ask tzdata about the zone
|
||||
`America/New_York`, referring to New York City, NY, USA, it will tell you that NYC was 4 hours, 56 minutes, and 2 seconds behind Greenwich
|
||||
until 18 November 1883, when it switched to 5 hours behind. It can also tell you when DST started and ended every year from 1920, when NYC
|
||||
introduced it, until now (and can predict when it may happen again in future years.
|
||||
|
||||
If you're scheduling an event or even just showing a clock this information is essential. tzdata gives you this information
|
||||
for the entire world, with frequent updates when countries change their time zones and DST rules. That's why tzdata is used
|
||||
by billions of computers worldwide.
|
||||
|
||||
Unfortunately, dealing with time is quite complicated. tzdata asserts that a time zone is an area where the time rules have been uniform
|
||||
since 1970. That zone will then be named by the largest city within the area.
|
||||
Continuing our NYC example, Washington DC's time has never differed from NYC since 1970 and it is smaller than NYC,
|
||||
so there's no `America/Washington_DC` and residents of DC will just use `America/New_York`
|
|
@ -1,86 +0,0 @@
|
|||
---
|
||||
layout: post
|
||||
title: What is nixos-rebuild anyway?
|
||||
date: 2024-01-29
|
||||
---
|
||||
If you've used NixOS before, you've almost certainly used the `nixos-rebuild` program before.
|
||||
With one `nixos-rebuild switch` command you can build your updated system configuration,
|
||||
add it to your bootloader as the default entry, stop all old services, and start any new services.
|
||||
|
||||
What you may not know is that `nixos-rebuild` is a bash script and you can do everything (relatively) easily without it.
|
||||
The [full source code](https://github.com/NixOS/nixpkgs/blob/c074160dcfa338f8424c440ccb0f0a5412de0dbf/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh) is quite long and includes many special cases, but most of these aren't necessary if you're building manually.
|
||||
|
||||
Some of this will make more sense if you know about two options `--build-host` and `--target-host`.
|
||||
With these you can build in one computer, copy it to the destination, and install there.
|
||||
|
||||
Unforutnately, there's one important question we have to answer first:
|
||||
|
||||
# What is a NixOS?
|
||||
NixOS is a very complicated way of defining options and setting them to a value.
|
||||
For example, your configuration you could set:
|
||||
```
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
```
|
||||
The value is matched by an "option" of the same name which describes the default value and the type [^type].
|
||||
This is how NixOS describes `environment.systemPackages`:
|
||||
```
|
||||
options.environment.systemPackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
|
||||
description = lib.mdDoc ''
|
||||
<removed for brevity>
|
||||
'';
|
||||
};
|
||||
```
|
||||
|
||||
The idea that makes this useful is that you can set values based off of each other.
|
||||
For example, the gamescope service sets;
|
||||
```
|
||||
environment.systemPackages = mkIf (!cfg.capSysNice) [ gamescope ];
|
||||
```
|
||||
meaning that `gamescope` is only added to `systemPackages` if the option `programs.gamescope.capSysNice` is disabled.
|
||||
|
||||
So we have a big set of options with values, but that doesn't make it an operating system.
|
||||
The piece that ties this all together is the "toplevel derivation", at `system.build.toplevel`.
|
||||
The toplevel derivation is a package that takes all of your settings and stuffs them into
|
||||
one directory. For example, when you set a kernel, the toplevel derivation sees that and puts it in `kernel`.
|
||||
When a module wants to put a configuration file in `/etc` it creates a file in `etc`.
|
||||
And all the packages in `environment.systemPackages` get linked together and put into `sw`.
|
||||
If you want to see yours, then go to `/run/current-system` on a NixOS machine.
|
||||
This will always have the version you're currently running [^booted-system].
|
||||
|
||||
|
||||
# Step 0: Build nix
|
||||
The script tries not to make too many assumptions about the build host. It must have a nix store,
|
||||
but that doesn't necessariy mean it has a new enough nix to build your configuration, or that your configuration
|
||||
is defined using only settings that your build nix can understand. Therefore, it downloads a newer nix if possible,
|
||||
or builds one using your configuration. I'm not _entirely_ sure why this happens, but it does.
|
||||
|
||||
# Step 1: Building your system
|
||||
Now that you have a nix to use, it's time to build your toplevel derivation. If you're using [flakes](https://zero-to-nix.com/concepts/flakes) that means tunning `nix build /etc/nixos#nixosConfigurations.$(hostname).config.system.build.toplevel`, which
|
||||
|
||||
|
||||
## Wait what if you're building remotely?
|
||||
Building a NixOS configuration is secretly two steps: evaluation and realisation.
|
||||
Evaluation turns your nix code into a derivation: a file with build instructions
|
||||
(environment variables, a build script, and arguments), a list of other derivations its needs before it can build,
|
||||
and the outputs it will create when you run it.
|
||||
Evaluation is always done on the local machine (where you run `nixos-rebuild`), because the build host might have different channels (for non-flake builds) or no access to the source of some inputs (for flake builds)[^flake-eval].
|
||||
|
||||
Realisation (you can think of it as building) takes a derivation and its tree of dependencies then creates the output paths by running each
|
||||
build script in its own sandbox (or downloading the result from a trusted substituter, often [cache.nixos.org](https://cache.nixos.org/)). Most of the hard work happens here.
|
||||
|
||||
In order to get the derivations from the local machine to the build host,
|
||||
`nixos-rebuild` uses `nix copy --derivation --to`,
|
||||
which works just like `nix-copy-closure` but copies derivations instead of the entire closure.
|
||||
Thn the hard work can happen on the destination without needing to copy all the nix source code and dependencies.
|
||||
|
||||
|
||||
# Step 2: Add a profile
|
||||
|
||||
# Step 3: Activate
|
||||
|
||||
[^booted-system]: ... for a certain definition of "running". Software is loaded from here but the kernel and modules will be in the version in `/run/booted-system` because Linux can't load modules from other kernel versions. This is only setup at boot and won't be changed by a `nixos-rebuild switch`.
|
||||
[^flake-eval]: It is actually possible to evaluate flakes on the remote machine, but this isn't supported. The `nix flake archive` command, which copies a flake and all of its inputs to the nix store, can copy to another machine with the `--to` argument. Building this way works but I haven't bothered writing a patch.
|
||||
[^type]: In NixOS `type` defines not just "can I set this to a string or only a list" but also what happens when multiple conflicting options are set. If you set `environment.systemPackages = [ pkgs.git ];` in one file and `environment.systemPackages = [ pkgs.mercurial ];` then the result will be `[ pkgs.git pkgs.mercurial ]` because `listOf` says to merge them.
|
184
_drafts/nixos-rebuild.md
Normal file
184
_drafts/nixos-rebuild.md
Normal file
|
@ -0,0 +1,184 @@
|
|||
---
|
||||
layout: post
|
||||
title: What is nixos-rebuild anyway?
|
||||
date: 2024-04-12
|
||||
---
|
||||
If you've used NixOS before, you've almost certainly used the `nixos-rebuild` program before.
|
||||
With one `nixos-rebuild switch` command you can build your updated system configuration,
|
||||
add it to your bootloader as the default entry, stop all old services, and start any new services.
|
||||
|
||||
What you may not know is that `nixos-rebuild` is a bash script and you can do everything (relatively) easily without it.
|
||||
The [full source code](https://github.com/NixOS/nixpkgs/blob/c074160dcfa338f8424c440ccb0f0a5412de0dbf/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh) is quite long and includes many special cases, but most of these aren't necessary if you're building manually.
|
||||
|
||||
Unfortunately, there's one important question we have to answer first:
|
||||
|
||||
# What is a NixOS?
|
||||
NixOS is a very complicated way of defining options and setting them to a value.
|
||||
For example, your configuration you could set:
|
||||
```nix
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
```
|
||||
The value is matched by an "option" of the same name which describes the default value and the type [^type].
|
||||
This is how NixOS describes `environment.systemPackages`:
|
||||
```nix
|
||||
options.environment.systemPackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
|
||||
description = lib.mdDoc ''
|
||||
...
|
||||
'';
|
||||
};
|
||||
```
|
||||
|
||||
The idea that makes this useful is that you can set values based off of each other.
|
||||
For example, the `htop` program sets:
|
||||
```nix
|
||||
with lib;
|
||||
let
|
||||
cfg = config.programs.htop;
|
||||
...
|
||||
in {
|
||||
...
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
environment.etc."htoprc".text = ''
|
||||
# Global htop configuration
|
||||
# To change set: programs.htop.settings.KEY = VALUE;
|
||||
'' + concatStringsSep "\n" (mapAttrsToList (key: value: "${key}=${fmt value}") cfg.settings);
|
||||
};
|
||||
}
|
||||
```
|
||||
Meaning that when `programs.htop.enable` is set then `programs.htop.package` is added to `environment.systemPackages` (to add htop to your path)
|
||||
and `environment.etc."htoprc".text` is set to an autogenerated configuration file (to create the configuration file in `/etc/htoprc`).
|
||||
|
||||
## Top-Level
|
||||
So you have a big set of options with values, but that doesn't make it an operating system.
|
||||
The piece that ties this all together is the "toplevel derivation",
|
||||
which you can access through the `system.build.toplevel` option.
|
||||
The [full definition](https://github.com/NixOS/nixpkgs/blob/c074160dcfa338f8424c440ccb0f0a5412de0dbf/nixos/modules/system/activation/top-level.nix#L48) is a bit obtuse, but in short
|
||||
it's a package that links to every generated file that you need for your operating system.
|
||||
|
||||
For example, when you set a kernel, the toplevel derivation sees that and puts it in `kernel`,
|
||||
when a module wants to put a configuration file in `/etc` it creates a file in `etc`, and when you add a package with `environment.systemPackages` it gets stuffed `sw`. Here's what's in mine:
|
||||
```
|
||||
artemis@starlight ~> tree -L 1 /run/current-system/
|
||||
/run/current-system/
|
||||
├── activate
|
||||
├── append-initrd-secrets -> /nix/store/b9179a3c206iid1z0fkr9d53kd76hm8q-append-initrd-secrets/bin/append-initrd-secrets
|
||||
├── bin
|
||||
├── boot.json
|
||||
├── dry-activate
|
||||
├── etc -> /nix/store/s7g253q8pf9lzw80cc20xfpbc2x9w6dv-etc/etc
|
||||
├── extra-dependencies
|
||||
├── firmware -> /nix/store/pw1mlhjxsg8b8id9g9n503h989k5gw6g-firmware/lib/firmware
|
||||
├── init
|
||||
├── init-interface-version
|
||||
├── initrd -> /nix/store/pk1kck0lknn4ap9d46ivnl049142xkpq-initrd-linux-6.8.3/initrd
|
||||
├── kernel -> /nix/store/0ir2cc8bjfp1idpqvyf9vphwxw0rj6g7-linux-6.8.3/bzImage
|
||||
├── kernel-modules -> /nix/store/rvk7dwjzy2090l58a8057r311il83s1m-linux-6.8.3-modules
|
||||
├── kernel-params
|
||||
├── nixos-version
|
||||
├── specialisation
|
||||
├── sw -> /nix/store/bdcvja8kfwkrz35ilb33pn89ls9mymkx-system-path
|
||||
├── system
|
||||
└── systemd -> /nix/store/4npvfi1zh3igsgglxqzwg0w7m2h7sr9b-systemd-255.4
|
||||
```
|
||||
|
||||
If you want to see yours, then go to `/run/current-system` on a NixOS machine,
|
||||
which will always have the version you're currently running [^booted-system].
|
||||
|
||||
|
||||
# An actual rebuild
|
||||
With all of that out of the way, let's step through an actual `nixos-rebuild` call:
|
||||
|
||||
## Step 0: Build nix
|
||||
The `nixos-rebuild` script tries not to make too many assumptions about the build host. It must have a nix store,
|
||||
but that doesn't necessarily mean it has a new enough nix to build your configuration, or that your configuration
|
||||
is defined using only settings that your build nix can understand. Therefore, it downloads a newer nix if possible,
|
||||
or builds one using your configuration. I'm not _entirely_ sure what cursed setup you'd need to make this useful,
|
||||
but it does happen.
|
||||
|
||||
## Step 1: Build your system
|
||||
Now that it has a nix to use, it's time to build your toplevel derivation. If you're using [flakes](https://zero-to-nix.com/concepts/flakes) that means it runs [^gcroot]
|
||||
```shell
|
||||
nix build /etc/nixos#nixosConfigurations.$(hostname).config.system.build.toplevel
|
||||
```
|
||||
which builds the toplevel derivation based on your hostname from the flake in `/etc/nixos`. If you don't like that default you can pass `--flake /your/flake#system-name` to `nixos-rebuild` and it will build `/your/flake#nixosConfigurations.system-name.config.system.build.toplevel` instead.
|
||||
|
||||
If you're not using flakes, that means [^no-link]
|
||||
```shell
|
||||
nix-build <nixpkgs/nixos> -A system
|
||||
```
|
||||
which builds the toplevel derivation
|
||||
based on `/etc/nixos/configuration.nix` using the nixpkgs in its [channel](https://zero-to-nix.com/concepts/channels). The path is somewhat obscured here though, even in [the source](https://github.com/NixOS/nixpkgs/blob/c074160dcfa338f8424c440ccb0f0a5412de0dbf/nixos/default.nix#L1): By default the configuration fie is loaded from the `nixos-config` channel,
|
||||
which nixos sets to `/etc/nixos/configuration.nix`.
|
||||
If you want to build from some other path, you can set `NIXOS_CONFIG` environment variable or pass `-I nixos-config=/your/path/to/whatever.nix` to `nixos-rebuild`, which will get passed through to `nix-build`.
|
||||
|
||||
## Step 2: Add a profile
|
||||
While your configuration lists everything that should be installed and running when it's active,
|
||||
it has no way of referencing previous configurations.
|
||||
NixOS handles this by creating a "profile", a fancy way of saying "create a symlink to each version".
|
||||
Profiles serve a dual purpose of being a "garbage collector root" (telling nix that it shouldn't delete these paths) and creating a list of versions for you to choose from, in case you want to rollback.
|
||||
|
||||
The symlinks are named `/nix/var/nix/profiles/system-{n}-link` for the version history
|
||||
and `/nix/var/nix/profiles/system` for the default.
|
||||
|
||||
Nix has an easy command to set this profile and create a new numbered version if necessary:
|
||||
`nix-env -p /nix/var/nix/profiles/system --set $(readlink result)`
|
||||
|
||||
|
||||
## Step 3: Activate
|
||||
The final step, activation, sets a lot of things in motion.
|
||||
It uses two important scripts inside the toplevel derivation:
|
||||
|
||||
`activate` sets up the most important configuration files that you need both during boot and while switching. Its responsibilities include:
|
||||
- Linking static configuration files from the toplevel derivation to `/etc`
|
||||
- Creating users and groups
|
||||
- Creating the impure `/bin/sh` and `/usr/bin/env` programs
|
||||
- Linking the toplevel to `/run/current-system`
|
||||
|
||||
`bin/switch-to-configuration` sets up files that only make sense when you've just made a new toplevel. It does things like:
|
||||
- Install the bootloader
|
||||
- Create bootloader entries for each of the system versions in the profile
|
||||
- Run `activate`
|
||||
- Figure out which services need to be restarted and restart them
|
||||
- Tell systemd to restart itself if needed
|
||||
|
||||
If you want more detail the [NixOS Manual](https://nixos.org/manual/nixos/stable/#sec-switching-systems) has a
|
||||
reasonable description of how `bin/switch-to-configuration` works.
|
||||
|
||||
|
||||
`nixos-rebuild` doesn't have to deal with any of that though.
|
||||
It just has to run [^systemd-run]
|
||||
```shell
|
||||
env -i LOCALE_ARCHIVE=$LOCALE_ARCHIVE NIXOS_INSTALL_BOOTLOADER= \
|
||||
$(readlink result)/bin/switch-to-configuration switch
|
||||
```
|
||||
Clearing the environment with `env -i` helps prevent weird impurities due to e.g. a strange PATH setting,
|
||||
though it's not technically necessary.
|
||||
The `LOCALE_ARCHIVE` variable is to fix programs complaining if they can't find internationalization metadata.
|
||||
|
||||
# Doing it yourself
|
||||
You can put all this together to switch yourself without `nixos-rebuild`:
|
||||
```shell
|
||||
# (flakes)
|
||||
nix build /etc/nixos#nixosConfigurations.$(hostname).config.system.build.toplevel
|
||||
# (not flakes)
|
||||
nix-build <nixos/nixpkgs> -A system
|
||||
|
||||
sudo nix-env -p /nix/var/nix/profiles/system --set $(readlink result)
|
||||
sudo result/bin/switch-to-configuration switch
|
||||
```
|
||||
|
||||
A lot less code than the 800 lines `nixos-rebuild` needs.
|
||||
|
||||
|
||||
#### Footnotes
|
||||
|
||||
[^booted-system]: ... for a certain definition of "running". Software is loaded from here but the kernel and modules will be in the version in `/run/booted-system` because Linux can't load modules from other kernel versions. This is only setup at boot and won't be changed by a `nixos-rebuild switch`.
|
||||
[^type]: In NixOS `type` defines not just "can I set this to a string or only a list" but also what happens when multiple conflicting options are set. If you set `environment.systemPackages = [ pkgs.git ];` in one file and `environment.systemPackages = [ pkgs.mercurial ];` then the result will be `[ pkgs.git pkgs.mercurial ]` because `listOf` says to merge them.
|
||||
[^gcroot]: `nixos-rebuild` also passes `--out-link ${tmpDir}/result` for the flake builder, which creates a [garbage collector](https://nixos.org/manual/nix/stable/package-management/garbage-collection) root in a temporary directory so nix won't delete the toplevel derivation between building it and the next step. I'm not sure why this only happens for flakes.
|
||||
[^no-link]: The `nix-build` command for non-flakes is also run with `--no-out-link` so it won't create a result symlink cluttering your current directory. `nixos-rebuild` reads the path of toplevel from `nix-build`'s standard out.
|
||||
[^systemd-run]: Although it might run this command sometimes, `nixos-rebuild` prefers a much longer `systemd-run` command. This runs the switch in the background, so it won't get killed if you lose your session because of networking issues.
|
||||
|
Loading…
Reference in a new issue