注解:一键安装docker单环境脚本,支持自定义版本(运行此脚本会使docker环境完全清空,请谨慎执行)
#!/bin/bash

# 设置颜色变量
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # 恢复默认颜色
Arch=$(arch) # 获取系统类型

# 日志文件
LOG_FILE="/var/log/docker_install_$(date +%Y%m%d%H%M%S).log"

# 记录日志函数
log() {
    echo -e "$(date +"%Y-%m-%d %H:%M:%S") $1" | tee -a "$LOG_FILE"
}

# 成功消息
success() {
    log "${GREEN}[成功]${NC} $1"
}

# 错误消息
error() {
    log "${RED}[错误]${NC} $1"
    exit 1
}

# 警告消息
warning() {
    log "${YELLOW}[警告]${NC} $1"
}

# 信息消息
info() {
    log "${BLUE}[信息]${NC} $1"
}

# 检查是否为root用户运行
check_root() {
    if [ "$(id -u)" -ne 0 ]; then
        error "请使用root权限运行此脚本"
    fi
    success "当前以root权限运行"
}

# 确保网络连通
check_network() {
    info "正在检查网络连通性..."
    
    # 尝试多个站点以提高可靠性
    for site in www.baidu.com www.aliyun.com www.qq.com; do
        if ping -c 2 -W 3 $site &>/dev/null; then
            success "网络连通性检查通过 (可访问 $site)"
            return 0
        fi
    done
    
    error "外网不通,无法继续安装。请检查您的网络配置后重试。"
}

# 获取系统信息
get_system_info() {
    info "正在获取系统信息..."
    
    # 获取系统架构
    Arch=$(arch)
    success "系统架构: $Arch"
    
    # 获取操作系统版本
    if [ -f /etc/os-release ]; then
        source /etc/os-release
        OS_NAME=$NAME
        OS_VERSION=$VERSION_ID
        success "操作系统: $OS_NAME $OS_VERSION"
    else
        warning "无法确定操作系统版本"
    fi
    
    # 检查内存
    MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')
    success "系统内存: $MEM_TOTAL MB"
    
    # 检查磁盘空间
    DISK_FREE=$(df -h / | awk 'NR==2 {print $4}')
    success "根分区可用空间: $DISK_FREE"
    
    # 如果内存小于2GB,给出警告
    if [ $MEM_TOTAL -lt 2048 ]; then
        warning "系统内存小于2GB,Docker可能无法正常运行某些容器"
    fi
}

# 系统环境配置
configure_system() {
    info "正在配置系统环境..."
    
    # 修改时区为上海
    info "修改时区为Asia/Shanghai..."
    if timedatectl set-timezone Asia/Shanghai; then
        success "时区已设置为Asia/Shanghai"
    else
        warning "时区设置失败"
    fi
    
    # 修改selinux
    info "正在关闭SELinux..."
    if [ -f /etc/selinux/config ]; then
        sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
        setenforce 0 &>/dev/null || true
        success "SELinux已关闭"
    else
        warning "未找到SELinux配置文件"
    fi
    
    # 关闭防火墙
    info "正在关闭防火墙..."
    if systemctl stop firewalld &>/dev/null && systemctl disable firewalld &>/dev/null; then
        success "防火墙已关闭并禁止开机自启动"
    else
        warning "防火墙操作失败,可能不存在firewalld服务"
    fi
    
    # 开启IP转发
    info "正在开启IP转发功能..."
    sed -i "/net.ipv4.ip_forward/d" /etc/sysctl.conf
    echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
    if sysctl -p &>/dev/null; then
        success "IP转发功能已开启"
    else
        warning "IP转发设置可能失败"
    fi
    
    # 同步时间
    info "正在同步系统时间..."
    if which ntpdate &>/dev/null; then
        ntpdate ntp1.aliyun.com &>/dev/null && success "系统时间已同步" || warning "时间同步失败"
    else
        info "正在安装ntpdate..."
        yum -y install ntp ntpdate &>/dev/null && ntpdate ntp1.aliyun.com &>/dev/null && success "系统时间已同步" || warning "时间同步失败"
    fi
}

# 彻底清理现有Docker安装
clean_existing_docker() {
    info "正在彻底清理现有Docker安装..."
    
    # 停止所有相关服务
    systemctl stop docker.service docker.socket containerd.service &>/dev/null || true
    
    # 卸载现有Docker软件包
    yum remove -y docker docker-client docker-client-latest docker-common docker-latest \
        docker-latest-logrotate docker-logrotate docker-engine docker-ce docker-ce-cli containerd.io &>/dev/null || true
    
    # 删除相关文件和目录
    rm -rf /usr/bin/docker /usr/bin/containerd /etc/docker /usr/lib/systemd/system/docker* /usr/lib/systemd/system/containerd* &>/dev/null || true
    
    # 重新加载systemd配置
    systemctl daemon-reload &>/dev/null
    systemctl reset-failed &>/dev/null
    
    success "已清理现有Docker安装"
}

