Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions modules/services/turnstile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Turnstile

An experimental module for
[turnstile](https://github.com/chimera-linux/turnstile).

Turnstile is a session/login tracker that runs a user level dinit/runit instance
when a user logs in and closes is safely when they log out.

This module is subject to extreme changes at any point.
209 changes: 209 additions & 0 deletions modules/services/turnstile/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.turnstile;

package = pkgs.callPackage ./package.nix {
graphicalMonitor = cfg.dinit.settings.enableGraphicalMonitor;
};
in
{
options.services.turnstile = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable [turnstilel](${cfg.package.meta.homepage}).
'';
};

package = lib.mkOption {
type = lib.types.package;
default = package;
description = ''
The package to use for `turnstile`.
'';
};

systemBootDir = lib.mkOption {
type = lib.types.path;
default = "/usr/lib/dinit.d/user/boot.d";
description = "Location of boot services used for all users";
};

settings = with lib; {
debug = mkOption {
type = types.enum [
"yes"
"no"
];
default = "no";
description = "Whether or not to enable debug output in turnstile";
};
backend = mkOption {
type = types.enum [
"dinit"
"runit"
];
default = "dinit";
description = "`runit` is not currently supported, but changing this option may break things";
};
debug_stderr = mkOption {
type = types.enum [
"yes"
"no"
];
default = "no";
description = "Whether or not to print debug to stderr in addition to stdout";
};
linger = mkOption {
type = types.enum [
"yes"
"no"
];
default = "no";
description = "Whether or not the service manager should linger after user logout. Requires ${cfg.settings.manage_rundir} to be enabled";
};
rundir_path = mkOption {
type = types.str;
default = "/run/user/%u";
description = "Where the rundir is for the user. See [turnstile](${cfg.package.meta.homepage}) documentation for available options";
};
manage_rundir = mkOption {
type = types.enum [
"yes"
"no"
];
default = "no";
description = "Whether or not `turnstile` should manage the runtime directory";

};
export_dbus_address = mkOption {
type = types.enum [
"yes"
"no"
];
default = "yes";
description = "Whether or not to export the D-Bus session address to the environment of the service manager";
};
login_timeout = mkOption {
type = types.ints.unsigned;
default = 60;
description = "How long the service manager waits on initial processes (in seconds) before giving up.";
};
root_session = mkOption {
type = types.enum [
"yes"
"no"
];
default = "no";
description = "Whether or not `turnstile` acts for the root user.";
};
};

dinit = with lib; {
enable = mkOption {
type = types.bool;
default = true;
description = "Whether or not to use the dinit backend for `turnstile`.";
};

settings = with types; {
# TODO: make this accept a str or a list of strings
service_dir = mkOption {
type = str;
default = "$HOME/.config/dinit.d";
description = "Users service dir for `turnstile`'s `dinit` backend. This should include a way to differentiate per user unless all users have identical services.";
};

boot_dir = mkOption {
type = types.str;
default = "${cfg.dinit.settings.service_dir}/boot.d";
description = "Users service boot dir for `turnstile`'s `dinit` backend. This should include a way to differentiate per user unless all users have identical services.";
};

system_boot_dir = mkOption {
type = types.path;
default = "/usr/lib/dinit.d/user/boot.d";
description = "Systems service boot dir for `turnstile`'s `dinit` backend.";
};

enableGraphicalMonitor = mkOption {
type = types.bool;
default = false;
description = "Whether or not to monitor environment changes to DISPLAY and WAYLAND_DISPLAY variables. Currently requires manually adding `dinitctl setenv VAR=$VAR`to any startup scripts for your graphical environment to function.";
};
};
};
};

