push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.automatic-timezoned;
in
{
options = {
services.automatic-timezoned = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable `automatic-timezoned`, simple daemon for keeping the system
timezone up-to-date based on the current location. It uses geoclue2 to
determine the current location and systemd-timedated to actually set
the timezone.
To avoid silent overriding by the service, if you have explicitly set a
timezone, either remove it or ensure that it is set with a lower priority
than the default value using `lib.mkDefault` or `lib.mkOverride`. This is
to make the choice deliberate. An error will be presented otherwise.
'';
};
package = lib.mkPackageOption pkgs "automatic-timezoned" { };
};
};
config = lib.mkIf cfg.enable {
# This will give users an error if they have set an explicit time
# zone, rather than having the service silently override it.
time.timeZone = null;
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.timedate1.set-timezone"
&& subject.user == "automatic-timezoned") {
return polkit.Result.YES;
}
});
'';
services.geoclue2 = {
enable = true;
appConfig.automatic-timezoned = {
isAllowed = true;
isSystem = true;
users = [ (toString config.ids.uids.automatic-timezoned) ];
};
};
systemd.services = {
automatic-timezoned = {
description = "Automatically update system timezone based on location";
requires = [ "automatic-timezoned-geoclue-agent.service" ];
after = [ "automatic-timezoned-geoclue-agent.service" ];
serviceConfig = {
Type = "exec";
User = "automatic-timezoned";
ExecStart = "${cfg.package}/bin/automatic-timezoned";
};
wantedBy = [ "default.target" ];
};
automatic-timezoned-geoclue-agent = {
description = "Geoclue agent for automatic-timezoned";
requires = [ "geoclue.service" ];
after = [ "geoclue.service" ];
serviceConfig = {
Type = "exec";
User = "automatic-timezoned";
ExecStart = "${pkgs.geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/agent";
Restart = "on-failure";
PrivateTmp = true;
};
wantedBy = [ "default.target" ];
};
};
users = {
users.automatic-timezoned = {
description = "automatic-timezoned";
uid = config.ids.uids.automatic-timezoned;
group = "automatic-timezoned";
};
groups.automatic-timezoned = {
gid = config.ids.gids.automatic-timezoned;
};
};
};
}

View File

@@ -0,0 +1,27 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.bpftune;
in
{
meta = {
maintainers = with lib.maintainers; [ nickcao ];
};
options = {
services.bpftune = {
enable = lib.mkEnableOption "bpftune BPF driven auto-tuning";
package = lib.mkPackageOption pkgs "bpftune" { };
};
};
config = lib.mkIf cfg.enable {
systemd.packages = [ cfg.package ];
systemd.services.bpftune.wantedBy = [ "multi-user.target" ];
};
}

View File

@@ -0,0 +1,81 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.cachix-agent;
in
{
meta.maintainers = lib.teams.cachix.members;
options.services.cachix-agent = {
enable = lib.mkEnableOption "Cachix Deploy Agent: <https://docs.cachix.org/deploy/>";
name = lib.mkOption {
type = lib.types.str;
description = "Agent name, usually same as the hostname";
default = config.networking.hostName;
defaultText = "config.networking.hostName";
};
verbose = lib.mkOption {
type = lib.types.bool;
description = "Enable verbose output";
default = false;
};
profile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Profile name, defaults to 'system' (NixOS).";
};
host = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Cachix uri to use.";
};
package = lib.mkPackageOption pkgs "cachix" { };
credentialsFile = lib.mkOption {
type = lib.types.path;
default = "/etc/cachix-agent.token";
description = ''
Required file that needs to contain CACHIX_AGENT_TOKEN=...
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.cachix-agent = {
description = "Cachix Deploy Agent";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
path = [ config.nix.package ];
wantedBy = [ "multi-user.target" ];
# Cachix requires $USER to be set
environment.USER = "root";
# don't stop the service if the unit disappears
unitConfig.X-StopOnRemoval = false;
serviceConfig = {
# we don't want to kill children processes as those are deployments
KillMode = "process";
Restart = "always";
RestartSec = 5;
EnvironmentFile = cfg.credentialsFile;
ExecStart = ''
${cfg.package}/bin/cachix ${lib.optionalString cfg.verbose "--verbose"} ${
lib.optionalString (cfg.host != null) "--host ${cfg.host}"
} \
deploy agent ${cfg.name} ${lib.optionalString (cfg.profile != null) cfg.profile}
'';
};
};
};
}

View File

@@ -0,0 +1,118 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.cachix-watch-store;
in
{
meta = {
maintainers = lib.teams.cachix.members ++ [ lib.maintainers.jfroche ];
};
options.services.cachix-watch-store = {
enable = lib.mkEnableOption "Cachix Watch Store: <https://docs.cachix.org>";
cacheName = lib.mkOption {
type = lib.types.str;
description = "Cachix binary cache name";
};
cachixTokenFile = lib.mkOption {
type = lib.types.path;
description = ''
Required file that needs to contain the cachix auth token.
'';
};
signingKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = ''
Optional file containing a self-managed signing key to sign uploaded store paths.
'';
default = null;
};
compressionLevel = lib.mkOption {
type = lib.types.nullOr (lib.types.ints.between 0 16);
description = "The compression level for ZSTD compression (between 0 and 16)";
default = null;
};
jobs = lib.mkOption {
type = lib.types.nullOr lib.types.ints.positive;
description = "Number of threads used for pushing store paths";
default = null;
};
host = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Cachix host to connect to";
};
verbose = lib.mkOption {
type = lib.types.bool;
description = "Enable verbose output";
default = false;
};
package = lib.mkPackageOption pkgs "cachix" { };
};
config = lib.mkIf cfg.enable {
systemd.services.cachix-watch-store-agent = {
description = "Cachix watch store Agent";
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
path = [ config.nix.package ];
wantedBy = [ "multi-user.target" ];
unitConfig = {
# allow to restart indefinitely
StartLimitIntervalSec = 0;
};
serviceConfig = {
# don't put too much stress on the machine when restarting
RestartSec = 1;
# we don't want to kill children processes as those are deployments
KillMode = "process";
Restart = "on-failure";
DynamicUser = true;
LoadCredential = [
"cachix-token:${toString cfg.cachixTokenFile}"
]
++ lib.optional (cfg.signingKeyFile != null) "signing-key:${toString cfg.signingKeyFile}";
};
script =
let
command = [
"${cfg.package}/bin/cachix"
]
++ (lib.optional cfg.verbose "--verbose")
++ (lib.optionals (cfg.host != null) [
"--host"
cfg.host
])
++ [ "watch-store" ]
++ (lib.optionals (cfg.compressionLevel != null) [
"--compression-level"
(toString cfg.compressionLevel)
])
++ (lib.optionals (cfg.jobs != null) [
"--jobs"
(toString cfg.jobs)
])
++ [ cfg.cacheName ];
in
''
export CACHIX_AUTH_TOKEN="$(<"$CREDENTIALS_DIRECTORY/cachix-token")"
${lib.optionalString (
cfg.signingKeyFile != null
) ''export CACHIX_SIGNING_KEY="$(<"$CREDENTIALS_DIRECTORY/signing-key")"''}
${lib.escapeShellArgs command}
'';
};
};
}

View File

