From ff945bb6ce31c955228ef0a2c38fb365bc3d38b7 Mon Sep 17 00:00:00 2001 From: bergware Date: Sat, 14 Dec 2024 15:37:28 +0100 Subject: [PATCH] Date and Time enhancements - Support Precision Time Protocol (PTP) - time accuracy in nanoseconds - PTP requires a local time server which supports the PTP protocol - Show only the applicable fields for the selected time sync value (animated) - Add 'hints' to help the user in configuration - Do not restart time service when only date/time format is updated Note for Tom: to make PTP work the package "linuxptp-4.2-x86_64-1_BW.txz" needs to be installed with stock Unraid --- emhttp/plugins/dynamix/DateTime.page | 250 ++++++++++++++---- .../plugins/dynamix/include/DashboardApps.php | 58 ++-- .../plugins/dynamix/include/StartStopPTP.php | 4 + emhttp/plugins/dynamix/sheets/DateTime.css | 4 +- etc/rc.d/rc.M | 7 +- etc/rc.d/rc.library.source | 10 +- etc/rc.d/rc.ntpd | 43 +-- etc/rc.d/rc.ptpd | 130 +++++++++ 8 files changed, 416 insertions(+), 90 deletions(-) create mode 100644 emhttp/plugins/dynamix/include/StartStopPTP.php create mode 100644 etc/rc.d/rc.ptpd diff --git a/emhttp/plugins/dynamix/DateTime.page b/emhttp/plugins/dynamix/DateTime.page index 3c1c13f5a..212de12b3 100644 --- a/emhttp/plugins/dynamix/DateTime.page +++ b/emhttp/plugins/dynamix/DateTime.page @@ -4,8 +4,8 @@ Icon="icon-clock" Tag="clock-o" --- -
+ _(Current date and time)_: : _(Date format)_: : _(Time format)_: : _(Time zone)_: : :timezone_help: -_(Use NTP)_: -: + + + -:use_ntp_help: - +
_(NTP interval)_: -: _(Use DEFAULT setting when public NTP servers are defined)_ +: + _(Use DEFAULT setting when public NTP servers are defined)_ _(NTP server)_ 1: : - -:ntp_server1_help: + _(Input a NTP server name, NTP pool name or IP address)_ _(NTP server)_ 2: : -:ntp_server2_help: - _(NTP server)_ 3: : -:ntp_server3_help: - _(NTP server)_ 4: : -:ntp_server4_help: +
+
+_(PTP profile)_: +: PTPv2 (IEEE 1588) +_(PTP transport)_: +: + +_(PTP mode)_: +: + +
+_(PTP server)_ 1: +: + _(Input a IPv4 address)__(Input a IPv6 address)__(Input a MAC address)_ + +_(PTP server)_ 2: +: + +_(PTP server)_ 3: +: + +_(PTP server)_ 4: +: + +
+_(PTP interface)_: +: + +_(PTP clock)_: +: + +
+
_(New date and time)_: -: "> +: "> + _(Input the correct date and time manually)_ :current_time_help: +
  -: +:
