#!/bin/bash
#
# setupchrony - Set up chrony
#
# Copyright (C) 2018 International Business Machines
# Eclipse Public License, Version 1.0 (EPL-1.0)
#     <http://www.eclipse.org/legal/epl-v10.html>
#
# 2018-07-11 GONG Jie <gongjie@linux.vnet.ibm.com>
#     - Draft
# 2018-07-13 GONG Jie <gongjie@linux.vnet.ibm.com>
#     - Release to field
#

PATH="/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
if [ -n "$LOGLABEL" ]; then
    log_label=$LOGLABEL
else
    log_label="xcat"
fi
#
# warn_if_bad           Put out warning message(s) if $1 has bad RC.
#
#       $1      0 (pass) or non-zero (fail).
#       $2+     Remaining arguments printed only if the $1 is non-zero.
#
#       Incoming $1 is returned unless it is 0
#
function warn_if_bad()
{
	local -i rc="$1"
	local script="${0##*/}"

	# Ignore if no problems
	[ "${rc}" -eq "0" ] && return 0

	# Broken
	shift
	logger -t $log_label -p local4.info "${script}: $@" >/dev/null 2>&1
	echo "${script}: $@" >&2
	return "${rc}"
}

#
# exit_if_bad           Put out error message(s) if $1 has bad RC.
#
#       $1      0 (pass) or non-zero (fail).
#       $2+     Remaining arguments printed only if the $1 is non-zero.
#
#               Exits with 1 unless $1 is 0
#
function exit_if_bad()
{
	warn_if_bad "$@" || exit 1
	return 0
}

#
# check_executes        Check for executable(s)
#
#       Returns 0 if true.
#       Returns 1 if not.
#
function check_executes()
{
	local cmd
	local all_ok="yes"
	for cmd in "$@"
	do
		if ! type "${cmd}" &>/dev/null
		then
			echo "Command \"${cmd}\" not found." >&2
			all_ok="no"
		fi
	done
	[ "${all_ok}" = "yes" ]
}

#
# check_exec_or_exit    Check for required executables.
#
#       Exits (not returns) if commands listed on command line do not exist.
#
#       Returns 0 if true.
#       Exits with 1 if not.
#
function check_exec_or_exit()
{
	check_executes "$@"
	exit_if_bad "$?" "Above listed required command(s) not found."
	return 0
}

[ "${UID}" -eq "0" ]
exit_if_bad "$?" "Must be run by UID=0. Actual UID=${UID}."

declare -a NTP_SERVERS=()

# Handle command line arguments
while [ "$#" -gt "0" ]
do
        case "$1" in
	"--use-ntpd")
		# Use traditional ntpd
		NTP_BACKEND="ntpd"
		;;
	"--backend")
		# The daemon xCAT::NTP::Backend picked for this cluster, passed by makentp so
		# site.ntpbackend reaches the nodes and not just the management node.
		shift
		case "$1" in
		"chrony"|"chronyd") NTP_BACKEND="chrony" ;;
		"ntp"|"ntpd")       NTP_BACKEND="ntpd" ;;
		*) logger -t $log_label -p local4.warning "setupntp: ignoring unknown --backend '$1'" ;;
		esac
		;;
	*)
		NTP_SERVERS+=($1)
		;;
	esac
	shift
done

if [ "${#NTP_SERVERS[@]}" -eq "0" ]
then
	# Handle xCAT passed environment variables
	case "${NTPSERVERS}" in
	"<xcatmaster>"|"&lt;xcatmaster&gt;"|"")
		NTP_SERVERS=(${MASTER})
		;;
	*)
		OLD_IFS="${IFS}"
		IFS=","
		NTP_SERVERS=(${NTPSERVERS})
		IFS="${OLD_IFS}"
		unset OLD_IFS
		;;
	esac
fi

# The requested backend is a preference, not a guarantee: a node that does not have the
# requested daemon still has to be configured, so use the other one and say so.
if [ "${NTP_BACKEND}" = "ntpd" ]
then
	USE_NTPD="yes"
	if ! check_executes ntpd >/dev/null 2>&1 &&
		check_executes chronyd >/dev/null 2>&1 &&
		check_executes systemctl >/dev/null 2>&1
	then
		USE_NTPD=""
		logger -t $log_label -p local4.warning \
			"setupntp: ntpd requested but ntpd is absent; using chrony"
	fi
else
	check_executes chronyd >/dev/null 2>&1 || USE_NTPD="yes"
	check_executes systemctl >/dev/null 2>&1 || USE_NTPD="yes"
	if [ -n "${USE_NTPD}" ] && [ "${NTP_BACKEND}" = "chrony" ]
	then
		logger -t $log_label -p local4.warning \
			"setupntp: chrony requested but chronyd/systemctl are absent; using ntpd"
	fi
fi

# Both backends discipline the clock, and so does systemd-timesyncd, so it has to go either
# way. Installing chrony or ntpsec usually displaces it already -- both Conflicts: time-daemon --
# but that only helps when an install actually happens; on a re-run, or where the daemon was
# already present, timesyncd is still enabled. This must stay ABOVE the ntpd hand-off below:
# that path execs setupntp.traditional, which never returns and never touches timesyncd.
if check_executes systemctl >/dev/null 2>&1
then
	systemctl stop systemd-timesyncd.service 2>/dev/null
	systemctl disable systemd-timesyncd.service 2>/dev/null
fi

if [ -n "${USE_NTPD}" ]
then
	# Call setupntp.traditional, and pass the parsed ntp servers
	logger -t $log_label -p local4.info "Will call setupntp.traditional"
	export NTPSERVERS="$(IFS=','; echo "${NTP_SERVERS[*]:-pool.ntp.org}")"
	exec "${0%/*}/setupntp.traditional"
	exit 255