@@ -0,0 +1,273 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.cloud-init;
path =
with pkgs;
[
cloud-init
iproute2
net-tools
openssh
shadow
util-linux
busybox
]
++ lib.optional cfg.btrfs.enable btrfs-progs
++ lib.optional cfg.ext4.enable e2fsprogs
++ lib.optional cfg.xfs.enable xfsprogs
++ cfg.extraPackages;
hasFs = fsName: lib.any (fs: fs.fsType == fsName) (lib.attrValues config.fileSystems);
settingsFormat = pkgs.formats.yaml { };
cfgfile = settingsFormat.generate "cloud.cfg" cfg.settings;
in
{
options = {
services.cloud-init = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable the cloud-init service. This services reads
configuration metadata in a cloud environment and configures
the machine according to this metadata.
This configuration is not completely compatible with the
NixOS way of doing configuration, as configuration done by
cloud-init might be overridden by a subsequent nixos-rebuild
call. However, some parts of cloud-init fall outside of
NixOS's responsibility, like filesystem resizing and ssh
public key provisioning, and cloud-init is useful for that
parts. Thus, be wary that using cloud-init in NixOS might
come as some cost.
'';
};
btrfs.enable = lib.mkOption {
type = lib.types.bool;
default = hasFs "btrfs";
defaultText = lib.literalExpression ''hasFs "btrfs"'';
description = ''
Allow the cloud-init service to operate `btrfs` filesystem.
'';
};
ext4.enable = lib.mkOption {
type = lib.types.bool;
default = hasFs "ext4";
defaultText = lib.literalExpression ''hasFs "ext4"'';
description = ''
Allow the cloud-init service to operate `ext4` filesystem.
'';
};
xfs.enable = lib.mkOption {
type = lib.types.bool;
default = hasFs "xfs";
defaultText = lib.literalExpression ''hasFs "xfs"'';
description = ''
Allow the cloud-init service to operate `xfs` filesystem.
'';
};
network.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Allow the cloud-init service to configure network interfaces
through systemd-networkd.
'';
};
extraPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = ''
List of additional packages to be available within cloud-init jobs.
'';
};
settings = lib.mkOption {
description = ''
Structured cloud-init configuration.
'';
type = lib.types.submodule {
freeformType = settingsFormat.type;
};
default = { };
};
config = lib.mkOption {
type = lib.types.str;
default = "";
description = ''
raw cloud-init configuration.
Takes precedence over the `settings` option if set.
'';
};
};
};
config = lib.mkIf cfg.enable {
services.cloud-init.settings = {
system_info = lib.mkDefault {
distro = "nixos";
network = {
renderers = [ "networkd" ];
};
};
users = lib.mkDefault [ "root" ];
disable_root = lib.mkDefault false;
preserve_hostname = lib.mkDefault false;
cloud_init_modules = lib.mkDefault [
"migrator"
"seed_random"
"bootcmd"
"write-files"
"growpart"
"resizefs"
"update_hostname"
"resolv_conf"
"ca-certs"
"rsyslog"
"users-groups"
];
cloud_config_modules = lib.mkDefault [
"disk_setup"
"mounts"
"ssh-import-id"
"set-passwords"
"timezone"
"disable-ec2-metadata"
"runcmd"
"ssh"
];
cloud_final_modules = lib.mkDefault [
"rightscale_userdata"
"scripts-vendor"
"scripts-per-once"
"scripts-per-boot"
"scripts-per-instance"
"scripts-user"
"ssh-authkey-fingerprints"
"keys-to-console"
"phone-home"
"final-message"
"power-state-change"
];
};
environment.etc."cloud/cloud.cfg" =
if cfg.config == "" then { source = cfgfile; } else { text = cfg.config; };
systemd.network.enable = lib.mkIf cfg.network.enable true;
systemd.services.cloud-init-local = {
description = "Initial cloud-init job (pre-networking)";
wantedBy = [ "multi-user.target" ];
# In certain environments (AWS for example), cloud-init-local will
# first configure an IP through DHCP, and later delete it.
# This can cause race conditions with anything else trying to set IP through DHCP.
before = [
"systemd-networkd.service"
"dhcpcd.service"
];
path = path;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
RemainAfterExit = "yes";
TimeoutSec = "infinity";
StandardOutput = "journal+console";
};
};
systemd.services.cloud-init = {
description = "Initial cloud-init job (metadata service crawler)";
wantedBy = [ "multi-user.target" ];
wants = [
"network-online.target"
"cloud-init-local.service"
"sshd.service"
"sshd-keygen.service"
];
after = [
"network-online.target"
"cloud-init-local.service"
];
before = [
"sshd.service"
"sshd-keygen.service"
];
requires = [ "network.target" ];
path = path;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
RemainAfterExit = "yes";
TimeoutSec = "infinity";
StandardOutput = "journal+console";
};
};
systemd.services.cloud-config = {
description = "Apply the settings specified in cloud-config";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [
"network-online.target"
"cloud-config.target"
];
path = path;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
RemainAfterExit = "yes";
TimeoutSec = "infinity";
StandardOutput = "journal+console";
};
};
systemd.services.cloud-final = {
description = "Execute cloud user/final scripts";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [
"network-online.target"
"cloud-config.service"
"rc-local.service"
];
requires = [ "cloud-config.target" ];
path = path;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
RemainAfterExit = "yes";
TimeoutSec = "infinity";
StandardOutput = "journal+console";
};
};
systemd.targets.cloud-config = {
description = "Cloud-config availability";
requires = [
"cloud-init-local.service"
"cloud-init.service"
];
};
};
meta.maintainers = [ lib.maintainers.zimbatm ];
}

View File

@@ -0,0 +1,257 @@
# D-Bus configuration and system bus daemon.
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.dbus;
configDir = pkgs.makeDBusConf.override {
inherit (cfg) apparmor;
dbus = cfg.dbusPackage;
suidHelper = "${config.security.wrapperDir}/dbus-daemon-launch-helper";
serviceDirectories = cfg.packages;
};
inherit (lib)
mkOption
mkEnableOption
mkIf
mkMerge
types
;
in
{
options = {
boot.initrd.systemd.dbus = {
enable = mkEnableOption "dbus in stage 1";
};
services.dbus = {
enable = mkOption {
type = types.bool;
default = false;
internal = true;
description = ''
Whether to start the D-Bus message bus daemon, which is
required by many other system services and applications.
'';
};
dbusPackage = lib.mkPackageOption pkgs "dbus" { };
brokerPackage = lib.mkPackageOption pkgs "dbus-broker" { };
implementation = mkOption {
type = types.enum [
"dbus"
"broker"
];
default = "dbus";
description = ''
The implementation to use for the message bus defined by the D-Bus specification.
Can be either the classic dbus daemon or dbus-broker, which aims to provide high
performance and reliability, while keeping compatibility to the D-Bus
reference implementation.
'';
};
packages = mkOption {
type = types.listOf types.path;
default = [ ];
description = ''
Packages whose D-Bus configuration files should be included in
the configuration of the D-Bus system-wide or session-wide
message bus. Specifically, files in the following directories
will be included into their respective DBus configuration paths:
{file}`«pkg»/etc/dbus-1/system.d`
{file}`«pkg»/share/dbus-1/system.d`
{file}`«pkg»/share/dbus-1/system-services`
{file}`«pkg»/etc/dbus-1/session.d`
{file}`«pkg»/share/dbus-1/session.d`
{file}`«pkg»/share/dbus-1/services`
'';
};
apparmor = mkOption {
type = types.enum [
"enabled"
"disabled"
"required"
];
description = ''
AppArmor mode for dbus.
`enabled` enables mediation when it's
supported in the kernel, `disabled`
always disables AppArmor even with kernel support, and
`required` fails when AppArmor was not found
in the kernel.
'';
default = "disabled";
};
};
};
config = mkIf cfg.enable (mkMerge [
{
environment.etc."dbus-1".source = configDir;
environment.pathsToLink = [
"/etc/dbus-1"
"/share/dbus-1"
];
users.users.messagebus = {
uid = config.ids.uids.messagebus;
description = "D-Bus system message bus daemon user";
home = "/run/dbus";
homeMode = "0755";
group = "messagebus";
};
users.groups.messagebus.gid = config.ids.gids.messagebus;
# Install dbus for dbus tools even when using dbus-broker
environment.systemPackages = [
cfg.dbusPackage
];
# You still need the dbus reference implementation installed to use dbus-broker
systemd.packages = [
cfg.dbusPackage
];
services.dbus.packages = [
cfg.dbusPackage
config.system.path
];
systemd.user.sockets.dbus.wantedBy = [
"sockets.target"
];
}
(mkIf config.boot.initrd.systemd.dbus.enable {
boot.initrd.systemd = {
users.messagebus = { };
groups.messagebus = { };
contents."/etc/dbus-1".source = pkgs.makeDBusConf.override {
inherit (cfg) apparmor;
dbus = cfg.dbusPackage;
suidHelper = "/bin/false";
serviceDirectories = [
cfg.dbusPackage
config.boot.initrd.systemd.package
];
};
packages = [ cfg.dbusPackage ];
storePaths = [
"${cfg.dbusPackage}/bin/dbus-daemon"
"${config.boot.initrd.systemd.package}/share/dbus-1/system-services"
"${config.boot.initrd.systemd.package}/share/dbus-1/system.d"
];
targets.sockets.wants = [ "dbus.socket" ];
};
})
(mkIf (cfg.implementation == "dbus") {
security.wrappers.dbus-daemon-launch-helper = {
source = "${cfg.dbusPackage}/libexec/dbus-daemon-launch-helper";
owner = "root";
group = "messagebus";
setuid = true;
setgid = false;
permissions = "u+rx,g+rx,o-rx";
};
systemd.services.dbus = {
aliases = [
# hack aiding to prevent dbus from restarting when switching from dbus-broker back to dbus
"dbus-broker.service"
];
# Don't restart dbus-daemon. Bad things tend to happen if we do.
reloadIfChanged = true;
restartTriggers = [
configDir
];
environment = {
LD_LIBRARY_PATH = config.system.nssModules.path;
};
};
systemd.user.services.dbus = {
aliases = [
# hack aiding to prevent dbus from restarting when switching from dbus-broker back to dbus
"dbus-broker.service"
];
# Don't restart dbus-daemon. Bad things tend to happen if we do.
reloadIfChanged = true;
restartTriggers = [
configDir
];
};
})
(mkIf (cfg.implementation == "broker") {
environment.systemPackages = [
cfg.brokerPackage
];
systemd.packages = [
cfg.brokerPackage
];
# Just to be sure we don't restart through the unit alias
systemd.services.dbus.reloadIfChanged = true;
systemd.user.services.dbus.reloadIfChanged = true;
# NixOS Systemd Module doesn't respect 'Install'
# https://github.com/NixOS/nixpkgs/issues/108643
systemd.services.dbus-broker = {
aliases = [
# allow other services to just depend on dbus,
# but also a hack aiding to prevent dbus from restarting when switching from dbus-broker back to dbus
"dbus.service"
];
unitConfig = {
# We get errors when reloading the dbus-broker service
# if /tmp got remounted after this service started
RequiresMountsFor = [ "/tmp" ];
};
# Don't restart dbus. Bad things tend to happen if we do.
reloadIfChanged = true;
restartTriggers = [
configDir
];
environment = {
LD_LIBRARY_PATH = config.system.nssModules.path;
};
};
systemd.user.services.dbus-broker = {
aliases = [
# allow other services to just depend on dbus,
# but also a hack aiding to prevent dbus from restarting when switching from dbus-broker back to dbus
"dbus.service"
];
# Don't restart dbus. Bad things tend to happen if we do.
reloadIfChanged = true;
restartTriggers = [
configDir
];
};
})
]);
}

