1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.programs.rust-motd;
10 format = pkgs.formats.toml { };
11
12 # Order the sections in the TOML according to the order of sections
13 # in `cfg.order`.
14 motdConf =
15 pkgs.runCommand "motd.conf"
16 {
17 __structuredAttrs = true;
18 inherit (cfg) order settings;
19 nativeBuildInputs = [
20 pkgs.remarshal
21 pkgs.jq
22 ];
23 }
24 ''
25 cat "$NIX_ATTRS_JSON_FILE" \
26 | jq '.settings as $settings
27 | .order
28 | map({ key: ., value: $settings."\(.)" })
29 | from_entries' -r \
30 | json2toml /dev/stdin "$out"
31 '';
32in
33{
34 options.programs.rust-motd = {
35 enable = lib.mkEnableOption "rust-motd, a Message Of The Day (MOTD) generator";
36 enableMotdInSSHD = lib.mkOption {
37 default = true;
38 type = lib.types.bool;
39 description = ''
40 Whether to let `openssh` print the
41 result when entering a new `ssh`-session.
42 By default either nothing or a static file defined via
43 [](#opt-users.motd) is printed. Because of that,
44 the latter option is incompatible with this module.
45 '';
46 };
47 refreshInterval = lib.mkOption {
48 default = "*:0/5";
49 type = lib.types.str;
50 description = ''
51 Interval in which the {manpage}`motd(5)` file is refreshed.
52 For possible formats, please refer to {manpage}`systemd.time(7)`.
53 '';
54 };
55 order = lib.mkOption {
56 type = lib.types.listOf lib.types.str;
57 default = builtins.attrNames cfg.settings;
58 defaultText = lib.literalExpression "attrNames cfg.settings";
59 description = ''
60 The order of the sections in [](#opt-programs.rust-motd.settings).
61 By default they are ordered alphabetically.
62
63 Context: since attribute sets in Nix are always
64 ordered alphabetically internally this means that
65
66 ```nix
67 {
68 uptime = { /* ... */ };
69 banner = { /* ... */ };
70 }
71 ```
72
73 will still have `banner` displayed before `uptime`.
74
75 To work around that, this option can be used to define the order of all keys,
76 i.e.
77
78 ```nix
79 {
80 order = [
81 "uptime"
82 "banner"
83 ];
84 }
85 ```
86
87 makes sure that `uptime` is placed before `banner` in the motd.
88 '';
89 };
90 settings = lib.mkOption {
91 type = lib.types.attrsOf format.type;
92 description = ''
93 Settings on what to generate. Please read the
94 [upstream documentation](https://github.com/rust-motd/rust-motd/blob/main/README.md#configuration)
95 for further information.
96 '';
97 };
98 };
99 config = lib.mkIf cfg.enable {
100 assertions = [
101 {
102 assertion = config.users.motd == "";
103 message = ''
104 `programs.rust-motd` is incompatible with `users.motd`!
105 '';
106 }
107 {
108 assertion = builtins.sort (a: b: a < b) cfg.order == builtins.attrNames cfg.settings;
109 message = ''
110 Please ensure that every section from `programs.rust-motd.settings` is present in
111 `programs.rust-motd.order`.
112 '';
113 }
114 ];
115 systemd.services.rust-motd = {
116 path = with pkgs; [ bash ];
117 documentation = [
118 "https://github.com/rust-motd/rust-motd/blob/v${pkgs.rust-motd.version}/README.md"
119 ];
120 description = "motd generator";
121 wantedBy = [ "multi-user.target" ];
122 serviceConfig = {
123 ExecStart = "${pkgs.writeShellScript "update-motd" ''
124 ${pkgs.rust-motd}/bin/rust-motd ${motdConf} > motd
125 ''}";
126 CapabilityBoundingSet = [ "" ];
127 LockPersonality = true;
128 MemoryDenyWriteExecute = true;
129 NoNewPrivileges = true;
130 PrivateDevices = true;
131 PrivateTmp = true;
132 ProtectClock = true;
133 ProtectControlGroups = true;
134 ProtectHome = true;
135 ProtectHostname = true;
136 ProtectKernelModules = true;
137 ProtectKernelLogs = true;
138 ProtectKernelTunables = true;
139 ProtectSystem = "full";
140 StateDirectory = "rust-motd";
141 RestrictAddressFamilies = [ "AF_UNIX" ];
142 RestrictNamespaces = true;
143 RestrictRealtime = true;
144 RestrictSUIDSGID = true;
145 RemoveIPC = true;
146 WorkingDirectory = "/var/lib/rust-motd";
147 };
148 };
149 systemd.timers.rust-motd = {
150 wantedBy = [ "timers.target" ];
151 timerConfig.OnCalendar = cfg.refreshInterval;
152 };
153 security.pam.services.sshd.text = lib.mkIf cfg.enableMotdInSSHD (
154 lib.mkDefault (
155 lib.mkAfter ''
156 session optional ${pkgs.pam}/lib/security/pam_motd.so motd=/var/lib/rust-motd/motd
157 ''
158 )
159 );
160 services.openssh.extraConfig =
161 lib.mkIf (cfg.settings ? last_login && cfg.settings.last_login != { })
162 ''
163 PrintLastLog no
164 '';
165 };
166 meta.maintainers = with lib.maintainers; [ ma27 ];
167}