-
-
Notifications
You must be signed in to change notification settings - Fork 14.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The keyd package already exists, but without a systemd service. Keyd requires write access to /var/run to create its socket. Currently the directory it uses can be changed with an environment variable, but the keyd repo state suggests that this may turn into a compile-time option. with that set, and some supplementary groups added, we can run the service under DynamicUser. Co-authored-by: pennae <[email protected]>
- Loading branch information
Showing
6 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
{ config, lib, pkgs, ... }: | ||
with lib; | ||
let | ||
cfg = config.services.keyd; | ||
settingsFormat = pkgs.formats.ini { }; | ||
in | ||
{ | ||
options = { | ||
services.keyd = { | ||
enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon"); | ||
|
||
ids = mkOption { | ||
type = types.listOf types.string; | ||
default = [ "*" ]; | ||
example = [ "*" "-0123:0456" ]; | ||
description = lib.mdDoc '' | ||
Device identifiers, as shown by {manpage}`keyd(1)`. | ||
''; | ||
}; | ||
|
||
settings = mkOption { | ||
type = settingsFormat.type; | ||
default = { }; | ||
example = { | ||
main = { | ||
capslock = "overload(control, esc)"; | ||
rightalt = "layer(rightalt)"; | ||
}; | ||
|
||
rightalt = { | ||
j = "down"; | ||
k = "up"; | ||
h = "left"; | ||
l = "right"; | ||
}; | ||
}; | ||
description = lib.mdDoc '' | ||
Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`. | ||
See <https://github.com/rvaiya/keyd> how to configure. | ||
''; | ||
}; | ||
}; | ||
}; | ||
|
||
config = mkIf cfg.enable { | ||
environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf" | ||
{ | ||
ids = '' | ||
[ids] | ||
${concatStringsSep "\n" cfg.ids} | ||
''; | ||
passAsFile = [ "ids" ]; | ||
} '' | ||
cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out | ||
''; | ||
|
||
hardware.uinput.enable = lib.mkDefault true; | ||
|
||
systemd.services.keyd = { | ||
description = "Keyd remapping daemon"; | ||
documentation = [ "man:keyd(1)" ]; | ||
|
||
wantedBy = [ "multi-user.target" ]; | ||
|
||
restartTriggers = [ | ||
config.environment.etc."keyd/default.conf".source | ||
]; | ||
|
||
# this is configurable in 2.4.2, later versions seem to remove this option. | ||
# post-2.4.2 may need to set makeFlags in the derivation: | ||
# | ||
# makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ]; | ||
environment.KEYD_SOCKET = "/run/keyd/keyd.sock"; | ||
|
||
serviceConfig = { | ||
ExecStart = "${pkgs.keyd}/bin/keyd"; | ||
Restart = "always"; | ||
|
||
DynamicUser = true; | ||
SupplementaryGroups = [ | ||
config.users.groups.input.name | ||
config.users.groups.uinput.name | ||
]; | ||
|
||
RuntimeDirectory = "keyd"; | ||
|
||
# Hardening | ||
CapabilityBoundingSet = ""; | ||
DeviceAllow = [ | ||
"char-input rw" | ||
"/dev/uinput rw" | ||
]; | ||
ProtectClock = true; | ||
PrivateNetwork = true; | ||
ProtectHome = true; | ||
ProtectHostname = true; | ||
PrivateUsers = true; | ||
PrivateMounts = true; | ||
RestrictNamespaces = true; | ||
ProtectKernelLogs = true; | ||
ProtectKernelModules = true; | ||
ProtectKernelTunables = true; | ||
ProtectControlGroups = true; | ||
MemoryDenyWriteExecute = true; | ||
RestrictRealtime = true; | ||
LockPersonality = true; | ||
ProtectProc = "noaccess"; | ||
UMask = "0077"; | ||
}; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# The test template is taken from the `./keymap.nix` | ||
{ system ? builtins.currentSystem | ||
, config ? { } | ||
, pkgs ? import ../.. { inherit system config; } | ||
}: | ||
|
||
with import ../lib/testing-python.nix { inherit system pkgs; }; | ||
|
||
let | ||
readyFile = "/tmp/readerReady"; | ||
resultFile = "/tmp/readerResult"; | ||
|
||
testReader = pkgs.writeScript "test-input-reader" '' | ||
rm -f ${resultFile} ${resultFile}.tmp | ||
logger "testReader: START: Waiting for $1 characters, expecting '$2'." | ||
touch ${readyFile} | ||
read -r -N $1 chars | ||
rm -f ${readyFile} | ||
if [ "$chars" == "$2" ]; then | ||
logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp | ||
else | ||
logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp | ||
fi | ||
# rename after the file is written to prevent a race condition | ||
mv ${resultFile}.tmp ${resultFile} | ||
''; | ||
|
||
|
||
mkKeyboardTest = name: { settings, test }: with pkgs.lib; makeTest { | ||
inherit name; | ||
|
||
nodes.machine = { | ||
services.keyd = { | ||
enable = true; | ||
inherit settings; | ||
}; | ||
}; | ||
|
||
testScript = '' | ||
import shlex | ||
machine.wait_for_unit("keyd.service") | ||
def run_test_case(cmd, test_case_name, inputs, expected): | ||
with subtest(test_case_name): | ||
assert len(inputs) == len(expected) | ||
machine.execute("rm -f ${readyFile} ${resultFile}") | ||
# set up process that expects all the keys to be entered | ||
machine.succeed( | ||
"{} {} {} {} >&2 &".format( | ||
cmd, | ||
"${testReader}", | ||
len(inputs), | ||
shlex.quote("".join(expected)), | ||
) | ||
) | ||
# wait for reader to be ready | ||
machine.wait_for_file("${readyFile}") | ||
# send all keys | ||
for key in inputs: | ||
machine.send_key(key) | ||
# wait for result and check | ||
machine.wait_for_file("${resultFile}") | ||
machine.succeed("grep -q 'PASS:' ${resultFile}") | ||
test = ${builtins.toJSON test} | ||
run_test_case("openvt -sw --", "${name}", test["press"], test["expect"]) | ||
''; | ||
}; | ||
|
||
in | ||
pkgs.lib.mapAttrs mkKeyboardTest { | ||
swap-ab_and_ctrl-as-shift = { | ||
test.press = [ "a" "ctrl-b" "c" ]; | ||
test.expect = [ "b" "A" "c" ]; | ||
|
||
settings.main = { | ||
"a" = "b"; | ||
"b" = "a"; | ||
"control" = "oneshot(shift)"; | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters