Skip to content

Commit df28a18

Browse files
committed
luci-app-chrony: Add chrony
This prefers the NTS version, and does not discern the non-NTS version, since chronyd.init startup script doesn't either. Signed-off-by: Paul Donald <[email protected]>
1 parent 3c16c59 commit df28a18

File tree

5 files changed

+573
-0
lines changed

5 files changed

+573
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#
2+
# Copyright (C) 2025 OpenWrt.org
3+
#
4+
# This is free software, licensed under Apache-2.0.
5+
# See /LICENSE for more information.
6+
#
7+
8+
include $(TOPDIR)/rules.mk
9+
10+
LUCI_TITLE:=LuCI support for chrony
11+
LUCI_DEPENDS:=+luci-base +chrony-nts
12+
13+
PKG_LICENSE:=Apache-2.0
14+
15+
include ../../luci.mk
16+
17+
# call BuildPackage - OpenWrt buildroot signature
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
'use strict';
2+
'require form';
3+
'require fs';
4+
'require uci';
5+
'require view';
6+
'require tools.widgets as widgets';
7+
8+
return view.extend({
9+
10+
render(data) {
11+
const docUrl = 'https://chrony-project.org/documentation.html';
12+
let m, s, o;
13+
14+
m = new form.Map('chrony', _('Chrony NTP/NTS daemon'),
15+
'%s'.format('<a id="docUrl" href="%s" target="_blank" rel="noreferrer">%s</a>'
16+
.format(docUrl, _('Documentation'))));
17+
18+
// Interface
19+
s = m.section(form.NamedSection, 'allow', 'allow', _('Allow'), _('An allow range permits access for chronyc from specific IPs to chronyd.') + '<br/>' +
20+
_('Delete this section to allow all local IPs.'));
21+
s.anonymous = true;
22+
s.addremove = true;
23+
24+
o = s.option(widgets.NetworkSelect, 'interface', _('Interface'),
25+
_('Choose IP ranges from this interface to set them as allowed ranges.') + '<br/>' +
26+
_('Choose a wan interface to allow from all IPs.') + '<br/>' +
27+
_('Additional firewall configuration is required if you intend wan access.'));
28+
o.nocreate = true;
29+
o.rmempty = false;
30+
31+
// NTS
32+
s = m.section(form.NamedSection, 'nts', 'nts', _('Network Time Security (NTS)'));
33+
s.anonymous = true;
34+
s.addremove = true;
35+
36+
o = s.option(form.Flag, 'rtccheck', _('RTC Check'),
37+
_('Check for the presence of %s.'.format('<code>/dev/rtc0</code>'), 'Check for RTC character device') + '<br/>' +
38+
_('Disables certificate time checks via %s if RTC is absent.'.format('<code>nocerttimecheck</code>') ) );
39+
o.default = o.disabled;
40+
41+
o = s.option(form.Flag, 'systemcerts', _('Use system CA bundle'));
42+
o.default = o.enabled;
43+
44+
o = s.option(form.FileUpload, 'trustedcerts', _('Trusted certificates'));
45+
o.optional = true;
46+
o.root_directory = '/etc';
47+
48+
// Stepping
49+
s = m.section(form.NamedSection, 'makestep', 'makestep', _('Stepping'),
50+
_('Corrects the system clock by stepping immediately when it is so far adrift that the slewing process would take a very long time.'));
51+
s.anonymous = true;
52+
s.addremove = true;
53+
s.singular = true;
54+
55+
o = s.option(form.Value, 'threshold', _('Trigger Amount Threshold'),
56+
_('Seconds float value.'));
57+
o.datatype = 'float';
58+
o.optional = true;
59+
60+
o = s.option(form.Value, 'limit', _('Limit'),
61+
_('First x clock updates'));
62+
o.datatype = 'integer';
63+
o.optional = true;
64+
65+
// Logging
66+
s = m.section(form.NamedSection, 'logging', 'logging', _('Logging'));
67+
s.anonymous = true;
68+
s.addremove = true;
69+
70+
o = s.option(form.Value, 'logchange', _('Log any change more than'),
71+
_('Seconds threshold for the adjustment of the system clock that will generate a syslog message.'));
72+
o.datatype = 'float';
73+
o.placeholder = '1';
74+
o.optional = true;
75+
76+
// System Clock
77+
s = m.section(form.NamedSection, 'systemclock', 'systemclock', _('System Clock'));
78+
s.anonymous = true;
79+
s.addremove = true;
80+
81+
o = s.option(form.Value, 'precision', _('Precision'),
82+
_('Precision of the system clock (in seconds).'));
83+
o.datatype = 'string';
84+
o.placeholder = _('8e-6 (8 microseconds)');
85+
o.optional = true;
86+
87+
o = s.option(form.ListValue, 'leapsecmode', _('Leap second mode'),
88+
_('Strategy to reconcile leap seconds in UTC with solar time.'));
89+
o.value('', _('(default)'))
90+
o.value('system')
91+
o.value('step')
92+
o.value('slew')
93+
o.value('ignore')
94+
o.optional = true;
95+
96+
// Smoothing
97+
s = m.section(form.NamedSection, 'smoothtime', 'smoothtime', _('Smoothing'),
98+
_('Use only when the clients are not configured to poll another NTP server also, because they could reject this server as a falseticker or fail to select a source completely.'));
99+
s.anonymous = true;
100+
s.addremove = true;
101+
102+
o = s.option(form.Value, 'maxppm', _('Max PPM'),
103+
_('Maximum frequency offset of the smoothed time to the tracked NTP time (in ppm).'));
104+
o.datatype = 'uinteger';
105+
o.placeholder = '400';
106+
o.optional = false;
107+
108+
o = s.option(form.Value, 'maxwander', _('Max wander'),
109+
_('Maximum rate at which the frequency offset is allowed to change (in ppm per second).'));
110+
o.datatype = 'float';
111+
o.placeholder = '0.01';
112+
o.optional = false;
113+
114+
o = s.option(form.Flag, 'leaponly', _('Leap seconds only'),
115+
_('Only leap seconds are smoothed out; ignore normal offset and frequency changes.'));
116+
o.default = o.disabled;
117+
118+
// Server entries
119+
s = m.section(form.TypedSection, 'server', _('Server'),
120+
_('Remote NTP servers for your chronyd'));
121+
s.anonymous = true;
122+
s.addremove = true;
123+
insertTypedSectionOptions(m, s, o, 'server');
124+
125+
// Pool entries
126+
s = m.section(form.TypedSection, 'pool', _('Pool'),
127+
_('Specifies a pool of NTP servers rather than a single NTP server.') + '<br/>' +
128+
_('The pool name is expected to resolve to multiple addresses which might change over time.'));
129+
s.anonymous = true;
130+
s.addremove = true;
131+
insertTypedSectionOptions(m, s, o, 'pool');
132+
133+
// Peer entries
134+
s = m.section(form.TypedSection, 'peer', _('Peer'),
135+
_('Specifies a symmetric association with an NTP peer.') + '<br/>' +
136+
_('A single symmetric association allows the peers to be both servers and clients to each other.'));
137+
s.anonymous = true;
138+
s.addremove = true;
139+
insertTypedSectionOptions(m, s, o, 'peer');
140+
141+
// Servers assigned (to us) via DHCP
142+
s = m.section(form.NamedSection, 'dhcp_ntp_server', 'dhcp_ntp_server', _('DHCP(v6)'),
143+
_('Options for servers provided to this host via DHCP(v6) (via the WAN for example).'));
144+
s.anonymous = true;
145+
insertTypedSectionOptions(m, s, o, 'dhcp_ntp_server');
146+
147+
148+
return m.render();
149+
}
150+
});
151+
152+
function insertTypedSectionOptions(m, s, o, type) {
153+
154+
o = s.option(form.Flag, 'disabled', _('Disabled'));
155+
o.default = o.disabled; // disabled default is disabled i.e., enabled
156+
157+
if (type != 'dhcp_ntp_server') {
158+
o = s.option(form.Value, 'hostname', _('Hostname'));
159+
o.optional = false;
160+
o.depends('disabled', '0');
161+
}
162+
163+
if (type != 'peer') {
164+
o = s.option(form.Flag, 'iburst', _('iburst'));
165+
o.rmempty = true;
166+
o.default = o.disabled
167+
o.depends('disabled', '0');
168+
169+
o = s.option(form.Flag, 'nts', _('NTS'));
170+
o.rmempty = true;
171+
o.default = o.disabled
172+
o.depends('disabled', '0');
173+
}
174+
175+
o = s.option(form.Flag, 'prefer', _('Prefer'));
176+
o.default = o.disabled;
177+
178+
o = s.option(form.Flag, 'xleave', _('Interleave'));
179+
o.default = o.disabled;
180+
181+
o = s.option(form.RangeSliderValue, 'minpoll', _('Minimum poll'),
182+
_('(Log_2 i.e. y=2^x) interval between readings of the NIC clock.'));
183+
o.min = -7;
184+
o.max = 24;
185+
o.step = 1;
186+
o.default = 4;
187+
o.calculate = (val) => {
188+
return 2**Number(val);
189+
};
190+
o.calcunits = _('seconds')
191+
o.depends('disabled', '0');
192+
193+
o = s.option(form.RangeSliderValue, 'maxpoll', _('Maximum poll'),
194+
_('(Log_2 i.e. y=2^x) interval between readings of the NIC clock.'));
195+
o.min = -7;
196+
o.max = 24;
197+
o.step = 1;
198+
o.default = 4;
199+
o.calculate = (val) => {
200+
return 2**Number(val);
201+
};
202+
o.calcunits = _('seconds');
203+
o.depends('disabled', '0');
204+
205+
o = s.option(form.Value, 'mindelay', _('Minimum delay'),
206+
_('A fixed round-trip delay in seconds to be used instead of that of the previous measurements.') + '<br/>' +
207+
_('Exponential and decimal notation are allowed.'));
208+
o.placeholder = '1e-4'
209+
o.depends('disabled', '0');
210+
211+
o = s.option(form.Value, 'maxdelay', _('Maximum delay'),
212+
_('A fixed round-trip delay in seconds to be used instead of that of the previous measurements.') + '<br/>' +
213+
_('Exponential and decimal notation are allowed.'));
214+
o.placeholder = '3';
215+
o.depends('disabled', '0');
216+
217+
o = s.option(form.RangeSliderValue, 'minsamples', _('Minimum samples'));
218+
o.min = 4;
219+
o.max = 64;
220+
o.step = 1;
221+
o.default = 6;
222+
o.depends('disabled', '0');
223+
224+
o = s.option(form.RangeSliderValue, 'maxsamples', _('Maximum samples'),
225+
_('Number of samples that chronyd should keep for each source.'));
226+
o.min = 4;
227+
o.max = 64;
228+
o.step = 1;
229+
o.default = 6;
230+
o.depends('disabled', '0');
231+
232+
}

0 commit comments

Comments
 (0)