mirror of
https://github.com/nix-community/home-manager.git
synced 2026-06-05 21:02:51 +00:00
Adds `home.services`, an attribute set of nixpkgs [modular services](https://nixos.org/manual/nixos/unstable/#modular-services) sourced from `<nixpkgs/lib/services/lib.nix>`. Each service exposes `process.argv` and the upstream NixOS-style systemd schema (`systemd.lib`, `systemd.mainExecStart`, `systemd.service`, `systemd.services`, `systemd.sockets`) by re-exporting `nixos/modules/system/service/systemd/service.nix`. Service modules shipped with `_class = "service"` (e.g. `pkgs.<name>.passthru.services.default`) drop in unchanged -- service portability across module systems is the point of modular services. Lifted units are evaluated and translated from NixOS-style attrs (`wantedBy`, `serviceConfig`, `unitConfig`, `environment`, ...) into the section-based INI shape (`{ Unit; Service; Install; }`) that home-manager's `systemd.user.{services,sockets}` consumes; only the common keys are mapped, uncommon options remain reachable via `unitConfig` / `serviceConfig` / `socketConfig`. Sub-services and their units are dashed under the parent service name; `process.argv` becomes the default `ExecStart` for the service's primary unit, which defaults to `WantedBy=default.target`. Mirrors the surface of nixpkgs' portable systemd module (services + sockets only); other unit kinds home-manager supports natively (timers etc.) are intentionally not modeled until upstream grows them. Each service's `configData.<name>` entries are materialized at `$XDG_CONFIG_HOME/system-services/<service-prefix>/<name>` (mirroring how `nixos/modules/system/service/systemd/{config-data-path,system}.nix` lifts `configData` to `environment.etc`), with the absolute path injected back into `configData.<name>.path` so the service can refer to its files at a stable location. Includes nmt tests covering: a basic `process.argv`-only service, a service with a `configData` entry, and importing `pkgs.ghostunnel.passthru.services.default` to assert the lifted user unit contains the expected ExecStart flags and `LoadCredential` entries.
188 lines
5.7 KiB
Nix
188 lines
5.7 KiB
Nix
{
|
|
lib,
|
|
config,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
inherit (lib) concatMapAttrs mkOption types;
|
|
|
|
portable-lib = import (pkgs.path + "/lib/services/lib.nix") { inherit lib; };
|
|
|
|
# Combine a service-name prefix with a unit name. The empty unit name
|
|
# `""` is the convention from nixpkgs' portable services for the
|
|
# service's *primary* unit (e.g. `services.foo.systemd.user.services.""`
|
|
# is the unit named `foo.service`). Sub-units use a hyphenated suffix.
|
|
dashed =
|
|
before: after:
|
|
if after == "" then
|
|
before
|
|
else if before == "" then
|
|
after
|
|
else
|
|
"${before}-${after}";
|
|
|
|
# Translate a NixOS-style systemd unit attrset (wantedBy, serviceConfig,
|
|
# unitConfig, environment, ...) into the section-based INI shape that
|
|
# home-manager's `systemd.user.<unitType>` expects (Unit/Service/Install).
|
|
# Only the common keys are mapped; uncommon options can still be set
|
|
# explicitly via `unitConfig` / `serviceConfig` / `socketConfig`.
|
|
unitAttrKeys = [
|
|
"description"
|
|
"documentation"
|
|
"requires"
|
|
"wants"
|
|
"upholds"
|
|
"after"
|
|
"before"
|
|
"bindsTo"
|
|
"partOf"
|
|
"conflicts"
|
|
"requisite"
|
|
"onFailure"
|
|
"onSuccess"
|
|
];
|
|
pickSection =
|
|
keys: src:
|
|
lib.listToAttrs (
|
|
lib.concatMap (
|
|
k:
|
|
lib.optional (src ? ${k} && src.${k} != null && src.${k} != [ ]) {
|
|
name = lib.toSentenceCase k;
|
|
value = src.${k};
|
|
}
|
|
) keys
|
|
);
|
|
envToList =
|
|
env: lib.mapAttrsToList (k: v: "${k}=${toString v}") (lib.filterAttrs (_: v: v != null) env);
|
|
installSection =
|
|
u:
|
|
lib.filterAttrs (_: v: v != [ ]) {
|
|
WantedBy = u.wantedBy or [ ];
|
|
RequiredBy = u.requiredBy or [ ];
|
|
};
|
|
toHmIni = unit: {
|
|
Unit = pickSection unitAttrKeys unit // (unit.unitConfig or { });
|
|
Service =
|
|
(unit.serviceConfig or { })
|
|
// lib.optionalAttrs (unit ? environment && unit.environment != { }) {
|
|
Environment = envToList unit.environment;
|
|
};
|
|
Install = installSection unit;
|
|
};
|
|
toHmIniSocket = sock: {
|
|
Unit = pickSection unitAttrKeys sock // (sock.unitConfig or { });
|
|
Socket =
|
|
(sock.socketConfig or { })
|
|
// lib.optionalAttrs (sock ? listenStreams && sock.listenStreams != [ ]) {
|
|
ListenStream = sock.listenStreams;
|
|
}
|
|
// lib.optionalAttrs (sock ? listenDatagrams && sock.listenDatagrams != [ ]) {
|
|
ListenDatagram = sock.listenDatagrams;
|
|
};
|
|
Install = installSection sock;
|
|
};
|
|
|
|
# Evaluate a deferredModule into attrs, then translate.
|
|
evalDeferred =
|
|
translator: unitModule:
|
|
translator
|
|
(lib.evalModules {
|
|
modules = [
|
|
(_: {
|
|
# Loose schema: NixOS-style unit attrs include nested sub-attrsets
|
|
# (e.g. `serviceConfig`) that need to merge across definitions.
|
|
freeformType =
|
|
with types;
|
|
attrsOf (oneOf [
|
|
(attrsOf raw)
|
|
(listOf raw)
|
|
raw
|
|
]);
|
|
})
|
|
unitModule
|
|
];
|
|
}).config;
|
|
|
|
makeUnits =
|
|
translator: unitType: prefix: service:
|
|
concatMapAttrs (unitName: unitModule: {
|
|
"${dashed prefix unitName}" = evalDeferred translator unitModule;
|
|
}) service.systemd.${unitType}
|
|
// concatMapAttrs (
|
|
subName: subService: makeUnits translator unitType (dashed prefix subName) subService
|
|
) service.services;
|
|
|
|
# Lift each service's `configData` entries into `xdg.configFile` paths.
|
|
# Mirrors how `nixos/modules/system/service/systemd/system.nix` lifts
|
|
# `configData` to `environment.etc`.
|
|
makeConfigFiles =
|
|
prefix: service:
|
|
lib.mapAttrs' (_: cfg: {
|
|
name = "system-services/${prefix}/${cfg.name}";
|
|
value = lib.filterAttrs (_: v: v != null) {
|
|
source = cfg.source or null;
|
|
text = cfg.text or null;
|
|
inherit (cfg) enable;
|
|
};
|
|
}) (lib.filterAttrs (_: cfg: cfg.enable) (service.configData or { }))
|
|
// concatMapAttrs (
|
|
subName: subService: makeConfigFiles (dashed prefix subName) subService
|
|
) service.services;
|
|
|
|
modularServiceConfiguration = portable-lib.configure {
|
|
serviceManagerPkgs = pkgs;
|
|
extraRootModules = [
|
|
./service.nix
|
|
./config-data-path.nix
|
|
];
|
|
extraRootSpecialArgs = {
|
|
systemdPackage = pkgs.systemd;
|
|
nixpkgsPath = pkgs.path;
|
|
xdgConfigHome = config.xdg.configHome;
|
|
};
|
|
};
|
|
in
|
|
{
|
|
meta.maintainers = [ lib.maintainers.kiara ];
|
|
|
|
options.home.services = mkOption {
|
|
description = ''
|
|
Home-Manager [modular services](https://nixos.org/manual/nixos/unstable/#modular-services).
|
|
|
|
Each entry is an abstract service that may declare a {option}`process.argv`
|
|
and home-manager-style {option}`systemd.user.{services,sockets}` units
|
|
(INI section shape). Units are emitted under home-manager's
|
|
{option}`systemd.user.services` (and friends) with the service name
|
|
as a prefix. Mirrors {option}`system.services` in NixOS.
|
|
'';
|
|
type = types.attrsOf modularServiceConfiguration.serviceSubmodule;
|
|
default = { };
|
|
visible = "shallow";
|
|
};
|
|
|
|
config = {
|
|
assertions = lib.concatLists (
|
|
lib.mapAttrsToList (
|
|
name: cfg: portable-lib.getAssertions [ "home" "services" name ] cfg
|
|
) config.home.services
|
|
);
|
|
|
|
warnings = lib.concatLists (
|
|
lib.mapAttrsToList (
|
|
name: cfg: portable-lib.getWarnings [ "home" "services" name ] cfg
|
|
) config.home.services
|
|
);
|
|
|
|
systemd.user.services = concatMapAttrs (
|
|
name: svc: makeUnits toHmIni "services" name svc
|
|
) config.home.services;
|
|
|
|
systemd.user.sockets = concatMapAttrs (
|
|
name: svc: makeUnits toHmIniSocket "sockets" name svc
|
|
) config.home.services;
|
|
|
|
xdg.configFile = concatMapAttrs (name: svc: makeConfigFiles name svc) config.home.services;
|
|
};
|
|
}
|