fi

# Unset xCAT passed environment variables
unset MASTER
unset NTPSERVERS

check_exec_or_exit cp cat logger grep
# hwclock is NOT required: it only persists the already-stepped clock to the RTC below, and on
# Ubuntu 24.04+ it moved to util-linux-extra, absent on minimal images. Requiring it here aborted
# the whole NTP setup, including the clock step that does not use it.
check_exec_or_exit systemctl timedatectl

systemctl stop ntp.service 2>/dev/null
systemctl disable ntp.service 2>/dev/null
systemctl stop ntpd.service 2>/dev/null
systemctl disable ntpd.service 2>/dev/null

systemctl disable ntp-wait.service 2>/dev/null
systemctl disable ntpdate.service 2>/dev/null

# On Ubuntu 18.04
systemctl stop chrony.service 2>/dev/null
# On RHEL 7, 8
systemctl stop chronyd.service 2>/dev/null

# The system is configured to maintain the RTC in universal time.
timedatectl set-local-rtc 0
warn_if_bad "$?" "Failed to configure the system to maintain the RTC in universal time"

# Synchronize and set the system clock once
logger -t $log_label -p local4.info "Syncing the clock ..."

chronyd -f /dev/null -q "$(
	if [ "${#NTP_SERVERS[@]}" -gt "0" ]
	then
		echo "server ${NTP_SERVERS[0]} iburst"
	else
		echo "pool pool.ntp.org iburst"
	fi
)"

# Set the hardware clock from the system clock, when hwclock is available (optional; see above).
if command -v hwclock >/dev/null 2>&1; then
    rm -f /etc/adjtime
    hwclock --systohc --utc
    warn_if_bad "$?" "Failed to set the hardware clock"
else
    logger -t $log_label -p local4.info "setupntp: hwclock not present; skipping RTC persist (system clock already set via chronyd)"
fi

## On RHEL 8
#CHRONY_USER="chrony"
#CHRONY_GROUP="chrony"
## On Ubuntu 18.04
#CHRONY_USER="_chrony"
#CHRONY_GROUP="_chrony"

CHRONY_CONF="/etc/chrony.conf"
KEY_FILE="/etc/chrony.keys"

if [ -d "/etc/chrony" ]
then
	# Ubuntu 18.04 default
	CHRONY_CONF="/etc/chrony/chrony.conf"
	KEY_FILE="/etc/chrony/chrony.keys"
fi

# Take the best educated guess for the pathname of the chrony drift file
DRIFT_FILE="$(
	for file in /var/lib/chrony/drift /var/lib/chrony/chrony.drift
	do
		if [ -f "${file}" ]
		then
			echo "${file}"
			exit
		fi
	done

	if [ "chrony" = \
		"$(rpm -qf /var/lib/chrony/drift --qf '%{name}' 2>/dev/null)" ]
	then
		echo /var/lib/chrony/drift
		exit
	fi

	for file in /usr/share/chrony/chrony.conf \
		/etc/chrony.conf /etc/chrony/chrony.conf \
		$(grep -a -m 1 -o -P '/([^\x00]+/)*[^\x00]+\.conf' \
			"$(type -p chronyd)" 2>/dev/null)
	do
		if [ -f "${file}" ]
		then
			while read -r key val blah
			do
				if [ "${key}" = "driftfile" -a -n "${val}" ]
				then
					echo "${val}"
					exit
				fi
			done <"${file}"
		fi
	done

	# The default value
	echo /var/lib/chrony/drift
)"

LOG_DIR="/var/log/chrony"

logger -t $log_label -p local4.info "Install: Setup NTP - chrony"
cp "${CHRONY_CONF}" "${CHRONY_CONF}.xcatsave"
cat >"${CHRONY_CONF}" <<EOF
# This chrony configuration file is generated by xCAT

# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
$(
	if [ "${#NTP_SERVERS[@]}" -gt "0" ]
	then
		for n in "${NTP_SERVERS[@]}"
		do
			echo "server ${n} iburst"
		done
	else
		echo "pool pool.ntp.org   iburst maxsources 4"
		echo "pool 0.pool.ntp.org iburst maxsources 4"
		echo "pool 1.pool.ntp.org iburst maxsources 4"
		echo "pool 2.pool.ntp.org iburst maxsources 8"
	fi
)

# Record the rate at which the system clock gains/losses time.
driftfile ${DRIFT_FILE}

# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3

# Enable kernel synchronization of the real-time clock (RTC).
rtcsync

# Enable hardware timestamping on all interfaces that support it.
#hwtimestamp *

# Increase the minimum number of selectable sources required to adjust
# the system clock.
#minsources 2

# Allows access by any node (IPv4 or IPv6).
allow

# Serve time even if not synchronized to a time source.
local stratum 10

# Specify file containing keys for NTP authentication.
keyfile ${KEY_FILE}

# Get TAI-UTC offset and leap seconds from the system tz database.
leapsectz right/UTC

# Specify directory for log files.
logdir ${LOG_DIR}

# Select which information is logged.
log measurements statistics tracking
EOF
exit_if_bad "$?" "Failed to create configuration file for chrony"

systemctl reenable chrony.service 2>/dev/null ||
	systemctl reenable chronyd.service 2>/dev/null
exit_if_bad "$?" "Failed to enable chrony service"

systemctl reload-or-restart chrony.service 2>/dev/null ||
	systemctl reload-or-restart chronyd.service 2>/dev/null
exit_if_bad "$?" "Failed to start chrony service"

logger -t $log_label -p local4.info "NTP setup accomplished!"

exit 0

# vim: filetype=sh
# vim: noautoindent
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# End of file
