Files
roowho2/nix/module.nix
T
oysteikt ba0e8f214d
Build and test / check (push) Successful in 1m7s
Build and test / build (push) Successful in 1m51s
Build and test / test (push) Successful in 2m2s
Build and test / docs (push) Successful in 4m7s
rwhod: allow configuring packet send interval
2026-07-20 21:52:34 +09:00

376 lines
13 KiB
Nix

{ config, pkgs, lib, utils, ... }:
let
cfg = config.services.roowho2;
format = pkgs.formats.toml { };
in {
options.services.roowho2 = {
enable = lib.mkEnableOption "the roowho2 daemon, replacement for multiple linux netkit services";
package = lib.mkPackageOption pkgs "roowho2" { };
settings = lib.mkOption {
type = lib.types.submodule {
freeformType = format.type;
options = {
log_level = lib.mkOption {
type = lib.types.enum [ "info" "debug" "trace" ];
default = "info";
description = "Log level for the roowho2 daemon.";
};
rwhod = {
enable = lib.mkEnableOption "the rwhod service" // {
default = true;
};
ignore_list_path = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = lib.literalExpression ''
pkgs.writeText "rwhod-ignore-list" '''
# Ignore the following users from rwhod
user:user1
user:user2
uid:1001
'''
'';
description = ''
List of users/UIDs to ignore from the local machine both when
broadcasting packets and when responding to local rwho/ruptime requests.
If set to `null`, rwhod will not ignore any users, and will serve all users on the system.
::: {.note}
This option is also available as `services.roowho2.settings.rwhod.ignoreUsers`,
which is a more convenient way to specify the ignore list from within a NixOS module.
:::
'';
};
interfaces = lib.mkOption {
type = with lib.types; nullOr (listOf str);
default = null;
example = [ "eth0" "wlan0" ];
description = ''
List of network interfaces to broadcast rwhod packets on.
If set to `null`, rwhod will try to autodetect all reasonable interfaces
to broadcast on, and use all of them.
If set to an empty list, rwhod will not broadcast on any interfaces,
and will only listen for incoming packets.
'';
};
send_interval_seconds = lib.mkOption {
type = lib.types.ints.unsigned;
default = 60;
description = ''
Interval between rwhod status packet broadcasts.
'';
};
ignoreUsers = lib.mkOption {
type = with lib.types; listOf (either str ints.unsigned);
default = [ ];
example = [ "user1" 1002 ];
description = ''
List of users/UIDs to ignore from the local machine both when
broadcasting packets and when responding to local rwho/ruptime requests.
If left empty, rwhod will not ignore any users, and will serve all users on the system.
'';
};
socketConfig = lib.mkOption {
type = utils.systemdUtils.unitOptions.unitOption;
default = { };
description = ''
Config for the systemd socket's `[Socket]` section.
See {manpage}`systemd.socket(5)` for details.
'';
example = {
IPAddressDeny = "any";
IPAddressAllow = [
"127.0.0.1"
];
};
};
};
fingerd = {
enable = lib.mkEnableOption "the fingerd service" // {
default = true;
};
ignore_list_path = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = lib.literalExpression ''
pkgs.writeText "fingerd-ignore-list" '''
# Ignore the following users from rwhod
user:user1
user:user2
uid:1001
'''
'';
description = ''
List of users/UIDs to ignore from the local machine when responding to
local or remote finger requests.
If set to `null`, fingerd will not ignore any users, and will serve all users on the system.
::: {.note}
This option is also available as `services.roowho2.settings.fingerd.ignoreUsers`,
which is a more convenient way to specify the ignore list from within a NixOS module.
:::
'';
};
ignoreUsers = lib.mkOption {
type = with lib.types; listOf (either str ints.unsigned);
default = [ ];
example = [ "user1" 1002 ];
description = ''
List of users/UIDs to ignore from the local machine when responding to
local or remote finger requests.
If left empty, fingerd will not ignore any users, and will serve all users on the system.
'';
};
# enable_remote_finger = lib.mkOption {
# type = lib.types.bool;
# default = false;
# example = true;
# description = ''
# Whether to allow remote finger requests.
# If set to false, only local finger requests will be served.
# '';
# };
# socketConfig = lib.mkOption {
# type = utils.systemdUtils.unitOptions.unitOption;
# default = { };
# description = ''
# Config for the systemd socket's `[Socket]` section.
# See {manpage}`systemd.socket(5)` for details.
# ::: {.note}
# This is only relevant if `enableRemoteFinger` is `true`.
# :::
# '';
# example = {
# IPAddressDeny = "any";
# IPAddressAllow = [
# "127.0.0.1"
# ];
# };
# };
};
};
};
default = { };
description = "Configuration settings for Roowho2.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.settings.rwhod.enable || cfg.settings.fingerd.enable;
message = "At least one of `services.roowho2.settings.rwhod.enable` or `services.roowho2.settings.fingerd.enable` must be true.";
}
{
assertion =
cfg.settings.rwhod.ignoreUsers == [ ]
|| cfg.settings.rwhod.ignore_list_path == "/etc/roowho2/rwhod-ignore-list";
message = "Cannot set both `services.roowho2.settings.rwhod.ignoreUsers` and `services.roowho2.settings.rwhod.ignore_list_path`.";
}
{
assertion =
cfg.settings.fingerd.ignoreUsers == [ ]
|| cfg.settings.fingerd.ignore_list_path == "/etc/roowho2/fingerd-ignore-list";
message = "Cannot set both `services.roowho2.settings.fingerd.ignoreUsers` and `services.roowho2.settings.fingerd.ignore_list_path`.";
}
];
services.roowho2.settings = {
rwhod.ignore_list_path =
lib.mkIf
(cfg.settings.rwhod.enable && cfg.settings.rwhod.ignoreUsers != [ ])
"/etc/roowho2/rwhod-ignore-list";
fingerd.ignore_list_path =
lib.mkIf
(cfg.settings.fingerd.enable && cfg.settings.fingerd.ignoreUsers != [ ])
"/etc/roowho2/fingerd-ignore-list";
};
environment.etc = let
generateIgnoreList = let
nameToUidMapping = lib.pipe config.users.users [
(lib.filterAttrs (_: user: user.uid != null))
(lib.mapAttrsToList (name: user: { name = name; value = user.uid; }))
lib.listToAttrs
];
uidToNameMapping = lib.pipe config.users.users [
(lib.filterAttrs (_: user: user.uid != null))
(lib.mapAttrsToList (name: user: { name = toString user.uid; value = name; }))
lib.listToAttrs
];
in users: lib.pipe users [
# Prefer UIDs for users we know the UID
(map (user: if builtins.isString user
then (nameToUidMapping.${user} or user)
else user))
# Then render back to strings
(map (user:
if builtins.isString user
then "user:${user}"
else "uid:${toString user} # ${uidToNameMapping.${toString user} or "unknown"}"))
(lib.concatStringsSep "\n")
];
in {
"roowho2/rwhod-ignore-list" = lib.mkIf (cfg.settings.rwhod.enable && cfg.settings.rwhod.ignoreUsers != [ ]) {
text = generateIgnoreList cfg.settings.rwhod.ignoreUsers;
};
"roowho2/fingerd-ignore-list" = lib.mkIf (cfg.settings.fingerd.enable && cfg.settings.fingerd.ignoreUsers != [ ]) {
text = generateIgnoreList cfg.settings.fingerd.ignoreUsers;
};
};
systemd.sockets.roowho2-client = {
wantedBy = [ "sockets.target" ];
description = "Roowho2 Client Communication Socket";
listenStreams = [ "/run/roowho2/roowho2.varlink" ];
socketConfig = {
Service = "roowho2.service";
FileDescriptorName = "client_socket";
};
};
systemd.sockets.roowho2-rwhod = lib.mkIf cfg.settings.rwhod.enable {
wantedBy = [ "sockets.target" ];
description = "Roowho2 Rwhod Socket";
listenDatagrams = [ "0.0.0.0:513" ];
socketConfig = {
Service = "roowho2.service";
FileDescriptorName = "rwhod_socket";
Broadcast = true;
} // cfg.settings.rwhod.socketConfig;
};
# systemd.sockets.roowho2-fingerd = lib.mkIf cfg.settings.fingerd.enable && cfg.settings.fingerd.enableRemoteFinger {
# wantedBy = [ "sockets.target" ];
# description = "Roowho2 Fingerd Socket";
# listenStreams = [ "0.0.0.0:79" ];
# socketConfig = {
# Service = "roowho2.service";
# FileDescriptorName = "fingerd_socket";
# } // cfg.settings.fingerd.socketConfig;
# };
systemd.services.roowho2 = {
serviceConfig = {
Type = "notify";
ExecStart = let
configFile = lib.pipe cfg.settings [
(lib.filterAttrsRecursive (n: v: v != null))
(settings: settings // {
rwhod = lib.removeAttrs settings.rwhod [ "socketConfig" "ignoreUsers" ];
fingerd = lib.removeAttrs settings.fingerd [ "socketConfig" "ignoreUsers" ];
})
(format.generate "roowho2-config.toml")
];
in "${lib.getExe' cfg.package "roowhod"} --config ${configFile}";
Restart = "on-failure";
DynamicUser = true;
ConfigurationDirectory = [ "roowho2" ];
# NOTE: roowho2 might at some point need to read from home directories
# to get user settings, so let's keep these disabled for now.
# PrivateUsers = true;
# ProtectHome = true;
# NOTE: We need this capability to be able to read inside the home directories of users without
# them needing to open their homedirs to the rest of the system.
AmbientCapabilities = [ "CAP_DAC_READ_SEARCH" ];
CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
DeviceAllow = "";
DevicePolicy = "closed";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
# NOTE: all ipc traffic is served through the socket activation fds or provided by systemd
PrivateIPC = true;
PrivateMounts = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = "strict";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SocketBindDeny = "any";
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
"~@resources"
];
RuntimeDirectory = "roowho2/root-mnt";
RuntimeDirectoryMode = "0700";
RootDirectory = "/run/roowho2/root-mnt";
BindReadOnlyPaths = lib.filter (x: x != null) ([
builtins.storeDir
"/etc"
# NOTE: need logind socket for utmp entries
"/run/systemd"
"/home"
# NOTE: finger might need access to mail directories
"-/var/spool"
"-/var/mail"
# NOTE: finger needs access to stat tty devices
"/dev"
] ++ lib.optionals cfg.settings.rwhod.enable [
cfg.settings.rwhod.ignore_list_path
] ++ lib.optionals cfg.settings.fingerd.enable [
cfg.settings.fingerd.ignore_list_path
]);
UMask = "0077";
};
};
networking.firewall.allowedUDPPorts = lib.mkIf cfg.settings.rwhod.enable [ 513 ];
environment.systemPackages = [ cfg.package ];
};
}