lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 26c93442450372625e67ee59f73122ebeba79732
parent 9c28bdb164296a1c3ae5e9d7c3e3b45282506cb7
Author: triesap <tyson@radroots.org>
Date:   Thu, 20 Aug 2026 21:30:45 +0000

build: add service nixos module

Diffstat:
Mbuild/nix/service/default.nix | 1+
Mbuild/nix/service/fixture.nix | 376+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abuild/nix/service/nixos-module.nix | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mflake.nix | 16+++++++++++++++-
4 files changed, 645 insertions(+), 1 deletion(-)

diff --git a/build/nix/service/default.nix b/build/nix/service/default.nix @@ -12,5 +12,6 @@ mkServiceApps = import ./apps.nix { inherit lib pkgs; }; mkServiceDevShell = import ./devshell.nix { inherit lib pkgs; }; mkServiceOciImage = import ./oci.nix { inherit lib pkgs; }; + mkServiceNixosModule = import ./nixos-module.nix { inherit lib; }; mkServiceOutputs = import ./compose.nix { inherit lib; }; } diff --git a/build/nix/service/fixture.nix b/build/nix/service/fixture.nix @@ -1,5 +1,6 @@ { lib, + nixosSystem, pkgs, service, toolchain, @@ -132,6 +133,117 @@ let '' else null; + nixosModule = service.mkServiceNixosModule { + serviceName = "fixture_service"; + binaryName = "fixture-service"; + packageFor = _: package; + commandForInstance = instanceName: [ + "--instance" + instanceName + "--config" + "/etc/radroots/services/fixture_service/${instanceName}/config.toml" + ]; + }; + nixosConfiguration = + if pkgs.stdenv.isLinux then + nixosSystem { + system = pkgs.stdenv.hostPlatform.system; + modules = [ + nixosModule + { + system.stateVersion = "25.11"; + users.groups.fixture-admin = { }; + services.radroots.fixture_service = { + enable = true; + adminGroup = "fixture-admin"; + instances.primary = { + configurationFile = pkgs.writeText "fixture-service-config.toml" '' + contract_version = 1 + ''; + credentials.api-token = "/run/operator/fixture-api-token"; + }; + }; + } + ]; + } + else + null; + nixosUnitName = "radroots-fixture_service-primary"; + nixosService = + if pkgs.stdenv.isLinux then nixosConfiguration.config.systemd.services.${nixosUnitName} else null; + nixosUnitText = + if pkgs.stdenv.isLinux then + nixosConfiguration.config.system.build.units."${nixosUnitName}.service".text + else + null; + nixosModuleCheck = + if pkgs.stdenv.isLinux then + pkgs.runCommand "fixture-service-nixos-module" + { + nativeBuildInputs = [ + pkgs.gnugrep + ]; + unit = pkgs.writeText "${nixosUnitName}.service" nixosUnitText; + } + '' + grep -Fx 'Type=simple' "$unit" + grep -Fx 'DynamicUser=true' "$unit" + grep -Fx 'User=radroots-fixture_service' "$unit" + grep -Fx 'Group=fixture-admin' "$unit" + grep -Fx 'UMask=0077' "$unit" + grep -Fx 'ConfigurationDirectory=radroots/services/fixture_service/primary' "$unit" + grep -Fx 'ConfigurationDirectoryMode=0500' "$unit" + grep -Fx 'StateDirectory=radroots/services/fixture_service/primary' "$unit" + grep -Fx 'StateDirectoryMode=0700' "$unit" + grep -Fx 'CacheDirectory=radroots/services/fixture_service/primary' "$unit" + grep -Fx 'CacheDirectoryMode=0700' "$unit" + grep -Fx 'RuntimeDirectory=radroots/services/fixture_service/primary' "$unit" + grep -Fx 'RuntimeDirectoryMode=0750' "$unit" + grep -Fx 'LoadCredential=api-token:/run/operator/fixture-api-token' "$unit" + grep -Fx 'NoNewPrivileges=true' "$unit" + grep -Fx 'PrivateTmp=true' "$unit" + grep -Fx 'PrivateDevices=true' "$unit" + grep -Fx 'ProtectSystem=strict' "$unit" + grep -Fx 'ProtectHome=true' "$unit" + grep -Fx 'ProtectKernelTunables=true' "$unit" + grep -Fx 'ProtectKernelModules=true' "$unit" + grep -Fx 'ProtectKernelLogs=true' "$unit" + grep -Fx 'ProtectControlGroups=true' "$unit" + grep -Fx 'ProtectHostname=true' "$unit" + grep -Fx 'ProtectClock=true' "$unit" + grep -Fx 'RestrictSUIDSGID=true' "$unit" + grep -Fx 'LockPersonality=true' "$unit" + grep -Fx 'CapabilityBoundingSet=' "$unit" + grep -Fx 'AmbientCapabilities=' "$unit" + test "$(grep -c '^RestrictAddressFamilies=' "$unit")" = 3 + grep -Fx 'RestrictAddressFamilies=AF_UNIX' "$unit" + grep -Fx 'RestrictAddressFamilies=AF_INET' "$unit" + grep -Fx 'RestrictAddressFamilies=AF_INET6' "$unit" + grep -Fx 'RestrictNamespaces=true' "$unit" + grep -Fx 'RestrictRealtime=true' "$unit" + grep -Fx 'RemoveIPC=true' "$unit" + grep -Fx 'DevicePolicy=closed' "$unit" + grep -Fx 'KeyringMode=private' "$unit" + grep -Fx 'ProcSubset=pid' "$unit" + grep -Fx 'ProtectProc=invisible' "$unit" + grep -Fx 'SystemCallArchitectures=native' "$unit" + grep -Fx 'TimeoutStopSec=30s' "$unit" + grep -Fx 'KillSignal=SIGTERM' "$unit" + grep -Fx 'KillMode=control-group' "$unit" + grep -Fx 'Restart=on-failure' "$unit" + grep -Fx 'RestartSec=5s' "$unit" + if grep -E '^(MemoryDenyWriteExecute|SystemCallFilter)=' "$unit"; then + echo "fixture unit enabled an unqualified hardening control" >&2 + exit 1 + fi + if grep -E '^Environment=.*(api-token|fixture-api-token)' "$unit"; then + echo "fixture unit exposed credential material through its environment" >&2 + exit 1 + fi + touch "$out" + '' + else + null; appSmoke = pkgs.runCommand "fixture-service-app-smoke" { } '' test "$(${apps.default.program} --help)" = "fixture-service" test "$(${apps.release-acceptance.program})" = "fixture-service release acceptance" @@ -199,6 +311,7 @@ let inherit smoke; } // lib.optionalAttrs pkgs.stdenv.isLinux { + nixos-module = nixosModuleCheck; oci-image = ociImageCheck; }; }; @@ -621,6 +734,219 @@ let )).outPath )) ]; + nixosServiceConfig = if pkgs.stdenv.isLinux then nixosService.serviceConfig else null; + nixosModuleArguments = { + serviceName = "fixture_service"; + binaryName = "fixture-service"; + packageFor = _: package; + commandForInstance = _: [ "--help" ]; + }; + invalidNixosModuleConstructors = [ + (nixosModuleArguments // { serviceName = "../fixture"; }) + (nixosModuleArguments // { serviceName = "fixture-service"; }) + (nixosModuleArguments // { serviceName = lib.concatStrings (lib.replicate 129 "a"); }) + (nixosModuleArguments // { binaryName = "fixture service"; }) + (nixosModuleArguments // { binaryName = lib.concatStrings (lib.replicate 129 "b"); }) + (nixosModuleArguments // { packageFor = "not-a-function"; }) + (nixosModuleArguments // { commandForInstance = "not-a-function"; }) + (nixosModuleArguments // { stopTimeoutSeconds = 0; }) + (nixosModuleArguments // { stopTimeoutSeconds = 86401; }) + (nixosModuleArguments // { addressFamilies = [ ]; }) + ( + nixosModuleArguments + // { + addressFamilies = [ + "AF_UNIX" + "AF_UNIX" + ]; + } + ) + (nixosModuleArguments // { addressFamilies = [ "AF_PACKET" ]; }) + ]; + invalidNixosModuleConstructorResults = map ( + arguments: builtins.tryEval ((service.mkServiceNixosModule arguments) { }) + ) invalidNixosModuleConstructors; + baseNixosModuleConfiguration = { + enable = true; + inherit package; + adminGroup = null; + instances.primary = { + configurationFile = pkgs.writeText "fixture-service-assertion-config.toml" ""; + credentials = { }; + }; + }; + nixosModuleAssertionsPass = + module: moduleServiceName: moduleConfiguration: + let + evaluated = module { + config = lib.setAttrByPath [ + "services" + "radroots" + moduleServiceName + ] moduleConfiguration; + inherit pkgs; + }; + in + lib.all (entry: entry.assertion) evaluated.config.assertions; + invalidNixosModuleConfigurations = [ + (baseNixosModuleConfiguration // { enable = false; }) + (baseNixosModuleConfiguration // { instances = { }; }) + (baseNixosModuleConfiguration // { adminGroup = "INVALID GROUP"; }) + ( + baseNixosModuleConfiguration + // { + adminGroup = lib.concatStrings (lib.replicate 129 "a"); + } + ) + ( + baseNixosModuleConfiguration + // { + instances = { + "../primary" = baseNixosModuleConfiguration.instances.primary; + }; + } + ) + ( + baseNixosModuleConfiguration + // { + instances = { + ${lib.concatStrings (lib.replicate 129 "a")} = baseNixosModuleConfiguration.instances.primary; + }; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials."bad:name" = "/run/operator/token"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials.token = "relative/token"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials.token = "/nix/store"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials.token = "/nix/store/secret"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials.token = "/run/operator/token:alias"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials.token = "/run/operator/token\nvalue"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials.token = "/${lib.concatStrings (lib.replicate 4096 "a")}"; + } + ) + ( + baseNixosModuleConfiguration + // { + instances.primary.credentials = lib.genAttrs (lib.genList ( + index: "credential-${toString index}" + ) 33) (_: "/run/operator/token"); + } + ) + ( + baseNixosModuleConfiguration + // { + instances = lib.genAttrs (lib.genList (index: "instance-${toString index}") 65) ( + _: baseNixosModuleConfiguration.instances.primary + ); + } + ) + ]; + invalidNixosModuleConfigurationResults = map ( + moduleConfiguration: + builtins.tryEval (nixosModuleAssertionsPass nixosModule "fixture_service" moduleConfiguration) + ) invalidNixosModuleConfigurations; + commandNixosModule = + command: + service.mkServiceNixosModule ( + nixosModuleArguments + // { + commandForInstance = _: command; + } + ); + invalidNixosCommandResults = + map + ( + command: + builtins.tryEval ( + nixosModuleAssertionsPass (commandNixosModule command) "fixture_service" + baseNixosModuleConfiguration + ) + ) + [ + (lib.replicate 65 "argument") + [ (lib.concatStrings (lib.replicate 4097 "a")) ] + [ "argument\nvalue" ] + ]; + maximumNixosModule = service.mkServiceNixosModule ( + nixosModuleArguments + // { + serviceName = lib.concatStrings (lib.replicate 128 "a"); + binaryName = lib.concatStrings (lib.replicate 128 "b"); + stopTimeoutSeconds = 86400; + addressFamilies = [ "AF_UNIX" ]; + } + ); + maximumNixosModuleConfiguration = { + enable = true; + inherit package; + adminGroup = lib.concatStrings (lib.replicate 128 "a"); + instances.${lib.concatStrings (lib.replicate 109 "b")} = { + configurationFile = pkgs.writeText "fixture-service-maximum-config.toml" ""; + credentials = lib.genAttrs (lib.genList (index: "credential-${toString index}") 32) ( + _: "/${lib.concatStrings (lib.replicate 4095 "c")}" + ); + }; + }; + overlongNixosUnitConfiguration = maximumNixosModuleConfiguration // { + instances = { + ${lib.concatStrings (lib.replicate 110 "b")} = baseNixosModuleConfiguration.instances.primary; + }; + }; + overlongNixosUnitResult = builtins.tryEval ( + nixosModuleAssertionsPass maximumNixosModule (lib.concatStrings ( + lib.replicate 128 "a" + )) overlongNixosUnitConfiguration + ); + maximumNixosInstanceCountConfiguration = baseNixosModuleConfiguration // { + instances = lib.genAttrs (lib.genList (index: "instance-${toString index}") 64) ( + _: baseNixosModuleConfiguration.instances.primary + ); + }; + maximumNixosCommandModule = commandNixosModule ( + lib.replicate 64 (lib.concatStrings (lib.replicate 4096 "a")) + ); + noAdminNixosEvaluation = nixosModule { + config = lib.setAttrByPath [ + "services" + "radroots" + "fixture_service" + ] baseNixosModuleConfiguration; + inherit pkgs; + }; + noAdminNixosServiceConfig = + noAdminNixosEvaluation.config.systemd.services.content.${nixosUnitName}.serviceConfig; in assert service.supportedSystems == [ @@ -656,6 +982,7 @@ assert "integration" ] ++ lib.optionals pkgs.stdenv.isLinux [ + "nixos-module" "oci-image" ] ++ [ @@ -701,6 +1028,55 @@ assert lib.all (result: result.success == false) invalidDevShellResults; assert maximumOciResult.success; assert lib.all (result: result.success == false) invalidOciResults; assert ( + pkgs.stdenv.isLinux + -> + builtins.attrNames ( + lib.filterAttrs (name: _: lib.hasPrefix "radroots-" name) nixosConfiguration.config.systemd.services + ) == [ nixosUnitName ] +); +assert (pkgs.stdenv.isLinux -> nixosService.wantedBy == [ "multi-user.target" ]); +assert (pkgs.stdenv.isLinux -> nixosService.wants == [ "network-online.target" ]); +assert (pkgs.stdenv.isLinux -> nixosService.after == [ "network-online.target" ]); +assert (pkgs.stdenv.isLinux -> nixosServiceConfig.Type == "simple"); +assert (pkgs.stdenv.isLinux -> nixosServiceConfig.DynamicUser); +assert (pkgs.stdenv.isLinux -> nixosServiceConfig.RuntimeDirectoryMode == "0750"); +assert ( + pkgs.stdenv.isLinux + -> + nixosServiceConfig.BindReadOnlyPaths == [ + "${nixosConfiguration.config.services.radroots.fixture_service.instances.primary.configurationFile}:/etc/radroots/services/fixture_service/primary/config.toml" + ] +); +assert ( + pkgs.stdenv.isLinux + -> + nixosServiceConfig.LoadCredential == [ + "api-token:/run/operator/fixture-api-token" + ] +); +assert (pkgs.stdenv.isLinux -> !(nixosServiceConfig ? MemoryDenyWriteExecute)); +assert (pkgs.stdenv.isLinux -> !(nixosServiceConfig ? SystemCallFilter)); +assert lib.all (result: result.success == false) invalidNixosModuleConstructorResults; +assert lib.all ( + result: result.success && result.value == false +) invalidNixosModuleConfigurationResults; +assert lib.all (result: result.success && result.value == false) invalidNixosCommandResults; +assert ( + nixosModuleAssertionsPass maximumNixosModule (lib.concatStrings ( + lib.replicate 128 "a" + )) maximumNixosModuleConfiguration +); +assert (overlongNixosUnitResult.success && overlongNixosUnitResult.value == false); +assert ( + nixosModuleAssertionsPass nixosModule "fixture_service" maximumNixosInstanceCountConfiguration +); +assert ( + nixosModuleAssertionsPass maximumNixosCommandModule "fixture_service" baseNixosModuleConfiguration +); +assert noAdminNixosServiceConfig.Group == "radroots-fixture_service"; +assert noAdminNixosServiceConfig.RuntimeDirectoryMode == "0700"; +assert !(noAdminNixosServiceConfig ? SupplementaryGroups); +assert ( !pkgs.stdenv.isLinux -> (builtins.tryEval ((service.mkServiceOciImage ociArgs).outPath)).success == false ); diff --git a/build/nix/service/nixos-module.nix b/build/nix/service/nixos-module.nix @@ -0,0 +1,253 @@ +{ lib }: +{ + serviceName, + binaryName, + packageFor, + commandForInstance, + stopTimeoutSeconds ? 30, + addressFamilies ? [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ], +}: +assert lib.assertMsg ( + builtins.isString serviceName + && builtins.stringLength serviceName <= 128 + && builtins.match "^[a-z][a-z0-9_]*$" serviceName != null +) "serviceName must be a bounded lowercase snake-case identifier"; +assert lib.assertMsg ( + builtins.isString binaryName + && builtins.stringLength binaryName <= 128 + && builtins.match "^[a-z][a-z0-9_-]*$" binaryName != null +) "binaryName must be a lowercase Cargo binary identifier"; +assert lib.assertMsg (builtins.isFunction packageFor) "packageFor must be a function"; +assert lib.assertMsg (builtins.isFunction commandForInstance) + "commandForInstance must be a function"; +assert lib.assertMsg ( + builtins.isInt stopTimeoutSeconds && stopTimeoutSeconds > 0 && stopTimeoutSeconds <= 86400 +) "stopTimeoutSeconds must be between 1 and 86400"; +assert lib.assertMsg ( + builtins.isList addressFamilies + && addressFamilies != [ ] + && builtins.length addressFamilies <= 3 + && lib.all ( + family: + builtins.elem family [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ] + ) addressFamilies + && builtins.length (lib.unique addressFamilies) == builtins.length addressFamilies +) "addressFamilies must be a unique nonempty subset of the governed families"; +{ + config, + pkgs, + ... +}: +let + optionPath = [ + "services" + "radroots" + serviceName + ]; + cfg = lib.getAttrFromPath optionPath config; + instanceNames = builtins.attrNames cfg.instances; + systemUser = "radroots-${serviceName}"; + validInstanceName = + name: + builtins.isString name + && builtins.stringLength name <= 128 + && builtins.match "^[a-z0-9][a-z0-9_-]*[a-z0-9]$|^[a-z0-9]$" name != null; + unitName = instanceName: "radroots-${serviceName}-${instanceName}"; + validUnitName = instanceName: builtins.stringLength (unitName instanceName) <= 247; + validCredentialName = + name: + builtins.stringLength name <= 128 && builtins.match "^[A-Za-z0-9][A-Za-z0-9_.-]*$" name != null; + validCredentialPath = + path: + builtins.isString path + && builtins.stringLength path <= 4096 + && lib.hasPrefix "/" path + && path != "/nix/store" + && !(lib.hasPrefix "/nix/store/" path) + && builtins.match "^[^:\n]+$" path != null; + validCredentials = + instance: + builtins.length (builtins.attrNames instance.credentials) <= 32 + && lib.all (name: validCredentialName name && validCredentialPath instance.credentials.${name}) ( + builtins.attrNames instance.credentials + ); + commandFor = instanceName: commandForInstance instanceName; + validCommand = + instanceName: + let + command = commandFor instanceName; + in + builtins.isList command + && builtins.length command <= 64 + && lib.all ( + argument: + builtins.isString argument + && builtins.stringLength argument <= 4096 + && builtins.match "^[^\n]*$" argument != null + ) command; + package = cfg.package; + assertions = [ + { + assertion = cfg.enable == (instanceNames != [ ]); + message = "the service must be enabled with at least one instance and disabled without instances"; + } + { + assertion = builtins.length instanceNames <= 64; + message = "a service may define at most 64 instances"; + } + { + assertion = lib.isDerivation package; + message = "the service package must be a derivation"; + } + { + assertion = + cfg.adminGroup == null + || ( + builtins.stringLength cfg.adminGroup <= 128 + && builtins.match "^[a-z_][a-z0-9_-]*$" cfg.adminGroup != null + ); + message = "adminGroup must be a bounded canonical system group name"; + } + ] + ++ lib.concatMap ( + instanceName: + let + instance = cfg.instances.${instanceName}; + in + [ + { + assertion = validInstanceName instanceName; + message = "every service instance must use a bounded canonical identifier"; + } + { + assertion = validUnitName instanceName; + message = "the derived systemd unit name exceeds its 255-byte limit"; + } + { + assertion = validCredentials instance; + message = "credentials must use bounded names and external absolute source paths"; + } + { + assertion = validCommand instanceName; + message = "the service command must contain at most 64 bounded single-line arguments"; + } + ] + ) instanceNames; + mkService = + instanceName: instance: + let + serviceInstance = "radroots/services/${serviceName}/${instanceName}"; + configurationPath = "/etc/${serviceInstance}/config.toml"; + credentialBindings = lib.mapAttrsToList (name: path: "${name}:${path}") instance.credentials; + serviceConfig = { + Type = "simple"; + User = systemUser; + Group = if cfg.adminGroup == null then systemUser else cfg.adminGroup; + DynamicUser = true; + ExecStart = lib.escapeShellArgs ([ "${package}/bin/${binaryName}" ] ++ commandFor instanceName); + WorkingDirectory = "/"; + Restart = "on-failure"; + RestartSec = "5s"; + TimeoutStopSec = "${toString stopTimeoutSeconds}s"; + KillSignal = "SIGTERM"; + KillMode = "control-group"; + SendSIGKILL = true; + UMask = "0077"; + ConfigurationDirectory = serviceInstance; + ConfigurationDirectoryMode = "0500"; + StateDirectory = serviceInstance; + StateDirectoryMode = "0700"; + CacheDirectory = serviceInstance; + CacheDirectoryMode = "0700"; + RuntimeDirectory = serviceInstance; + RuntimeDirectoryMode = if cfg.adminGroup == null then "0700" else "0750"; + RuntimeDirectoryPreserve = false; + BindReadOnlyPaths = [ + "${instance.configurationFile}:${configurationPath}" + ]; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectClock = true; + RestrictSUIDSGID = true; + LockPersonality = true; + CapabilityBoundingSet = ""; + AmbientCapabilities = ""; + RestrictAddressFamilies = addressFamilies; + RestrictNamespaces = true; + RestrictRealtime = true; + RemoveIPC = true; + DevicePolicy = "closed"; + KeyringMode = "private"; + ProcSubset = "pid"; + ProtectProc = "invisible"; + SystemCallArchitectures = "native"; + } + // lib.optionalAttrs (credentialBindings != [ ]) { + LoadCredential = credentialBindings; + }; + in + lib.nameValuePair (unitName instanceName) { + description = "Hardened ${serviceName} service instance ${instanceName}"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + restartIfChanged = true; + stopIfChanged = true; + inherit serviceConfig; + }; +in +{ + options = lib.setAttrByPath optionPath { + enable = lib.mkEnableOption "the hardened ${serviceName} service"; + package = lib.mkOption { + type = lib.types.package; + default = packageFor pkgs; + description = "The exact package containing the ${binaryName} service binary."; + }; + adminGroup = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Optional existing group admitted by the service's local admin policy."; + }; + instances = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + configurationFile = lib.mkOption { + type = lib.types.path; + description = "A non-secret configuration file mounted read-only at the canonical path."; + }; + credentials = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "External absolute credential source paths passed through LoadCredential."; + }; + }; + } + ); + default = { }; + description = "Explicit service instances; every declared instance is active when enabled."; + }; + }; + + config = { + inherit assertions; + systemd.services = lib.mkIf cfg.enable (lib.mapAttrs' mkService cfg.instances); + }; +} diff --git a/flake.nix b/flake.nix @@ -16,11 +16,24 @@ }; outputs = - inputs@{ flake-parts, ... }: + inputs@{ + self, + flake-parts, + ... + }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ inputs.treefmt-nix.flakeModule ]; systems = import ./build/nix/service/systems.nix; + flake.nixosModules.default = + (import ./build/nix/service/nixos-module.nix { lib = inputs.nixpkgs.lib; }) + { + serviceName = "fixture_service"; + binaryName = "fixture-service"; + packageFor = pkgs: self.packages.${pkgs.stdenv.hostPlatform.system}.default; + commandForInstance = _: [ "--help" ]; + }; + perSystem = { config, @@ -51,6 +64,7 @@ }; serviceFixture = import ./build/nix/service/fixture.nix { inherit lib pkgs service; + nixosSystem = inputs.nixpkgs.lib.nixosSystem; toolchain = toolchains.stable; }; in