diff options
Diffstat (limited to 'pkgs')
47 files changed, 2932 insertions, 127 deletions
diff --git a/pkgs/applications/audio/squeezelite/default.nix b/pkgs/applications/audio/squeezelite/default.nix index deaec9e16d7cc..92f6932fb3751 100644 --- a/pkgs/applications/audio/squeezelite/default.nix +++ b/pkgs/applications/audio/squeezelite/default.nix @@ -45,13 +45,13 @@ stdenv.mkDerivation { pname = binName; # versions are specified in `squeezelite.h` # see https://github.com/ralph-irving/squeezelite/issues/29 - version = "2.0.0.1486"; + version = "2.0.0.1488"; src = fetchFromGitHub { owner = "ralph-irving"; repo = "squeezelite"; - rev = "fd4a82e7d0e53124d9618320f3c115d90654509d"; - hash = "sha256-nR2Px7VYjAktUsueEyBAV2392+/dX6JYIy7YSMh05c0="; + rev = "0e85ddfd79337cdc30b7d29922b1d790600bb6b4"; + hash = "sha256-FGqo/c74JN000w/iRnvYUejqnYGDzHNZu9pEmR7yR3s="; }; buildInputs = [ flac libmad libvorbis mpg123 ] diff --git a/pkgs/applications/office/planify/default.nix b/pkgs/applications/office/planify/default.nix index 86117b22e8036..8d4c079753caa 100644 --- a/pkgs/applications/office/planify/default.nix +++ b/pkgs/applications/office/planify/default.nix @@ -10,6 +10,7 @@ , evolution-data-server , glib , glib-networking +, gst_all_1 , gtk4 , gtksourceview5 , gxml @@ -49,6 +50,9 @@ stdenv.mkDerivation rec { evolution-data-server glib glib-networking + # Needed for GtkMediaStream creation with success.ogg, see #311295. + gst_all_1.gst-plugins-base + gst_all_1.gst-plugins-good gtk4 gtksourceview5 gxml diff --git a/pkgs/applications/radio/chirp/default.nix b/pkgs/applications/radio/chirp/default.nix index d73dcc01a54e6..9854fa8076efe 100644 --- a/pkgs/applications/radio/chirp/default.nix +++ b/pkgs/applications/radio/chirp/default.nix @@ -10,13 +10,13 @@ python3.pkgs.buildPythonApplication rec { pname = "chirp"; - version = "0.4.0-unstable-2024-05-03"; + version = "0.4.0-unstable-2024-05-10"; src = fetchFromGitHub { owner = "kk7ds"; repo = "chirp"; - rev = "e95140ff433b805ca16df04cba501b7332a9ec95"; - hash = "sha256-UU3Ve6Yb7UK2nOLTfJasrlMX4iu1cpLBLScvhRhTUJ0="; + rev = "d5dc5c8e053dbcf87c8b0ccf03109c0870c22bfb"; + hash = "sha256-Tqq1dTjtzHTgaHUAio5B8V4Bo+P8EPa3s/kG181TrCc="; }; buildInputs = [ glib diff --git a/pkgs/applications/science/math/eigenmath/default.nix b/pkgs/applications/science/math/eigenmath/default.nix index becf26c61af7a..2f51a00a9b150 100644 --- a/pkgs/applications/science/math/eigenmath/default.nix +++ b/pkgs/applications/science/math/eigenmath/default.nix @@ -7,13 +7,13 @@ stdenv.mkDerivation rec { pname = "eigenmath"; - version = "0-unstable-2024-05-03"; + version = "0-unstable-2024-05-12"; src = fetchFromGitHub { owner = "georgeweigt"; repo = pname; - rev = "69ebfbaa7f328ddc9d97b7c404369818a5febe4a"; - hash = "sha256-lHxVyUXIY9+YIStA6202Bhy/b0xaxJbW/VPI7FbNJk0="; + rev = "978b3bd582a90c8e82079f2e4e4a3a5038cbbe08"; + hash = "sha256-DoGX8nUcWcioTq8ymB+HLsCnt9V6HTKSX4Zs2CQz78Q="; }; checkPhase = let emulator = stdenv.hostPlatform.emulator buildPackages; in '' diff --git a/pkgs/applications/version-management/git-cola/default.nix b/pkgs/applications/version-management/git-cola/default.nix index 0f5fd0cb7aa2e..38c094ad0e20c 100644 --- a/pkgs/applications/version-management/git-cola/default.nix +++ b/pkgs/applications/version-management/git-cola/default.nix @@ -10,14 +10,14 @@ python3Packages.buildPythonApplication rec { pname = "git-cola"; - version = "4.7.0"; + version = "4.7.1"; pyproject = true; src = fetchFromGitHub { owner = "git-cola"; repo = "git-cola"; rev = "v${version}"; - hash = "sha256-BiSs3vWJacCtGthHi/nFJm4Hqt0uO6XXZi/Zqvjb928="; + hash = "sha256-93aayGGMgkSghTpx8M5Cfbxf2szAwrSzuoWK6GCTqZ8="; }; buildInputs = lib.optionals stdenv.isLinux [ diff --git a/pkgs/applications/video/kodi/addons/visualization-projectm/default.nix b/pkgs/applications/video/kodi/addons/visualization-projectm/default.nix index 8eaecd87fc47c..38ab9d308762f 100644 --- a/pkgs/applications/video/kodi/addons/visualization-projectm/default.nix +++ b/pkgs/applications/video/kodi/addons/visualization-projectm/default.nix @@ -3,13 +3,13 @@ buildKodiBinaryAddon rec { pname = "visualization-projectm"; namespace = "visualization.projectm"; - version = "20.2.0"; + version = "21.0.1"; src = fetchFromGitHub { owner = "xbmc"; repo = namespace; rev = "${version}-${rel}"; - hash = "sha256-Kcl1ep+RJlofFmxkrGT3T+XXdwiCofq1hggwU0PAd0E="; + hash = "sha256-wjSQmOtQb4KjY3iH3Xh7AdQwE6ked+cpW6/gdNYS+NA="; }; extraBuildInputs = [ pkg-config libGL projectm ]; diff --git a/pkgs/by-name/ad/adwsteamgtk/package.nix b/pkgs/by-name/ad/adwsteamgtk/package.nix index 419541b28ed79..393962961569c 100644 --- a/pkgs/by-name/ad/adwsteamgtk/package.nix +++ b/pkgs/by-name/ad/adwsteamgtk/package.nix @@ -12,7 +12,7 @@ python3Packages.buildPythonApplication rec { pname = "adwsteamgtk"; - version = "0.6.10"; + version = "0.6.11"; # built with meson, not a python format format = "other"; @@ -20,7 +20,7 @@ python3Packages.buildPythonApplication rec { owner = "Foldex"; repo = "AdwSteamGtk"; rev = "refs/tags/v${version}"; - hash = "sha256-sh4FLXG78i20Bt8pCCbhO6Sx815stjAZRLCD+X5Zk40="; + hash = "sha256-f7+2gTpG5Ntgq+U2AkQihzSTrO+oMsLWxgxe4dVyz8A="; }; buildInputs = [ diff --git a/pkgs/by-name/li/libui-ng/package.nix b/pkgs/by-name/li/libui-ng/package.nix index 9cded1afcc7e9..80e0589ac2c12 100644 --- a/pkgs/by-name/li/libui-ng/package.nix +++ b/pkgs/by-name/li/libui-ng/package.nix @@ -12,13 +12,13 @@ stdenv.mkDerivation rec { pname = "libui-ng"; - version = "4.1-unstable-2024-02-05"; + version = "4.1-unstable-2024-05-03"; src = fetchFromGitHub { owner = "libui-ng"; repo = "libui-ng"; - rev = "4d46de31eafad84c88b939356bcd64e6c5ee3821"; - hash = "sha256-Yb8VdJe75uBzRnsfTOVxUXstZmu6dJ9nBuOrf86KO5s="; + rev = "56f1ad65f0f32bb1eb67a268cca4658fbe4567c1"; + hash = "sha256-wo4iS/a1ErdipFDPYKvaGpO/JGtk6eU/qMLC4eUoHnA="; }; postPatch = lib.optionalString (stdenv.isDarwin && stdenv.isx86_64) '' diff --git a/pkgs/by-name/ma/maxfetch/package.nix b/pkgs/by-name/ma/maxfetch/package.nix new file mode 100644 index 0000000000000..3b664020a4e86 --- /dev/null +++ b/pkgs/by-name/ma/maxfetch/package.nix @@ -0,0 +1,39 @@ +{ lib +, stdenvNoCC +, fetchFromGitHub +, makeBinaryWrapper +, gnused +, ncurses +, procps +}: + +stdenvNoCC.mkDerivation { + pname = "maxfetch"; + version = "unstable-2023-07-31"; + + src = fetchFromGitHub { + owner = "jobcmax"; + repo = "maxfetch"; + rev = "17baa4047073e20572403b70703c69696af6b68d"; + hash = "sha256-LzOhrFFjGs9GIDjk1lUFKhlnzJuEUrKjBcv1eT3kaY8="; + }; + + nativeBuildInputs = [ makeBinaryWrapper ]; + + installPhase = '' + runHook preInstall + install -Dm755 maxfetch $out/bin/maxfetch + wrapProgram $out/bin/maxfetch \ + --prefix PATH : ${lib.makeBinPath [ gnused ncurses procps ]} + runHook postInstall + ''; + + meta = with lib; { + description = "Nice fetching program written in sh"; + homepage = "https://github.com/jobcmax/maxfetch"; + license = licenses.gpl2Plus; + mainProgram = "maxfetch"; + maintainers = with maintainers; [ jtbx ]; + platforms = platforms.unix; + }; +} diff --git a/pkgs/by-name/me/megapixels/package.nix b/pkgs/by-name/me/megapixels/package.nix index 74ced5c3af268..91bd2b302b948 100644 --- a/pkgs/by-name/me/megapixels/package.nix +++ b/pkgs/by-name/me/megapixels/package.nix @@ -29,13 +29,13 @@ let in stdenv.mkDerivation (finalAttrs: { pname = "megapixels"; - version = "1.8.1"; + version = "1.8.2"; src = fetchFromGitLab { owner = "megapixels-org"; repo = "Megapixels"; rev = finalAttrs.version; - hash = "sha256-TXiPJbd4TPpsEvmD97F7xkm4rS1g+ViTVTNlxeXrQaw="; + hash = "sha256-odsOYrk//ZhodsumLpJjhPDcwF1gkE/no166u+IDxSY="; }; nativeBuildInputs = [ diff --git a/pkgs/by-name/nr/nrfconnect/package.nix b/pkgs/by-name/nr/nrfconnect/package.nix index c3a6e0e7f3ddf..5f62d6283851e 100644 --- a/pkgs/by-name/nr/nrfconnect/package.nix +++ b/pkgs/by-name/nr/nrfconnect/package.nix @@ -5,11 +5,11 @@ let pname = "nrfconnect"; - version = "4.3.0"; + version = "4.4.1"; src = fetchurl { url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/desktop-software/nrf-connect-for-desktop/${lib.versions.major version}-${lib.versions.minor version}-${lib.versions.patch version}/nrfconnect-${version}-x86_64.appimage"; - hash = "sha256-G8//dZqPxn6mR8Bjzf/bAn9Gv7t2AFWIF9twCGbqMd8="; + hash = "sha256-x/vVSOEajuQtLATRXk8DVLlXHegCqP+acecaOFNeBb8="; name = "${pname}-${version}.AppImage"; }; @@ -25,7 +25,6 @@ in appimageTools.wrapType2 { ]; extraInstallCommands = '' - mv $out/bin/nrfconnect-* $out/bin/nrfconnect install -Dm444 ${appimageContents}/nrfconnect.desktop -t $out/share/applications install -Dm444 ${appimageContents}/usr/share/icons/hicolor/512x512/apps/nrfconnect.png \ -t $out/share/icons/hicolor/512x512/apps diff --git a/pkgs/by-name/re/renode-dts2repl/package.nix b/pkgs/by-name/re/renode-dts2repl/package.nix index 9dba1fb8eacd7..f9ad25154dad2 100644 --- a/pkgs/by-name/re/renode-dts2repl/package.nix +++ b/pkgs/by-name/re/renode-dts2repl/package.nix @@ -6,14 +6,14 @@ python3.pkgs.buildPythonApplication { pname = "renode-dts2repl"; - version = "0-unstable-2024-04-30"; + version = "0-unstable-2024-05-09"; pyproject = true; src = fetchFromGitHub { owner = "antmicro"; repo = "dts2repl"; - rev = "dc2212318aec6c2aa6920795e16fab7dfcb8796e"; - hash = "sha256-XY2rQqu2CSKRImx0GmC5ggTZTyvrtX+LvqkYj0sEBuU="; + rev = "b95c930c2122e227bbacee42f35933a4c2d40771"; + hash = "sha256-Sax+ckln+R6ll/UPztESJEjO8dtq8THmi309CaFTv0I="; }; nativeBuildInputs = [ diff --git a/pkgs/by-name/sp/spicetify-cli/package.nix b/pkgs/by-name/sp/spicetify-cli/package.nix index 13d1b8aceedbc..22489b3b7c9c3 100644 --- a/pkgs/by-name/sp/spicetify-cli/package.nix +++ b/pkgs/by-name/sp/spicetify-cli/package.nix @@ -2,13 +2,13 @@ buildGoModule rec { pname = "spicetify-cli"; - version = "2.36.10"; + version = "2.36.11"; src = fetchFromGitHub { owner = "spicetify"; repo = "spicetify-cli"; rev = "v${version}"; - hash = "sha256-lwbd5sXqzC3H2GwmVqxAdt6Qcic00wh39l5Kp1UIYAs="; + hash = "sha256-ZqWGKuYDxuKVqz6cNxZ3cTcKTxkxuu42b48hlAialKc="; }; vendorHash = "sha256-UPrLXzAdvCOmLm1tekzKyulQ4+2BSyPUF1k66GwKS88="; 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..e050e74d79eaf --- /dev/null +++ b/pkgs/by-name/sw/switch-to-configuration-ng/src/main.rs @@ -0,0 +1,2077 @@ +use std::{ + cell::RefCell, + collections::HashMap, + io::{BufRead, 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; +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, unit_file: &Path) -> Result<()> { + let ini = Ini::load_from_file(unit_file) + .with_context(|| format!("Failed to load unit file {}", unit_file.display()))?; + + // 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_file: &Path) -> Result<UnitInfo> { + // Parse the main unit and all overrides + let mut unit_data = HashMap::new(); + + parse_systemd_ini(&mut unit_data, base_unit_file)?; + + for entry in + glob(&format!("{}.d/*.conf", base_unit_file.display())).context("Invalid glob pattern")? + { + let Ok(entry) = entry else { + continue; + }; + + parse_systemd_ini(&mut unit_data, &entry)?; + } + + // Handle drop-in template-unit instance overrides + if unit_file != base_unit_file { + for entry in + glob(&format!("{}.d/*.conf", unit_file.display())).context("Invalid glob pattern")? + { + let Ok(entry) = entry else { + continue; + }; + + parse_systemd_ini(&mut unit_data, &entry)?; + } + } + + 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 ¤t_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(¤t_unit_file, ¤t_base_unit_file)?; + if parse_systemd_bool(Some(¤t_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(¤t_unit_file, ¤t_base_unit_file)?; + let new_unit_info = parse_unit(&new_unit_file, &new_base_unit_file)?; + match compare_units(¤t_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), + ¤t_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(¤t_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, + ¤t_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, + ¤t_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 + ); + } + } +} diff --git a/pkgs/development/tools/treefmt/default.nix b/pkgs/by-name/tr/treefmt/package.nix index 48fc4b44962e8..48fc4b44962e8 100644 --- a/pkgs/development/tools/treefmt/default.nix +++ b/pkgs/by-name/tr/treefmt/package.nix diff --git a/pkgs/by-name/tr/treefmt2/package.nix b/pkgs/by-name/tr/treefmt2/package.nix new file mode 100644 index 0000000000000..defe9100906aa --- /dev/null +++ b/pkgs/by-name/tr/treefmt2/package.nix @@ -0,0 +1,33 @@ +{ lib, buildGoModule, fetchFromGitHub }: +buildGoModule rec { + pname = "treefmt"; + version = "2.0.0-rc1"; + + src = fetchFromGitHub { + owner = "numtide"; + repo = "treefmt"; + rev = "v${version}"; + hash = "sha256-iRjd7iYd3617XZrGD6Bi6d1SoE8dgATMbT4AMXklfgM="; + }; + + vendorHash = "sha256-xbXy5Csl2JD5/F5mtvh8J36VZqrUIfO3OBV/LE+KzWA="; + + subPackages = [ "." ]; + + CGO_ENABLED = 1; + + ldflags = [ + "-s" + "-w" + "-X git.numtide.com/numtide/treefmt/build.Name=${pname}" + "-X git.numtide.com/numtide/treefmt/build.Version=v${version}" + ]; + + meta = { + description = "one CLI to format the code tree"; + homepage = "https://github.com/numtide/treefmt"; + license = lib.licenses.mit; + maintainers = [ lib.maintainers.brianmcgee lib.maintainers.zimbatm ]; + mainProgram = "treefmt"; + }; +} diff --git a/pkgs/tools/wayland/ydotool/default.nix b/pkgs/by-name/yd/ydotool/package.nix index 42f95735de597..137a701d87a36 100644 --- a/pkgs/tools/wayland/ydotool/default.nix +++ b/pkgs/by-name/yd/ydotool/package.nix @@ -1,13 +1,22 @@ -{ lib, stdenv, fetchFromGitHub, cmake, scdoc, util-linux, xorg }: +{ + lib, + stdenv, + fetchFromGitHub, + cmake, + scdoc, + util-linux, + xorg, + nixosTests, +}: -stdenv.mkDerivation rec { +stdenv.mkDerivation (finalAttrs: { pname = "ydotool"; version = "1.0.4"; src = fetchFromGitHub { owner = "ReimuNotMoe"; repo = "ydotool"; - rev = "v${version}"; + rev = "v${finalAttrs.version}"; hash = "sha256-MtanR+cxz6FsbNBngqLE+ITKPZFHmWGsD1mBDk0OVng="; }; @@ -19,13 +28,22 @@ stdenv.mkDerivation rec { ''; strictDeps = true; - nativeBuildInputs = [ cmake scdoc ]; + nativeBuildInputs = [ + cmake + scdoc + ]; - meta = with lib; { - homepage = "https://github.com/ReimuNotMoe/ydotool"; + passthru.tests.basic = nixosTests.ydotool; + + meta = { description = "Generic Linux command-line automation tool"; - license = licenses.agpl3Plus; - maintainers = with maintainers; [ willibutz kraem ]; - platforms = with platforms; linux; + homepage = "https://github.com/ReimuNotMoe/ydotool"; + license = lib.licenses.agpl3Plus; + mainProgram = "ydotool"; + maintainers = with lib.maintainers; [ + willibutz + kraem + ]; + platforms = lib.platforms.linux; }; -} +}) diff --git a/pkgs/data/themes/colloid-gtk-theme/default.nix b/pkgs/data/themes/colloid-gtk-theme/default.nix index ef69114c855e0..60849a407d866 100644 --- a/pkgs/data/themes/colloid-gtk-theme/default.nix +++ b/pkgs/data/themes/colloid-gtk-theme/default.nix @@ -22,13 +22,13 @@ lib.checkListOfEnum "${pname}: tweaks" [ "nord" "dracula" "gruvbox" "everforest" stdenvNoCC.mkDerivation rec { inherit pname; - version = "2024-04-14"; + version = "2024-05-13"; src = fetchFromGitHub { owner = "vinceliuice"; repo = pname; rev = version; - hash = "sha256-DQYnR2V86KbIJfAA4ObRtKQq1IuECie1qdOBYVu8NtA="; + hash = "sha256-24U1iMByy+cFQuLUWYPBSuvJwYzwS0rCr7L6OWyMUz0="; }; nativeBuildInputs = [ diff --git a/pkgs/development/python-modules/androidtvremote2/default.nix b/pkgs/development/python-modules/androidtvremote2/default.nix index a0c066c4d9751..df179dab636be 100644 --- a/pkgs/development/python-modules/androidtvremote2/default.nix +++ b/pkgs/development/python-modules/androidtvremote2/default.nix @@ -1,40 +1,37 @@ -{ lib -, aiofiles -, buildPythonPackage -, cryptography -, fetchFromGitHub -, protobuf -, pythonOlder -, setuptools +{ + lib, + aiofiles, + buildPythonPackage, + cryptography, + fetchFromGitHub, + protobuf, + pythonOlder, + setuptools, }: buildPythonPackage rec { pname = "androidtvremote2"; - version = "0.0.15"; - format = "pyproject"; + version = "0.1.1"; + pyproject = true; - disabled = pythonOlder "3.7"; + disabled = pythonOlder "3.10"; src = fetchFromGitHub { owner = "tronikos"; repo = "androidtvremote2"; rev = "refs/tags/v${version}"; - hash = "sha256-aLNmuMuwaMhl/Utv6bVU4fR8zmhj0fUiInL4RHT4dVw="; + hash = "sha256-Zem2IWBUWmyVdBjqoVKFk+/lg5T7CPXCKFXhFusQFLY="; }; - nativeBuildInputs = [ - setuptools - ]; + build-system = [ setuptools ]; - propagatedBuildInputs = [ + dependencies = [ aiofiles cryptography protobuf ]; - pythonImportsCheck = [ - "androidtvremote2" - ]; + pythonImportsCheck = [ "androidtvremote2" ]; # Module only has a dummy test doCheck = false; diff --git a/pkgs/development/python-modules/b2sdk/default.nix b/pkgs/development/python-modules/b2sdk/default.nix index 266443af70f02..aa5fa2e2c40db 100644 --- a/pkgs/development/python-modules/b2sdk/default.nix +++ b/pkgs/development/python-modules/b2sdk/default.nix @@ -21,7 +21,7 @@ buildPythonPackage rec { pname = "b2sdk"; - version = "2.1.0"; + version = "2.2.1"; pyproject = true; disabled = pythonOlder "3.7"; @@ -30,7 +30,7 @@ buildPythonPackage rec { owner = "Backblaze"; repo = "b2-sdk-python"; rev = "refs/tags/v${version}"; - hash = "sha256-/TuPT+Y0asGGtfRNxxvpX0WkS4O4asFmIuqSdQ4I9fQ="; + hash = "sha256-ENEAynUd66sjS+/Qoy9qyffPpSvxdnY1Nwdi+JTE96I="; }; build-system = [ pdm-backend ]; diff --git a/pkgs/development/python-modules/dirigera/default.nix b/pkgs/development/python-modules/dirigera/default.nix index 36789741f83aa..7b5f5d536601e 100644 --- a/pkgs/development/python-modules/dirigera/default.nix +++ b/pkgs/development/python-modules/dirigera/default.nix @@ -12,7 +12,7 @@ buildPythonPackage rec { pname = "dirigera"; - version = "1.1.6"; + version = "1.1.7"; pyproject = true; disabled = pythonOlder "3.7"; @@ -21,7 +21,7 @@ buildPythonPackage rec { owner = "Leggin"; repo = "dirigera"; rev = "refs/tags/v${version}"; - hash = "sha256-OXq8eJyZQBsJEK81GxstfMHqDShlZyOWSXLwP9Zfpqw="; + hash = "sha256-KAwXpcs0EvmaxBVgZ7TlT1hDI0m7f8hJigUTluVATsw="; }; build-system = [ setuptools ]; diff --git a/pkgs/development/python-modules/dogpile-cache/default.nix b/pkgs/development/python-modules/dogpile-cache/default.nix index 51d799b90f13f..e03a72626a976 100644 --- a/pkgs/development/python-modules/dogpile-cache/default.nix +++ b/pkgs/development/python-modules/dogpile-cache/default.nix @@ -12,7 +12,7 @@ buildPythonPackage rec { pname = "dogpile-cache"; - version = "1.3.2"; + version = "1.3.3"; format = "pyproject"; disabled = pythonOlder "3.6"; @@ -20,7 +20,7 @@ buildPythonPackage rec { src = fetchPypi { pname = "dogpile.cache"; inherit version; - hash = "sha256-T3HcAzOtNRycb3BPW6Kje/Ucbu0EN9Gt9W4HWVmv5js="; + hash = "sha256-+EuO0LD7KX0VEFVEf6jcr3uuVm1Nve/s3MHzdmKrWIs="; }; nativeBuildInputs = [ diff --git a/pkgs/development/python-modules/hikari/default.nix b/pkgs/development/python-modules/hikari/default.nix index e76d7780c0bfa..062a6a9ebe14c 100644 --- a/pkgs/development/python-modules/hikari/default.nix +++ b/pkgs/development/python-modules/hikari/default.nix @@ -17,13 +17,13 @@ }: buildPythonPackage rec { pname = "hikari"; - version = "2.0.0.dev124"; + version = "2.0.0.dev125"; src = fetchFromGitHub { owner = "hikari-py"; repo = "hikari"; rev = version; - hash = "sha256-zDgU3Ol/I3YNnwXm+aBh20KwonW746p5TObuwuWORog="; + hash = "sha256-qxgIYquXUWrm8bS8EamERMHOnjI2aPyK7bQieVG66uA="; # The git commit is part of the `hikari.__git_sha1__` original output; # leave that output the same in nixpkgs. Use the `.git` directory # to retrieve the commit SHA, and remove the directory afterwards, diff --git a/pkgs/development/python-modules/opower/default.nix b/pkgs/development/python-modules/opower/default.nix index 2c00224e92295..d889c97c3278c 100644 --- a/pkgs/development/python-modules/opower/default.nix +++ b/pkgs/development/python-modules/opower/default.nix @@ -13,7 +13,7 @@ buildPythonPackage rec { pname = "opower"; - version = "0.4.4"; + version = "0.4.5"; pyproject = true; disabled = pythonOlder "3.9"; @@ -22,7 +22,7 @@ buildPythonPackage rec { owner = "tronikos"; repo = "opower"; rev = "refs/tags/v${version}"; - hash = "sha256-GG7r0/rZcB4cuog3m0qCOrmcYiscPQggQhRC9sQGG40="; + hash = "sha256-PBxxLbVOvJSFmDXgKeI5sICUR7NJGUEUUahK9eBsvbE="; }; build-system = [ setuptools ]; diff --git a/pkgs/development/python-modules/pycrdt-websocket/default.nix b/pkgs/development/python-modules/pycrdt-websocket/default.nix index a0e060b1bf2a5..f713b194f79ad 100644 --- a/pkgs/development/python-modules/pycrdt-websocket/default.nix +++ b/pkgs/development/python-modules/pycrdt-websocket/default.nix @@ -18,7 +18,7 @@ buildPythonPackage rec { pname = "pycrdt-websocket"; - version = "0.13.3"; + version = "0.13.4"; pyproject = true; disabled = pythonOlder "3.8"; @@ -27,7 +27,7 @@ buildPythonPackage rec { owner = "jupyter-server"; repo = "pycrdt-websocket"; rev = "refs/tags/v${version}"; - hash = "sha256-4kxPRPb8XfbQHYmQqnSNfqywUZxQy5b0qWIryLDtP8w="; + hash = "sha256-nkm1ZQ6bYBRDDoREovhEthDZoHApYxzAnwVgDgpWW/s="; }; build-system = [ diff --git a/pkgs/development/python-modules/pymilter/default.nix b/pkgs/development/python-modules/pymilter/default.nix index be0ee9bd2bc45..9cefa458d7e9f 100644 --- a/pkgs/development/python-modules/pymilter/default.nix +++ b/pkgs/development/python-modules/pymilter/default.nix @@ -1,4 +1,15 @@ -{ lib, python, buildPythonPackage, fetchFromGitHub, libmilter, bsddb3, pydns, iana-etc, libredirect }: +{ lib +, python +, buildPythonPackage +, fetchFromGitHub +, fetchpatch +, libmilter +, bsddb3 +, pydns +, iana-etc +, libredirect +, pyasyncore +}: buildPythonPackage rec { pname = "pymilter"; @@ -13,7 +24,14 @@ buildPythonPackage rec { }; buildInputs = [ libmilter ]; + nativeCheckInputs = [ pyasyncore ]; propagatedBuildInputs = [ bsddb3 pydns ]; + patches = [ (fetchpatch { + name = "Remove-calls-to-the-deprecated-method-assertEquals"; + url = "https://github.com/sdgathman/pymilter/pull/57.patch"; + hash = "sha256-/5LlDR15nMR3l7rkVjT3w4FbDTFAAgNdERWlPNL2TVg="; + }) + ]; preBuild = '' sed -i 's/import thread/import _thread as thread/' Milter/greylist.py diff --git a/pkgs/development/python-modules/pynws/default.nix b/pkgs/development/python-modules/pynws/default.nix index 8a28d85b06d42..5e2dc4547e9f8 100644 --- a/pkgs/development/python-modules/pynws/default.nix +++ b/pkgs/development/python-modules/pynws/default.nix @@ -1,31 +1,32 @@ -{ lib -, aiohttp -, buildPythonPackage -, fetchFromGitHub -, freezegun -, metar -, pytest-aiohttp -, pytest-asyncio -, pytest-cov -, pytestCheckHook -, pythonOlder -, setuptools -, setuptools-scm -, tenacity +{ + lib, + aiohttp, + buildPythonPackage, + fetchFromGitHub, + freezegun, + metar, + pytest-aiohttp, + pytest-asyncio, + pytest-cov, + pytestCheckHook, + pythonOlder, + setuptools, + setuptools-scm, + tenacity, }: buildPythonPackage rec { pname = "pynws"; - version = "1.7.0"; + version = "1.8.0"; pyproject = true; - disabled = pythonOlder "3.6"; + disabled = pythonOlder "3.8"; src = fetchFromGitHub { owner = "MatthewFlamm"; - repo = pname; + repo = "pynws"; rev = "refs/tags/v${version}"; - hash = "sha256-JjXGDjLITzJxEmCIv7RPvb+Jqe9hm++ptpJOryuK9M0="; + hash = "sha256-KUCylHYng6mn2TWKf8C7k0IoerM22OIQ7pJMKi5SF3A="; }; build-system = [ @@ -38,9 +39,7 @@ buildPythonPackage rec { metar ]; - optional-dependencies.retry = [ - tenacity - ]; + optional-dependencies.retry = [ tenacity ]; nativeCheckInputs = [ freezegun @@ -55,6 +54,7 @@ buildPythonPackage rec { meta = with lib; { description = "Python library to retrieve data from NWS/NOAA"; homepage = "https://github.com/MatthewFlamm/pynws"; + changelog = "https://github.com/MatthewFlamm/pynws/releases/tag/v${version}"; license = with licenses; [ mit ]; maintainers = with maintainers; [ fab ]; }; diff --git a/pkgs/development/python-modules/pysigma-backend-insightidr/default.nix b/pkgs/development/python-modules/pysigma-backend-insightidr/default.nix index 1fef60d8acf51..c10bf0108761a 100644 --- a/pkgs/development/python-modules/pysigma-backend-insightidr/default.nix +++ b/pkgs/development/python-modules/pysigma-backend-insightidr/default.nix @@ -10,7 +10,7 @@ buildPythonPackage rec { pname = "pysigma-backend-insightidr"; - version = "0.2.2"; + version = "0.2.3"; format = "pyproject"; disabled = pythonOlder "3.8"; @@ -19,7 +19,7 @@ buildPythonPackage rec { owner = "SigmaHQ"; repo = "pySigma-backend-insightidr"; rev = "refs/tags/v${version}"; - hash = "sha256-B42MADteF0+GC/CPJPLaTGdGcQjC8KEsK9u3tBmtObg="; + hash = "sha256-wQMnnJ0KU+53MS3PIBkwIhUiyUdCrDbdUT6upk2Pp/8="; }; nativeBuildInputs = [ diff --git a/pkgs/development/python-modules/pytrydan/default.nix b/pkgs/development/python-modules/pytrydan/default.nix index 2a55b834ebb0b..6ee8162b4ba06 100644 --- a/pkgs/development/python-modules/pytrydan/default.nix +++ b/pkgs/development/python-modules/pytrydan/default.nix @@ -17,7 +17,7 @@ buildPythonPackage rec { pname = "pytrydan"; - version = "0.6.0"; + version = "0.6.1"; pyproject = true; disabled = pythonOlder "3.10"; @@ -26,7 +26,7 @@ buildPythonPackage rec { owner = "dgomes"; repo = "pytrydan"; rev = "refs/tags/v${version}"; - hash = "sha256-+hFwBFYtRseVwesZtSrL3J/ZnsMAjD2ZAhTlk41hfqU="; + hash = "sha256-5sTHfxNV4JEonGke8ZZ/pXoLA15iCuJ/iSW1XwFMltg="; }; postPatch = '' diff --git a/pkgs/development/python-modules/sphinxcontrib-confluencebuilder/default.nix b/pkgs/development/python-modules/sphinxcontrib-confluencebuilder/default.nix index 240e51c724180..4d73733104105 100644 --- a/pkgs/development/python-modules/sphinxcontrib-confluencebuilder/default.nix +++ b/pkgs/development/python-modules/sphinxcontrib-confluencebuilder/default.nix @@ -10,13 +10,13 @@ buildPythonPackage rec { pname = "sphinxcontrib-confluencebuilder"; - version = "2.5.1"; + version = "2.5.2"; format = "pyproject"; src = fetchPypi { pname = "sphinxcontrib_confluencebuilder"; inherit version; - hash = "sha256-PQpkwQ95UVJwDGTAq1xdcSvd07FZpZfA/4jq3ywlMas="; + hash = "sha256-FwjjlMTIhAD/v4Ig+uqrJJybdPqpPG+7OMuJwSqWo84="; }; nativeBuildInputs = [ diff --git a/pkgs/development/python-modules/tencentcloud-sdk-python/default.nix b/pkgs/development/python-modules/tencentcloud-sdk-python/default.nix index 6d99ec33a6539..0244e1b2d54da 100644 --- a/pkgs/development/python-modules/tencentcloud-sdk-python/default.nix +++ b/pkgs/development/python-modules/tencentcloud-sdk-python/default.nix @@ -10,7 +10,7 @@ buildPythonPackage rec { pname = "tencentcloud-sdk-python"; - version = "3.0.1143"; + version = "3.0.1144"; pyproject = true; disabled = pythonOlder "3.9"; @@ -19,7 +19,7 @@ buildPythonPackage rec { owner = "TencentCloud"; repo = "tencentcloud-sdk-python"; rev = "refs/tags/${version}"; - hash = "sha256-xUjOgqhHk1Yj0WNcx051kqRmpg/YqVVvA40ueIDkr10="; + hash = "sha256-cUuigY67wgYj6DvFiplXJbqcLNKxH9odeG5a8bSZ7/8="; }; build-system = [ setuptools ]; diff --git a/pkgs/development/ruby-modules/gem-config/default.nix b/pkgs/development/ruby-modules/gem-config/default.nix index e6018b06fa992..02d014bef8995 100644 --- a/pkgs/development/ruby-modules/gem-config/default.nix +++ b/pkgs/development/ruby-modules/gem-config/default.nix @@ -586,7 +586,7 @@ in ]; }; - nokogiri = attrs: { + nokogiri = attrs: ({ buildFlags = [ "--use-system-libraries" "--with-zlib-lib=${zlib.out}/lib" @@ -601,7 +601,9 @@ in "--with-iconv-dir=${libiconv}" "--with-opt-include=${libiconv}/include" ]; - }; + } // lib.optionalAttrs stdenv.isDarwin { + buildInputs = [ libxml2 ]; + }); openssl = attrs: { # https://github.com/ruby/openssl/issues/369 diff --git a/pkgs/development/tools/analysis/checkov/default.nix b/pkgs/development/tools/analysis/checkov/default.nix index 14c8ff81ae9d1..b1d98d8344880 100644 --- a/pkgs/development/tools/analysis/checkov/default.nix +++ b/pkgs/development/tools/analysis/checkov/default.nix @@ -6,14 +6,14 @@ python3.pkgs.buildPythonApplication rec { pname = "checkov"; - version = "3.2.90"; + version = "3.2.91"; pyproject = true; src = fetchFromGitHub { owner = "bridgecrewio"; repo = "checkov"; rev = "refs/tags/${version}"; - hash = "sha256-I/fSguqs5h4YTP/6whBFYceYd4tC8wrh+PfW8i7+kpY="; + hash = "sha256-lw89E7xD1tSAxgf1bwuPojOikOqIBfisePAtTGHXr4c="; }; patches = [ ./flake8-compat-5.x.patch ]; diff --git a/pkgs/os-specific/darwin/rectangle/default.nix b/pkgs/os-specific/darwin/rectangle/default.nix index ca83bb7db4c00..af7bb58e54a17 100644 --- a/pkgs/os-specific/darwin/rectangle/default.nix +++ b/pkgs/os-specific/darwin/rectangle/default.nix @@ -7,11 +7,11 @@ stdenvNoCC.mkDerivation rec { pname = "rectangle"; - version = "0.77"; + version = "0.79"; src = fetchurl { url = "https://github.com/rxhanson/Rectangle/releases/download/v${version}/Rectangle${version}.dmg"; - hash = "sha256-wViFngw6iwlOG7KsW+zqxjm9ZRJahscX0TYz7/7T5nw="; + hash = "sha256-XczwgLONTt7wL+oW1ruw6wBwZTMd5VyN+79xJy0NUIg="; }; sourceRoot = "."; diff --git a/pkgs/shells/hishtory/default.nix b/pkgs/shells/hishtory/default.nix index f6e7152ae9fcc..5bdf3a1b5992d 100644 --- a/pkgs/shells/hishtory/default.nix +++ b/pkgs/shells/hishtory/default.nix @@ -5,13 +5,13 @@ buildGoModule rec { pname = "hishtory"; - version = "0.293"; + version = "0.294"; src = fetchFromGitHub { owner = "ddworken"; repo = pname; rev = "v${version}"; - hash = "sha256-5I61ey7GJ78dhSgRMkbRcKf3zk0j7zW2MyN2QSbAnpE="; + hash = "sha256-vwjzJTBgD4XzZekxGk02WW9/A7wLlLBsIUyvt0VCkOg="; }; vendorHash = "sha256-zTwZ/sMhQdlf7RYfR2/K/m08U1Il0VQmYFyNNiYsWhc="; diff --git a/pkgs/tools/admin/aliyun-cli/default.nix b/pkgs/tools/admin/aliyun-cli/default.nix index b12f6a782e243..e08a71183b4f6 100644 --- a/pkgs/tools/admin/aliyun-cli/default.nix +++ b/pkgs/tools/admin/aliyun-cli/default.nix @@ -2,17 +2,17 @@ buildGoModule rec { pname = "aliyun-cli"; - version = "3.0.204"; + version = "3.0.205"; src = fetchFromGitHub { rev = "v${version}"; owner = "aliyun"; repo = pname; fetchSubmodules = true; - sha256 = "sha256-Wi35FcuDaTgGG7KgCcU/m6UIKzUuAaMF8J96YvjWgJc="; + sha256 = "sha256-fUInyAJKMvHZ13sWjqWr4KPge/hpeDSkJl69nnWJkPc="; }; - vendorHash = "sha256-AvlDqaJ5w9oDV1slwArr12KA1d3FKVU9H5WK4s3ePtU="; + vendorHash = "sha256-4jGwhcWANYUXuzFjXmFKzMVQXqFtPJt/y3IrjveRNYA="; subPackages = [ "main" ]; diff --git a/pkgs/tools/misc/graylog/5.2.nix b/pkgs/tools/misc/graylog/5.2.nix index e262d84f3f095..1247214e97535 100644 --- a/pkgs/tools/misc/graylog/5.2.nix +++ b/pkgs/tools/misc/graylog/5.2.nix @@ -2,8 +2,8 @@ let buildGraylog = callPackage ./graylog.nix {}; in buildGraylog { - version = "5.2.4"; - sha256 = "sha256-TbZMRMLpYlg6wrsC+tDEk8sLYJ1nwJum/rL30CEGQcw="; + version = "5.2.7"; + sha256 = "sha256-so9IHX0r3dmj5wLrLtQgrcXk+hu6E8/1d7wJu1XDcVA="; maintainers = [ lib.maintainers.f2k1de ]; license = lib.licenses.sspl; } diff --git a/pkgs/tools/misc/ttyplot/default.nix b/pkgs/tools/misc/ttyplot/default.nix index fc53f0b8997ac..64b9b7cddfab7 100644 --- a/pkgs/tools/misc/ttyplot/default.nix +++ b/pkgs/tools/misc/ttyplot/default.nix @@ -2,13 +2,13 @@ stdenv.mkDerivation rec { pname = "ttyplot"; - version = "1.6.2"; + version = "1.6.4"; src = fetchFromGitHub { owner = "tenox7"; repo = "ttyplot"; rev = version; - hash = "sha256-HBJvTDhp1CA96gRU2Q+lMxcFaZ+txXcmNb8Cg1BFiH4="; + hash = "sha256-yxAFqi3TgiKiZYgR891ahkwUqZLk/JDsjujOYmBjUtk="; }; nativeBuildInputs = [ diff --git a/pkgs/tools/security/qdigidoc/default.nix b/pkgs/tools/security/qdigidoc/default.nix index 862249d2ebd10..7e90eca619395 100644 --- a/pkgs/tools/security/qdigidoc/default.nix +++ b/pkgs/tools/security/qdigidoc/default.nix @@ -1,6 +1,7 @@ { lib , mkDerivation , fetchurl +, fetchpatch , cmake , flatbuffers , gettext @@ -17,12 +18,12 @@ mkDerivation rec { pname = "qdigidoc"; - version = "4.4.0"; + version = "4.5.1"; src = fetchurl { url = "https://github.com/open-eid/DigiDoc4-Client/releases/download/v${version}/qdigidoc4-${version}.tar.gz"; - hash = "sha256-5zo0yoY0wufm9DWRIccxJ5g4DXn75nT4fd2h+5QP4oQ="; + hash = "sha256-grhSuexp5yd/s8h5AdmdSLBmQY85l9HKZ15oTTvC6PI="; }; tsl = fetchurl { @@ -30,6 +31,14 @@ mkDerivation rec { sha256 = "1cikz36w9phgczcqnwk4k3mx3kk919wy2327jksmfa4cjfjq4a8d"; }; + patches = [ + # https://github.com/open-eid/DigiDoc4-Client/pull/1251 + (fetchpatch { + url = "https://github.com/open-eid/DigiDoc4-Client/commit/30281d14c5fb5582832eafbc254b56f8d685227d.patch"; + hash = "sha256-nv23NbPUogOhS8No3SMIrAcPChl+d1HkxnePpCKIoUw="; + }) + ]; + nativeBuildInputs = [ cmake gettext pkg-config qttools ]; postPatch = '' @@ -64,6 +73,6 @@ mkDerivation rec { homepage = "https://www.id.ee/"; license = licenses.lgpl21Plus; platforms = platforms.linux; - maintainers = with maintainers; [ mmahut yana ]; + maintainers = with maintainers; [ flokli mmahut yana ]; }; } diff --git a/pkgs/tools/security/web-eid-app/default.nix b/pkgs/tools/security/web-eid-app/default.nix index 067963e1aaffd..e2e09ed0146b3 100644 --- a/pkgs/tools/security/web-eid-app/default.nix +++ b/pkgs/tools/security/web-eid-app/default.nix @@ -10,13 +10,13 @@ mkDerivation rec { pname = "web-eid-app"; - version = "2.4.0"; + version = "2.5.0"; src = fetchFromGitHub { owner = "web-eid"; repo = "web-eid-app"; rev = "v${version}"; - sha256 = "sha256-xWwguxs/121BFF1zhb/HxS9b1vTwQRemhPKOfHEXVZQ="; + sha256 = "sha256-CaMf7cRhZ8K6YAUG38B+ijNOKaOmaACqNabNfHZGT68="; fetchSubmodules = true; }; diff --git a/pkgs/tools/wayland/mpvpaper/default.nix b/pkgs/tools/wayland/mpvpaper/default.nix index a21873be95802..acadb99c2d669 100644 --- a/pkgs/tools/wayland/mpvpaper/default.nix +++ b/pkgs/tools/wayland/mpvpaper/default.nix @@ -16,13 +16,13 @@ stdenv.mkDerivation rec { pname = "mpvpaper"; - version = "1.5"; + version = "1.6"; src = fetchFromGitHub { owner = "GhostNaN"; repo = pname; rev = version; - sha256 = "sha256-TlA2hmpHGe4PWb+Pe3cq2Hhce4NXVI1DnknseGmuFeY="; + sha256 = "sha256-/A2C6T7gP+VGON3Peaz2Y4rNC63UT+zYr4RNM2gdLUY="; }; strictDeps = true; diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 6aab5726b3851..ebfa8ec827ac3 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -4765,8 +4765,6 @@ with pkgs; wtype = callPackage ../tools/wayland/wtype { }; - ydotool = callPackage ../tools/wayland/ydotool { }; - cambalache = callPackage ../development/tools/cambalache { }; cambrinary = python3Packages.callPackage ../applications/misc/cambrinary { }; @@ -40943,8 +40941,6 @@ with pkgs; inherit (darwin.apple_sdk.frameworks) CoreServices; }; - treefmt = callPackage ../development/tools/treefmt { }; - nufmt = callPackage ../development/tools/nufmt { }; bottom = darwin.apple_sdk_11_0.callPackage ../tools/system/bottom { }; |