1{ config, lib, pkgs, ... }:
2with lib;
3let
4 cfg = config.services.keyd;
5 settingsFormat = pkgs.formats.ini { };
6in
7{
8 options = {
9 services.keyd = {
10 enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon");
11
12 ids = mkOption {
13 type = types.listOf types.string;
14 default = [ "*" ];
15 example = [ "*" "-0123:0456" ];
16 description = lib.mdDoc ''
17 Device identifiers, as shown by {manpage}`keyd(1)`.
18 '';
19 };
20
21 settings = mkOption {
22 type = settingsFormat.type;
23 default = { };
24 example = {
25 main = {
26 capslock = "overload(control, esc)";
27 rightalt = "layer(rightalt)";
28 };
29
30 rightalt = {
31 j = "down";
32 k = "up";
33 h = "left";
34 l = "right";
35 };
36 };
37 description = lib.mdDoc ''
38 Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`.
39 See <https://github.com/rvaiya/keyd> how to configure.
40 '';
41 };
42 };
43 };
44
45 config = mkIf cfg.enable {
46 environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf"
47 {
48 ids = ''
49 [ids]
50 ${concatStringsSep "\n" cfg.ids}
51 '';
52 passAsFile = [ "ids" ];
53 } ''
54 cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out
55 '';
56
57 hardware.uinput.enable = lib.mkDefault true;
58
59 systemd.services.keyd = {
60 description = "Keyd remapping daemon";
61 documentation = [ "man:keyd(1)" ];
62
63 wantedBy = [ "multi-user.target" ];
64
65 restartTriggers = [
66 config.environment.etc."keyd/default.conf".source
67 ];
68
69 # this is configurable in 2.4.2, later versions seem to remove this option.
70 # post-2.4.2 may need to set makeFlags in the derivation:
71 #
72 # makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ];
73 environment.KEYD_SOCKET = "/run/keyd/keyd.sock";
74
75 serviceConfig = {
76 ExecStart = "${pkgs.keyd}/bin/keyd";
77 Restart = "always";
78
79 # TODO investigate why it doesn't work propeprly with DynamicUser
80 # See issue: https://github.com/NixOS/nixpkgs/issues/226346
81 # DynamicUser = true;
82 SupplementaryGroups = [
83 config.users.groups.input.name
84 config.users.groups.uinput.name
85 ];
86
87 RuntimeDirectory = "keyd";
88
89 # Hardening
90 CapabilityBoundingSet = "";
91 DeviceAllow = [
92 "char-input rw"
93 "/dev/uinput rw"
94 ];
95 ProtectClock = true;
96 PrivateNetwork = true;
97 ProtectHome = true;
98 ProtectHostname = true;
99 PrivateUsers = true;
100 PrivateMounts = true;
101 PrivateTmp = true;
102 RestrictNamespaces = true;
103 ProtectKernelLogs = true;
104 ProtectKernelModules = true;
105 ProtectKernelTunables = true;
106 ProtectControlGroups = true;
107 MemoryDenyWriteExecute = true;
108 RestrictRealtime = true;
109 LockPersonality = true;
110 ProtectProc = "invisible";
111 SystemCallFilter = [
112 "@system-service"
113 "~@privileged"
114 "~@resources"
115 ];
116 RestrictAddressFamilies = [ "AF_UNIX" ];
117 RestrictSUIDSGID = true;
118 IPAddressDeny = [ "any" ];
119 NoNewPrivileges = true;
120 ProtectSystem = "strict";
121 ProcSubset = "pid";
122 UMask = "0077";
123 };
124 };
125 };
126}