From 352b240ab76aeb9d77566d85c0b92d2465856414 Mon Sep 17 00:00:00 2001 From: Dev380 <49997896+Dev380@users.noreply.github.com> Date: Tue, 20 May 2025 01:38:47 -0400 Subject: [PATCH 1/3] maintainers: add Dev380 --- maintainers/maintainer-list.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index d3ca955a9840..0685e0bdb975 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -6476,6 +6476,11 @@ } ]; }; + Dev380 = { + name = "Dev380"; + github = "Dev380"; + githubId = 49997896; + }; developer-guy = { name = "Batuhan Apaydın"; email = "developerguyn@gmail.com"; From 2a3d3814cfaa486bb70b10060c828cba97749074 Mon Sep 17 00:00:00 2001 From: Dev380 <49997896+Dev380@users.noreply.github.com> Date: Tue, 20 May 2025 01:39:57 -0400 Subject: [PATCH 2/3] nohang: init at 0.2.0 nohang: use installFlags instead of manually running make Co-authored-by: Leah Amelia Chen nohang: fix substituteInPlace not substituting src/* was too broad so --replace-fail made it fail since only the main python file actually needs substituting nohang: substitute "env" program too nixosTests.nohang: only build test for linux Co-authored-by: Leah Amelia Chen --- pkgs/by-name/no/nohang/package.nix | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 pkgs/by-name/no/nohang/package.nix diff --git a/pkgs/by-name/no/nohang/package.nix b/pkgs/by-name/no/nohang/package.nix new file mode 100644 index 000000000000..08c6a2e99ccf --- /dev/null +++ b/pkgs/by-name/no/nohang/package.nix @@ -0,0 +1,50 @@ +{ + lib, + stdenv, + fetchFromGitHub, + python3, + sudo, + libnotify, + coreutils, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "nohang"; + version = "0.2.0"; + + src = fetchFromGitHub { + owner = "hakavlad"; + repo = "nohang"; + tag = "v${finalAttrs.version}"; + hash = "sha256-aRiBGnJHdtQ5euvJ4DSuCZO5i5OJVRiCesaatrIARmg="; + }; + + postPatch = '' + patchShebangs src + substituteInPlace src/nohang \ + --replace-fail 'notify-send' '${lib.getExe libnotify}' \ + --replace-fail 'sudo' '${lib.getExe sudo}' \ + --replace-fail "'env'" "'${lib.getExe' coreutils "env"}'" + ''; + + buildInputs = [ python3 ]; + + dontBuild = true; + + installTargets = [ "base" ]; + installFlags = [ + "DESTDIR=${placeholder "out"}" + "PREFIX=/" + "SBINDIR=/sbin" + "SYSCONFDIR=/etc" + "SYSTEMDUNITDIR=/lib/systemd/system" + ]; + + meta = { + homepage = "https://github.com/hakavlad/nohang"; + description = "Sophisticated low memory handler for Linux"; + license = with lib.licenses; [ mit ]; + mainProgram = "nohang"; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ Dev380 ]; + }; +}) From 12c0a7fc520a5f91f147e62fbae4acc5544639c1 Mon Sep 17 00:00:00 2001 From: Dev380 <49997896+Dev380@users.noreply.github.com> Date: Tue, 20 May 2025 01:40:57 -0400 Subject: [PATCH 3/3] nixos/nohang: init module nixosTests.nohang: init These tests are modified from earlyoom's tests. It tries to OOM the test VM and then check that nohang's output is as expected. Unlike the earlyoom test, we use the default config, so we test for whether the signal nohang set was SIGKILL or SIGTERM and adjust the output checking accordingly nohang: add linked test for the nixos module nixos/doc/rl-2511: init nohang nixos/nohang: change option name to configPath nixos/nohang: use lib.getExe instead of interpolation Co-authored-by: Leah Amelia Chen nixos/nohang: use lib.getExe "tail" instead Co-authored-by: Leah Amelia Chen nixosTests.nohang: switch to runTest nixos/nohang: use multiline string for CapabilityBoundingSet in service config Co-authored-by: Sandro nohang: fix whitespace formatting --- .../manual/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/system/nohang.nix | 113 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/nohang.nix | 56 +++++++++ pkgs/by-name/no/nohang/package.nix | 5 + 6 files changed, 178 insertions(+) create mode 100644 nixos/modules/services/system/nohang.nix create mode 100644 nixos/tests/nohang.nix diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 63095c936728..91ef22883b4f 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -22,6 +22,8 @@ - [LibreChat](https://www.librechat.ai/), open-source self-hostable ChatGPT clone with Agents and RAG APIs. Available as [services.librechat](#opt-services.librechat.enable). +- [nohang](https://github.com/hakavlad/nohang), a daemon for Linux that prevents out of memory (OOM) situations from affecting system responsiveness. Available as [services.nohang](#opt-services.nohang.enable) + - [DankMaterialShell](https://danklinux.com), a complete desktop shell for Wayland compositors built with Quickshell. Available as [programs.dms-shell](#opt-programs.dms-shell.enable). - [dms-greeter](https://danklinux.com), a modern display manager greeter for DankMaterialShell that works with greetd and supports multiple Wayland compositors. Available as [services.displayManager.dms-greeter](#opt-services.displayManager.dms-greeter.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index ed876450bac2..da1a3e66a159 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1530,6 +1530,7 @@ ./services/system/localtimed.nix ./services/system/nix-daemon-firewall.nix ./services/system/nix-daemon.nix + ./services/system/nohang.nix ./services/system/nscd.nix ./services/system/nvme-rs.nix ./services/system/saslauthd.nix diff --git a/nixos/modules/services/system/nohang.nix b/nixos/modules/services/system/nohang.nix new file mode 100644 index 000000000000..abc8d00f90e7 --- /dev/null +++ b/nixos/modules/services/system/nohang.nix @@ -0,0 +1,113 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.nohang; + + inherit (lib) + literalExpression + mkEnableOption + mkIf + mkOption + mkPackageOption + types + ; +in +{ + meta = { + maintainers = with lib.maintainers; [ Dev380 ]; + }; + + options.services.nohang = { + enable = mkEnableOption "nohang, a daemon that keeps system responsiveness when Linux is out of memory"; + + package = mkPackageOption pkgs "nohang" { }; + + configPath = mkOption { + type = types.either (types.enum [ + "basic" + "desktop" + ]) types.path; + default = "desktop"; + example = literalExpression "./my-nohang-config.conf"; + description = '' + Configuration file to use with nohang. The default and desktop example configurations in the nohang repository + can be used by setting this to "basic" or "desktop" (which is the default). Otherwise, you can set it to the path + of a custom configuration file. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.nohang = { + description = "Sophisticated low memory handler"; + documentation = [ + "man:nohang(8)" + "https://github.com/hakavlad/nohang" + ]; + after = [ "sysinit.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = + "${lib.getExe cfg.package} --monitor --config " + + ( + if cfg.configPath == "basic" then + "${cfg.package}/etc/nohang/nohang.conf" + else if cfg.configPath == "desktop" then + "${cfg.package}/etc/nohang/nohang-desktop.conf" + else + cfg.configPath + ); + Slice = "hostcritical.slice"; + SyslogIdentifier = + if cfg.configPath == "basic" then + "nohang" + else if cfg.configPath == "desktop" then + "nohang-desktop" + else + "nohang-custom-config"; + KillMode = "mixed"; + Restart = "always"; + RestartSec = 0; + + CPUSchedulingResetOnFork = true; + RestrictRealtime = "yes"; + + TasksMax = 25; + MemoryMax = "100M"; + MemorySwapMax = "100M"; + + UMask = 27; + ProtectSystem = "strict"; + ReadWritePaths = "/var/log"; + InaccessiblePaths = "/home /root"; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + ProtectHostname = true; + MemoryDenyWriteExecute = "yes"; + RestrictNamespaces = "yes"; + LockPersonality = "yes"; + PrivateTmp = true; + DeviceAllow = "/dev/kmsg rw"; + DevicePolicy = "closed"; + + CapabilityBoundingSet = [ + "CAP_KILL" + "CAP_IPC_LOCK" + "CAP_SYS_PTRACE" + "CAP_DAC_READ_SEARCH" + "CAP_DAC_OVERRIDE" + "CAP_AUDIT_WRITE" + "CAP_SETUID" + "CAP_SETGID" + "CAP_SYS_RESOURCE" + "CAP_SYSLOG" + ]; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 0cc6942f10db..36cd4a2ab0ff 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1105,6 +1105,7 @@ in nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; }; nixseparatedebuginfod2 = runTest ./nixseparatedebuginfod2.nix; node-red = runTest ./node-red.nix; + nohang = runTest ./nohang.nix; nomad = runTest ./nomad.nix; nominatim = runTest ./nominatim.nix; non-default-filesystems = handleTest ./non-default-filesystems.nix { }; diff --git a/nixos/tests/nohang.nix b/nixos/tests/nohang.nix new file mode 100644 index 000000000000..8b7c4ec0ca15 --- /dev/null +++ b/nixos/tests/nohang.nix @@ -0,0 +1,56 @@ +# The following was modified from ./earlyoom.nix +{ lib, pkgs, ... }: +{ + name = "nohang"; + meta = { + maintainers = with lib.maintainers; [ + Dev380 + ]; + }; + + nodes.machine = + { pkgs, ... }: + { + # Limit VM resource usage. + virtualisation.memorySize = 1024; + + services.nohang.enable = true; + # disable other oom killers just in case + systemd.oomd.enable = false; + + systemd.services.testbloat = { + description = "Create a lot of memory pressure"; + serviceConfig = { + ExecStart = "${lib.getExe' pkgs.coreutils "tail"} /dev/zero"; + }; + }; + }; + + # SIGTERM may be given so tail /dev/zero may or may not succeed + # The output will have have something like "Sending SIGTERM to /nix/store/87fc" + # with the truncated path so we'll check for that in the test + testScript = '' + machine.wait_for_unit("nohang.service") + + with subtest("nohang should kill the bad service"): + machine.execute("systemctl start --wait testbloat.service") + signal_type = None + match machine.get_unit_info("testbloat.service")["Result"]: + case "signal": + signal_type = "SIGKILL" + case "success": + signal_type = "SIGTERM" + output = machine.succeed('journalctl -u nohang.service -b0') + + if not f'[ OK ] Sending {signal_type}' in output: + raise Exception(f"'[ OK ] Sending {signal_type}' not in output") + if not 'The victim' in output: + raise Exception("'The victim' not in output") + if not 'Memory status after implementing a corrective action:' in output: + raise Exception("'Memory status after implementing a corrective action:' not in output") + if not 'FINISHING implement_corrective_action()' in output: + raise Exception("'FINISHING implement_corrective_action()' not in output") + if not f"{signal_type} to {'${pkgs.coreutils}'[:len('/nix/store/1234')]}: 1" in output: + raise Exception(f"'{signal_type} to {'${pkgs.coreutils}'[:len('/nix/store/1234')]}: 1' not in output") + ''; +} diff --git a/pkgs/by-name/no/nohang/package.nix b/pkgs/by-name/no/nohang/package.nix index 08c6a2e99ccf..846a644bd6ab 100644 --- a/pkgs/by-name/no/nohang/package.nix +++ b/pkgs/by-name/no/nohang/package.nix @@ -1,6 +1,7 @@ { lib, stdenv, + nixosTests, fetchFromGitHub, python3, sudo, @@ -39,6 +40,10 @@ stdenv.mkDerivation (finalAttrs: { "SYSTEMDUNITDIR=/lib/systemd/system" ]; + passthru.tests = { + inherit (nixosTests) nohang; + }; + meta = { homepage = "https://github.com/hakavlad/nohang"; description = "Sophisticated low memory handler for Linux";