diff --git a/emhttp/plugins/dynamix/include/DashboardApps.php b/emhttp/plugins/dynamix/include/DashboardApps.php index 871d29abb..d2ac16a03 100644 --- a/emhttp/plugins/dynamix/include/DashboardApps.php +++ b/emhttp/plugins/dynamix/include/DashboardApps.php @@ -1,6 +1,6 @@ 0) { $wsport = $lv->domain_get_ws_port($res); - $vmrcprotocol = $lv->domain_get_vmrc_protocol($res) ; + $vmrcprotocol = $lv->domain_get_vmrc_protocol($res); if ($vmrcprotocol == "vnc") $vmrcscale = "&resize=scale"; else $vmrcscale = ""; - $vmrcurl = autov('/plugins/dynamix.vm.manager/'.$vmrcprotocol.'.html',true).$vmrcscale.'&autoconnect=true&host=' . $_SERVER['HTTP_HOST'] ; - if ($vmrcprotocol == "spice") $vmrcurl .= '&vmname='. urlencode($vm) . '&port=/wsproxy/'.$vmrcport.'/' ; else $vmrcurl .= '&port=&path=/wsproxy/' . $wsport . '/'; + $vmrcurl = autov('/plugins/dynamix.vm.manager/'.$vmrcprotocol.'.html',true).$vmrcscale.'&autoconnect=true&host=' . $_SERVER['HTTP_HOST']; + if ($vmrcprotocol == "spice") $vmrcurl .= '&vmname='. urlencode($vm) . '&port=/wsproxy/'.$vmrcport.'/'; else $vmrcurl .= '&port=&path=/wsproxy/' . $wsport . '/'; } elseif ($vmrcport == -1 || $autoport) { - $vmrcprotocol = $lv->domain_get_vmrc_protocol($res) ; - if ($autoport == "yes") $auto = "auto" ; else $auto="manual" ; + $vmrcprotocol = $lv->domain_get_vmrc_protocol($res); + $auto = ($autoport == "yes") ? "auto" : "manual"; } elseif (!empty($arrConfig['gpu'])) { $arrValidGPUDevices = getValidGPUDevices(); foreach ($arrConfig['gpu'] as $arrGPU) { foreach ($arrValidGPUDevices as $arrDev) { if ($arrGPU['id'] == $arrDev['id']) { if (count(array_filter($arrValidGPUDevices, function($v) use ($arrDev) { return $v['name'] == $arrDev['name']; })) > 1) { - $vmrcprotocol = "VGA" ; + $vmrcprotocol = "VGA"; } else { - $vmrcprotocol = "VGA" ; + $vmrcprotocol = "VGA"; } } } } - } + } $template = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name'); if (empty($template)) $template = 'Custom'; $log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : ''); - if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ; - if (!isset($domain_cfg["RDPOPT"])) $vmrcconsole .= ";no" ; else $vmrcconsole .= ";".$domain_cfg["RDPOPT"] ; + if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web"; else $vmrcconsole = $domain_cfg["CONSOLE"]; + if (!isset($domain_cfg["RDPOPT"])) $vmrcconsole .= ";no"; else $vmrcconsole .= ";".$domain_cfg["RDPOPT"]; $WebUI = html_entity_decode($arrConfig["template"]["webui"]); $menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI))); $icon = $lv->domain_get_icon_url($res); @@ -153,7 +181,7 @@ if ($_POST['vms']) { #Build VM Usage array. $menuusage = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,true,addslashes(str_replace('"',"'",$WebUI))); $vmusagehtml[] = "$image$vm
"._($status)."
"; - $vmusagehtml[] = "

"._("Loading")."...."; + $vmusagehtml[] = "