# 安装依赖
install_dependencies() {
    info "正在安装必要的依赖软件..."
    yum -y install wget ntp yum-utils device-mapper-persistent-data lvm2 &>/dev/null
    
    if [ $? -eq 0 ]; then
        success "依赖软件安装完成"
    else
        error "依赖软件安装失败,请检查yum源配置"
    fi
}

# 选择Docker版本
select_docker_version() {
    info "正在获取可用的Docker版本列表..."
    
    # 创建临时文件存储版本列表
    VERSIONS_FILE=$(mktemp)
    
    # 获取版本列表
    if ! curl -s https://download.docker.com/linux/static/stable/$Arch/ | grep -o 'docker-[0-9]*\.[0-9]*\.[0-9]*\.tgz' | sort -Vu > $VERSIONS_FILE; then
        error "无法获取Docker版本列表,请检查网络连接"
        exit 1
    fi
    
    # 显示最近的10个版本
    info "以下是最近的15个可用Docker版本:"
    tail -n 15 $VERSIONS_FILE | cat -n
    
    # 用户选择版本
    echo ""
    read -ep "请输入完整版本名称(例如 docker-28.0.0.tgz) [默认为最新版本]: " tgz
    
    if [ -z "$tgz" ]; then
        # 默认选择最新版本
        tgz=$(tail -n 1 $VERSIONS_FILE)
        info "未指定版本,将使用最新版本: $tgz"
    else
        # 验证用户输入的版本是否存在
        if ! grep -q "^$tgz$" $VERSIONS_FILE; then
            warn "您输入的版本 '$tgz' 不在列表中,可能不存在或拼写错误"
            read -ep "是否继续安装此版本? (y/n) [n]: " confirm
            if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
                error "已取消安装,请重新运行脚本并选择正确的版本"
                rm -f $VERSIONS_FILE
                exit 1
            fi
        fi
    fi
    
    success "已选择Docker版本: $tgz"
    rm -f $VERSIONS_FILE
}


