From 3ed2f2e1a8de72a3c6080f6f6afb979900d6bae0 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 13 Feb 2026 21:12:55 +0100 Subject: [PATCH] add distccd --- modules/dynamicism/distccd.nix | 50 ++++++++++++ modules/dynamicism/dynamicism.nix | 2 +- modules/dynamicism/submodule.nix | 20 ++--- tests/default.nix | 2 +- tests/distccd/configuration.nix | 14 ++++ tests/distccd/test-script.py | 127 ++++++++++++++++++++++++++++++ tests/distccd/test.nix | 23 ++++++ 7 files changed, 227 insertions(+), 11 deletions(-) create mode 100644 modules/dynamicism/distccd.nix create mode 100644 tests/distccd/configuration.nix create mode 100644 tests/distccd/test-script.py create mode 100644 tests/distccd/test.nix diff --git a/modules/dynamicism/distccd.nix b/modules/dynamicism/distccd.nix new file mode 100644 index 0000000..3b87764 --- /dev/null +++ b/modules/dynamicism/distccd.nix @@ -0,0 +1,50 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.services.distccd; + + cliArgs = lib.cli.toCommandLineShellGNU { explicitBool = false; } { + no-detach = true; + daemon = true; + enable-tcp-insecure = true; + port = cfg.port; + + # Nulls are handled automatically. + job-lifetime = cfg.jobTimeout; + log-level = cfg.logLevel; + jobs = cfg.maxJobs; + nice = cfg.nice; + stats = cfg.stats.enable; + stats-port = if cfg.stats.enable then cfg.stats.port else null; + zeroconf = cfg.zeroconf; + allow = cfg.allowedClients; + }; + + startDistccd = pkgs.writeShellApplication { + name = "start-distccd"; + runtimeInputs = [ pkgs.distccMasquerade ]; + + text = '' + ${lib.getExe' cfg.package "distccd"} \ + ${cliArgs} + ''; + }; +in +{ + dynamicism.for.distccd = { + source-options = [ + "services.distccd.jobTimeout" + "services.distccd.logLevel" + "services.distccd.maxJobs" + "services.distccd.nice" + ]; + unitDropins."distccd.service" = pkgs.writeTextFile { + name = "distccd-override.conf"; + text = '' + [Service] + ExecStart= + ExecStart=${lib.getExe startDistccd} + ''; + passthru.startScript = startDistccd; + }; + }; +} diff --git a/modules/dynamicism/dynamicism.nix b/modules/dynamicism/dynamicism.nix index c68b859..482b364 100644 --- a/modules/dynamicism/dynamicism.nix +++ b/modules/dynamicism/dynamicism.nix @@ -138,6 +138,6 @@ in imports = [ ./gotosocial.nix ./harmonia.nix - #./tzupdate.nix + ./distccd.nix ]; } diff --git a/modules/dynamicism/submodule.nix b/modules/dynamicism/submodule.nix index 4fe269c..50e0889 100644 --- a/modules/dynamicism/submodule.nix +++ b/modules/dynamicism/submodule.nix @@ -83,16 +83,18 @@ in doEdits = config.unitDropins |> lib.mapAttrsToList (service: dropin: '' cat "${dropin}" | systemctl edit "${service}" --runtime --drop=dynix-${dropin.name} --stdin - ''); + '') + |> lib.concatStringsSep "\n"; doReloads = config.unitDropins - |> lib.mapAttrsToList (service: _: '' - systemctl reload-or-restart "${service}" - ''); - in [ - doEdits - doReloads - ] |> lib.concatLists - |> lib.concatStringsSep "\n"; + |> lib.mapAttrsToList (service: _: '' + systemctl reload-or-restart "${service}" + '') + |> lib.concatStringsSep "\n"; + in '' + ${doEdits} + + ${doReloads} + ''; }; }; } diff --git a/tests/default.nix b/tests/default.nix index 49e875c..a381346 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -68,5 +68,5 @@ in lib.makeScope lib.callPackageWith (self: { gotosocial = runDynixTest ./gotosocial/test.nix; harmonia = runDynixTest ./harmonia/test.nix; - #tzupdate = runDynixTest ./tzupdate/test.nix; + distccd = runDynixTest ./distccd/test.nix; }) diff --git a/tests/distccd/configuration.nix b/tests/distccd/configuration.nix new file mode 100644 index 0000000..a8fb343 --- /dev/null +++ b/tests/distccd/configuration.nix @@ -0,0 +1,14 @@ +{ ... }: +{ + services.distccd = { + enable = true; + jobTimeout = 900; + maxJobs = 12; + nice = -10; + }; + + dynamicism.for.distccd.enable = true; + + networking.hostName = "distccd-machine"; +} + diff --git a/tests/distccd/test-script.py b/tests/distccd/test-script.py new file mode 100644 index 0000000..9d408af --- /dev/null +++ b/tests/distccd/test-script.py @@ -0,0 +1,127 @@ +import argparse +import functools +#from pathlib import Path +#from pprint import pformat +import shlex +import textwrap +from typing import cast, TYPE_CHECKING + +from beartype import beartype + +from test_driver.machine import Machine +from test_driver.errors import RequestedAssertionFailed + +if TYPE_CHECKING: + global machine + machine = cast(Machine, ...) + assert machine.shell is not None + +ls = "eza -lah --color=always --group-directories-first" + +indent = functools.partial(textwrap.indent, prefix=' ') + +@beartype +def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str: + output = "" + for command in commands: + with machine.nested(f"must succeed: {command}"): + (status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout) + if status != 0: + machine.log(f"output: {out}") + raise RequestedAssertionFailed( + f"command `{command}` failed (exit code {status})", + ) + output += out + + return output + +parser = argparse.ArgumentParser() +#parser.add_argument("--no-detach", action="store_true") +#parser.add_argument("--daemon", action="store_true") +#parser.add_argument("--enable-tcp-insecure", action="store_true") +#parser.add_argument("--port", type=int) +parser.add_argument("--jobs", type=int) +parser.add_argument("--job-lifetime", type=int) +parser.add_argument("--log-level", type=str) +#parser.add_argument("--nice", type=int) +#parser.add_argument("--stats", action="store_true") +#parser.add_argument("--stats-port", type=int) + +@beartype +def get_cli_args() -> argparse.Namespace: + machine.wait_for_unit("distccd.service") + mainpid = int(machine.get_unit_property("distccd.service", "MainPID")) + machine.log(f"{mainpid=}") + pidtext = machine.succeed(f"pgrep -P {mainpid}") + machine.log(f"{pidtext=}") + pid = int(pidtext.splitlines()[0]) + machine.log(f"{pid=}") + execstart = machine.get_unit_property("distccd.service", "ExecStart") + print(f"{execstart=}") + + cmdline = machine.succeed(f"cat /proc/{pid}/cmdline") + cmdline_args = cmdline.split("\0") + machine.log(f"{cmdline_args=}") + print(f"{cmdline_args=}") + + #return shlex.join(cmdline_args[1:]) + args, rest = parser.parse_known_args(cmdline_args) + return args + +machine.wait_for_unit("default.target") +assert "lix" in machine.succeed("nix --version").lower() +machine.log("INIT") + +# Config should have our initial values. +args = get_cli_args() +#assert '--jobs 12' in args, f'--jobs 12 not in {args=}' +assert args.jobs == 12, f'{args.jobs=} != 12' +assert args.job_lifetime == 900, f'{args.job_lifetime} != 900' +assert args.log_level == 'warning', f'{args.log_level=} != warning' + +with machine.nested("must succeed: initial nixos-rebuild switch"): + machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") + +# Config should not have changed. +args = get_cli_args() +#machine.log(f"config.toml after first rebuild: {indent(pformat(args))}") +#assert int(args['workers']) == 4, f"{args['workers']=} != 4" +#assert int(args['max_connection_rate']) == 256, f"{args['max_connection_rate']=} != 256" +# +new_jobs = 4 +expr = textwrap.dedent(f""" + let + nixos = import {{ }}; + in nixos.config.dynamicism.doChange {{ + option = "services.distccd.maxJobs"; + value = {new_jobs}; + }} +""").strip() +machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} +""".strip()) + +args = get_cli_args() + +# Only jobs should have changed. The others should still be default. +assert args.jobs == new_jobs, f'{args.jobs=} != {new_jobs=}' +assert args.job_lifetime == 900, f'{args.job_lifetime} != 900' +assert args.log_level == 'warning', f'{args.log_level=} != warning' + +new_log_level = 'error' +expr = textwrap.dedent(f""" + let + nixos = import {{ }}; + in nixos.config.dynamicism.doChange {{ + option = "services.distccd.logLevel"; + value = "{new_log_level}"; + }} +""").strip() +machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} +""".strip()) + +args = get_cli_args() +#assert args.jobs == new_jobs, f'{args.jobs=} != {new_jobs=}' +#assert args.job_lifetime == 900, f'{args.job_lifetime} != 900' +assert args.log_level == new_log_level, f'{args.log_level=} != {new_log_level=}' diff --git a/tests/distccd/test.nix b/tests/distccd/test.nix new file mode 100644 index 0000000..2e8089f --- /dev/null +++ b/tests/distccd/test.nix @@ -0,0 +1,23 @@ +{ mkDynixConfigurationDotNix, config, ... }: +{ + name = "nixos-test-dynamicism-distccd"; + + defaults = { ... }: { }; + + extraPythonPackages = p: [ p.beartype ]; + + nodes.machine = { name, pkgs, ... }: { + imports = [ ./configuration.nix ]; + + environment.systemPackages = let + configFileTree = mkDynixConfigurationDotNix { + inherit (config) name; + configuration = ./configuration.nix; + }; + in [ + configFileTree + ]; + }; + + testScript = builtins.readFile ./test-script.py; +}