about summary refs log tree commit diff
path: root/pkgs/by-name/sw
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/sw')
-rw-r--r--pkgs/by-name/sw/sway-assign-cgroups/package.nix2
-rw-r--r--pkgs/by-name/sw/sway-easyfocus/package.nix6
-rw-r--r--pkgs/by-name/sw/sway-unwrapped/package.nix2
-rw-r--r--pkgs/by-name/sw/sway/package.nix4
-rw-r--r--pkgs/by-name/sw/swaycons/package.nix12
-rw-r--r--pkgs/by-name/sw/swaylock/package.nix2
-rw-r--r--pkgs/by-name/sw/swaymux/package.nix2
-rw-r--r--pkgs/by-name/sw/swayosd/package.nix6
-rw-r--r--pkgs/by-name/sw/swayws/package.nix2
-rw-r--r--pkgs/by-name/sw/swaywsr/package.nix8
-rw-r--r--pkgs/by-name/sw/swiftlint/package.nix42
-rw-r--r--pkgs/by-name/sw/switch-to-configuration-ng/.gitignore1
-rw-r--r--pkgs/by-name/sw/switch-to-configuration-ng/Cargo.lock527
-rw-r--r--pkgs/by-name/sw/switch-to-configuration-ng/Cargo.toml19
-rw-r--r--pkgs/by-name/sw/switch-to-configuration-ng/build.rs30
-rw-r--r--pkgs/by-name/sw/switch-to-configuration-ng/package.nix36
-rw-r--r--pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs2128
-rw-r--r--pkgs/by-name/sw/switcheroo/package.nix10
-rw-r--r--pkgs/by-name/sw/sword/package.nix64
-rw-r--r--pkgs/by-name/sw/swww/Cargo.lock138
-rw-r--r--pkgs/by-name/sw/swww/package.nix32
21 files changed, 2958 insertions, 115 deletions
diff --git a/pkgs/by-name/sw/sway-assign-cgroups/package.nix b/pkgs/by-name/sw/sway-assign-cgroups/package.nix
index 83602655a669b..47acd2ae4d1d1 100644
--- a/pkgs/by-name/sw/sway-assign-cgroups/package.nix
+++ b/pkgs/by-name/sw/sway-assign-cgroups/package.nix
@@ -24,7 +24,7 @@ python3Packages.buildPythonApplication rec {
   '';
 
   meta = with lib; {
-    description = "Place GUI applications into systemd scopes for systemd-oomd compatibility.";
+    description = "Place GUI applications into systemd scopes for systemd-oomd compatibility";
     mainProgram = "assign-cgroups.py";
     longDescription = ''
       Automatically assign a dedicated systemd scope to the GUI applications
diff --git a/pkgs/by-name/sw/sway-easyfocus/package.nix b/pkgs/by-name/sw/sway-easyfocus/package.nix
index 022770a223441..bc19c7ebd01d7 100644
--- a/pkgs/by-name/sw/sway-easyfocus/package.nix
+++ b/pkgs/by-name/sw/sway-easyfocus/package.nix
@@ -2,7 +2,7 @@
 , rustPlatform
 , fetchFromGitHub
 , pkg-config
-, wrapGAppsHook
+, wrapGAppsHook3
 , atk
 , cairo
 , gdk-pixbuf
@@ -27,7 +27,7 @@ rustPlatform.buildRustPackage rec {
 
   nativeBuildInputs = [
     pkg-config
-    wrapGAppsHook
+    wrapGAppsHook3
   ];
 
   buildInputs = [
@@ -41,7 +41,7 @@ rustPlatform.buildRustPackage rec {
   ];
 
   meta = {
-    description = "A tool to help efficiently focus windows in Sway, inspired by i3-easyfocus";
+    description = "Tool to help efficiently focus windows in Sway, inspired by i3-easyfocus";
     homepage = "https://github.com/edzdez/sway-easyfocus";
     license = lib.licenses.mit;
     maintainers = with lib.maintainers; [ eclairevoyant ];
diff --git a/pkgs/by-name/sw/sway-unwrapped/package.nix b/pkgs/by-name/sw/sway-unwrapped/package.nix
index 8a747f8e19471..6bcff1c52a2d2 100644
--- a/pkgs/by-name/sw/sway-unwrapped/package.nix
+++ b/pkgs/by-name/sw/sway-unwrapped/package.nix
@@ -78,7 +78,7 @@ stdenv.mkDerivation (finalAttrs: {
   passthru.tests.basic = nixosTests.sway;
 
   meta = {
-    description = "An i3-compatible tiling Wayland compositor";
+    description = "I3-compatible tiling Wayland compositor";
     longDescription = ''
       Sway is a tiling Wayland compositor and a drop-in replacement for the i3
       window manager for X11. It works with your existing i3 configuration and
diff --git a/pkgs/by-name/sw/sway/package.nix b/pkgs/by-name/sw/sway/package.nix
index 2ae6d3ed01546..5506447f692ec 100644
--- a/pkgs/by-name/sw/sway/package.nix
+++ b/pkgs/by-name/sw/sway/package.nix
@@ -2,7 +2,7 @@
 , sway-unwrapped
 , makeWrapper, symlinkJoin, writeShellScriptBin
 , withBaseWrapper ? true, extraSessionCommands ? "", dbus
-, withGtkWrapper ? false, wrapGAppsHook, gdk-pixbuf, glib, gtk3
+, withGtkWrapper ? false, wrapGAppsHook3, gdk-pixbuf, glib, gtk3
 , extraOptions ? [] # E.g.: [ "--verbose" ]
 # Used by the NixOS module:
 , isNixOS ? false
@@ -44,7 +44,7 @@ in symlinkJoin rec {
 
   strictDeps = false;
   nativeBuildInputs = [ makeWrapper ]
-    ++ (optional withGtkWrapper wrapGAppsHook);
+    ++ (optional withGtkWrapper wrapGAppsHook3);
 
   buildInputs = optionals withGtkWrapper [ gdk-pixbuf glib gtk3 ];
 
diff --git a/pkgs/by-name/sw/swaycons/package.nix b/pkgs/by-name/sw/swaycons/package.nix
index 9716f87087951..b60b5a6115a21 100644
--- a/pkgs/by-name/sw/swaycons/package.nix
+++ b/pkgs/by-name/sw/swaycons/package.nix
@@ -5,21 +5,21 @@
 
 rustPlatform.buildRustPackage rec {
   pname = "swaycons";
-  version = "unstable-2023-01-05";
+  version = "unstable-2023-11-29";
 
   src = fetchFromGitHub {
-    owner = "ActuallyAllie";
+    owner = "allie-wake-up";
     repo = "swaycons";
-    rev = "e863599fb56177fc9747d60db661be2d7c2d290b";
-    hash = "sha256-zkCpZ3TehFKNePtSyFaEk+MA4mi1+la9yFjRPFy+eq8=";
+    rev = "aa1102393be34e8bd7ed4e74c574e851fbd8cff9";
+    hash = "sha256-vyZcfBH2mry8Yd41QPX4+yLv0nS9J1yrgg7lpslJs7M=";
   };
 
-  cargoSha256 = "sha256-GcoRx52dwL/ehJ1Xg6xQHVzPIKXWqBrG7IjzxRjfgqA=";
+  cargoSha256 = "sha256-cdZ7DpH//c9TulvPYd6aaXpQHYC1b+T7BrxAyr56Pf0=";
 
   meta = with lib; {
     description = "Window Icons in Sway with Nerd Fonts!";
     mainProgram = "swaycons";
-    homepage = "https://github.com/ActuallyAllie/swaycons";
+    homepage = "https://github.com/allie-wake-up/swaycons";
     license = licenses.asl20;
     platforms = platforms.linux;
     maintainers = with maintainers; [ aacebedo ];
diff --git a/pkgs/by-name/sw/swaylock/package.nix b/pkgs/by-name/sw/swaylock/package.nix
index 0b438df13d289..0ba3b727c9490 100644
--- a/pkgs/by-name/sw/swaylock/package.nix
+++ b/pkgs/by-name/sw/swaylock/package.nix
@@ -1,4 +1,4 @@
-{ lib, stdenv, fetchFromGitHub, fetchpatch
+{ lib, stdenv, fetchFromGitHub
 , meson, ninja, pkg-config, scdoc, wayland-scanner
 , wayland, wayland-protocols, libxkbcommon, cairo, gdk-pixbuf, pam
 }:
diff --git a/pkgs/by-name/sw/swaymux/package.nix b/pkgs/by-name/sw/swaymux/package.nix
index dfe928d845205..7d9fee3866517 100644
--- a/pkgs/by-name/sw/swaymux/package.nix
+++ b/pkgs/by-name/sw/swaymux/package.nix
@@ -24,7 +24,7 @@ stdenv.mkDerivation (finalAttrs: {
 
   meta = with lib; {
     changelog = "https://git.grimmauld.de/Grimmauld/swaymux/commits/branch/main";
-    description = "A program to quickly navigate sway";
+    description = "Program to quickly navigate sway";
     homepage = "https://git.grimmauld.de/Grimmauld/swaymux";
     license = licenses.bsd3;
     longDescription = ''
diff --git a/pkgs/by-name/sw/swayosd/package.nix b/pkgs/by-name/sw/swayosd/package.nix
index d3238053c0196..f7f5c84d204b4 100644
--- a/pkgs/by-name/sw/swayosd/package.nix
+++ b/pkgs/by-name/sw/swayosd/package.nix
@@ -2,7 +2,7 @@
 , rustPlatform
 , fetchFromGitHub
 , pkg-config
-, wrapGAppsHook
+, wrapGAppsHook3
 , brightnessctl
 , cargo
 , coreutils
@@ -36,7 +36,7 @@ stdenv.mkDerivation rec {
   };
 
   nativeBuildInputs = [
-    wrapGAppsHook
+    wrapGAppsHook3
     pkg-config
     meson
     rustc
@@ -71,7 +71,7 @@ stdenv.mkDerivation rec {
   '';
 
   meta = with lib; {
-    description = "A GTK based on screen display for keyboard shortcuts";
+    description = "GTK based on screen display for keyboard shortcuts";
     homepage = "https://github.com/ErikReider/SwayOSD";
     license = licenses.gpl3Plus;
     maintainers = with maintainers; [ aleksana barab-i sergioribera ];
diff --git a/pkgs/by-name/sw/swayws/package.nix b/pkgs/by-name/sw/swayws/package.nix
index a216e9efba71c..902db2f603d10 100644
--- a/pkgs/by-name/sw/swayws/package.nix
+++ b/pkgs/by-name/sw/swayws/package.nix
@@ -21,7 +21,7 @@ rustPlatform.buildRustPackage rec {
   doCheck = false;
 
   meta = with lib; {
-    description = "A sway workspace tool which allows easy moving of workspaces to and from outputs";
+    description = "Sway workspace tool which allows easy moving of workspaces to and from outputs";
     mainProgram = "swayws";
     homepage = "https://gitlab.com/w0lff/swayws";
     license = licenses.mit;
diff --git a/pkgs/by-name/sw/swaywsr/package.nix b/pkgs/by-name/sw/swaywsr/package.nix
index 5926e605b7505..8901eb3c69e24 100644
--- a/pkgs/by-name/sw/swaywsr/package.nix
+++ b/pkgs/by-name/sw/swaywsr/package.nix
@@ -2,16 +2,16 @@
 
 rustPlatform.buildRustPackage rec {
   pname = "swaywsr";
-  version = "1.1.1";
+  version = "1.3.0";
 
   src = fetchFromGitHub {
     owner = "pedroscaff";
     repo = pname;
-    rev = "0276b43824af5c40085248c1275feaa372c412a5";
-    sha256 = "sha256-KCMsn9uevmmjHkP4zwfaWSUI10JgT3M91iqmXI9Cv2Y=";
+    rev = "521fbf92738f44be438d3be6bdd665f02ac9d35c";
+    hash = "sha256-6hGEcJz+zGfwz1q+XKQYfyJJK7lr+kCgk2/uiq1xP0M=";
   };
 
-  cargoSha256 = "sha256-j/9p28ezy8m5NXReOmG1oryWd+GcY/fNW6i7OrEvjSc=";
+  cargoHash = "sha256-zoV2vy41fVsX8BtddURqQymMX4Zpso+GOBBqoVr3tYo=";
 
   nativeBuildInputs = [ python3 ];
   buildInputs = [ libxcb ];
diff --git a/pkgs/by-name/sw/swiftlint/package.nix b/pkgs/by-name/sw/swiftlint/package.nix
new file mode 100644
index 0000000000000..aa6c641e32415
--- /dev/null
+++ b/pkgs/by-name/sw/swiftlint/package.nix
@@ -0,0 +1,42 @@
+{
+  stdenvNoCC,
+  lib,
+  fetchurl,
+  unzip,
+  nix-update-script,
+}:
+stdenvNoCC.mkDerivation rec {
+  pname = "swiftlint";
+  version = "0.55.1";
+
+  src = fetchurl {
+    url = "https://github.com/realm/SwiftLint/releases/download/${version}/portable_swiftlint.zip";
+    hash = "sha256-Tmhw30CJaVQlcYnHjzmwrDpugHgR2/ihHIV8M+O2zwI=";
+  };
+
+  dontPatch = true;
+  dontConfigure = true;
+  dontBuild = true;
+
+  nativeBuildInputs = [ unzip ];
+
+  sourceRoot = ".";
+
+  installPhase = ''
+    runHook preInstall
+    install -Dm755 swiftlint $out/bin/swiftlint
+    runHook postInstall
+  '';
+
+  passthru.updateScript = nix-update-script { };
+
+  meta = with lib; {
+    description = "A tool to enforce Swift style and conventions";
+    homepage = "https://realm.github.io/SwiftLint/";
+    license = licenses.mit;
+    mainProgram = "swiftlint";
+    maintainers = with maintainers; [ matteopacini ];
+    platforms = platforms.darwin;
+    sourceProvenance = with sourceTypes; [ binaryNativeCode ];
+  };
+}
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/.gitignore b/pkgs/by-name/sw/switch-to-configuration-ng/.gitignore
new file mode 100644
index 0000000000000..ea8c4bf7f35f6
--- /dev/null
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/Cargo.lock b/pkgs/by-name/sw/switch-to-configuration-ng/Cargo.lock
new file mode 100644
index 0000000000000..d66a2c6e75278
--- /dev/null
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/Cargo.lock
@@ -0,0 +1,527 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags 1.3.2",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "dbus"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+ "winapi",
+]
+
+[[package]]
+name = "dbus-codegen"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcd91775d91fc83c7d526aa7c08078bac0b30f382706689901ac819fe6ddc812"
+dependencies = [
+ "clap",
+ "dbus",
+ "xml-rs",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "dlv-list"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
+dependencies = [
+ "const-random",
+]
+
+[[package]]
+name = "error-chain"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "libc"
+version = "0.2.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+
+[[package]]
+name = "libdbus-sys"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "nix"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
+dependencies = [
+ "bitflags 2.5.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "ordered-multimap"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
+dependencies = [
+ "dlv-list",
+ "hashbrown",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "rust-ini"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap",
+ "trim-in-place",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.200"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.200"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "switch-to-configuration"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "dbus",
+ "dbus-codegen",
+ "glob",
+ "log",
+ "nix",
+ "regex",
+ "rust-ini",
+ "syslog",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syslog"
+version = "6.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc7e95b5b795122fafe6519e27629b5ab4232c73ebb2428f568e82b1a457ad3"
+dependencies = [
+ "error-chain",
+ "hostname",
+ "libc",
+ "log",
+ "time",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "trim-in-place"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/Cargo.toml b/pkgs/by-name/sw/switch-to-configuration-ng/Cargo.toml
new file mode 100644
index 0000000000000..51d805dcd959d
--- /dev/null
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "switch-to-configuration"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.82"
+dbus = "0.9.7"
+glob = "0.3.1"
+log = "0.4.21"
+nix = { version = "0.28.0", features = ["fs", "signal"] }
+regex = "1.10.4"
+rust-ini = "0.21.0"
+syslog = "6.1.1"
+
+[build-dependencies]
+dbus-codegen = "0.11.0"
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/build.rs b/pkgs/by-name/sw/switch-to-configuration-ng/build.rs
new file mode 100644
index 0000000000000..41fed3086e690
--- /dev/null
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/build.rs
@@ -0,0 +1,30 @@
+use std::io::Write;
+
+fn code_for_dbus_xml(xml: impl AsRef<std::path::Path>) -> String {
+    dbus_codegen::generate(
+        &std::fs::read_to_string(xml).unwrap(),
+        &dbus_codegen::GenOpts {
+            methodtype: None,
+            connectiontype: dbus_codegen::ConnectionType::Blocking,
+            ..Default::default()
+        },
+    )
+    .unwrap()
+}
+
+fn main() {
+    let systemd_dbus_interface_dir = std::env::var("SYSTEMD_DBUS_INTERFACE_DIR").unwrap();
+    let systemd_dbus_interface_dir = std::path::Path::new(systemd_dbus_interface_dir.as_str());
+
+    let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
+
+    let systemd_manager_code =
+        code_for_dbus_xml(systemd_dbus_interface_dir.join("org.freedesktop.systemd1.Manager.xml"));
+    let mut file = std::fs::File::create(out_path.join("systemd_manager.rs")).unwrap();
+    file.write_all(systemd_manager_code.as_bytes()).unwrap();
+
+    let logind_manager_code =
+        code_for_dbus_xml(systemd_dbus_interface_dir.join("org.freedesktop.login1.Manager.xml"));
+    let mut file = std::fs::File::create(out_path.join("logind_manager.rs")).unwrap();
+    file.write_all(logind_manager_code.as_bytes()).unwrap();
+}
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/package.nix b/pkgs/by-name/sw/switch-to-configuration-ng/package.nix
new file mode 100644
index 0000000000000..aa748a0c2b3d5
--- /dev/null
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/package.nix
@@ -0,0 +1,36 @@
+{
+  buildPackages,
+  dbus,
+  lib,
+  pkg-config,
+  rustPlatform,
+}:
+
+rustPlatform.buildRustPackage {
+  pname = "switch-to-configuration";
+  version = "0.1.0";
+
+  src = lib.fileset.toSource {
+    root = ./.;
+    fileset = lib.fileset.unions [
+      ./Cargo.lock
+      ./Cargo.toml
+      ./build.rs
+      ./src
+    ];
+  };
+
+  cargoLock.lockFile = ./Cargo.lock;
+
+  nativeBuildInputs = [ pkg-config ];
+  buildInputs = [ dbus ];
+
+  env.SYSTEMD_DBUS_INTERFACE_DIR = "${buildPackages.systemd}/share/dbus-1/interfaces";
+
+  meta = {
+    description = "NixOS switch-to-configuration program";
+    mainProgram = "switch-to-configuration";
+    maintainers = with lib.maintainers; [ jmbaur ];
+    license = lib.licenses.mit;
+  };
+}
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs b/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs
new file mode 100644
index 0000000000000..a35b55065c073
--- /dev/null
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs
@@ -0,0 +1,2128 @@
+use std::{
+    cell::RefCell,
+    collections::HashMap,
+    io::{BufRead, Read, Write},
+    os::unix::{fs::PermissionsExt, process::CommandExt},
+    path::{Path, PathBuf},
+    rc::Rc,
+    str::FromStr,
+    sync::OnceLock,
+    time::Duration,
+};
+
+use anyhow::{anyhow, bail, Context, Result};
+use dbus::{
+    blocking::{stdintf::org_freedesktop_dbus::Properties, LocalConnection, Proxy},
+    Message,
+};
+use glob::glob;
+use ini::{Ini, ParseOption};
+use log::LevelFilter;
+use nix::{
+    fcntl::{Flock, FlockArg, OFlag},
+    sys::{
+        signal::{self, SigHandler, Signal},
+        stat::Mode,
+    },
+};
+use regex::Regex;
+use syslog::Facility;
+
+mod systemd_manager {
+    #![allow(non_upper_case_globals)]
+    #![allow(non_camel_case_types)]
+    #![allow(non_snake_case)]
+    #![allow(unused)]
+    include!(concat!(env!("OUT_DIR"), "/systemd_manager.rs"));
+}
+
+mod logind_manager {
+    #![allow(non_upper_case_globals)]
+    #![allow(non_camel_case_types)]
+    #![allow(non_snake_case)]
+    #![allow(unused)]
+    include!(concat!(env!("OUT_DIR"), "/logind_manager.rs"));
+}
+
+use crate::systemd_manager::OrgFreedesktopSystemd1Manager;
+use crate::{
+    logind_manager::OrgFreedesktopLogin1Manager,
+    systemd_manager::{
+        OrgFreedesktopSystemd1ManagerJobRemoved, OrgFreedesktopSystemd1ManagerReloading,
+    },
+};
+
+type UnitInfo = HashMap<String, HashMap<String, Vec<String>>>;
+
+const SYSINIT_REACTIVATION_TARGET: &str = "sysinit-reactivation.target";
+
+// To be robust against interruption, record what units need to be started etc. We read these files
+// again every time this program starts to make sure we continue where the old (interrupted) script
+// left off.
+const START_LIST_FILE: &str = "/run/nixos/start-list";
+const RESTART_LIST_FILE: &str = "/run/nixos/restart-list";
+const RELOAD_LIST_FILE: &str = "/run/nixos/reload-list";
+
+// Parse restart/reload requests by the activation script. Activation scripts may write
+// newline-separated units to the restart file and switch-to-configuration will handle them. While
+// `stopIfChanged = true` is ignored, switch-to-configuration will handle `restartIfChanged =
+// false` and `reloadIfChanged = true`. This is the same as specifying a restart trigger in the
+// NixOS module.
+//
+// The reload file asks this program to reload a unit. This is the same as specifying a reload
+// trigger in the NixOS module and can be ignored if the unit is restarted in this activation.
+const RESTART_BY_ACTIVATION_LIST_FILE: &str = "/run/nixos/activation-restart-list";
+const RELOAD_BY_ACTIVATION_LIST_FILE: &str = "/run/nixos/activation-reload-list";
+const DRY_RESTART_BY_ACTIVATION_LIST_FILE: &str = "/run/nixos/dry-activation-restart-list";
+const DRY_RELOAD_BY_ACTIVATION_LIST_FILE: &str = "/run/nixos/dry-activation-reload-list";
+
+#[derive(Debug, Clone, PartialEq)]
+enum Action {
+    Switch,
+    Boot,
+    Test,
+    DryActivate,
+}
+
+impl std::str::FromStr for Action {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        Ok(match s {
+            "switch" => Self::Switch,
+            "boot" => Self::Boot,
+            "test" => Self::Test,
+            "dry-activate" => Self::DryActivate,
+            _ => bail!("invalid action {s}"),
+        })
+    }
+}
+
+impl Into<&'static str> for &Action {
+    fn into(self) -> &'static str {
+        match self {
+            Action::Switch => "switch",
+            Action::Boot => "boot",
+            Action::Test => "test",
+            Action::DryActivate => "dry-activate",
+        }
+    }
+}
+
+// Allow for this switch-to-configuration to remain consistent with the perl implementation.
+// Perl's "die" uses errno to set the exit code: https://perldoc.perl.org/perlvar#%24%21
+fn die() -> ! {
+    std::process::exit(std::io::Error::last_os_error().raw_os_error().unwrap_or(1));
+}
+
+fn parse_os_release() -> Result<HashMap<String, String>> {
+    Ok(std::fs::read_to_string("/etc/os-release")
+        .context("Failed to read /etc/os-release")?
+        .lines()
+        .into_iter()
+        .fold(HashMap::new(), |mut acc, line| {
+            if let Some((k, v)) = line.split_once('=') {
+                acc.insert(k.to_string(), v.to_string());
+            }
+
+            acc
+        }))
+}
+
+fn do_install_bootloader(command: &str, toplevel: &Path) -> Result<()> {
+    let mut cmd_split = command.split_whitespace();
+    let Some(argv0) = cmd_split.next() else {
+        bail!("missing first argument in install bootloader commands");
+    };
+
+    match std::process::Command::new(argv0)
+        .args(cmd_split.collect::<Vec<&str>>())
+        .arg(toplevel)
+        .spawn()
+        .map(|mut child| child.wait())
+    {
+        Ok(Ok(status)) if status.success() => {}
+        _ => {
+            eprintln!("Failed to install bootloader");
+            die();
+        }
+    }
+
+    Ok(())
+}
+
+extern "C" fn handle_sigpipe(_signal: nix::libc::c_int) {}
+
+fn required_env(var: &str) -> anyhow::Result<String> {
+    std::env::var(var).with_context(|| format!("missing required environment variable ${var}"))
+}
+
+#[derive(Debug)]
+struct UnitState {
+    state: String,
+    substate: String,
+}
+
+// Asks the currently running systemd instance via dbus which units are active. Returns a hash
+// where the key is the name of each unit and the value a hash of load, state, substate.
+fn get_active_units<'a>(
+    systemd_manager: &Proxy<'a, &LocalConnection>,
+) -> Result<HashMap<String, UnitState>> {
+    let units = systemd_manager
+        .list_units_by_patterns(Vec::new(), Vec::new())
+        .context("Failed to list systemd units")?;
+
+    Ok(units
+        .into_iter()
+        .filter_map(
+            |(
+                id,
+                _description,
+                _load_state,
+                active_state,
+                sub_state,
+                following,
+                _unit_path,
+                _job_id,
+                _job_type,
+                _job_path,
+            )| {
+                if following == "" && active_state != "inactive" {
+                    Some((id, active_state, sub_state))
+                } else {
+                    None
+                }
+            },
+        )
+        .fold(HashMap::new(), |mut acc, (id, active_state, sub_state)| {
+            acc.insert(
+                id,
+                UnitState {
+                    state: active_state,
+                    substate: sub_state,
+                },
+            );
+
+            acc
+        }))
+}
+
+// This function takes a single ini file that specified systemd configuration like unit
+// configuration and parses it into a HashMap where the keys are the sections of the unit file and
+// the values are HashMaps themselves. These HashMaps have the unit file keys as their keys (left
+// side of =) and an array of all values that were set as their values. If a value is empty (for
+// example `ExecStart=`), then all current definitions are removed.
+//
+// Instead of returning the HashMap, this function takes a mutable reference to a HashMap to return
+// the data in. This allows calling the function multiple times with the same Hashmap to parse
+// override files.
+fn parse_systemd_ini(data: &mut UnitInfo, mut unit_file: impl Read) -> Result<()> {
+    let mut unit_file_content = String::new();
+    _ = unit_file
+        .read_to_string(&mut unit_file_content)
+        .context("Failed to read unit file")?;
+
+    let ini = Ini::load_from_str_opt(
+        &unit_file_content,
+        ParseOption {
+            enabled_quote: true,
+            // Allow for escaped characters that won't get interpreted by the INI parser. These
+            // often show up in systemd unit files device/mount/swap unit names (e.g. dev-disk-by\x2dlabel-root.device).
+            enabled_escape: false,
+        },
+    )
+    .context("Failed parse unit file as INI")?;
+
+    // Copy over all sections
+    for (section, properties) in ini.iter() {
+        let Some(section) = section else {
+            continue;
+        };
+
+        if section == "Install" {
+            // Skip the [Install] section because it has no relevant keys for us
+            continue;
+        }
+
+        let section_map = if let Some(section_map) = data.get_mut(section) {
+            section_map
+        } else {
+            data.insert(section.to_string(), HashMap::new());
+            data.get_mut(section)
+                .ok_or(anyhow!("section name should exist in hashmap"))?
+        };
+
+        for (ini_key, _) in properties {
+            let values = properties.get_all(ini_key);
+            let values = values
+                .into_iter()
+                .map(String::from)
+                .collect::<Vec<String>>();
+
+            // Go over all values
+            let mut new_vals = Vec::new();
+            let mut clear_existing = false;
+
+            for val in values {
+                // If a value is empty, it's an override that tells us to clean the value
+                if val.is_empty() {
+                    new_vals.clear();
+                    clear_existing = true;
+                } else {
+                    new_vals.push(val);
+                }
+            }
+
+            match (section_map.get_mut(ini_key), clear_existing) {
+                (Some(existing_vals), false) => existing_vals.extend(new_vals),
+                _ => _ = section_map.insert(ini_key.to_string(), new_vals),
+            };
+        }
+    }
+
+    Ok(())
+}
+
+// This function takes the path to a systemd configuration file (like a unit configuration) and
+// parses it into a UnitInfo structure.
+//
+// If a directory with the same basename ending in .d exists next to the unit file, it will be
+// assumed to contain override files which will be parsed as well and handled properly.
+fn parse_unit(unit_file: &Path, base_unit_path: &Path) -> Result<UnitInfo> {
+    // Parse the main unit and all overrides
+    let mut unit_data = HashMap::new();
+
+    let base_unit_file = std::fs::File::open(base_unit_path)
+        .with_context(|| format!("Failed to open unit file {}", base_unit_path.display()))?;
+    parse_systemd_ini(&mut unit_data, base_unit_file).with_context(|| {
+        format!(
+            "Failed to parse systemd unit file {}",
+            base_unit_path.display()
+        )
+    })?;
+
+    for entry in
+        glob(&format!("{}.d/*.conf", base_unit_path.display())).context("Invalid glob pattern")?
+    {
+        let Ok(entry) = entry else {
+            continue;
+        };
+
+        let unit_file = std::fs::File::open(&entry)
+            .with_context(|| format!("Failed to open unit file {}", entry.display()))?;
+        parse_systemd_ini(&mut unit_data, unit_file)?;
+    }
+
+    // Handle drop-in template-unit instance overrides
+    if unit_file != base_unit_path {
+        for entry in
+            glob(&format!("{}.d/*.conf", unit_file.display())).context("Invalid glob pattern")?
+        {
+            let Ok(entry) = entry else {
+                continue;
+            };
+
+            let unit_file = std::fs::File::open(&entry)
+                .with_context(|| format!("Failed to open unit file {}", entry.display()))?;
+            parse_systemd_ini(&mut unit_data, unit_file)?;
+        }
+    }
+
+    Ok(unit_data)
+}
+
+// Checks whether a specified boolean in a systemd unit is true or false, with a default that is
+// applied when the value is not set.
+fn parse_systemd_bool(
+    unit_data: Option<&UnitInfo>,
+    section_name: &str,
+    bool_name: &str,
+    default: bool,
+) -> bool {
+    if let Some(Some(Some(Some(b)))) = unit_data.map(|data| {
+        data.get(section_name).map(|section| {
+            section.get(bool_name).map(|vals| {
+                vals.last()
+                    .map(|last| matches!(last.as_str(), "1" | "yes" | "true" | "on"))
+            })
+        })
+    }) {
+        b
+    } else {
+        default
+    }
+}
+
+#[derive(Debug, PartialEq)]
+enum UnitComparison {
+    Equal,
+    UnequalNeedsRestart,
+    UnequalNeedsReload,
+}
+
+// Compare the contents of two unit files and return whether the unit needs to be restarted or
+// reloaded. If the units differ, the service is restarted unless the only difference is
+// `X-Reload-Triggers` in the `Unit` section. If this is the only modification, the unit is
+// reloaded instead of restarted. If the only difference is `Options` in the `[Mount]` section, the
+// unit is reloaded rather than restarted.
+fn compare_units(current_unit: &UnitInfo, new_unit: &UnitInfo) -> UnitComparison {
+    let mut ret = UnitComparison::Equal;
+
+    let unit_section_ignores = HashMap::from(
+        [
+            "X-Reload-Triggers",
+            "Description",
+            "Documentation",
+            "OnFailure",
+            "OnSuccess",
+            "OnFailureJobMode",
+            "IgnoreOnIsolate",
+            "StopWhenUnneeded",
+            "RefuseManualStart",
+            "RefuseManualStop",
+            "AllowIsolate",
+            "CollectMode",
+            "SourcePath",
+        ]
+        .map(|name| (name, ())),
+    );
+
+    let mut section_cmp = new_unit.keys().fold(HashMap::new(), |mut acc, key| {
+        acc.insert(key.as_str(), ());
+        acc
+    });
+
+    // Iterate over the sections
+    for (section_name, section_val) in current_unit {
+        // Missing section in the new unit?
+        if !section_cmp.contains_key(section_name.as_str()) {
+            // If the [Unit] section was removed, make sure that only keys were in it that are
+            // ignored
+            if section_name == "Unit" {
+                for (ini_key, _ini_val) in section_val {
+                    if !unit_section_ignores.contains_key(ini_key.as_str()) {
+                        return UnitComparison::UnequalNeedsRestart;
+                    }
+                }
+                continue; // check the next section
+            } else {
+                return UnitComparison::UnequalNeedsRestart;
+            }
+        }
+
+        section_cmp.remove(section_name.as_str());
+
+        // Comparison hash for the section contents
+        let mut ini_cmp = new_unit
+            .get(section_name)
+            .map(|section_val| {
+                section_val.keys().fold(HashMap::new(), |mut acc, ini_key| {
+                    acc.insert(ini_key.as_str(), ());
+                    acc
+                })
+            })
+            .unwrap_or_default();
+
+        // Iterate over the keys of the section
+        for (ini_key, current_value) in section_val {
+            ini_cmp.remove(ini_key.as_str());
+            let Some(Some(new_value)) = new_unit
+                .get(section_name)
+                .map(|section| section.get(ini_key))
+            else {
+                // If the key is missing in the new unit, they are different unless the key that is
+                // now missing is one of the ignored keys
+                if section_name == "Unit" && unit_section_ignores.contains_key(ini_key.as_str()) {
+                    continue;
+                }
+                return UnitComparison::UnequalNeedsRestart;
+            };
+
+            // If the contents are different, the units are different
+            if current_value != new_value {
+                if section_name == "Unit" {
+                    if ini_key == "X-Reload-Triggers" {
+                        ret = UnitComparison::UnequalNeedsReload;
+                        continue;
+                    } else if unit_section_ignores.contains_key(ini_key.as_str()) {
+                        continue;
+                    }
+                }
+
+                // If this is a mount unit, check if it was only `Options`
+                if section_name == "Mount" && ini_key == "Options" {
+                    ret = UnitComparison::UnequalNeedsReload;
+                    continue;
+                }
+
+                return UnitComparison::UnequalNeedsRestart;
+            }
+        }
+
+        // A key was introduced that was missing in the previous unit
+        if !ini_cmp.is_empty() {
+            if section_name == "Unit" {
+                for (ini_key, _) in ini_cmp {
+                    if ini_key == "X-Reload-Triggers" {
+                        ret = UnitComparison::UnequalNeedsReload;
+                    } else if unit_section_ignores.contains_key(ini_key) {
+                        continue;
+                    } else {
+                        return UnitComparison::UnequalNeedsRestart;
+                    }
+                }
+            } else {
+                return UnitComparison::UnequalNeedsRestart;
+            }
+        }
+    }
+
+    // A section was introduced that was missing in the previous unit
+    if !section_cmp.is_empty() {
+        if section_cmp.keys().len() == 1 && section_cmp.contains_key("Unit") {
+            if let Some(new_unit_unit) = new_unit.get("Unit") {
+                for (ini_key, _) in new_unit_unit {
+                    if !unit_section_ignores.contains_key(ini_key.as_str()) {
+                        return UnitComparison::UnequalNeedsRestart;
+                    } else if ini_key == "X-Reload-Triggers" {
+                        ret = UnitComparison::UnequalNeedsReload;
+                    }
+                }
+            }
+        } else {
+            return UnitComparison::UnequalNeedsRestart;
+        }
+    }
+
+    ret
+}
+
+// Called when a unit exists in both the old systemd and the new system and the units differ. This
+// figures out of what units are to be stopped, restarted, reloaded, started, and skipped.
+fn handle_modified_unit(
+    toplevel: &Path,
+    unit: &str,
+    base_name: &str,
+    new_unit_file: &Path,
+    new_base_unit_file: &Path,
+    new_unit_info: Option<&UnitInfo>,
+    active_cur: &HashMap<String, UnitState>,
+    units_to_stop: &mut HashMap<String, ()>,
+    units_to_start: &mut HashMap<String, ()>,
+    units_to_reload: &mut HashMap<String, ()>,
+    units_to_restart: &mut HashMap<String, ()>,
+    units_to_skip: &mut HashMap<String, ()>,
+) -> Result<()> {
+    let use_restart_as_stop_and_start = new_unit_info.is_none();
+
+    if matches!(
+        unit,
+        "sysinit.target" | "basic.target" | "multi-user.target" | "graphical.target"
+    ) || unit.ends_with(".unit")
+        || unit.ends_with(".slice")
+    {
+        // Do nothing.  These cannot be restarted directly.
+
+        // Slices and Paths don't have to be restarted since properties (resource limits and
+        // inotify watches) seem to get applied on daemon-reload.
+    } else if unit.ends_with(".mount") {
+        // Just restart the unit. We wouldn't have gotten into this subroutine if only `Options`
+        // was changed, in which case the unit would be reloaded. The only exception is / and /nix
+        // because it's very unlikely we can safely unmount them so we reload them instead. This
+        // means that we may not get all changes into the running system but it's better than
+        // crashing it.
+        if unit == "-.mount" || unit == "nix.mount" {
+            units_to_reload.insert(unit.to_string(), ());
+            record_unit(RELOAD_LIST_FILE, unit);
+        } else {
+            units_to_restart.insert(unit.to_string(), ());
+            record_unit(RESTART_LIST_FILE, unit);
+        }
+    } else if unit.ends_with(".socket") {
+        // FIXME: do something?
+        // Attempt to fix this: https://github.com/NixOS/nixpkgs/pull/141192
+        // Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609
+        // More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430
+    } else {
+        let fallback = parse_unit(new_unit_file, new_base_unit_file)?;
+        let new_unit_info = if new_unit_info.is_some() {
+            new_unit_info
+        } else {
+            Some(&fallback)
+        };
+
+        if parse_systemd_bool(new_unit_info, "Service", "X-ReloadIfChanged", false)
+            && !units_to_restart.contains_key(unit)
+            && !(if use_restart_as_stop_and_start {
+                units_to_restart.contains_key(unit)
+            } else {
+                units_to_stop.contains_key(unit)
+            })
+        {
+            units_to_reload.insert(unit.to_string(), ());
+            record_unit(RELOAD_LIST_FILE, unit);
+        } else if !parse_systemd_bool(new_unit_info, "Service", "X-RestartIfChanged", true)
+            || parse_systemd_bool(new_unit_info, "Unit", "RefuseManualStop", false)
+            || parse_systemd_bool(new_unit_info, "Unit", "X-OnlyManualStart", false)
+        {
+            units_to_skip.insert(unit.to_string(), ());
+        } else {
+            // It doesn't make sense to stop and start non-services because they can't have
+            // ExecStop=
+            if !parse_systemd_bool(new_unit_info, "Service", "X-StopIfChanged", true)
+                || !unit.ends_with(".service")
+            {
+                // This unit should be restarted instead of stopped and started.
+                units_to_restart.insert(unit.to_string(), ());
+                record_unit(RESTART_LIST_FILE, unit);
+                // Remove from units to reload so we don't restart and reload
+                if units_to_reload.contains_key(unit) {
+                    units_to_reload.remove(unit);
+                    unrecord_unit(RELOAD_LIST_FILE, unit);
+                }
+            } else {
+                // If this unit is socket-activated, then stop the socket unit(s) as well, and
+                // restart the socket(s) instead of the service.
+                let mut socket_activated = false;
+                if unit.ends_with(".service") {
+                    let mut sockets = if let Some(Some(Some(sockets))) = new_unit_info.map(|info| {
+                        info.get("Service")
+                            .map(|service_section| service_section.get("Sockets"))
+                    }) {
+                        sockets
+                            .join(" ")
+                            .split_whitespace()
+                            .into_iter()
+                            .map(String::from)
+                            .collect()
+                    } else {
+                        Vec::new()
+                    };
+
+                    if sockets.is_empty() {
+                        sockets.push(format!("{}.socket", base_name));
+                    }
+
+                    for socket in &sockets {
+                        if active_cur.contains_key(socket) {
+                            // We can now be sure this is a socket-activated unit
+
+                            if use_restart_as_stop_and_start {
+                                units_to_restart.insert(socket.to_string(), ());
+                            } else {
+                                units_to_stop.insert(socket.to_string(), ());
+                            }
+
+                            // Only restart sockets that actually exist in new configuration:
+                            if toplevel.join("etc/systemd/system").join(socket).exists() {
+                                if use_restart_as_stop_and_start {
+                                    units_to_restart.insert(socket.to_string(), ());
+                                    record_unit(RESTART_LIST_FILE, socket);
+                                } else {
+                                    units_to_start.insert(socket.to_string(), ());
+                                    record_unit(START_LIST_FILE, socket);
+                                }
+
+                                socket_activated = true;
+                            }
+
+                            // Remove from units to reload so we don't restart and reload
+                            if units_to_reload.contains_key(unit) {
+                                units_to_reload.remove(unit);
+                                unrecord_unit(RELOAD_LIST_FILE, unit);
+                            }
+                        }
+                    }
+                }
+
+                // If the unit is not socket-activated, record that this unit needs to be started
+                // below. We write this to a file to ensure that the service gets restarted if
+                // we're interrupted.
+                if !socket_activated {
+                    if use_restart_as_stop_and_start {
+                        units_to_restart.insert(unit.to_string(), ());
+                        record_unit(RESTART_LIST_FILE, unit);
+                    } else {
+                        units_to_start.insert(unit.to_string(), ());
+                        record_unit(START_LIST_FILE, unit);
+                    }
+                }
+
+                if use_restart_as_stop_and_start {
+                    units_to_restart.insert(unit.to_string(), ());
+                } else {
+                    units_to_stop.insert(unit.to_string(), ());
+                }
+                // Remove from units to reload so we don't restart and reload
+                if units_to_reload.contains_key(unit) {
+                    units_to_reload.remove(unit);
+                    unrecord_unit(RELOAD_LIST_FILE, unit);
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
+
+// Writes a unit name into a given file to be more resilient against crashes of the script. Does
+// nothing when the action is dry-activate.
+fn record_unit(p: impl AsRef<Path>, unit: &str) {
+    if ACTION.get() != Some(&Action::DryActivate) {
+        if let Ok(mut f) = std::fs::File::options().append(true).create(true).open(p) {
+            _ = writeln!(&mut f, "{unit}");
+        }
+    }
+}
+
+// The opposite of record_unit, removes a unit name from a file
+fn unrecord_unit(p: impl AsRef<Path>, unit: &str) {
+    if ACTION.get() != Some(&Action::DryActivate) {
+        if let Ok(contents) = std::fs::read_to_string(&p) {
+            if let Ok(mut f) = std::fs::File::options()
+                .write(true)
+                .truncate(true)
+                .create(true)
+                .open(&p)
+            {
+                contents
+                    .lines()
+                    .into_iter()
+                    .filter(|line| line != &unit)
+                    .for_each(|line| _ = writeln!(&mut f, "{line}"))
+            }
+        }
+    }
+}
+
+fn map_from_list_file(p: impl AsRef<Path>) -> HashMap<String, ()> {
+    std::fs::read_to_string(p)
+        .unwrap_or_default()
+        .lines()
+        .filter(|line| !line.is_empty())
+        .into_iter()
+        .fold(HashMap::new(), |mut acc, line| {
+            acc.insert(line.to_string(), ());
+            acc
+        })
+}
+
+#[derive(Debug)]
+struct Filesystem {
+    device: String,
+    fs_type: String,
+    options: String,
+}
+
+#[derive(Debug)]
+#[allow(unused)]
+struct Swap(String);
+
+// Parse a fstab file, given its path. Returns a tuple of filesystems and swaps.
+//
+// Filesystems is a hash of mountpoint and { device, fsType, options } Swaps is a hash of device
+// and { options }
+fn parse_fstab(fstab: impl BufRead) -> (HashMap<String, Filesystem>, HashMap<String, Swap>) {
+    let mut filesystems = HashMap::new();
+    let mut swaps = HashMap::new();
+
+    for line in fstab.lines() {
+        let Ok(line) = line else {
+            break;
+        };
+
+        if line.contains('#') {
+            continue;
+        }
+
+        let mut split = line.split_whitespace();
+        let (Some(device), Some(mountpoint), Some(fs_type), options) = (
+            split.next(),
+            split.next(),
+            split.next(),
+            split.next().unwrap_or_default(),
+        ) else {
+            continue;
+        };
+
+        if fs_type == "swap" {
+            swaps.insert(device.to_string(), Swap(options.to_string()));
+        } else {
+            filesystems.insert(
+                mountpoint.to_string(),
+                Filesystem {
+                    device: device.to_string(),
+                    fs_type: fs_type.to_string(),
+                    options: options.to_string(),
+                },
+            );
+        }
+    }
+
+    (filesystems, swaps)
+}
+
+// Converts a path to the name of a systemd mount unit that would be responsible for mounting this
+// path.
+fn path_to_unit_name(bin_path: &Path, path: &str) -> String {
+    let Ok(output) = std::process::Command::new(bin_path.join("systemd-escape"))
+        .arg("--suffix=mount")
+        .arg("-p")
+        .arg(path)
+        .output()
+    else {
+        eprintln!("Unable to escape {}!", path);
+        die();
+    };
+
+    let Ok(unit) = String::from_utf8(output.stdout) else {
+        eprintln!("Unable to convert systemd-espape output to valid UTF-8");
+        die();
+    };
+
+    unit.trim().to_string()
+}
+
+// Returns a HashMap containing the same contents as the passed in `units`, minus the units in
+// `units_to_filter`.
+fn filter_units(
+    units_to_filter: &HashMap<String, ()>,
+    units: &HashMap<String, ()>,
+) -> HashMap<String, ()> {
+    let mut res = HashMap::new();
+
+    for (unit, _) in units {
+        if !units_to_filter.contains_key(unit) {
+            res.insert(unit.to_string(), ());
+        }
+    }
+
+    res
+}
+
+fn unit_is_active<'a>(conn: &LocalConnection, unit: &str) -> Result<bool> {
+    let unit_object_path = conn
+        .with_proxy(
+            "org.freedesktop.systemd1",
+            "/org/freedesktop/systemd1",
+            Duration::from_millis(5000),
+        )
+        .get_unit(unit)
+        .with_context(|| format!("Failed to get unit {unit}"))?;
+
+    let active_state: String = conn
+        .with_proxy(
+            "org.freedesktop.systemd1",
+            unit_object_path,
+            Duration::from_millis(5000),
+        )
+        .get("org.freedesktop.systemd1.Unit", "ActiveState")
+        .with_context(|| format!("Failed to get ExecMainStatus for {unit}"))?;
+
+    Ok(matches!(active_state.as_str(), "active" | "activating"))
+}
+
+static ACTION: OnceLock<Action> = OnceLock::new();
+
+#[derive(Debug)]
+enum Job {
+    Start,
+    Restart,
+    Reload,
+    Stop,
+}
+
+impl std::fmt::Display for Job {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Job::Start => "start",
+                Job::Restart => "restart",
+                Job::Reload => "reload",
+                Job::Stop => "stop",
+            }
+        )
+    }
+}
+
+fn new_dbus_proxies<'a>(
+    conn: &'a LocalConnection,
+) -> (
+    Proxy<'a, &'a LocalConnection>,
+    Proxy<'a, &'a LocalConnection>,
+) {
+    (
+        conn.with_proxy(
+            "org.freedesktop.systemd1",
+            "/org/freedesktop/systemd1",
+            Duration::from_millis(5000),
+        ),
+        conn.with_proxy(
+            "org.freedesktop.login1",
+            "/org/freedesktop/login1",
+            Duration::from_millis(5000),
+        ),
+    )
+}
+
+fn block_on_jobs(
+    conn: &LocalConnection,
+    submitted_jobs: &Rc<RefCell<HashMap<dbus::Path<'static>, Job>>>,
+) {
+    while !submitted_jobs.borrow().is_empty() {
+        _ = conn.process(Duration::from_millis(500));
+    }
+}
+
+fn remove_file_if_exists(p: impl AsRef<Path>) -> std::io::Result<()> {
+    match std::fs::remove_file(p) {
+        Err(err) if err.kind() != std::io::ErrorKind::NotFound => Err(err),
+        _ => Ok(()),
+    }
+}
+
+/// Performs switch-to-configuration functionality for a single non-root user
+fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
+    if Path::new(&parent_exe)
+        != Path::new("/proc/self/exe")
+            .canonicalize()
+            .context("Failed to get full path to current executable")?
+            .as_path()
+    {
+        eprintln!(
+            r#"This program is not meant to be called from outside of switch-to-configuration."#
+        );
+        die();
+    }
+
+    let dbus_conn = LocalConnection::new_session().context("Failed to open dbus connection")?;
+    let (systemd, _) = new_dbus_proxies(&dbus_conn);
+
+    let nixos_activation_done = Rc::new(RefCell::new(false));
+    let _nixos_activation_done = nixos_activation_done.clone();
+    let jobs_token = systemd
+        .match_signal(
+            move |signal: OrgFreedesktopSystemd1ManagerJobRemoved,
+                  _: &LocalConnection,
+                  _: &Message| {
+                if signal.unit.as_str() == "nixos-activation.service" {
+                    *_nixos_activation_done.borrow_mut() = true;
+                }
+
+                true
+            },
+        )
+        .context("Failed to add signal match for systemd removed jobs")?;
+
+    // The systemd user session seems to not send a Reloaded signal, so we don't have anything to
+    // wait on here.
+    _ = systemd.reexecute();
+
+    systemd
+        .restart_unit("nixos-activation.service", "replace")
+        .context("Failed to restart nixos-activation.service")?;
+
+    while !*nixos_activation_done.borrow() {
+        _ = dbus_conn
+            .process(Duration::from_secs(500))
+            .context("Failed to process dbus messages")?;
+    }
+
+    dbus_conn
+        .remove_match(jobs_token)
+        .context("Failed to remove jobs token")?;
+
+    Ok(())
+}
+
+/// Performs switch-to-configuration functionality for the entire system
+fn do_system_switch() -> anyhow::Result<()> {
+    let out = PathBuf::from(required_env("OUT")?);
+    let toplevel = PathBuf::from(required_env("TOPLEVEL")?);
+    let distro_id = required_env("DISTRO_ID")?;
+    let install_bootloader = required_env("INSTALL_BOOTLOADER")?;
+    let locale_archive = required_env("LOCALE_ARCHIVE")?;
+    let new_systemd = PathBuf::from(required_env("SYSTEMD")?);
+
+    let mut args = std::env::args();
+    let argv0 = args.next().ok_or(anyhow!("no argv[0]"))?;
+
+    let Some(Ok(action)) = args.next().map(|a| Action::from_str(&a)) else {
+        eprintln!(
+            r#"Usage: {} [switch|boot|test|dry-activate]
+switch:       make the configuration the boot default and activate now
+boot:         make the configuration the boot default
+test:         activate the configuration, but don't make it the boot default
+dry-activate: show what would be done if this configuration were activated
+"#,
+            argv0
+                .split(std::path::MAIN_SEPARATOR_STR)
+                .last()
+                .unwrap_or("switch-to-configuration")
+        );
+        std::process::exit(1);
+    };
+
+    let action = ACTION.get_or_init(|| action);
+
+    // The action that is to be performed (like switch, boot, test, dry-activate) Also exposed via
+    // environment variable from now on
+    std::env::set_var("NIXOS_ACTION", Into::<&'static str>::into(action));
+
+    // Expose the locale archive as an environment variable for systemctl and the activation script
+    if !locale_archive.is_empty() {
+        std::env::set_var("LOCALE_ARCHIVE", locale_archive);
+    }
+
+    let current_system_bin = std::path::PathBuf::from("/run/current-system/sw/bin")
+        .canonicalize()
+        .context("/run/current-system/sw/bin is missing")?;
+
+    let os_release = parse_os_release().context("Failed to parse os-release")?;
+
+    let distro_id_re = Regex::new(format!("^\"?{}\"?$", distro_id).as_str())
+        .context("Invalid regex for distro ID")?;
+
+    // This is a NixOS installation if it has /etc/NIXOS or a proper /etc/os-release.
+    if !Path::new("/etc/NIXOS").is_file()
+        && !os_release
+            .get("ID")
+            .map(|id| distro_id_re.is_match(id))
+            .unwrap_or_default()
+    {
+        eprintln!("This is not a NixOS installation!");
+        die();
+    }
+
+    std::fs::create_dir_all("/run/nixos").context("Failed to create /run/nixos directory")?;
+    let perms = std::fs::Permissions::from_mode(0o755);
+    std::fs::set_permissions("/run/nixos", perms)
+        .context("Failed to set permissions on /run/nixos directory")?;
+
+    let Ok(lock) = std::fs::OpenOptions::new()
+        .append(true)
+        .create(true)
+        .open("/run/nixos/switch-to-configuration.lock")
+    else {
+        eprintln!("Could not open lock");
+        die();
+    };
+
+    let Ok(_lock) = Flock::lock(lock, FlockArg::LockExclusive) else {
+        eprintln!("Could not acquire lock");
+        die();
+    };
+
+    if syslog::init(Facility::LOG_USER, LevelFilter::Debug, Some("nixos")).is_err() {
+        bail!("Failed to initialize logger");
+    }
+
+    // Install or update the bootloader.
+    if matches!(action, Action::Switch | Action::Boot) {
+        do_install_bootloader(&install_bootloader, &toplevel)?;
+    }
+
+    // Just in case the new configuration hangs the system, do a sync now.
+    if std::env::var("NIXOS_NO_SYNC")
+        .as_deref()
+        .unwrap_or_default()
+        != "1"
+    {
+        let fd = nix::fcntl::open("/nix/store", OFlag::O_NOCTTY, Mode::S_IROTH)
+            .context("Failed to open /nix/store")?;
+        nix::unistd::syncfs(fd).context("Failed to sync /nix/store")?;
+    }
+
+    if *action == Action::Boot {
+        std::process::exit(0);
+    }
+
+    let current_init_interface_version =
+        std::fs::read_to_string("/run/current-system/init-interface-version").unwrap_or_default();
+
+    let new_init_interface_version =
+        std::fs::read_to_string(toplevel.join("init-interface-version"))
+            .context("File init-interface-version should exist")?;
+
+    // Check if we can activate the new configuration.
+    if current_init_interface_version != new_init_interface_version {
+        eprintln!(
+            r#"Warning: the new NixOS configuration has an ‘init’ that is
+incompatible with the current configuration.  The new configuration
+won't take effect until you reboot the system.
+"#
+        );
+        std::process::exit(100);
+    }
+
+    // Ignore SIGHUP so that we're not killed if we're running on (say) virtual console 1 and we
+    // restart the "tty1" unit.
+    let handler = SigHandler::Handler(handle_sigpipe);
+    unsafe { signal::signal(Signal::SIGPIPE, handler) }.context("Failed to set SIGPIPE handler")?;
+
+    let mut units_to_stop = HashMap::new();
+    let mut units_to_skip = HashMap::new();
+    let mut units_to_filter = HashMap::new(); // units not shown
+
+    let mut units_to_start = map_from_list_file(START_LIST_FILE);
+    let mut units_to_restart = map_from_list_file(RESTART_LIST_FILE);
+    let mut units_to_reload = map_from_list_file(RELOAD_LIST_FILE);
+
+    let dbus_conn = LocalConnection::new_system().context("Failed to open dbus connection")?;
+    let (systemd, logind) = new_dbus_proxies(&dbus_conn);
+
+    let submitted_jobs = Rc::new(RefCell::new(HashMap::new()));
+    let finished_jobs = Rc::new(RefCell::new(HashMap::new()));
+
+    let systemd_reload_status = Rc::new(RefCell::new(false));
+
+    systemd
+        .subscribe()
+        .context("Failed to subscribe to systemd dbus messages")?;
+
+    // Wait for the system to have finished booting.
+    loop {
+        let system_state: String = systemd
+            .get("org.freedesktop.systemd1.Manager", "SystemState")
+            .context("Failed to get system state")?;
+
+        match system_state.as_str() {
+            "running" | "degraded" | "maintenance" => break,
+            _ => {
+                _ = dbus_conn
+                    .process(Duration::from_millis(500))
+                    .context("Failed to process dbus messages")?
+            }
+        }
+    }
+
+    let _systemd_reload_status = systemd_reload_status.clone();
+    let reloading_token = systemd
+        .match_signal(
+            move |signal: OrgFreedesktopSystemd1ManagerReloading,
+                  _: &LocalConnection,
+                  _msg: &Message| {
+                *_systemd_reload_status.borrow_mut() = signal.active;
+
+                true
+            },
+        )
+        .context("Failed to add systemd Reloading match")?;
+
+    let _submitted_jobs = submitted_jobs.clone();
+    let _finished_jobs = finished_jobs.clone();
+    let job_removed_token = systemd
+        .match_signal(
+            move |signal: OrgFreedesktopSystemd1ManagerJobRemoved,
+                  _: &LocalConnection,
+                  _msg: &Message| {
+                if let Some(old) = _submitted_jobs.borrow_mut().remove(&signal.job) {
+                    let mut finished_jobs = _finished_jobs.borrow_mut();
+                    finished_jobs.insert(signal.job, (signal.unit, old, signal.result));
+                }
+
+                true
+            },
+        )
+        .context("Failed to add systemd JobRemoved match")?;
+
+    let current_active_units = get_active_units(&systemd)?;
+
+    let template_unit_re = Regex::new(r"^(.*)@[^\.]*\.(.*)$")
+        .context("Invalid regex for matching systemd template units")?;
+    let unit_name_re = Regex::new(r"^(.*)\.[[:lower:]]*$")
+        .context("Invalid regex for matching systemd unit names")?;
+
+    for (unit, unit_state) in &current_active_units {
+        let current_unit_file = Path::new("/etc/systemd/system").join(&unit);
+        let new_unit_file = toplevel.join("etc/systemd/system").join(&unit);
+
+        let mut base_unit = unit.clone();
+        let mut current_base_unit_file = current_unit_file.clone();
+        let mut new_base_unit_file = new_unit_file.clone();
+
+        // Detect template instances
+        if let Some((Some(template_name), Some(template_instance))) =
+            template_unit_re.captures(&unit).map(|captures| {
+                (
+                    captures.get(1).map(|c| c.as_str()),
+                    captures.get(2).map(|c| c.as_str()),
+                )
+            })
+        {
+            if !current_unit_file.exists() && !new_unit_file.exists() {
+                base_unit = format!("{}@.{}", template_name, template_instance);
+                current_base_unit_file = Path::new("/etc/systemd/system").join(&base_unit);
+                new_base_unit_file = toplevel.join("etc/systemd/system").join(&base_unit);
+            }
+        }
+
+        let mut base_name = base_unit.as_str();
+        if let Some(Some(new_base_name)) = unit_name_re
+            .captures(&base_unit)
+            .map(|capture| capture.get(1).map(|first| first.as_str()))
+        {
+            base_name = new_base_name;
+        }
+
+        if current_base_unit_file.exists()
+            && (unit_state.state == "active" || unit_state.state == "activating")
+        {
+            if new_base_unit_file
+                .canonicalize()
+                .map(|full_path| full_path == Path::new("/dev/null"))
+                .unwrap_or(true)
+            {
+                let current_unit_info = parse_unit(&current_unit_file, &current_base_unit_file)?;
+                if parse_systemd_bool(Some(&current_unit_info), "Unit", "X-StopOnRemoval", true) {
+                    _ = units_to_stop.insert(unit.to_string(), ());
+                }
+            } else if unit.ends_with(".target") {
+                let new_unit_info = parse_unit(&new_unit_file, &new_base_unit_file)?;
+
+                // Cause all active target units to be restarted below. This should start most
+                // changed units we stop here as well as any new dependencies (including new mounts
+                // and swap devices).  FIXME: the suspend target is sometimes active after the
+                // system has resumed, which probably should not be the case.  Just ignore it.
+                if !matches!(
+                    unit.as_str(),
+                    "suspend.target" | "hibernate.target" | "hybrid-sleep.target"
+                ) {
+                    if !(parse_systemd_bool(
+                        Some(&new_unit_info),
+                        "Unit",
+                        "RefuseManualStart",
+                        false,
+                    ) || parse_systemd_bool(
+                        Some(&new_unit_info),
+                        "Unit",
+                        "X-OnlyManualStart",
+                        false,
+                    )) {
+                        units_to_start.insert(unit.to_string(), ());
+                        record_unit(START_LIST_FILE, unit);
+                        // Don't spam the user with target units that always get started.
+                        if std::env::var("STC_DISPLAY_ALL_UNITS").as_deref() != Ok("1") {
+                            units_to_filter.insert(unit.to_string(), ());
+                        }
+                    }
+                }
+
+                // Stop targets that have X-StopOnReconfiguration set. This is necessary to respect
+                // dependency orderings involving targets: if unit X starts after target Y and
+                // target Y starts after unit Z, then if X and Z have both changed, then X should
+                // be restarted after Z.  However, if target Y is in the "active" state, X and Z
+                // will be restarted at the same time because X's dependency on Y is already
+                // satisfied.  Thus, we need to stop Y first. Stopping a target generally has no
+                // effect on other units (unless there is a PartOf dependency), so this is just a
+                // bookkeeping thing to get systemd to do the right thing.
+                if parse_systemd_bool(
+                    Some(&new_unit_info),
+                    "Unit",
+                    "X-StopOnReconfiguration",
+                    false,
+                ) {
+                    units_to_stop.insert(unit.to_string(), ());
+                }
+            } else {
+                let current_unit_info = parse_unit(&current_unit_file, &current_base_unit_file)?;
+                let new_unit_info = parse_unit(&new_unit_file, &new_base_unit_file)?;
+                match compare_units(&current_unit_info, &new_unit_info) {
+                    UnitComparison::UnequalNeedsRestart => {
+                        handle_modified_unit(
+                            &toplevel,
+                            &unit,
+                            base_name,
+                            &new_unit_file,
+                            &new_base_unit_file,
+                            Some(&new_unit_info),
+                            &current_active_units,
+                            &mut units_to_stop,
+                            &mut units_to_start,
+                            &mut units_to_reload,
+                            &mut units_to_restart,
+                            &mut units_to_skip,
+                        )?;
+                    }
+                    UnitComparison::UnequalNeedsReload if !units_to_restart.contains_key(unit) => {
+                        units_to_reload.insert(unit.clone(), ());
+                        record_unit(RELOAD_LIST_FILE, &unit);
+                    }
+                    _ => {}
+                }
+            }
+        }
+    }
+
+    // Compare the previous and new fstab to figure out which filesystems need a remount or need to
+    // be unmounted. New filesystems are mounted automatically by starting local-fs.target.
+    // FIXME: might be nicer if we generated units for all mounts; then we could unify this with
+    // the unit checking code above.
+    let (current_filesystems, current_swaps) = std::fs::read_to_string("/etc/fstab")
+        .map(|fstab| parse_fstab(std::io::Cursor::new(fstab)))
+        .unwrap_or_default();
+    let (new_filesystems, new_swaps) = std::fs::read_to_string(toplevel.join("etc/fstab"))
+        .map(|fstab| parse_fstab(std::io::Cursor::new(fstab)))
+        .unwrap_or_default();
+
+    for (mountpoint, current_filesystem) in current_filesystems {
+        // Use current version of systemctl binary before daemon is reexeced.
+        let unit = path_to_unit_name(&current_system_bin, &mountpoint);
+        if let Some(new_filesystem) = new_filesystems.get(&mountpoint) {
+            if current_filesystem.fs_type != new_filesystem.fs_type
+                || current_filesystem.device != new_filesystem.device
+            {
+                if matches!(mountpoint.as_str(), "/" | "/nix") {
+                    if current_filesystem.options != new_filesystem.options {
+                        // Mount options changes, so remount it.
+                        units_to_reload.insert(unit.to_string(), ());
+                        record_unit(RELOAD_LIST_FILE, &unit)
+                    } else {
+                        // Don't unmount / or /nix if the device changed
+                        units_to_skip.insert(unit, ());
+                    }
+                } else {
+                    // Filesystem type or device changed, so unmount and mount it.
+                    units_to_restart.insert(unit.to_string(), ());
+                    record_unit(RESTART_LIST_FILE, &unit);
+                }
+            } else if current_filesystem.options != new_filesystem.options {
+                // Mount options changes, so remount it.
+                units_to_reload.insert(unit.to_string(), ());
+                record_unit(RELOAD_LIST_FILE, &unit)
+            }
+        } else {
+            // Filesystem entry disappeared, so unmount it.
+            units_to_stop.insert(unit, ());
+        }
+    }
+
+    // Also handles swap devices.
+    for (device, _) in current_swaps {
+        if new_swaps.get(&device).is_none() {
+            // Swap entry disappeared, so turn it off.  Can't use "systemctl stop" here because
+            // systemd has lots of alias units that prevent a stop from actually calling "swapoff".
+            if *action == Action::DryActivate {
+                eprintln!("would stop swap device: {}", &device);
+            } else {
+                eprintln!("stopping swap device: {}", &device);
+                let c_device = std::ffi::CString::new(device.clone())
+                    .context("failed to convert device to cstring")?;
+                if unsafe { nix::libc::swapoff(c_device.as_ptr()) } != 0 {
+                    let err = std::io::Error::last_os_error();
+                    eprintln!("Failed to stop swapping to {device}: {err}");
+                }
+            }
+        }
+        // FIXME: update swap options (i.e. its priority).
+    }
+
+    // Should we have systemd re-exec itself?
+    let current_pid1_path = Path::new("/proc/1/exe")
+        .canonicalize()
+        .unwrap_or_else(|_| PathBuf::from("/unknown"));
+    let current_systemd_system_config = Path::new("/etc/systemd/system.conf")
+        .canonicalize()
+        .unwrap_or_else(|_| PathBuf::from("/unknown"));
+    let Ok(new_pid1_path) = new_systemd.join("lib/systemd/systemd").canonicalize() else {
+        die();
+    };
+    let new_systemd_system_config = toplevel
+        .join("etc/systemd/system.conf")
+        .canonicalize()
+        .unwrap_or_else(|_| PathBuf::from("/unknown"));
+
+    let restart_systemd = current_pid1_path != new_pid1_path
+        || current_systemd_system_config != new_systemd_system_config;
+
+    let units_to_stop_filtered = filter_units(&units_to_filter, &units_to_stop);
+
+    // Show dry-run actions.
+    if *action == Action::DryActivate {
+        if !units_to_stop_filtered.is_empty() {
+            let mut units = units_to_stop_filtered
+                .keys()
+                .into_iter()
+                .map(String::as_str)
+                .collect::<Vec<&str>>();
+            units.sort_by_key(|name| name.to_lowercase());
+            eprintln!("would stop the following units: {}", units.join(", "));
+        }
+
+        if !units_to_skip.is_empty() {
+            let mut units = units_to_skip
+                .keys()
+                .into_iter()
+                .map(String::as_str)
+                .collect::<Vec<&str>>();
+            units.sort_by_key(|name| name.to_lowercase());
+            eprintln!(
+                "would NOT stop the following changed units: {}",
+                units.join(", ")
+            );
+        }
+
+        eprintln!("would activate the configuration...");
+        _ = std::process::Command::new(out.join("dry-activate"))
+            .arg(&out)
+            .spawn()
+            .map(|mut child| child.wait());
+
+        // Handle the activation script requesting the restart or reload of a unit.
+        for unit in std::fs::read_to_string(DRY_RESTART_BY_ACTIVATION_LIST_FILE)
+            .unwrap_or_default()
+            .lines()
+        {
+            let current_unit_file = Path::new("/etc/systemd/system").join(unit);
+            let new_unit_file = toplevel.join("etc/systemd/system").join(unit);
+            let mut base_unit = unit.to_string();
+            let mut new_base_unit_file = new_unit_file.clone();
+
+            // Detect template instances.
+            if let Some((Some(template_name), Some(template_instance))) =
+                template_unit_re.captures(&unit).map(|captures| {
+                    (
+                        captures.get(1).map(|c| c.as_str()),
+                        captures.get(2).map(|c| c.as_str()),
+                    )
+                })
+            {
+                if !current_unit_file.exists() && !new_unit_file.exists() {
+                    base_unit = format!("{}@.{}", template_name, template_instance);
+                    new_base_unit_file = toplevel.join("etc/systemd/system").join(&base_unit);
+                }
+            }
+
+            let mut base_name = base_unit.as_str();
+            if let Some(Some(new_base_name)) = unit_name_re
+                .captures(&base_unit)
+                .map(|capture| capture.get(1).map(|first| first.as_str()))
+            {
+                base_name = new_base_name;
+            }
+
+            // Start units if they were not active previously
+            if !current_active_units.contains_key(unit) {
+                units_to_start.insert(unit.to_string(), ());
+                continue;
+            }
+
+            handle_modified_unit(
+                &toplevel,
+                unit,
+                base_name,
+                &new_unit_file,
+                &new_base_unit_file,
+                None,
+                &current_active_units,
+                &mut units_to_stop,
+                &mut units_to_start,
+                &mut units_to_reload,
+                &mut units_to_restart,
+                &mut units_to_skip,
+            )?;
+        }
+
+        remove_file_if_exists(DRY_RESTART_BY_ACTIVATION_LIST_FILE)
+            .with_context(|| format!("Failed to remove {}", DRY_RESTART_BY_ACTIVATION_LIST_FILE))?;
+
+        for unit in std::fs::read_to_string(DRY_RELOAD_BY_ACTIVATION_LIST_FILE)
+            .unwrap_or_default()
+            .lines()
+        {
+            if current_active_units.contains_key(unit)
+                && !units_to_restart.contains_key(unit)
+                && !units_to_stop.contains_key(unit)
+            {
+                units_to_reload.insert(unit.to_string(), ());
+                record_unit(RELOAD_LIST_FILE, unit);
+            }
+        }
+
+        remove_file_if_exists(DRY_RELOAD_BY_ACTIVATION_LIST_FILE)
+            .with_context(|| format!("Failed to remove {}", DRY_RELOAD_BY_ACTIVATION_LIST_FILE))?;
+
+        if restart_systemd {
+            eprintln!("would restart systemd");
+        }
+
+        if !units_to_reload.is_empty() {
+            let mut units = units_to_reload
+                .keys()
+                .into_iter()
+                .map(String::as_str)
+                .collect::<Vec<&str>>();
+            units.sort_by_key(|name| name.to_lowercase());
+            eprintln!("would reload the following units: {}", units.join(", "));
+        }
+
+        if !units_to_restart.is_empty() {
+            let mut units = units_to_restart
+                .keys()
+                .into_iter()
+                .map(String::as_str)
+                .collect::<Vec<&str>>();
+            units.sort_by_key(|name| name.to_lowercase());
+            eprintln!("would restart the following units: {}", units.join(", "));
+        }
+
+        let units_to_start_filtered = filter_units(&units_to_filter, &units_to_start);
+        if !units_to_start_filtered.is_empty() {
+            let mut units = units_to_start_filtered
+                .keys()
+                .into_iter()
+                .map(String::as_str)
+                .collect::<Vec<&str>>();
+            units.sort_by_key(|name| name.to_lowercase());
+            eprintln!("would start the following units: {}", units.join(", "));
+        }
+
+        std::process::exit(0);
+    }
+
+    log::info!("switching to system configuration {}", toplevel.display());
+
+    if !units_to_stop.is_empty() {
+        if !units_to_stop_filtered.is_empty() {
+            let mut units = units_to_stop_filtered
+                .keys()
+                .into_iter()
+                .map(String::as_str)
+                .collect::<Vec<&str>>();
+            units.sort_by_key(|name| name.to_lowercase());
+            eprintln!("stopping the following units: {}", units.join(", "));
+        }
+
+        for unit in units_to_stop.keys() {
+            match systemd.stop_unit(unit, "replace") {
+                Ok(job_path) => {
+                    let mut j = submitted_jobs.borrow_mut();
+                    j.insert(job_path.to_owned(), Job::Stop);
+                }
+                Err(_) => {}
+            };
+        }
+
+        block_on_jobs(&dbus_conn, &submitted_jobs);
+    }
+
+    if !units_to_skip.is_empty() {
+        let mut units = units_to_skip
+            .keys()
+            .into_iter()
+            .map(String::as_str)
+            .collect::<Vec<&str>>();
+        units.sort_by_key(|name| name.to_lowercase());
+        eprintln!(
+            "NOT restarting the following changed units: {}",
+            units.join(", "),
+        );
+    }
+
+    // Wait for all stop jobs to finish
+    block_on_jobs(&dbus_conn, &submitted_jobs);
+
+    let mut exit_code = 0;
+
+    // Activate the new configuration (i.e., update /etc, make accounts, and so on).
+    eprintln!("activating the configuration...");
+    match std::process::Command::new(out.join("activate"))
+        .arg(&out)
+        .spawn()
+        .map(|mut child| child.wait())
+    {
+        Ok(Ok(status)) if status.success() => {}
+        Err(_) => {
+            // allow toplevel to not have an activation script
+        }
+        _ => {
+            eprintln!("Failed to run activate script");
+            exit_code = 2;
+        }
+    }
+
+    // Handle the activation script requesting the restart or reload of a unit.
+    for unit in std::fs::read_to_string(RESTART_BY_ACTIVATION_LIST_FILE)
+        .unwrap_or_default()
+        .lines()
+    {
+        let new_unit_file = toplevel.join("etc/systemd/system").join(unit);
+        let mut base_unit = unit.to_string();
+        let mut new_base_unit_file = new_unit_file.clone();
+
+        // Detect template instances.
+        if let Some((Some(template_name), Some(template_instance))) =
+            template_unit_re.captures(&unit).map(|captures| {
+                (
+                    captures.get(1).map(|c| c.as_str()),
+                    captures.get(2).map(|c| c.as_str()),
+                )
+            })
+        {
+            if !new_unit_file.exists() {
+                base_unit = format!("{}@.{}", template_name, template_instance);
+                new_base_unit_file = toplevel.join("etc/systemd/system").join(&base_unit);
+            }
+        }
+
+        let mut base_name = base_unit.as_str();
+        if let Some(Some(new_base_name)) = unit_name_re
+            .captures(&base_unit)
+            .map(|capture| capture.get(1).map(|first| first.as_str()))
+        {
+            base_name = new_base_name;
+        }
+
+        // Start units if they were not active previously
+        if !current_active_units.contains_key(unit) {
+            units_to_start.insert(unit.to_string(), ());
+            record_unit(START_LIST_FILE, unit);
+            continue;
+        }
+
+        handle_modified_unit(
+            &toplevel,
+            unit,
+            base_name,
+            &new_unit_file,
+            &new_base_unit_file,
+            None,
+            &current_active_units,
+            &mut units_to_stop,
+            &mut units_to_start,
+            &mut units_to_reload,
+            &mut units_to_restart,
+            &mut units_to_skip,
+        )?;
+    }
+
+    // We can remove the file now because it has been propagated to the other restart/reload files
+    remove_file_if_exists(RESTART_BY_ACTIVATION_LIST_FILE)
+        .with_context(|| format!("Failed to remove {}", RESTART_BY_ACTIVATION_LIST_FILE))?;
+
+    for unit in std::fs::read_to_string(RELOAD_BY_ACTIVATION_LIST_FILE)
+        .unwrap_or_default()
+        .lines()
+    {
+        if current_active_units.contains_key(unit)
+            && !units_to_restart.contains_key(unit)
+            && !units_to_stop.contains_key(unit)
+        {
+            units_to_reload.insert(unit.to_string(), ());
+            record_unit(RELOAD_LIST_FILE, unit);
+        }
+    }
+
+    // We can remove the file now because it has been propagated to the other reload file
+    remove_file_if_exists(RELOAD_BY_ACTIVATION_LIST_FILE)
+        .with_context(|| format!("Failed to remove {}", RELOAD_BY_ACTIVATION_LIST_FILE))?;
+
+    // Restart systemd if necessary. Note that this is done using the current version of systemd,
+    // just in case the new one has trouble communicating with the running pid 1.
+    if restart_systemd {
+        eprintln!("restarting systemd...");
+        _ = systemd.reexecute(); // we don't get a dbus reply here
+
+        while !*systemd_reload_status.borrow() {
+            _ = dbus_conn
+                .process(Duration::from_millis(500))
+                .context("Failed to process dbus messages")?;
+        }
+    }
+
+    // Forget about previously failed services.
+    systemd
+        .reset_failed()
+        .context("Failed to reset failed units")?;
+
+    // Make systemd reload its units.
+    _ = systemd.reload(); // we don't get a dbus reply here
+    while !*systemd_reload_status.borrow() {
+        _ = dbus_conn
+            .process(Duration::from_millis(500))
+            .context("Failed to process dbus messages")?;
+    }
+
+    dbus_conn
+        .remove_match(reloading_token)
+        .context("Failed to cleanup systemd Reloading match")?;
+
+    // Reload user units
+    match logind.list_users() {
+        Err(err) => {
+            eprintln!("Unable to list users with logind: {err}");
+            die();
+        }
+        Ok(users) => {
+            for (uid, name, _) in users {
+                eprintln!("reloading user units for {}...", name);
+                let myself = Path::new("/proc/self/exe")
+                    .canonicalize()
+                    .context("Failed to get full path to /proc/self/exe")?;
+
+                std::process::Command::new(&myself)
+                    .uid(uid)
+                    .env("XDG_RUNTIME_DIR", format!("/run/user/{}", uid))
+                    .env("__NIXOS_SWITCH_TO_CONFIGURATION_PARENT_EXE", &myself)
+                    .spawn()
+                    .map(|mut child| _ = child.wait())
+                    .with_context(|| format!("Failed to run user activation for {name}"))?;
+            }
+        }
+    }
+
+    // Restart sysinit-reactivation.target. This target only exists to restart services ordered
+    // before sysinit.target. We cannot use X-StopOnReconfiguration to restart sysinit.target
+    // because then ALL services of the system would be restarted since all normal services have a
+    // default dependency on sysinit.target. sysinit-reactivation.target ensures that services
+    // ordered BEFORE sysinit.target get re-started in the correct order. Ordering between these
+    // services is respected.
+    eprintln!("restarting {SYSINIT_REACTIVATION_TARGET}");
+    match systemd.restart_unit(SYSINIT_REACTIVATION_TARGET, "replace") {
+        Ok(job_path) => {
+            let mut jobs = submitted_jobs.borrow_mut();
+            jobs.insert(job_path, Job::Restart);
+        }
+        Err(err) => {
+            eprintln!("Failed to restart {SYSINIT_REACTIVATION_TARGET}: {err}");
+            exit_code = 4;
+        }
+    }
+
+    // Wait for the restart job of sysinit-reactivation.service to finish
+    block_on_jobs(&dbus_conn, &submitted_jobs);
+
+    // Before reloading we need to ensure that the units are still active. They may have been
+    // deactivated because one of their requirements got stopped. If they are inactive but should
+    // have been reloaded, the user probably expects them to be started.
+    if !units_to_reload.is_empty() {
+        for (unit, _) in units_to_reload.clone() {
+            if !unit_is_active(&dbus_conn, &unit)? {
+                // Figure out if we need to start the unit
+                let unit_info = parse_unit(
+                    toplevel.join("etc/systemd/system").join(&unit).as_path(),
+                    toplevel.join("etc/systemd/system").join(&unit).as_path(),
+                )?;
+                if !parse_systemd_bool(Some(&unit_info), "Unit", "RefuseManualStart", false)
+                    || parse_systemd_bool(Some(&unit_info), "Unit", "X-OnlyManualStart", false)
+                {
+                    units_to_start.insert(unit.clone(), ());
+                    record_unit(START_LIST_FILE, &unit);
+                }
+                // Don't reload the unit, reloading would fail
+                units_to_reload.remove(&unit);
+                unrecord_unit(RELOAD_LIST_FILE, &unit);
+            }
+        }
+    }
+
+    // Reload units that need it. This includes remounting changed mount units.
+    if !units_to_reload.is_empty() {
+        let mut units = units_to_reload
+            .keys()
+            .into_iter()
+            .map(String::as_str)
+            .collect::<Vec<&str>>();
+        units.sort_by_key(|name| name.to_lowercase());
+        eprintln!("reloading the following units: {}", units.join(", "));
+
+        for unit in units {
+            match systemd.reload_unit(unit, "replace") {
+                Ok(job_path) => {
+                    submitted_jobs
+                        .borrow_mut()
+                        .insert(job_path.clone(), Job::Reload);
+                }
+                Err(err) => {
+                    eprintln!("Failed to reload {unit}: {err}");
+                    exit_code = 4;
+                }
+            }
+        }
+
+        block_on_jobs(&dbus_conn, &submitted_jobs);
+
+        remove_file_if_exists(RELOAD_LIST_FILE)
+            .with_context(|| format!("Failed to remove {}", RELOAD_LIST_FILE))?;
+    }
+
+    // Restart changed services (those that have to be restarted rather than stopped and started).
+    if !units_to_restart.is_empty() {
+        let mut units = units_to_restart
+            .keys()
+            .into_iter()
+            .map(String::as_str)
+            .collect::<Vec<&str>>();
+        units.sort_by_key(|name| name.to_lowercase());
+        eprintln!("restarting the following units: {}", units.join(", "));
+
+        for unit in units {
+            match systemd.restart_unit(unit, "replace") {
+                Ok(job_path) => {
+                    let mut jobs = submitted_jobs.borrow_mut();
+                    jobs.insert(job_path, Job::Restart);
+                }
+                Err(err) => {
+                    eprintln!("Failed to restart {unit}: {err}");
+                    exit_code = 4;
+                }
+            }
+        }
+
+        block_on_jobs(&dbus_conn, &submitted_jobs);
+
+        remove_file_if_exists(RESTART_LIST_FILE)
+            .with_context(|| format!("Failed to remove {}", RESTART_LIST_FILE))?;
+    }
+
+    // Start all active targets, as well as changed units we stopped above. The latter is necessary
+    // because some may not be dependencies of the targets (i.e., they were manually started).
+    // FIXME: detect units that are symlinks to other units.  We shouldn't start both at the same
+    // time because we'll get a "Failed to add path to set" error from systemd.
+    let units_to_start_filtered = filter_units(&units_to_filter, &units_to_start);
+    if !units_to_start_filtered.is_empty() {
+        let mut units = units_to_start_filtered
+            .keys()
+            .into_iter()
+            .map(String::as_str)
+            .collect::<Vec<&str>>();
+        units.sort_by_key(|name| name.to_lowercase());
+        eprintln!("starting the following units: {}", units.join(", "));
+    }
+
+    for unit in units_to_start.keys() {
+        match systemd.start_unit(unit, "replace") {
+            Ok(job_path) => {
+                let mut jobs = submitted_jobs.borrow_mut();
+                jobs.insert(job_path, Job::Start);
+            }
+            Err(err) => {
+                eprintln!("Failed to start {unit}: {err}");
+                exit_code = 4;
+            }
+        }
+    }
+
+    block_on_jobs(&dbus_conn, &submitted_jobs);
+
+    remove_file_if_exists(START_LIST_FILE)
+        .with_context(|| format!("Failed to remove {}", START_LIST_FILE))?;
+
+    for (unit, job, result) in finished_jobs.borrow().values() {
+        match result.as_str() {
+            "timeout" | "failed" | "dependency" => {
+                eprintln!("Failed to {} {}", job, unit);
+                exit_code = 4;
+            }
+            _ => {}
+        }
+    }
+
+    dbus_conn
+        .remove_match(job_removed_token)
+        .context("Failed to cleanup systemd job match")?;
+
+    // Print failed and new units.
+    let mut failed_units = Vec::new();
+    let mut new_units = Vec::new();
+
+    // NOTE: We want switch-to-configuration to be able to report to the user any units that failed
+    // to start or units that systemd had to restart due to having previously failed. This is
+    // inherently a race condition between how long our program takes to run and how long the unit
+    // in question takes to potentially fail. The amount of time we wait for new messages on the
+    // bus to settle is purely tuned so that this program is compatible with the Perl
+    // implementation.
+    //
+    // Wait for events from systemd to settle. process() will return true if we have received any
+    // messages on the bus.
+    while dbus_conn
+        .process(Duration::from_millis(250))
+        .unwrap_or_default()
+    {}
+
+    let new_active_units = get_active_units(&systemd)?;
+
+    for (unit, unit_state) in new_active_units {
+        if &unit_state.state == "failed" {
+            failed_units.push(unit);
+            continue;
+        }
+
+        if unit_state.substate == "auto-restart" && unit.ends_with(".service") {
+            // A unit in auto-restart substate is a failure *if* it previously failed to start
+            let unit_object_path = systemd
+                .get_unit(&unit)
+                .context("Failed to get unit info for {unit}")?;
+            let exec_main_status: i32 = dbus_conn
+                .with_proxy(
+                    "org.freedesktop.systemd1",
+                    unit_object_path,
+                    Duration::from_millis(5000),
+                )
+                .get("org.freedesktop.systemd1.Service", "ExecMainStatus")
+                .context("Failed to get ExecMainStatus for {unit}")?;
+
+            if exec_main_status != 0 {
+                failed_units.push(unit);
+                continue;
+            }
+        }
+
+        // Ignore scopes since they are not managed by this script but rather created and managed
+        // by third-party services via the systemd dbus API. This only lists units that are not
+        // failed (including ones that are in auto-restart but have not failed previously)
+        if unit_state.state != "failed"
+            && !current_active_units.contains_key(&unit)
+            && !unit.ends_with(".scope")
+        {
+            new_units.push(unit);
+        }
+    }
+
+    if !new_units.is_empty() {
+        new_units.sort_by_key(|name| name.to_lowercase());
+        eprintln!(
+            "the following new units were started: {}",
+            new_units.join(", ")
+        );
+    }
+
+    if !failed_units.is_empty() {
+        failed_units.sort_by_key(|name| name.to_lowercase());
+        eprintln!(
+            "warning: the following units failed: {}",
+            failed_units.join(", ")
+        );
+        _ = std::process::Command::new(new_systemd.join("bin/systemctl"))
+            .arg("status")
+            .arg("--no-pager")
+            .arg("--full")
+            .args(failed_units)
+            .spawn()
+            .map(|mut child| child.wait());
+
+        exit_code = 4;
+    }
+
+    if exit_code == 0 {
+        log::info!(
+            "finished switching to system configuration {}",
+            toplevel.display()
+        );
+    } else {
+        log::error!(
+            "switching to system configuration {} failed (status {})",
+            toplevel.display(),
+            exit_code
+        );
+    }
+
+    std::process::exit(exit_code);
+}
+
+fn main() -> anyhow::Result<()> {
+    match (
+        unsafe { nix::libc::geteuid() },
+        std::env::var("__NIXOS_SWITCH_TO_CONFIGURATION_PARENT_EXE").ok(),
+    ) {
+        (0, None) => do_system_switch(),
+        (1..=u32::MAX, None) => bail!("This program does not support being ran outside of the switch-to-configuration environment"),
+        (_, Some(parent_exe)) => do_user_switch(parent_exe),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::collections::HashMap;
+
+    #[test]
+    fn parse_fstab() {
+        {
+            let (filesystems, swaps) = super::parse_fstab(std::io::Cursor::new(""));
+            assert!(filesystems.is_empty());
+            assert!(swaps.is_empty());
+        }
+
+        {
+            let (filesystems, swaps) = super::parse_fstab(std::io::Cursor::new(
+                r#"\
+invalid
+                    "#,
+            ));
+            assert!(filesystems.is_empty());
+            assert!(swaps.is_empty());
+        }
+
+        {
+            let (filesystems, swaps) = super::parse_fstab(std::io::Cursor::new(
+                r#"\
+# This is a generated file.  Do not edit!
+#
+# To make changes, edit the fileSystems and swapDevices NixOS options
+# in your /etc/nixos/configuration.nix file.
+#
+# <file system> <mount point>   <type>  <options>       <dump>  <pass>
+
+# Filesystems.
+/dev/mapper/root / btrfs x-initrd.mount,compress=zstd,noatime,defaults 0 0
+/dev/disk/by-partlabel/BOOT /boot vfat x-systemd.automount 0 2
+/dev/disk/by-partlabel/home /home ext4 defaults 0 2
+/dev/mapper/usr /nix/.ro-store erofs x-initrd.mount,ro 0 2
+
+
+# Swap devices.
+                    "#,
+            ));
+            assert_eq!(filesystems.len(), 4);
+            assert_eq!(swaps.len(), 0);
+            let home_fs = filesystems.get("/home").unwrap();
+            assert_eq!(home_fs.fs_type, "ext4");
+            assert_eq!(home_fs.device, "/dev/disk/by-partlabel/home");
+            assert_eq!(home_fs.options, "defaults");
+        }
+    }
+
+    #[test]
+    fn filter_units() {
+        assert_eq!(
+            super::filter_units(&HashMap::from([]), &HashMap::from([])),
+            HashMap::from([])
+        );
+
+        assert_eq!(
+            super::filter_units(
+                &HashMap::from([("foo".to_string(), ())]),
+                &HashMap::from([("foo".to_string(), ()), ("bar".to_string(), ())])
+            ),
+            HashMap::from([("bar".to_string(), ())])
+        );
+    }
+
+    #[test]
+    fn compare_units() {
+        {
+            assert!(
+                super::compare_units(&HashMap::from([]), &HashMap::from([]))
+                    == super::UnitComparison::Equal
+            );
+
+            assert!(
+                super::compare_units(
+                    &HashMap::from([("Unit".to_string(), HashMap::from([]))]),
+                    &HashMap::from([])
+                ) == super::UnitComparison::Equal
+            );
+
+            assert!(
+                super::compare_units(
+                    &HashMap::from([(
+                        "Unit".to_string(),
+                        HashMap::from([(
+                            "X-Reload-Triggers".to_string(),
+                            vec!["foobar".to_string()]
+                        )])
+                    )]),
+                    &HashMap::from([])
+                ) == super::UnitComparison::Equal
+            );
+        }
+
+        {
+            assert!(
+                super::compare_units(
+                    &HashMap::from([("foobar".to_string(), HashMap::from([]))]),
+                    &HashMap::from([])
+                ) == super::UnitComparison::UnequalNeedsRestart
+            );
+
+            assert!(
+                super::compare_units(
+                    &HashMap::from([(
+                        "Mount".to_string(),
+                        HashMap::from([("Options".to_string(), vec![])])
+                    )]),
+                    &HashMap::from([(
+                        "Mount".to_string(),
+                        HashMap::from([("Options".to_string(), vec!["ro".to_string()])])
+                    )])
+                ) == super::UnitComparison::UnequalNeedsReload
+            );
+        }
+
+        {
+            assert!(
+                super::compare_units(
+                    &HashMap::from([]),
+                    &HashMap::from([(
+                        "Unit".to_string(),
+                        HashMap::from([(
+                            "X-Reload-Triggers".to_string(),
+                            vec!["foobar".to_string()]
+                        )])
+                    )])
+                ) == super::UnitComparison::UnequalNeedsReload
+            );
+
+            assert!(
+                super::compare_units(
+                    &HashMap::from([(
+                        "Unit".to_string(),
+                        HashMap::from([(
+                            "X-Reload-Triggers".to_string(),
+                            vec!["foobar".to_string()]
+                        )])
+                    )]),
+                    &HashMap::from([(
+                        "Unit".to_string(),
+                        HashMap::from([(
+                            "X-Reload-Triggers".to_string(),
+                            vec!["barfoo".to_string()]
+                        )])
+                    )])
+                ) == super::UnitComparison::UnequalNeedsReload
+            );
+
+            assert!(
+                super::compare_units(
+                    &HashMap::from([(
+                        "Mount".to_string(),
+                        HashMap::from([("Type".to_string(), vec!["ext4".to_string()])])
+                    )]),
+                    &HashMap::from([(
+                        "Mount".to_string(),
+                        HashMap::from([("Type".to_string(), vec!["btrfs".to_string()])])
+                    )])
+                ) == super::UnitComparison::UnequalNeedsRestart
+            );
+        }
+    }
+
+    #[test]
+    fn parse_systemd_ini() {
+        // Ensure we don't attempt to unescape content in unit files.
+        // https://github.com/NixOS/nixpkgs/issues/315602
+        {
+            let mut unit_info = HashMap::new();
+
+            let test_unit = std::io::Cursor::new(
+                r#"[Unit]
+After=dev-disk-by\x2dlabel-root.device
+"#,
+            );
+            super::parse_systemd_ini(&mut unit_info, test_unit).unwrap();
+
+            assert_eq!(
+                unit_info
+                    .get("Unit")
+                    .unwrap()
+                    .get("After")
+                    .unwrap()
+                    .first()
+                    .unwrap(),
+                "dev-disk-by\\x2dlabel-root.device"
+            );
+        }
+    }
+}
diff --git a/pkgs/by-name/sw/switcheroo/package.nix b/pkgs/by-name/sw/switcheroo/package.nix
index 9d2327f4095a5..632960d70bccd 100644
--- a/pkgs/by-name/sw/switcheroo/package.nix
+++ b/pkgs/by-name/sw/switcheroo/package.nix
@@ -18,19 +18,19 @@
 
 stdenv.mkDerivation (finalAttrs: {
   pname = "switcheroo";
-  version = "2.1.0";
+  version = "2.2.0";
 
   src = fetchFromGitLab {
     owner = "adhami3310";
     repo = "Switcheroo";
     rev = "v${finalAttrs.version}";
-    hash = "sha256-hopN2ynksaYoNYjXrh7plmhfmGYyqqK75GOtbsE95ZY=";
+    hash = "sha256-AwecOA8HWGimhQyCEG3Z3hhwa9RVWssykUXsdvqqs9U=";
   };
 
   cargoDeps = rustPlatform.fetchCargoTarball {
     src = finalAttrs.src;
     name = "switcheroo-${finalAttrs.version}";
-    hash = "sha256-wN6MsiOgYFgzDzdGei0ptRbG+h+xMJiFfzCcg6Xtryw=";
+    hash = "sha256-fpI4ue30DhkeWAolyeots+LkaRyaIPhYmIqRmx08i2s=";
   };
 
   nativeBuildInputs = [
@@ -64,8 +64,8 @@ stdenv.mkDerivation (finalAttrs: {
 
   meta = with lib; {
     changelog = "https://gitlab.com/adhami3310/Switcheroo/-/releases/v${finalAttrs.version}";
-    description = "An app for converting images between different formats";
-    homepage = "https://gitlab.com/adhami3310/Switcheroo";
+    description = "App for converting images between different formats";
+    homepage = "https://apps.gnome.org/Converter/";
     license = licenses.gpl3Plus;
     mainProgram = "switcheroo";
     maintainers = with maintainers; [ michaelgrahamevans ];
diff --git a/pkgs/by-name/sw/sword/package.nix b/pkgs/by-name/sw/sword/package.nix
new file mode 100644
index 0000000000000..c8382ccbaa7c9
--- /dev/null
+++ b/pkgs/by-name/sw/sword/package.nix
@@ -0,0 +1,64 @@
+{
+  lib,
+  stdenv,
+  fetchurl,
+  pkg-config,
+  icu,
+  clucene_core,
+  curl,
+}:
+
+stdenv.mkDerivation (finalAttrs: {
+  pname = "sword";
+  version = "1.9.0";
+
+  src = fetchurl {
+    url = "https://www.crosswire.org/ftpmirror/pub/sword/source/v${lib.versions.majorMinor finalAttrs.version}/sword-${finalAttrs.version}.tar.gz";
+    hash = "sha256-QkCc894vrxEIUj4sWsB0XSH57SpceO2HjuncwwNCa4o=";
+  };
+
+  nativeBuildInputs = [ pkg-config ];
+  buildInputs = [
+    icu
+    clucene_core
+    curl
+  ];
+
+  outputs = [
+    "out"
+    "dev"
+  ];
+
+  prePatch = ''
+    patchShebangs .;
+  '';
+
+  configureFlags = [
+    "--without-conf"
+    "--enable-tests=no"
+  ];
+
+  CXXFLAGS = [
+    "-Wno-unused-but-set-variable"
+    "-Wno-unknown-warning-option"
+    # compat with icu61+ https://github.com/unicode-org/icu/blob/release-64-2/icu4c/readme.html#L554
+    "-DU_USING_ICU_NAMESPACE=1"
+  ];
+
+  meta = {
+    description = "Software framework that allows research manipulation of Biblical texts";
+    homepage = "https://www.crosswire.org/sword/";
+    longDescription = ''
+      The SWORD Project is the CrossWire Bible Society's free Bible software
+      project. Its purpose is to create cross-platform open-source tools --
+      covered by the GNU General Public License -- that allow programmers and
+      Bible societies to write new Bible software more quickly and easily. We
+      also create Bible study software for all readers, students, scholars, and
+      translators of the Bible, and have a growing collection of many hundred
+      texts in around 100 languages.
+    '';
+    license = lib.licenses.gpl2;
+    maintainers = with lib.maintainers; [ AndersonTorres ];
+    platforms = lib.platforms.unix;
+  };
+})
diff --git a/pkgs/by-name/sw/swww/Cargo.lock b/pkgs/by-name/sw/swww/Cargo.lock
index d9c0ee3e72a81..bc481ee36a3be 100644
--- a/pkgs/by-name/sw/swww/Cargo.lock
+++ b/pkgs/by-name/sw/swww/Cargo.lock
@@ -31,47 +31,48 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.6.13"
+version = "0.6.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
  "anstyle-wincon",
  "colorchoice",
+ "is_terminal_polyfill",
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle"
-version = "1.0.6"
+version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
 dependencies = [
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.2"
+version = "3.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
 dependencies = [
  "anstyle",
  "windows-sys 0.52.0",
@@ -229,6 +230,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
 name = "cast"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -236,12 +243,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
 name = "cc"
-version = "1.0.94"
+version = "1.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
+checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
 dependencies = [
  "jobserver",
  "libc",
+ "once_cell",
 ]
 
 [[package]]
@@ -345,9 +353,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
 [[package]]
 name = "colorchoice"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
 
 [[package]]
 name = "crc32fast"
@@ -521,9 +529,9 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.28"
+version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
 dependencies = [
  "crc32fast",
  "miniz_oxide",
@@ -571,9 +579,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.14.3"
+version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 
 [[package]]
 name = "heck"
@@ -612,11 +620,11 @@ dependencies = [
 
 [[package]]
 name = "image-webp"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
+checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d"
 dependencies = [
- "byteorder",
+ "byteorder-lite",
  "thiserror",
 ]
 
@@ -659,6 +667,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "is_terminal_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+
+[[package]]
 name = "itertools"
 version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -684,9 +698,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "jobserver"
-version = "0.1.30"
+version = "0.1.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2"
+checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
 dependencies = [
  "libc",
 ]
@@ -724,9 +738,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.154"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
 
 [[package]]
 name = "libfuzzer-sys"
@@ -763,9 +777,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "lock_api"
-version = "0.4.11"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
 dependencies = [
  "autocfg",
  "scopeguard",
@@ -1020,9 +1034,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.80"
+version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
 dependencies = [
  "unicode-ident",
 ]
@@ -1219,9 +1233,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.32"
+version = "0.38.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
 dependencies = [
  "bitflags 2.5.0",
  "errno",
@@ -1265,18 +1279,18 @@ checksum = "621e3680f3e07db4c9c2c3fb07c6223ab2fab2e54bd3c04c3ae037990f428c32"
 
 [[package]]
 name = "serde"
-version = "1.0.198"
+version = "1.0.200"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
+checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.198"
+version = "1.0.200"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
+checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1361,7 +1375,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
 name = "swww"
-version = "0.9.4"
+version = "0.9.5"
 dependencies = [
  "assert_cmd",
  "clap",
@@ -1374,7 +1388,7 @@ dependencies = [
 
 [[package]]
 name = "swww-daemon"
-version = "0.9.4"
+version = "0.9.5"
 dependencies = [
  "bitcode",
  "keyframe",
@@ -1394,9 +1408,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.59"
+version = "2.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1449,18 +1463,18 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
 
 [[package]]
 name = "thiserror"
-version = "1.0.58"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.58"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1544,9 +1558,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.22.9"
+version = "0.22.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
+checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
 dependencies = [
  "indexmap",
  "serde",
@@ -1569,7 +1583,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
 name = "utils"
-version = "0.9.4"
+version = "0.9.5"
 dependencies = [
  "bitcode",
  "criterion",
@@ -1764,37 +1778,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
 
 [[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
 name = "winapi-util"
-version = "0.1.6"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
 dependencies = [
- "winapi",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
 name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1935,9 +1927,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
 
 [[package]]
 name = "winnow"
-version = "0.6.6"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
+checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578"
 dependencies = [
  "memchr",
 ]
diff --git a/pkgs/by-name/sw/swww/package.nix b/pkgs/by-name/sw/swww/package.nix
index 4dc92570f93d3..de6889f5b1f20 100644
--- a/pkgs/by-name/sw/swww/package.nix
+++ b/pkgs/by-name/sw/swww/package.nix
@@ -1,22 +1,23 @@
-{ lib
-, fetchFromGitHub
-, rustPlatform
-, pkg-config
-, lz4
-, libxkbcommon
-, installShellFiles
-, scdoc
+{
+  lib,
+  fetchFromGitHub,
+  rustPlatform,
+  pkg-config,
+  lz4,
+  libxkbcommon,
+  installShellFiles,
+  scdoc,
 }:
 
 rustPlatform.buildRustPackage rec {
   pname = "swww";
-  version = "0.9.4";
+  version = "0.9.5";
 
   src = fetchFromGitHub {
     owner = "LGFae";
     repo = "swww";
     rev = "refs/tags/v${version}";
-    hash = "sha256-LvSPKg8cWlwKu4a+P/G0dOqV+aPsUq3axI1QqnLj4U8=";
+    hash = "sha256-ldy9HhIsWdtTdvtRLV3qDT80oX646BI4Q+YX5wJXbsc=";
   };
 
   cargoLock = {
@@ -52,12 +53,15 @@ rustPlatform.buildRustPackage rec {
       --zsh completions/_swww
   '';
 
-  meta = with lib; {
+  meta = {
     description = "Efficient animated wallpaper daemon for wayland, controlled at runtime";
     homepage = "https://github.com/LGFae/swww";
-    license = licenses.gpl3;
-    maintainers = with maintainers; [ mateodd25 donovanglover ];
-    platforms = platforms.linux;
+    license = lib.licenses.gpl3;
+    maintainers = with lib.maintainers; [
+      mateodd25
+      donovanglover
+    ];
+    platforms = lib.platforms.linux;
     mainProgram = "swww";
   };
 }