# 下载并安装Docker
download_and_install_docker() {
    info "正在下载Docker二进制包: $tgz"
    
    # 创建临时目录
    TMP_DIR=$(mktemp -d)
    cd $TMP_DIR || error "无法创建临时目录"
    
    # 下载Docker二进制包
    if ! wget -c --progress=bar:force https://download.docker.com/linux/static/stable/$Arch/$tgz; then
        error "Docker二进制包下载失败"
    fi
    
    success "Docker二进制包下载完成"
    
    # 解压文件
    info "正在解压Docker二进制包..."
    if ! tar -zxf $tgz -C ./ ; then
        error "Docker二进制包解压失败"
    fi
    
    # 复制文件到指定位置
    info "正在安装Docker二进制文件..."
    chown root:root docker/* 
    \cp -p docker/* /usr/bin/
    
    # 创建docker用户组
    info "正在创建docker用户组..."
    groupadd docker &>/dev/null || true
    success "docker用户组已创建"
    
    # 清理临时文件
    cd - > /dev/null
    rm -rf $TMP_DIR
}

# 创建服务文件
create_service_files() {
    info "正在创建Docker服务文件..."
    
    # 创建目录
    mkdir -p /usr/lib/systemd/system
    
    # 创建docker.socket文件
    cat > /usr/lib/systemd/system/docker.socket << 'EOF'
[Unit]
Description=Docker Socket for the API

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
EOF
    
    # 创建containerd.service文件
    cat > /usr/lib/systemd/system/containerd.service << 'EOF'
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/bin/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5

LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity

TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target
EOF
    
    # 创建docker.service文件 - 修改为不强制要求containerd.service
    cat > /usr/lib/systemd/system/docker.service << 'EOF'
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target docker.socket firewalld.service containerd.service
Wants=network-online.target containerd.service
Requires=docker.socket

[Service]
Type=notify
# 如果没有containerd,则使用内置的
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

StartLimitBurst=3
StartLimitInterval=60s

LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

TasksMax=infinity
Delegate=yes
KillMode=process
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
EOF
    
    success "Docker服务文件创建完成"
}

# 配置Docker
configure_docker() {
    info "正在配置Docker..."
    
    # 创建配置目录
    mkdir -p /etc/docker
    
    # 创建daemon.json配置文件
    cat > /etc/docker/daemon.json << 'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://registry.docker-cn.com"
  ]
}
EOF
    
    success "Docker配置完成"
}

# 启动Docker服务
start_docker() {
    info "正在启动Docker服务..."
    
    # 重新加载systemd配置
    systemctl daemon-reload
    
    # 启用并启动docker.socket
    systemctl enable docker.socket &>/dev/null
    systemctl start docker.socket &>/dev/null
    
    # 尝试启动containerd服务
    info "正在启动containerd服务..."
    systemctl enable containerd &>/dev/null
    if ! systemctl start containerd &>/dev/null; then
        warning "containerd服务启动失败,将尝试在没有containerd的情况下启动Docker"
    else
        success "containerd服务启动成功"
    fi
    
    # 启用并启动Docker服务
    info "正在启动Docker服务..."
    systemctl enable docker.service &>/dev/null
    
    # 尝试启动Docker服务
    if ! systemctl start docker.service &>/dev/null; then
        # 如果启动失败,查看日志
        journalctl -xeu docker.service --no-pager | tail -n 20 >> $LOG_FILE
        
        # 修改docker.service文件,移除containerd依赖
        info "Docker服务启动失败,尝试修改配置后重新启动..."
        sed -i 's/Wants=network-online.target containerd.service/Wants=network-online.target/g' /usr/lib/systemd/system/docker.service
        
        # 重新加载systemd配置
        systemctl daemon-reload
        
        # 再次尝试启动
        if ! systemctl start docker.service &>/dev/null; then
            error "Docker服务启动失败,请检查日志: journalctl -xeu docker.service"
        fi
    fi
    
    # 检查Docker服务状态
    if systemctl is-active docker &>/dev/null; then
        success "Docker服务启动成功"
    else
        error "Docker服务启动失败,请检查日志: journalctl -xeu docker.service"
    fi
    
    # 显示Docker版本信息
    docker_version=$(docker --version | cut -d ' ' -f 3 | tr -d ',')
    success "Docker版本: $docker_version"
}

# 添加当前用户到docker组
add_user_to_docker_group() {
    if [ "$SUDO_USER" ]; then
        info "正在将用户 $SUDO_USER 添加到docker组..."
        usermod -aG docker $SUDO_USER
        success "用户 $SUDO_USER 已添加到docker组 (需要重新登录才能生效)"
    else
        info "如果需要非root用户使用Docker,请运行: sudo usermod -aG docker 用户名"
    fi
}

# 验证Docker安装
verify_docker() {
    info "正在验证Docker安装..."
    
    # 检查Docker是否正在运行
    if ! docker info &>/dev/null; then
        warning "Docker验证失败: 无法获取Docker信息"
        return
    fi
    
    # 显示Docker信息
    docker info | grep -E "Containers:|Images:|Server Version:|Storage Driver:|Logging Driver:" | while read line; do
        info "$line"
    done
    
    # 尝试运行hello-world容器
    info "尝试运行hello-world测试容器..."
    if docker run --rm hello-world &>/dev/null; then
        success "Docker验证成功: hello-world容器运行正常"
    else
        warning "无法运行hello-world容器,可能需要手动拉取镜像"
    fi
}

# 显示安装完成信息
show_completion_info() {
    echo -e "\n${GREEN}=========================================${NC}"
    echo -e "${GREEN}       Docker 安装成功!${NC}"
    echo -e "${GREEN}=========================================${NC}"
    echo -e "\n${BLUE}Docker 命令示例:${NC}"
    echo -e "  ${YELLOW}docker info${NC}         - 显示Docker系统信息"
    echo -e "  ${YELLOW}docker ps${NC}           - 列出运行中的容器"
    echo -e "  ${YELLOW}docker images${NC}       - 列出本地镜像"
    echo -e "  ${YELLOW}docker run${NC}          - 运行容器"
    echo -e "  ${YELLOW}docker-compose${NC}      - 使用Docker Compose (如已安装)"
    echo -e "\n${BLUE}服务管理:${NC}"
    echo -e "  ${YELLOW}systemctl status docker${NC}  - 查看Docker状态"
    echo -e "  ${YELLOW}systemctl restart docker${NC} - 重启Docker服务"
    echo -e "\n${BLUE}日志文件:${NC} ${YELLOW}$LOG_FILE${NC}"
    echo -e "\n${GREEN}感谢使用Docker安装脚本!${NC}\n"
}

# 主函数
main() {
    echo -e "${BLUE}=========================================${NC}"
    echo -e "${BLUE}       Docker 安装与配置脚本${NC}"
    echo -e "${BLUE}=========================================${NC}"
    
    # 检查root权限
    check_root
    
    # 检查网络连通性
    check_network
    
    # 获取系统信息
    get_system_info
    
    # 配置系统环境
    configure_system
    
    # 彻底清理现有Docker安装
    clean_existing_docker
    
    # 安装依赖
    install_dependencies
    
    # 选择Docker版本
    select_docker_version
    
    # 下载并安装Docker
    download_and_install_docker
    
    # 创建服务文件
    create_service_files
    
    # 配置Docker
    configure_docker
    
    # 启动Docker服务
    start_docker
    
    # 添加当前用户到docker组
    add_user_to_docker_group
    
    # 验证Docker安装
    verify_docker
    
    # 显示安装完成信息
    show_completion_info
}

# 执行主函数
main "$@"