1{
2 lib,
3 config,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.ntpd-rs;
10 format = pkgs.formats.toml { };
11 configFile = format.generate "ntpd-rs.toml" cfg.settings;
12
13 validateConfig =
14 file:
15 pkgs.runCommand "validate-ntpd-rs.toml"
16 {
17 nativeBuildInputs = [ cfg.package ];
18 }
19 ''
20 ntp-ctl validate -c ${file}
21 ln -s "${file}" "$out"
22 '';
23in
24{
25 options.services.ntpd-rs = {
26 enable = lib.mkEnableOption "Network Time Service (ntpd-rs)";
27 metrics.enable = lib.mkEnableOption "ntpd-rs Prometheus Metrics Exporter";
28
29 package = lib.mkPackageOption pkgs "ntpd-rs" { };
30
31 useNetworkingTimeServers = lib.mkOption {
32 type = lib.types.bool;
33 default = true;
34 description = ''
35 Use source time servers from {var}`networking.timeServers` in config.
36 '';
37 };
38
39 settings = lib.mkOption {
40 type = lib.types.submodule {
41 freeformType = format.type;
42 };
43 default = { };
44 description = ''
45 Settings to write to {file}`ntp.toml`
46
47 See <https://docs.ntpd-rs.pendulum-project.org/man/ntp.toml.5>
48 for more information about available options.
49 '';
50 };
51 };
52
53 config = lib.mkIf cfg.enable {
54 assertions = [
55 {
56 assertion = !config.services.timesyncd.enable;
57 message = ''
58 `ntpd-rs` is not compatible with `services.timesyncd`. Please disable one of them.
59 '';
60 }
61 ];
62
63 environment.systemPackages = [ cfg.package ];
64 systemd.packages = [ cfg.package ];
65
66 services.timesyncd.enable = false;
67 systemd.services.systemd-timedated.environment = {
68 SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd-rs.service";
69 };
70
71 services.ntpd-rs.settings = {
72 observability = {
73 observation-path = lib.mkDefault "/var/run/ntpd-rs/observe";
74 };
75 source = lib.mkIf cfg.useNetworkingTimeServers (
76 map (ts: {
77 mode = if lib.strings.hasInfix "pool" ts then "pool" else "server";
78 address = ts;
79 }) config.networking.timeServers
80 );
81 };
82
83 systemd.services.ntpd-rs = {
84 wantedBy = [ "multi-user.target" ];
85 serviceConfig = {
86 User = "";
87 Group = "";
88 DynamicUser = true;
89 ExecStart = [
90 ""
91 "${lib.makeBinPath [ cfg.package ]}/ntp-daemon --config=${validateConfig configFile}"
92 ];
93
94 CapabilityBoundingSet = [
95 "CAP_SYS_TIME"
96 "CAP_NET_BIND_SERVICE"
97 ];
98 AmbientCapabilities = [
99 "CAP_SYS_TIME"
100 "CAP_NET_BIND_SERVICE"
101 ];
102 LimitCORE = 0;
103 LimitNOFILE = 65535;
104 LockPersonality = true;
105 MemorySwapMax = 0;
106 MemoryZSwapMax = 0;
107 PrivateTmp = true;
108 ProcSubset = "pid";
109 ProtectControlGroups = true;
110 ProtectHome = true;
111 ProtectHostname = true;
112 ProtectKernelLogs = true;
113 ProtectKernelModules = true;
114 ProtectKernelTunables = true;
115 ProtectProc = "invisible";
116 ProtectSystem = "strict";
117 Restart = "on-failure";
118 RestartSec = "10s";
119 RestrictAddressFamilies = [
120 "AF_INET"
121 "AF_INET6"
122 "AF_UNIX"
123 "AF_NETLINK"
124 ];
125 RestrictNamespaces = true;
126 RestrictRealtime = true;
127 SystemCallArchitectures = "native";
128 SystemCallFilter = [
129 "@system-service"
130 "@resources"
131 "@network-io"
132 "@clock"
133 ];
134 NoNewPrivileges = true;
135 UMask = "0077";
136 };
137 };
138
139 systemd.services.ntpd-rs-metrics = lib.mkIf cfg.metrics.enable {
140 wantedBy = [ "multi-user.target" ];
141 serviceConfig = {
142 User = "";
143 Group = "";
144 DynamicUser = true;
145 ExecStart = [
146 ""
147 "${lib.makeBinPath [ cfg.package ]}/ntp-metrics-exporter --config=${validateConfig configFile}"
148 ];
149
150 CapabilityBoundingSet = [ ];
151 LimitCORE = 0;
152 LimitNOFILE = 65535;
153 LockPersonality = true;
154 MemorySwapMax = 0;
155 MemoryZSwapMax = 0;
156 PrivateTmp = true;
157 ProcSubset = "pid";
158 ProtectClock = true;
159 ProtectControlGroups = true;
160 ProtectHome = true;
161 ProtectHostname = true;
162 ProtectKernelLogs = true;
163 ProtectKernelModules = true;
164 ProtectKernelTunables = true;
165 ProtectProc = "invisible";
166 ProtectSystem = "strict";
167 PrivateDevices = true;
168 RestrictSUIDSGID = true;
169 RemoveIPC = true;
170 RestrictAddressFamilies = [
171 "AF_INET"
172 "AF_INET6"
173 "AF_UNIX"
174 ];
175 RestrictNamespaces = true;
176 RestrictRealtime = true;
177 SystemCallArchitectures = "native";
178 SystemCallFilter = [
179 "@system-service"
180 "@network-io"
181 "~@privileged"
182 "~@resources"
183 "~@mount"
184 ];
185 NoNewPrivileges = true;
186 UMask = "0077";
187 };
188 };
189 };
190
191 meta.maintainers = with lib.maintainers; [ fpletz ];
192}