View File

@@ -0,0 +1,198 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.earlyoom;
inherit (lib)
literalExpression
mkDefault
mkEnableOption
mkIf
mkOption
mkPackageOption
mkRemovedOptionModule
optionalString
optionals
types
;
in
{
meta = {
maintainers = [ ];
};
options.services.earlyoom = {
enable = mkEnableOption "early out of memory killing";
package = mkPackageOption pkgs "earlyoom" { };
freeMemThreshold = mkOption {
type = types.ints.between 1 100;
default = 10;
description = ''
Minimum available memory (in percent).
If the available memory falls below this threshold (and the analog is true for
{option}`freeSwapThreshold`) the killing begins.
SIGTERM is sent first to the process that uses the most memory; then, if the available
memory falls below {option}`freeMemKillThreshold` (and the analog is true for
{option}`freeSwapKillThreshold`), SIGKILL is sent.
See [README](https://github.com/rfjakob/earlyoom#command-line-options) for details.
'';
};
freeMemKillThreshold = mkOption {
type = types.nullOr (types.ints.between 1 100);
default = null;
description = ''
Minimum available memory (in percent) before sending SIGKILL.
If unset, this defaults to half of {option}`freeMemThreshold`.
See the description of [](#opt-services.earlyoom.freeMemThreshold).
'';
};
freeSwapThreshold = mkOption {
type = types.ints.between 1 100;
default = 10;
description = ''
Minimum free swap space (in percent) before sending SIGTERM.
See the description of [](#opt-services.earlyoom.freeMemThreshold).
'';
};
freeSwapKillThreshold = mkOption {
type = types.nullOr (types.ints.between 1 100);
default = null;
description = ''
Minimum free swap space (in percent) before sending SIGKILL.
If unset, this defaults to half of {option}`freeSwapThreshold`.
See the description of [](#opt-services.earlyoom.freeMemThreshold).
'';
};
enableDebugInfo = mkOption {
type = types.bool;
default = false;
description = ''
Enable debugging messages.
'';
};
enableNotifications = mkOption {
type = types.bool;
default = false;
description = ''
Send notifications about killed processes via the system d-bus.
WARNING: enabling this option (while convenient) should *not* be done on a
machine where you do not trust the other users as it allows any other
local user to DoS your session by spamming notifications.
To actually see the notifications in your GUI session, you need to have
`systembus-notify` running as your user, which this
option handles by enabling {option}`services.systembus-notify`.
See [README](https://github.com/rfjakob/earlyoom#notifications) for details.
'';
};
killHook = mkOption {
type = types.nullOr types.path;
default = null;
example = literalExpression ''
pkgs.writeShellScript "earlyoom-kill-hook" '''
echo "Process $EARLYOOM_NAME ($EARLYOOM_PID) was killed" >> /path/to/log
'''
'';
description = ''
An absolute path to an executable to be run for each process killed.
Some environment variables are available, see
[README](https://github.com/rfjakob/earlyoom#notifications) and
[the man page](https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#-n-pathtoscript)
for details.
WARNING: earlyoom is running in a sandbox with ProtectSystem="strict"
by default, so filesystem write is also prohibited for the hook.
If you want to change these protection rules, override the systemd
service via `systemd.services.earlyoom.serviceConfig.ProtectSystem`.
'';
};
reportInterval = mkOption {
type = types.int;
default = 3600;
example = 0;
description = "Interval (in seconds) at which a memory report is printed (set to 0 to disable).";
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"-g"
"--prefer"
"(^|/)(java|chromium)$"
];
description = ''
Extra command-line arguments to be passed to earlyoom. Each element in
the value list will be escaped as an argument without further
word-breaking.
'';
};
};
imports = [
(mkRemovedOptionModule [ "services" "earlyoom" "useKernelOOMKiller" ] ''
This option is deprecated and ignored by earlyoom since 1.2.
'')
(mkRemovedOptionModule [ "services" "earlyoom" "notificationsCommand" ] ''
This option was removed in earlyoom 1.6, but was reimplemented in 1.7
and is available as the new option `services.earlyoom.killHook`.
'')
(mkRemovedOptionModule [ "services" "earlyoom" "ignoreOOMScoreAdjust" ] ''
This option is deprecated and ignored by earlyoom since 1.7.
'')
];
config = mkIf cfg.enable {
services.systembus-notify.enable = mkDefault cfg.enableNotifications;
systemd.packages = [ cfg.package ];
systemd.services.earlyoom = {
overrideStrategy = "asDropin";
wantedBy = [ "multi-user.target" ];
path = optionals cfg.enableNotifications [ pkgs.dbus ];
# We setup `EARLYOOM_ARGS` via drop-ins, so disable the default import
# from /etc/default/earlyoom.
serviceConfig.EnvironmentFile = "";
environment.EARLYOOM_ARGS =
lib.cli.toGNUCommandLineShell { } {
m =
"${toString cfg.freeMemThreshold}"
+ optionalString (cfg.freeMemKillThreshold != null) ",${toString cfg.freeMemKillThreshold}";
s =
"${toString cfg.freeSwapThreshold}"
+ optionalString (cfg.freeSwapKillThreshold != null) ",${toString cfg.freeSwapKillThreshold}";
r = "${toString cfg.reportInterval}";
d = cfg.enableDebugInfo;
n = cfg.enableNotifications;
N = if cfg.killHook != null then cfg.killHook else null;
}
+ " "
+ lib.escapeShellArgs cfg.extraArgs;
};
};
}

View File

@@ -0,0 +1,89 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (lib) mkOption types;
inherit (lib.types) listOf str;
cfg = config.services.kerberos_server;
inherit (config.security.krb5) package;
format = import ../../../security/krb5/krb5-conf-format.nix { inherit pkgs lib; } {
enableKdcACLEntries = true;
};
in
{
imports = [
(lib.mkRenamedOptionModule
[ "services" "kerberos_server" "realms" ]
[ "services" "kerberos_server" "settings" "realms" ]
)
./mit.nix
./heimdal.nix
];
options = {
services.kerberos_server = {
enable = lib.mkEnableOption "the kerberos authentication server";
settings = mkOption {
type = format.type;
description = ''
Settings for the kerberos server of choice.
See the following documentation:
- Heimdal: {manpage}`kdc.conf(5)`
- MIT Kerberos: <https://web.mit.edu/kerberos/krb5-1.21/doc/admin/conf_files/kdc_conf.html>
'';
default = { };
};
extraKDCArgs = mkOption {
type = listOf str;
description = ''
Extra arguments to pass to the KDC process. See {manpage}`kdc(8)`.
'';
default = [ ];
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ package ];
assertions = [
{
assertion = cfg.settings.realms != { };
message = "The server needs at least one realm";
}
{
assertion = lib.length (lib.attrNames cfg.settings.realms) <= 1;
message = "Only one realm per server is currently supported.";
}
{
assertion =
let
inherit (builtins) attrValues elem length;
realms = attrValues cfg.settings.realms;
accesses = lib.concatMap (r: map (a: a.access) r.acl) realms;
property = a: !elem "all" a || (length a <= 1) || (length a <= 2 && elem "get-keys" a);
in
builtins.all property accesses;
message = "Cannot specify \"all\" in a list with additional permissions other than \"get-keys\"";
}
];
systemd.slices.system-kerberos-server = { };
systemd.targets.kerberos-server = {
wantedBy = [ "multi-user.target" ];
};
};
meta = {
doc = ./kerberos-server.md;
};
}

View File

@@ -0,0 +1,129 @@
{
pkgs,
config,
lib,
utils,
...
}:
let
inherit (lib) mapAttrs;
inherit (utils) escapeSystemdExecArgs;
cfg = config.services.kerberos_server;
package = config.security.krb5.package;
aclConfigs = lib.pipe cfg.settings.realms [
(mapAttrs (
name:
{ acl, ... }:
lib.concatMapStringsSep "\n" (
{
principal,
access,
target,
...
}:
if target != "*" && target != "" then
"${principal}\t${lib.concatStringsSep "," (lib.toList access)}\t${target}"
else
"${principal}\t${lib.concatStringsSep "," (lib.toList access)}"
) acl
))
(lib.mapAttrsToList (
name: text: {
dbname = "/var/lib/heimdal/heimdal";
acl_file = pkgs.writeText "${name}.acl" text;
}
))
];
finalConfig = cfg.settings // {
realms = mapAttrs (_: v: removeAttrs v [ "acl" ]) (cfg.settings.realms or { });
kdc = (cfg.settings.kdc or { }) // {
database = aclConfigs;
};
};
format = import ../../../security/krb5/krb5-conf-format.nix { inherit pkgs lib; } {
enableKdcACLEntries = true;
};
kdcConfFile = format.generate "kdc.conf" finalConfig;
in
{
config = lib.mkIf (cfg.enable && package.passthru.implementation == "heimdal") {
environment.etc."heimdal-kdc/kdc.conf".source = kdcConfFile;
systemd.tmpfiles.settings."10-heimdal" =
let
databases = lib.pipe finalConfig.kdc.database [
(map (dbAttrs: dbAttrs.dbname or null))
(lib.filter (x: x != null))
lib.unique
];
in
lib.genAttrs databases (_: {
d = {
user = "root";
group = "root";
mode = "0700";
};
});
systemd.services.kadmind = {
description = "Kerberos Administration Daemon";
partOf = [ "kerberos-server.target" ];
wantedBy = [ "kerberos-server.target" ];
documentation = [
"man:kadmind(8)"
"info:heimdal"
];
serviceConfig = {
ExecStart = "${package}/libexec/kadmind --config-file=/etc/heimdal-kdc/kdc.conf";
Slice = "system-kerberos-server.slice";
StateDirectory = "heimdal";
};
restartTriggers = [ kdcConfFile ];
};
systemd.services.kdc = {
description = "Key Distribution Center daemon";
partOf = [ "kerberos-server.target" ];
wantedBy = [ "kerberos-server.target" ];
documentation = [
"man:kdc(8)"
"info:heimdal"
];
serviceConfig = {
ExecStart = escapeSystemdExecArgs (
[
"${package}/libexec/kdc"
"--config-file=/etc/heimdal-kdc/kdc.conf"
]
++ cfg.extraKDCArgs
);
Slice = "system-kerberos-server.slice";
StateDirectory = "heimdal";
};
restartTriggers = [ kdcConfFile ];
};
systemd.services.kpasswdd = {
description = "Kerberos Password Changing daemon";
partOf = [ "kerberos-server.target" ];
wantedBy = [ "kerberos-server.target" ];
documentation = [
"man:kpasswdd(8)"
"info:heimdal"
];
serviceConfig = {
ExecStart = "${package}/libexec/kpasswdd";
Slice = "system-kerberos-server.slice";
StateDirectory = "heimdal";
};
restartTriggers = [ kdcConfFile ];
};
};
}

View File

@@ -0,0 +1,63 @@
# kerberos_server {#module-services-kerberos-server}
Kerberos is a computer-network authentication protocol that works on the basis of tickets to allow nodes communicating over a non-secure network to prove their identity to one another in a secure manner.
This module provides both the MIT and Heimdal implementations of the a Kerberos server.
## Usage {#module-services-kerberos-server-usage}
To enable a Kerberos server:
```nix
{
security.krb5 = {
# Here you can choose between the MIT and Heimdal implementations.
package = pkgs.krb5;
# package = pkgs.heimdal;
# Optionally set up a client on the same machine as the server
enable = true;
settings = {
libdefaults.default_realm = "EXAMPLE.COM";
realms."EXAMPLE.COM" = {
kdc = "kerberos.example.com";
admin_server = "kerberos.example.com";
};
};
};
services.kerberos-server = {
enable = true;
settings = {
realms."EXAMPLE.COM" = {
acl = [
{
principal = "adminuser";
access = [
"add"
"cpw"
];
}
];
};
};
};
}
```
## Notes {#module-services-kerberos-server-notes}
- The Heimdal documentation will sometimes assume that state is stored in `/var/heimdal`, but this module uses `/var/lib/heimdal` instead.
- Due to the heimdal implementation being chosen through `security.krb5.package`, it is not possible to have a system with one implementation of the client and another of the server.
- While `services.kerberos_server.settings` has a common freeform type between the two implementations, the actual settings that can be set can vary between the two implementations. To figure out what settings are available, you should consult the upstream documentation for the implementation you are using.
## Upstream Documentation {#module-services-kerberos-server-upstream-documentation}
- MIT Kerberos homepage: <https://web.mit.edu/kerberos>
- MIT Kerberos docs: <https://web.mit.edu/kerberos/krb5-latest/doc/index.html>
- Heimdal Kerberos GitHub wiki: <https://github.com/heimdal/heimdal/wiki>
- Heimdal kerberos doc manpages (Debian unstable): <https://manpages.debian.org/unstable/heimdal-docs/index.html>
- Heimdal Kerberos kdc manpages (Debian unstable): <https://manpages.debian.org/unstable/heimdal-kdc/index.html>
Note the version number in the URLs, it may be different for the latest version.

View File

@@ -0,0 +1,112 @@
{
pkgs,
config,
lib,
utils,
...
}:
let
inherit (lib) mapAttrs;
inherit (utils) escapeSystemdExecArgs;
cfg = config.services.kerberos_server;
package = config.security.krb5.package;
PIDFile = "/run/kdc.pid";
format = import ../../../security/krb5/krb5-conf-format.nix { inherit pkgs lib; } {
enableKdcACLEntries = true;
};
aclMap = {
add = "a";
cpw = "c";
delete = "d";
get-keys = "e";
get = "i";
list = "l";
modify = "m";
all = "x";
};
aclConfigs = lib.pipe cfg.settings.realms [
(mapAttrs (
name:
{ acl, ... }:
lib.concatMapStringsSep "\n" (
{
principal,
access,
target,
...
}:
let
access_code = map (a: aclMap.${a}) (lib.toList access);
in
"${principal} ${lib.concatStrings access_code} ${target}"
) acl
))
(lib.concatMapAttrs (
name: text: {
${name} = {
acl_file = pkgs.writeText "${name}.acl" text;
};
}
))
];
finalConfig = cfg.settings // {
realms = mapAttrs (n: v: (removeAttrs v [ "acl" ]) // aclConfigs.${n}) (cfg.settings.realms or { });
};
kdcConfFile = format.generate "kdc.conf" finalConfig;
env = {
# What Debian uses, could possibly link directly to Nix store?
KRB5_KDC_PROFILE = "/etc/krb5kdc/kdc.conf";
};
in
{
config = lib.mkIf (cfg.enable && package.passthru.implementation == "krb5") {
environment = {
etc."krb5kdc/kdc.conf".source = kdcConfFile;
variables = env;
};
systemd.services.kadmind = {
description = "Kerberos Administration Daemon";
partOf = [ "kerberos-server.target" ];
wantedBy = [ "kerberos-server.target" ];
serviceConfig = {
ExecStart = "${package}/bin/kadmind -nofork";
Slice = "system-kerberos-server.slice";
StateDirectory = "krb5kdc";
};
restartTriggers = [ kdcConfFile ];
environment = env;
};
systemd.services.kdc = {
description = "Key Distribution Center daemon";
partOf = [ "kerberos-server.target" ];
wantedBy = [ "kerberos-server.target" ];
serviceConfig = {
Type = "forking";
PIDFile = PIDFile;
ExecStart = escapeSystemdExecArgs (
[
"${package}/bin/krb5kdc"
"-P"
"${PIDFile}"
]
++ cfg.extraKDCArgs
);
Slice = "system-kerberos-server.slice";
StateDirectory = "krb5kdc";
};
restartTriggers = [ kdcConfFile ];
environment = env;
};
};
}

View File

@@ -0,0 +1,80 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.localtimed;
in
{
imports = [ (lib.mkRenamedOptionModule [ "services" "localtime" ] [ "services" "localtimed" ]) ];
options = {
services.localtimed = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable `localtimed`, a simple daemon for keeping the
system timezone up-to-date based on the current location. It uses
geoclue2 to determine the current location.
To avoid silent overriding by the service, if you have explicitly set a
timezone, either remove it or ensure that it is set with a lower priority
than the default value using `lib.mkDefault` or `lib.mkOverride`. This is
to make the choice deliberate. An error will be presented otherwise.
'';
};
package = lib.mkPackageOption pkgs "localtime" { };
geoclue2Package = lib.mkPackageOption pkgs "Geoclue2" { default = "geoclue2-with-demo-agent"; };
};
};
config = lib.mkIf cfg.enable {
# This will give users an error if they have set an explicit time
# zone, rather than having the service silently override it.
time.timeZone = null;
services.geoclue2.appConfig.localtimed = {
isAllowed = true;
isSystem = true;
users = [ (toString config.ids.uids.localtimed) ];
};
# Install the polkit rules.
environment.systemPackages = [ cfg.package ];
systemd.services.localtimed = {
wantedBy = [ "multi-user.target" ];
partOf = [ "localtimed-geoclue-agent.service" ];
after = [ "localtimed-geoclue-agent.service" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/localtimed";
Restart = "on-failure";
Type = "exec";
User = "localtimed";
};
};
systemd.services.localtimed-geoclue-agent = {
wantedBy = [ "multi-user.target" ];
partOf = [ "geoclue.service" ];
after = [ "geoclue.service" ];
serviceConfig = {
ExecStart = "${cfg.geoclue2Package}/libexec/geoclue-2.0/demos/agent";
Restart = "on-failure";
Type = "exec";
User = "localtimed";
};
};
users = {
users.localtimed = {
uid = config.ids.uids.localtimed;
group = "localtimed";
};
groups.localtimed.gid = config.ids.gids.localtimed;
};
};
}

View File

@@ -0,0 +1,296 @@
/*
Declares what makes the nix-daemon work on systemd.
See also
- nixos/modules/config/nix.nix: the nix.conf
- nixos/modules/config/nix-remote-build.nix: the nix.conf
*/
{
config,
lib,
pkgs,
...
}:
let
cfg = config.nix;
nixPackage = cfg.package.out;
# nixVersion is an attribute which defines the implementation version.
# This is useful for Nix implementations which don't follow Nix's versioning.
isNixAtLeast = lib.versionAtLeast (nixPackage.nixVersion or (lib.getVersion nixPackage));
makeNixBuildUser = nr: {
name = "nixbld${toString nr}";
value = {
description = "Nix build user ${toString nr}";
/*
For consistency with the setgid(2), setuid(2), and setgroups(2)
calls in `libstore/build.cc', don't add any supplementary group
here except "nixbld".
*/
uid = builtins.add config.ids.uids.nixbld nr;
isSystemUser = true;
group = "nixbld";
extraGroups = [ "nixbld" ];
};
};
nixbldUsers = lib.listToAttrs (map makeNixBuildUser (lib.range 1 cfg.nrBuildUsers));
in
{
imports = [
(lib.mkRenamedOptionModuleWith {
sinceRelease = 2205;
from = [
"nix"
"daemonIONiceLevel"
];
to = [
"nix"
"daemonIOSchedPriority"
];
})
(lib.mkRenamedOptionModuleWith {
sinceRelease = 2211;
from = [
"nix"
"readOnlyStore"
];
to = [
"boot"
"readOnlyNixStore"
];
})
(lib.mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.")
];
###### interface
options = {
nix = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to enable Nix.
Disabling Nix makes the system hard to modify and the Nix programs and configuration will not be made available by NixOS itself.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.nix;
defaultText = lib.literalExpression "pkgs.nix";
description = ''
This option specifies the Nix package instance to use throughout the system.
'';
};
daemonCPUSchedPolicy = lib.mkOption {
type = lib.types.enum [
"other"
"batch"
"idle"
];
default = "other";
example = "batch";
description = ''
Nix daemon process CPU scheduling policy. This policy propagates to
build processes. `other` is the default scheduling
policy for regular tasks. The `batch` policy is
similar to `other`, but optimised for
non-interactive tasks. `idle` is for extremely
low-priority tasks that should only be run when no other task
requires CPU time.
Please note that while using the `idle` policy may
greatly improve responsiveness of a system performing expensive
builds, it may also slow down and potentially starve crucial
configuration updates during load.
`idle` may therefore be a sensible policy for
systems that experience only intermittent phases of high CPU load,
such as desktop or portable computers used interactively. Other
systems should use the `other` or
`batch` policy instead.
For more fine-grained resource control, please refer to
{manpage}`systemd.resource-control(5)` and adjust
{option}`systemd.services.nix-daemon` directly.
'';
};
daemonIOSchedClass = lib.mkOption {
type = lib.types.enum [
"best-effort"
"idle"
];
default = "best-effort";
example = "idle";
description = ''
Nix daemon process I/O scheduling class. This class propagates to
build processes. `best-effort` is the default
class for regular tasks. The `idle` class is for
extremely low-priority tasks that should only perform I/O when no
other task does.
Please note that while using the `idle` scheduling
class can improve responsiveness of a system performing expensive
builds, it might also slow down or starve crucial configuration
updates during load.
`idle` may therefore be a sensible class for
systems that experience only intermittent phases of high I/O load,
such as desktop or portable computers used interactively. Other
systems should use the `best-effort` class.
'';
};
daemonIOSchedPriority = lib.mkOption {
type = lib.types.int;
default = 4;
example = 1;
description = ''
Nix daemon process I/O scheduling priority. This priority propagates
to build processes. The supported priorities depend on the
scheduling policy: With idle, priorities are not used in scheduling
decisions. best-effort supports values in the range 0 (high) to 7
(low).
'';
};
# Environment variables for running Nix.
envVars = lib.mkOption {
type = lib.types.attrs;
internal = true;
default = { };
description = "Environment variables used by Nix.";
};
nrBuildUsers = lib.mkOption {
type = lib.types.int;
description = ''
Number of `nixbld` user accounts created to
perform secure concurrent builds. If you receive an error
message saying that all build users are currently in use,
you should increase this value.
'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
environment.systemPackages = [
nixPackage
pkgs.nix-info
]
++ lib.optional (config.programs.bash.completion.enable) pkgs.nix-bash-completions;
systemd.packages = [ nixPackage ];
systemd.tmpfiles = lib.mkMerge [
(lib.mkIf (isNixAtLeast "2.8") {
packages = [ nixPackage ];
})
(lib.mkIf (!isNixAtLeast "2.8") {
rules = [
"d /nix/var/nix/daemon-socket 0755 root root - -"
];
})
];
systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
systemd.services.nix-daemon = {
path = [
nixPackage
pkgs.util-linux
config.programs.ssh.package
]
++ lib.optionals cfg.distributedBuilds [ pkgs.gzip ];
environment =
cfg.envVars
// {
CURL_CA_BUNDLE = config.security.pki.caBundle;
}
// config.networking.proxy.envVars;
unitConfig.RequiresMountsFor = "/nix/store";
serviceConfig = {
CPUSchedulingPolicy = cfg.daemonCPUSchedPolicy;
IOSchedulingClass = cfg.daemonIOSchedClass;
IOSchedulingPriority = cfg.daemonIOSchedPriority;
LimitNOFILE = 1048576;
Delegate = "yes";
DelegateSubgroup = "supervisor";
};
restartTriggers = [ config.environment.etc."nix/nix.conf".source ];
# `stopIfChanged = false` changes to switch behavior
# from stop -> update units -> start
# to update units -> restart
#
# The `stopIfChanged` setting therefore controls a trade-off between a
# more predictable lifecycle, which runs the correct "version" of
# the `ExecStop` line, and on the other hand the availability of
# sockets during the switch, as the effectiveness of the stop operation
# depends on the socket being stopped as well.
#
# As `nix-daemon.service` does not make use of `ExecStop`, we prefer
# to keep the socket up and available. This is important for machines
# that run Nix-based services, such as automated build, test, and deploy
# services, that expect the daemon socket to be available at all times.
#
# Notably, the Nix client does not retry on failure to connect to the
# daemon socket, and the in-process RemoteStore instance will disable
# itself. This makes retries infeasible even for services that are
# aware of the issue. Failure to connect can affect not only new client
# processes, but also new RemoteStore instances in existing processes,
# as well as existing RemoteStore instances that have not saturated
# their connection pool.
#
# Also note that `stopIfChanged = true` does not kill existing
# connection handling daemons, as one might wish to happen before a
# breaking Nix upgrade (which is rare). The daemon forks that handle
# the individual connections split off into their own sessions, causing
# them not to be stopped by systemd.
# If a Nix upgrade does require all existing daemon processes to stop,
# nix-daemon must do so on its own accord, and only when the new version
# starts and detects that Nix's persistent state needs an upgrade.
stopIfChanged = false;
};
# Set up the environment variables for running Nix.
environment.sessionVariables = cfg.envVars;
nix.nrBuildUsers = lib.mkDefault (
if cfg.settings.auto-allocate-uids or false then
0
else
lib.max 32 (if cfg.settings.max-jobs == "auto" then 0 else cfg.settings.max-jobs)
);
users.users = nixbldUsers;
services.displayManager.hiddenUsers = lib.attrNames nixbldUsers;
# Legacy configuration conversion.
nix.settings = lib.mkMerge [
(lib.mkIf (isNixAtLeast "2.3pre") { sandbox-fallback = false; })
];
};
}

View File

@@ -0,0 +1,34 @@
# We basically use nscd as a proxy for forwarding nss requests to appropriate
# nss modules, as we run nscd with LD_LIBRARY_PATH set to the directory
# containing all such modules
# Note that we can not use `enable-cache no` As this will actually cause nscd
# to just reject the nss requests it receives, which then causes glibc to
# fallback to trying to handle the request by itself. Which won't work as glibc
# is not aware of the path in which the nss modules live. As a workaround, we
# have `enable-cache yes` with an explicit ttl of 0
server-user nscd
enable-cache passwd yes
positive-time-to-live passwd 0
negative-time-to-live passwd 0
shared passwd yes
enable-cache group yes
positive-time-to-live group 0
negative-time-to-live group 0
shared group yes
enable-cache netgroup yes
positive-time-to-live netgroup 0
negative-time-to-live netgroup 0
shared netgroup yes
enable-cache hosts yes
positive-time-to-live hosts 0
negative-time-to-live hosts 0
shared hosts yes
enable-cache services yes
positive-time-to-live services 0
negative-time-to-live services 0
shared services yes

View File

@@ -0,0 +1,162 @@
{
config,
lib,
pkgs,
...
}:
let
nssModulesPath = config.system.nssModules.path;
cfg = config.services.nscd;
in
{
###### interface
options = {
services.nscd = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to enable the Name Service Cache Daemon.
Disabling this is strongly discouraged, as this effectively disables NSS Lookups
from all non-glibc NSS modules, including the ones provided by systemd.
'';
};
enableNsncd = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to use nsncd instead of nscd from glibc.
This is a nscd-compatible daemon, that proxies lookups, without any caching.
Using nscd from glibc is discouraged.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "nscd";
description = ''
User account under which nscd runs.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = "nscd";
description = ''
User group under which nscd runs.
'';
};
config = lib.mkOption {
type = lib.types.lines;
default = builtins.readFile ./nscd.conf;
description = ''
Configuration to use for Name Service Cache Daemon.
Only used in case glibc-nscd is used.
'';
};
package = lib.mkOption {
type = lib.types.package;
default =
if pkgs.stdenv.hostPlatform.libc == "glibc" then pkgs.stdenv.cc.libc.bin else pkgs.glibc.bin;
defaultText = lib.literalExpression ''
if pkgs.stdenv.hostPlatform.libc == "glibc"
then pkgs.stdenv.cc.libc.bin
else pkgs.glibc.bin;
'';
description = ''
package containing the nscd binary to be used by the service.
Ignored when enableNsncd is set to true.
'';
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
environment.etc."nscd.conf".text = cfg.config;
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
};
users.groups.${cfg.group} = { };
systemd.services.nscd = {
description = "Name Service Cache Daemon" + lib.optionalString cfg.enableNsncd " (nsncd)";
before = [
"nss-lookup.target"
"nss-user-lookup.target"
];
wants = [
"nss-lookup.target"
"nss-user-lookup.target"
];
wantedBy = [ "multi-user.target" ];
requiredBy = [
"nss-lookup.target"
"nss-user-lookup.target"
];
environment = {
LD_LIBRARY_PATH = nssModulesPath;
};
restartTriggers = lib.optionals (!cfg.enableNsncd) (
[
config.environment.etc.hosts.source
config.environment.etc."nsswitch.conf".source
config.environment.etc."nscd.conf".source
]
++ lib.optionals config.users.mysql.enable [
config.environment.etc."libnss-mysql.cfg".source
config.environment.etc."libnss-mysql-root.cfg".source
]
);
# In some configurations, nscd needs to be started as root; it will
# drop privileges after all the NSS modules have read their
# configuration files. So prefix the ExecStart command with "!" to
# prevent systemd from dropping privileges early. See ExecStart in
# systemd.service(5). We use a static user, because some NSS modules
# sill want to read their configuration files after the privilege drop
# and so users can set the owner of those files to the nscd user.
serviceConfig = {
ExecStart = if cfg.enableNsncd then "${pkgs.nsncd}/bin/nsncd" else "!@${cfg.package}/bin/nscd nscd";
Type = if cfg.enableNsncd then "notify" else "forking";
User = cfg.user;
Group = cfg.group;
RemoveIPC = true;
PrivateTmp = true;
# https://github.com/twosigma/nsncd/pull/33/files#r1496927653
Environment = [ "NSNCD_HANDOFF_TIMEOUT=10" ];
NoNewPrivileges = true;
RestrictSUIDSGID = true;
ProtectSystem = "strict";
ProtectHome = "read-only";
RuntimeDirectory = "nscd";
PIDFile = "/run/nscd/nscd.pid";
Restart = "always";
ExecReload = lib.optionals (!cfg.enableNsncd) [
"${cfg.package}/bin/nscd --invalidate passwd"
"${cfg.package}/bin/nscd --invalidate group"
"${cfg.package}/bin/nscd --invalidate hosts"
];
};
};
};
}

View File

@@ -0,0 +1,204 @@
{
config,
options,
lib,
pkgs,
...
}:
let
inherit (lib) types;
cfg = config.services.nvme-rs;
opt = options.services.nvme-rs;
settingsFormat = pkgs.formats.toml { };
in
{
options.services.nvme-rs = {
enable = lib.mkEnableOption "nvme-rs, a monitoring service";
package = lib.mkPackageOption pkgs "nvme-rs" { };
settings = lib.mkOption {
type = types.submodule {
freeformType = settingsFormat.type;
options = {
check_interval_secs = lib.mkOption {
type = types.int;
default = 3600;
description = "Check interval in seconds";
example = 86400;
};
thresholds = lib.mkOption {
type = types.submodule {
freeformType = settingsFormat.type;
options = {
temp_warning = lib.mkOption {
type = types.int;
default = 55;
description = "Temperature warning threshold (°C)";
};
temp_critical = lib.mkOption {
type = types.int;
default = 65;
description = "Temperature critical threshold (°C)";
};
wear_warning = lib.mkOption {
type = types.int;
default = 20;
description = "Wear warning threshold (%)";
};
wear_critical = lib.mkOption {
type = types.int;
default = 50;
description = "Wear critical threshold (%)";
};
spare_warning = lib.mkOption {
type = types.int;
default = 50;
description = "Available spare warning threshold (%)";
};
error_threshold = lib.mkOption {
type = types.int;
default = 100;
description = "Error count warning threshold";
};
};
};
default = { };
description = "Threshold configuration for NVMe monitoring";
};
email = lib.mkOption {
type = types.nullOr (
types.submodule {
freeformType = settingsFormat.type;
options = {
smtp_server = lib.mkOption {
type = types.str;
default = "smtp.gmail.com";
description = "SMTP server address";
example = "mail.example.com";
};
smtp_port = lib.mkOption {
type = types.port;
default = 587;
description = "SMTP server port";
};
smtp_username = lib.mkOption {
type = types.str;
description = "SMTP username";
example = "your-email@gmail.com";
};
smtp_password_file = lib.mkOption {
type = types.path;
description = "File containing SMTP password";
example = "/run/secrets/smtp-password";
};
from = lib.mkOption {
type = types.str;
description = "Sender email address";
example = "nvme-monitor@example.com";
};
to = lib.mkOption {
type = types.str;
description = "Recipient email address";
example = "admin@example.com";
};
use_tls = lib.mkOption {
type = types.bool;
default = true;
description = "Use TLS for SMTP connection";
};
};
}
);
default = null;
description = "Email notification configuration";
};
};
};
default = { };
description = ''
Configuration for nvme-rs in TOML format.
See the config.toml example for all available options.
'';
};
};
config = lib.mkIf cfg.enable {
services.nvme-rs.settings = opt.settings.default;
systemd.services.nvme-rs = {
description = "NVMe health monitoring service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig =
let
settingsWithoutNull =
if cfg.settings.email == null then lib.removeAttrs cfg.settings [ "email" ] else cfg.settings;
configFile = settingsFormat.generate "nvme-rs.toml" settingsWithoutNull;
in
{
ExecStart = lib.escapeShellArgs [
"${lib.getExe cfg.package}"
"daemon"
"--config"
"${configFile}"
];
DynamicUser = true;
SupplementaryGroups = [ "disk" ];
CapabilityBoundingSet = [ "CAP_SYS_ADMIN" ];
AmbientCapabilities = [ "CAP_SYS_ADMIN" ];
LimitCORE = 0;
LimitNOFILE = 65535;
LockPersonality = true;
MemorySwapMax = 0;
MemoryZSwapMax = 0;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
Restart = "on-failure";
RestartSec = "10s";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"@resources"
"~@privileged"
];
NoNewPrivileges = true;
UMask = "0077";
};
};
environment.systemPackages = [ cfg.package ];
};
}