config = lib.mkIf cfg.enable {
environment.systemPackages = [
cfg.package
]
++ lib.lists.optional cfg.dinit.enable (lib.lowPrio pkgs.dinit);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️


finit.tmpfiles.rules = [
"d ${cfg.dinit.settings.system_boot_dir} 0777"
];

environment.etc = {
"turnstile/turnstiled.conf".source =
(pkgs.formats.keyValue { }).generate "turnstiled.conf"
cfg.settings;

"turnstile/backend/dinit.conf".text = ''
boot_dir="${cfg.dinit.settings.boot_dir}"
system_boot_dir="${cfg.dinit.settings.system_boot_dir}"
services_dir1="${cfg.dinit.settings.service_dir}"
services_dir2="/etc/dinit.d/user"
services_dir3="/usr/local/lib/dinit.d/user"
services_dir4="/usr/lib/dinit.d/user"
'';
Comment on lines +159 to +166

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"turnstile/backend/dinit.conf".text = ''
boot_dir="${cfg.dinit.boot_dir}"
system_boot_dir="${cfg.dinit.system_boot_dir}"
services_dir1="${cfg.dinit.service_dir}"
services_dir2="/etc/dinit.d/user"
services_dir3="/usr/local/lib/dinit.d/user"
services_dir4="/usr/lib/dinit.d/user"
'';
"turnstile/backend/dinit.conf".text = (pkgs.formats.keyValue { }).generate "dinit.conf" cfg.dinit.settings;

see comments above about adding services.turnstiled.dinit.settings option

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving unresolved because I think I want to change the services.turnstiled.dinit.settings.service_dir option to allow a list so users can change services_dir 2-4 and beyond. Makes the conversion a bit more complicated but I will definitely look into this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as much as i don't like opinionated modules... yes, that is so so so much better 😅

please 🙇‍♂️


"pam.d/turnstiled".text = ''
auth sufficient pam_rootok.so
session optional pam_keyinit.so force revoke
session optional pam_umask.so usergroups umask=022
-session optional pam_elogind.so
session required pam_env.so conffile=/etc/security/pam_env.conf readenv=1 # env (order 10100)
session required ${cfg.package}/pam/pam_turnstile.so turnstiled
session required pam_limits.so
'';
Comment on lines +168 to +176

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can move this under security.pam.services.turnstiled.text instead... in the future, when we have a real PAM stack, will be beneficial

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have done this from the start... changed it.

};

security.pam.services = lib.mkMerge [
{
login.text = lib.mkAfter "session optional ${cfg.package}/pam/pam_turnstile.so";
turnstiled.text = ''
auth sufficient pam_rootok.so
session optional pam_keyinit.so force revoke
session optional pam_umask.so usergroups umask=022
-session optional pam_elogind.so
session required pam_env.so conffile=/etc/security/pam_env.conf readenv=1 # env (order 10100)
session required ${cfg.package}/pam/pam_turnstile.so turnstiled
session required pam_limits.so
'';
}
];

finit.services.turnstiled = {
description = "turnstiled, a user-service manager manager";
command = "${cfg.package}/bin/turnstiled";
conditions = "service/syslogd/ready";
log = true;
pid = "/run/turnstiled.pid";
path =
with pkgs;
[
cfg.package

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there multiple programs under bin/ that turnstiled internally accesses?

config.programs.coreutils.package
]
++ lib.lists.optional cfg.dinit.enable dinit;
};
};
}
68 changes: 68 additions & 0 deletions modules/services/turnstile/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
lib,
stdenv,
fetchFromGitHub,
Comment thread
aanderse marked this conversation as resolved.
pkg-config,
scdoc,
meson,
ninja,
pam,
dinit,
graphicalMonitor ? true,
}:

