unl0kr: port to an s6 service
this has some drawbacks in its current form and will be tidied it writes the password also to the consold. it requires 'sudo'.
This commit is contained in:
@@ -16,7 +16,9 @@
|
||||
'';
|
||||
});
|
||||
|
||||
persist.byStore.private = [
|
||||
# N.B.: we can't persist anything to `private` storage at this point,
|
||||
# because mounting the private storage generally relies on having a service manager running.
|
||||
persist.byStore.ephemeral = [
|
||||
".local/share/s6/logs"
|
||||
];
|
||||
|
||||
|
@@ -3,44 +3,6 @@ let
|
||||
cfg = config.sane.programs.unl0kr;
|
||||
|
||||
tty = "tty${builtins.toString cfg.config.vt}";
|
||||
redirect-tty = pkgs.static-nix-shell.mkPython3 {
|
||||
pname = "redirect-tty";
|
||||
srcRoot = ./.;
|
||||
};
|
||||
launcher = pkgs.writeShellApplication {
|
||||
name = "unl0kr-login";
|
||||
text = ''
|
||||
extraPath=/run/current-system/sw/bin:/bin:${lib.makeBinPath [ cfg.package config.sane.programs.shadow.package redirect-tty ]}
|
||||
locate() {
|
||||
PATH=$PATH:$extraPath command -v "$1"
|
||||
}
|
||||
|
||||
# give some time for the framebuffer device to appear;
|
||||
# unl0kr depends on it but doesn't know to wait for it.
|
||||
for _ in $(seq 25); do
|
||||
if [ -e /dev/fb0 ]; then
|
||||
break
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
# TODO: make this more robust to failure.
|
||||
# - if `unl0kr` fails, then the second `redirect-tty` sends a newline to `login`, causing it to exit and the service fails.
|
||||
# - if `redirect-tty` fails, then... the service is left hanging.
|
||||
# possible alternatives:
|
||||
# - wait on `unl0kr` to complete _before_ starting `login`, and re-introduce a timeout to `login`
|
||||
# i.e. `pw=$(unl0kr); (sleep 1 && echo "$pw" | redirect-tty "/dev/(tty)") &; login -p <user>`
|
||||
# but modified to not leak pword to CLI
|
||||
# - implement some sort of watchdog (e.g. detect spawned children?)
|
||||
# - set a timeout at the outer scope (which gets canceled upon successful login)
|
||||
PATH=$PATH:$extraPath sh -c 'redirect-tty "/dev/${tty}" unl0kr ; sleep 2 ; redirect-tty "/dev/${tty}" echo ""' &
|
||||
|
||||
# N.B.: invoke `login` by full path instead of modifying `PATH`,
|
||||
# because we don't want the user session to inherit the PATH of this script!
|
||||
_login="$(locate login)"
|
||||
"$_login" -p ${cfg.config.user}
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs.unl0kr = {
|
||||
@@ -60,14 +22,6 @@ in
|
||||
type = types.int;
|
||||
default = 1;
|
||||
};
|
||||
options.user = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
which user to login by default.
|
||||
unl0kr is just a virtual keyboard for entering a password: one has to choose the user to login before launching it.
|
||||
on a typical single-user install, leave this unset and the user will be chosen based on who this package is installed for.
|
||||
'';
|
||||
};
|
||||
options.delay = mkOption {
|
||||
type = types.int;
|
||||
default = 3;
|
||||
@@ -76,28 +30,10 @@ in
|
||||
this is a safety mechanism, to allow users an exit in case DE is broken.
|
||||
'';
|
||||
};
|
||||
options.launcher = mkOption {
|
||||
type = types.package;
|
||||
default = launcher;
|
||||
description = ''
|
||||
script to tie `unl0kr` and `login` together.
|
||||
exposed for debugging.
|
||||
'';
|
||||
};
|
||||
|
||||
config = lib.mkMerge (lib.mapAttrsToList
|
||||
(userName: en: lib.optionalAttrs en {
|
||||
user = lib.mkDefault userName;
|
||||
})
|
||||
cfg.enableFor.user
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
suggestedPrograms = [
|
||||
"shadow" #< for login
|
||||
];
|
||||
|
||||
# TODO: lift this into toplevel s6 stuff
|
||||
fs.".profile".symlink.text = ''
|
||||
unl0krCheck() {
|
||||
# if already running a desktop environment, or if running from ssh, then `tty` will show /dev/pts/NN.
|
||||
@@ -125,38 +61,23 @@ in
|
||||
# it still *works*, but wow, kinda weird and concerning
|
||||
"/dev/tty0"
|
||||
];
|
||||
};
|
||||
|
||||
# unl0kr is run as root, and especially with sandboxing, needs to be installed for root if expected to work.
|
||||
sane.programs.unl0kr.enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
||||
|
||||
systemd = lib.mkIf cfg.enabled {
|
||||
# prevent nixos-rebuild from killing us after a redeploy
|
||||
services."autovt@${tty}".enable = false;
|
||||
services.unl0kr = {
|
||||
serviceConfig.ExecStart = "${cfg.config.launcher}/bin/unl0kr-login";
|
||||
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.Restart = "always";
|
||||
serviceConfig.RestartSec = "5";
|
||||
# XXX: unsure which of these are necessary nor what they do.
|
||||
# copied from greetd and agetty services.
|
||||
# serviceConfig.{TTYPath,StandardInput,StandardOutput} connects the corresponding tty to the service's stdin/stdout.
|
||||
serviceConfig.TTYPath = "/dev/${tty}";
|
||||
serviceConfig.TTYReset = "yes";
|
||||
serviceConfig.StandardInput = "tty";
|
||||
serviceConfig.StandardOutput = "tty";
|
||||
|
||||
# copied from greetd; not sure how `after` and `conflict` being identical makes any sense.
|
||||
after = [ "getty@${tty}.service" ];
|
||||
conflicts = [ "getty@${tty}.service" ];
|
||||
wantedBy = [ "graphical.target" ];
|
||||
|
||||
# don't kill session on nixos re-deploy
|
||||
restartIfChanged = false;
|
||||
description = "unl0kr framebuffer password entry/filesystem unlocker";
|
||||
partOf = [ "private-storage" ];
|
||||
# TODO: lift the gocryptfs stuff into a separate service
|
||||
command = pkgs.writeShellScript "unl0kr-start" ''
|
||||
# if ! test -d $XDG_RUNTIME_DIR/unl0kr; then
|
||||
# mkdir $XDG_RUNTIME_DIR/unl0kr
|
||||
# fi
|
||||
# unl0kr > $XDG_RUNTIME_DIR/unl0kr/passwd
|
||||
if ! test -f /mnt/persist/private/init; then
|
||||
unl0kr | sudo gocryptfs -fg /nix/persist/private /mnt/persist/private -o allow_other
|
||||
fi
|
||||
#^ otherwise, if already unlocked, exit true
|
||||
'';
|
||||
readiness.waitExists = [ "/mnt/persist/private/init" ];
|
||||
};
|
||||
|
||||
defaultUnit = "graphical.target";
|
||||
};
|
||||
|
||||
security.loginDefs.settings = lib.mkIf cfg.enabled {
|
||||
|
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p python3
|
||||
# vim: set filetype=python :
|
||||
|
||||
"""
|
||||
launch some program, and redirect its output to appear as if it
|
||||
were *input* by the user, on some TTY.
|
||||
this allows piping into programs which expect to be running directly on a TTY (like `login`, or `sway`).
|
||||
|
||||
based on: <https://unix.stackexchange.com/a/345572>
|
||||
"""
|
||||
import fcntl
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
|
||||
def write_to(text: bytes, dest_path: str) -> None:
|
||||
with open(dest_path, "w") as f:
|
||||
for byte in text:
|
||||
fcntl.ioctl(f.fileno(), termios.TIOCSTI, bytes([byte]))
|
||||
|
||||
def main(argv: list[str]):
|
||||
dest_path = argv[1]
|
||||
cmd = argv[2:]
|
||||
|
||||
cmd_output = subprocess.check_output(cmd)
|
||||
write_to(cmd_output, dest_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
@@ -117,4 +117,5 @@
|
||||
# '';
|
||||
|
||||
sane.users.colin.default = true;
|
||||
services.getty.autologinUser = "colin";
|
||||
}
|
||||
|
@@ -240,7 +240,9 @@ let
|
||||
# only do this for the services which are *defined* by this program though (i.e. `scvCfg ? description`)
|
||||
# so as to avoid idioms like when sway adds `graphical-session.partOf = default`
|
||||
depends = svcCfg.depends
|
||||
++ lib.optionals (svcName != "dbus" && builtins.elem "user" config.sandbox.whitelistDbus && cfg.dbus.enabled) [
|
||||
++ lib.optionals (((config.persist.byStore or {}).private or []) != []) [
|
||||
"private-storage"
|
||||
] ++ lib.optionals (svcName != "dbus" && builtins.elem "user" config.sandbox.whitelistDbus && cfg.dbus.enabled) [
|
||||
"dbus"
|
||||
] ++ lib.optionals ((!builtins.elem "wayland" svcCfg.partOf) && config.sandbox.whitelistWayland) [
|
||||
"wayland"
|
||||
|
Reference in New Issue
Block a user