Manual
snenslock
is a simple lockscreen written in Rust. It switches to an unused TTY and prohibits switching
back to another TTY until an authentication challenge was passed, e.g. typing in a password.
This approach is strongly inspired by physlock.
Below is a screenshot of snenslock
in action using the tui
-greeter:
The screenshots are generated during the integration tests running on Hydra CI.
License
snenslock
is released under the terms of GNU GPLv2.0.
This was also done to be compatible with its primary inspiration physlock(1)
.
How it works
The way how snenslock
locks your screen can be best explained with a flowchart:
Additional notes
- The currently active user session is determined via logind's DBus API,
namely
org.freedesktop.login1(5)
. - The following safe-guards are implemented:
- The lockscreen is rendered to an unused TTY. To prohibit switching
back to another TTY, the
ioctl(2)
calledVT_LOCKSWITCH
is used. - The log-level for the console is set to
1
to make sure that no kernel messages are written to the TTY. - The
sysrq
shortcuts to kill a process and to alter the log-level are disabled.
- The lockscreen is rendered to an unused TTY. To prohibit switching
back to another TTY, the
- By default, the machine asks for the
root
password if the password of the locking user was wrongly entered five times. This can be changed using the CLI flag-t
(orprograms.snenslock.rootAtAuthTimes
). - Optionally it's possible to initiate a shutdown if the authentication
failed N times. This can be configured using the CLI flag
-p N
(orprograms.snenslock.poweroffAfter
) - The purple box is considered a "safe state": as soon as that is reached, the machine can be considered to be locked. If the greeter crashes, it will be restarted. However, please make sure that this state is actually reached, before that your machine is not locked!
- There are two greeters: a
tui
greeter (see screenshot above) and aminimal
greeter. Since greeters can have arbitrarily complex GUI code (e.g. future greeters being fully graphic), a fallback is implemented to use a very minimal greeter. If the selected greeter crashes >3 times, this one is activated rather than restarting the previously used greeter. - The code of the main process (i.e. the code that does locking, PAM check etc)
is designed to not crash. If the greeter (i.e. the process interacting with the user
to get a password) crashes, it will be restarted.
However, software does crash. That means if
snenslock
itself crashes, the machine remains locked, but cannot be unlocked withsnenslock
. In that case there are two ways to unlock:- Force a reboot with sysrq.
- Login via SSH and unlock the TTYs manually.
Important Assumptions
There are a few assumptions that must be met to make sure this software functions properly:
- The software is installed via the NixOS module in the repository's flake.
- As soon as the lock is active, the computer can be considered secured.
- The systemd unit is considered
active
after this has happened. Before it's only in stateactivating
. It must be ensured that the lock can be created.
- The systemd unit is considered
- There must be a free, unused TTY. Otherwise creating the lock fails.
- The software is used to either lock TTYs or Wayland sessions (only tested with
sway
currently). E.g. X11 support is explicitly out of scope. It may work, but it was never tested. You'll probably want to use XScreenSaver then.
Installation
With flakes
{
inputs.snenslock.url = "git+https://git.mbosch.me/ma27/snenslock?shallow=true";
inputs.snenslock.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self, nixpkgs, snenslock, ... }: {
nixosConfigurations.yourmachine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
snenslock.nixosModules.snenslock
{
programs.snenslock = {
enable = true;
greeter = "tui";
};
}
];
};
};
}
Additional config options can be found in flake.nix
or in the options' reference
Without flakes
Note: it's highly recommended to specify a revision here and not to
always fetch latest master
.
{
imports = [
(import (builtins.fetchGit {
url = "https://git.mbosch.me/ma27/snenslock";
shallow = true;
ref = "master";
})).nixosModules.snenslock
];
programs.snenslock.enable = true;
}
Binary cache
The flake is regularly built on my personal Hydra, the binary cache can be used to substitute
prebuilt artifacts of snenslock
. All output paths are signed with the following key:
hydra.ist.nicht-so.sexy-1:E+AwZnzYPdycs1IkHrlG0eJeBleAW/ukX10lcjTc2RQ=
Usage
By default, a daemon will run listening to the lock
signal from systemd-logind
.
In other words, the you can start snenslock by running loginctl lock-session
.
Work around sway problems
Display settings
Note: I haven't observed this in a while. This section may be outdated.
It is a known problem that sway(1)
doesn't like TTY switches which may result in the following
behavior when using external (HiDPI) screens:
- The lockscreen doesn't disappear on an external monitor.
- The scaling is broken when using larger fonts on the TTY.
It can be worked around by declaring programs.snenslock.fixExternalMonitorsInSway = true;
.
This script basically sets the resolution of external screens to a low resolution
(1280x720
) and then resets it to the previous resolution after snenslock
has unlocked
the screen. See also flake.nix
for further details.
swayidle
When using swayidle
to lock with snenslock
, swayidle
doesn't
get back to "active state" on unlock until some activity such as hitting a random key is made by the user.
This means that swayidle
assumes it's still locked after unlock and thus doesn't lock again after the timeout
is reached the next time.
The only workaround known so far is to restart swayidle.service
after unlock
in via programs.snenslock.hooks.afterLockCommands
.
Hacking
-
Install
pam(8)
service forsnenslock
(already done if you're on a machine withprograms.snenslock.enable = true;
):{ security.pam.services.snenslock = {}; }
-
Then,
snenslock
can be executed locally with e.g. the following command:sudo -E nix run .# -- --greeter tui --greeter-cfg snenslock.example.toml
Alternatively, the VM tests in flake.nix
may be used.
Options' reference
programs.snenslock.enable
Whether to enable snenslock.
Type: boolean
Default:
false
Example:
true
programs.snenslock.disableFallbackToRoot
Whether to disable authenticating as root after each N failed attempts.
Type: boolean
Default:
false
programs.snenslock.fixExternalMonitorsInSway
Whether to enable fixing mode of external monitors in sway.
Type: boolean
Default:
false
Example:
true
programs.snenslock.greeter
Which greeter to use to unlock the machine.
Type: one of “minimal”, “tui”
Default:
"minimal"
programs.snenslock.greeterCfg.tui.asterisk_char
Character to use as asterisk.
Type: string matching the pattern ^.{1}$
Default:
"*"
programs.snenslock.greeterCfg.tui.clock_format
Format of the clock in the lockscreen.
Type: string
Default:
"%a %d %b %H:%M"
programs.snenslock.greeterCfg.tui.form_width
Width of the TUI login form.
Type: positive integer, meaning >0
Default:
50
programs.snenslock.greeterCfg.tui.no_asterisks
Whether to disable asterisks for passwords.
Type: boolean
Default:
false
programs.snenslock.greeterCfg.tui.refresh_seconds
Seconds after which the clock gets refreshed.
Type: positive integer, meaning >0
Default:
1
programs.snenslock.greeterCfg.tui.relative_battery_refresh
Update times of the battery indicator relative to the refresh time of the UI itself (currently configured via ).
By default this is 10
whereas the UI gets refreshed every second
(implying that the tui renderer loop gets executed 10 times). Every tenth
iteration (=every ~10 seconds) the battery status also gets updated.
Type: unsigned integer, meaning >=0
Default:
10
programs.snenslock.greeterCfg.tui.show_battery_status
Whether to enable displaying battery capacity & charging status at the bottom right.
Type: boolean
Default:
false
Example:
true
programs.snenslock.hooks.afterLockCommands
Commands to execute after the lock has finished. Useful to perform commands such as resuming running music, clearing cached gpg passphrases etc.
Type: strings concatenated with “\n”
Default:
""
programs.snenslock.hooks.beforeLockCommands
Commands to execute before starting the lcokscreen. Useful for e.g. pausing music etc.
Type: strings concatenated with “\n”
Default:
""
programs.snenslock.lock-on-suspend.enable
Whether to enable locking with snenslock on suspend.
Type: boolean
Default:
false
Example:
true
programs.snenslock.logind.enable
Whether to enable locking on org.freedesktop.login1 DBus signals.
Type: boolean
Default:
true
Example:
true
programs.snenslock.poweroffAfter
After how many failed attempts the machine should be powered off.
Type: null or (positive integer, meaning >0)
Default:
null
programs.snenslock.rootAtAuthTimes
After each N attempts to authenticate as the locking user, an unlock as root is attempted.
Type: positive integer, meaning >0
Default:
5