stdenv.mkDerivation (finalAttrs: {
pname = "turnstile";
version = "v0.1.11";

src = fetchFromGitHub {
owner = "chimera-linux";
repo = "turnstile";
rev = "${finalAttrs.version}";
sha256 = "sha256-94J+w0RHxzw7wS70LcpEzMvgevAqAwl0EtiANUmdRYU=";
};

buildInputs = [
pkg-config
scdoc
meson
ninja
pam
Comment thread
aanderse marked this conversation as resolved.
dinit
];

# nativeBuildInputs = [
# dinit
# ];

postPatch = lib.strings.concatStrings [
(lib.strings.optionalString graphicalMonitor ''
substituteInPlace backend/dinit \
--replace-fail '/usr/bin/dinit-monitor' '${lib.getExe' dinit "dinit-monitor"}'
'')

''
substituteInPlace meson.build \
--replace-fail "get_option('prefix'), get_option('sysconfdir'), 'turnstile'" "'/etc', 'turnstile'"
''
];

mesonFlags = [
"-Ddefault_backend=dinit"
"-Ddinit=enabled"
"-Dstatedir=/var/lib/turnstiled"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't that already the value? if not, sounds like we need to modify localstatedir instead?

"-Dpam_moddir=./pam"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that right? 🤔

];

patches = lib.lists.optional (!graphicalMonitor) (./remove_graphical_monitor.diff);

Comment thread
aanderse marked this conversation as resolved.
meta = with lib; {
homepage = "https://github.com/chimera-linux/turnstile";
description = "This program waits for user logins and then runs the associated user-service manager";
license = licenses.bsd2;
maintainers = with maintainers; [ vitrial ];
platforms = platforms.linux;
mainProgram = "turnstiled";
};

})
26 changes: 26 additions & 0 deletions modules/services/turnstile/patch.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
diff --git a/backend/dinit b/backend/dinit
index 7bacaac..065d14a 100644
--- a/backend/dinit
+++ b/backend/dinit
@@ -136,7 +136,7 @@ cat << EOF > "${DINIT_DIR}/graphical.monitor"
type = process
depends-on = login.target
options = pass-cs-fd
-command = /usr/bin/dinit-monitor -E -c "$0 graphical-notify" WAYLAND_DISPLAY DISPLAY
+command = /run/current-system/sw/bin/dinit-monitor -E -c "$0 graphical-notify" WAYLAND_DISPLAY DISPLAY
EOF

# this is needed for login to proceed
diff --git a/meson.build b/meson.build
index aa07c9c..fdf89fa 100644
--- a/meson.build
+++ b/meson.build
@@ -28,7 +28,7 @@ have_runit = get_option('runit').enabled()
conf_data = configuration_data()
conf_data.set_quoted('RUN_PATH', get_option('rundir'))
conf_data.set_quoted('CONF_PATH', join_paths(
- get_option('prefix'), get_option('sysconfdir'), 'turnstile'
+ '/etc/', 'turnstile'
))
conf_data.set10('MANAGE_RUNDIR', get_option('manage_rundir'))

38 changes: 38 additions & 0 deletions modules/services/turnstile/remove_graphical_monitor.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
diff --git a/backend/dinit b/backend/dinit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh boy... this is a tough one, right?

do we let turnstiled manage these units, or drop this behaviour and have finix manage them?

assume we let turnstiled manage units for the user... what was your motivation in optionally dropping/modifying these?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turnstile generates these initial units at runtime and I haven't had the time to test if that is for a good reason or just simply how they choose to do it. It might just happen that in a nondeclarative environment its necessary/more convenient.

The purpose of removing the graphical ones optionally was simply the annoyance referenced in the option description. You have to add dinitctl setenv to all of your graphical environment startup scripts or you're left with a useless monitor/target every time you look at your services.

All that to say, I think that if finix can generate them with no negative side effects it most definitely should, it may just make pushing new backends to upstream more annoying if we end up pursuing that in any way.

index 7bacaac..27ee9b7 100644
--- a/backend/dinit
+++ b/backend/dinit
@@ -121,8 +121,6 @@ type = internal
depends-on = system
waits-for.d = ${boot_dir}
depends-on = login.target
-depends-ms = graphical.monitor
-depends-ms = graphical.target
EOF

# this must also succeed
@@ -131,24 +129,9 @@ type = internal
waits-for.d = ${system_boot_dir}
EOF

-# monitor service to watch for environment changes
-cat << EOF > "${DINIT_DIR}/graphical.monitor"
-type = process
-depends-on = login.target
-options = pass-cs-fd
-command = /usr/bin/dinit-monitor -E -c "$0 graphical-notify" WAYLAND_DISPLAY DISPLAY
-EOF
-
# this is needed for login to proceed
cat << EOF > "${DINIT_DIR}/login.target"
type = internal
EOF

-# this is not necessary to have started for login to proceed
-cat << EOF > "${DINIT_DIR}/graphical.target"
-type = triggered
-depends-on = graphical.monitor
-depends-on = login.target
-EOF
-
exec dinit --user --ready-fd 3 --services-dir "$DINIT_DIR" "$@" 3>"$DINIT_READY_PIPE"
Loading