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