From 6f86cf9bead6638544c8742cf25ecd66f8d0dbae Mon Sep 17 00:00:00 2001 From: Artemis Tosini Date: Fri, 12 Apr 2024 05:02:47 +0000 Subject: [PATCH] nixos-rebuild should be ready for editing --- _config.yml | 13 +- _drafts/_nixos-rebuild-2.md | 19 +++ _drafts/draft-the-trouble-with-time.md | 23 ++++ _drafts/nixos-rebuild-1.md | 86 ------------ _drafts/nixos-rebuild.md | 184 +++++++++++++++++++++++++ 5 files changed, 235 insertions(+), 90 deletions(-) create mode 100644 _drafts/_nixos-rebuild-2.md create mode 100644 _drafts/draft-the-trouble-with-time.md delete mode 100644 _drafts/nixos-rebuild-1.md create mode 100644 _drafts/nixos-rebuild.md diff --git a/_config.yml b/_config.yml index 0dcadd1..6c0e752 100644 --- a/_config.yml +++ b/_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 diff --git a/_drafts/_nixos-rebuild-2.md b/_drafts/_nixos-rebuild-2.md new file mode 100644 index 0000000..b56a18d --- /dev/null +++ b/_drafts/_nixos-rebuild-2.md @@ -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. + diff --git a/_drafts/draft-the-trouble-with-time.md b/_drafts/draft-the-trouble-with-time.md new file mode 100644 index 0000000..0e8e2d7 --- /dev/null +++ b/_drafts/draft-the-trouble-with-time.md @@ -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` diff --git a/_drafts/nixos-rebuild-1.md b/_drafts/nixos-rebuild-1.md deleted file mode 100644 index a0e7b9d..0000000 --- a/_drafts/nixos-rebuild-1.md +++ /dev/null @@ -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 '' - - ''; -}; -``` - -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. diff --git a/_drafts/nixos-rebuild.md b/_drafts/nixos-rebuild.md new file mode 100644 index 0000000..42254f0 --- /dev/null +++ b/_drafts/nixos-rebuild.md @@ -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 -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 -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. +