1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.cage;
12in
13{
14 options.services.cage.enable = mkEnableOption "cage kiosk service";
15
16 options.services.cage.user = mkOption {
17 type = types.str;
18 default = "demo";
19 description = ''
20 User to log-in as.
21 '';
22 };
23
24 options.services.cage.extraArguments = mkOption {
25 type = types.listOf types.str;
26 default = [ ];
27 defaultText = literalExpression "[]";
28 description = "Additional command line arguments to pass to Cage.";
29 example = [ "-d" ];
30 };
31
32 options.services.cage.environment = mkOption {
33 type = types.attrsOf types.str;
34 default = { };
35 example = {
36 WLR_LIBINPUT_NO_DEVICES = "1";
37 };
38 description = "Additional environment variables to pass to Cage.";
39 };
40
41 options.services.cage.program = mkOption {
42 type = types.path;
43 default = "${pkgs.xterm}/bin/xterm";
44 defaultText = literalExpression ''"''${pkgs.xterm}/bin/xterm"'';
45 description = ''
46 Program to run in cage.
47 '';
48 };
49
50 options.services.cage.package = mkPackageOption pkgs "cage" { };
51
52 config = mkIf cfg.enable {
53
54 # The service is partially based off of the one provided in the
55 # cage wiki at
56 # https://github.com/Hjdskes/cage/wiki/Starting-Cage-on-boot-with-systemd.
57 systemd.services."cage-tty1" = {
58 enable = true;
59 after = [
60 "systemd-user-sessions.service"
61 "plymouth-start.service"
62 "plymouth-quit.service"
63 "systemd-logind.service"
64 "getty@tty1.service"
65 ];
66 before = [ "graphical.target" ];
67 wants = [
68 "dbus.socket"
69 "systemd-logind.service"
70 "plymouth-quit.service"
71 ];
72 wantedBy = [ "graphical.target" ];
73 conflicts = [ "getty@tty1.service" ];
74
75 restartIfChanged = false;
76 unitConfig.ConditionPathExists = "/dev/tty1";
77 serviceConfig = {
78 ExecStart = ''
79 ${cfg.package}/bin/cage \
80 ${escapeShellArgs cfg.extraArguments} \
81 -- ${cfg.program}
82 '';
83 User = cfg.user;
84
85 IgnoreSIGPIPE = "no";
86
87 # Log this user with utmp, letting it show up with commands 'w' and
88 # 'who'. This is needed since we replace (a)getty.
89 UtmpIdentifier = "%n";
90 UtmpMode = "user";
91 # A virtual terminal is needed.
92 TTYPath = "/dev/tty1";
93 TTYReset = "yes";
94 TTYVHangup = "yes";
95 TTYVTDisallocate = "yes";
96 # Fail to start if not controlling the virtual terminal.
97 StandardInput = "tty-fail";
98 StandardOutput = "journal";
99 StandardError = "journal";
100 # Set up a full (custom) user session for the user, required by Cage.
101 PAMName = "cage";
102 };
103 environment = cfg.environment;
104 };
105
106 security.polkit.enable = true;
107
108 security.pam.services.cage.text = ''
109 auth required pam_unix.so nullok
110 account required pam_unix.so
111 session required pam_unix.so
112 session required pam_env.so conffile=/etc/pam/environment readenv=0
113 session required ${config.systemd.package}/lib/security/pam_systemd.so
114 '';
115
116 hardware.graphics.enable = mkDefault true;
117
118 systemd.targets.graphical.wants = [ "cage-tty1.service" ];
119
120 systemd.defaultUnit = "graphical.target";
121 };
122
123 meta.maintainers = with lib.maintainers; [ matthewbauer ];
124
125}