View File

@@ -0,0 +1,58 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.saslauthd;
in
{
###### interface
options = {
services.saslauthd = {
enable = lib.mkEnableOption "saslauthd, the Cyrus SASL authentication daemon";
package = lib.mkPackageOption pkgs [ "cyrus_sasl" "bin" ] { };
mechanism = lib.mkOption {
type = lib.types.str;
default = "pam";
description = "Auth mechanism to use";
};
config = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Configuration to use for Cyrus SASL authentication daemon.";
};
};
};
###### implementation
config = lib.mkIf cfg.enable {
systemd.services.saslauthd = {
description = "Cyrus SASL authentication daemon";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "@${cfg.package}/sbin/saslauthd saslauthd -a ${cfg.mechanism} -O ${pkgs.writeText "saslauthd.conf" cfg.config}";
Type = "forking";
PIDFile = "/run/saslauthd/saslauthd.pid";
Restart = "always";
};
};
};
}

View File

@@ -0,0 +1,202 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.self-deploy;
workingDirectory = "/var/lib/nixos-self-deploy";
repositoryDirectory = "${workingDirectory}/repo";
outPath = "${workingDirectory}/system";
gitWithRepo = "git -C ${repositoryDirectory}";
renderNixArgs =
args:
let
toArg =
key: value:
if builtins.isString value then
" --argstr ${lib.escapeShellArg key} ${lib.escapeShellArg value}"
else
" --arg ${lib.escapeShellArg key} ${lib.escapeShellArg (toString value)}";
in
lib.concatStrings (lib.mapAttrsToList toArg args);
isPathType = x: lib.types.path.check x;
in
{
options.services.self-deploy = {
enable = lib.mkEnableOption "self-deploy";
nixFile = lib.mkOption {
type = lib.types.path;
default = "/default.nix";
description = ''
Path to nix file in repository. Leading '/' refers to root of
git repository.
'';
};
nixAttribute = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
Attribute of `nixFile` that builds the current system.
'';
};
nixArgs = lib.mkOption {
type = lib.types.attrs;
default = { };
description = ''
Arguments to `nix-build` passed as `--argstr` or `--arg` depending on
the type.
'';
};
switchCommand = lib.mkOption {
type = lib.types.enum [
"boot"
"switch"
"dry-activate"
"test"
];
default = "switch";
description = ''
The `switch-to-configuration` subcommand used.
'';
};
repository = lib.mkOption {
type =
with lib.types;
oneOf [
path
str
];
description = ''
The repository to fetch from. Must be properly formatted for git.
If this value is set to a path (must begin with `/`) then it's
assumed that the repository is local and the resulting service
won't wait for the network to be up.
If the repository will be fetched over SSH, you must add an
entry to `programs.ssh.knownHosts` for the SSH host for the fetch
to be successful.
'';
};
sshKeyFile = lib.mkOption {
type = with lib.types; nullOr path;
default = null;
description = ''
Path to SSH private key used to fetch private repositories over
SSH.
'';
};
branch = lib.mkOption {
type = lib.types.str;
default = "master";
description = ''
Branch to track
Technically speaking any ref can be specified here, as this is
passed directly to a `git fetch`, but for the use-case of
continuous deployment you're likely to want to specify a branch.
'';
};
startAt = lib.mkOption {
type = with lib.types; either str (listOf str);
default = "hourly";
description = ''
The schedule on which to run the `self-deploy` service. Format
specified by `systemd.time 7`.
This value can also be a list of `systemd.time 7` formatted
strings, in which case the service will be started on multiple
schedules.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.self-deploy = rec {
inherit (cfg) startAt;
serviceConfig.Type = "oneshot";
requires = lib.mkIf (!(isPathType cfg.repository)) [ "network-online.target" ];
after = requires;
environment.GIT_SSH_COMMAND = lib.mkIf (
cfg.sshKeyFile != null
) "${pkgs.openssh}/bin/ssh -i ${lib.escapeShellArg cfg.sshKeyFile}";
restartIfChanged = false;
path =
with pkgs;
[
git
gnutar
gzip
nix
]
++ lib.optionals (cfg.switchCommand == "boot") [ systemd ];
script = ''
if [ ! -e ${repositoryDirectory} ]; then
mkdir --parents ${repositoryDirectory}
git init ${repositoryDirectory}
fi
${gitWithRepo} fetch ${lib.escapeShellArg cfg.repository} ${lib.escapeShellArg cfg.branch}
${gitWithRepo} checkout FETCH_HEAD
nix-build${renderNixArgs cfg.nixArgs} ${
lib.cli.toGNUCommandLineShell { } {
attr = cfg.nixAttribute;
out-link = outPath;
}
} ${lib.escapeShellArg "${repositoryDirectory}${cfg.nixFile}"}
${lib.optionalString (
cfg.switchCommand != "test"
) "nix-env --profile /nix/var/nix/profiles/system --set ${outPath}"}
${outPath}/bin/switch-to-configuration ${cfg.switchCommand}
rm ${outPath}
${gitWithRepo} gc --prune=all
${lib.optionalString (cfg.switchCommand == "boot") "systemctl reboot"}
'';
};
};
}

