luci-app-chrony: Add chrony
authorPaul Donald <[email protected]>
Fri, 25 Apr 2025 02:21:38 +0000 (04:21 +0200)
committerPaul Donald <[email protected]>
Tue, 24 Jun 2025 13:46:33 +0000 (15:46 +0200)
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]>
applications/luci-app-chrony/Makefile [new file with mode: 0644]
applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js [new file with mode: 0644]
applications/luci-app-chrony/po/templates/chrony.pot [new file with mode: 0644]
applications/luci-app-chrony/root/usr/share/luci/menu.d/luci-app-chrony.json [new file with mode: 0644]
applications/luci-app-chrony/root/usr/share/rpcd/acl.d/luci-app-chrony.json [new file with mode: 0644]

diff --git a/applications/luci-app-chrony/Makefile b/applications/luci-app-chrony/Makefile
new file mode 100644 (file)
index 0000000..1154215
--- /dev/null
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2025 OpenWrt.org
+#
+# This is free software, licensed under Apache-2.0.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for chrony
+LUCI_DEPENDS:=+luci-base +chrony-nts
+
+PKG_LICENSE:=Apache-2.0
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js b/applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js
new file mode 100644 (file)
index 0000000..4dcb152
--- /dev/null
@@ -0,0 +1,232 @@
+'use strict';
+'require form';
+'require fs';
+'require uci';
+'require view';
+'require tools.widgets as widgets';
+
+return view.extend({
+
+       render(data) {
+               const docUrl = 'https://chrony-project.org/documentation.html';
+               let m, s, o;
+
+               m = new form.Map('chrony', _('Chrony NTP/NTS daemon'),
+                       '%s'.format('<a id="docUrl" href="%s" target="_blank" rel="noreferrer">%s</a>'
+                       .format(docUrl, _('Documentation'))));
+
+               // Interface
+               s = m.section(form.NamedSection, 'allow', 'allow', _('Allow'), _('An allow range permits access for chronyc from specific IPs to chronyd.') + '<br/>' +
+                       _('Delete this section to allow all local IPs.'));
+               s.anonymous = true;
+               s.addremove = true;
+
+               o = s.option(widgets.NetworkSelect, 'interface', _('Interface'),
+                       _('Choose IP ranges from this interface to set them as allowed ranges.') + '<br/>' +
+                       _('Choose a wan interface to allow from all IPs.') + '<br/>' +
+                       _('Additional firewall configuration is required if you intend wan access.'));
+               o.nocreate = true;
+               o.rmempty = false;
+
+               // NTS
+               s = m.section(form.NamedSection, 'nts', 'nts', _('Network Time Security (NTS)'));
+               s.anonymous = true;
+               s.addremove = true;
+
+               o = s.option(form.Flag, 'rtccheck', _('RTC Check'),
+                       _('Check for the presence of %s.'.format('<code>/dev/rtc0</code>'), 'Check for RTC character device') + '<br/>' +
+                       _('Disables certificate time checks via %s if RTC is absent.'.format('<code>nocerttimecheck</code>') ) );
+               o.default = o.disabled;
+
+               o = s.option(form.Flag, 'systemcerts', _('Use system CA bundle'));
+               o.default = o.enabled;
+
+               o = s.option(form.FileUpload, 'trustedcerts', _('Trusted certificates'));
+               o.optional = true;
+               o.root_directory = '/etc';
+
+               // Stepping
+               s = m.section(form.NamedSection, 'makestep', 'makestep', _('Stepping'), 
+                       _('Corrects the system clock by stepping immediately when it is so far adrift that the slewing process would take a very long time.'));
+               s.anonymous = true;
+               s.addremove = true;
+               s.singular = true;
+
+               o = s.option(form.Value, 'threshold', _('Trigger Amount Threshold'),
+                       _('Seconds float value.'));
+               o.datatype = 'float';
+               o.optional = true;
+
+               o = s.option(form.Value, 'limit', _('Limit'),
+                       _('First x clock updates'));
+               o.datatype = 'integer';
+               o.optional = true;
+
+               // Logging
+               s = m.section(form.NamedSection, 'logging', 'logging', _('Logging'));
+               s.anonymous = true;
+               s.addremove = true;
+
+               o = s.option(form.Value, 'logchange', _('Log any change more than'),
+                       _('Seconds threshold for the adjustment of the system clock that will generate a syslog message.'));
+               o.datatype = 'float';
+               o.placeholder = '1';
+               o.optional = true;
+
+               // System Clock
+               s = m.section(form.NamedSection, 'systemclock', 'systemclock', _('System Clock'));
+               s.anonymous = true;
+               s.addremove = true;
+
+               o = s.option(form.Value, 'precision', _('Precision'),
+                       _('Precision of the system clock (in seconds).'));
+               o.datatype = 'string';
+               o.placeholder = _('8e-6 (8 microseconds)');
+               o.optional = true;
+
+               o = s.option(form.ListValue, 'leapsecmode', _('Leap second mode'),
+                       _('Strategy to reconcile leap seconds in UTC with solar time.'));
+               o.value('', _('(default)'))
+               o.value('system')
+               o.value('step')
+               o.value('slew')
+               o.value('ignore')
+               o.optional = true;
+
+               // Smoothing
+               s = m.section(form.NamedSection, 'smoothtime', 'smoothtime', _('Smoothing'),
+                       _('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.'));
+               s.anonymous = true;
+               s.addremove = true;
+
+               o = s.option(form.Value, 'maxppm', _('Max PPM'),
+                       _('Maximum frequency offset of the smoothed time to the tracked NTP time (in ppm).'));
+               o.datatype = 'uinteger';
+               o.placeholder = '400';
+               o.optional = false;
+
+               o = s.option(form.Value, 'maxwander', _('Max wander'),
+                       _('Maximum rate at which the frequency offset is allowed to change (in ppm per second).'));
+               o.datatype = 'float';
+               o.placeholder = '0.01';
+               o.optional = false;
+
+               o = s.option(form.Flag, 'leaponly', _('Leap seconds only'),
+                       _('Only leap seconds are smoothed out; ignore normal offset and frequency changes.'));
+               o.default = o.disabled;
+
+               // Server entries
+               s = m.section(form.TypedSection, 'server', _('Server'),
+                       _('Remote NTP servers for your chronyd'));
+               s.anonymous = true;
+               s.addremove = true;
+               insertTypedSectionOptions(m, s, o, 'server');
+
+               // Pool entries
+               s = m.section(form.TypedSection, 'pool', _('Pool'),
+                       _('Specifies a pool of NTP servers rather than a single NTP server.') + '<br/>' + 
+                       _('The pool name is expected to resolve to multiple addresses which might change over time.'));
+               s.anonymous = true;
+               s.addremove = true;
+               insertTypedSectionOptions(m, s, o, 'pool');
+
+               // Peer entries
+               s = m.section(form.TypedSection, 'peer', _('Peer'),
+                       _('Specifies a symmetric association with an NTP peer.') + '<br/>' +
+                       _('A single symmetric association allows the peers to be both servers and clients to each other.'));
+               s.anonymous = true;
+               s.addremove = true;
+               insertTypedSectionOptions(m, s, o, 'peer');
+
+               // Servers assigned (to us) via DHCP
+               s = m.section(form.NamedSection, 'dhcp_ntp_server', 'dhcp_ntp_server', _('DHCP(v6)'),
+                       _('Options for servers provided to this host via DHCP(v6) (via the WAN for example).'));
+               s.anonymous = true;
+               insertTypedSectionOptions(m, s, o, 'dhcp_ntp_server');
+
+
+               return m.render();
+       }
+});
+
+function insertTypedSectionOptions(m, s, o, type) {
+
+               o = s.option(form.Flag, 'disabled', _('Disabled'));
+               o.default = o.disabled; // disabled default is disabled i.e., enabled
+
+               if (type != 'dhcp_ntp_server') {
+                       o = s.option(form.Value, 'hostname', _('Hostname'));
+                       o.optional = false;
+                       o.depends('disabled', '0');
+               }
+
+               if (type != 'peer') {
+                       o = s.option(form.Flag, 'iburst', _('iburst'));
+                       o.rmempty = true;
+                       o.default = o.disabled
+                       o.depends('disabled', '0');
+
+                       o = s.option(form.Flag, 'nts', _('NTS'));
+                       o.rmempty = true;
+                       o.default = o.disabled
+                       o.depends('disabled', '0');
+               }
+
+               o = s.option(form.Flag, 'prefer', _('Prefer'));
+               o.default = o.disabled;
+
+               o = s.option(form.Flag, 'xleave', _('Interleave'));
+               o.default = o.disabled;
+
+               o = s.option(form.RangeSliderValue, 'minpoll', _('Minimum poll'),
+                       _('(Log_2 i.e. y=2^x) interval between readings of the NIC clock.'));
+               o.min = -7;
+               o.max = 24;
+               o.step = 1;
+               o.default = 4;
+               o.calculate = (val) => {
+                       return 2**Number(val);
+               };
+               o.calcunits = _('seconds')
+               o.depends('disabled', '0');
+
+               o = s.option(form.RangeSliderValue, 'maxpoll', _('Maximum poll'),
+                       _('(Log_2 i.e. y=2^x) interval between readings of the NIC clock.'));
+               o.min = -7;
+               o.max = 24;
+               o.step = 1;
+               o.default = 4;
+               o.calculate = (val) => {
+                       return 2**Number(val);
+               };
+               o.calcunits = _('seconds');
+               o.depends('disabled', '0');
+
+               o = s.option(form.Value, 'mindelay', _('Minimum delay'),
+                       _('A fixed round-trip delay in seconds to be used instead of that of the previous measurements.') + '<br/>' + 
+                       _('Exponential and decimal notation are allowed.'));
+               o.placeholder = '1e-4'
+               o.depends('disabled', '0');
+
+               o = s.option(form.Value, 'maxdelay', _('Maximum delay'),
+                       _('A fixed round-trip delay in seconds to be used instead of that of the previous measurements.') + '<br/>' + 
+                       _('Exponential and decimal notation are allowed.'));
+               o.placeholder = '3';
+               o.depends('disabled', '0');
+
+               o = s.option(form.RangeSliderValue, 'minsamples', _('Minimum samples'));
+               o.min = 4;
+               o.max = 64;
+               o.step = 1;
+               o.default = 6;
+               o.depends('disabled', '0');
+
+               o = s.option(form.RangeSliderValue, 'maxsamples', _('Maximum samples'),
+                       _('Number of samples that chronyd should keep for each source.'));
+               o.min = 4;
+               o.max = 64;
+               o.step = 1;
+               o.default = 6;
+               o.depends('disabled', '0');
+
+}
diff --git a/applications/luci-app-chrony/po/templates/chrony.pot b/applications/luci-app-chrony/po/templates/chrony.pot
new file mode 100644 (file)
index 0000000..0178bfc
--- /dev/null
@@ -0,0 +1,300 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:183
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:195
+msgid "(Log_2 i.e. y=2^x) interval between readings of the NIC clock."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:90
+msgid "(default)"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:85
+msgid "8e-6 (8 microseconds)"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:207
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:213
+msgid ""
+"A fixed round-trip delay in seconds to be used instead of that of the "
+"previous measurements."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:137
+msgid ""
+"A single symmetric association allows the peers to be both servers and "
+"clients to each other."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:28
+msgid "Additional firewall configuration is required if you intend wan access."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:20
+msgid "Allow"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:20
+msgid "An allow range permits access for chronyc from specific IPs to chronyd."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:38
+msgctxt "Check for RTC character device"
+msgid "Check for the presence of %s."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:26
+msgid "Choose IP ranges from this interface to set them as allowed ranges."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:27
+msgid "Choose a wan interface to allow from all IPs."
+msgstr ""
+
+#: applications/luci-app-chrony/root/usr/share/luci/menu.d/luci-app-chrony.json:3
+msgid "Chrony"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:15
+msgid "Chrony NTP/NTS daemon"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:51
+msgid ""
+"Corrects the system clock by stepping immediately when it is so far adrift "
+"that the slewing process would take a very long time."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:143
+msgid "DHCP(v6)"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:21
+msgid "Delete this section to allow all local IPs."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:155
+msgid "Disabled"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:39
+msgid "Disables certificate time checks via %s if RTC is absent."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:17
+msgid "Documentation"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:208
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:214
+msgid "Exponential and decimal notation are allowed."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:62
+msgid "First x clock updates"
+msgstr ""
+
+#: applications/luci-app-chrony/root/usr/share/rpcd/acl.d/luci-app-chrony.json:3
+msgid "Grant UCI access for luci-app-chrony"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:159
+msgid "Hostname"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:25
+msgid "Interface"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:179
+msgid "Interleave"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:88
+msgid "Leap second mode"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:115
+msgid "Leap seconds only"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:61
+msgid "Limit"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:71
+msgid "Log any change more than"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:67
+msgid "Logging"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:103
+msgid "Max PPM"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:109
+msgid "Max wander"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:212
+msgid "Maximum delay"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:104
+msgid ""
+"Maximum frequency offset of the smoothed time to the tracked NTP time (in "
+"ppm)."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:194
+msgid "Maximum poll"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:110
+msgid ""
+"Maximum rate at which the frequency offset is allowed to change (in ppm per "
+"second)."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:225
+msgid "Maximum samples"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:206
+msgid "Minimum delay"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:182
+msgid "Minimum poll"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:218
+msgid "Minimum samples"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:170
+msgid "NTS"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:33
+msgid "Network Time Security (NTS)"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:226
+msgid "Number of samples that chronyd should keep for each source."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:116
+msgid ""
+"Only leap seconds are smoothed out; ignore normal offset and frequency "
+"changes."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:144
+msgid ""
+"Options for servers provided to this host via DHCP(v6) (via the WAN for "
+"example)."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:135
+msgid "Peer"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:127
+msgid "Pool"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:82
+msgid "Precision"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:83
+msgid "Precision of the system clock (in seconds)."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:176
+msgid "Prefer"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:37
+msgid "RTC Check"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:121
+msgid "Remote NTP servers for your chronyd"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:57
+msgid "Seconds float value."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:72
+msgid ""
+"Seconds threshold for the adjustment of the system clock that will generate "
+"a syslog message."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:120
+msgid "Server"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:98
+msgid "Smoothing"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:128
+msgid "Specifies a pool of NTP servers rather than a single NTP server."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:136
+msgid "Specifies a symmetric association with an NTP peer."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:50
+msgid "Stepping"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:89
+msgid "Strategy to reconcile leap seconds in UTC with solar time."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:78
+msgid "System Clock"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:129
+msgid ""
+"The pool name is expected to resolve to multiple addresses which might "
+"change over time."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:56
+msgid "Trigger Amount Threshold"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:45
+msgid "Trusted certificates"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:99
+msgid ""
+"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."
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:42
+msgid "Use system CA bundle"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:165
+msgid "iburst"
+msgstr ""
+
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:191
+#: applications/luci-app-chrony/htdocs/luci-static/resources/view/chrony.js:203
+msgid "seconds"
+msgstr ""
diff --git a/applications/luci-app-chrony/root/usr/share/luci/menu.d/luci-app-chrony.json b/applications/luci-app-chrony/root/usr/share/luci/menu.d/luci-app-chrony.json
new file mode 100644 (file)
index 0000000..87d2cfe
--- /dev/null
@@ -0,0 +1,13 @@
+{
+       "admin/services/chrony": {
+               "title": "Chrony",
+               "order": 30,
+               "action": {
+                       "type": "view",
+                       "path": "chrony"
+               },
+               "depends": {
+                       "acl": [ "luci-app-chrony" ]
+               }
+       }
+}
diff --git a/applications/luci-app-chrony/root/usr/share/rpcd/acl.d/luci-app-chrony.json b/applications/luci-app-chrony/root/usr/share/rpcd/acl.d/luci-app-chrony.json
new file mode 100644 (file)
index 0000000..397f18f
--- /dev/null
@@ -0,0 +1,11 @@
+{
+       "luci-app-chrony": {
+               "description": "Grant UCI access for luci-app-chrony",
+               "read": {
+                       "uci": [ "chrony" ]
+               },
+               "write": {
+                       "uci": [ "chrony" ]
+               }
+       }
+}