注解:一键安装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 curl 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版本列表,请检查网络连接"
    fi

    # 显示最近的15个版本
    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
            warning "您输入的版本 '$tgz' 不在列表中,可能不存在或拼写错误"
            read -ep "是否继续安装此版本? (y/n) [n]: " confirm
            if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
                rm -f $VERSIONS_FILE
                error "已取消安装,请重新运行脚本并选择正确的版本"
            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 -xf $tgz -C ./; then
        error "Docker二进制包解压失败"
    fi

    # 复制文件到指定位置
    info "正在安装Docker二进制文件..."
    chown root:root docker/*
    \cp -rp 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文件
    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
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 Compose
install_docker_compose() {
    info "正在获取可用的 Docker Compose 版本列表..."

    # 获取 GitHub release 版本列表
    COMPOSE_VERSIONS_FILE=$(mktemp)
    if ! curl -s https://api.github.com/repos/docker/compose/releases \
        | grep '"tag_name"' \
        | grep -o 'v[0-9]*\.[0-9]*\.[0-9]*' \
        | sort -Vu > $COMPOSE_VERSIONS_FILE 2>/dev/null; then
        warning "无法从 GitHub 获取 Docker Compose 版本列表,将使用默认版本"
    fi

    # 显示最近 15 个版本
    if [ -s $COMPOSE_VERSIONS_FILE ]; then
        info "以下是最近的15个可用 Docker Compose 版本:"
        tail -n 15 $COMPOSE_VERSIONS_FILE | cat -n
        echo ""
    fi

    read -ep "请输入版本号 (例如 v2.36.0) [默认为最新版本]: " COMPOSE_VERSION

    if [ -z "$COMPOSE_VERSION" ]; then
        COMPOSE_VERSION=$(tail -n 1 $COMPOSE_VERSIONS_FILE)
        if [ -z "$COMPOSE_VERSION" ]; then
            # GitHub API 拉取失败时使用兜底版本
            COMPOSE_VERSION="v2.36.0"
            warning "无法自动获取最新版本,将使用默认版本: $COMPOSE_VERSION"
        else
            info "未指定版本,将使用最新版本: $COMPOSE_VERSION"
        fi
    else
        # 格式校验:必须以 v 开头
        if [[ ! "$COMPOSE_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            warning "版本号格式不正确,应类似 v2.36.0,将使用最新版本"
            COMPOSE_VERSION=$(tail -n 1 $COMPOSE_VERSIONS_FILE)
            [ -z "$COMPOSE_VERSION" ] && COMPOSE_VERSION="v2.36.0"
        fi
    fi

    rm -f $COMPOSE_VERSIONS_FILE
    success "已选择 Docker Compose 版本: $COMPOSE_VERSION"

    # 根据系统架构映射下载文件名
    case "$Arch" in
        x86_64)  COMPOSE_ARCH="x86_64" ;;
        aarch64) COMPOSE_ARCH="aarch64" ;;
        armv7l)  COMPOSE_ARCH="armv7" ;;
        *)
            warning "未知架构 $Arch,尝试使用 x86_64"
            COMPOSE_ARCH="x86_64"
            ;;
    esac

    COMPOSE_BINARY="docker-compose-linux-${COMPOSE_ARCH}"
    COMPOSE_URL="https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/${COMPOSE_BINARY}"
    COMPOSE_DEST="/usr/local/bin/docker-compose"

    info "正在下载 Docker Compose: $COMPOSE_URL"

    # 优先尝试 GitHub 直链,失败则尝试国内代理
    if ! wget -c --progress=bar:force -O "$COMPOSE_DEST" "$COMPOSE_URL" 2>/dev/null; then
        warning "GitHub 直链下载失败,正在尝试国内代理..."
        COMPOSE_PROXY_URL="https://ghproxy.com/$COMPOSE_URL"
        if ! wget -c --progress=bar:force -O "$COMPOSE_DEST" "$COMPOSE_PROXY_URL" 2>/dev/null; then
            rm -f "$COMPOSE_DEST"
            error "Docker Compose 下载失败,请检查网络后手动安装"
        fi
    fi

    # 赋予可执行权限
    chmod +x "$COMPOSE_DEST"

    # 创建插件目录软链接,兼容 docker compose 子命令方式
    PLUGIN_DIR="/usr/libexec/docker/cli-plugins"
    mkdir -p "$PLUGIN_DIR"
    ln -sf "$COMPOSE_DEST" "$PLUGIN_DIR/docker-compose"

    # 验证安装结果
    if command -v docker-compose &>/dev/null; then
        COMPOSE_INSTALLED_VERSION=$(docker-compose version --short 2>/dev/null)
        success "Docker Compose 安装成功,版本: $COMPOSE_INSTALLED_VERSION"
        success "独立命令路径: $COMPOSE_DEST"
        success "插件链接路径: $PLUGIN_DIR/docker-compose"
    else
        error "Docker Compose 安装后无法找到命令,请手动检查: $COMPOSE_DEST"
    fi
}

# 添加当前用户到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() {
    COMPOSE_VER=$(docker-compose version --short 2>/dev/null || echo "未知")

    echo -e "\n${GREEN}=========================================${NC}"
    echo -e "${GREEN}     Docker 及 Compose 安装成功!${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 "\n${BLUE}Docker Compose 命令示例 (版本: $COMPOSE_VER):${NC}"
    echo -e "  ${YELLOW}docker compose up -d${NC}     - 启动 Compose 服务 (v2 插件方式)"
    echo -e "  ${YELLOW}docker compose down${NC}      - 停止并移除 Compose 服务"
    echo -e "  ${YELLOW}docker compose ps${NC}        - 查看 Compose 服务状态"
    echo -e "  ${YELLOW}docker compose logs -f${NC}   - 实时查看 Compose 服务日志"
    echo -e "  ${YELLOW}docker-compose up -d${NC}     - 启动 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 Compose
    install_docker_compose

    # 添加当前用户到docker组
    add_user_to_docker_group

    # 验证Docker安装
    verify_docker

    # 显示安装完成信息
    show_completion_info
}

# 执行主函数
main "$@"