View File

@@ -0,0 +1,143 @@
{
config,
lib,
pkgs,
utils,
...
}:
let
cfg = config.services.swapspace;
inherit (lib)
types
mkIf
mkOption
mkPackageOption
mkEnableOption
;
inherit (pkgs)
makeWrapper
runCommand
writeText
;
configFile = writeText "swapspace.conf" (lib.generators.toKeyValue { } cfg.settings);
userWrapper =
runCommand "swapspace"
{
buildInputs = [ makeWrapper ];
}
''
mkdir -p "$out/bin"
makeWrapper '${lib.getExe cfg.package}' "$out/bin/swapspace" \
--add-flags "-c '${configFile}'"
'';
in
{
options.services.swapspace = {
enable = mkEnableOption "Swapspace, a dynamic swap space manager";
package = mkPackageOption pkgs "swapspace" { };
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"-P"
"-v"
];
description = "Any extra arguments to pass to swapspace";
};
installWrapper = mkOption {
type = types.bool;
default = true;
description = ''
This will add swapspace wrapped with the generated config, to environment.systemPackages
'';
};
settings = mkOption {
type = types.submodule {
options = {
swappath = mkOption {
type = types.str;
default = "/var/lib/swapspace";
description = "Location where swapspace may create and delete swapfiles";
};
lower_freelimit = mkOption {
type = types.ints.between 0 99;
default = 20;
description = "Lower free-space threshold: if the percentage of free space drops below this number, additional swapspace is allocated";
};
upper_freelimit = mkOption {
type = types.ints.between 0 100;
default = 60;
description = "Upper free-space threshold: if the percentage of free space exceeds this number, swapspace will attempt to free up swapspace";
};
freetarget = mkOption {
type = types.ints.between 2 99;
default = 30;
description = ''
Percentage of free space swapspace should aim for when adding swapspace.
This should fall somewhere between lower_freelimit and upper_freelimit.
'';
};
min_swapsize = mkOption {
type = types.str;
default = "4m";
description = "Smallest allowed size for individual swapfiles";
};
max_swapsize = mkOption {
type = types.str;
default = "2t";
description = "Greatest allowed size for individual swapfiles";
};
cooldown = mkOption {
type = types.ints.unsigned;
default = 600;
description = ''
Duration (roughly in seconds) of the moratorium on swap allocation that is instated if disk space runs out, or the cooldown time after a new swapfile is successfully allocated before swapspace will consider deallocating swap space again.
The default cooldown period is about 10 minutes.
'';
};
buffer_elasticity = mkOption {
type = types.ints.between 0 100;
default = 30;
description = ''Percentage of buffer space considered to be "free"'';
};
cache_elasticity = mkOption {
type = types.ints.between 0 100;
default = 80;
description = ''Percentage of cache space considered to be "free"'';
};
};
};
default = { };
description = ''
Config file for swapspace.
See the options here: <https://github.com/Tookmund/Swapspace/blob/master/swapspace.conf>
'';
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ (if cfg.installWrapper then userWrapper else cfg.package) ];
systemd.packages = [ cfg.package ];
systemd.services.swapspace = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = [
""
"${lib.getExe cfg.package} -c ${configFile} ${utils.escapeSystemdExecArgs cfg.extraArgs}"
];
};
};
systemd.tmpfiles.settings.swapspace = {
${cfg.settings.swappath}.d = {
mode = "0700";
};
};
};
meta = {
maintainers = with lib.maintainers; [
Luflosi
phanirithvij
];
};
}

