Files
nix-files/hosts/common/programs/unl0kr/default.nix
2024-10-05 12:59:30 +00:00

87 lines
3.5 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.unl0kr;
in
{
sane.programs.unl0kr = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.autostart = mkOption {
type = types.bool;
default = true;
description = ''
whether to launch unl0kr at boot.
unl0kr takes the role of a greeter, presenting a virtual keyboard to the framebuffer
and allowing password auth via either keyboard, mouse, or touch.
'';
};
};
};
# pkgs.unl0kr works fine, but the newer version packaged as part of buffybox is way more performant
packageUnwrapped = pkgs.buffybox;
# N.B.: this sandboxing applies to `unl0kr` itself -- the on-screen-keyboard;
# NOT to the wrapper which invokes `login`.
sandbox.whitelistDri = true;
sandbox.extraPaths = [
"/dev/fb0"
"/dev/input"
"/run/udev"
"/sys/class/input"
"/sys/devices"
#v without /dev/tty0, it fails to fully take over the tty (even though it's ostensibly running on ${cfg.config.vt})
# and your password is dumped to the framebuffer.
# it still *works*, but wow, kinda weird and concerning
"/dev/tty0"
];
services.unl0kr = {
description = "unl0kr framebuffer password entry/filesystem unlocker";
partOf = lib.mkIf cfg.config.autostart [ "private-storage" ];
command = pkgs.writeShellScript "unl0kr-start" ''
while ! test -f /mnt/persist/private/init; do
if test -f /run/gocryptfs/private.key; then
# wait for the mount to appear or for the key to disappear (because it was incorrect).
# i'm not sure this is actually correct, or just behaves as `sleep 4` in practice
${lib.getExe' pkgs.inotify-tools "inotifywait"} --timeout 4 --event create --event delete /mnt/persist/private /run/gocryptfs
else
echo "starting unl0kr"
if [ -n "$XDG_VTNR" ]; then
# switch back to the tty our session is running on (in case the user tabbed away after logging in),
# as only that TTY is sure to have echo disabled.
# this is racy, but when we race it's obvious from the UI that your password is being echo'd
${lib.getExe' pkgs.kbd "chvt"} "$XDG_VTNR"
fi
# don't start unl0kr if there's no framebuffer yet, else it'll just not render anything, indefinitely.
test -e /dev/fb0 && \
unl0kr > /run/gocryptfs/private.key.incoming &&
cp /run/gocryptfs/private.key.incoming /run/gocryptfs/private.key
echo "unl0kr exited"
fi
sleep 1
done
while true; do
sleep infinity
done
'';
};
};
security.loginDefs.settings = lib.mkIf cfg.enabled {
# see: `man login.defs`
# disable timeout for `login` program.
# LOGIN_TIMEOUT=0 lets me pipe input into `login` and not worry about the pipe randomly dying.
LOGIN_TIMEOUT = 0;
# LOGIN_RETRIES=1 ensures that if the password is wrong, then login exits and the whole service restarts so unl0kr re-appears.
# docs mention `UNIX_MAX_RETRIES` setting within pam_unix (hardcoded to 3): seems that's an upper-limit to this value, but no lower limit.
LOGIN_RETRIES = 1;
FAIL_DELAY = 1; #< delay this long after failed loging before allowing retry
};
environment.etc."unl0kr.conf" = lib.mkIf cfg.enabled {
source = ./unl0kr.conf;
};
}