"._("Loading")."...."; $vmusagehtml[] = "
"._("Loading")."...."; $vmusagehtml[] = "
"._("Loading")."...."; $vmusagehtml[] = "
"._("Loading")."...."; diff --git a/emhttp/plugins/dynamix/include/StartStopPTP.php b/emhttp/plugins/dynamix/include/StartStopPTP.php new file mode 100644 index 000000000..553a8a0fe --- /dev/null +++ b/emhttp/plugins/dynamix/include/StartStopPTP.php @@ -0,0 +1,4 @@ + diff --git a/emhttp/plugins/dynamix/sheets/DateTime.css b/emhttp/plugins/dynamix/sheets/DateTime.css index b2eb5cbbe..b3012f111 100644 --- a/emhttp/plugins/dynamix/sheets/DateTime.css +++ b/emhttp/plugins/dynamix/sheets/DateTime.css @@ -1 +1,3 @@ -span.ntp{margin-left:40px} +div.extra,span.ipv4,span.ipv6,span.mac{display:none} +select,input[type=text]{margin-right:40px} +select[name=timeZone]{max-width:166px} diff --git a/etc/rc.d/rc.M b/etc/rc.d/rc.M index 2ae1f5dc8..632ca2e8a 100755 --- a/etc/rc.d/rc.M +++ b/etc/rc.d/rc.M @@ -11,7 +11,7 @@ # Heavily modified by Patrick Volkerding # # LimeTech - modified for Unraid OS -# Bergware - modified for Unraid OS, October 2023 +# Bergware - modified for Unraid OS, December 2024 # run & log functions . /etc/rc.d/rc.runlog @@ -108,6 +108,11 @@ fi # Mount any additional filesystem types that haven't already been mounted: mount -a -v 2>/dev/null | grep -v -e "already mounted" -e "ignored" | cut -f 1 -d : | tr -d ' ' | while read DEV; do mount | grep "$DEV "; done +# Start the Precision Time Protocol daemon: +if [[ -x /etc/rc.d/rc.ptpd ]]; then + /etc/rc.d/rc.ptpd start +fi + # Start the Network Time Protocol daemon: if [[ -x /etc/rc.d/rc.ntpd ]]; then /etc/rc.d/rc.ntpd start diff --git a/etc/rc.d/rc.library.source b/etc/rc.d/rc.library.source index a37043f06..3dc4bfdbf 100644 --- a/etc/rc.d/rc.library.source +++ b/etc/rc.d/rc.library.source @@ -4,12 +4,20 @@ # # Library used by nfsd, ntpd, rpc, samba, nginx, sshd, avahidaemon, show_interfaces # -# Bergware - created for Unraid OS, December 2023 +# Bergware - created for Unraid OS, December 2024 WIREGUARD="/etc/wireguard" NETWORK_INI="/var/local/emhttp/network.ini" NETWORK_EXTRA="/boot/config/network-extra.cfg" +var(){ + if [[ $# -eq 3 ]]; then + [[ -r "$3" ]] && sed -n "/^\[$1\]\$/,/^\[/p" "$3" | grep -Pom1 "^$2=\"\K[^\"]+" + elif [[ $# -eq 2 ]]; then + [[ -r "$2" ]] && grep -Pom1 "^$1=\"\K[^\"]+" "$2" + fi +} + ipv(){ local t=${1//[^:]} [[ ${#t} -le 1 ]] && echo 4 || echo 6 diff --git a/etc/rc.d/rc.ntpd b/etc/rc.d/rc.ntpd index deedfedc9..532c4a033 100755 --- a/etc/rc.d/rc.ntpd +++ b/etc/rc.d/rc.ntpd @@ -5,15 +5,15 @@ # Start/stop/restart ntpd. # # LimeTech - modified to initialize ntp.conf file from config -# Bergware - modified for Unraid OS, October 2023 +# Bergware - modified for Unraid OS, December 2024 DAEMON="NTP server daemon" CALLER="ntp" NTPD="/usr/sbin/ntpd" OPTIONS="-g -u ntp:ntp" CONF="/etc/ntp.conf" +CFG="/boot/config/plugins/dynamix/dynamix.cfg" IDENT="/boot/config/ident.cfg" -CONFIG="/boot/config/plugins/dynamix/dynamix.cfg" # run & log functions . /etc/rc.d/rc.runlog @@ -39,24 +39,25 @@ ntpd_build(){ echo "interface listen $NET" >>$CONF done fi + NTP_POLL=$(var NTP POLL $CFG) # ntp poll interval may be adjusted to predefined values - if [[ -f $CONFIG ]]; then - NTP_POLL=$(grep -Po '^ntppoll="\K[^"]+' $CONFIG) - if [[ -n $NTP_POLL ]]; then - MINPOLL="minpoll $NTP_POLL" - MAXPOLL="maxpoll $NTP_POLL" - fi + if [[ -n $NTP_POLL ]]; then + MINPOLL="minpoll $NTP_POLL" + MAXPOLL="maxpoll $NTP_POLL" + fi + # allow ntp to use ptp as sync source + if [[ $(var PTP SYNC $CFG) != yes ]]; then + # add configured ntp servers or pools + for n in {1..4}; do + NTP="NTP_SERVER$n" + if [[ -n ${!NTP} ]]; then + # use either server or pool peers depending on remote ntp name + # pools use a round-robin mechanism to get a server out of the pool + [[ ${!NTP} =~ "pool" ]] && PEER=pool || PEER=server + echo "$PEER ${!NTP} iburst $MINPOLL $MAXPOLL" >>$CONF + fi + done fi - # add configured ntp servers or pools - for n in {1..4}; do - NTP="NTP_SERVER$n" - if [[ -n ${!NTP} ]]; then - # use either server or pool peers depending on remote ntp name - # pools use a round-robin mechanism to get a server out of the pool - [[ ${!NTP} =~ "pool" ]] && PEER=pool || PEER=server - echo "$PEER ${!NTP} iburst $MINPOLL $MAXPOLL" >>$CONF - fi - done } ntpd_start(){ @@ -64,7 +65,7 @@ ntpd_start(){ local REPLY # read Unraid settings [[ -r $IDENT ]] && . <(fromdos <$IDENT) - # if ntp not enabled, don't start ntp + # if time sync not enabled, don't start ntp if [[ $USE_NTP != yes ]]; then REPLY="Service not enabled" elif ntpd_running; then @@ -90,7 +91,7 @@ ntpd_stop(){ kill -HUP $(cat /var/run/ntpd.pid) rm -f /var/run/ntpd.pid else - killall --ns $$ -HUP -q ntpd + killall --ns $$ -HUP -q ntpd fi if ! ntpd_running; then REPLY="Stopped"; else REPLY="Failed"; fi fi @@ -107,7 +108,7 @@ ntpd_restart(){ } ntpd_reload(){ - killall --ns $$ -HUP -q ntpd + killall --ns $$ -HUP -q ntpd . <(fromdos <$IDENT) ntpd_build $NTPD $OPTIONS 2>/dev/null diff --git a/etc/rc.d/rc.ptpd b/etc/rc.d/rc.ptpd new file mode 100644 index 000000000..692d7529d --- /dev/null +++ b/etc/rc.d/rc.ptpd @@ -0,0 +1,130 @@ +#!/bin/bash +# +# script: rc.ptpd +# +# Start/stop/restart services ptp4l and phc2sys. +# +# Bergware - created for Unraid OS, December 2024 + +DAEMON="PTP server daemon" +CALLER="ptp" +PTPD="/usr/sbin/ptp4l" +PHC="/usr/sbin/phc2sys" +OPTIONS1="-s -l 5 -f /etc/ptp4l.conf" +OPTIONS2="-a -r -l 5" +CONF="/etc/ptp4l.conf" +CFG="/boot/config/plugins/dynamix/dynamix.cfg" +IDENT="/boot/config/ident.cfg" + +# run & log functions +. /etc/rc.d/rc.runlog + +# library functions +. /etc/rc.d/rc.library.source + +ptpd_running(){ + sleep 0.1 + [[ $(pgrep -cf $PTPD) -gt 0 ]] +} + +ptpd_build(){ + echo "[global]" >$CONF + TRANSPORT=$(var PTP TRANSPORT $CFG) + echo "network_transport $TRANSPORT" >>$CONF + echo "time_stamping $(var PTP CLOCK $CFG)" >>$CONF + [[ $(var PTP MODE $CFG) == unicast ]] && UNICAST=1 || UNICAST=0 + if [[ $UNICAST == 1 ]]; then + echo "unicast_req_duration 60" >>$CONF + echo "" >>$CONF + echo "[unicast_master_table]" >>$CONF + echo "table_id 1" >>$CONF + echo "logQueryInterval 1" >>$CONF + for n in {1..4}; do + PTP=$(var PTP "SERVER$n" $CFG) + [[ -n $PTP ]] && echo "$TRANSPORT $PTP" >>$CONF + done + fi + echo "" >>$CONF + echo "[$(var PTP PORT $CFG)]" >>$CONF + [[ $UNICAST == 1 ]] && echo "unicast_master_table 1" >>$CONF +} + +ptpd_start(){ + log "Starting $DAEMON..." + local REPLY + # read Unraid settings + [[ -r $IDENT ]] && . <(fromdos <$IDENT) + # if time sync not enabled, don't start ptp + if [[ $USE_NTP != yes ]]; then + REPLY="Service not enabled" + elif [[ $(var PTP SYNC $CFG) == yes || $FORCE == yes ]]; then + if ptpd_running; then + REPLY="Already started" + else + # generate our config file + ptpd_build + $PTPD $OPTIONS1 &>/dev/null & + if ptpd_running; then + $PHC $OPTIONS2 &>/dev/null & + REPLY="Started" + else + REPLY="Failed" + fi + fi + else + REPLY="Not started" + fi + log "$DAEMON... $REPLY." +} + +ptpd_stop(){ + log "Stopping $DAEMON..." + local REPLY + if ! ptpd_running; then + REPLY="Already stopped" + else + pkill -f $PTPD 2>/dev/null + pkill -f $PHC 2>/dev/null + if ! ptpd_running; then REPLY="Stopped"; else REPLY="Failed"; fi + fi + log "$DAEMON... $REPLY." +} + +ptpd_restart(){ + log "Restarting $DAEMON..." + ptpd_stop + sleep 1 + ptpd_start +} + +ptpd_status(){ + if ptpd_running; then + echo "$DAEMON is currently running." + else + echo "$DAEMON is not running." + exit 1 + fi +} + +case "$1" in +'start') + ptpd_start + ;; +'forcestart') + FORCE=yes + ptpd_start + ;; +'stop') + ptpd_stop + ;; +'restart') + ptpd_restart + ;; +'status') + ptpd_status + ;; +*) + echo "Usage: $BASENAME start|forcestart|stop|restart|status" + exit 1 +esac +exit 0