From 814b7a67da15047db49630d6554acd52dd6e89e1 Mon Sep 17 00:00:00 2001 From: eveeifyeve Date: Sat, 25 Jul 2026 09:22:53 +1000 Subject: [PATCH 1/5] portable-lib: migrate to use nixpkgs --- modules/default.nix | 3 +- modules/portable/assertions.nix | 32 ----------- modules/portable/config-data-item.nix | 64 ---------------------- modules/portable/config-data.nix | 45 ---------------- modules/portable/lib.nix | 77 --------------------------- modules/portable/service.nix | 54 ------------------- 6 files changed, 1 insertion(+), 274 deletions(-) delete mode 100644 modules/portable/assertions.nix delete mode 100644 modules/portable/config-data-item.nix delete mode 100644 modules/portable/config-data.nix delete mode 100644 modules/portable/lib.nix delete mode 100644 modules/portable/service.nix diff --git a/modules/default.nix b/modules/default.nix index 90f213a..98c86a0 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -6,8 +6,7 @@ ... }: let - portable-lib = import ./portable/lib.nix { inherit lib; }; - + portable-lib = import "${pkgs.path}/lib/services/lib.nix" { inherit lib; }; modularServiceConfiguration = portable-lib.configure { serviceManagerPkgs = pkgs; extraRootModules = [ diff --git a/modules/portable/assertions.nix b/modules/portable/assertions.nix deleted file mode 100644 index c904625..0000000 --- a/modules/portable/assertions.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ lib, ... }: -{ - options = { - assertions = lib.mkOption { - type = with lib.types; listOf unspecified; - internal = true; - default = [ ]; - example = [ - { - assertion = false; - message = "you can't enable this for that reason"; - } - ]; - description = '' - This option allows modules to express conditions that must - hold for the evaluation of the system configuration to - succeed, along with associated error messages for the user. - ''; - }; - - warnings = lib.mkOption { - internal = true; - default = [ ]; - type = with lib.types; listOf str; - example = [ "The `foo' service is deprecated and will go away soon!" ]; - description = '' - This option allows modules to show warnings to users during - the evaluation of the system configuration. - ''; - }; - }; -} diff --git a/modules/portable/config-data-item.nix b/modules/portable/config-data-item.nix deleted file mode 100644 index 6ae7eb1..0000000 --- a/modules/portable/config-data-item.nix +++ /dev/null @@ -1,64 +0,0 @@ -# This file is a function that returns a module. -pkgs: -{ - lib, - name, - config, - options, - ... -}: -let - inherit (lib) mkOption types; -in -{ - options = { - enable = mkOption { - type = types.bool; - default = true; - description = '' - Whether this configuration file should be generated. - This option allows specific configuration files to be disabled. - ''; - }; - - name = mkOption { - type = types.str; - description = '' - Name of the configuration file (relative to the service's configuration directory). Defaults to the attribute name. - ''; - }; - - path = mkOption { - type = types.str; - readOnly = true; - description = '' - The actual path where this configuration file will be available. - This is determined by the service manager implementation. - - On NixOS it is an absolute path. - Other service managers may provide a relative path, in order to be unprivileged and/or relocatable. - ''; - }; - - text = mkOption { - default = null; - type = types.nullOr types.lines; - description = "Text content of the configuration file."; - }; - - source = mkOption { - type = types.path; - description = "Path of the source file."; - }; - }; - - config = { - name = lib.mkDefault name; - source = lib.mkIf (config.text != null) ( - let - name' = "service-configdata-" + lib.replaceStrings [ "/" ] [ "-" ] name; - in - lib.mkDerivedConfig options.text (pkgs.writeText name') - ); - }; -} diff --git a/modules/portable/config-data.nix b/modules/portable/config-data.nix deleted file mode 100644 index 870a6e2..0000000 --- a/modules/portable/config-data.nix +++ /dev/null @@ -1,45 +0,0 @@ -# Non-modular context provided by the modular services integration. -{ pkgs }: - -# Configuration data support for portable services -# This module provides configData for services, enabling configuration reloading -# without terminating and restarting the service process. -{ - lib, - ... -}: -let - inherit (lib) mkOption types; - inherit (lib.modules) importApply; -in -{ - options = { - configData = mkOption { - default = { }; - example = lib.literalExpression '' - { - "server.conf" = { - text = ''' - port = 8080 - workers = 4 - '''; - }; - "ssl/cert.pem" = { - source = ./cert.pem; - }; - } - ''; - description = '' - Configuration data files for the service - - These files are made available to the service and can be updated without restarting the service process, enabling configuration reloading. - The service manager implementation determines how these files are exposed to the service (e.g., via a specific directory path). - This path is available in the `path` sub-option for each `configData.` entry. - - This is particularly useful for services that support configuration reloading via signals (e.g., SIGHUP) or which pick up changes automatically, so that no downtime is required in order to reload the service. - ''; - - type = types.lazyAttrsOf (types.submodule (importApply ./config-data-item.nix pkgs)); - }; - }; -} diff --git a/modules/portable/lib.nix b/modules/portable/lib.nix deleted file mode 100644 index 88db80f..0000000 --- a/modules/portable/lib.nix +++ /dev/null @@ -1,77 +0,0 @@ -{ lib, ... }: -let - inherit (lib) - concatLists - mapAttrsToList - showOption - types - ; -in -rec { - flattenMapServicesConfigToList = - f: loc: config: - f loc config - ++ concatLists ( - mapAttrsToList ( - k: v: - flattenMapServicesConfigToList f ( - loc - ++ [ - "services" - k - ] - ) v - ) config.services - ); - - getWarnings = flattenMapServicesConfigToList ( - loc: config: map (msg: "in ${showOption loc}: ${msg}") config.warnings - ); - - getAssertions = flattenMapServicesConfigToList ( - loc: config: - map (ass: { - message = "in ${showOption loc}: ${ass.message}"; - assertion = ass.assertion; - }) config.assertions - ); - - /** - This is the entrypoint for the portable part of modular services. - - It provides the various options that are consumed by service manager implementations. - - # Inputs - - `serviceManagerPkgs`: A Nixpkgs instance which will be used for built-in logic such as converting `configData..text` to a store path. - - `extraRootModules`: Modules to be loaded into the "root" service submodule, but not into its sub-`services`. That's the modules' own responsibility. - - `extraRootSpecialArgs`: Fixed module arguments that are provided in a similar manner to `extraRootModules`. - - # Output - - An attribute set. - - `serviceSubmodule`: a Module System option type which is a `submodule` with the portable modules and this function's inputs loaded into it. - */ - configure = - { - serviceManagerPkgs, - extraRootModules ? [ ], - extraRootSpecialArgs ? { }, - }: - let - modules = [ - (lib.modules.importApply ./service.nix { pkgs = serviceManagerPkgs; }) - ]; - serviceSubmodule = types.submoduleWith { - class = "service"; - modules = modules ++ extraRootModules; - specialArgs = extraRootSpecialArgs; - }; - in - { - inherit serviceSubmodule; - }; -} diff --git a/modules/portable/service.nix b/modules/portable/service.nix deleted file mode 100644 index c5dde7f..0000000 --- a/modules/portable/service.nix +++ /dev/null @@ -1,54 +0,0 @@ -# Non-module arguments -# These are separate from the module arguments to avoid implicit dependencies. -# This makes service modules self-contained, allowing mixing of Nixpkgs versions. -{ pkgs }: - -# The module -{ - lib, - ... -}: -let - inherit (lib) mkOption types; - pathOrStr = types.coercedTo types.path (x: "${x}") types.str; -in -{ - # https://nixos.org/manual/nixos/unstable/#modular-services - _class = "service"; - imports = [ - (pkgs.path + "/modules/generic/meta-maintainers.nix") - ./assertions.nix - (lib.modules.importApply ./config-data.nix { inherit pkgs; }) - ]; - options = { - services = mkOption { - type = types.attrsOf ( - types.submoduleWith { - modules = [ - (lib.modules.importApply ./service.nix { inherit pkgs; }) - ]; - } - ); - description = '' - A collection of [modular services](https://nixos.org/manual/nixos/unstable/#modular-services) that are configured in one go. - - You could consider the sub-service relationship to be an ownership relation. - It **does not** automatically create any other relationship between services (e.g. systemd slices), unless perhaps such a behavior is explicitly defined and enabled in another option. - ''; - default = { }; - visible = "shallow"; - }; - process = { - argv = lib.mkOption { - type = types.listOf pathOrStr; - example = lib.literalExpression ''[ (lib.getExe config.package) "--nobackground" ]''; - description = '' - Command filename and arguments for starting this service. - This is a raw command-line that should not contain any shell escaping. - If expansion of environmental variables is required then use - a shell script or `importas` from `pkgs.execline`. - ''; - }; - }; - }; -} From 877ed7b31934fc7fbcd3e0ce2408bab3a8d197ac Mon Sep 17 00:00:00 2001 From: eveeifyeve Date: Sat, 25 Jul 2026 09:44:44 +1000 Subject: [PATCH 2/5] modular-services: cleanup --- README.md | 2 +- flake.nix | 4 +- modules/default.nix | 46 ---------------------- modules/finit/system.nix | 29 -------------- modules/{finit => }/service.nix | 8 +++- modules/system.nix | 67 +++++++++++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 79 deletions(-) delete mode 100644 modules/default.nix delete mode 100644 modules/finit/system.nix rename modules/{finit => }/service.nix (75%) create mode 100644 modules/system.nix diff --git a/README.md b/README.md index 754f2b3..29b5049 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # finix - modular services -Support for [NixOS modular services](https://nixos.org/manual/nixos/unstable/#modular-services) for `finix`. +Support for [Modular services](https://nixos.org/manual/nixos/unstable/#modular-services) for `finix`. There is a Matrix room for discussions: diff --git a/flake.nix b/flake.nix index 4bad867..1439fcd 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { - description = "Support for NixOS modular services in finix"; + description = "Support for Modular services in finix"; outputs = _: { - nixosModules.default = import ./modules; + nixosModules.default = import ./modules/service.nix; }; } diff --git a/modules/default.nix b/modules/default.nix deleted file mode 100644 index 98c86a0..0000000 --- a/modules/default.nix +++ /dev/null @@ -1,46 +0,0 @@ -{ - config, - options, - pkgs, - lib, - ... -}: -let - portable-lib = import "${pkgs.path}/lib/services/lib.nix" { inherit lib; }; - modularServiceConfiguration = portable-lib.configure { - serviceManagerPkgs = pkgs; - extraRootModules = [ - ./finit/service.nix - ]; - }; -in -{ - imports = [ - ./finit/system.nix - ]; - - options = { - system.services = lib.mkOption { - type = lib.types.attrsOf modularServiceConfiguration.serviceSubmodule; - default = { }; - description = '' - A collection of modular services. - ''; - visible = "shallow"; - }; - }; - - config = { - assertions = lib.concatLists ( - lib.mapAttrsToList ( - name: cfg: portable-lib.getAssertions (options.system.services.loc ++ [ name ]) cfg - ) config.system.services - ); - - warnings = lib.concatLists ( - lib.mapAttrsToList ( - name: cfg: portable-lib.getWarnings (options.system.services.loc ++ [ name ]) cfg - ) config.system.services - ); - }; -} diff --git a/modules/finit/system.nix b/modules/finit/system.nix deleted file mode 100644 index afbc20c..0000000 --- a/modules/finit/system.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ config, lib, ... }: -let - makeServices = - prefixes: service: - lib.concatMapAttrs ( - name: module: - let - label = if name == "" then prefixes else prefixes ++ [ name ]; - in - { - "${lib.concatStringsSep "-" label}" = - { ... }: - { - imports = [ module ]; - }; - } - ) service.finit.services - // lib.concatMapAttrs ( - subServiceName: subService: makeServices (prefixes ++ [ subServiceName ]) subService - ) service.services; -in -{ - # Assert Finit services for those defined in isolation to the system. - config = { - finit.services = lib.concatMapAttrs ( - topLevelName: topLevelService: makeServices [ topLevelName ] topLevelService - ) config.system.services; - }; -} diff --git a/modules/finit/service.nix b/modules/service.nix similarity index 75% rename from modules/finit/service.nix rename to modules/service.nix index cea0143..0e12c49 100644 --- a/modules/finit/service.nix +++ b/modules/service.nix @@ -1,6 +1,12 @@ { config, lib, ... }: +let + inherit (lib) mkOption types; +in { - imports = [ (lib.mkAliasOptionModule [ "finit" "service" ] [ "finit" "services" "" ]) ]; + imports = [ + (lib.mkAliasOptionModule [ "finit" "service" ] [ "system" "services" ]) + (lib.mkAliasOptionModule [ "finit" "services" ] [ "system" "services" ]) + ]; options = { finit.services = lib.mkOption { diff --git a/modules/system.nix b/modules/system.nix new file mode 100644 index 0000000..2a3c176 --- /dev/null +++ b/modules/system.nix @@ -0,0 +1,67 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + portable-lib = import "${pkgs.path}/lib/services/lib.nix" { inherit lib; }; + modularServiceConfiguration = portable-lib.configure { + serviceManagerPkgs = pkgs; + extraRootModules = [ + ./finit/service.nix + ]; + }; + + makeServices = + prefixes: service: + lib.concatMapAttrs ( + name: module: + let + label = if name == "" then prefixes else prefixes ++ [ name ]; + in + { + "${lib.concatStringsSep "-" label}" = + { ... }: + { + imports = [ module ]; + }; + } + ) service.finit.services + // lib.concatMapAttrs ( + subServiceName: subService: makeServices (prefixes ++ [ subServiceName ]) subService + ) service.services; +in +{ + + options = { + system.services = lib.mkOption { + type = lib.types.attrsOf modularServiceConfiguration.serviceSubmodule; + default = { }; + description = '' + A collection of modular services. + ''; + visible = "shallow"; + }; + }; + + # Assert Finit services for those defined in isolation to the system. + config = { + finit.services = lib.concatMapAttrs ( + topLevelName: topLevelService: makeServices [ topLevelName ] topLevelService + ) config.system.services; + + assertions = lib.concatLists ( + lib.mapAttrsToList ( + name: cfg: portable-lib.getAssertions (config.system.services.loc ++ [ name ]) cfg + ) config.system.services + ); + + warnings = lib.concatLists ( + lib.mapAttrsToList ( + name: cfg: portable-lib.getWarnings (config.system.services.loc ++ [ name ]) cfg + ) config.system.services + ); + }; +} From 786bffbb89ec90df069761cf60df09b2b0cca7cd Mon Sep 17 00:00:00 2001 From: eveeifyeve Date: Mon, 31 Aug 2026 03:32:10 +1000 Subject: [PATCH 3/5] modular-services: overriding support to finix command --- modules/service.nix | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/service.nix b/modules/service.nix index 0e12c49..47b5aa0 100644 --- a/modules/service.nix +++ b/modules/service.nix @@ -17,6 +17,32 @@ in ''; }; + finit.command = mkOption { + type = types.str; + default = lib.escapeShellArgs config.process.argv; + defaultText = lib.literalExpression "lib.escapeShellArgs config.process.argv"; + description = '' + Starting Command for finix. + + This option sets the primary starting entry, and is the way to extend the + command line derived from {option}`process.argv`. + + By default, it is set to `lib.escapeShellArgs {option}process.argv`. Because + {option}`process.argv` is already a command line (not an argument + list), it is used verbatim so that references like `$MAINPID` are preserved. + When {option}`process.argv` is unset, this option is `null` and no + `finit.command` is emitted; a service may then set + `finit.command` itself. + + To extend {option}`process.argv`, you can append + to the command line: + ```nix + finit.command = + config.process.argv + ""; + ``` + ''; + }; + # Import this logic into sub-services also. # Extends the portable `services` option. services = lib.mkOption { @@ -33,7 +59,8 @@ in config = { finit.services."" = { - command = lib.escapeShellArgs config.process.argv; + command = config.finit.command; }; + }; } From 783493c6598818d73dffd03879651e4a7c6f30c8 Mon Sep 17 00:00:00 2001 From: eveeifyeve Date: Mon, 31 Aug 2026 03:33:11 +1000 Subject: [PATCH 4/5] modular-services: reloading and readiness notification support Ref: NixOS/nixpkgs#540857 & NixOS/nixpkgs#535695 --- modules/service.nix | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/modules/service.nix b/modules/service.nix index 47b5aa0..11c7e52 100644 --- a/modules/service.nix +++ b/modules/service.nix @@ -2,7 +2,7 @@ let inherit (lib) mkOption types; in -{ + { imports = [ (lib.mkAliasOptionModule [ "finit" "service" ] [ "system" "services" ]) (lib.mkAliasOptionModule [ "finit" "services" ] [ "system" "services" ]) @@ -43,6 +43,32 @@ in ''; }; + finit.reload = mkOption { + type = types.nullOr types.str; + default = if config.process.reloadCommand != null then config.process.reloadCommand else null; + defaultText = lib.literalExpression "config.process.reloadCommand"; + description = '' + Reload Command for finix. + + This option sets the primary reload entry, and is the way to extend the + command line derived from {option}`process.reloadCommand`. + + By default, it is set to {option}`process.reloadCommand`. Because + {option}`process.reloadCommand` is already a command line (not an argument + list), it is used verbatim so that references like `$MAINPID` are preserved. + When {option}`process.reloadCommand` is unset, this option is `null` and no + `finit.reload` is emitted; a service may then set + `finit.reload` itself. + + To extend {option}`process.reloadCommand`, you can append + to the command line: + ```nix + finit.reload = + config.process.reloadCommand + ""; + ``` + ''; + }; + # Import this logic into sub-services also. # Extends the portable `services` option. services = lib.mkOption { @@ -59,7 +85,16 @@ in config = { finit.services."" = { + conditions = [ "service/syslogd/ready" ]; command = config.finit.command; + reload = config.finit.reload; + notify = + if config.notificationProtocol.systemd then + "systemd" + else if config.notificationProtocol.s6 then + "s6" + else + null; }; }; From 563af8043ba1edbf02379c2aa10e6b29ae963e47 Mon Sep 17 00:00:00 2001 From: eveeifyeve Date: Sat, 5 Sep 2026 03:22:07 +1000 Subject: [PATCH 5/5] modular-services: tests init --- flake.lock | 43 ++++++++++++++++++++++++++++++ flake.nix | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 flake.lock diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..89a8a7a --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "finix": { + "locked": { + "lastModified": 1788403395, + "narHash": "sha256-nqsj0NmmYJ1aFj6hHUFQuKnyHSjqyMxcBNI99mMbrfw=", + "owner": "finix-community", + "repo": "finix", + "rev": "f765fafde5e21b9f456827d1bf84a42417abe67d", + "type": "github" + }, + "original": { + "owner": "finix-community", + "repo": "finix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1788404924, + "narHash": "sha256-lhEhY8X5EgkQ/eg6IFz4cc8jRuSYSvnxx1al7d1dvZ0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0968519e14f7aa7d3e9b389682bd74d2b51c8ce8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "finix": "finix", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 1439fcd..64c726d 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,79 @@ { description = "Support for Modular services in finix"; - - outputs = _: { - nixosModules.default = import ./modules/service.nix; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + finix.url = "github:finix-community/finix"; }; + + outputs = + { + self, + nixpkgs, + finix, + }: + let + finixSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + forAllFinixSystems = + f: + nixpkgs.lib.genAttrs finixSystems ( + system: + let + pkgs = import nixpkgs { inherit system; }; + in + f pkgs system + ); + in + { + nixosModules.default = import ./modules/service.nix; + + checks = forAllFinixSystems ( + pkgs: system: { + default = pkgs.testers.modularServiceCompliance { + callReload = path: "initctl reload ${path}"; + sharedDir = ".finix-test"; + evalConfig = + { services, ... }: + let + machine = finix.lib.finixSystem { + inherit (pkgs) lib; + modules = [ + { + nixpkgs.pkgs = nixpkgs.lib.mkDefault pkgs; + } + { + system.services = services; + } + self.nixosModules.default + ]; + }; + in + { + config = machine.config.system.services; + checkDrv = machine.config.system.build.toplevel; + }; + mkTest = + { + name, + services, + testExe, + }: + finix.lib.mkTest { + inherit name; + nodes.machine = { + imports = [ self.nixosModules.default ]; + system.services = services; + }; + testScript = '' + machine.wait_for_condition("service/syslogd/ready") + machine.succeed("${testExe}") + ''; + }; + }; + } + ); + }; }