View File

@@ -0,0 +1,32 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.systembus-notify;
inherit (lib) mkEnableOption mkIf;
in
{
options.services.systembus-notify = {
enable = mkEnableOption ''
System bus notification support
WARNING: enabling this option (while convenient) should *not* be done on a
machine where you do not trust the other users as it allows any other
local user to DoS your session by spamming notifications
'';
};
config = mkIf cfg.enable {
systemd = {
packages = with pkgs; [ systembus-notify ];
user.services.systembus-notify.wantedBy = [ "graphical-session.target" ];
};
};
}

View File

@@ -0,0 +1,47 @@
# systemd-lock-handler {#module-services-systemd-lock-handler}
The `systemd-lock-handler` module provides a service that bridges
D-Bus events from `logind` to user-level systemd targets:
- `lock.target` started by `loginctl lock-session`,
- `unlock.target` started by `loginctl unlock-session` and
- `sleep.target` started by `systemctl suspend`.
You can create a user service that starts with any of these targets.
For example, to create a service for `swaylock`:
```nix
{
services.systemd-lock-handler.enable = true;
systemd.user.services.swaylock = {
description = "Screen locker for Wayland";
documentation = [ "man:swaylock(1)" ];
# If swaylock exits cleanly, unlock the session:
onSuccess = [ "unlock.target" ];
# When lock.target is stopped, stops this too:
partOf = [ "lock.target" ];
# Delay lock.target until this service is ready:
before = [ "lock.target" ];
wantedBy = [ "lock.target" ];
serviceConfig = {
# systemd will consider this service started when swaylock forks...
Type = "forking";
# ... and swaylock will fork only after it has locked the screen.
ExecStart = "${lib.getExe pkgs.swaylock} -f";
# If swaylock crashes, always restart it immediately:
Restart = "on-failure";
RestartSec = 0;
};
};
}
```
See [upstream documentation](https://sr.ht/~whynothugo/systemd-lock-handler) for more information.

View File

@@ -0,0 +1,28 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.systemd-lock-handler;
inherit (lib) mkIf mkEnableOption mkPackageOption;
in
{
options.services.systemd-lock-handler = {
enable = mkEnableOption "systemd-lock-handler";
package = mkPackageOption pkgs "systemd-lock-handler" { };
};
config = mkIf cfg.enable {
systemd.packages = [ cfg.package ];
# https://github.com/NixOS/nixpkgs/issues/81138
systemd.user.services.systemd-lock-handler.wantedBy = [ "default.target" ];
};
meta = {
maintainers = with lib.maintainers; [ liff ];
doc = ./systemd-lock-handler.md;
};
}

View File

@@ -0,0 +1,62 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.uptimed;
stateDir = "/var/lib/uptimed";
in
{
options = {
services.uptimed = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable `uptimed`, allowing you to track
your highest uptimes.
'';
};
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.uptimed ];
users.users.uptimed = {
description = "Uptimed daemon user";
home = stateDir;
uid = config.ids.uids.uptimed;
group = "uptimed";
};
users.groups.uptimed = { };
systemd.services.uptimed = {
unitConfig.Documentation = "man:uptimed(8) man:uprecords(1)";
description = "uptimed service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Restart = "on-failure";
User = "uptimed";
Nice = 19;
IOSchedulingClass = "idle";
PrivateTmp = "yes";
PrivateNetwork = "yes";
NoNewPrivileges = "yes";
StateDirectory = [ "uptimed" ];
InaccessibleDirectories = "/home";
ExecStart = "${pkgs.uptimed}/sbin/uptimed -f -p ${stateDir}/pid";
};
preStart = ''
if ! test -f ${stateDir}/bootid ; then
${pkgs.uptimed}/sbin/uptimed -b
fi
'';
};
};
}

