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