#!/bin/bash ############################################################################### # NGINX Installation Script for China Mainland with Mirror Acceleration ############################################################################### # @author Advanced Bash Shell Engineer # @version 1.0.0 # @license MIT # @created 2026-01-19 # @desc Production-grade NGINX installation script with China mirror support # Supports Ubuntu 18.04/20.04/22.04/24.04 with version pinning ############################################################################### ############################################################################### # GLOBAL CONSTANTS ############################################################################### readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" readonly SCRIPT_VERSION="1.0.0" readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Color codes for output readonly COLOR_RED="\033[0;31m" readonly COLOR_GREEN="\033[0;32m" readonly COLOR_YELLOW="\033[1;33m" readonly COLOR_BLUE="\033[0;34m" readonly COLOR_RESET="\033[0m" # Log levels readonly LOG_LEVEL_DEBUG=0 readonly LOG_LEVEL_INFO=1 readonly LOG_LEVEL_WARN=2 readonly LOG_LEVEL_ERROR=3 # Default configuration readonly DEFAULT_NGINX_VERSION="stable" readonly DEFAULT_MIRROR="ustc" readonly SUPPORTED_UBUNTU_VERSIONS=("18.04" "20.04" "22.04" "24.04") # Mirror configurations (China mainland accelerated sources) declare -A MIRROR_URLS=( ["aliyun"]="http://mirrors.aliyun.com/nginx" ["tsinghua"]="https://mirrors.tuna.tsinghua.edu.cn/nginx" ["ustc"]="https://mirrors.ustc.edu.cn/nginx/ubuntu" ["official"]="http://nginx.org" ) declare -A MIRROR_KEY_URLS=( ["aliyun"]="http://mirrors.aliyun.com/nginx/keys/nginx_signing.key" ["tsinghua"]="https://mirrors.tuna.tsinghua.edu.cn/nginx/keys/nginx_signing.key" ["ustc"]="https://mirrors.ustc.edu.cn/nginx/keys/nginx_signing.key" ["official"]="https://nginx.org/keys/nginx_signing.key" ) # Global variables CURRENT_LOG_LEVEL="${LOG_LEVEL_INFO}" NGINX_VERSION="${DEFAULT_NGINX_VERSION}" MIRROR_SOURCE="${DEFAULT_MIRROR}" FORCE_REINSTALL=false DRY_RUN=false ############################################################################### # ERROR HANDLING & TRAPS ############################################################################### set -euo pipefail IFS=$'\n\t' ### ### Cleanup function for graceful exit ### @param none ### @return void ### @require none ### cleanup() { local exit_code=$? if [[ ${exit_code} -ne 0 ]]; then log_error "脚本退出,错误码: ${exit_code}" fi # > Perform cleanup operations if needed return "${exit_code}" } trap cleanup EXIT trap 'log_error "用户中断脚本执行"; exit 130' INT TERM ############################################################################### # LOGGING FUNCTIONS ############################################################################### ### ### Core logging function with level-based filtering ### @param log_level integer Log level (0-3) ### @param message string Message to log ### @param color string Color code for output ### @return void ### @require none ### _log() { local log_level=$1 local message=$2 local color=$3 local level_name=$4 if [[ ${log_level} -ge ${CURRENT_LOG_LEVEL} ]]; then local timestamp timestamp="$(date '+%Y-%m-%d %H:%M:%S')" echo -e "${color}[${timestamp}] [${level_name}] ${message}${COLOR_RESET}" >&2 fi } ### ### Debug level logging ### @param message string Debug message ### @return void ### @require none ### log_debug() { _log "${LOG_LEVEL_DEBUG}" "$1" "${COLOR_BLUE}" "调试" } ### ### Info level logging ### @param message string Info message ### @return void ### @require none ### log_info() { _log "${LOG_LEVEL_INFO}" "$1" "${COLOR_GREEN}" "信息" } ### ### Warning level logging ### @param message string Warning message ### @return void ### @require none ### log_warn() { _log "${LOG_LEVEL_WARN}" "$1" "${COLOR_YELLOW}" "警告" } ### ### Error level logging ### @param message string Error message ### @return void ### @require none ### log_error() { _log "${LOG_LEVEL_ERROR}" "$1" "${COLOR_RED}" "错误" } ############################################################################### # VALIDATION FUNCTIONS ############################################################################### ### ### Check if script is running with root privileges ### @param none ### @return 0 if root, 1 otherwise ### @require none ### check_root_privileges() { if [[ ${EUID} -ne 0 ]]; then log_error "此脚本必须以 root 身份运行,或使用 sudo 执行" return 1 fi log_debug "已确认具备 root 权限" return 0 } ### ### Validate Ubuntu version compatibility ### @param none ### @return 0 if supported, 1 otherwise ### @require lsb_release command ### validate_ubuntu_version() { local ubuntu_version # > Check if lsb_release exists if ! command -v lsb_release &> /dev/null; then log_error "未找到 lsb_release 命令,无法识别 Ubuntu 版本。" return 1 fi ubuntu_version="$(lsb_release -rs)" log_debug "检测到 Ubuntu 版本: ${ubuntu_version}" # > Validate against supported versions local supported=false for version in "${SUPPORTED_UBUNTU_VERSIONS[@]}"; do if [[ "${ubuntu_version}" == "${version}" ]]; then supported=true break fi done if [[ "${supported}" == false ]]; then log_error "Ubuntu ${ubuntu_version} 不受支持。支持的版本: ${SUPPORTED_UBUNTU_VERSIONS[*]}" return 1 fi log_info "Ubuntu ${ubuntu_version} 受支持" return 0 } ### ### Validate mirror source selection ### @param mirror_name string 镜像源 name ### @return 0 if valid, 1 otherwise ### @require none ### validate_mirror_source() { local mirror_name=$1 if [[ ! -v MIRROR_URLS["${mirror_name}"] ]]; then log_error "无效的镜像源: ${mirror_name}" log_info "可用镜像源: ${!MIRROR_URLS[*]}" return 1 fi log_debug "镜像源 '${mirror_name}' 有效" return 0 } ### ### Check network connectivity to mirror ### @param mirror_url string URL to test ### @return 0 if reachable, 1 otherwise ### @require curl ### check_mirror_connectivity() { local mirror_url=$1 local timeout=10 log_debug "正在测试镜像连通性: ${mirror_url}" if curl -sSf --connect-timeout "${timeout}" --max-time "${timeout}" \ "${mirror_url}" -o /dev/null 2>/dev/null; then log_debug "镜像 ${mirror_url} 可访问" return 0 else log_warn "镜像 ${mirror_url} 不可访问" return 1 fi } ############################################################################### # SYSTEM PREPARATION FUNCTIONS ############################################################################### ### ### Install required system dependencies ### @param none ### @return 0 on success, 1 on failure ### @require apt-get ### install_dependencies() { log_info "正在安装系统依赖..." local dependencies=( "curl" "gnupg2" "ca-certificates" "lsb-release" "ubuntu-keyring" "apt-transport-https" ) if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会安装: ${dependencies[*]}" return 0 fi # > Update package index first if ! apt-get update -qq; then log_error "更新软件包索引失败" return 1 fi # > Install dependencies if ! apt-get install -y -qq "${dependencies[@]}"; then log_error "安装依赖失败" return 1 fi log_info "依赖安装完成" return 0 } ### ### Remove existing NGINX installation if present ### @param none ### @return 0 on success or if not installed ### @require apt-get, dpkg ### remove_existing_nginx() { log_info "正在检查是否已安装 NGINX..." if ! dpkg -l | grep -q "^ii.*nginx"; then log_info "未发现已安装的 NGINX" return 0 fi if [[ "${FORCE_REINSTALL}" == false ]]; then log_warn "NGINX 已安装。如需重装请使用 --force。" return 1 fi log_info "正在卸载已安装的 NGINX..." if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会卸载已安装的 NGINX" return 0 fi # > Stop NGINX service if running if systemctl is-active --quiet nginx 2>/dev/null; then systemctl stop nginx || true fi # > Remove NGINX packages if ! apt-get remove --purge -y nginx nginx-common nginx-full 2>/dev/null; then log_warn "部分 NGINX 软件包可能未能完全卸载(可忽略)" fi # > Clean up configuration files apt-get autoremove -y -qq || true log_info "已卸载现有 NGINX" return 0 } ############################################################################### # NGINX INSTALLATION FUNCTIONS ############################################################################### ### ### Import NGINX GPG signing key ### @param mirror_name string 镜像源 name ### @return 0 on success, 1 on failure ### @require curl, gpg ### import_nginx_gpg_key() { local mirror_name=$1 local key_url="${MIRROR_KEY_URLS[${mirror_name}]}" local keyring_path="/usr/share/keyrings/nginx-archive-keyring.gpg" log_info "正在导入 NGINX GPG 签名密钥(来源:${mirror_name})..." if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会从以下地址导入 GPG 密钥: ${key_url}" return 0 fi # > Remove old keyring if exists [[ -f "${keyring_path}" ]] && rm -f "${keyring_path}" # > Download and import GPG key if ! curl -fsSL "${key_url}" | gpg --dearmor -o "${keyring_path}" 2>/dev/null; then log_error "导入 GPG 密钥失败: ${key_url}" return 1 fi # > Verify the key was imported correctly if ! gpg --dry-run --quiet --no-keyring --import --import-options import-show \ "${keyring_path}" &>/dev/null; then log_error "GPG 密钥校验失败" return 1 fi # > Set proper permissions chmod 644 "${keyring_path}" log_info "GPG 密钥导入并校验成功" return 0 } ### ### Configure NGINX APT repository ### @param mirror_name string 镜像源 name ### @return 0 on success, 1 on failure ### @require lsb_release ### configure_nginx_repository() { local mirror_name=$1 local mirror_url="${MIRROR_URLS[${mirror_name}]}" local codename codename="$(lsb_release -cs)" local repo_file="/etc/apt/sources.list.d/nginx.list" local keyring_path="/usr/share/keyrings/nginx-archive-keyring.gpg" # > 不同镜像源目录结构可能不同: # > - 官方/部分镜像:.../packages/ubuntu # > - USTC:.../ubuntu local repo_base case "${mirror_name}" in ustc) repo_base="${mirror_url}" ;; *) repo_base="${mirror_url}/packages/ubuntu" ;; esac log_info "正在配置 NGINX 软件源(Ubuntu ${codename})..." if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会配置软件源:deb [signed-by=${keyring_path}] ${repo_base} ${codename} nginx" return 0 fi # > Create repository configuration local repo_config="deb [signed-by=${keyring_path}] ${repo_base} ${codename} nginx" echo "${repo_config}" | tee "${repo_file}" > /dev/null log_debug "已生成软件源配置文件:${repo_file}" log_debug "软件源地址:${repo_base} ${codename}" log_info "NGINX 软件源配置完成" return 0 } ### ### Configure APT pinning preferences for NGINX ### @param none ### @return 0 on success ### @require none ### configure_apt_pinning() { local pref_file="/etc/apt/preferences.d/99nginx" log_info "正在配置 APT Pin 优先级..." if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会配置 APT Pin 优先级" return 0 fi # > Create pinning configuration for priority cat > "${pref_file}" < Update package index with new repository if [[ "${DRY_RUN}" == false ]]; then if ! apt-get update -qq; then log_error "更新软件包索引失败" return 1 fi fi # > Handle version specification if [[ "${version}" != "stable" && "${version}" != "mainline" ]]; then # > Specific version requested package_spec="nginx=${version}" log_debug "安装指定版本:${package_spec}" else log_debug "从软件源安装:${version}" fi if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会安装软件包:${package_spec}" return 0 fi # > Install NGINX if ! DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${package_spec}"; then log_error "安装 NGINX 失败" return 1 fi log_info "NGINX 安装完成" return 0 } ### ### Verify NGINX installation ### @param none ### @return 0 on success, 1 on failure ### @require nginx ### verify_nginx_installation() { log_info "正在验证 NGINX 安装结果..." # > Check if nginx binary exists if ! command -v nginx &> /dev/null; then log_error "未在 PATH 中找到 nginx 可执行文件" return 1 fi # > Get and display version local nginx_version_output nginx_version_output="$(nginx -v 2>&1)" log_info "已安装: ${nginx_version_output}" # > Test configuration if ! nginx -t &>/dev/null; then log_error "NGINX 配置文件校验失败" return 1 fi log_info "NGINX 安装验证通过" return 0 } ### ### Enable and start NGINX service ### @param none ### @return 0 on success, 1 on failure ### @require systemctl ### enable_nginx_service() { log_info "正在设置 NGINX 开机自启并启动服务..." if [[ "${DRY_RUN}" == true ]]; then log_info "[演练模式] 将会启用并启动 NGINX 服务" return 0 fi # > Enable service to start on boot if ! systemctl enable nginx &>/dev/null; then log_error "设置 NGINX 开机自启失败" return 1 fi # > Start the service if ! systemctl start nginx; then log_error "启动 NGINX 服务失败" return 1 fi # > Verify service is running if ! systemctl is-active --quiet nginx; then log_error "NGINX 服务未处于运行状态" return 1 fi log_info "NGINX 服务已启用并启动" return 0 } ############################################################################### # MAIN ORCHESTRATION ############################################################################### ### ### Display usage information ### @param none ### @return void ### @require none ### show_usage() { cat < Step 1: Pre-flight checks log_info "步骤 1/8:执行预检查..." check_root_privileges || return 1 validate_ubuntu_version || return 1 validate_mirror_source "${MIRROR_SOURCE}" || return 1 # > Step 2: Check mirror connectivity log_info "步骤 2/8:检查镜像连通性..." if ! check_mirror_connectivity "${MIRROR_URLS[${MIRROR_SOURCE}]}"; then log_warn "主镜像不可用,尝试回退方案..." # > Fallback to official if mirror fails if [[ "${MIRROR_SOURCE}" != "official" ]]; then MIRROR_SOURCE="official" log_info "已回退到官方源" fi fi # > Step 3: Install dependencies log_info "步骤 3/8:安装依赖..." install_dependencies || return 1 # > Step 4: Handle existing installation log_info "步骤 4/8:检查已安装版本..." remove_existing_nginx || return 1 # > Step 5: Import GPG key log_info "步骤 5/8:导入 NGINX GPG 密钥..." import_nginx_gpg_key "${MIRROR_SOURCE}" || return 1 # > Step 6: Configure repository log_info "步骤 6/8:配置 NGINX 软件源..." configure_nginx_repository "${MIRROR_SOURCE}" || return 1 configure_apt_pinning || return 1 # > Step 7: Install NGINX log_info "步骤 7/8:安装 NGINX..." install_nginx_package "${NGINX_VERSION}" || return 1 verify_nginx_installation || return 1 # > Step 8: Enable service log_info "步骤 8/8:启用 NGINX 服务..." enable_nginx_service || return 1 log_info "=========================================" log_info "✓ NGINX 安装完成!" log_info "=========================================" if [[ "${DRY_RUN}" == false ]]; then log_info "服务状态: $(systemctl is-active nginx)" log_info "NGINX 版本: $(nginx -v 2>&1 | cut -d'/' -f2)" log_info "" log_info "常用命令:" log_info " 启动: sudo systemctl start nginx" log_info " 停止: sudo systemctl stop nginx" log_info " 重启: sudo systemctl restart nginx" log_info " 状态: sudo systemctl status nginx" log_info " 校验配置: sudo nginx -t" fi return 0 } ############################################################################### # SCRIPT ENTRY POINT ############################################################################### # ASCII Flow Diagram - Function Call Hierarchy # ┌─────────────────────────────────────────────────────────────┐ # │ MAIN() │ # └──────────────────┬──────────────────────────────────────────┘ # │ # ┌─────────────┼─────────────┬──────────────┬─────────────┐ # │ │ │ │ │ # ▼ ▼ ▼ ▼ ▼ # ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌────────────┐ ┌─────────┐ # │Pre-flight│ │Install │ │Import │ │Configure │ │Install │ # │Checks │ │Deps │ │GPG Key │ │Repository │ │NGINX │ # └─────────┘ └──────────┘ └─────────┘ └────────────┘ └─────────┘ # │ │ │ │ │ # ├─check_root_privileges │ │ │ # ├─validate_ubuntu_version │ │ │ # └─validate_mirror_source │ │ │ # │ │ │ │ # └─install_dependencies │ │ # │ │ │ # └─import_nginx_gpg_key │ # │ │ # ├─configure_nginx_repository # └─configure_apt_pinning # │ # ├─install_nginx_package # └─verify_nginx_installation # Parse command line arguments parse_arguments "$@" # Execute main workflow main exit $?