View File

@@ -0,0 +1,190 @@
{
utils,
config,
lib,
pkgs,
...
}:
let
cfg = config.services.userborn;
userCfg = config.users;
userbornConfig = {
groups = lib.mapAttrsToList (username: opts: {
inherit (opts) name gid members;
}) config.users.groups;
users = lib.mapAttrsToList (username: opts: {
inherit (opts)
name
uid
group
description
home
password
hashedPassword
hashedPasswordFile
initialPassword
initialHashedPassword
;
isNormal = opts.isNormalUser;
shell = utils.toShellPath opts.shell;
}) (lib.filterAttrs (_: u: u.enable) config.users.users);
};
userbornConfigJson = pkgs.writeText "userborn.json" (builtins.toJSON userbornConfig);
immutableEtc = config.system.etc.overlay.enable && !config.system.etc.overlay.mutable;
# The filenames created by userborn.
passwordFiles = [
"group"
"passwd"
"shadow"
];
in
{
options.services.userborn = {
enable = lib.mkEnableOption "userborn";
package = lib.mkPackageOption pkgs "userborn" { };
passwordFilesLocation = lib.mkOption {
type = lib.types.str;
default = if immutableEtc then "/var/lib/nixos" else "/etc";
defaultText = lib.literalExpression ''if immutableEtc then "/var/lib/nixos" else "/etc"'';
description = ''
The location of the original password files.
If this is not `/etc`, the files are symlinked from this location to `/etc`.
The primary motivation for this is an immutable `/etc`, where we cannot
write the files directly to `/etc`.
However this an also serve other use cases, e.g. when `/etc` is on a `tmpfs`.
'';
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = !(config.systemd.sysusers.enable && cfg.enable);
message = "You cannot use systemd-sysusers and Userborn at the same time";
}
{
assertion = config.system.activationScripts.users == "";
message = "system.activationScripts.users has to be empty to use userborn";
}
{
assertion = immutableEtc -> (cfg.passwordFilesLocation != "/etc");
message = "When `system.etc.overlay.mutable = false`, `services.userborn.passwordFilesLocation` cannot be set to `/etc`";
}
];
system.activationScripts.users = lib.mkForce "";
system.activationScripts.hashes = lib.mkForce "";
systemd = {
# Create home directories, do not create /var/empty even if that's a user's
# home.
tmpfiles.settings.home-directories =
lib.mapAttrs'
(
username: opts:
lib.nameValuePair (toString opts.home) {
d = {
mode = opts.homeMode;
user = opts.name;
inherit (opts) group;
};
}
)
(
lib.filterAttrs (
_username: opts: opts.enable && opts.createHome && opts.home != "/var/empty"
) userCfg.users
);
services.userborn = {
wantedBy = [ "sysinit.target" ];
requiredBy = [ "sysinit-reactivation.target" ];
after = [
"systemd-remount-fs.service"
"systemd-tmpfiles-setup-dev-early.service"
];
before = [
"systemd-tmpfiles-setup-dev.service"
"sysinit.target"
"shutdown.target"
"sysinit-reactivation.target"
];
conflicts = [ "shutdown.target" ];
restartTriggers = [
userbornConfigJson
cfg.passwordFilesLocation
];
# This way we don't have to re-declare all the dependencies to other
# services again.
aliases = [ "systemd-sysusers.service" ];
unitConfig = {
Description = "Manage Users and Groups";
DefaultDependencies = false;
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutSec = "90s";
ExecStart = "${lib.getExe cfg.package} ${userbornConfigJson} ${cfg.passwordFilesLocation}";
ExecStartPre = lib.mkMerge [
(lib.mkIf (cfg.passwordFilesLocation != "/etc") [
"${pkgs.coreutils}/bin/mkdir -p ${cfg.passwordFilesLocation}"
])
# Make the source files writable before executing userborn.
(lib.mkIf (!userCfg.mutableUsers) (
lib.map (file: "-${pkgs.util-linux}/bin/umount ${cfg.passwordFilesLocation}/${file}") passwordFiles
))
];
# Make the source files read-only after userborn has finished.
ExecStartPost = lib.mkIf (!userCfg.mutableUsers) (
lib.map (
file:
"${pkgs.util-linux}/bin/mount --bind -o ro ${cfg.passwordFilesLocation}/${file} ${cfg.passwordFilesLocation}/${file}"
) passwordFiles
);
};
};
};
# Statically create the symlinks to passwordFilesLocation when they're not
# inside /etc because we will not be able to do it at runtime in case of an
# immutable /etc!
environment.etc = lib.mkIf (cfg.passwordFilesLocation != "/etc") (
lib.listToAttrs (
lib.map (
file:
lib.nameValuePair file {
source = "${cfg.passwordFilesLocation}/${file}";
mode = "direct-symlink";
}
) passwordFiles
)
);
};
meta.maintainers = with lib.maintainers; [ nikstur ];
}

View File

@@ -0,0 +1,44 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.zram-generator;
settingsFormat = pkgs.formats.ini { };
in
{
meta = {
maintainers = with lib.maintainers; [ nickcao ];
};
options.services.zram-generator = {
enable = lib.mkEnableOption "Systemd unit generator for zram devices";
package = lib.mkPackageOption pkgs "zram-generator" { };
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = settingsFormat.type;
};
default = { };
description = ''
Configuration for zram-generator,
see <https://github.com/systemd/zram-generator> for documentation.
'';
};
};
config = lib.mkIf cfg.enable {
system.requiredKernelConfig = with config.lib.kernelConfig; [
(isEnabled "ZRAM")
];
systemd.packages = [ cfg.package ];
systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap
environment.etc."systemd/zram-generator.conf".source =
settingsFormat.generate "zram-generator.conf" cfg.settings;
};
}