1774 字
9 分钟
Shell脚本分享 - Linux同步UTC+8时间

脚本内容#

#!/usr/bin/env bash
# 设置严格模式
set -euo pipefail
IFS=$'\n\t'  # 添加IFS设置,使数组处理更安全

# 定义颜色输出
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'

# 脚本路径和名称定义
readonly SCRIPT_PATH="/usr/local/bin"
readonly SCRIPT_NAME="timesync.sh"
readonly FULL_SCRIPT_PATH="${SCRIPT_PATH}/${SCRIPT_NAME}"
readonly LOG_FILE="/var/log/timesync.log"
readonly CONFIG_FILE="/etc/timesync.conf"
readonly LOG_DAYS=7  # 日志保留天数

# 默认配置
readonly DEFAULT_TIMEZONE="Asia/Shanghai"
declare -a DEFAULT_SERVERS=(
    "ntp.aliyun.com"
    "time1.cloud.tencent.com"
    "cn.pool.ntp.org"
    "ntp.ntsc.ac.cn"
)

# 声明全局变量
declare TIMEZONE
declare -a NTP_SERVERS

# 记录脚本内容
log_script_content() {
    local timezone=${1:-$DEFAULT_TIMEZONE}
    # 替换时区中的斜杠为连字符
    local formatted_timezone=${timezone//\//-}
    local content_file="${SCRIPT_PATH}/Synctime-${formatted_timezone}.sh"
    
    log "记录脚本内容到 $content_file"
    cat > "$content_file" << 'EOF'
#!/usr/bin/env bash
# 设置严格模式
set -euo pipefail
IFS=$'\n\t'  # 添加IFS设置,使数组处理更安全

# 定义颜色输出
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'

# 脚本路径和名称定义
readonly SCRIPT_PATH="/usr/local/bin"
readonly SCRIPT_NAME="timesync.sh"
readonly FULL_SCRIPT_PATH="${SCRIPT_PATH}/${SCRIPT_NAME}"
readonly LOG_FILE="/var/log/timesync.log"
readonly CONFIG_FILE="/etc/timesync.conf"
readonly LOG_DAYS=7  # 日志保留天数

# 默认配置
readonly DEFAULT_TIMEZONE="Asia/Shanghai"
declare -a DEFAULT_SERVERS=(
    "ntp.aliyun.com"
    "time1.cloud.tencent.com"
    "cn.pool.ntp.org"
    "ntp.ntsc.ac.cn"
)

# 声明全局变量
declare TIMEZONE
declare -a NTP_SERVERS

# 日志函数
log() {
    local timestamp
    timestamp=$(date +'%Y-%m-%d %H:%M:%S')
    echo -e "${GREEN}[${timestamp}] $1${NC}" | tee -a "$LOG_FILE"
}

error() {
    local timestamp
    timestamp=$(date +'%Y-%m-%d %H:%M:%S')
    echo -e "${RED}[${timestamp}] 错误: $1${NC}" >&2 | tee -a "$LOG_FILE"
    exit 1
}

# 清理旧日志
cleanup_logs() {
    if [[ -f "$LOG_FILE" ]]; then
        log "清理超过 ${LOG_DAYS} 天的旧日志..."
        local temp_file="${LOG_FILE}.tmp"
        touch -d "-${LOG_DAYS} days" "$temp_file"
        
        if [[ "$LOG_FILE" -ot "$temp_file" ]]; then
            log "删除旧日志文件"
            > "$LOG_FILE"  # 清空日志文件而不是删除它,以保持文件权限
        fi
        rm -f "$temp_file"
    fi
}

# 检查依赖命令是否存在
check_command() {
    command -v "$1" >/dev/null 2>&1 || error "未找到命令: $1,请先安装"
}

# 检查root权限
check_root() {
    if [[ $EUID -ne 0 ]]; then
        error "此脚本需要root权限运行"
    fi
}

# 创建必要的目录
create_directories() {
    local dirs=("$(dirname "$LOG_FILE")" "$(dirname "$CONFIG_FILE")" "$SCRIPT_PATH")
    for dir in "${dirs[@]}"; do
        [[ -d "$dir" ]] || mkdir -p "$dir" || error "无法创建目录: $dir"
    done
}

# 加载配置文件
load_config() {
    if [[ -f "$CONFIG_FILE" ]]; then
        # shellcheck source=/dev/null
        source "$CONFIG_FILE"
        log "加载配置文件: $CONFIG_FILE"
    else
        log "未找到配置文件,使用默认配置"
        TIMEZONE="$DEFAULT_TIMEZONE"
        NTP_SERVERS=("${DEFAULT_SERVERS[@]}")
        save_config
    fi
}

# 保存配置文件
save_config() {
    log "保存配置到 $CONFIG_FILE"
    {
        echo "TIMEZONE=\"$TIMEZONE\""
        echo "NTP_SERVERS=("
        printf '    "%s"\n' "${NTP_SERVERS[@]}"
        echo ")"
    } > "$CONFIG_FILE"
}

# 安装必要的包
install_packages() {
    log "检查并安装必要的软件包..."
    
    # 检查是否已安装htpdate
    if ! command -v htpdate &>/dev/null; then
        log "正在安装htpdate..."
        if command -v apt-get >/dev/null 2>&1; then
            DEBIAN_FRONTEND=noninteractive apt-get update -y || error "更新软件包失败"
            DEBIAN_FRONTEND=noninteractive apt-get install -y htpdate || error "安装 htpdate 失败"
        elif command -v yum >/dev/null 2>&1; then
            yum install -y htpdate || error "安装 htpdate 失败"
        else
            error "不支持的包管理器"
        fi
    else
        log "htpdate 已安装"
    fi
    
    # 检查其他必要命令
    check_command timedatectl
    check_command hwclock
}

# 同步时间
sync_time() {
    log "设置时区为 $TIMEZONE..."
    if ! timedatectl set-timezone "$TIMEZONE"; then
        error "时区设置失败"
    fi

    local sync_successful=false
    local sync_errors=()

    for server in "${NTP_SERVERS[@]}"; do
        log "尝试从 $server 同步时间..."
        if timeout 30 htpdate -s "$server" 2>/dev/null; then
            sync_successful=true
            log "成功从 $server 同步时间"
            break
        else
            local error_msg="从 $server 同步失败"
            log "$error_msg"
            sync_errors+=("$error_msg")
        fi
    done

    if ! $sync_successful; then
        error "所有服务器时间同步失败:${sync_errors[*]}"
    fi

    if ! hwclock -w; then
        error "硬件时钟更新失败"
    fi
    log "时间同步完成!当前时间: $(date)"
}

# 安装定时任务
install_cron() {
    log "设置每天凌晨4点运行的定时任务..."
    
    local cron_file="/etc/cron.d/timesync"
    printf "0 4 * * * root %s >> %s 2>&1\n" "$FULL_SCRIPT_PATH" "$LOG_FILE" > "$cron_file" || error "无法创建定时任务文件"
    chmod 644 "$cron_file" || error "无法设置定时任务文件权限"

    # 重启 cron 服务
    if command -v systemctl >/dev/null 2>&1; then
        systemctl restart cron.service || systemctl restart crond.service || error "无法重启 cron 服务"
    else
        service cron restart || service crond restart || error "无法重启 cron 服务"
    fi
    log "定时任务已安装"
}

# 清理函数
cleanup() {
    local exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        error "脚本执行失败,退出码: $exit_code"
    fi
    cleanup_logs
}

# 主函数
main() {
    trap cleanup EXIT
    
    check_root
    create_directories
    install_packages
    load_config
    sync_time
    install_cron
}

# 运行主函数
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
EOF

    chmod +x "$content_file"
    log "脚本内容已写入到: $content_file"
}

# 日志函数
log() {
    local timestamp
    timestamp=$(date +'%Y-%m-%d %H:%M:%S')
    echo -e "${GREEN}[${timestamp}] $1${NC}" | tee -a "$LOG_FILE"
}

error() {
    local timestamp
    timestamp=$(date +'%Y-%m-%d %H:%M:%S')
    echo -e "${RED}[${timestamp}] 错误: $1${NC}" >&2 | tee -a "$LOG_FILE"
    exit 1
}

# 清理旧日志
cleanup_logs() {
    if [[ -f "$LOG_FILE" ]]; then
        log "清理超过 ${LOG_DAYS} 天的旧日志..."
        local temp_file="${LOG_FILE}.tmp"
        touch -d "-${LOG_DAYS} days" "$temp_file"
        
        if [[ "$LOG_FILE" -ot "$temp_file" ]]; then
            log "删除旧日志文件"
            > "$LOG_FILE"  # 清空日志文件而不是删除它,以保持文件权限
        fi
        rm -f "$temp_file"
    fi
}

# 检查依赖命令是否存在
check_command() {
    command -v "$1" >/dev/null 2>&1 || error "未找到命令: $1,请先安装"
}

# 检查root权限
check_root() {
    if [[ $EUID -ne 0 ]]; then
        error "此脚本需要root权限运行"
    fi
}

# 创建必要的目录
create_directories() {
    local dirs=("$(dirname "$LOG_FILE")" "$(dirname "$CONFIG_FILE")" "$SCRIPT_PATH")
    for dir in "${dirs[@]}"; do
        [[ -d "$dir" ]] || mkdir -p "$dir" || error "无法创建目录: $dir"
    done
}

# 加载配置文件
load_config() {
    if [[ -f "$CONFIG_FILE" ]]; then
        # shellcheck source=/dev/null
        source "$CONFIG_FILE"
        log "加载配置文件: $CONFIG_FILE"
    else
        log "未找到配置文件,使用默认配置"
        TIMEZONE="$DEFAULT_TIMEZONE"
        NTP_SERVERS=("${DEFAULT_SERVERS[@]}")
        save_config
    fi
    # 记录脚本内容
    log_script_content "$TIMEZONE"
}

# 保存配置文件
save_config() {
    log "保存配置到 $CONFIG_FILE"
    {
        echo "TIMEZONE=\"$TIMEZONE\""
        echo "NTP_SERVERS=("
        printf '    "%s"\n' "${NTP_SERVERS[@]}"
        echo ")"
    } > "$CONFIG_FILE"
}

# 安装必要的包
install_packages() {
    log "检查并安装必要的软件包..."
    
    # 检查是否已安装htpdate
    if ! command -v htpdate &>/dev/null; then
        log "正在安装htpdate..."
        if command -v apt-get >/dev/null 2>&1; then
            DEBIAN_FRONTEND=noninteractive apt-get update -y || error "更新软件包失败"
            DEBIAN_FRONTEND=noninteractive apt-get install -y htpdate || error "安装 htpdate 失败"
        elif command -v yum >/dev/null 2>&1; then
            yum install -y htpdate || error "安装 htpdate 失败"
        else
            error "不支持的包管理器"
        fi
    else
        log "htpdate 已安装"
    fi
    
    # 检查其他必要命令
    check_command timedatectl
    check_command hwclock
}

# 同步时间
sync_time() {
    log "设置时区为 $TIMEZONE..."
    if ! timedatectl set-timezone "$TIMEZONE"; then
        error "时区设置失败"
    fi

    local sync_successful=false
    local sync_errors=()

    for server in "${NTP_SERVERS[@]}"; do
        log "尝试从 $server 同步时间..."
        if timeout 30 htpdate -s "$server" 2>/dev/null; then
            sync_successful=true
            log "成功从 $server 同步时间"
            break
        else
            local error_msg="从 $server 同步失败"
            log "$error_msg"
            sync_errors+=("$error_msg")
        fi
    done

    if ! $sync_successful; then
        error "所有服务器时间同步失败:${sync_errors[*]}"
    fi

    if ! hwclock -w; then
        error "硬件时钟更新失败"
    fi
    log "时间同步完成!当前时间: $(date)"
}

# 安装定时任务
install_cron() {
    log "设置每天凌晨4点运行的定时任务..."
    
    local cron_file="/etc/cron.d/timesync"
    printf "0 4 * * * root %s >> %s 2>&1\n" "$FULL_SCRIPT_PATH" "$LOG_FILE" > "$cron_file" || error "无法创建定时任务文件"
    chmod 644 "$cron_file" || error "无法设置定时任务文件权限"

    # 重启 cron 服务
    if command -v systemctl >/dev/null 2>&1; then
        systemctl restart cron.service || systemctl restart crond.service || error "无法重启 cron 服务"
    else
        service cron restart || service crond restart || error "无法重启 cron 服务"
    fi
    log "定时任务已安装"
}

# 清理函数
cleanup() {
    local exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        error "脚本执行失败,退出码: $exit_code"
    fi
    cleanup_logs
}

# 主函数
main() {
    trap cleanup EXIT
    
    check_root
    create_directories
    install_packages
    load_config
    sync_time
    install_cron
}

# 运行主函数
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
Shell脚本分享 - Linux同步UTC+8时间
https://devniko.com/posts/shell-share-synctime-asiashanghai/
作者
Niko
发布于
2024-04-01
许可协议
CC BY-NC-SA 4.0