1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11
12 cfg = config.services.fcron;
13
14 queuelen = optionalString (cfg.queuelen != null) "-q ${toString cfg.queuelen}";
15
16 # Duplicate code, also found in cron.nix. Needs deduplication.
17 systemCronJobs = ''
18 SHELL=${pkgs.bash}/bin/bash
19 PATH=${config.system.path}/bin:${config.system.path}/sbin
20 ${optionalString (config.services.cron.mailto != null) ''
21 MAILTO="${config.services.cron.mailto}"
22 ''}
23 NIX_CONF_DIR=/etc/nix
24 ${lib.concatStrings (map (job: job + "\n") config.services.cron.systemCronJobs)}
25 '';
26
27 allowdeny = target: users: {
28 source = pkgs.writeText "fcron.${target}" (concatStringsSep "\n" users);
29 target = "fcron.${target}";
30 mode = "644";
31 gid = config.ids.gids.fcron;
32 };
33
34in
35
36{
37
38 ###### interface
39
40 options = {
41
42 services.fcron = {
43
44 enable = mkOption {
45 type = types.bool;
46 default = false;
47 description = "Whether to enable the {command}`fcron` daemon.";
48 };
49
50 allow = mkOption {
51 type = types.listOf types.str;
52 default = [ "all" ];
53 description = ''
54 Users allowed to use fcrontab and fcrondyn (one name per
55 line, `all` for everyone).
56 '';
57 };
58
59 deny = mkOption {
60 type = types.listOf types.str;
61 default = [ ];
62 description = "Users forbidden from using fcron.";
63 };
64
65 maxSerialJobs = mkOption {
66 type = types.int;
67 default = 1;
68 description = "Maximum number of serial jobs which can run simultaneously.";
69 };
70
71 queuelen = mkOption {
72 type = types.nullOr types.int;
73 default = null;
74 description = "Number of jobs the serial queue and the lavg queue can contain.";
75 };
76
77 systab = mkOption {
78 type = types.lines;
79 default = "";
80 description = ''The "system" crontab contents.'';
81 };
82 };
83
84 };
85
86 ###### implementation
87
88 config = mkIf cfg.enable {
89
90 services.fcron.systab = systemCronJobs;
91
92 environment.etc = listToAttrs (
93 map
94 (x: {
95 name = x.target;
96 value = x;
97 })
98 [
99 (allowdeny "allow" (cfg.allow))
100 (allowdeny "deny" cfg.deny)
101 # see man 5 fcron.conf
102 {
103 source =
104 let
105 isSendmailWrapped = lib.hasAttr "sendmail" config.security.wrappers;
106 sendmailPath =
107 if isSendmailWrapped then "/run/wrappers/bin/sendmail" else "${config.system.path}/bin/sendmail";
108 in
109 pkgs.writeText "fcron.conf" ''
110 fcrontabs = /var/spool/fcron
111 pidfile = /run/fcron.pid
112 fifofile = /run/fcron.fifo
113 fcronallow = /etc/fcron.allow
114 fcrondeny = /etc/fcron.deny
115 shell = /bin/sh
116 sendmail = ${sendmailPath}
117 editor = ${pkgs.vim}/bin/vim
118 '';
119 target = "fcron.conf";
120 gid = config.ids.gids.fcron;
121 mode = "0644";
122 }
123 ]
124 );
125
126 environment.systemPackages = [ pkgs.fcron ];
127 users.users.fcron = {
128 uid = config.ids.uids.fcron;
129 home = "/var/spool/fcron";
130 group = "fcron";
131 };
132 users.groups.fcron.gid = config.ids.gids.fcron;
133
134 security.wrappers = {
135 fcrontab = {
136 source = "${pkgs.fcron}/bin/fcrontab";
137 owner = "fcron";
138 group = "fcron";
139 setgid = true;
140 setuid = true;
141 };
142 fcrondyn = {
143 source = "${pkgs.fcron}/bin/fcrondyn";
144 owner = "fcron";
145 group = "fcron";
146 setgid = true;
147 setuid = false;
148 };
149 fcronsighup = {
150 source = "${pkgs.fcron}/bin/fcronsighup";
151 owner = "root";
152 group = "fcron";
153 setuid = true;
154 };
155 };
156 systemd.services.fcron = {
157 description = "fcron daemon";
158 wantedBy = [ "multi-user.target" ];
159
160 path = [ pkgs.fcron ];
161
162 preStart = ''
163 install \
164 --mode 0770 \
165 --owner fcron \
166 --group fcron \
167 --directory /var/spool/fcron
168 # load system crontab file
169 /run/wrappers/bin/fcrontab -u systab - < ${pkgs.writeText "systab" cfg.systab}
170 '';
171
172 serviceConfig = {
173 Type = "forking";
174 ExecStart = "${pkgs.fcron}/sbin/fcron -m ${toString cfg.maxSerialJobs} ${queuelen}";
175 };
176 };
177 };
178}