La synchronisation du temps est une des bases de l’infrastructure informatique moderne. De l’enregistrement précis à la coordination des systèmes distribués, en passant par les communications sécurisées, il est essentiel de disposer d’une heure système cohérente entre les serveurs et les terminaux. Pour les professionnels de l’informatique et les fournisseurs de services gérés (MSP), il est essentiel de disposer d’une méthode fiable et automatisée pour synchroniser l’heure dans Linux. Cet article présente un script shell efficace conçu pour synchroniser l’heure en utilisant chronyd
ou systemd-timesyncd
, deux services de protocole de temps réseau (NTP) largement supportés sous Linux.
Contexte
De nombreux environnements informatiques, en particulier ceux qui s’appuient sur des plateformes de gestion centralisée et d’automatisation comme NinjaOne, exigent une synchronisation temporelle stricte. Les corrélations de journaux, les versions de fichiers, les jetons d’authentification et les pistes d’audit dépendent tous d’horloges système précises. Bien que les services NTP tels que chronyd
et systemd-timesyncd
s’en chargent souvent en arrière-plan, une mauvaise configuration, des pannes de service ou des incohérences dans l’image du système peuvent perturber la synchronisation.
Ce script shell s’attaque directement à ces problèmes en détectant automatiquement les services de synchronisation disponibles, en s’assurant qu’ils sont correctement configurés, en les démarrant temporairement si nécessaire, en synchronisant l’heure et en établissant des diagnostics détaillés. Il gère également les services de manière fluide, en ramenant les systèmes à leur état antérieur si les services de synchronisation temporelle ont été initialement désactivés.
Le script :
#!/usr/bin/env bash # # Description: Synchronize the time on Linux using the system's network time server. Expects chrony or systemd-timesyncd to be installed and running. # By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. # Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. # Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. # Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. # Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. # Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. # Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. # EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). # # Minimum OS Architecture Supported: Debian 11 (Bullseye)+, Red Hat Enterprise Linux (RHEL) 8+ # # Release Notes: Initial Release # # When run directly without testing, the "__()" function does nothing. test || __() { :; } die() { local _ret="${2:-1}" echo "$1" >&2 exit "${_ret}" } function GetChronyConfigFile() { local _configFile # Check if there is a chrony config folder if [[ -d "/etc/chrony" ]]; then # Check if the config file exists if [[ -f "/etc/chrony/chrony.conf" ]]; then _configFile="/etc/chrony/chrony.conf" fi fi # Check if primary default config file exists, this takes precedence over the chrony folder if [[ -f "/etc/chrony.conf" ]]; then _configFile="/etc/chrony.conf" fi # Get the system's default config file for chronyd _chronySystemDefaultConfigFile=$( # Get man page for chronyd # Looking for a line containing this "The compiled-in default value is /etc/chrony/chrony.conf." man chronyd | # Get the default config file location grep /chrony.conf | grep default | # Get the last item in the line awk '{print $NF}' | # Remove the trailing period sed 's/\.$//' ) # Check if the config file exists if [[ -f "$_chronySystemDefaultConfigFile" ]]; then _configFile="$_chronySystemDefaultConfigFile" fi # Validate that the config file exists if [[ -n "${_configFile}" && -f "$_configFile" ]]; then echo "$_configFile" else echo "[Error] No chrony config file found" exit 1 fi } function GetNtpPoolServers() { local -a _configFiles local -a _pools local -a _servers local -a _timedatectlConfigFiles _timedatectlConfigFiles=( # Last take precedence over previous ones "/usr/lib/systemd/timesyncd.conf.d" "/usr/local/lib/systemd/timesyncd.conf.d" "/run/systemd/timesyncd.conf.d" "/etc/systemd/timesyncd.conf.d" "/usr/lib/systemd/timesyncd.conf" "/usr/local/lib/systemd/timesyncd.conf" "/run/systemd/timesyncd.conf" "/etc/systemd/timesyncd.conf" ) # Get the NTP pool servers for the specified region echo "[Info] Getting NTP pools and servers" if command -v chronyc &>/dev/null; then # Get the NTP pool servers for chrony if [[ -f "${_chronyConfigFile}" ]]; then _pools+=$(grep -E "^pool" "${_chronyConfigFile}" | awk '{print $2}' | sort -u) _servers+=$(grep -E "^server" "${_chronyConfigFile}" | awk '{print $2}' | sort -u) fi elif command -v timedatectl &>/dev/null; then # Get the NTP pool servers for systemd-timesyncd for configFile in "${_timedatectlConfigFiles[@]}"; do if [[ -d "$configFile" ]]; then # If the config file is a directory, check for .conf files inside it for confFile in "$configFile"/*.conf; do if [[ -f "$confFile" ]]; then # Append the contents of the .conf file to the list of config files _pools+=$(grep -E "^NTP=" "$configFile" | awk -F '=' '{print $2}' | sort -u) _pools+=$(grep -E "^FallbackNTP=" "$configFile" | awk -F '=' '{print $2}' | sort -u) fi done elif [[ -f "$configFile" ]]; then _pools+=$(grep -E "^NTP=" "$configFile" | awk -F '=' '{print $2}' | sort -u) _pools+=$(grep -E "^FallbackNTP=" "$configFile" | awk -F '=' '{print $2}' | sort -u) fi done else echo "[Error] chronyc or timedatectl not found" exit 1 fi # Return the list of NTP pools and servers if [[ -n "${_pools}" ]]; then echo "[Info] NTP pools:" echo "$_pools" fi if [[ -n "${_servers}" ]]; then echo "[Info] NTP servers:" echo "$_servers" fi if [[ -z "${_pools}" && -z "${_servers}" ]]; then # If no pools or servers were found echo "[Info] Distribution's default NTP servers are being used." fi } function GetNtpConfiguration() { # Get the NTP configuration for the specified region if command -v chronyc &>/dev/null; then echo "[Info] NTP configuration:" if ! [[ -f "$_chronyConfigFile" ]]; then _chronyConfigFile="$(GetChronyConfigFile)" fi # Get the NTP configuration for chrony grep -E "^server|^pool" "${_chronyConfigFile}" 2>/dev/null if [[ -f "${_chronyConfigFile}" ]]; then grep -E "^sourcedir" "${_chronyConfigFile}" 2>/dev/null | awk '{print $2}' | while read -r _dir; do if [[ -d "$_dir" ]]; then for confFile in "$_dir"/*.conf; do # Check if the file exists before grepping if [[ -f "$confFile" ]]; then grep -E "^server|^pool" <"$confFile" 2>/dev/null fi done fi done grep -E "^confdir" "${_chronyConfigFile}" 2>/dev/null | awk '{print $2}' | while read -r _dir; do if [[ -d "$_dir" ]]; then for confFile in "$_dir"/*.conf; do # Check if the file exists before grepping if [[ -f "$confFile" ]]; then grep -E "^server|^pool" <"$confFile" 2>/dev/null fi done fi done fi elif command -v timedatectl &>/dev/null; then # Get the NTP configuration for systemd-timesyncd if (("$(timedatectl --version | grep systemd | awk '{print $2}')" >= 239)); then # Try to get the NTP configuration echo "[Info] NTP configuration:" timedatectl show-timesync 2>/dev/null else # If it fails then we are running and older version of systemd, then get the status echo "[Warn] systemd version is older than 239, show-timesync is not available." fi else echo "[Error] No NTP configuration found." exit 1 fi } function IsChronySecurityConfigured() { # Check if the keyfile is set in chrony.conf if grep -q "^keyfile" "${_chronyConfigFile}" 2>/dev/null; then # Check if commandkey is set in chrony.conf if grep -q "^commandkey" "${_chronyConfigFile}" 2>/dev/null; then echo "[Info] Keyfile and commandkey are set in ${_chronyConfigFile}" _id=$(grep "^keyfile" "${_chronyConfigFile}" 2>/dev/null | awk '{print $2}') _keyfile=$(grep "^commandkey" "${_chronyConfigFile}" 2>/dev/null | awk '{print $2}') # Check if the keyfile exists if [[ -f "$_keyfile" ]]; then # Check if commandkey exists in keyfile if grep -q "^$_id" "$_keyfile"; then # Keyfile exists and commandkey is set return 0 else # Keyfile exists but commandkey is not set echo "[Error] Keyfile $_keyfile exists but commandkey $_id is not set in it" return 1 fi else # Keyfile does not exist echo "[Error] Keyfile $_keyfile does not exist" return 1 fi else # Keyfile is set but commandkey is not set return 1 fi else # Keyfile is not set return 1 fi } __ begin __ # Check if the script is being run as root. If not, exit with an error message. if [[ $(id -u) -ne 0 ]]; then die "[Error] This script must be run with root permissions. Try running it with sudo or as the system/root user." 1 fi _is_enabled=0 _is_active=0 # Check if time synchronization services are installed if systemctl list-unit-files | grep -q -E "(systemd-timesyncd|chronyd).service"; then echo "[Info] Time synchronization service(s) found:" # Check if systemd-timesyncd or chronyd is running while read -r service; do # Read each service name if systemctl is-enabled --quiet "${service}" 2>/dev/null; then # Check if the service is enabled echo "[Info] ${service} is enabled" _is_enabled=1 else echo "[Info] ${service} is not enabled" fi if systemctl is-active --quiet "${service}" 2>/dev/null; then # Check if the service is active/running echo "[Info] ${service} is running" _is_active=1 else echo "[Info] ${service} is not running" fi done < <( systemctl list-unit-files | # List all unit files grep -E "(systemd-timesyncd|chronyd).service" | # Filter for systemd-timesyncd or chronyd awk '{print $1}' # Get the first column (service name) ) _service_name=$(systemctl list-unit-files | grep -E "(systemd-timesyncd|chronyd).service" | awk '{print $1}') if [[ -z "${_service_name}" ]] && [[ $_is_enabled -eq 0 ]] && [[ "${_is_active}" -eq 0 ]]; then die "[Error] No time synchronization service found. Please install systemd-timesyncd, or chrony." 1 fi if [[ "${_is_active}" -eq 0 ]]; then echo "[Info] Time synchronization service is not running. Will start it now, sync it, and stop it after." # Start the service if [[ "${_service_name}" == *"chronyd"* ]]; then # If chronyd is available, start it as it takes precedence over systemd-timesyncd # Start chronyd echo "[Info] Starting time synchronization service: chronyd" systemctl start chronyd.service 2>/dev/null || die "[Error] Failed to start chronyd service" 1 sleep 2 elif [[ "${_service_name}" == *"systemd-timesyncd"* ]]; then # chronyd is not available, so we will use systemd-timesyncd # Start systemd-timesyncd echo "[Info] Starting time synchronization service: systemd-timesyncd" systemctl start systemd-timesyncd.service 2>/dev/null || die "[Error] Failed to start systemd-timesyncd service" 1 sleep 2 else # If neither chronyd nor systemd-timesyncd is available, exit with an error die "[Error] No time synchronization service found. Please install systemd-timesyncd, or chrony." 1 fi fi else die "[Error] No time synchronization service found. Please install systemd-timesyncd, or chrony." 1 fi _shouldError=0 # Sync time now echo "[Info] Syncing time now" if command -v chronyc &>/dev/null; then # Check if keyfile is set in chrony.conf if IsChronySecurityConfigured; then # Use chronyc to sync time echo "[Info] Syncing time using chronyc with keyfile in ${_chronyConfigFile}" if [[ "$(chronyc -a makestep 2>/dev/null)" == *"OK"* ]]; then echo "[Info] Time syncing" else echo "[Error] Failed to sync time using chronyc" _shouldError=1 fi else # Sync time using chronyc echo "[Info] Syncing time using chronyc" if [[ "$(chronyc makestep 2>/dev/null)" == *"OK"* ]]; then echo "[Info] Time syncing" else echo "[Error] Failed to sync time using chronyc" _shouldError=1 fi fi # Verify that time is synced if we had no errors if ((_shouldError == 0)); then # Sleep for 5 seconds to allow some time to sync after restarting chronyd sleep 5 # Check if time is synced with chronyc tracking case $(chronyc tracking 2>/dev/null | grep "Leap status") in *"Leap Second"* | *"Normal"*) # If the leap status is "Leap Second" or "Normal", then time is synced echo "[Info] Time synced successfully" ;; *"Insert second"*) # If the leap status is "Insert second", then time is being synced echo "[Info] Chrony is in the process of inserting a second and will be synced over time to prevent time jumps." ;; *"Delete second"*) # If the leap status is "Delete second", then time is being synced echo "[Info] Chrony is in the process of deleting a second and will be synced over time to prevent time jumps." ;; *"Not synchronized"*) # If the leap status is "Not synchronized", then time is not synced echo "[Error] Chrony is not synchronized." _shouldError=1 ;; *"Unknown"*) # If the leap status is "Unknown", then time is not synced echo "[Error] Unknown status from chronyc tracking." _shouldError=1 ;; *) # If the leap status is not recognized, then time is not synced echo "[Error] Failed to get sync status from chronyc" _shouldError=1 ;; esac # Stop chronyd if it was not running before if ((_is_active == 0)); then sleep 5 # Get the service name _service_name=$(systemctl list-unit-files | grep -E "chronyd.service" | awk '{print $1}') echo "[Info] Stopping time synchronization service: ${_service_name}" # Stop the service systemctl stop "$_service_name" 2>/dev/null fi fi elif command -v timedatectl &>/dev/null; then # Sync time using timedatectl echo "[Info] Syncing time using timedatectl" # Restart systemd-timesyncd to force a sync if systemctl restart systemd-timesyncd.service 2>/dev/null; then if timedatectl status 2>/dev/null | grep "synchronized" | grep -q "yes"; then echo "[Info] Time synced successfully" else echo "[Error] Failed to sync time using timedatectl" _shouldError=1 fi else echo "[Error] Failed to sync time using timedatectl" _shouldError=1 fi # Stop systemd-timesyncd if it was not running before if ((_is_active == 0)); then sleep 5 # Get the service name _service_name=$(systemctl list-unit-files | grep -E "systemd-timesyncd.service" | awk '{print $1}') echo "[Info] Stopping time synchronization service: ${_service_name}" # Stop the service systemctl stop "$_service_name" 2>/dev/null fi else echo "[Error] No time synchronization service found. Please install systemd-timesyncd, or chrony." exit 1 fi echo "" GetNtpConfiguration GetNtpPoolServers if ((_shouldError == 1)); then exit 1 fi
Description détaillée
Ce script est structuré en plusieurs fonctions et sections pour un contrôle modulaire et une plus grande clarté. Voici un aperçu de ses principaux composants :
1. Contrôles préalables
- Contrôle des autorisations de la racine
Veillez à ce que le script soit exécuté en tant queracine
, car le contrôle du temps et des services nécessite des privilèges élevés. - Découverte de services
Recherchezchronyd
ousystemd-timesyncd
. Si aucun des deux n’est installé, le script se termine par un message d’erreur clair.
2. Contrôle et synchronisation des services
- Évaluation de l’état des services
Détermine si chaque service est activé et/ou en cours d’exécution. S’il n’est pas en cours d’exécution, il démarre temporairement le programme approprié. - Synchronisation temporelle
- Si vous utilisez
chronyc
: Emet la commandemaketep
pour synchroniser immédiatement l’heure. - Si vous utilisez
timedatectl
: Redémarresystemd-timesyncd
et vérifie l’état de la synchronisation.
- Si vous utilisez
- Restauration des services
Si un service était initialement inactif, le script l’arrête à nouveau après la synchronisation.
3. Inspection de la configuration
GetChronyConfigFile()
: localise le fichier de configuration de chrony en se basant sur les conventions du système ou les valeurs par défaut des pages de manuel.GetNtpPoolServers()
: collecte les pools NTP et les configurations des serveurs à partir des fichiers de configurationchronyd
outimesyncd
.GetNtpConfiguration()
: affiche toutes les configurations du serveur, du pool et éventuellement de la source pour aider à valider les paramètres NTP.IsChronySecurityConfigured()
: vérifie que les paramètres de sécurité dechronyd
, tels quekeyfile
etcommandkey
, sont correctement définis.
Cas d’utilisation potentiels
Étude de cas : gestion à distance des correctifs
Un MSP gère des systèmes Linux sur plusieurs fuseaux horaires. Un correctif récent déployé via NinjaOne fait état d’horodatages d’installation incohérents. L’administrateur déploie ce script en tant qu’étape de pré-contrôle dans une stratégie NinjaOne. Le script garantit que tous les systèmes se synchronisent sur le même pool NTP avant le début du processus de correction. Par conséquent, les journaux s’alignent correctement et les rapports sont exacts.
Comparaisons
Méthode | Avantages | Limites |
ntpdate (ancien) | Rapide, autonome | Obsolète, peu fiable |
chronyd/systemd-timesyncd | Moderne, sûr, persistant | Nécessite une configuration adéquate |
Ce script | Détection automatisée, validation, contrôles de sécurité | Accès initial à la racine nécessaire |
Contrairement aux commandes ad hoc ou aux outils existants, ce script fournit une méthode structurée, tenant compte des erreurs et agnostique en matière de services, adaptée à un déploiement automatisé.
FAQ
Q : Que se passe-t-il si ni chronyd
ni systemd-timesyncd
n’est installé ?
A : Le script s’arrête et conseille à l’utilisateur d’installer l’un des deux services.
Q : Ce script peut-il être programmé via cron ou NinjaOne ?
A : Oui, il est entièrement compatible avec les plateformes d’automatisation et les tâches cron.
Q : Cela va-t-il interférer avec ma configuration actuelle de synchronisation temporelle ?
A : Non. Si un service de synchronisation temporelle était inactif avant l’exécution du script, il sera à nouveau arrêté après la synchronisation.
Q : Peut-on l’utiliser en toute sécurité sur des serveurs de production ?
A : Oui, en supposant que le redémarrage des services n’affecte pas les applications dépendantes. Il est recommandé de procéder à des tests en environnement de test.
Implications
Une mauvaise synchronisation de l’heure peut entraîner toute une série de problèmes : échecs de connexion dus à la non-concordance des jetons, journaux corrompus et réplication interrompue. En validant les fichiers de configuration, en vérifiant la sécurité des services et en fournissant des journaux faciles à auditer, ce script améliore la fiabilité et la sécurité de l’infrastructure. IL est particulièrement important pour les systèmes impliqués dans la gestion des identités, les communications sécurisées et les environnements où la conformité est importante.
Recommandations
- Test de pré-déploiement : commencez toujours par exécuter le script dans un environnement de test.
- Associez-le à la surveillance : utilisez NinjaOne ou des outils similaires pour contrôler l’état des services après leur déploiement.
- Incluez-le dans l’image de base : ajoutez ce script à la configuration post-installation pour éviter toute dérive future.
- Sortie du journal : redirigez la sortie du script vers un fichier journal à des fins d’audit (
. /sync-time.sh >> /var/log/time-sync.log
).
Conclusion
La synchronisation temporelle est essentielle à l’intégrité opérationnelle à tous les niveaux de l’informatique. Ce script offre un moyen portable, conforme et à toute épreuve de synchroniser l’heure sous Linux à l’aide d’un script shell. Intégré à des plateformes telles que NinjaOne, ce script permet aux professionnels de l’informatique et aux MSP de maintenir l’uniformité de leur flotte sans effort : en améliorant la préparation aux audits, la précision des données et la fiabilité du système.
Pour celles et ceux qui cherchent à synchroniser l’heure sur Linux à l’aide de scripts shell, cet outil est à la fois robuste et facile à automatiser. En l’intégrant dans les processus du cycle de vie, vous pouvez garantir un horodatage cohérent et sécurisé dans l’ensemble de votre infrastructure.