nixos/kanata: sync with version 1.0.6

- improve some descriptions
- device -> devices
- add options
  - extraArgs
  - port
- create a symlink in RUNTIME_DIRECTORY
- grant it read permission of /dev/uinput
- relax network-related restrictions when port is used
- change type of some hardening options to list to align with systemd
  - CapabilityBoundingSet
  - IPAddressDeny
  - SystemCallArchitectures
This commit is contained in:
Lin Jian 2022-08-01 15:35:34 +08:00 committed by pennae
parent a43993655a
commit b6d45f1448

View File

@ -1,4 +1,4 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, utils, ... }:
with lib; with lib;
@ -7,10 +7,20 @@ let
keyboard = { keyboard = {
options = { options = {
device = mkOption { devices = mkOption {
type = types.str; type = types.addCheck (types.listOf types.str)
example = "/dev/input/by-id/usb-0000_0000-event-kbd"; (devices: (length devices) > 0);
description = lib.mdDoc "Path to the keyboard device."; example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
# TODO replace note with tip, which has not been implemented yet in
# nixos/lib/make-options-doc/mergeJSON.py
description = mdDoc ''
Paths to keyboard devices.
::: {.note}
To avoid unnecessary triggers of the service unit, unplug devices in
the order of the list.
:::
'';
}; };
config = mkOption { config = mkOption {
type = types.lines; type = types.lines;
@ -33,18 +43,32 @@ let
;; tap within 100ms for capslk, hold more than 100ms for lctl ;; tap within 100ms for capslk, hold more than 100ms for lctl
cap (tap-hold 100 100 caps lctl)) cap (tap-hold 100 100 caps lctl))
''; '';
description = lib.mdDoc '' description = mdDoc ''
Configuration other than defcfg. Configuration other than `defcfg`. See [example config
See <https://github.com/jtroo/kanata> for more information. files](https://github.com/jtroo/kanata) for more information.
''; '';
}; };
extraDefCfg = mkOption { extraDefCfg = mkOption {
type = types.lines; type = types.lines;
default = ""; default = "";
example = "danger-enable-cmd yes"; example = "danger-enable-cmd yes";
description = lib.mdDoc '' description = mdDoc ''
Configuration of defcfg other than linux-dev. Configuration of `defcfg` other than `linux-dev`. See [example
See <https://github.com/jtroo/kanata> for more information. config files](https://github.com/jtroo/kanata) for more information.
'';
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
description = mdDoc "Extra command line arguments passed to kanata.";
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 6666;
description = mdDoc ''
Port to run the notification server on. `null` will not run the
server.
''; '';
}; };
}; };
@ -52,16 +76,18 @@ let
mkName = name: "kanata-${name}"; mkName = name: "kanata-${name}";
mkDevices = devices: concatStringsSep ":" devices;
mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" '' mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
(defcfg (defcfg
${keyboard.extraDefCfg} ${keyboard.extraDefCfg}
linux-dev ${keyboard.device}) linux-dev ${mkDevices keyboard.devices})
${keyboard.config} ${keyboard.config}
''; '';
mkService = name: keyboard: nameValuePair (mkName name) { mkService = name: keyboard: nameValuePair (mkName name) {
description = "kanata for ${keyboard.device}"; description = "kanata for ${mkDevices keyboard.devices}";
# Because path units are used to activate service units, which # Because path units are used to activate service units, which
# will start the old stopped services during "nixos-rebuild # will start the old stopped services during "nixos-rebuild
@ -72,10 +98,14 @@ let
serviceConfig = { serviceConfig = {
ExecStart = '' ExecStart = ''
${cfg.package}/bin/kanata \ ${cfg.package}/bin/kanata \
--cfg ${mkConfig name keyboard} --cfg ${mkConfig name keyboard} \
--symlink-path ''${RUNTIME_DIRECTORY}/${name} \
${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
${utils.escapeSystemdExecArgs keyboard.extraArgs}
''; '';
DynamicUser = true; DynamicUser = true;
RuntimeDirectory = mkName name;
SupplementaryGroups = with config.users.groups; [ SupplementaryGroups = with config.users.groups; [
input.name input.name
uinput.name uinput.name
@ -83,15 +113,16 @@ let
# hardening # hardening
DeviceAllow = [ DeviceAllow = [
"/dev/uinput w" "/dev/uinput rw"
"char-input r" "char-input r"
]; ];
CapabilityBoundingSet = ""; CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed"; DevicePolicy = "closed";
IPAddressDeny = "any"; IPAddressAllow = optional (keyboard.port != null) "localhost";
IPAddressDeny = [ "any" ];
LockPersonality = true; LockPersonality = true;
MemoryDenyWriteExecute = true; MemoryDenyWriteExecute = true;
PrivateNetwork = true; PrivateNetwork = keyboard.port == null;
PrivateUsers = true; PrivateUsers = true;
ProcSubset = "pid"; ProcSubset = "pid";
ProtectClock = true; ProtectClock = true;
@ -102,10 +133,11 @@ let
ProtectKernelModules = true; ProtectKernelModules = true;
ProtectKernelTunables = true; ProtectKernelTunables = true;
ProtectProc = "invisible"; ProtectProc = "invisible";
RestrictAddressFamilies = "none"; RestrictAddressFamilies =
if (keyboard.port == null) then "none" else [ "AF_INET" ];
RestrictNamespaces = true; RestrictNamespaces = true;
RestrictRealtime = true; RestrictRealtime = true;
SystemCallArchitectures = "native"; SystemCallArchitectures = [ "native" ];
SystemCallFilter = [ SystemCallFilter = [
"@system-service" "@system-service"
"~@privileged" "~@privileged"
@ -115,13 +147,32 @@ let
}; };
}; };
mkPath = name: keyboard: nameValuePair (mkName name) { mkPathName = i: name: "${mkName name}-${toString i}";
description = "kanata trigger for ${keyboard.device}";
wantedBy = [ "multi-user.target" ]; mkPath = name: n: i: device:
pathConfig = { nameValuePair (mkPathName i name) {
PathExists = keyboard.device; description =
"${toString (i+1)}/${toString n} kanata trigger for ${name}, watching ${device}";
wantedBy = optional (i == 0) "multi-user.target";
pathConfig = {
PathExists = device;
# (ab)use systemd.path to construct a trigger chain so that the
# service unit is only started when all paths exist
# however, manual of systemd.path says Unit's suffix is not ".path"
Unit =
if (i + 1) == n
then "${mkName name}.service"
else "${mkPathName (i + 1) name}.path";
};
unitConfig.StopPropagatedFrom = optional (i > 0) "${mkName name}.service";
}; };
};
mkPaths = name: keyboard:
let
n = length keyboard.devices;
in
imap0 (mkPath name n) keyboard.devices
;
in in
{ {
options.services.kanata = { options.services.kanata = {
@ -131,15 +182,19 @@ in
default = pkgs.kanata; default = pkgs.kanata;
defaultText = lib.literalExpression "pkgs.kanata"; defaultText = lib.literalExpression "pkgs.kanata";
example = lib.literalExpression "pkgs.kanata-with-cmd"; example = lib.literalExpression "pkgs.kanata-with-cmd";
description = lib.mdDoc '' description = mdDoc ''
kanata package to use. The kanata package to use.
If you enable danger-enable-cmd, pkgs.kanata-with-cmd should be used.
::: {.note}
If `danger-enable-cmd` is enabled in any of the keyboards, the
`kanata-with-cmd` package should be used.
:::
''; '';
}; };
keyboards = mkOption { keyboards = mkOption {
type = types.attrsOf (types.submodule keyboard); type = types.attrsOf (types.submodule keyboard);
default = { }; default = { };
description = lib.mdDoc "Keyboard configurations."; description = mdDoc "Keyboard configurations.";
}; };
}; };
@ -147,7 +202,11 @@ in
hardware.uinput.enable = true; hardware.uinput.enable = true;
systemd = { systemd = {
paths = mapAttrs' mkPath cfg.keyboards; paths = trivial.pipe cfg.keyboards [
(mapAttrsToList mkPaths)
concatLists
listToAttrs
];
services = mapAttrs' mkService cfg.keyboards; services = mapAttrs' mkService cfg.keyboards;
}; };
}; };