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 <github@acc.pluie.me>

nixos/nohang: use lib.getExe "tail" instead

Co-authored-by: Leah Amelia Chen <github@acc.pluie.me>

nixosTests.nohang: switch to runTest

nixos/nohang: use multiline string for CapabilityBoundingSet in service config

Co-authored-by: Sandro <sandro.jaeckel@gmail.com>

nohang: fix whitespace formatting
This commit is contained in:
Dev380
2025-05-20 01:40:57 -04:00
committed by Doron Behar
parent 2a3d3814cf
commit 12c0a7fc52
6 changed files with 178 additions and 0 deletions

View File

@@ -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).

View File

@@ -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

View File

@@ -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"
];
};
};
};
}

View File

@@ -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 { };

56
nixos/tests/nohang.nix Normal file
View File

@@ -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")
'';
}

View File

@@ -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";