From 45a7d43f772efc04f847a7fd48ad02874251dc88 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 9 Feb 2026 14:17:27 +0100 Subject: [PATCH 01/18] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/ed142ab1b3a092c4d149245d0c4126a5d7ea00b0' (2026-01-20) → 'github:NixOS/nixpkgs/fef9403a3e4d31b0a23f0bacebbec52c248fbb51' (2026-02-08) • Updated input 'qyriad-nur': 'github:Qyriad/nur-packages/c08309f918a0528ceb23659c0cc4a3c901fe8afa' (2026-01-02) → 'github:Qyriad/nur-packages/23716e0347215a721f9489515a0c3dc91122c7d5' (2026-02-06) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 9998eff..68f5005 100644 --- a/flake.lock +++ b/flake.lock @@ -21,11 +21,11 @@ "nixpkgs": { "flake": false, "locked": { - "lastModified": 1768875095, - "narHash": "sha256-dYP3DjiL7oIiiq3H65tGIXXIT1Waiadmv93JS0sS+8A=", + "lastModified": 1770537093, + "narHash": "sha256-pF1quXG5wsgtyuPOHcLfYg/ft/QMr8NnX0i6tW2187s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ed142ab1b3a092c4d149245d0c4126a5d7ea00b0", + "rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51", "type": "github" }, "original": { @@ -38,11 +38,11 @@ "qyriad-nur": { "flake": false, "locked": { - "lastModified": 1767357308, - "narHash": "sha256-PWDIBupTHASnzwPUuafIhwBCFKxjsSVj4QRAdX5y/z4=", + "lastModified": 1770385314, + "narHash": "sha256-zDlvon/yF9STxGi3l38j9EgTFHBHOjCJlP8mMX7zw5M=", "owner": "Qyriad", "repo": "nur-packages", - "rev": "c08309f918a0528ceb23659c0cc4a3c901fe8afa", + "rev": "23716e0347215a721f9489515a0c3dc91122c7d5", "type": "github" }, "original": { From 68e9b9a1e4221f9217e3b12d5d782988b2ebce56 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 9 Feb 2026 14:32:56 +0100 Subject: [PATCH 02/18] significantly improve purity --- default.nix | 64 +--------------------- modules-package.nix | 61 +++++++++++++++++++++ modules/tests-common.nix | 4 -- modules/tests-main.py | 76 -------------------------- modules/tests.nix | 52 ------------------ shell.nix | 15 +++-- tests/basic/configuration-package.nix | 2 +- tests/basic/configuration.nix | 24 ++++---- tests/basic/hardware-configuration.nix | 7 +++ tests/basic/test-script.py | 12 +++- tests/basic/test.nix | 19 ++++--- tests/default.nix | 33 +++++++++-- 12 files changed, 139 insertions(+), 230 deletions(-) create mode 100644 modules-package.nix delete mode 100644 modules/tests-common.nix delete mode 100644 modules/tests-main.py delete mode 100644 modules/tests.nix create mode 100644 tests/basic/hardware-configuration.nix diff --git a/default.nix b/default.nix index b7a3bef..bdfa4ef 100644 --- a/default.nix +++ b/default.nix @@ -5,71 +5,13 @@ in import src { inherit pkgs; }, }: let inherit (qpkgs) lib; - - dynix = qpkgs.callPackage ./package.nix { }; - + dynix = qpkgs.callPackage ./modules-package.nix { }; byStdenv = lib.mapAttrs (stdenvName: stdenv: let withStdenv = dynix.override { inherit stdenv; }; dynix' = withStdenv.overrideAttrs (prev: { pname = "${prev.pname}-${stdenvName}"; }); in dynix') qpkgs.validStdenvs; - - evalNixos = import (pkgs.path + "/nixos"); - - doChange = { - option, - value, - }: - assert lib.isList option; - assert lib.all lib.isString option; - let - nixosBefore = evalNixos { - configuration = ./configuration.nix; - }; - dynamicBefore = nixosBefore.config.dynamicism.finalSettings; - - nixosAfter = evalNixos { - configuration = { ... }: { - imports = [ - ./configuration.nix - (lib.setAttrByPath option (lib.mkOverride (-999) value)) - ]; - }; - }; - - withActivationScripts = evalNixos { - configuration = ({ ... }: { - imports = [ ./configuration.nix ]; - config.environment.systemPackages = [ nixosAfter.config.dynamicism.for.gotosocial.activate ]; - }); - }; - in { - inherit nixosBefore nixosAfter withActivationScripts; - }; - -in dynix.overrideAttrs (final: prev: let - self = final.finalPackage; -in lib.recursiveUpdate prev { - passthru = { - ts = let - scope = pkgs.callPackage ./modules/tests.nix { }; - in scope.packages scope; - - dync = self.nixos.config.dynamicism; - dyno = self.nixos.options.dynamicism; - gotosocial = self.nixos.options.dynamicism.for.valueMeta.attrs.gotosocial.configuration; - - inherit byStdenv; - nixos = evalNixos { - configuration = ./configuration.nix; - }; - c = self.nixos; - nixos-vm = self.nixos.config.system.build.vm; - doChange = builtins.seq self.nixos.config.dynamicism doChange; - withVox = self.doChange { - option = lib.splitString "." "services.gotosocial.settings.application-name"; - value = "Vox is an asshole"; - }; - }; +in dynix.overrideAttrs (prev: lib.recursiveUpdate prev { + passthru = { inherit byStdenv; }; }) diff --git a/modules-package.nix b/modules-package.nix new file mode 100644 index 0000000..b36b809 --- /dev/null +++ b/modules-package.nix @@ -0,0 +1,61 @@ +{ + lib, + stdenvNoCC, + callPackage, +}: let + stdenv = stdenvNoCC; +in stdenv.mkDerivation (self: { + name = "dynix-modules"; + + strictDeps = true; + __structuredAttrs = true; + + outputs = [ "out" "modules" ]; + + src = lib.fileset.toSource { + root = ./modules/dynamicism; + fileset = lib.fileset.unions [ + ./modules/dynamicism + ]; + }; + + phases = [ "unpackPhase" "patchPhase" "installPhase "]; + + installPhase = lib.dedent '' + mkdir -vp "$out" + cp -rv * "$out/" + + #mkdir -vp "$modules/share/nix/modules/dynix" + #cp --reflink=auto -rv * "$modules/share/nix/modules/dynix/" + + mkdir -vp "$modules/share/nixos/modules/dynix" + cp --reflink=auto -rv * "$modules/share/nixos/modules/dynix/" + ''; + + passthru.mkDevShell = { + path, + mkShell, + python3Packages, + }: let + mkShell' = mkShell.override { inherit stdenv; }; + pyEnv = python3Packages.python.withPackages (p: [ + p.beartype + ]); + in mkShell' { + name = "devshell-for-${self.finalPackage.name}"; + packages = [ pyEnv ]; + env.PYTHONPATH = [ + "${pyEnv}/${pyEnv.sitePackages}" + # Cursed. + "${path}/nixos/lib/test-driver/src" + ] |> lib.concatStringsSep ":"; + }; + + passthru.tests = lib.fix (callPackage ./tests { + dynix = self.finalPackage; + }).packages; + + meta = { + outputsToInstall = [ "modules" ]; + }; +}) diff --git a/modules/tests-common.nix b/modules/tests-common.nix deleted file mode 100644 index 322771e..0000000 --- a/modules/tests-common.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ pkgs, ... }: -{ - nix.package = pkgs.lixPackageSets.latest.lix; -} diff --git a/modules/tests-main.py b/modules/tests-main.py deleted file mode 100644 index dbd33a6..0000000 --- a/modules/tests-main.py +++ /dev/null @@ -1,76 +0,0 @@ -#import re -from pathlib import Path -from typing import cast, TYPE_CHECKING - -from test_driver.machine import Machine -from test_driver.errors import RequestedAssertionFailed - -DEFAULT_NIX = "@DEFAULT_NIX@" -CONFIGURATION_NIX = "@CONFIGURATION_NIX@" -DYNAMICISM = "@DYNAMICISM@" - -if TYPE_CHECKING: - global machine - machine = cast(Machine, ...) - -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 - -def get_config_file() -> Path: - machine.wait_for_unit("gotosocial.service") - gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID")) - print(f"{gotosocial_pid=}") - - cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline") - cmdline_args = cmdline.split("\0") - - config_file_idx = cmdline_args.index("--config-path") + 1 - config_file = Path(cmdline_args[config_file_idx]) - - machine.log(f"copying from VM: {config_file=}") - machine.copy_from_vm(config_file.as_posix()) - - return machine.out_dir / config_file.name - - -machine.wait_for_unit("default.target") -assert "lix" in run_log(machine, "nix --version").lower() - -print(f"{CONFIGURATION_NIX=}") -machine.succeed("mkdir -vp /etc/nixos") -machine.copy_from_host(CONFIGURATION_NIX, "/etc/nixos") -machine.copy_from_host(DYNAMICISM, "/etc/nixos") - -run_log(machine, f"nix build --log-format multiline-with-logs --impure -E 'import {{ configuration = {CONFIGURATION_NIX}; }}'") -run_log(machine, f"nixos-rebuild switch --file {CONFIGURATION_NIX} --verbose --print-build-logs") - -config_file_local = get_config_file() -machine.log(f"opening copied file: {config_file_local=}") -with open(config_file_local, "r") as f: - text = f.read() - lines = text.splitlines() - application_name = next(line for line in lines if line.startswith("application-name:")) - assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}" - -print(f"{DEFAULT_NIX=}") - -run_log(machine, "eza -lah --color=always --group-directories-first --tree /etc/") - -#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value") -#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value") - -#service_text = machine.succeed("systemctl show gotosocial.service") -#service_props = dict(line.split("=", maxsplit=1) for line in service_text.splitlines()) -#exec_start = service_props['ExecStart'] -#print(f"{exec_start=}") diff --git a/modules/tests.nix b/modules/tests.nix deleted file mode 100644 index 917c7bd..0000000 --- a/modules/tests.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - pkgs, - lib, - testers, -}: - let - inherit (testers) runNixOSTest; -in lib.makeScope lib.callPackageWith (self: { - main = runNixOSTest { - name = "nixos-test-dynamicism-main"; - - defaults = { pkgs, ... }: { - imports = [ ./dynamicism ]; - - nix = { - package = pkgs.lixPackageSets.latest.lix; - settings.experimental-features = [ "nix-command" ]; - nixPath = [ "nixpkgs=${pkgs.path}" ]; - }; - - environment.shellAliases = { - ls = "eza --long --header --group --group-directories-first --classify --binary"; - }; - environment.systemPackages = with pkgs; [ - eza - fd - ripgrep - ]; - }; - - nodes.machine = { name, ... }: { - #services.gotosocial = { - # enable = true; - # setupPostgresqlDB = true; - # settings = { - # application-name = "gotosocial-for-${name}"; - # host = "${name}.local"; - # }; - #}; - # - #dynamicism.for.gotosocial.enable = true; - }; - - # What's a little IFD between friends? - testScript = pkgs.replaceVars ./tests-main.py { - DEFAULT_NIX = ../default.nix; - CONFIGURATION_NIX = ./tests-configuration.nix; - DYNAMICISM = ./dynamicism; - } - |> builtins.readFile; - }; -}) diff --git a/shell.nix b/shell.nix index c73c766..3506e2d 100644 --- a/shell.nix +++ b/shell.nix @@ -1,5 +1,10 @@ { - pkgs ? import { }, + pkgs ? import { + config = { + checkMeta = true; + allowAliases = false; + }; + }, qpkgs ? let src = fetchTarball "https://github.com/Qyriad/nur-packages/archive/main.tar.gz"; in import src { inherit pkgs; }, @@ -12,12 +17,6 @@ byStdenv = lib.mapAttrs (lib.const mkDevShell) dynix.byStdenv; -in devShell.overrideAttrs (prev: lib.recursiveUpdate prev { +in devShell.overrideAttrs (prev: { passthru = { inherit byStdenv; }; - env.PYTHONPATH = [ - "${pkgs.python3Packages.beartype}/${pkgs.python3.sitePackages}" - ] |> lib.concatStringsSep ":"; - packages = prev.packages or [ ] ++ [ - pkgs.python3Packages.beartype - ]; }) diff --git a/tests/basic/configuration-package.nix b/tests/basic/configuration-package.nix index 4b50af6..e6bb010 100644 --- a/tests/basic/configuration-package.nix +++ b/tests/basic/configuration-package.nix @@ -5,5 +5,5 @@ set -euo pipefail mkdir -vp "$out/share/nixos" cp -rv ${./configuration.nix} "$out/share/nixos/configuration.nix" - cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism" + #cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism" '' diff --git a/tests/basic/configuration.nix b/tests/basic/configuration.nix index 06470e3..bc751ed 100644 --- a/tests/basic/configuration.nix +++ b/tests/basic/configuration.nix @@ -1,22 +1,18 @@ { pkgs, lib, config, modulesPath, ... }: let name = config.networking.hostName; - nixosLibPath = (modulesPath + "/../lib"); moduleList = import (modulesPath + "/module-list.nix"); - optionalPath = p: lib.optional (builtins.pathExists p) p; + dynixFromSearchPath = let + res = builtins.tryEval ; + in lib.optional res.success res.value; in -assert builtins.pathExists nixosLibPath; -builtins.seq lib -builtins.seq modulesPath -builtins.seq moduleList { imports = moduleList ++ [ - (modulesPath + "/testing/test-instrumentation.nix") + "${modulesPath}/testing/test-instrumentation.nix" + ./hardware-configuration.nix ] ++ lib.concatLists [ - (optionalPath ./hardware-configuration.nix) - (optionalPath ./dynamicism) - (optionalPath ../../modules/dynamicism) + dynixFromSearchPath ]; system.switch.enable = true; @@ -32,13 +28,18 @@ builtins.seq moduleList nix = { package = pkgs.lixPackageSets.latest.lix; - nixPath = [ "nixpkgs=${pkgs.path}" ]; + nixPath = [ + "nixpkgs=${pkgs.path}" + "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" + ]; settings = { experimental-features = [ "nix-command" "pipe-operator" ]; substituters = lib.mkForce [ ]; hashed-mirrors = null; connect-timeout = 1; + # For my debugging purposes. + show-trace = true; }; }; @@ -54,6 +55,7 @@ builtins.seq moduleList dynamicism.for.gotosocial.enable = true; environment.pathsToLink = [ "/share" ]; + environment.extraOutputsToInstall = [ "modules" ]; environment.variables = { "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; }; diff --git a/tests/basic/hardware-configuration.nix b/tests/basic/hardware-configuration.nix new file mode 100644 index 0000000..f036a72 --- /dev/null +++ b/tests/basic/hardware-configuration.nix @@ -0,0 +1,7 @@ +/** Dummy hardware configuration. + * Will be replaced with the real one in the test VM. + */ +{ ... }: +{ + +} diff --git a/tests/basic/test-script.py b/tests/basic/test-script.py index f507503..03974fc 100644 --- a/tests/basic/test-script.py +++ b/tests/basic/test-script.py @@ -52,11 +52,17 @@ def get_config_file() -> str: machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() +machine.log("INIT") -run_log(machine, "nixos-generate-config") +machine.succeed("nix profile install -vv $(realpath /run/current-system/sw/share/nixos/modules/dynix)") + +machine.succeed("nixos-generate-config") machine.succeed("mkdir -vp /etc/nixos") -machine.succeed("cp -rv /run/current-system/sw/share/nixos/* /etc/nixos/") -machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs -v --fallback >&2") +# Dereference is required since that configuration.nix is probably a symlink to the store. +machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/") + +machine.log("REBUILDING configuration inside VM") +machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") config_text = get_config_file() lines = config_text.splitlines() diff --git a/tests/basic/test.nix b/tests/basic/test.nix index 61110ad..4356685 100644 --- a/tests/basic/test.nix +++ b/tests/basic/test.nix @@ -1,22 +1,23 @@ -{ - pkgs, - lib, - config, - ... -}: +{ dynix, ... }: + { name = "nixos-test-dynamicism-main"; defaults = { ... }: { }; - #node.pkgsReadOnly = false; - extraPythonPackages = p: [ p.beartype ]; nodes.machine = { pkgs, config, ... }: { - imports = [ ./configuration.nix ]; + # NOTE: Anything in this `nodes.machine = ` module will not be included + # in the VM's NixOS configuration once it does `nixos-rebuild switch`, + # except for `./configuration.nix` which will be copied to `/etc/nixos/`. + # dynix will also be statefully installed to root's user profile. + imports = [ + ./configuration.nix + (toString dynix) + ]; system.includeBuildDependencies = true; system.switch.enable = true; diff --git a/tests/default.nix b/tests/default.nix index bc4beef..d17c3ac 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,8 +1,31 @@ { pkgs ? import { }, - lib ? pkgs.lib, -}: lib.makeScope lib.callPackageWith (self: let - inherit (pkgs.testers) runNixOSTest; -in { - basic = runNixOSTest ./basic/test.nix; + qpkgs ? let + src = fetchTree (builtins.parseFlakeRef "github:Qyriad/nur-packages"); + in import src { inherit pkgs; }, + lib ? qpkgs.lib, + dynix ? qpkgs.callPackage ../modules-package.nix { }, +}: let + + runDynixTest = testModule: pkgs.testers.runNixOSTest { + imports = [ testModule ]; + + # Note: these are arguments to the *test* modules. + # Not the NixOS modules for the NixOS configuration the test is testing. + # Wew. + _module.args = { inherit dynix; }; + + # Why is this argument called "extraBaseModule**s**" but take a single module argument... + extraBaseModules = { name, ... }: { + #imports = [ dynixInjectionModule ]; + config.environment.systemPackages = [ dynix ]; + # Just making something in this strict in `name`, + # which is only present as an argument for nodes and I don't want to + # confuse that with the test modules. + config.warnings = builtins.seq name [ ]; + }; + }; + +in lib.makeScope lib.callPackageWith (self: { + basic = runDynixTest ./basic/test.nix; }) From 1f466b63d337a10b9009ff3c93466c94fd083fea Mon Sep 17 00:00:00 2001 From: Qyriad Date: Tue, 10 Feb 2026 14:19:55 +0100 Subject: [PATCH 03/18] tests.basic -> tests.gotosocial --- modules/dynamicism/default.nix | 18 +++--------------- modules/dynamicism/gotosocial.nix | 19 +++++++++++++++++++ tests/default.nix | 2 +- .../configuration-package.nix | 0 tests/{basic => gotosocial}/configuration.nix | 0 tests/{basic => gotosocial}/default.nix | 0 .../hardware-configuration.nix | 0 tests/{basic => gotosocial}/test-script.py | 0 tests/{basic => gotosocial}/test.nix | 0 9 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 modules/dynamicism/gotosocial.nix rename tests/{basic => gotosocial}/configuration-package.nix (100%) rename tests/{basic => gotosocial}/configuration.nix (100%) rename tests/{basic => gotosocial}/default.nix (100%) rename tests/{basic => gotosocial}/hardware-configuration.nix (100%) rename tests/{basic => gotosocial}/test-script.py (100%) rename tests/{basic => gotosocial}/test.nix (100%) diff --git a/modules/dynamicism/default.nix b/modules/dynamicism/default.nix index 8e4764c..dae5bd6 100644 --- a/modules/dynamicism/default.nix +++ b/modules/dynamicism/default.nix @@ -120,19 +120,7 @@ in ); # Implementations. - config.dynamicism.for.gotosocial = let - cfg = config.dynamicism.for.gotosocial; - in { - source-options = [ - "services.gotosocial.settings" - ]; - - configFile = settingsFormat.generate "gotosocial-override.yml" config.services.gotosocial.settings; - - unitDropins."gotosocial.service" = pkgs.writeText "gotosocial-override.conf" '' - [Service] - ExecStart= - ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${cfg.configFile} start - ''; - }; + imports = [ + ./gotosocial.nix + ]; } diff --git a/modules/dynamicism/gotosocial.nix b/modules/dynamicism/gotosocial.nix new file mode 100644 index 0000000..d52e68c --- /dev/null +++ b/modules/dynamicism/gotosocial.nix @@ -0,0 +1,19 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.dynamicism.for.gotosocial; + + settingsFormat = pkgs.formats.yaml { }; +in +{ + dynamicism.for.gotosocial = { + source-options = [ "services.gotosocial.settings" ]; + + configFile = settingsFormat.generate "gotosocial-overrde.yml" config.services.gotosocial.settings; + + unitDropins."gotosocial.service" = pkgs.writeText "gotosocial-override.conf" '' + [Service] + ExecStart= + ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${cfg.configFile} start + ''; + }; +} diff --git a/tests/default.nix b/tests/default.nix index d17c3ac..a6aea2d 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -27,5 +27,5 @@ }; in lib.makeScope lib.callPackageWith (self: { - basic = runDynixTest ./basic/test.nix; + gotosocial = runDynixTest ./gotosocial/test.nix; }) diff --git a/tests/basic/configuration-package.nix b/tests/gotosocial/configuration-package.nix similarity index 100% rename from tests/basic/configuration-package.nix rename to tests/gotosocial/configuration-package.nix diff --git a/tests/basic/configuration.nix b/tests/gotosocial/configuration.nix similarity index 100% rename from tests/basic/configuration.nix rename to tests/gotosocial/configuration.nix diff --git a/tests/basic/default.nix b/tests/gotosocial/default.nix similarity index 100% rename from tests/basic/default.nix rename to tests/gotosocial/default.nix diff --git a/tests/basic/hardware-configuration.nix b/tests/gotosocial/hardware-configuration.nix similarity index 100% rename from tests/basic/hardware-configuration.nix rename to tests/gotosocial/hardware-configuration.nix diff --git a/tests/basic/test-script.py b/tests/gotosocial/test-script.py similarity index 100% rename from tests/basic/test-script.py rename to tests/gotosocial/test-script.py diff --git a/tests/basic/test.nix b/tests/gotosocial/test.nix similarity index 100% rename from tests/basic/test.nix rename to tests/gotosocial/test.nix From 8dba8e7ce87ff9d65771d62aab2e75b4f7d1ef90 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Tue, 10 Feb 2026 14:59:44 +0100 Subject: [PATCH 04/18] working on harmonia --- modules-package.nix | 15 ++- modules/dynamicism/default.nix | 85 ++++++++------ modules/dynamicism/gotosocial.nix | 17 +-- modules/dynamicism/harmonia.nix | 26 +++++ modules/dynamicism/submodule.nix | 4 +- modules/dynamicism/tzupdate.nix | 20 ++++ tests/default.nix | 36 ++++-- tests/gotosocial/configuration-package.nix | 6 +- tests/gotosocial/configuration.nix | 4 +- tests/gotosocial/test-script.py | 4 +- tests/gotosocial/test.nix | 27 +---- tests/harmonia/configuration.nix | 72 ++++++++++++ tests/harmonia/hardware-configuration.nix | 4 + tests/harmonia/test-script.py | 125 +++++++++++++++++++++ tests/harmonia/test.nix | 23 ++++ tests/module-allow-rebuild-in-vm.nix | 31 +++++ tests/tzupdate/configuration.nix | 67 +++++++++++ tests/tzupdate/hardware-configuration.nix | 4 + tests/tzupdate/test-script.py | 52 +++++++++ tests/tzupdate/test.nix | 24 ++++ 20 files changed, 556 insertions(+), 90 deletions(-) create mode 100644 modules/dynamicism/harmonia.nix create mode 100644 modules/dynamicism/tzupdate.nix create mode 100644 tests/harmonia/configuration.nix create mode 100644 tests/harmonia/hardware-configuration.nix create mode 100644 tests/harmonia/test-script.py create mode 100644 tests/harmonia/test.nix create mode 100644 tests/module-allow-rebuild-in-vm.nix create mode 100644 tests/tzupdate/configuration.nix create mode 100644 tests/tzupdate/hardware-configuration.nix create mode 100644 tests/tzupdate/test-script.py create mode 100644 tests/tzupdate/test.nix diff --git a/modules-package.nix b/modules-package.nix index b36b809..d29908a 100644 --- a/modules-package.nix +++ b/modules-package.nix @@ -19,17 +19,14 @@ in stdenv.mkDerivation (self: { ]; }; - phases = [ "unpackPhase" "patchPhase" "installPhase "]; + phases = [ "unpackPhase" "patchPhase" "installPhase" ]; installPhase = lib.dedent '' - mkdir -vp "$out" - cp -rv * "$out/" + mkdir -p "$out" + cp -r * "$out/" - #mkdir -vp "$modules/share/nix/modules/dynix" - #cp --reflink=auto -rv * "$modules/share/nix/modules/dynix/" - - mkdir -vp "$modules/share/nixos/modules/dynix" - cp --reflink=auto -rv * "$modules/share/nixos/modules/dynix/" + mkdir -p "$modules/share/nixos/modules/dynix" + cp --reflink=auto -r "$out/"* "$modules/share/nixos/modules/dynix/" ''; passthru.mkDevShell = { @@ -51,6 +48,8 @@ in stdenv.mkDerivation (self: { ] |> lib.concatStringsSep ":"; }; + passthru.modulesPath = self.finalPackage.modules + "/share/nixos/modules"; + passthru.tests = lib.fix (callPackage ./tests { dynix = self.finalPackage; }).packages; diff --git a/modules/dynamicism/default.nix b/modules/dynamicism/default.nix index dae5bd6..5e2b4e7 100644 --- a/modules/dynamicism/default.nix +++ b/modules/dynamicism/default.nix @@ -4,6 +4,9 @@ let mkOption showOption ; + inherit (lib.asserts) + checkAssertWarn + ; t = lib.types; inherit (import ./lib.nix { inherit lib; }) @@ -19,7 +22,6 @@ let opts = options.dynamicism; subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs; - settingsFormat = pkgs.formats.yaml { }; finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath: lib.setAttrByPath optPath (lib.getAttrFromPath optPath config) @@ -82,45 +84,62 @@ in # # Generic implementation. # - config.dynamicism.doChange = { - option, - value, - configuration ? builtins.getEnv "NIXOS_CONFIG", - }: let - loc = opts.doChange.loc ++ [ "(function argument)" "value" ]; - option' = typeCheck loc convenientAttrPath option; - nixosAfter = evalNixos { - configuration = { config, ... }: { - imports = [ - configuration - (lib.setAttrByPath option' (lib.mkOverride (-999) value)) - ]; + config.system.activationScripts."dynamicism-reset" = { + deps = [ "etc" "stdio" "specialfs" ]; + text = '' + echo "DYNIX: removing existing systemd dropins" + # FIXME: do for each enabled submodule + if [[ -d /run/systemd/system ]]; then + rm -v /run/systemd/system/*/dynix-*.conf || true + fi + ''; + }; + config.dynamicism = { + doChange = { + option, + value, + configuration ? builtins.getEnv "NIXOS_CONFIG", + }: let + loc = opts.doChange.loc ++ [ "(function argument)" "value" ]; + option' = typeCheck loc convenientAttrPath option; + nixosAfter = evalNixos { + configuration = { config, ... }: { + imports = [ + configuration + (lib.setAttrByPath option' (lib.mkOverride (-999) value)) + ]; - environment.systemPackages = [ - config.dynamicism.for.gotosocial.activate - ]; + environment.systemPackages = [ + config.dynamicism.for.gotosocial.activate + ]; + }; }; - }; - allActivations = lib.mapAttrsToList (name: submod: submod.activate) config.dynamicism.for; - allActivationScripts = pkgs.writeShellApplication { - name = "dynamicism-activate"; - runtimeInputs = allActivations; - text = nixosAfter.config.dynamicism.for - |> lib.mapAttrsToList (name: submod: '' - echo "Activating dynamicism for ${name}" - ${lib.getExe submod.activate} - '') - |> lib.concatStringsSep "\n"; - }; - in allActivationScripts; + allActivations = config.dynamicism.for + |> lib.filterAttrs (name: submod: submod.enable) + |> lib.mapAttrsToList (name: submod: submod.activate); + allActivationScripts = pkgs.writeShellApplication { + name = "dynamicism-activate"; + runtimeInputs = allActivations; + text = nixosAfter.config.dynamicism.for + |> lib.filterAttrs (name: submod: submod.enable) + |> lib.mapAttrsToList (name: submod: '' + echo "Activating dynamicism for ${name}" + ${lib.getExe submod.activate} + '') + |> lib.concatStringsSep "\n"; + }; + in allActivationScripts; - config.dynamicism.finalSettings = lib.asserts.checkAssertWarn ourAssertions [ ] ( - recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) config.dynamicism.for - ); + finalSettings = config.dynamicism.for + |> recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) + |> checkAssertWarn ourAssertions [ ]; + }; # Implementations. imports = [ ./gotosocial.nix + ./harmonia.nix + #./tzupdate.nix ]; } diff --git a/modules/dynamicism/gotosocial.nix b/modules/dynamicism/gotosocial.nix index d52e68c..cbcff2b 100644 --- a/modules/dynamicism/gotosocial.nix +++ b/modules/dynamicism/gotosocial.nix @@ -3,17 +3,20 @@ let cfg = config.dynamicism.for.gotosocial; settingsFormat = pkgs.formats.yaml { }; + configFile = settingsFormat.generate "gotosocial-override.yml" config.services.gotosocial.settings; in { dynamicism.for.gotosocial = { source-options = [ "services.gotosocial.settings" ]; - configFile = settingsFormat.generate "gotosocial-overrde.yml" config.services.gotosocial.settings; - - unitDropins."gotosocial.service" = pkgs.writeText "gotosocial-override.conf" '' - [Service] - ExecStart= - ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${cfg.configFile} start - ''; + unitDropins."gotosocial.service" = pkgs.writeTextFile { + name = "gotosocial-override.conf"; + text = '' + [Service] + ExecStart= + ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${configFile} start + ''; + passthru = { inherit configFile; }; + }; }; } diff --git a/modules/dynamicism/harmonia.nix b/modules/dynamicism/harmonia.nix new file mode 100644 index 0000000..e4284d0 --- /dev/null +++ b/modules/dynamicism/harmonia.nix @@ -0,0 +1,26 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.dynamicism.for.harmonia; + settingsFormat = pkgs.formats.toml { }; + + # FIXME: referring to config.dynamicism.finalSettings.* in here + # makes lib.checkAssertWarn in the generic module cause infinite recursion. + finalSettings = config.services.harmonia.settings; + + configFile = settingsFormat.generate "harmonia-override.toml" finalSettings; +in +{ + dynamicism.for.harmonia = { + source-options = [ "services.harmonia.settings" ]; + + unitDropins."harmonia.service" = pkgs.writeTextFile { + name = "harmonia-override.conf"; + text = '' + [Service] + Environment=CONFIG_FILE=${configFile} + ''; + + passthru = { inherit configFile; }; + }; + }; +} diff --git a/modules/dynamicism/submodule.nix b/modules/dynamicism/submodule.nix index 1b9dac9..4fe269c 100644 --- a/modules/dynamicism/submodule.nix +++ b/modules/dynamicism/submodule.nix @@ -65,7 +65,7 @@ in }; unitDropins = mkOption { - type = t.attrsOf t.pathInStore; + type = t.attrsOf t.package; internal = true; }; @@ -82,7 +82,7 @@ in text = let doEdits = config.unitDropins |> lib.mapAttrsToList (service: dropin: '' - cat "${dropin}" | systemctl edit "${service}" --runtime --stdin + cat "${dropin}" | systemctl edit "${service}" --runtime --drop=dynix-${dropin.name} --stdin ''); doReloads = config.unitDropins |> lib.mapAttrsToList (service: _: '' diff --git a/modules/dynamicism/tzupdate.nix b/modules/dynamicism/tzupdate.nix new file mode 100644 index 0000000..73f634d --- /dev/null +++ b/modules/dynamicism/tzupdate.nix @@ -0,0 +1,20 @@ +{ pkgs, lib, config, ... }: let + cfg = config.dynamicism.for.tzupdate; + + + # FIXME: referring to config.dynamicism.finalSettings.* in here + # makes lib.checkAssertWarn in the generic module cause infinite recursion. + #finalSettings = config.dynamicism.finalSettings.tzupdate; + finalSettings = config.services.tzupdate.timer; +in +{ + dynamicism.for.tzupdate = { + source-options = [ "services.tzupdate.timer" ]; + + unitDropins."tzupdate.timer" = pkgs.writeText "tzupdate-timer-override.conf" '' + [Timer] + OnCalendar= + OnCalendar=${finalSettings.interval} + ''; + }; +} diff --git a/tests/default.nix b/tests/default.nix index a6aea2d..40369b9 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -10,22 +10,40 @@ runDynixTest = testModule: pkgs.testers.runNixOSTest { imports = [ testModule ]; - # Note: these are arguments to the *test* modules. - # Not the NixOS modules for the NixOS configuration the test is testing. - # Wew. - _module.args = { inherit dynix; }; - # Why is this argument called "extraBaseModule**s**" but take a single module argument... - extraBaseModules = { name, ... }: { - #imports = [ dynixInjectionModule ]; - config.environment.systemPackages = [ dynix ]; + # Also note this is an extra base module for each node of the test, + # not an extra test module. + extraBaseModules = { name, config, options, modulesPath, ... }: { + imports = (import "${modulesPath}/module-list.nix") ++ [ + ./module-allow-rebuild-in-vm.nix + "${modulesPath}/testing/test-instrumentation.nix" + (toString dynix) + ]; + environment.systemPackages = [ dynix ]; + + systemd.services."install-dynix" = { + enable = true; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ config.system.path ]; + wantedBy = [ "multi-user.target" ]; + after = [ "default.target" ]; + script = '' + nix profile install -vv "$(realpath /run/current-system/sw/share/nixos/modules/dynix/)" + ''; + }; + + passthru = { inherit options; }; + # Just making something in this strict in `name`, # which is only present as an argument for nodes and I don't want to # confuse that with the test modules. - config.warnings = builtins.seq name [ ]; + warnings = builtins.seq name [ ]; }; }; in lib.makeScope lib.callPackageWith (self: { gotosocial = runDynixTest ./gotosocial/test.nix; + harmonia = runDynixTest ./harmonia/test.nix; + #tzupdate = runDynixTest ./tzupdate/test.nix; }) diff --git a/tests/gotosocial/configuration-package.nix b/tests/gotosocial/configuration-package.nix index e6bb010..96a89d8 100644 --- a/tests/gotosocial/configuration-package.nix +++ b/tests/gotosocial/configuration-package.nix @@ -1,9 +1,7 @@ { runCommand, -}: runCommand "tests-basic-configuration-dot-nix" { +}: runCommand "tests-gotosocial-configuration-dot-nix" { } '' set -euo pipefail - mkdir -vp "$out/share/nixos" - cp -rv ${./configuration.nix} "$out/share/nixos/configuration.nix" - #cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism" + install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix" '' diff --git a/tests/gotosocial/configuration.nix b/tests/gotosocial/configuration.nix index bc751ed..deef056 100644 --- a/tests/gotosocial/configuration.nix +++ b/tests/gotosocial/configuration.nix @@ -18,7 +18,7 @@ in system.switch.enable = true; documentation.enable = false; - networking.hostName = "machine"; + networking.hostName = "gotosocial-machine"; boot.loader.grub = { enable = true; @@ -47,7 +47,7 @@ in enable = true; setupPostgresqlDB = true; settings = { - application-name = "gotosocial-for-${name}"; + application-name = "gotosocial-for-machine"; host = "${name}.local"; }; }; diff --git a/tests/gotosocial/test-script.py b/tests/gotosocial/test-script.py index 03974fc..d8b2af8 100644 --- a/tests/gotosocial/test-script.py +++ b/tests/gotosocial/test-script.py @@ -34,7 +34,6 @@ def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str: def get_config_file() -> str: machine.wait_for_unit("gotosocial.service") gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID")) - print(f"{gotosocial_pid=}") cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline") cmdline_args = cmdline.split("\0") @@ -53,8 +52,7 @@ def get_config_file() -> str: machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() machine.log("INIT") - -machine.succeed("nix profile install -vv $(realpath /run/current-system/sw/share/nixos/modules/dynix)") +run_log(machine, "journalctl --no-pager -eu install-dynix.service") machine.succeed("nixos-generate-config") machine.succeed("mkdir -vp /etc/nixos") diff --git a/tests/gotosocial/test.nix b/tests/gotosocial/test.nix index 4356685..9628b93 100644 --- a/tests/gotosocial/test.nix +++ b/tests/gotosocial/test.nix @@ -1,7 +1,7 @@ -{ dynix, ... }: +{ ... }: { - name = "nixos-test-dynamicism-main"; + name = "nixos-test-dynamicism-gotosocial"; defaults = { ... }: { }; @@ -9,27 +9,12 @@ p.beartype ]; - nodes.machine = { pkgs, config, ... }: { + nodes.machine = { pkgs, ... }: { # NOTE: Anything in this `nodes.machine = ` module will not be included # in the VM's NixOS configuration once it does `nixos-rebuild switch`, # except for `./configuration.nix` which will be copied to `/etc/nixos/`. # dynix will also be statefully installed to root's user profile. - imports = [ - ./configuration.nix - (toString dynix) - ]; - - system.includeBuildDependencies = true; - system.switch.enable = true; - - virtualisation.additionalPaths = [ config.system.build.toplevel ]; - virtualisation = { - memorySize = 4096; - cores = 4; - writableStore = true; - mountHostNixStore = true; - installBootLoader = true; - }; + imports = [ ./configuration.nix ]; environment.systemPackages = let configFileTree = pkgs.callPackage ./configuration-package.nix { }; @@ -38,7 +23,5 @@ ]; }; - # What's a little IFD between friends? - testScript = ./test-script.py - |> builtins.readFile; + testScript = builtins.readFile ./test-script.py; } diff --git a/tests/harmonia/configuration.nix b/tests/harmonia/configuration.nix new file mode 100644 index 0000000..7bf19bb --- /dev/null +++ b/tests/harmonia/configuration.nix @@ -0,0 +1,72 @@ +{ pkgs, lib, config, modulesPath, ... }: +let + moduleList = import "${modulesPath}/module-list.nix"; + + dynixFromSearchPath = let + res = builtins.tryEval ; + in lib.optional res.success res.value; +in +{ + imports = [ + "${modulesPath}/testing/test-instrumentation.nix" + ./hardware-configuration.nix + ] ++ lib.concatLists [ + dynixFromSearchPath + moduleList + ]; + + dynamicism.for.harmonia.enable = true; + services.harmonia = { + enable = true; + settings = { + # Default. + workers = 4; + # Default. + max_connection_rate = 256; + }; + }; + + system.switch.enable = true; + documentation.enable = false; + + networking.hostName = "harmonia-machine"; + + boot.loader.grub = { + enable = true; + device = "/dev/vda"; + forceInstall = true; + }; + + nix = { + package = pkgs.lixPackageSets.latest.lix; + nixPath = [ + "nixpkgs=${pkgs.path}" + "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" + ]; + + settings = { + experimental-features = [ "nix-command" "pipe-operator" ]; + substituters = lib.mkForce [ ]; + hashed-mirrors = null; + connect-timeout = 1; + # For my debugging purposes. + show-trace = true; + }; + }; + + environment.pathsToLink = [ "/share" ]; + environment.extraOutputsToInstall = [ "modules" ]; + environment.variables = { + "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; + }; + + environment.shellAliases = { + ls = "eza --long --header --group --group-directories-first --classify --binary"; + }; + + environment.systemPackages = with pkgs; [ + eza + fd + ripgrep + ]; +} diff --git a/tests/harmonia/hardware-configuration.nix b/tests/harmonia/hardware-configuration.nix new file mode 100644 index 0000000..facb35d --- /dev/null +++ b/tests/harmonia/hardware-configuration.nix @@ -0,0 +1,4 @@ +{ ... }: +{ + +} diff --git a/tests/harmonia/test-script.py b/tests/harmonia/test-script.py new file mode 100644 index 0000000..a91bf78 --- /dev/null +++ b/tests/harmonia/test-script.py @@ -0,0 +1,125 @@ +import functools +from pathlib import Path +from pprint import pformat +import shlex +import textwrap +import tomllib +from typing import Any, 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 + +@beartype +def get_config_file() -> dict[str, Any]: + machine.wait_for_unit("harmonia.service") + pid = int(machine.get_unit_property("harmonia.service", "MainPID")) + env_lines: list[str] = machine.succeed(f"cat /proc/{pid}/environ").replace("\0", "\n").splitlines() + pairs: list[list[str]] = [line.split("=", maxsplit=1) for line in env_lines] + env = dict(pairs) + + config_file = Path(env["CONFIG_FILE"]) + + machine.log(f"copying from VM: {config_file=}") + machine.copy_from_vm(config_file.as_posix()) + + config_file_path = machine.out_dir / config_file.name + with open(config_file_path, "rb") as f: + config_data = tomllib.load(f) + + config_file_path.unlink() + return config_data + +machine.wait_for_unit("default.target") +assert "lix" in machine.succeed("nix --version").lower() +machine.log("INIT") + +# Config should have our initial values. +config_toml = get_config_file() +machine.log(f"config.toml BEFORE first rebuild (initial): {indent(pformat(config_toml))}") +assert int(config_toml['workers']) == 4, f"{config_toml['workers']=} != 4" +assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256" + +with machine.nested("must succeed: initial nixos-rebuild switch"): + machine.succeed("nixos-generate-config") + machine.succeed("mkdir -vp /etc/nixos") + # Dereference is required since that configuration.nix is probably a symlink to the store. + machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/") + machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") + +# Config should not have changed. +config_toml = get_config_file() +machine.log(f"config.toml after first rebuild: {indent(pformat(config_toml))}") +assert int(config_toml['workers']) == 4, f"{config_toml['workers']=} != 4" +assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256" + +new_workers = 20 +expr = textwrap.dedent(f""" + let + nixos = import {{ }}; + in nixos.config.dynamicism.doChange {{ + option = "services.harmonia.settings.workers"; + value = {new_workers}; + }} +""").strip() +machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} +""".strip()) + +# Workers, but not max connection rate, should have changed. +config_toml = get_config_file() +machine.log(f"config.toml after DYNAMIC ACTIVATION: {indent(pformat(config_toml))}") +assert int(config_toml['workers']) == new_workers, f"{config_toml['workers']=} != {new_workers}" +assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256" + +new_max_connection_rate = 100 +expr = textwrap.dedent(f""" + let + nixos = import {{ }}; + in nixos.config.dynamicism.doChange {{ + option = [ "services" "harmonia" "settings" "max_connection_rate" ]; + value = {new_max_connection_rate}; + }} +""").strip() +machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} +""".strip()) + +# Max connection rate should have changed, but workers should have reverted. +config_toml = get_config_file() +machine.log(f"config_toml after SECOND dynamic activation: {indent(pformat(config_toml))}") +assert int(config_toml['max_connection_rate']) == new_max_connection_rate, f"{config_toml['max_connection_rate']=} != {new_max_connection_rate}" + +# And this should set everything back. +machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") +machine.systemctl("restart harmonia.service") +machine.wait_for_unit("harmonia.service") +config_toml = get_config_file() +machine.log(f"config_toml after NORMAL activation: {indent(pformat(config_toml))}") +assert int(config_toml['max_connection_rate']) == 256, f'{config_toml["max_connection_rate"]=} != 256' +assert int(config_toml['workers']) == 4, f'{config_toml["workers"]=} != 4' diff --git a/tests/harmonia/test.nix b/tests/harmonia/test.nix new file mode 100644 index 0000000..0aea418 --- /dev/null +++ b/tests/harmonia/test.nix @@ -0,0 +1,23 @@ +{ ... }: +{ + name = "nixos-test-dynamicism-harmonia"; + + defaults = { ... }: { }; + + extraPythonPackages = p: [ p.beartype ]; + + nodes.machine = { name, pkgs, ... }: { + imports = [ ./configuration.nix ]; + + environment.systemPackages = let + configFileTree = pkgs.runCommand "${name}-configuration-dot-nix" { } '' + set -euo pipefail + install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix" + ''; + in [ + configFileTree + ]; + }; + + testScript = builtins.readFile ./test-script.py; +} diff --git a/tests/module-allow-rebuild-in-vm.nix b/tests/module-allow-rebuild-in-vm.nix new file mode 100644 index 0000000..3ec14d5 --- /dev/null +++ b/tests/module-allow-rebuild-in-vm.nix @@ -0,0 +1,31 @@ +{ name, config, ... }: + +{ + system.includeBuildDependencies = true; + system.switch.enable = true; + + documentation.enable = false; + + virtualisation = { + additionalPaths = [ config.system.build.toplevel ]; + writableStore = true; + mountHostNixStore = true; + installBootLoader = true; + + # With how much memory Nix eval uses, this is essentially required. + memorySize = 4096; + cores = 4; + + }; + + boot.loader.grub = { + enable = true; + device = "/dev/vda"; + forceInstall = true; + }; + + # Just making something in this strict in `name`, + # which is only present as an argument for nodes and I don't want to + # confuse that with the test modules. + warnings = builtins.seq name [ ]; +} diff --git a/tests/tzupdate/configuration.nix b/tests/tzupdate/configuration.nix new file mode 100644 index 0000000..176975c --- /dev/null +++ b/tests/tzupdate/configuration.nix @@ -0,0 +1,67 @@ +{ pkgs, lib, config, modulesPath, ... }: +let + name = config.networking.hostName; + moduleList = import "${modulesPath}/module-list.nix"; + + dynixFromSearchPath = let + res = builtins.tryEval ; + in lib.optional res.success res.value; +in +{ + imports = [ + "${modulesPath}/testing/test-instrumentation.nix" + ./hardware-configuration.nix + ] ++ lib.concatLists [ + moduleList + dynixFromSearchPath + ]; + + dynamicism.for.tzupdate.enable = true; + services.tzupdate = { + enable = true; + }; + + system.switch.enable = true; + documentation.enable = false; + + networking.hostName = "tzupdate-machine"; + + boot.loader.grub = { + enable = true; + device = "/dev/vda"; + forceInstall = true; + }; + + nix = { + package = pkgs.lixPackageSets.latest.lix; + nixPath = [ + "nixpkgs=${pkgs.path}" + "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" + ]; + + settings = { + experimental-features = [ "nix-command" "pipe-operator" ]; + substituters = lib.mkForce [ ]; + hashed-mirrors = null; + connect-timeout = 1; + # For my debugging purposes. + show-trace = true; + }; + }; + + environment.pathsToLink = [ "/share" ]; + environment.extraOutputsToInstall = [ "modules" ]; + environment.variables = { + "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; + }; + + environment.shellAliases = { + ls = "eza --long --header --group --group-directories-first --classify --binary"; + }; + + environment.systemPackages = with pkgs; [ + eza + fd + ripgrep + ]; +} diff --git a/tests/tzupdate/hardware-configuration.nix b/tests/tzupdate/hardware-configuration.nix new file mode 100644 index 0000000..facb35d --- /dev/null +++ b/tests/tzupdate/hardware-configuration.nix @@ -0,0 +1,4 @@ +{ ... }: +{ + +} diff --git a/tests/tzupdate/test-script.py b/tests/tzupdate/test-script.py new file mode 100644 index 0000000..53762a0 --- /dev/null +++ b/tests/tzupdate/test-script.py @@ -0,0 +1,52 @@ +#from pathlib import Path +#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" + +@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 + +machine.wait_for_unit("default.target") +assert "lix" in machine.succeed("nix --version").lower() +machine.log("INIT") + + +machine.succeed("nixos-generate-config") +machine.succeed("mkdir -vp /etc/nixos") +# Dereference is required since that configuration.nix is probably a symlink to the store. +machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/") + +machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") + +@beartype +def get_interval() -> str: + prop = machine.get_unit_property("tzupdate.timer", "OnCalendar") + print(f"{prop=}") + + return prop + +get_interval() diff --git a/tests/tzupdate/test.nix b/tests/tzupdate/test.nix new file mode 100644 index 0000000..2d42c10 --- /dev/null +++ b/tests/tzupdate/test.nix @@ -0,0 +1,24 @@ +{ ... }: + +{ + name = "nixos-test-dynamicism-tzupdate"; + + extraPythonPackages = p: [ + p.beartype + ]; + + nodes.machine = { name, pkgs, ... }: { + imports = [ ./configuration.nix ]; + + environment.systemPackages = let + configFileTree = pkgs.runCommand "${name}-configuration-dot-nix" { } '' + set -euo pipefail + install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix" + ''; + in [ + configFileTree + ]; + }; + + testScript = builtins.readFile ./test-script.py; +} From 8a6bd41baaaf6d2ab03f83c567389194173bf8f4 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Wed, 11 Feb 2026 13:16:54 +0100 Subject: [PATCH 05/18] remove old unused files for now --- Cargo.lock | 920 ---------------------------------- Cargo.toml | 25 - configuration.nix | 36 -- default.nix | 2 +- modules-package.nix | 60 --- modules/dynamic-overrides.nix | 21 - package.nix | 92 ++-- src/args.rs | 97 ---- src/color.rs | 53 -- src/lib.rs | 179 ------- src/line.rs | 43 -- src/main.rs | 48 -- src/nixcmd.rs | 27 - src/source.rs | 234 --------- tests/default.nix | 2 +- 15 files changed, 35 insertions(+), 1804 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 configuration.nix delete mode 100644 modules-package.nix delete mode 100644 modules/dynamic-overrides.nix delete mode 100644 src/args.rs delete mode 100644 src/color.rs delete mode 100644 src/lib.rs delete mode 100644 src/line.rs delete mode 100644 src/main.rs delete mode 100644 src/nixcmd.rs delete mode 100644 src/source.rs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index eef46d8..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,920 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clap" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "command-error" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6ac3abfcf15b4536b079bcee923683fe3dc1173b70be5e05ccf28ca112862e" -dependencies = [ - "dyn-clone", - "process-wrap", - "shell-words", - "utf8-command", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "dynix" -version = "0.1.0" -dependencies = [ - "clap", - "command-error", - "fs-err", - "itertools", - "libc", - "serde", - "serde_json", - "tap", - "tracing", - "tracing-human-layer", - "tracing-subscriber", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fs-err" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7" -dependencies = [ - "autocfg", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "is_ci" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "owo-colors" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" -dependencies = [ - "supports-color 2.1.0", - "supports-color 3.0.2", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "proc-macro2" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "process-wrap" -version = "8.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" -dependencies = [ - "indexmap", - "nix", - "tracing", - "windows", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "supports-color" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" -dependencies = [ - "is-terminal", - "is_ci", -] - -[[package]] -name = "supports-color" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" -dependencies = [ - "is_ci", -] - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "terminal_size" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" -dependencies = [ - "rustix", - "windows-sys 0.60.2", -] - -[[package]] -name = "textwrap" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" -dependencies = [ - "smawk", - "terminal_size", - "unicode-linebreak", - "unicode-width", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-human-layer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b285fd79bba4659408f5d290b3f30fd69d428c630d8c00bb4ba255f2501d50e3" -dependencies = [ - "itertools", - "owo-colors", - "parking_lot", - "textwrap", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "parking_lot", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "utf8-command" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b151524c94cda49046b29e6d20b03092ff9363b02acc1bf3994da60910c55b" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "zmij" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index dd24d30..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "dynix" -version = "0.1.0" -edition = "2024" - -[[bin]] -name = "dynix" -path = "src/main.rs" - -[lib] -name = "dynix" -path = "src/lib.rs" - -[dependencies] -clap = { version = "4.5.54", features = ["color", "derive"] } -command-error = "0.8.0" -fs-err = "3.2.2" -itertools = "0.14.0" -libc = { version = "0.2.180", features = ["extra_traits"] } -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" -tap = "1.0.1" -tracing = { version = "0.1.44", features = ["attributes"] } -tracing-human-layer = "0.2.1" -tracing-subscriber = { version = "0.3.22", default-features = false, features = ["std", "env-filter", "fmt", "ansi", "registry", "parking_lot"] } diff --git a/configuration.nix b/configuration.nix deleted file mode 100644 index f34f2e5..0000000 --- a/configuration.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ pkgs, modulesPath, ... }: - -{ - imports = [ - ./modules/dynamic-overrides.nix - ./modules/dynamicism - "${modulesPath}/profiles/qemu-guest.nix" - ]; - - dynamicism.for.gotosocial.enable = true; - - # Just an example system. - users.mutableUsers = false; - users.users.root = { - password = "root"; - }; - - services.openssh = { - enable = true; - settings = { - PermitRootLogin = "yes"; - }; - }; - - environment.shellAliases = { - ls = "eza --long --header --group --group-directories-first --classify --binary"; - }; - - environment.systemPackages = with pkgs; [ - eza - fd - ripgrep - ]; - - system.stateVersion = "25.11"; -} diff --git a/default.nix b/default.nix index bdfa4ef..b17a6c7 100644 --- a/default.nix +++ b/default.nix @@ -5,7 +5,7 @@ in import src { inherit pkgs; }, }: let inherit (qpkgs) lib; - dynix = qpkgs.callPackage ./modules-package.nix { }; + dynix = qpkgs.callPackage ./package.nix { }; byStdenv = lib.mapAttrs (stdenvName: stdenv: let withStdenv = dynix.override { inherit stdenv; }; dynix' = withStdenv.overrideAttrs (prev: { diff --git a/modules-package.nix b/modules-package.nix deleted file mode 100644 index d29908a..0000000 --- a/modules-package.nix +++ /dev/null @@ -1,60 +0,0 @@ -{ - lib, - stdenvNoCC, - callPackage, -}: let - stdenv = stdenvNoCC; -in stdenv.mkDerivation (self: { - name = "dynix-modules"; - - strictDeps = true; - __structuredAttrs = true; - - outputs = [ "out" "modules" ]; - - src = lib.fileset.toSource { - root = ./modules/dynamicism; - fileset = lib.fileset.unions [ - ./modules/dynamicism - ]; - }; - - phases = [ "unpackPhase" "patchPhase" "installPhase" ]; - - installPhase = lib.dedent '' - mkdir -p "$out" - cp -r * "$out/" - - mkdir -p "$modules/share/nixos/modules/dynix" - cp --reflink=auto -r "$out/"* "$modules/share/nixos/modules/dynix/" - ''; - - passthru.mkDevShell = { - path, - mkShell, - python3Packages, - }: let - mkShell' = mkShell.override { inherit stdenv; }; - pyEnv = python3Packages.python.withPackages (p: [ - p.beartype - ]); - in mkShell' { - name = "devshell-for-${self.finalPackage.name}"; - packages = [ pyEnv ]; - env.PYTHONPATH = [ - "${pyEnv}/${pyEnv.sitePackages}" - # Cursed. - "${path}/nixos/lib/test-driver/src" - ] |> lib.concatStringsSep ":"; - }; - - passthru.modulesPath = self.finalPackage.modules + "/share/nixos/modules"; - - passthru.tests = lib.fix (callPackage ./tests { - dynix = self.finalPackage; - }).packages; - - meta = { - outputsToInstall = [ "modules" ]; - }; -}) diff --git a/modules/dynamic-overrides.nix b/modules/dynamic-overrides.nix deleted file mode 100644 index 1952e96..0000000 --- a/modules/dynamic-overrides.nix +++ /dev/null @@ -1,21 +0,0 @@ -# Managed by dynix. -{ lib, ... }: - -lib.mkMerge [ - { - services.gotosocial = { - enable = true; - setupPostgresqlDB = true; - settings = { - application-name = "example!"; - host = "yuki.local"; - }; - }; - } - { - services.gotosocial.settings.application-name = lib.mkOverride 99 "removed herobrine"; - } - { - services.gotosocial.settings.application-name = lib.mkOverride 98 "reädded herobrine"; - } -] diff --git a/package.nix b/package.nix index 2cb8cee..d29908a 100644 --- a/package.nix +++ b/package.nix @@ -1,86 +1,60 @@ { lib, - stdenv, - rustHooks, - rustPackages, - versionCheckHook, -}: lib.callWith' rustPackages ({ - rustPlatform, - cargo, + stdenvNoCC, + callPackage, }: let - cargoToml = lib.importTOML ./Cargo.toml; - cargoPackage = cargoToml.package; - + stdenv = stdenvNoCC; in stdenv.mkDerivation (self: { - pname = cargoPackage.name; - version = cargoPackage.version; + name = "dynix-modules"; strictDeps = true; __structuredAttrs = true; - doCheck = true; - doInstallCheck = true; + outputs = [ "out" "modules" ]; src = lib.fileset.toSource { - root = ./.; + root = ./modules/dynamicism; fileset = lib.fileset.unions [ - ./Cargo.toml - ./Cargo.lock - ./src + ./modules/dynamicism ]; }; - cargoDeps = rustPlatform.importCargoLock { - lockFile = ./Cargo.lock; - }; + phases = [ "unpackPhase" "patchPhase" "installPhase" ]; - versionCheckProgramArg = "--version"; + installPhase = lib.dedent '' + mkdir -p "$out" + cp -r * "$out/" - nativeBuildInputs = rustHooks.asList ++ [ - cargo - ]; - - nativeInstallCheckInputs = [ - versionCheckHook - ]; + mkdir -p "$modules/share/nixos/modules/dynix" + cp --reflink=auto -r "$out/"* "$modules/share/nixos/modules/dynix/" + ''; passthru.mkDevShell = { + path, mkShell, + python3Packages, }: let - mkShell' = mkShell.override { stdenv = stdenv; }; + mkShell' = mkShell.override { inherit stdenv; }; + pyEnv = python3Packages.python.withPackages (p: [ + p.beartype + ]); in mkShell' { - name = "${self.pname}-devshell-${self.version}"; - inputsFrom = [ self.finalPackage ]; - packages = [ - rustPackages.rustc - rustPackages.rustfmt - ]; + name = "devshell-for-${self.finalPackage.name}"; + packages = [ pyEnv ]; + env.PYTHONPATH = [ + "${pyEnv}/${pyEnv.sitePackages}" + # Cursed. + "${path}/nixos/lib/test-driver/src" + ] |> lib.concatStringsSep ":"; }; - passthru.tests.clippy = self.finalPackage.overrideAttrs (prev: { - pname = "${self.pname}-clippy"; + passthru.modulesPath = self.finalPackage.modules + "/share/nixos/modules"; - nativeCheckInputs = prev.nativeCheckInputs or [ ] ++ [ - rustPackages.clippy - ]; - - dontConfigure = true; - dontBuild = true; - doCheck = true; - dontFixup = true; - dontInstallCheck = true; - - checkPhase = lib.trim '' - echo "cargoClippyPhase()" - cargo clippy --all-targets --profile "$cargoCheckType" -- --deny warnings - ''; - - installPhase = lib.trim '' - touch "$out" - ''; - }); + passthru.tests = lib.fix (callPackage ./tests { + dynix = self.finalPackage; + }).packages; meta = { - mainProgram = "dynix"; + outputsToInstall = [ "modules" ]; }; -})) +}) diff --git a/src/args.rs b/src/args.rs deleted file mode 100644 index cc2fa07..0000000 --- a/src/args.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::sync::Arc; - -use clap::ColorChoice; - -use crate::prelude::*; - -//#[derive(Debug, Clone, PartialEq)] -//#[derive(clap::Args)] -//#[group(required = true, multiple = false)] -//pub enum Config -//{ -// Flake, -//} - -#[derive(Debug, Clone, PartialEq)] -pub struct NixOsOption { - name: String, - value: String, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct NixOptionParseError(pub Box); - -impl Display for NixOptionParseError { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{}", self.0) - } -} - -impl From for NixOptionParseError { - fn from(value: String) -> Self { - Self(value.into_boxed_str()) - } -} - -impl StdError for NixOptionParseError {} - -impl FromStr for NixOsOption { - type Err = NixOptionParseError; - - fn from_str(s: &str) -> Result { - // FIXME: allow escaping equals sign? - let Some(delim) = s.find('=') else { - return Err(format!("equals sign not found in {}", s).into()); - }; - - todo!(); - } -} - -#[derive(Debug, Clone, PartialEq, clap::Parser)] -pub struct AppendCmd { - #[arg(required = true)] - pub name: Arc, - #[arg(required = true)] - pub value: Arc, -} - -#[derive(Debug, Clone, PartialEq, clap::Parser)] -pub struct DeltaCmd {} - -#[derive(Debug, Clone, PartialEq, clap::Subcommand)] -#[command(flatten_help = true)] -pub enum Subcommand { - Append(AppendCmd), - // TODO: rename - Delta(DeltaCmd), -} - -#[derive(Debug, Clone, PartialEq, clap::Parser)] -#[command(version, about, author)] -#[command(arg_required_else_help(true), args_override_self(true))] -#[command(propagate_version = true)] -pub struct Args { - #[arg(long, global(true), default_value = "auto")] - pub color: ColorChoice, - - // FIXME: default to /etc/configuration.nix, or something? - #[arg(long, global(true), default_value = "./configuration.nix")] - pub file: Arc, - - #[command(subcommand)] - pub subcommand: Subcommand, -} -///// Flakeref to a base configuration to modify. -//#[arg(group = "config", long, default_value("."))] -//#[arg(long, default_value(Some(".")))] -//flake: Option>>, -// -//#[arg(group = "config", long)] -//expr: Option, - -//impl Parser { -// fn eval_cmd(&self) { -// todo!(); -// } -//} diff --git a/src/color.rs b/src/color.rs deleted file mode 100644 index 1e420f6..0000000 --- a/src/color.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{ - env, - sync::{LazyLock, OnceLock}, -}; - -#[allow(unused_imports)] -use crate::prelude::*; - -/// The actual, final value for whether color should be used, based on CLI and environment values. -pub static SHOULD_COLOR: LazyLock = LazyLock::new(|| is_clicolor_forced() || is_color_reqd()); - -/// Initialized from the `--color` value from the CLI, along with `io::stdin().is_terminal()`. -pub static _CLI_ENABLE_COLOR: OnceLock = OnceLock::new(); - -fn is_color_reqd() -> bool { - _CLI_ENABLE_COLOR.get().copied().unwrap_or(false) -} - -fn is_clicolor_forced() -> bool { - env::var("CLICOLOR_FORCE") - .map(|value| { - if value.is_empty() || value == "0" { - false - } else { - true - } - }) - .unwrap_or(false) -} - -/// Silly wrapper around LazyLock<&'static str> to impl Display. -pub(crate) struct _LazyLockDisplay(LazyLock<&'static str>); -impl Display for _LazyLockDisplay { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - Display::fmt(&*self.0, f) - } -} - -pub(crate) const ANSI_GREEN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { - SHOULD_COLOR.then_some("\x1b[32m").unwrap_or_default() -})); - -pub(crate) const ANSI_MAGENTA: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { - SHOULD_COLOR.then_some("\x1b[35m").unwrap_or_default() -})); - -pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { - SHOULD_COLOR.then_some("\x1b[36m").unwrap_or_default() -})); - -pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { - SHOULD_COLOR.then_some("\x1b[0m").unwrap_or_default() -})); diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 1956803..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::{iter, sync::Arc}; - -pub(crate) mod prelude { - #![allow(unused_imports)] - - pub use std::{ - error::Error as StdError, - ffi::{OsStr, OsString}, - fmt::{Display, Formatter, Result as FmtResult}, - io::{Error as IoError, Read, Seek, SeekFrom, Write}, - path::{Path, PathBuf}, - process::{Command, ExitCode}, - str::FromStr, - }; - - #[cfg(unix)] - pub use std::os::{ - fd::AsRawFd, - unix::ffi::{OsStrExt, OsStringExt}, - }; - - pub type BoxDynError = Box; - - pub use command_error::{CommandExt, OutputLike}; - pub use fs_err::File; - #[cfg(unix)] - pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt}; - - pub use tap::{Pipe, Tap}; - - pub use tracing::{Level, debug, error, info, trace, warn}; -} - -use prelude::*; - -pub mod args; -pub use args::{AppendCmd, Args, DeltaCmd}; -mod color; -pub use color::{_CLI_ENABLE_COLOR, SHOULD_COLOR}; -pub mod line; -mod nixcmd; -pub use line::Line; -pub mod source; -pub use source::SourceLine; - -use serde::{Deserialize, Serialize}; - -use crate::source::SourceFile; - -pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; - -#[tracing::instrument(level = "debug")] -pub fn do_delta(args: Arc, delta_args: DeltaCmd) -> Result<(), BoxDynError> { - todo!(); -} - -#[tracing::instrument(level = "debug")] -pub fn do_append(args: Arc, append_args: AppendCmd) -> Result<(), BoxDynError> { - let filepath = Path::new(&args.file); - let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") { - iter::once(OsStr::new("./")) - .chain(filepath.iter()) - .collect() - } else { - filepath.to_path_buf() - }; - - // Get what file that thing is defined in. - let def_path = get_where(&append_args.name, &filepath)?; - - let mut opts = File::options(); - opts.read(true) - .write(true) - .create(false) - .custom_flags(libc::O_CLOEXEC); - let source_file = SourceFile::open_from(Arc::from(def_path), opts)?; - - let pri = get_highest_prio(&append_args.name, source_file.clone())?; - let new_pri = pri - 1; - - let new_pri_line = get_next_prio_line( - source_file.clone(), - append_args.name.into(), - new_pri, - append_args.value.into(), - )?; - - eprintln!("new_pri_line={new_pri_line}"); - - write_next_prio(source_file, new_pri_line)?; - - Ok(()) -} - -#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] -pub struct DefinitionWithLocation { - pub file: Box, - pub value: Box, -} - -pub fn expr_for_configuration(source_file: &Path) -> OsString { - [ - OsStr::new("import { configuration = "), - source_file.as_os_str(), - OsStr::new("; }"), - ] - .into_iter() - .collect() -} - -pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result, BoxDynError> { - let expr = expr_for_configuration(configuration_nix); - let attrpath = format!("options.{}.definitionsWithLocations", option_name); - - let output = nixcmd::NixEvalExpr { expr, attrpath } - .into_command() - .output_checked_utf8()?; - let stdout = output.stdout(); - - let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?; - let last_location = definitions.into_iter().last().unwrap(); - - Ok(Box::from(last_location.file)) -} - -pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result { - // Get the current highest priority. - - let expr = expr_for_configuration(&source.path()); - - // Get the highest priority, and the file its defined in. - let attrpath = format!("options.{}.highestPrio", option_name); - let output = nixcmd::NixEvalExpr { expr, attrpath } - .into_command() - .output_checked_utf8()?; - let stdout = output.stdout(); - let highest_prio = i64::from_str(stdout.trim())?; - - Ok(highest_prio) -} - -pub fn get_next_prio_line( - source: SourceFile, - option_name: Arc, - new_prio: i64, - new_value: Arc, -) -> Result { - let source_lines = source.lines()?; - let last_line = source_lines.last(); - assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]")); - let last_line = last_line.unwrap(); - - let new_line = SourceLine { - line: last_line.line, - path: source.path(), - text: Arc::from(format!( - " {option_name} = lib.mkOverride ({new_prio}) ({new_value});", - )), - }; - - Ok(new_line) -} - -pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> { - let new_mod_start = SourceLine { - line: new_line.line.prev(), - path: source.path(), - text: Arc::from(" {"), - }; - let new_mod_end = SourceLine { - line: new_line.line.next(), - path: source.path(), - text: Arc::from(" }"), - }; - - source.insert_lines(&[new_mod_start, new_line, new_mod_end])?; - - Ok(()) -} diff --git a/src/line.rs b/src/line.rs deleted file mode 100644 index e7ec169..0000000 --- a/src/line.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::num::NonZeroU64; - -#[allow(unused_imports)] -use crate::prelude::*; - - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Line(pub u64); - -/// Constructors. -impl Line { - pub const fn from_index(index: u64) -> Self { - Self(index) - } - - pub const fn from_linenr(linenr: NonZeroU64) -> Self { - Self(linenr.get() - 1) - } - - pub const fn next(self) -> Self { - Self::from_index(self.index() + 1) - } - - /// Panics if self is line index 0. - pub const fn prev(self) -> Self { - Self::from_index(self.index() - 1) - } -} - -/// Getters. -impl Line { - /// 0-indexed - pub const fn index(self) -> u64 { - self.0 - } - - /// 1-indexed - pub const fn linenr(self) -> u64 { - self.0 + 1 - } -} - -pub struct Lines(Vec); diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index ef00265..0000000 --- a/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::io::{self, IsTerminal}; -use std::process::ExitCode; -use std::{error::Error as StdError, sync::Arc}; - -use clap::{ColorChoice, Parser as _}; -use tracing_human_layer::HumanLayer; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{EnvFilter, layer::SubscriberExt}; - -fn main_wrapped() -> Result<(), Box> { - let args = Arc::new(dynix::Args::parse()); - - let success = dynix::_CLI_ENABLE_COLOR.set(match args.color { - ColorChoice::Always => true, - ColorChoice::Auto => io::stdin().is_terminal(), - ColorChoice::Never => false, - }); - if cfg!(debug_assertions) { - success.expect("logic error in CLI_ENABLE_COLOR"); - } - - tracing_subscriber::registry() - .with(HumanLayer::new().with_color_output(*dynix::SHOULD_COLOR)) - .with(EnvFilter::from_default_env()) - .init(); - - tracing::debug!("Parsed command-line arguments: {args:?}"); - - { - use dynix::args::Subcommand::*; - match &args.subcommand { - Append(append_args) => dynix::do_append(args.clone(), append_args.clone())?, - Delta(delta_args) => dynix::do_delta(args.clone(), delta_args.clone())?, - }; - } - - Ok(()) -} - -fn main() -> ExitCode { - match main_wrapped() { - Ok(_) => ExitCode::SUCCESS, - Err(e) => { - eprintln!("dynix: error: {}", e); - ExitCode::FAILURE - } - } -} diff --git a/src/nixcmd.rs b/src/nixcmd.rs deleted file mode 100644 index 3121565..0000000 --- a/src/nixcmd.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[allow(unused_imports)] -use crate::prelude::*; - -#[derive(Debug, Clone, PartialEq, Hash)] -pub(crate) struct NixEvalExpr { - pub(crate) expr: E, - pub(crate) attrpath: A, -} - -impl NixEvalExpr -where - E: AsRef, - A: AsRef, -{ - pub(crate) fn into_command(self) -> Command { - let mut cmd = Command::new("nix-instantiate"); - cmd.arg("--eval") - .arg("--json") - .arg("--strict") - .arg("--expr") - .arg(self.expr) - .arg("-A") - .arg(self.attrpath); - - cmd - } -} diff --git a/src/source.rs b/src/source.rs deleted file mode 100644 index c0099a1..0000000 --- a/src/source.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::{ - cell::{Ref, RefCell}, - hash::Hash, - io::{BufRead, BufReader, BufWriter}, - ops::Deref, - ptr, - sync::{Arc, Mutex, OnceLock}, -}; - -use crate::Line; -use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET}; -#[allow(unused_imports)] -use crate::prelude::*; - -use fs_err::OpenOptions; -use itertools::Itertools; - -pub fn replace_file<'a>( - path: &Path, - contents: impl IntoIterator, -) -> Result<(), IoError> { - let tmp_path = path.with_added_extension(".tmp"); - let tmp_file = File::options() - .create(true) - .write(true) - .truncate(true) - .custom_flags(libc::O_EXCL | libc::O_CLOEXEC) - .open(&tmp_path)?; - - let mut writer = BufWriter::new(tmp_file); - for slice in contents { - writer.write_all(slice)?; - } - - writer.flush()?; - drop(writer); - - // Rename the temporary file to the new file, which is atomic (TODO: I think). - fs_err::rename(&tmp_path, &path)?; - - Ok(()) -} - -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct SourceLine { - pub line: Line, - pub path: Arc, - pub text: Arc, -} - -impl SourceLine { - pub fn text(&self) -> Arc { - Arc::clone(&self.text) - } - - pub fn text_ref(&self) -> &str { - &self.text - } - - pub fn text_bytes(&self) -> Arc<[u8]> { - let len: usize = self.text.as_bytes().len(); - - // We need to consume an Arc, but we are &self. - let text = Arc::clone(&self.text); - let str_ptr: *const str = Arc::into_raw(text); - let start: *const u8 = str_ptr.cast(); - let slice_ptr: *const [u8] = ptr::slice_from_raw_parts(start, len); - - unsafe { Arc::<[u8]>::from_raw(slice_ptr) } - } - - pub fn text_bytes_ref(&self) -> &[u8] { - self.text.as_bytes() - } - - pub fn path(&self) -> Arc { - Arc::clone(&self.path) - } -} - -impl Display for SourceLine { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!( - f, - "{ANSI_MAGENTA}{}{ANSI_RESET}:{ANSI_GREEN}{}{ANSI_RESET}: `{ANSI_CYAN}{}{ANSI_RESET}`", - self.path.display(), - self.line.linenr(), - self.text.trim(), - ) - } -} - -#[derive(Debug, Clone)] -pub struct SourceFile { - path: Arc, - file: Arc>, - /// References to `SourceFile` do not prevent mutating `lines`. - /// Also `lines` is lazily initialized. - lines: Arc>>>, -} - -impl SourceFile { - /// Panics if `path` is a directory path instead of a file path. - pub fn open_from(path: Arc, options: OpenOptions) -> Result { - trace!( - "SourceFile::open_from(path={:?}, options={:?})", - path, - options.options(), - ); - assert!(path.file_name().is_some()); - - let file = Arc::new(Mutex::new(options.open(&*path)?)); - - Ok(Self { - path, - file, - lines: Default::default(), - }) - } - - pub fn buf_reader(&mut self) -> Result, IoError> { - let file_mut = Arc::get_mut(&mut self.file) - .unwrap_or_else(|| panic!("'File' for {} has existing handle", self.path.display())) - .get_mut() - .unwrap_or_else(|e| { - panic!("'File' for {} was mutex-poisoned: {e}", self.path.display()) - }); - - let reader = BufReader::new(file_mut); - - Ok(reader) - } - - fn _lines(&self) -> Result, IoError> { - if let Some(lines) = self.lines.get() { - let as_slice = Ref::map(lines.borrow(), |lines| lines.as_slice()); - return Ok(as_slice); - } - let lines = BufReader::new(&*self.file.lock().unwrap()) - .lines() - .enumerate() - .map(|(index, line_res)| { - line_res.map(|line| SourceLine { - line: Line::from_index(index as u64), - path: Arc::clone(&self.path), - text: Arc::from(line), - }) - }) - .collect::, IoError>>()?; - // Mutex should have dropped by now. - debug_assert!(self.file.try_lock().is_ok()); - - self.lines.set(RefCell::new(lines)).unwrap(); - - Ok(self._lines_slice()) - } - - pub fn lines(&self) -> Result + '_, IoError> { - self._lines() - } - - pub fn line(&self, line: Line) -> Result + '_, IoError> { - let lines_lock = self._lines()?; - let line = Ref::map(lines_lock, |lines| &lines[line.index() as usize]); - - Ok(line) - } - - /// `lines` but already be initialized. - fn _lines_slice(&self) -> Ref<'_, [SourceLine]> { - debug_assert!(self.lines.get().is_some()); - Ref::map(self.lines.get().unwrap().borrow(), |lines| lines.as_slice()) - } - - /// With debug assertions, panics if `lines` are not contiguous. - pub fn insert_lines(&mut self, new_lines: &[SourceLine]) -> Result<(), IoError> { - if new_lines.is_empty() { - return Ok(()); - } - let num_lines_before_new = new_lines.last().unwrap().line.prev().index() as usize; - - debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); - - let path = self.path(); - let cur_lines = self.lines()?; - let first_half = cur_lines - .iter() - .take(num_lines_before_new) - .map(SourceLine::text); - let middle = new_lines.iter().map(SourceLine::text); - let second_half = cur_lines - .iter() - .skip(num_lines_before_new) - .map(SourceLine::text); - - let final_lines: Vec = first_half - .chain(middle) - .chain(second_half) - .enumerate() - .map(|(idx, text)| SourceLine { - line: Line::from_index(idx as u64), - text, - path: self.path(), - }) - .collect(); - - // Assert lines are continuous. - debug_assert!(final_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); - debug_assert_eq!(cur_lines.len() + new_lines.len(), final_lines.len()); - - drop(cur_lines); - - let data = final_lines - .iter() - .map(SourceLine::text_bytes_ref) - .pipe(|iterator| Itertools::intersperse(iterator, b"\n")); - replace_file(&path, data)?; - - // Finally, update state. - self.lines.get().unwrap().replace(final_lines); - - Ok(()) - } - - pub fn path(&self) -> Arc { - Arc::clone(&self.path) - } -} - -impl PartialEq for SourceFile { - fn eq(&self, other: &Self) -> bool { - *self.path == *other.path - } -} diff --git a/tests/default.nix b/tests/default.nix index 40369b9..5e3d082 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -4,7 +4,7 @@ src = fetchTree (builtins.parseFlakeRef "github:Qyriad/nur-packages"); in import src { inherit pkgs; }, lib ? qpkgs.lib, - dynix ? qpkgs.callPackage ../modules-package.nix { }, + dynix ? qpkgs.callPackage ../package.nix { }, }: let runDynixTest = testModule: pkgs.testers.runNixOSTest { From d7a0cbefe537abad8be34ff22a266518379cf71e Mon Sep 17 00:00:00 2001 From: Qyriad Date: Wed, 11 Feb 2026 13:19:59 +0100 Subject: [PATCH 06/18] tests: refactor --- .editorconfig | 2 +- default.nix | 3 +- package.nix | 22 +++++++-- tests/default.nix | 27 +++++++++-- tests/dynix-vm-configuration.nix | 59 +++++++++++++++++++++++ tests/gotosocial/configuration.nix | 54 --------------------- tests/gotosocial/test-script.py | 6 --- tests/gotosocial/test.nix | 7 ++- tests/harmonia/configuration.nix | 50 ++------------------ tests/harmonia/test-script.py | 4 -- tests/harmonia/test.nix | 14 ++++-- tests/mk-test-configuration-dot-nix.nix | 63 +++++++++++++++++++++++++ 12 files changed, 184 insertions(+), 127 deletions(-) create mode 100644 tests/dynix-vm-configuration.nix create mode 100644 tests/mk-test-configuration-dot-nix.nix diff --git a/.editorconfig b/.editorconfig index aab3429..061699c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,5 +11,5 @@ indent_style = space indent_size = 4 [*.nix] -indent_style = tab +indent_style = space indent_size = 2 diff --git a/default.nix b/default.nix index b17a6c7..4372c35 100644 --- a/default.nix +++ b/default.nix @@ -5,7 +5,8 @@ in import src { inherit pkgs; }, }: let inherit (qpkgs) lib; - dynix = qpkgs.callPackage ./package.nix { }; + dynix = qpkgs.callPackage ./package.nix { } + |> qpkgs.stdlib.mkStdenvPretty; byStdenv = lib.mapAttrs (stdenvName: stdenv: let withStdenv = dynix.override { inherit stdenv; }; dynix' = withStdenv.overrideAttrs (prev: { diff --git a/package.nix b/package.nix index d29908a..3d0e673 100644 --- a/package.nix +++ b/package.nix @@ -2,9 +2,12 @@ lib, stdenvNoCC, callPackage, + linkFarm, }: let stdenv = stdenvNoCC; -in stdenv.mkDerivation (self: { +in stdenv.mkDerivation (finalAttrs: let + self = finalAttrs.finalPackage; +in { name = "dynix-modules"; strictDeps = true; @@ -21,12 +24,15 @@ in stdenv.mkDerivation (self: { phases = [ "unpackPhase" "patchPhase" "installPhase" ]; + modulesOut = "${placeholder "modules"}/share/nixos/modules/dynix"; + installPhase = lib.dedent '' + runHook preInstall mkdir -p "$out" cp -r * "$out/" mkdir -p "$modules/share/nixos/modules/dynix" - cp --reflink=auto -r "$out/"* "$modules/share/nixos/modules/dynix/" + cp --reflink=auto -r "$out/"* "$modulesOut/" ''; passthru.mkDevShell = { @@ -39,7 +45,7 @@ in stdenv.mkDerivation (self: { p.beartype ]); in mkShell' { - name = "devshell-for-${self.finalPackage.name}"; + name = "devshell-for-${self.name}"; packages = [ pyEnv ]; env.PYTHONPATH = [ "${pyEnv}/${pyEnv.sitePackages}" @@ -48,13 +54,19 @@ in stdenv.mkDerivation (self: { ] |> lib.concatStringsSep ":"; }; - passthru.modulesPath = self.finalPackage.modules + "/share/nixos/modules"; + passthru.modulesPath = self.modules + "/share/nixos/modules"; passthru.tests = lib.fix (callPackage ./tests { - dynix = self.finalPackage; + dynix = self; }).packages; + passthru.allTests = linkFarm "dynix-all-tests" self.tests; + meta = { + longDescription = lib.dedent '' + Default output contains the modules at top-level, meant for `import`. + The `modules` output contains the modules prefixed under `/share/nixos/modules/dynix`. + ''; outputsToInstall = [ "modules" ]; }; }) diff --git a/tests/default.nix b/tests/default.nix index 5e3d082..dc690a6 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -3,23 +3,35 @@ qpkgs ? let src = fetchTree (builtins.parseFlakeRef "github:Qyriad/nur-packages"); in import src { inherit pkgs; }, + callPackage ? qpkgs.callPackage, lib ? qpkgs.lib, dynix ? qpkgs.callPackage ../package.nix { }, }: let + mkDynixConfigurationDotNix = callPackage ./mk-test-configuration-dot-nix.nix { }; + runDynixTest = testModule: pkgs.testers.runNixOSTest { imports = [ testModule ]; + # NOTE: these are arguments to each *test module*. + # Not the NixOS modules of the test's nodes. + _module.args = { inherit mkDynixConfigurationDotNix; }; + # Why is this argument called "extraBaseModule**s**" but take a single module argument... # Also note this is an extra base module for each node of the test, # not an extra test module. extraBaseModules = { name, config, options, modulesPath, ... }: { + /** + * Everything in this module will disappear once nixos-rebuild switch happens. + * So each test will need to use `mkDynixConfigurationDotNix` to get + * ./dynix-vm-configuration included in the in-VM configuration. + */ + imports = (import "${modulesPath}/module-list.nix") ++ [ ./module-allow-rebuild-in-vm.nix - "${modulesPath}/testing/test-instrumentation.nix" + ./dynix-vm-configuration.nix (toString dynix) ]; - environment.systemPackages = [ dynix ]; systemd.services."install-dynix" = { enable = true; @@ -27,9 +39,18 @@ serviceConfig.RemainAfterExit = true; path = [ config.system.path ]; wantedBy = [ "multi-user.target" ]; + serviceConfig.requisteOf = [ "multi-user.target" ]; after = [ "default.target" ]; script = '' - nix profile install -vv "$(realpath /run/current-system/sw/share/nixos/modules/dynix/)" + nix profile install -vv "${dynix.modules}" + + mkdir -vp /etc/nixos + nixos-generate-config + cp -rv --dereference /run/current-system/sw/share/nixos/*.nix /etc/nixos/ + if ! [[ -e /etc/nixos/dynix-vm-configuration.nix ]]; then + echo "FAILURE" + echo "FAILURE" >&2 + fi ''; }; diff --git a/tests/dynix-vm-configuration.nix b/tests/dynix-vm-configuration.nix new file mode 100644 index 0000000..fbc6193 --- /dev/null +++ b/tests/dynix-vm-configuration.nix @@ -0,0 +1,59 @@ +{ pkgs, lib, modulesPath, ... }: +let + moduleList = import "${modulesPath}/module-list.nix"; + + dynixFromSearchPath = let + res = builtins.tryEval ; + in lib.optional res.success res.value; +in +{ + imports = [ + "${modulesPath}/testing/test-instrumentation.nix" + ] ++ lib.concatLists [ + dynixFromSearchPath + moduleList + ]; + + system.switch.enable = true; + system.includeBuildDependencies = true; + documentation.enable = false; + + boot.loader.grub = { + enable = true; + device = "/dev/vda"; + forceInstall = true; + }; + + nix = { + package = pkgs.lixPackageSets.latest.lix; + nixPath = [ + "nixpkgs=${pkgs.path}" + "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" + ]; + + settings = { + experimental-features = [ "nix-command" "pipe-operator" ]; + substituters = lib.mkForce [ ]; + hashed-mirrors = null; + connect-timeout = 1; + # For my debugging purposes. + show-trace = true; + }; + }; + + environment.pathsToLink = [ "/share" ]; + environment.extraOutputsToInstall = [ "modules" ]; + environment.variables = { + "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; + }; + + environment.shellAliases = { + ls = "eza --long --header --group --group-directories-first --classify --binary"; + }; + + environment.systemPackages = with pkgs; [ + eza + fd + ripgrep + ]; +} diff --git a/tests/gotosocial/configuration.nix b/tests/gotosocial/configuration.nix index deef056..8ada5c8 100644 --- a/tests/gotosocial/configuration.nix +++ b/tests/gotosocial/configuration.nix @@ -1,48 +1,9 @@ { pkgs, lib, config, modulesPath, ... }: let name = config.networking.hostName; - moduleList = import (modulesPath + "/module-list.nix"); - - dynixFromSearchPath = let - res = builtins.tryEval ; - in lib.optional res.success res.value; in { - imports = moduleList ++ [ - "${modulesPath}/testing/test-instrumentation.nix" - ./hardware-configuration.nix - ] ++ lib.concatLists [ - dynixFromSearchPath - ]; - - system.switch.enable = true; - documentation.enable = false; - networking.hostName = "gotosocial-machine"; - - boot.loader.grub = { - enable = true; - device = "/dev/vda"; - forceInstall = true; - }; - - nix = { - package = pkgs.lixPackageSets.latest.lix; - nixPath = [ - "nixpkgs=${pkgs.path}" - "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" - ]; - - settings = { - experimental-features = [ "nix-command" "pipe-operator" ]; - substituters = lib.mkForce [ ]; - hashed-mirrors = null; - connect-timeout = 1; - # For my debugging purposes. - show-trace = true; - }; - }; - services.gotosocial = { enable = true; setupPostgresqlDB = true; @@ -53,19 +14,4 @@ in }; dynamicism.for.gotosocial.enable = true; - - environment.pathsToLink = [ "/share" ]; - environment.extraOutputsToInstall = [ "modules" ]; - environment.variables = { - "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; - }; - - environment.shellAliases = { - ls = "eza --long --header --group --group-directories-first --classify --binary"; - }; - environment.systemPackages = with pkgs; [ - eza - fd - ripgrep - ]; } diff --git a/tests/gotosocial/test-script.py b/tests/gotosocial/test-script.py index d8b2af8..2fa42ed 100644 --- a/tests/gotosocial/test-script.py +++ b/tests/gotosocial/test-script.py @@ -52,12 +52,6 @@ def get_config_file() -> str: machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() machine.log("INIT") -run_log(machine, "journalctl --no-pager -eu install-dynix.service") - -machine.succeed("nixos-generate-config") -machine.succeed("mkdir -vp /etc/nixos") -# Dereference is required since that configuration.nix is probably a symlink to the store. -machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/") machine.log("REBUILDING configuration inside VM") machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") diff --git a/tests/gotosocial/test.nix b/tests/gotosocial/test.nix index 9628b93..fd5bfbb 100644 --- a/tests/gotosocial/test.nix +++ b/tests/gotosocial/test.nix @@ -1,4 +1,4 @@ -{ ... }: +{ mkDynixConfigurationDotNix, config, ... }: { name = "nixos-test-dynamicism-gotosocial"; @@ -17,7 +17,10 @@ imports = [ ./configuration.nix ]; environment.systemPackages = let - configFileTree = pkgs.callPackage ./configuration-package.nix { }; + configFileTree = mkDynixConfigurationDotNix { + inherit (config) name; + configuration = ./configuration.nix; + }; in [ configFileTree ]; diff --git a/tests/harmonia/configuration.nix b/tests/harmonia/configuration.nix index 7bf19bb..37494cb 100644 --- a/tests/harmonia/configuration.nix +++ b/tests/harmonia/configuration.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, config, modulesPath, ... }: +{ lib, modulesPath, ... }: let moduleList = import "${modulesPath}/module-list.nix"; @@ -8,10 +8,10 @@ let in { imports = [ - "${modulesPath}/testing/test-instrumentation.nix" - ./hardware-configuration.nix + #"${modulesPath}/testing/test-instrumentation.nix" + #./hardware-configuration.nix ] ++ lib.concatLists [ - dynixFromSearchPath + #dynixFromSearchPath moduleList ]; @@ -26,47 +26,5 @@ in }; }; - system.switch.enable = true; - documentation.enable = false; - networking.hostName = "harmonia-machine"; - - boot.loader.grub = { - enable = true; - device = "/dev/vda"; - forceInstall = true; - }; - - nix = { - package = pkgs.lixPackageSets.latest.lix; - nixPath = [ - "nixpkgs=${pkgs.path}" - "/nix/var/nix/profiles/per-user/root/profile/share/nixos/modules" - ]; - - settings = { - experimental-features = [ "nix-command" "pipe-operator" ]; - substituters = lib.mkForce [ ]; - hashed-mirrors = null; - connect-timeout = 1; - # For my debugging purposes. - show-trace = true; - }; - }; - - environment.pathsToLink = [ "/share" ]; - environment.extraOutputsToInstall = [ "modules" ]; - environment.variables = { - "NIXOS_CONFIG" = "/etc/nixos/configuration.nix"; - }; - - environment.shellAliases = { - ls = "eza --long --header --group --group-directories-first --classify --binary"; - }; - - environment.systemPackages = with pkgs; [ - eza - fd - ripgrep - ]; } diff --git a/tests/harmonia/test-script.py b/tests/harmonia/test-script.py index a91bf78..e96a053 100644 --- a/tests/harmonia/test-script.py +++ b/tests/harmonia/test-script.py @@ -66,10 +66,6 @@ assert int(config_toml['workers']) == 4, f"{config_toml['workers']=} != 4" assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256" with machine.nested("must succeed: initial nixos-rebuild switch"): - machine.succeed("nixos-generate-config") - machine.succeed("mkdir -vp /etc/nixos") - # Dereference is required since that configuration.nix is probably a symlink to the store. - machine.succeed("cp -rv --dereference /run/current-system/sw/share/nixos/configuration.nix /etc/nixos/") machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") # Config should not have changed. diff --git a/tests/harmonia/test.nix b/tests/harmonia/test.nix index 0aea418..873c7dd 100644 --- a/tests/harmonia/test.nix +++ b/tests/harmonia/test.nix @@ -1,4 +1,4 @@ -{ ... }: +{ mkDynixConfigurationDotNix, config, ... }: { name = "nixos-test-dynamicism-harmonia"; @@ -10,10 +10,14 @@ imports = [ ./configuration.nix ]; environment.systemPackages = let - configFileTree = pkgs.runCommand "${name}-configuration-dot-nix" { } '' - set -euo pipefail - install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix" - ''; + #configFileTree = pkgs.runCommand "${name}-configuration-dot-nix" { } '' + # set -euo pipefail + # install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix" + #''; + configFileTree = mkDynixConfigurationDotNix { + inherit (config) name; + configuration = ./configuration.nix; + }; in [ configFileTree ]; diff --git a/tests/mk-test-configuration-dot-nix.nix b/tests/mk-test-configuration-dot-nix.nix new file mode 100644 index 0000000..a31cf50 --- /dev/null +++ b/tests/mk-test-configuration-dot-nix.nix @@ -0,0 +1,63 @@ +{ + lib, + stdenvNoCC, +}: let + stdenv = stdenvNoCC; + + mkDynixConfigurationDotNix = finalAttrs: { + name, + configuration, + }: assert lib.isStringLike configuration; let + self = finalAttrs.finalPackage; + in { + name = "configuration-dot-nix-for-${name}"; + strictDeps = true; + __structuredAttrs = true; + preferLocalBuild = true; + + phases = [ "installPhase" ]; + + #outputs = [ "out" "modules" ]; + outputs = [ "out" ]; + modulesOut = "${placeholder "out"}/share/nixos"; + #modulesOut = "${placeholder "modules"}/share/nixos/modules"; + + baseConfiguration = configuration; + + installPhase = '' + runHook preInstall + + install -Dm a=r "$baseConfiguration" "$modulesOut/test-configuration.nix" + install -Dm a=r "${./dynix-vm-configuration.nix}" "$modulesOut/dynix-vm-configuration.nix" + + echo "/** GENERATED BY mk-test-configuration-dot-nix! */" >> "$modulesOut/configuration.nix" + echo "{ ... }:" >> "$modulesOut/configuration.nix" + echo >> "$modulesOut/configuration.nix" + echo >> "$modulesOut/configuration.nix" + echo "{" >> "$modulesOut/configuration.nix" + echo " imports = [" >> "$modulesOut/configuration.nix" + echo " ./test-configuration.nix" >> "$modulesOut/configuration.nix" + echo " ./dynix-vm-configuration.nix" >> "$modulesOut/configuration.nix" + echo " ./hardware-configuration.nix" >> "$modulesOut/configuration.nix" + echo " ];" >> "$modulesOut/configuration.nix" + echo "}" >> "$modulesOut/configuration.nix" + + #mkdir -p "$out" + #cp -r --reflink=auto "$modulesOut/"* "$out/" + + runHook postInstall + ''; + + passthru = { + modulesPath = self.out + "/share/nixos"; + configuration = self.out + "/share/nixos/configuration.nix"; + }; + + meta = { + #outputsToInstall = [ "modules" ]; + }; + }; +in lib.extendMkDerivation { + constructDrv = stdenv.mkDerivation; + extendDrvArgs = mkDynixConfigurationDotNix; +} From 15641360ca6402c934a2aa2ae1fe9ec012e0491e Mon Sep 17 00:00:00 2001 From: Qyriad Date: Wed, 11 Feb 2026 13:19:59 +0100 Subject: [PATCH 07/18] make dynamicism/default.nix a stub for dynamicism.nix --- modules/dynamicism/default.nix | 144 +---------------------------- modules/dynamicism/dynamicism.nix | 145 ++++++++++++++++++++++++++++++ modules/dynamicism/gotosocial.nix | 2 - 3 files changed, 147 insertions(+), 144 deletions(-) create mode 100644 modules/dynamicism/dynamicism.nix diff --git a/modules/dynamicism/default.nix b/modules/dynamicism/default.nix index 5e2b4e7..1fca96c 100644 --- a/modules/dynamicism/default.nix +++ b/modules/dynamicism/default.nix @@ -1,145 +1,5 @@ -{ pkgs, lib, config, options, ... }: -let - inherit (lib.options) - mkOption - showOption - ; - inherit (lib.asserts) - checkAssertWarn - ; - t = lib.types; +{ ... }: - inherit (import ./lib.nix { inherit lib; }) - typeCheck - convenientAttrPath - concatFoldl - recUpdateFoldl - recUpdateFoldlAttrs - ; - - evalNixos = import (pkgs.path + "/nixos"); - - opts = options.dynamicism; - - subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs; - - finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath: - lib.setAttrByPath optPath (lib.getAttrFromPath optPath config) - ) submod.source-options; - - ourAssertions = lib.concatAttrValues { - unitsExist = subOpts - |> lib.attrValues - |> concatFoldl (submod: submod.systemd-services-updated.value - |> lib.map (unit: { - assertion = config.systemd.units.${unit}.enable or false; - message = '' - ${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}' - ''; - }) - |> lib.optionals submod.enable.value - ); - - optsExist = concatFoldl (submod: lib.optionals submod.enable.value (lib.map (optPath: { - assertion = lib.hasAttrByPath optPath options; - message = "'${showOption submod.source-options.loc}' specified non-existent option '${showOption optPath}'"; - }) submod.source-options.value)) (lib.attrValues subOpts); - }; -in { - # - # Interface. - # - options.dynamicism = { - for = mkOption { - type = t.attrsOf (t.submoduleWith { - modules = [ ./submodule.nix ]; - shorthandOnlyDefinesConfig = false; - specialArgs = { inherit pkgs; }; - }); - default = { }; - }; - - finalSettings = mkOption { - type = t.attrsOf t.raw; - internal = true; - readOnly = true; - description = '' - Attrset of each `source-options` tree to their actual values. - ''; - }; - - doChange = mkOption { - type = t.functionTo t.pathInStore; - readOnly = true; - description = '' - The function to call to Do The Thing. - ''; - }; - }; - - # Assertions. - config.assertions = ourAssertions; - - # - # Generic implementation. - # - config.system.activationScripts."dynamicism-reset" = { - deps = [ "etc" "stdio" "specialfs" ]; - text = '' - echo "DYNIX: removing existing systemd dropins" - # FIXME: do for each enabled submodule - if [[ -d /run/systemd/system ]]; then - rm -v /run/systemd/system/*/dynix-*.conf || true - fi - ''; - }; - config.dynamicism = { - doChange = { - option, - value, - configuration ? builtins.getEnv "NIXOS_CONFIG", - }: let - loc = opts.doChange.loc ++ [ "(function argument)" "value" ]; - option' = typeCheck loc convenientAttrPath option; - nixosAfter = evalNixos { - configuration = { config, ... }: { - imports = [ - configuration - (lib.setAttrByPath option' (lib.mkOverride (-999) value)) - ]; - - environment.systemPackages = [ - config.dynamicism.for.gotosocial.activate - ]; - }; - }; - - allActivations = config.dynamicism.for - |> lib.filterAttrs (name: submod: submod.enable) - |> lib.mapAttrsToList (name: submod: submod.activate); - allActivationScripts = pkgs.writeShellApplication { - name = "dynamicism-activate"; - runtimeInputs = allActivations; - text = nixosAfter.config.dynamicism.for - |> lib.filterAttrs (name: submod: submod.enable) - |> lib.mapAttrsToList (name: submod: '' - echo "Activating dynamicism for ${name}" - ${lib.getExe submod.activate} - '') - |> lib.concatStringsSep "\n"; - }; - in allActivationScripts; - - finalSettings = config.dynamicism.for - |> recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) - |> checkAssertWarn ourAssertions [ ]; - }; - - # Implementations. - imports = [ - ./gotosocial.nix - ./harmonia.nix - #./tzupdate.nix - ]; + imports = [ ./dynamicism.nix ]; } diff --git a/modules/dynamicism/dynamicism.nix b/modules/dynamicism/dynamicism.nix new file mode 100644 index 0000000..5e2b4e7 --- /dev/null +++ b/modules/dynamicism/dynamicism.nix @@ -0,0 +1,145 @@ +{ pkgs, lib, config, options, ... }: +let + inherit (lib.options) + mkOption + showOption + ; + inherit (lib.asserts) + checkAssertWarn + ; + t = lib.types; + + inherit (import ./lib.nix { inherit lib; }) + typeCheck + convenientAttrPath + concatFoldl + recUpdateFoldl + recUpdateFoldlAttrs + ; + + evalNixos = import (pkgs.path + "/nixos"); + + opts = options.dynamicism; + + subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs; + + finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath: + lib.setAttrByPath optPath (lib.getAttrFromPath optPath config) + ) submod.source-options; + + ourAssertions = lib.concatAttrValues { + unitsExist = subOpts + |> lib.attrValues + |> concatFoldl (submod: submod.systemd-services-updated.value + |> lib.map (unit: { + assertion = config.systemd.units.${unit}.enable or false; + message = '' + ${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}' + ''; + }) + |> lib.optionals submod.enable.value + ); + + optsExist = concatFoldl (submod: lib.optionals submod.enable.value (lib.map (optPath: { + assertion = lib.hasAttrByPath optPath options; + message = "'${showOption submod.source-options.loc}' specified non-existent option '${showOption optPath}'"; + }) submod.source-options.value)) (lib.attrValues subOpts); + }; +in +{ + # + # Interface. + # + options.dynamicism = { + for = mkOption { + type = t.attrsOf (t.submoduleWith { + modules = [ ./submodule.nix ]; + shorthandOnlyDefinesConfig = false; + specialArgs = { inherit pkgs; }; + }); + default = { }; + }; + + finalSettings = mkOption { + type = t.attrsOf t.raw; + internal = true; + readOnly = true; + description = '' + Attrset of each `source-options` tree to their actual values. + ''; + }; + + doChange = mkOption { + type = t.functionTo t.pathInStore; + readOnly = true; + description = '' + The function to call to Do The Thing. + ''; + }; + }; + + # Assertions. + config.assertions = ourAssertions; + + # + # Generic implementation. + # + config.system.activationScripts."dynamicism-reset" = { + deps = [ "etc" "stdio" "specialfs" ]; + text = '' + echo "DYNIX: removing existing systemd dropins" + # FIXME: do for each enabled submodule + if [[ -d /run/systemd/system ]]; then + rm -v /run/systemd/system/*/dynix-*.conf || true + fi + ''; + }; + config.dynamicism = { + doChange = { + option, + value, + configuration ? builtins.getEnv "NIXOS_CONFIG", + }: let + loc = opts.doChange.loc ++ [ "(function argument)" "value" ]; + option' = typeCheck loc convenientAttrPath option; + nixosAfter = evalNixos { + configuration = { config, ... }: { + imports = [ + configuration + (lib.setAttrByPath option' (lib.mkOverride (-999) value)) + ]; + + environment.systemPackages = [ + config.dynamicism.for.gotosocial.activate + ]; + }; + }; + + allActivations = config.dynamicism.for + |> lib.filterAttrs (name: submod: submod.enable) + |> lib.mapAttrsToList (name: submod: submod.activate); + allActivationScripts = pkgs.writeShellApplication { + name = "dynamicism-activate"; + runtimeInputs = allActivations; + text = nixosAfter.config.dynamicism.for + |> lib.filterAttrs (name: submod: submod.enable) + |> lib.mapAttrsToList (name: submod: '' + echo "Activating dynamicism for ${name}" + ${lib.getExe submod.activate} + '') + |> lib.concatStringsSep "\n"; + }; + in allActivationScripts; + + finalSettings = config.dynamicism.for + |> recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) + |> checkAssertWarn ourAssertions [ ]; + }; + + # Implementations. + imports = [ + ./gotosocial.nix + ./harmonia.nix + #./tzupdate.nix + ]; +} diff --git a/modules/dynamicism/gotosocial.nix b/modules/dynamicism/gotosocial.nix index cbcff2b..f8b30a9 100644 --- a/modules/dynamicism/gotosocial.nix +++ b/modules/dynamicism/gotosocial.nix @@ -1,7 +1,5 @@ { pkgs, lib, config, ... }: let - cfg = config.dynamicism.for.gotosocial; - settingsFormat = pkgs.formats.yaml { }; configFile = settingsFormat.generate "gotosocial-override.yml" config.services.gotosocial.settings; in From 4268754afb3101ec14892fd25675d54ac2a6f4b6 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 13 Feb 2026 17:19:54 +0100 Subject: [PATCH 08/18] little more cleanup --- tests/default.nix | 6 ++++-- tests/mk-test-configuration-dot-nix.nix | 15 +++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/default.nix b/tests/default.nix index dc690a6..49e875c 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -28,7 +28,9 @@ */ imports = (import "${modulesPath}/module-list.nix") ++ [ + # For the VM node, but not the in-VM configuration.nix ./module-allow-rebuild-in-vm.nix + # For the VM node, and the in-VM configuration.nix ./dynix-vm-configuration.nix (toString dynix) ]; @@ -37,12 +39,12 @@ enable = true; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; + serviceConfig.RequisteOf = [ "multi-user.target" ]; path = [ config.system.path ]; wantedBy = [ "multi-user.target" ]; - serviceConfig.requisteOf = [ "multi-user.target" ]; after = [ "default.target" ]; script = '' - nix profile install -vv "${dynix.modules}" + nix profile install -vv "${dynix.modules}" # " mkdir -vp /etc/nixos nixos-generate-config diff --git a/tests/mk-test-configuration-dot-nix.nix b/tests/mk-test-configuration-dot-nix.nix index a31cf50..da574ef 100644 --- a/tests/mk-test-configuration-dot-nix.nix +++ b/tests/mk-test-configuration-dot-nix.nix @@ -6,6 +6,9 @@ mkDynixConfigurationDotNix = finalAttrs: { name, + /** A *path* to a `configuration.nix`-like NixOS module. + * NOT a NixOS module *value*. + */ configuration, }: assert lib.isStringLike configuration; let self = finalAttrs.finalPackage; @@ -17,18 +20,17 @@ phases = [ "installPhase" ]; - #outputs = [ "out" "modules" ]; outputs = [ "out" ]; modulesOut = "${placeholder "out"}/share/nixos"; - #modulesOut = "${placeholder "modules"}/share/nixos/modules"; baseConfiguration = configuration; + dynixVmConfiguration = ./dynix-vm-configuration.nix; installPhase = '' runHook preInstall install -Dm a=r "$baseConfiguration" "$modulesOut/test-configuration.nix" - install -Dm a=r "${./dynix-vm-configuration.nix}" "$modulesOut/dynix-vm-configuration.nix" + install -Dm a=r "$dynixVmConfiguration" "$modulesOut/dynix-vm-configuration.nix" echo "/** GENERATED BY mk-test-configuration-dot-nix! */" >> "$modulesOut/configuration.nix" echo "{ ... }:" >> "$modulesOut/configuration.nix" @@ -42,9 +44,6 @@ echo " ];" >> "$modulesOut/configuration.nix" echo "}" >> "$modulesOut/configuration.nix" - #mkdir -p "$out" - #cp -r --reflink=auto "$modulesOut/"* "$out/" - runHook postInstall ''; @@ -52,10 +51,6 @@ modulesPath = self.out + "/share/nixos"; configuration = self.out + "/share/nixos/configuration.nix"; }; - - meta = { - #outputsToInstall = [ "modules" ]; - }; }; in lib.extendMkDerivation { constructDrv = stdenv.mkDerivation; From af46de56287c4017fab6efbe45804da2f1068946 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 13 Feb 2026 20:03:17 +0100 Subject: [PATCH 09/18] include post-change configuration in allActivationScripts passthru --- modules/dynamicism/dynamicism.nix | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/dynamicism/dynamicism.nix b/modules/dynamicism/dynamicism.nix index 5e2b4e7..c68b859 100644 --- a/modules/dynamicism/dynamicism.nix +++ b/modules/dynamicism/dynamicism.nix @@ -115,21 +115,19 @@ in }; }; - allActivations = config.dynamicism.for - |> lib.filterAttrs (name: submod: submod.enable) - |> lib.mapAttrsToList (name: submod: submod.activate); - allActivationScripts = pkgs.writeShellApplication { - name = "dynamicism-activate"; - runtimeInputs = allActivations; - text = nixosAfter.config.dynamicism.for - |> lib.filterAttrs (name: submod: submod.enable) - |> lib.mapAttrsToList (name: submod: '' - echo "Activating dynamicism for ${name}" - ${lib.getExe submod.activate} - '') - |> lib.concatStringsSep "\n"; - }; - in allActivationScripts; + runAllActivationScripts = nixosAfter.config.dynamicism.for + |> lib.filterAttrs (lib.const (lib.getAttr "enable")) + |> lib.mapAttrsToList (name: submod: '' + echo "Activating dynamic configuration for ${name}" + ${lib.getExe submod.activate} + '') + |> lib.concatStringsSep "\n"; + + in pkgs.writeShellApplication { + name = "dynamicism-activate"; + text = runAllActivationScripts; + passthru.configuration = nixosAfter; + }; finalSettings = config.dynamicism.for |> recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) From 3ed2f2e1a8de72a3c6080f6f6afb979900d6bae0 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 13 Feb 2026 21:12:55 +0100 Subject: [PATCH 10/18] 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; +} From 76b5ac628da397bc564dd0fbbce29dac5f01d538 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 11/18] start new dynamic apply function --- modules/dynamicism/dynamicism.nix | 51 ++++++++++++++++++++++++++++++- modules/dynamicism/lib.nix | 6 ++-- modules/dynamicism/submodule.nix | 16 +++++++++- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/modules/dynamicism/dynamicism.nix b/modules/dynamicism/dynamicism.nix index 482b364..1060f85 100644 --- a/modules/dynamicism/dynamicism.nix +++ b/modules/dynamicism/dynamicism.nix @@ -23,6 +23,8 @@ let subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs; + seqTrue = v: lib.seq v true; + finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath: lib.setAttrByPath optPath (lib.getAttrFromPath optPath config) ) submod.source-options; @@ -55,7 +57,9 @@ in type = t.attrsOf (t.submoduleWith { modules = [ ./submodule.nix ]; shorthandOnlyDefinesConfig = false; - specialArgs = { inherit pkgs; }; + specialArgs = { + host = { inherit pkgs options config; }; + }; }); default = { }; }; @@ -69,6 +73,12 @@ in ''; }; + applyDynamicConfiguration = mkOption { + #type = t.functionTo t.pathInStore; + type = t.functionTo t.raw; + readOnly = true; + }; + doChange = mkOption { type = t.functionTo t.pathInStore; readOnly = true; @@ -95,6 +105,45 @@ in ''; }; config.dynamicism = { + + applyDynamicConfiguration = { + baseConfiguration ? builtins.getEnv "NIXOS_CONFIG", + newConfiguration ? baseConfiguration + "/dynamic.nix", + }: let + locFor = appendage: lib.concatLists [ + opts.applyDynamicConfiguration.loc + [ "(function argument)" ] + (lib.toList appendage) + ]; + + _file = "«inline module in ${showOption opts.applyDynamicConfiguration.loc}»"; + + nixosBefore = evalNixos { + configuration = { ... }: { + inherit _file; + imports = [ baseConfiguration ]; + }; + }; + + nixosAfter = evalNixos { + configuration = { ... }: { + inherit _file; + imports = [ baseConfiguration newConfiguration ]; + }; + }; + + submodulesChanged = lib.filter (submodName: + nixosBefore.config.dynamicism.for.${submodName}.finalSettings + != + nixosAfter.config.dynamicism.for.${submodName}.finalSettings + ) (lib.attrNames config.dynamicism.for); + in + assert seqTrue (typeCheck (locFor "baseConfiguration") t.deferredModule baseConfiguration); + assert seqTrue (typeCheck (locFor "newConfiguration") t.deferredModule newConfiguration); + { + inherit submodulesChanged; + }; + doChange = { option, value, diff --git a/modules/dynamicism/lib.nix b/modules/dynamicism/lib.nix index a988ae4..a957f38 100644 --- a/modules/dynamicism/lib.nix +++ b/modules/dynamicism/lib.nix @@ -4,12 +4,12 @@ t = lib.types; in lib.fix (self: { /** Perform module-system type checking and resolving on a single option value. */ - typeCheck = loc: option: value: - assert lib.isOptionType option; + typeCheck = loc: optionType: value: + assert lib.isOptionType optionType; assert lib.isList loc; assert lib.all lib.isString loc; let - merged = lib.modules.mergeDefinitions loc option [ { + merged = lib.modules.mergeDefinitions loc optionType [ { inherit value; file = "«inline»"; } ]; diff --git a/modules/dynamicism/submodule.nix b/modules/dynamicism/submodule.nix index 50e0889..bf26b2a 100644 --- a/modules/dynamicism/submodule.nix +++ b/modules/dynamicism/submodule.nix @@ -1,8 +1,8 @@ { name, - pkgs, lib, config, + host, ... }: let @@ -19,6 +19,12 @@ let ; t = lib.types; + inherit (import ./lib.nix { inherit lib; }) + recUpdateFoldl + ; + + pkgs = host.pkgs; + /** Either a list of strings, or a dotted string that will be split. */ convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str); @@ -59,6 +65,14 @@ in default = lib.attrNames config.unitDropins; }; + finalSettings = mkOption { + type = t.attrsOf t.raw; + internal = true; + default = recUpdateFoldl (optPath: + lib.setAttrByPath optPath (lib.getAttrFromPath optPath host.config) + ) config.source-options; + }; + configFile = mkOption { type = t.pathInStore; internal = true; From dfdf027bc6f61255071d17501c4769a24b9c328f Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 12/18] restore old files now! --- Cargo.lock | 920 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 25 ++ package.nix | 64 +++- src/args.rs | 97 +++++ src/color.rs | 53 +++ src/lib.rs | 179 +++++++++ src/line.rs | 43 +++ src/main.rs | 48 +++ src/nixcmd.rs | 27 ++ src/source.rs | 234 ++++++++++++ tests/default.nix | 2 +- 11 files changed, 1676 insertions(+), 16 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/args.rs create mode 100644 src/color.rs create mode 100644 src/lib.rs create mode 100644 src/line.rs create mode 100644 src/main.rs create mode 100644 src/nixcmd.rs create mode 100644 src/source.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..eef46d8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,920 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "command-error" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6ac3abfcf15b4536b079bcee923683fe3dc1173b70be5e05ccf28ca112862e" +dependencies = [ + "dyn-clone", + "process-wrap", + "shell-words", + "utf8-command", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "dynix" +version = "0.1.0" +dependencies = [ + "clap", + "command-error", + "fs-err", + "itertools", + "libc", + "serde", + "serde_json", + "tap", + "tracing", + "tracing-human-layer", + "tracing-subscriber", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fs-err" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7" +dependencies = [ + "autocfg", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +dependencies = [ + "supports-color 2.1.0", + "supports-color 3.0.2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "process-wrap" +version = "8.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" +dependencies = [ + "indexmap", + "nix", + "tracing", + "windows", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "supports-color" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" +dependencies = [ + "is-terminal", + "is_ci", +] + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix", + "windows-sys 0.60.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "terminal_size", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-human-layer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b285fd79bba4659408f5d290b3f30fd69d428c630d8c00bb4ba255f2501d50e3" +dependencies = [ + "itertools", + "owo-colors", + "parking_lot", + "textwrap", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "parking_lot", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "utf8-command" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b151524c94cda49046b29e6d20b03092ff9363b02acc1bf3994da60910c55b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dd24d30 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "dynix" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "dynix" +path = "src/main.rs" + +[lib] +name = "dynix" +path = "src/lib.rs" + +[dependencies] +clap = { version = "4.5.54", features = ["color", "derive"] } +command-error = "0.8.0" +fs-err = "3.2.2" +itertools = "0.14.0" +libc = { version = "0.2.180", features = ["extra_traits"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +tap = "1.0.1" +tracing = { version = "0.1.44", features = ["attributes"] } +tracing-human-layer = "0.2.1" +tracing-subscriber = { version = "0.3.22", default-features = false, features = ["std", "env-filter", "fmt", "ansi", "registry", "parking_lot"] } diff --git a/package.nix b/package.nix index 3d0e673..a336a2e 100644 --- a/package.nix +++ b/package.nix @@ -1,38 +1,65 @@ { lib, - stdenvNoCC, + clangStdenv, callPackage, linkFarm, + rustHooks, + rustPackages, + versionCheckHook, +}: lib.callWith' rustPackages ({ + rustPlatform, + cargo, }: let - stdenv = stdenvNoCC; + stdenv = clangStdenv; + cargoToml = lib.importTOML ./Cargo.toml; + cargoPackage = cargoToml.package; in stdenv.mkDerivation (finalAttrs: let self = finalAttrs.finalPackage; in { - name = "dynix-modules"; + pname = cargoPackage.name; + version = cargoPackage.version; strictDeps = true; __structuredAttrs = true; outputs = [ "out" "modules" ]; - src = lib.fileset.toSource { + doCheck = true; + doInstallCheck = true; + + modulesSrc = lib.fileset.toSource { root = ./modules/dynamicism; fileset = lib.fileset.unions [ ./modules/dynamicism ]; }; - phases = [ "unpackPhase" "patchPhase" "installPhase" ]; + src = lib.fileset.toSource { + root = ./.; + fileset = lib.fileset.unions [ + ./Cargo.toml + ./Cargo.lock + ./src + ]; + }; + + cargoDeps = rustPlatform.importCargoLock { + lockFile = ./Cargo.lock; + }; + + nativeBuildInputs = rustHooks.asList ++ [ + cargo + ]; + + nativeInstallCheckInputs = [ + versionCheckHook + ]; modulesOut = "${placeholder "modules"}/share/nixos/modules/dynix"; - installPhase = lib.dedent '' - runHook preInstall - mkdir -p "$out" - cp -r * "$out/" - - mkdir -p "$modules/share/nixos/modules/dynix" - cp --reflink=auto -r "$out/"* "$modulesOut/" + postInstall = lib.dedent '' + mkdir -p "$modulesOut" + cp -r "$modulesSrc/"* "$modulesOut/" ''; passthru.mkDevShell = { @@ -46,7 +73,12 @@ in { ]); in mkShell' { name = "devshell-for-${self.name}"; - packages = [ pyEnv ]; + inputsFrom = [ self ]; + packages = [ + pyEnv + rustPackages.rustc + rustPackages.rustfmt + ]; env.PYTHONPATH = [ "${pyEnv}/${pyEnv.sitePackages}" # Cursed. @@ -55,6 +87,7 @@ in { }; passthru.modulesPath = self.modules + "/share/nixos/modules"; + passthru.dynix = self.modulesPath + "/dynix"; passthru.tests = lib.fix (callPackage ./tests { dynix = self; @@ -67,6 +100,7 @@ in { Default output contains the modules at top-level, meant for `import`. The `modules` output contains the modules prefixed under `/share/nixos/modules/dynix`. ''; - outputsToInstall = [ "modules" ]; + mainProgram = "dynix"; + outputsToInstall = [ "out" "modules" ]; }; -}) +})) diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..cc2fa07 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,97 @@ +use std::sync::Arc; + +use clap::ColorChoice; + +use crate::prelude::*; + +//#[derive(Debug, Clone, PartialEq)] +//#[derive(clap::Args)] +//#[group(required = true, multiple = false)] +//pub enum Config +//{ +// Flake, +//} + +#[derive(Debug, Clone, PartialEq)] +pub struct NixOsOption { + name: String, + value: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct NixOptionParseError(pub Box); + +impl Display for NixOptionParseError { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{}", self.0) + } +} + +impl From for NixOptionParseError { + fn from(value: String) -> Self { + Self(value.into_boxed_str()) + } +} + +impl StdError for NixOptionParseError {} + +impl FromStr for NixOsOption { + type Err = NixOptionParseError; + + fn from_str(s: &str) -> Result { + // FIXME: allow escaping equals sign? + let Some(delim) = s.find('=') else { + return Err(format!("equals sign not found in {}", s).into()); + }; + + todo!(); + } +} + +#[derive(Debug, Clone, PartialEq, clap::Parser)] +pub struct AppendCmd { + #[arg(required = true)] + pub name: Arc, + #[arg(required = true)] + pub value: Arc, +} + +#[derive(Debug, Clone, PartialEq, clap::Parser)] +pub struct DeltaCmd {} + +#[derive(Debug, Clone, PartialEq, clap::Subcommand)] +#[command(flatten_help = true)] +pub enum Subcommand { + Append(AppendCmd), + // TODO: rename + Delta(DeltaCmd), +} + +#[derive(Debug, Clone, PartialEq, clap::Parser)] +#[command(version, about, author)] +#[command(arg_required_else_help(true), args_override_self(true))] +#[command(propagate_version = true)] +pub struct Args { + #[arg(long, global(true), default_value = "auto")] + pub color: ColorChoice, + + // FIXME: default to /etc/configuration.nix, or something? + #[arg(long, global(true), default_value = "./configuration.nix")] + pub file: Arc, + + #[command(subcommand)] + pub subcommand: Subcommand, +} +///// Flakeref to a base configuration to modify. +//#[arg(group = "config", long, default_value("."))] +//#[arg(long, default_value(Some(".")))] +//flake: Option>>, +// +//#[arg(group = "config", long)] +//expr: Option, + +//impl Parser { +// fn eval_cmd(&self) { +// todo!(); +// } +//} diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..1e420f6 --- /dev/null +++ b/src/color.rs @@ -0,0 +1,53 @@ +use std::{ + env, + sync::{LazyLock, OnceLock}, +}; + +#[allow(unused_imports)] +use crate::prelude::*; + +/// The actual, final value for whether color should be used, based on CLI and environment values. +pub static SHOULD_COLOR: LazyLock = LazyLock::new(|| is_clicolor_forced() || is_color_reqd()); + +/// Initialized from the `--color` value from the CLI, along with `io::stdin().is_terminal()`. +pub static _CLI_ENABLE_COLOR: OnceLock = OnceLock::new(); + +fn is_color_reqd() -> bool { + _CLI_ENABLE_COLOR.get().copied().unwrap_or(false) +} + +fn is_clicolor_forced() -> bool { + env::var("CLICOLOR_FORCE") + .map(|value| { + if value.is_empty() || value == "0" { + false + } else { + true + } + }) + .unwrap_or(false) +} + +/// Silly wrapper around LazyLock<&'static str> to impl Display. +pub(crate) struct _LazyLockDisplay(LazyLock<&'static str>); +impl Display for _LazyLockDisplay { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + Display::fmt(&*self.0, f) + } +} + +pub(crate) const ANSI_GREEN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { + SHOULD_COLOR.then_some("\x1b[32m").unwrap_or_default() +})); + +pub(crate) const ANSI_MAGENTA: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { + SHOULD_COLOR.then_some("\x1b[35m").unwrap_or_default() +})); + +pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { + SHOULD_COLOR.then_some("\x1b[36m").unwrap_or_default() +})); + +pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| { + SHOULD_COLOR.then_some("\x1b[0m").unwrap_or_default() +})); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1956803 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,179 @@ +use std::{iter, sync::Arc}; + +pub(crate) mod prelude { + #![allow(unused_imports)] + + pub use std::{ + error::Error as StdError, + ffi::{OsStr, OsString}, + fmt::{Display, Formatter, Result as FmtResult}, + io::{Error as IoError, Read, Seek, SeekFrom, Write}, + path::{Path, PathBuf}, + process::{Command, ExitCode}, + str::FromStr, + }; + + #[cfg(unix)] + pub use std::os::{ + fd::AsRawFd, + unix::ffi::{OsStrExt, OsStringExt}, + }; + + pub type BoxDynError = Box; + + pub use command_error::{CommandExt, OutputLike}; + pub use fs_err::File; + #[cfg(unix)] + pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt}; + + pub use tap::{Pipe, Tap}; + + pub use tracing::{Level, debug, error, info, trace, warn}; +} + +use prelude::*; + +pub mod args; +pub use args::{AppendCmd, Args, DeltaCmd}; +mod color; +pub use color::{_CLI_ENABLE_COLOR, SHOULD_COLOR}; +pub mod line; +mod nixcmd; +pub use line::Line; +pub mod source; +pub use source::SourceLine; + +use serde::{Deserialize, Serialize}; + +use crate::source::SourceFile; + +pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; + +#[tracing::instrument(level = "debug")] +pub fn do_delta(args: Arc, delta_args: DeltaCmd) -> Result<(), BoxDynError> { + todo!(); +} + +#[tracing::instrument(level = "debug")] +pub fn do_append(args: Arc, append_args: AppendCmd) -> Result<(), BoxDynError> { + let filepath = Path::new(&args.file); + let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") { + iter::once(OsStr::new("./")) + .chain(filepath.iter()) + .collect() + } else { + filepath.to_path_buf() + }; + + // Get what file that thing is defined in. + let def_path = get_where(&append_args.name, &filepath)?; + + let mut opts = File::options(); + opts.read(true) + .write(true) + .create(false) + .custom_flags(libc::O_CLOEXEC); + let source_file = SourceFile::open_from(Arc::from(def_path), opts)?; + + let pri = get_highest_prio(&append_args.name, source_file.clone())?; + let new_pri = pri - 1; + + let new_pri_line = get_next_prio_line( + source_file.clone(), + append_args.name.into(), + new_pri, + append_args.value.into(), + )?; + + eprintln!("new_pri_line={new_pri_line}"); + + write_next_prio(source_file, new_pri_line)?; + + Ok(()) +} + +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] +pub struct DefinitionWithLocation { + pub file: Box, + pub value: Box, +} + +pub fn expr_for_configuration(source_file: &Path) -> OsString { + [ + OsStr::new("import { configuration = "), + source_file.as_os_str(), + OsStr::new("; }"), + ] + .into_iter() + .collect() +} + +pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result, BoxDynError> { + let expr = expr_for_configuration(configuration_nix); + let attrpath = format!("options.{}.definitionsWithLocations", option_name); + + let output = nixcmd::NixEvalExpr { expr, attrpath } + .into_command() + .output_checked_utf8()?; + let stdout = output.stdout(); + + let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?; + let last_location = definitions.into_iter().last().unwrap(); + + Ok(Box::from(last_location.file)) +} + +pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result { + // Get the current highest priority. + + let expr = expr_for_configuration(&source.path()); + + // Get the highest priority, and the file its defined in. + let attrpath = format!("options.{}.highestPrio", option_name); + let output = nixcmd::NixEvalExpr { expr, attrpath } + .into_command() + .output_checked_utf8()?; + let stdout = output.stdout(); + let highest_prio = i64::from_str(stdout.trim())?; + + Ok(highest_prio) +} + +pub fn get_next_prio_line( + source: SourceFile, + option_name: Arc, + new_prio: i64, + new_value: Arc, +) -> Result { + let source_lines = source.lines()?; + let last_line = source_lines.last(); + assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]")); + let last_line = last_line.unwrap(); + + let new_line = SourceLine { + line: last_line.line, + path: source.path(), + text: Arc::from(format!( + " {option_name} = lib.mkOverride ({new_prio}) ({new_value});", + )), + }; + + Ok(new_line) +} + +pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> { + let new_mod_start = SourceLine { + line: new_line.line.prev(), + path: source.path(), + text: Arc::from(" {"), + }; + let new_mod_end = SourceLine { + line: new_line.line.next(), + path: source.path(), + text: Arc::from(" }"), + }; + + source.insert_lines(&[new_mod_start, new_line, new_mod_end])?; + + Ok(()) +} diff --git a/src/line.rs b/src/line.rs new file mode 100644 index 0000000..e7ec169 --- /dev/null +++ b/src/line.rs @@ -0,0 +1,43 @@ +use std::num::NonZeroU64; + +#[allow(unused_imports)] +use crate::prelude::*; + + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Line(pub u64); + +/// Constructors. +impl Line { + pub const fn from_index(index: u64) -> Self { + Self(index) + } + + pub const fn from_linenr(linenr: NonZeroU64) -> Self { + Self(linenr.get() - 1) + } + + pub const fn next(self) -> Self { + Self::from_index(self.index() + 1) + } + + /// Panics if self is line index 0. + pub const fn prev(self) -> Self { + Self::from_index(self.index() - 1) + } +} + +/// Getters. +impl Line { + /// 0-indexed + pub const fn index(self) -> u64 { + self.0 + } + + /// 1-indexed + pub const fn linenr(self) -> u64 { + self.0 + 1 + } +} + +pub struct Lines(Vec); diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ef00265 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,48 @@ +use std::io::{self, IsTerminal}; +use std::process::ExitCode; +use std::{error::Error as StdError, sync::Arc}; + +use clap::{ColorChoice, Parser as _}; +use tracing_human_layer::HumanLayer; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{EnvFilter, layer::SubscriberExt}; + +fn main_wrapped() -> Result<(), Box> { + let args = Arc::new(dynix::Args::parse()); + + let success = dynix::_CLI_ENABLE_COLOR.set(match args.color { + ColorChoice::Always => true, + ColorChoice::Auto => io::stdin().is_terminal(), + ColorChoice::Never => false, + }); + if cfg!(debug_assertions) { + success.expect("logic error in CLI_ENABLE_COLOR"); + } + + tracing_subscriber::registry() + .with(HumanLayer::new().with_color_output(*dynix::SHOULD_COLOR)) + .with(EnvFilter::from_default_env()) + .init(); + + tracing::debug!("Parsed command-line arguments: {args:?}"); + + { + use dynix::args::Subcommand::*; + match &args.subcommand { + Append(append_args) => dynix::do_append(args.clone(), append_args.clone())?, + Delta(delta_args) => dynix::do_delta(args.clone(), delta_args.clone())?, + }; + } + + Ok(()) +} + +fn main() -> ExitCode { + match main_wrapped() { + Ok(_) => ExitCode::SUCCESS, + Err(e) => { + eprintln!("dynix: error: {}", e); + ExitCode::FAILURE + } + } +} diff --git a/src/nixcmd.rs b/src/nixcmd.rs new file mode 100644 index 0000000..3121565 --- /dev/null +++ b/src/nixcmd.rs @@ -0,0 +1,27 @@ +#[allow(unused_imports)] +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Hash)] +pub(crate) struct NixEvalExpr { + pub(crate) expr: E, + pub(crate) attrpath: A, +} + +impl NixEvalExpr +where + E: AsRef, + A: AsRef, +{ + pub(crate) fn into_command(self) -> Command { + let mut cmd = Command::new("nix-instantiate"); + cmd.arg("--eval") + .arg("--json") + .arg("--strict") + .arg("--expr") + .arg(self.expr) + .arg("-A") + .arg(self.attrpath); + + cmd + } +} diff --git a/src/source.rs b/src/source.rs new file mode 100644 index 0000000..c0099a1 --- /dev/null +++ b/src/source.rs @@ -0,0 +1,234 @@ +use std::{ + cell::{Ref, RefCell}, + hash::Hash, + io::{BufRead, BufReader, BufWriter}, + ops::Deref, + ptr, + sync::{Arc, Mutex, OnceLock}, +}; + +use crate::Line; +use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET}; +#[allow(unused_imports)] +use crate::prelude::*; + +use fs_err::OpenOptions; +use itertools::Itertools; + +pub fn replace_file<'a>( + path: &Path, + contents: impl IntoIterator, +) -> Result<(), IoError> { + let tmp_path = path.with_added_extension(".tmp"); + let tmp_file = File::options() + .create(true) + .write(true) + .truncate(true) + .custom_flags(libc::O_EXCL | libc::O_CLOEXEC) + .open(&tmp_path)?; + + let mut writer = BufWriter::new(tmp_file); + for slice in contents { + writer.write_all(slice)?; + } + + writer.flush()?; + drop(writer); + + // Rename the temporary file to the new file, which is atomic (TODO: I think). + fs_err::rename(&tmp_path, &path)?; + + Ok(()) +} + +#[derive(Debug, Clone, PartialEq, Hash)] +pub struct SourceLine { + pub line: Line, + pub path: Arc, + pub text: Arc, +} + +impl SourceLine { + pub fn text(&self) -> Arc { + Arc::clone(&self.text) + } + + pub fn text_ref(&self) -> &str { + &self.text + } + + pub fn text_bytes(&self) -> Arc<[u8]> { + let len: usize = self.text.as_bytes().len(); + + // We need to consume an Arc, but we are &self. + let text = Arc::clone(&self.text); + let str_ptr: *const str = Arc::into_raw(text); + let start: *const u8 = str_ptr.cast(); + let slice_ptr: *const [u8] = ptr::slice_from_raw_parts(start, len); + + unsafe { Arc::<[u8]>::from_raw(slice_ptr) } + } + + pub fn text_bytes_ref(&self) -> &[u8] { + self.text.as_bytes() + } + + pub fn path(&self) -> Arc { + Arc::clone(&self.path) + } +} + +impl Display for SourceLine { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!( + f, + "{ANSI_MAGENTA}{}{ANSI_RESET}:{ANSI_GREEN}{}{ANSI_RESET}: `{ANSI_CYAN}{}{ANSI_RESET}`", + self.path.display(), + self.line.linenr(), + self.text.trim(), + ) + } +} + +#[derive(Debug, Clone)] +pub struct SourceFile { + path: Arc, + file: Arc>, + /// References to `SourceFile` do not prevent mutating `lines`. + /// Also `lines` is lazily initialized. + lines: Arc>>>, +} + +impl SourceFile { + /// Panics if `path` is a directory path instead of a file path. + pub fn open_from(path: Arc, options: OpenOptions) -> Result { + trace!( + "SourceFile::open_from(path={:?}, options={:?})", + path, + options.options(), + ); + assert!(path.file_name().is_some()); + + let file = Arc::new(Mutex::new(options.open(&*path)?)); + + Ok(Self { + path, + file, + lines: Default::default(), + }) + } + + pub fn buf_reader(&mut self) -> Result, IoError> { + let file_mut = Arc::get_mut(&mut self.file) + .unwrap_or_else(|| panic!("'File' for {} has existing handle", self.path.display())) + .get_mut() + .unwrap_or_else(|e| { + panic!("'File' for {} was mutex-poisoned: {e}", self.path.display()) + }); + + let reader = BufReader::new(file_mut); + + Ok(reader) + } + + fn _lines(&self) -> Result, IoError> { + if let Some(lines) = self.lines.get() { + let as_slice = Ref::map(lines.borrow(), |lines| lines.as_slice()); + return Ok(as_slice); + } + let lines = BufReader::new(&*self.file.lock().unwrap()) + .lines() + .enumerate() + .map(|(index, line_res)| { + line_res.map(|line| SourceLine { + line: Line::from_index(index as u64), + path: Arc::clone(&self.path), + text: Arc::from(line), + }) + }) + .collect::, IoError>>()?; + // Mutex should have dropped by now. + debug_assert!(self.file.try_lock().is_ok()); + + self.lines.set(RefCell::new(lines)).unwrap(); + + Ok(self._lines_slice()) + } + + pub fn lines(&self) -> Result + '_, IoError> { + self._lines() + } + + pub fn line(&self, line: Line) -> Result + '_, IoError> { + let lines_lock = self._lines()?; + let line = Ref::map(lines_lock, |lines| &lines[line.index() as usize]); + + Ok(line) + } + + /// `lines` but already be initialized. + fn _lines_slice(&self) -> Ref<'_, [SourceLine]> { + debug_assert!(self.lines.get().is_some()); + Ref::map(self.lines.get().unwrap().borrow(), |lines| lines.as_slice()) + } + + /// With debug assertions, panics if `lines` are not contiguous. + pub fn insert_lines(&mut self, new_lines: &[SourceLine]) -> Result<(), IoError> { + if new_lines.is_empty() { + return Ok(()); + } + let num_lines_before_new = new_lines.last().unwrap().line.prev().index() as usize; + + debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); + + let path = self.path(); + let cur_lines = self.lines()?; + let first_half = cur_lines + .iter() + .take(num_lines_before_new) + .map(SourceLine::text); + let middle = new_lines.iter().map(SourceLine::text); + let second_half = cur_lines + .iter() + .skip(num_lines_before_new) + .map(SourceLine::text); + + let final_lines: Vec = first_half + .chain(middle) + .chain(second_half) + .enumerate() + .map(|(idx, text)| SourceLine { + line: Line::from_index(idx as u64), + text, + path: self.path(), + }) + .collect(); + + // Assert lines are continuous. + debug_assert!(final_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line)); + debug_assert_eq!(cur_lines.len() + new_lines.len(), final_lines.len()); + + drop(cur_lines); + + let data = final_lines + .iter() + .map(SourceLine::text_bytes_ref) + .pipe(|iterator| Itertools::intersperse(iterator, b"\n")); + replace_file(&path, data)?; + + // Finally, update state. + self.lines.get().unwrap().replace(final_lines); + + Ok(()) + } + + pub fn path(&self) -> Arc { + Arc::clone(&self.path) + } +} + +impl PartialEq for SourceFile { + fn eq(&self, other: &Self) -> bool { + *self.path == *other.path + } +} diff --git a/tests/default.nix b/tests/default.nix index a381346..43dd66f 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -32,7 +32,7 @@ ./module-allow-rebuild-in-vm.nix # For the VM node, and the in-VM configuration.nix ./dynix-vm-configuration.nix - (toString dynix) + dynix.dynix ]; systemd.services."install-dynix" = { From 4aac9a8dba1ce26220bbdb890f6c6d229db217cd Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 13/18] maint(deps): update Cargo.lock --- Cargo.lock | 60 +++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eef46d8..7f13360 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,9 +69,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "cfg-if" @@ -87,9 +87,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.54" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "colorchoice" @@ -190,9 +190,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.2.2" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf68cef89750956493a66a10f512b9e58d9db21f2a573c079c0bdf1207a54a7" +checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" dependencies = [ "autocfg", ] @@ -271,9 +271,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "linux-raw-sys" @@ -307,9 +307,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "nix" @@ -385,9 +385,9 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -406,9 +406,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -424,9 +424,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -435,9 +435,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustix" @@ -555,9 +555,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-linebreak" @@ -915,6 +915,6 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" From f46c2a9934d57b8d418c97e99e43b84991a70760 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 14/18] packaging: convert split derivation to hybrid split/multi-derivation --- package.nix | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/package.nix b/package.nix index a336a2e..0252d88 100644 --- a/package.nix +++ b/package.nix @@ -27,13 +27,6 @@ in { doCheck = true; doInstallCheck = true; - modulesSrc = lib.fileset.toSource { - root = ./modules/dynamicism; - fileset = lib.fileset.unions [ - ./modules/dynamicism - ]; - }; - src = lib.fileset.toSource { root = ./.; fileset = lib.fileset.unions [ @@ -43,6 +36,26 @@ in { ]; }; + dynixModules = stdenv.mkDerivation (modulesSelf: { + pname = "dynix-modules"; + inherit (self) version; + src = lib.fileset.toSource { + root = ./modules/dynamicism; + fileset = lib.fileset.unions [ + ./modules/dynamicism + ]; + }; + + phases = [ "unpackPhase" "patchPhase" "installPhase" ]; + + modulesOut = "${placeholder "out"}/share/nixos/modules/dynix"; + + installPhase = lib.dedent '' + mkdir -p "$modulesOut" + cp -r "$src/"* "$modulesOut/" + ''; + }); + cargoDeps = rustPlatform.importCargoLock { lockFile = ./Cargo.lock; }; @@ -55,11 +68,9 @@ in { versionCheckHook ]; - modulesOut = "${placeholder "modules"}/share/nixos/modules/dynix"; - - postInstall = lib.dedent '' - mkdir -p "$modulesOut" - cp -r "$modulesSrc/"* "$modulesOut/" + postFixup = lib.dedent '' + mkdir -p "$modules" + cp -r --reflink=auto "$dynixModules/"* "$modules/" ''; passthru.mkDevShell = { @@ -97,8 +108,10 @@ in { meta = { longDescription = lib.dedent '' - Default output contains the modules at top-level, meant for `import`. + Default output contains the Rust binary. The `modules` output contains the modules prefixed under `/share/nixos/modules/dynix`. + The `dynix` passthru attr is a shortcut for the modules output *with* the modules prefix, + and thus `dynix.dynix` can be passed directly to `imports = [`. ''; mainProgram = "dynix"; outputsToInstall = [ "out" "modules" ]; From 26397ccf3797c29e27fe0dfb4d9261944c873f54 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 15/18] packaging: use fenix for dev shell --- flake.lock | 17 +++++++++++++++++ flake.nix | 8 +++++++- package.nix | 5 +++-- shell.nix | 6 +++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index 68f5005..f07fa2c 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,21 @@ { "nodes": { + "fenix": { + "flake": false, + "locked": { + "lastModified": 1771226183, + "narHash": "sha256-AbaMtaLbe37l2VI/KSRk63PuBnX/YDDFL0G1eFMbvwI=", + "owner": "nix-community", + "repo": "fenix", + "rev": "2e3759c5ef51f320eb0aaf83f2a32baae33db237", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -53,6 +69,7 @@ }, "root": { "inputs": { + "fenix": "fenix", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "qyriad-nur": "qyriad-nur" diff --git a/flake.nix b/flake.nix index 8e0dfb6..f4270f0 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,10 @@ flake = false; }; flake-utils.url = "github:numtide/flake-utils"; + fenix = { + url = "github:nix-community/fenix"; + flake = false; + }; qyriad-nur = { url = "github:Qyriad/nur-packages"; flake = false; @@ -15,11 +19,13 @@ self, nixpkgs, flake-utils, + fenix, qyriad-nur, }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; qpkgs = import qyriad-nur { inherit pkgs; }; inherit (qpkgs) lib; + fenixLib = import fenix { inherit pkgs; }; dynix = import ./default.nix { inherit pkgs qpkgs; }; extraVersions = lib.mapAttrs' (stdenvName: value: { @@ -27,7 +33,7 @@ inherit value; }) dynix.byStdenv; - devShell = import ./shell.nix { inherit pkgs qpkgs dynix; }; + devShell = import ./shell.nix { inherit pkgs qpkgs dynix fenixLib; }; extraDevShells = lib.mapAttrs' (stdenvName: value: { name = "${stdenvName}-dynix"; inherit value; diff --git a/package.nix b/package.nix index 0252d88..69116af 100644 --- a/package.nix +++ b/package.nix @@ -77,6 +77,7 @@ in { path, mkShell, python3Packages, + fenixToolchain, }: let mkShell' = mkShell.override { inherit stdenv; }; pyEnv = python3Packages.python.withPackages (p: [ @@ -87,8 +88,8 @@ in { inputsFrom = [ self ]; packages = [ pyEnv - rustPackages.rustc - rustPackages.rustfmt + stdenv.cc + fenixToolchain ]; env.PYTHONPATH = [ "${pyEnv}/${pyEnv.sitePackages}" diff --git a/shell.nix b/shell.nix index 3506e2d..e2af1b4 100644 --- a/shell.nix +++ b/shell.nix @@ -9,10 +9,14 @@ src = fetchTarball "https://github.com/Qyriad/nur-packages/archive/main.tar.gz"; in import src { inherit pkgs; }, dynix ? import ./default.nix { inherit pkgs qpkgs; }, + fenixLib ? let + src = fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz"; + in import src { inherit pkgs; }, + fenixToolchain ? fenixLib.latest.toolchain, }: let inherit (pkgs) lib; - mkDevShell = dynix: qpkgs.callPackage dynix.mkDevShell { }; + mkDevShell = dynix: qpkgs.callPackage dynix.mkDevShell { inherit fenixToolchain; }; devShell = mkDevShell dynix; byStdenv = lib.mapAttrs (lib.const mkDevShell) dynix.byStdenv; From da509d97c7050c48cf7e8adf25b172feed54b95c Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 16/18] packaging: split derivations correctly to avoid Rust rebuilds for module changes --- package.nix | 92 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/package.nix b/package.nix index 69116af..6e16ec3 100644 --- a/package.nix +++ b/package.nix @@ -27,18 +27,69 @@ in { doCheck = true; doInstallCheck = true; - src = lib.fileset.toSource { - root = ./.; - fileset = lib.fileset.unions [ - ./Cargo.toml - ./Cargo.lock - ./src - ]; + phases = [ "unpackPhase" "patchPhase" "installPhase" ]; + + src = linkFarm "dynix-source" { + inherit (self) dynixCommand dynixModules; }; - dynixModules = stdenv.mkDerivation (modulesSelf: { - pname = "dynix-modules"; + installPhase = lib.dedent '' + runHook preInstall + + mkdir -p "$out" + cp -r --reflink=auto "$dynixCommand/"* "$out/" + mkdir -p "$modules" + cp -r --reflink=auto "$dynixModules/"* "$modules/" + + runHook postInstall + ''; + + # + # SUB-DERIVATONS + # + + dynixCommand = stdenv.mkDerivation (finalAttrs: let + commandSelf = finalAttrs.finalPackage; + in { + pname = "${self.pname}-command"; inherit (self) version; + inherit (self) strictDeps __structuredAttrs; + inherit (self) doCheck doInstallCheck; + + src = lib.fileset.toSource { + root = ./.; + fileset = lib.fileset.unions [ + ./Cargo.toml + ./Cargo.lock + ./src + ]; + }; + + cargoDeps = rustPlatform.importCargoLock { + lockFile = ./Cargo.lock; + }; + + nativeBuildInputs = rustHooks.asList ++ [ + cargo + ]; + + nativeInstallCheckInputs = [ + versionCheckHook + ]; + + meta = { + mainProgram = "dynix"; + }; + }); + + dynixModules = stdenv.mkDerivation (finalAttrs: let + modulesSelf = finalAttrs.finalPackage; + in { + pname = "${self.pname}-modules"; + inherit (self) version; + inherit (self) strictDeps __structuredAttrs; + inherit (self) doCheck doInstallCheck; + src = lib.fileset.toSource { root = ./modules/dynamicism; fileset = lib.fileset.unions [ @@ -51,27 +102,18 @@ in { modulesOut = "${placeholder "out"}/share/nixos/modules/dynix"; installPhase = lib.dedent '' + runHook preInstall + mkdir -p "$modulesOut" cp -r "$src/"* "$modulesOut/" + + runHook postInstall ''; }); - cargoDeps = rustPlatform.importCargoLock { - lockFile = ./Cargo.lock; - }; - - nativeBuildInputs = rustHooks.asList ++ [ - cargo - ]; - - nativeInstallCheckInputs = [ - versionCheckHook - ]; - - postFixup = lib.dedent '' - mkdir -p "$modules" - cp -r --reflink=auto "$dynixModules/"* "$modules/" - ''; + # + # ---------------------------------------------------------------------------- + # passthru.mkDevShell = { path, From 3765e918d6c82d146cf3e3d906a1add29d1d956a Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 16 Feb 2026 18:02:39 +0100 Subject: [PATCH 17/18] IT WORKS --- Cargo.lock | 20 +++++ Cargo.toml | 7 ++ modules/dynamicism/dynamicism.nix | 100 +++++++++++---------- modules/dynamicism/gotosocial.nix | 2 +- modules/dynamicism/lib.nix | 13 +++ modules/dynamicism/submodule.nix | 24 +----- package.nix | 4 +- src/args.rs | 34 +++++++- src/lib.rs | 110 +++++++++++++++--------- tests/default.nix | 11 ++- tests/distccd/test-script.py | 49 ++++++----- tests/distccd/test.nix | 17 ++-- tests/gotosocial/test-script.py | 84 +++++++++++++----- tests/gotosocial/test.nix | 18 ++-- tests/harmonia/test-script.py | 47 +++++----- tests/harmonia/test.nix | 21 ++--- tests/mk-test-configuration-dot-nix.nix | 9 ++ tests/module-allow-rebuild-in-vm.nix | 4 +- 18 files changed, 348 insertions(+), 226 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f13360..cba4ce7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,8 @@ dependencies = [ "fs-err", "itertools", "libc", + "regex", + "regex-lite", "serde", "serde_json", "tap", @@ -422,6 +424,18 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -433,6 +447,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.9" diff --git a/Cargo.toml b/Cargo.toml index dd24d30..2e6dd55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,19 @@ path = "src/main.rs" name = "dynix" path = "src/lib.rs" +[features] +default = ["regex-full"] +regex-full = ["dep:regex"] +regex-lite = ["dep:regex-lite"] + [dependencies] clap = { version = "4.5.54", features = ["color", "derive"] } command-error = "0.8.0" fs-err = "3.2.2" itertools = "0.14.0" libc = { version = "0.2.180", features = ["extra_traits"] } +regex = { version = "1.12.3", optional = true } +regex-lite = { version = "0.1.9", optional = true } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" tap = "1.0.1" diff --git a/modules/dynamicism/dynamicism.nix b/modules/dynamicism/dynamicism.nix index 1060f85..50f921d 100644 --- a/modules/dynamicism/dynamicism.nix +++ b/modules/dynamicism/dynamicism.nix @@ -11,7 +11,6 @@ let inherit (import ./lib.nix { inherit lib; }) typeCheck - convenientAttrPath concatFoldl recUpdateFoldl recUpdateFoldlAttrs @@ -64,6 +63,13 @@ in default = { }; }; + finalEnabledSubmodules = mkOption { + type = options.dynamicism.for.type; + internal = true; + readOnly = true; + default = lib.filterAttrs (lib.const (lib.getAttr "enable")) config.dynamicism.for; + }; + finalSettings = mkOption { type = t.attrsOf t.raw; internal = true; @@ -78,14 +84,6 @@ in type = t.functionTo t.raw; readOnly = true; }; - - doChange = mkOption { - type = t.functionTo t.pathInStore; - readOnly = true; - description = '' - The function to call to Do The Thing. - ''; - }; }; # Assertions. @@ -94,27 +92,49 @@ in # # Generic implementation. # - config.system.activationScripts."dynamicism-reset" = { - deps = [ "etc" "stdio" "specialfs" ]; - text = '' - echo "DYNIX: removing existing systemd dropins" - # FIXME: do for each enabled submodule - if [[ -d /run/systemd/system ]]; then - rm -v /run/systemd/system/*/dynix-*.conf || true + + config.system.activationScripts = config.dynamicism.for + |> lib.filterAttrs (lib.const (lib.getAttr "enable")) + |> lib.mapAttrs' (name: submod: let + forUnit = unitName: assert lib.isString unitName; let + dropinDir = "/run/systemd/system/${unitName}.d"; + systemctl = lib.getExe' pkgs.systemd "systemctl"; + in '' + if [[ -d "${dropinDir}" ]]; then + echo "Removing files in "${dropinDir}" >&2 + + rm -rvf "${dropinDir}/"* + rmdir "${dropinDir}" + + ${systemctl} daemon-reload + ${systemctl} try-reload-or-restart "${unitName}" fi ''; - }; + + in { + name = "dynix-reset-dynamicism-for-${name}"; + value.deps = [ "etc" "stdio" "specialfs" "binsh" "usrbinenv" "var" "udevd" ]; + value.text = '' + echo "Removing existing dynamic overrides for ${name}" >&2 + ${submod.systemd-services-updated |> lib.map forUnit |> lib.concatStringsSep "\n"} + ''; + }); + config.dynamicism = { applyDynamicConfiguration = { baseConfiguration ? builtins.getEnv "NIXOS_CONFIG", - newConfiguration ? baseConfiguration + "/dynamic.nix", + newConfiguration ? (lib.filesystem.dirOf baseConfiguration) + "/dynamic.nix", }: let locFor = appendage: lib.concatLists [ opts.applyDynamicConfiguration.loc [ "(function argument)" ] (lib.toList appendage) ]; + in + assert seqTrue (typeCheck (locFor "baseConfiguration") t.deferredModule baseConfiguration); + assert seqTrue (typeCheck (locFor "newConfiguration") t.deferredModule newConfiguration); + let _file = "«inline module in ${showOption opts.applyDynamicConfiguration.loc}»"; @@ -132,49 +152,25 @@ in }; }; - submodulesChanged = lib.filter (submodName: + submodulesChanged = nixosAfter.config.dynamicism.finalEnabledSubmodules + |> lib.filterAttrs (submodName: _: nixosBefore.config.dynamicism.for.${submodName}.finalSettings != nixosAfter.config.dynamicism.for.${submodName}.finalSettings - ) (lib.attrNames config.dynamicism.for); - in - assert seqTrue (typeCheck (locFor "baseConfiguration") t.deferredModule baseConfiguration); - assert seqTrue (typeCheck (locFor "newConfiguration") t.deferredModule newConfiguration); - { - inherit submodulesChanged; - }; + ); - doChange = { - option, - value, - configuration ? builtins.getEnv "NIXOS_CONFIG", - }: let - loc = opts.doChange.loc ++ [ "(function argument)" "value" ]; - option' = typeCheck loc convenientAttrPath option; - nixosAfter = evalNixos { - configuration = { config, ... }: { - imports = [ - configuration - (lib.setAttrByPath option' (lib.mkOverride (-999) value)) - ]; - - environment.systemPackages = [ - config.dynamicism.for.gotosocial.activate - ]; - }; - }; - - runAllActivationScripts = nixosAfter.config.dynamicism.for - |> lib.filterAttrs (lib.const (lib.getAttr "enable")) - |> lib.mapAttrsToList (name: submod: '' + runForSubmodCalled = name: '' echo "Activating dynamic configuration for ${name}" - ${lib.getExe submod.activate} - '') + ${lib.getExe nixosAfter.config.dynamicism.for.${name}.activate} + ''; + + runForChanged = submodulesChanged + |> lib.mapAttrsToList (name: _: runForSubmodCalled name) |> lib.concatStringsSep "\n"; in pkgs.writeShellApplication { name = "dynamicism-activate"; - text = runAllActivationScripts; + text = runForChanged; passthru.configuration = nixosAfter; }; diff --git a/modules/dynamicism/gotosocial.nix b/modules/dynamicism/gotosocial.nix index f8b30a9..b1fedc6 100644 --- a/modules/dynamicism/gotosocial.nix +++ b/modules/dynamicism/gotosocial.nix @@ -12,7 +12,7 @@ in text = '' [Service] ExecStart= - ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${configFile} start + ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${configFile} server start ''; passthru = { inherit configFile; }; }; diff --git a/modules/dynamicism/lib.nix b/modules/dynamicism/lib.nix index a957f38..ec41ab7 100644 --- a/modules/dynamicism/lib.nix +++ b/modules/dynamicism/lib.nix @@ -18,6 +18,19 @@ in lib.fix (self: { /** Either a list of strings, or a dotted string that will be split. */ convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str); + executablePathInStore = lib.mkOptionType { + name = "exepath"; + description = "executable path in the Nix store"; + descriptionClass = "noun"; + merge = lib.mergeEqualOption; + functor = lib.defaultFunctor "exepath"; + check = x: if lib.isDerivation x then ( + x.meta.mainProgram or null != null + ) else ( + lib.pathInStore.check x + ); + }; + concatFoldl = f: list: lib.foldl' (acc: value: acc ++ (f value)) [ ] list; recUpdateFoldl = f: list: lib.foldl' (acc: value: lib.recursiveUpdate acc (f value)) { } list; recUpdateFoldlAttrs = f: attrs: lib.foldlAttrs (acc: name: value: lib.recursiveUpdate acc (f name value)) { } attrs; diff --git a/modules/dynamicism/submodule.nix b/modules/dynamicism/submodule.nix index bf26b2a..4100217 100644 --- a/modules/dynamicism/submodule.nix +++ b/modules/dynamicism/submodule.nix @@ -14,32 +14,15 @@ let mkEnableOption literalExpression ; - inherit (lib.types) - mkOptionType - ; t = lib.types; inherit (import ./lib.nix { inherit lib; }) + convenientAttrPath + executablePathInStore recUpdateFoldl ; pkgs = host.pkgs; - - /** Either a list of strings, or a dotted string that will be split. */ - convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str); - - executablePathInStore = mkOptionType { - name = "exepath"; - description = "executable path in the Nix store"; - descriptionClass = "noun"; - merge = lib.mergeEqualOption; - functor = lib.defaultFunctor "exepath"; - check = x: if lib.isDerivation x then ( - x.meta.mainProgram or null != null - ) else ( - lib.pathInStore.check x - ); - }; in { options = { @@ -74,8 +57,9 @@ in }; configFile = mkOption { - type = t.pathInStore; + type = t.nullOr t.pathInStore; internal = true; + default = null; }; unitDropins = mkOption { diff --git a/package.nix b/package.nix index 6e16ec3..448ab31 100644 --- a/package.nix +++ b/package.nix @@ -122,9 +122,7 @@ in { fenixToolchain, }: let mkShell' = mkShell.override { inherit stdenv; }; - pyEnv = python3Packages.python.withPackages (p: [ - p.beartype - ]); + pyEnv = python3Packages.python.withPackages (p: [ p.beartype ]); in mkShell' { name = "devshell-for-${self.name}"; inputsFrom = [ self ]; diff --git a/src/args.rs b/src/args.rs index cc2fa07..0028e0f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,4 +1,7 @@ -use std::sync::Arc; +use std::{ + env, + sync::{Arc, LazyLock}, +}; use clap::ColorChoice; @@ -52,6 +55,7 @@ impl FromStr for NixOsOption { pub struct AppendCmd { #[arg(required = true)] pub name: Arc, + #[arg(required = true)] pub value: Arc, } @@ -67,6 +71,28 @@ pub enum Subcommand { Delta(DeltaCmd), } +static DEFAULT_PATH: LazyLock> = LazyLock::new(|| { + // This has to be in a let binding to keep the storage around. + let nixos_config = env::var_os("NIXOS_CONFIG"); + let nixos_config = nixos_config + .as_deref() + .map(Path::new) + .unwrap_or(Path::new("/etc/nixos/configuration.nix")); + + nixos_config + .parent() + .unwrap_or_else(|| { + error!( + "Your $NIXOS_CONFIG value doesn't make sense: {}. Ignoring.", + nixos_config.display(), + ); + Path::new("/etc/nixos") + }) + .join("dynamic.nix") + .into_os_string() + .into_boxed_os_str() +}); + #[derive(Debug, Clone, PartialEq, clap::Parser)] #[command(version, about, author)] #[command(arg_required_else_help(true), args_override_self(true))] @@ -75,8 +101,10 @@ pub struct Args { #[arg(long, global(true), default_value = "auto")] pub color: ColorChoice, - // FIXME: default to /etc/configuration.nix, or something? - #[arg(long, global(true), default_value = "./configuration.nix")] + /// The .nix file with dynamic overrides to modify. + /// [default: $(dirname ${NIXOS_CONFIG-/etc/nixos/configuration.nix})/dynamic.nix] + #[arg(long, global(true), default_value = &**DEFAULT_PATH)] + #[arg(hide_default_value(true))] pub file: Arc, #[command(subcommand)] diff --git a/src/lib.rs b/src/lib.rs index 1956803..cf7839a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ -use std::{iter, sync::Arc}; +use std::{ + iter, + sync::{Arc, LazyLock}, +}; pub(crate) mod prelude { #![allow(unused_imports)] @@ -41,14 +44,38 @@ pub mod line; mod nixcmd; pub use line::Line; pub mod source; -pub use source::SourceLine; +pub use source::{SourceFile, SourceLine}; +#[cfg(all(not(feature = "regex-full"), not(feature = "regex-lite")))] +compile_error!("At least one of features `regex-full` or `regex-lite` must be used"); + +#[cfg(feature = "regex-full")] +use regex as _regex; + +// Having both `regex-full` and `regex-lite` isn't an error; it's just wasteful. +#[cfg(not(feature = "regex-full"))] +use regex_lite as _regex; + +use _regex::Regex; + +use itertools::Itertools; use serde::{Deserialize, Serialize}; -use crate::source::SourceFile; - pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' ']; +/// Regex pattern to extract the priority in a `lib.mkOverride` call. +static MK_OVERRIDE_RE: LazyLock = LazyLock::new(|| { + // Named capture group: priority + // - Word boundary + // - Literal `mkOverride` + // - One or more whitespace characters + // - Literal open parenthesis + // - Named capture group "priority" + // - One or more of: digit characters, or literal `-` + // - Literal close parenthesis + Regex::new(r"(?-u)\bmkOverride\s+\((?[\d-]+)\)").unwrap() +}); + #[tracing::instrument(level = "debug")] pub fn do_delta(args: Arc, delta_args: DeltaCmd) -> Result<(), BoxDynError> { todo!(); @@ -65,17 +92,15 @@ pub fn do_append(args: Arc, append_args: AppendCmd) -> Result<(), BoxDynEr filepath.to_path_buf() }; - // Get what file that thing is defined in. - let def_path = get_where(&append_args.name, &filepath)?; - let mut opts = File::options(); opts.read(true) .write(true) .create(false) .custom_flags(libc::O_CLOEXEC); - let source_file = SourceFile::open_from(Arc::from(def_path), opts)?; - let pri = get_highest_prio(&append_args.name, source_file.clone())?; + let source_file = SourceFile::open_from(Arc::from(filepath), opts)?; + let pri = get_where(source_file.clone())?; + let new_pri = pri - 1; let new_pri_line = get_next_prio_line( @@ -85,7 +110,7 @@ pub fn do_append(args: Arc, append_args: AppendCmd) -> Result<(), BoxDynEr append_args.value.into(), )?; - eprintln!("new_pri_line={new_pri_line}"); + debug!("new_pri_line={new_pri_line}"); write_next_prio(source_file, new_pri_line)?; @@ -108,35 +133,30 @@ pub fn expr_for_configuration(source_file: &Path) -> OsString { .collect() } -pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result, BoxDynError> { - let expr = expr_for_configuration(configuration_nix); - let attrpath = format!("options.{}.definitionsWithLocations", option_name); - - let output = nixcmd::NixEvalExpr { expr, attrpath } - .into_command() - .output_checked_utf8()?; - let stdout = output.stdout(); - - let definitions: Box<[DefinitionWithLocation]> = serde_json::from_str(&stdout)?; - let last_location = definitions.into_iter().last().unwrap(); - - Ok(Box::from(last_location.file)) +fn maybe_extract_prio_from_line(line: &SourceLine) -> Option { + MK_OVERRIDE_RE + .captures(line.text_ref()) + .map(|caps| caps.name("priority").unwrap().as_str()) + .map(|prio_str| { + i64::from_str(prio_str).unwrap_or_else(|e| { + panic!( + "lib.mkOverride called with non-integer {}: {}. Nix source code is wrong!\n{}", + prio_str, e, line, + ); + }) + }) } -pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result { - // Get the current highest priority. +pub fn get_where(dynamic_nix: SourceFile) -> Result { + let lines = dynamic_nix.lines()?; + let prio = lines + .into_iter() + .filter_map(maybe_extract_prio_from_line) + .sorted_unstable() + .next() // Priorities with lower integer values are "stronger" priorities. + .unwrap_or(0); - let expr = expr_for_configuration(&source.path()); - - // Get the highest priority, and the file its defined in. - let attrpath = format!("options.{}.highestPrio", option_name); - let output = nixcmd::NixEvalExpr { expr, attrpath } - .into_command() - .output_checked_utf8()?; - let stdout = output.stdout(); - let highest_prio = i64::from_str(stdout.trim())?; - - Ok(highest_prio) + Ok(prio) } pub fn get_next_prio_line( @@ -146,15 +166,19 @@ pub fn get_next_prio_line( new_value: Arc, ) -> Result { let source_lines = source.lines()?; - let last_line = source_lines.last(); - assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]")); - let last_line = last_line.unwrap(); + let penultimate = source_lines.get(source_lines.len() - 2); + // FIXME: don't rely on whitespace lol + debug_assert_eq!(penultimate.map(SourceLine::text).as_deref(), Some(" ];")); + let penultimate = penultimate.unwrap(); + + let new_generation = 0 - new_prio; let new_line = SourceLine { - line: last_line.line, + line: penultimate.line, path: source.path(), text: Arc::from(format!( - " {option_name} = lib.mkOverride ({new_prio}) ({new_value});", + " {} = lib.mkOverride ({}) ({}); # DYNIX GENERATION {}", + option_name, new_prio, new_value, new_generation, )), }; @@ -165,12 +189,12 @@ pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<( let new_mod_start = SourceLine { line: new_line.line.prev(), path: source.path(), - text: Arc::from(" {"), + text: Arc::from(" {"), }; let new_mod_end = SourceLine { line: new_line.line.next(), path: source.path(), - text: Arc::from(" }"), + text: Arc::from(" }"), }; source.insert_lines(&[new_mod_start, new_line, new_mod_end])?; diff --git a/tests/default.nix b/tests/default.nix index 43dd66f..535c54b 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -39,12 +39,17 @@ enable = true; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; - serviceConfig.RequisteOf = [ "multi-user.target" ]; path = [ config.system.path ]; wantedBy = [ "multi-user.target" ]; + requiredBy = [ "multi-user.target" ]; after = [ "default.target" ]; script = '' - nix profile install -vv "${dynix.modules}" # " + if [[ -e /etc/nixos/hardware-configuration.nix ]]; then + echo "install-dynix: configuration already copied; nothing to do" + exit 0 + fi + + nix profile install -vv "${dynix.drvPath}^*" # " mkdir -vp /etc/nixos nixos-generate-config @@ -58,6 +63,8 @@ passthru = { inherit options; }; + environment.systemPackages = [ config.passthru.configurationDotNix ]; + # Just making something in this strict in `name`, # which is only present as an argument for nodes and I don't want to # confuse that with the test modules. diff --git a/tests/distccd/test-script.py b/tests/distccd/test-script.py index 9d408af..d5867bf 100644 --- a/tests/distccd/test-script.py +++ b/tests/distccd/test-script.py @@ -4,7 +4,7 @@ import functools #from pprint import pformat import shlex import textwrap -from typing import cast, TYPE_CHECKING +from typing import Any, cast, TYPE_CHECKING from beartype import beartype @@ -68,6 +68,27 @@ def get_cli_args() -> argparse.Namespace: args, rest = parser.parse_known_args(cmdline_args) return args +@beartype +def dynix_append(option: str, value: Any): + machine.succeed(f''' + dynix append {shlex.quote(option)} {shlex.quote(str(value))} + '''.strip()) + +@beartype +def do_apply(): + expr = textwrap.dedent(""" + let + nixos = import { }; + in nixos.config.dynamicism.applyDynamicConfiguration { + baseConfiguration = /etc/nixos/configuration.nix; + newConfiguration = /etc/nixos/dynamic.nix; + } + """).strip() + + machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} + """.strip()) + machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() machine.log("INIT") @@ -89,17 +110,8 @@ args = get_cli_args() #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()) +dynix_append("services.distccd.maxJobs", new_jobs) +do_apply() args = get_cli_args() @@ -109,17 +121,8 @@ 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()) +dynix_append("services.distccd.logLevel", f'"{new_log_level}"') +do_apply() args = get_cli_args() #assert args.jobs == new_jobs, f'{args.jobs=} != {new_jobs=}' diff --git a/tests/distccd/test.nix b/tests/distccd/test.nix index 2e8089f..ad9ac73 100644 --- a/tests/distccd/test.nix +++ b/tests/distccd/test.nix @@ -1,4 +1,7 @@ { mkDynixConfigurationDotNix, config, ... }: +let + testName = config.name; +in { name = "nixos-test-dynamicism-distccd"; @@ -6,17 +9,13 @@ extraPythonPackages = p: [ p.beartype ]; - nodes.machine = { name, pkgs, ... }: { + nodes.machine = { ... }: { imports = [ ./configuration.nix ]; - environment.systemPackages = let - configFileTree = mkDynixConfigurationDotNix { - inherit (config) name; - configuration = ./configuration.nix; - }; - in [ - configFileTree - ]; + passthru.configurationDotNix = mkDynixConfigurationDotNix { + name = testName; + configuration = ./configuration.nix; + }; }; testScript = builtins.readFile ./test-script.py; diff --git a/tests/gotosocial/test-script.py b/tests/gotosocial/test-script.py index 2fa42ed..9799dfb 100644 --- a/tests/gotosocial/test-script.py +++ b/tests/gotosocial/test-script.py @@ -46,37 +46,75 @@ def get_config_file() -> str: config_file_path = machine.out_dir / config_file.name with open(config_file_path, "r") as f: - return f.read() + data = f.read() + + config_file_path.unlink() + + return data + +@beartype +def dynix_append(option: str, value: str): + machine.succeed(f''' + dynix append {shlex.quote(option)} {shlex.quote(value)} + '''.strip()) + +@beartype +def do_apply(): + expr = textwrap.dedent(""" + let + nixos = import { }; + in nixos.config.dynamicism.applyDynamicConfiguration { + baseConfiguration = /etc/nixos/configuration.nix; + newConfiguration = /etc/nixos/dynamic.nix; + } + """).strip() + + machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} + """.strip()) machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() -machine.log("INIT") +machine.wait_for_unit("install-dynix.service") + +dynix_out = machine.succeed("dynix --version") +assert "dynix" in dynix_out, f"dynix not in {dynix_out=}" + +machine.log("REBUILDING configuration inside VM") +machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") +machine.wait_for_unit("gotosocial.service") + +# Make sure the config before any dynamic changes is what we expect. +config_text = get_config_file() +lines = config_text.splitlines() +try: + application_name = next(line for line in lines if line.startswith("application-name:")) +except StopIteration: + raise AssertionError(f"no 'application-name:' found in config file: {textwrap.indent(config_text, " ")}") +assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}" + +new_app_name = "yay!" +dynix_append("services.gotosocial.settings.application-name", f'"{new_app_name}"') +do_apply() + +config_text = get_config_file() +lines = config_text.splitlines() +try: + application_name = next(line for line in lines if line.startswith("application-name:")) +except StopIteration: + raise AssertionError(f"no 'application-name:' found in config file: {textwrap.indent(config_text, " ")}") +assert new_app_name in application_name, f"'{new_app_name}' should be in {application_name=}" machine.log("REBUILDING configuration inside VM") machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs --fallback") +machine.wait_for_unit("gotosocial.service") + config_text = get_config_file() lines = config_text.splitlines() -application_name = next((line for line in lines if line.startswith("application-name:")), None) -assert application_name is not None, f"no 'application-name:' found in config file: {textwrap.indent(config_text, "")}" +try: + application_name = next(line for line in lines if line.startswith("application-name:")) +except StopIteration: + raise AssertionError(f"no 'application-name:' found in config file: {textwrap.indent(config_text, " ")}") assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}" - -new_app_name = "yay!" -expr = textwrap.dedent(f""" - let - nixos = import {{ }}; - in nixos.config.dynamicism.doChange {{ - option = "services.gotosocial.settings.application-name"; - value = "{new_app_name}"; - }} -""").strip() -machine.succeed(rf""" - nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} -""".strip()) - -config_file_new = get_config_file() -lines = config_file_new.splitlines() - -application_name = next(line for line in lines if line.startswith("application-name:")) -assert new_app_name in application_name, f"'{new_app_name}' should be in {application_name=}" diff --git a/tests/gotosocial/test.nix b/tests/gotosocial/test.nix index fd5bfbb..ccb71f6 100644 --- a/tests/gotosocial/test.nix +++ b/tests/gotosocial/test.nix @@ -1,5 +1,7 @@ { mkDynixConfigurationDotNix, config, ... }: - +let + testName = config.name; +in { name = "nixos-test-dynamicism-gotosocial"; @@ -9,21 +11,17 @@ p.beartype ]; - nodes.machine = { pkgs, ... }: { + nodes.machine = { ... }: { # NOTE: Anything in this `nodes.machine = ` module will not be included # in the VM's NixOS configuration once it does `nixos-rebuild switch`, # except for `./configuration.nix` which will be copied to `/etc/nixos/`. # dynix will also be statefully installed to root's user profile. imports = [ ./configuration.nix ]; - environment.systemPackages = let - configFileTree = mkDynixConfigurationDotNix { - inherit (config) name; - configuration = ./configuration.nix; - }; - in [ - configFileTree - ]; + passthru.configurationDotNix = mkDynixConfigurationDotNix { + name = testName; + configuration = ./configuration.nix; + }; }; testScript = builtins.readFile ./test-script.py; diff --git a/tests/harmonia/test-script.py b/tests/harmonia/test-script.py index e96a053..215ea38 100644 --- a/tests/harmonia/test-script.py +++ b/tests/harmonia/test-script.py @@ -55,6 +55,27 @@ def get_config_file() -> dict[str, Any]: config_file_path.unlink() return config_data +@beartype +def dynix_append(option: str, value: Any): + machine.succeed(f''' + dynix append {shlex.quote(option)} {shlex.quote(str(value))} + '''.strip()) + +@beartype +def do_apply(): + expr = textwrap.dedent(""" + let + nixos = import { }; + in nixos.config.dynamicism.applyDynamicConfiguration { + baseConfiguration = /etc/nixos/configuration.nix; + newConfiguration = /etc/nixos/dynamic.nix; + } + """).strip() + + machine.succeed(rf""" + nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} + """.strip()) + machine.wait_for_unit("default.target") assert "lix" in machine.succeed("nix --version").lower() machine.log("INIT") @@ -75,17 +96,8 @@ assert int(config_toml['workers']) == 4, f"{config_toml['workers']=} != 4" assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256" new_workers = 20 -expr = textwrap.dedent(f""" - let - nixos = import {{ }}; - in nixos.config.dynamicism.doChange {{ - option = "services.harmonia.settings.workers"; - value = {new_workers}; - }} -""").strip() -machine.succeed(rf""" - nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} -""".strip()) +dynix_append("services.harmonia.settings.workers", new_workers) +do_apply() # Workers, but not max connection rate, should have changed. config_toml = get_config_file() @@ -94,17 +106,8 @@ assert int(config_toml['workers']) == new_workers, f"{config_toml['workers']=} ! assert int(config_toml['max_connection_rate']) == 256, f"{config_toml['max_connection_rate']=} != 256" new_max_connection_rate = 100 -expr = textwrap.dedent(f""" - let - nixos = import {{ }}; - in nixos.config.dynamicism.doChange {{ - option = [ "services" "harmonia" "settings" "max_connection_rate" ]; - value = {new_max_connection_rate}; - }} -""").strip() -machine.succeed(rf""" - nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)} -""".strip()) +dynix_append("services.harmonia.settings.max_connection_rate", new_max_connection_rate) +do_apply() # Max connection rate should have changed, but workers should have reverted. config_toml = get_config_file() diff --git a/tests/harmonia/test.nix b/tests/harmonia/test.nix index 873c7dd..25fcfbf 100644 --- a/tests/harmonia/test.nix +++ b/tests/harmonia/test.nix @@ -1,4 +1,7 @@ { mkDynixConfigurationDotNix, config, ... }: +let + testName = config.name; +in { name = "nixos-test-dynamicism-harmonia"; @@ -6,21 +9,13 @@ extraPythonPackages = p: [ p.beartype ]; - nodes.machine = { name, pkgs, ... }: { + nodes.machine = { ... }: { imports = [ ./configuration.nix ]; - environment.systemPackages = let - #configFileTree = pkgs.runCommand "${name}-configuration-dot-nix" { } '' - # set -euo pipefail - # install -Dm a=r ${./configuration.nix} "$out/share/nixos/configuration.nix" - #''; - configFileTree = mkDynixConfigurationDotNix { - inherit (config) name; - configuration = ./configuration.nix; - }; - in [ - configFileTree - ]; + passthru.configurationDotNix = mkDynixConfigurationDotNix { + name = testName; + configuration = ./configuration.nix; + }; }; testScript = builtins.readFile ./test-script.py; diff --git a/tests/mk-test-configuration-dot-nix.nix b/tests/mk-test-configuration-dot-nix.nix index da574ef..6b7dc25 100644 --- a/tests/mk-test-configuration-dot-nix.nix +++ b/tests/mk-test-configuration-dot-nix.nix @@ -44,6 +44,15 @@ echo " ];" >> "$modulesOut/configuration.nix" echo "}" >> "$modulesOut/configuration.nix" + echo "/** GENERATED BY mk-test-configuration-dot-nix! */" >> "$modulesOut/dynamic.nix" + echo "{ lib, ... }:" >> "$modulesOut/dynamic.nix" + echo >> "$modulesOut/dynamic.nix" + echo >> "$modulesOut/dynamic.nix" + echo "{" >> "$modulesOut/dynamic.nix" + echo " imports = [ ./configuration.nix ];" >> "$modulesOut/dynamic.nix" + echo " config = lib.mkMerge [" >> "$modulesOut/dynamic.nix" + echo " ];" >> "$modulesOut/dynamic.nix" + echo "}" >> "$modulesOut/dynamic.nix" runHook postInstall ''; diff --git a/tests/module-allow-rebuild-in-vm.nix b/tests/module-allow-rebuild-in-vm.nix index 3ec14d5..96280fe 100644 --- a/tests/module-allow-rebuild-in-vm.nix +++ b/tests/module-allow-rebuild-in-vm.nix @@ -13,8 +13,8 @@ installBootLoader = true; # With how much memory Nix eval uses, this is essentially required. - memorySize = 4096; - cores = 4; + memorySize = 8192; + cores = 8; }; From d76474c5240450bd9c4ceae9993941b900d3d23d Mon Sep 17 00:00:00 2001 From: Qyriad Date: Tue, 17 Feb 2026 20:05:15 +0100 Subject: [PATCH 18/18] how did THAT break it --- modules/dynamicism/dynamicism.nix | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/dynamicism/dynamicism.nix b/modules/dynamicism/dynamicism.nix index 50f921d..6f79993 100644 --- a/modules/dynamicism/dynamicism.nix +++ b/modules/dynamicism/dynamicism.nix @@ -11,6 +11,8 @@ let inherit (import ./lib.nix { inherit lib; }) typeCheck + convenientAttrPath + executablePathInStore concatFoldl recUpdateFoldl recUpdateFoldlAttrs @@ -79,6 +81,11 @@ in ''; }; + finalActivationScript = mkOption { + type = executablePathInStore; + internal = true; + }; + applyDynamicConfiguration = mkOption { #type = t.functionTo t.pathInStore; type = t.functionTo t.raw; @@ -101,6 +108,7 @@ in systemctl = lib.getExe' pkgs.systemd "systemctl"; in '' if [[ -d "${dropinDir}" ]]; then + echo "Removing files in "${dropinDir}" echo "Removing files in "${dropinDir}" >&2 rm -rvf "${dropinDir}/"* @@ -115,8 +123,13 @@ in name = "dynix-reset-dynamicism-for-${name}"; value.deps = [ "etc" "stdio" "specialfs" "binsh" "usrbinenv" "var" "udevd" ]; value.text = '' + set -x + echo "Removing existing dynamic overrides for ${name}" echo "Removing existing dynamic overrides for ${name}" >&2 + ${submod.systemd-services-updated |> lib.map forUnit |> lib.concatStringsSep "\n"} + + set +x ''; }); @@ -152,7 +165,8 @@ in }; }; - submodulesChanged = nixosAfter.config.dynamicism.finalEnabledSubmodules + submodulesChanged = nixosAfter.config.dynamicism.for + |> lib.filterAttrs (lib.const (lib.getAttr "enable")) |> lib.filterAttrs (submodName: _: nixosBefore.config.dynamicism.for.${submodName}.finalSettings != @@ -174,6 +188,17 @@ in passthru.configuration = nixosAfter; }; + finalActivationScript = pkgs.writeShellApplication { + name = "dynamicism-activate-all"; + text = config.dynamicism.for + |> lib.filterAttrs (lib.const (lib.getAttr "enable")) + |> lib.mapAttrsToList (name: submod: '' + echo "Activating dynamic configuration for ${name}" + ${lib.getExe submod.activate} + '') + |> lib.concatStringsSep "\n"; + }; + finalSettings = config.dynamicism.for |> recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) |> checkAssertWarn ourAssertions [ ];