本笔记旨在记录 Debian 系统初始化配置的详细步骤,包括系统更新、网络设置、SSH 服务配置、Oh-My-Zsh 安装与插件配置,以及 Vim 编辑器基础配置。

0. 更换 APT 镜像源

为了提高软件包下载速度和稳定性,建议将 Debian 系统的 APT 镜像源更换为国内的常用镜像站。本节提供一个自动化脚本,帮助你轻松完成此操作。

0.1 更换源脚本

将以下内容保存为 change_debian_sources.sh 文件,并赋予执行权限后运行。

#!/bin/bash

# --- 字体颜色定义 ---
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# --- 函数定义 ---
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查是否以root权限运行
check_root() {
    if [[ $EUID -ne 0 ]]; then
        log_error "此脚本必须以 root 用户身份运行。"
        log_error "请使用 'sudo ./change_debian_sources.sh' 运行,或直接切换到 root 用户再执行。"
        exit 1
    fi
}

# 获取 Debian 发行版代号
get_debian_codename() {
    if [ -f "/etc/os-release" ]; then
        . /etc/os-release
        echo "$VERSION_CODENAME"
    elif [ -f "/etc/debian_version" ]; then
        # fallback for older systems or minimal installs
        # Try to guess based on version if codename not found in os-release
        DEB_VER=$(cat /etc/debian_version | cut -d'.' -f1)
        case "$DEB_VER" in
            12) echo "bookworm";;
            13) echo "bullseye";;
            14) echo "buster";;
            15) echo "stretch";;
            *) echo "";; # Unknown or no codename
        esac
    fi
}

# 镜像源内容模板函数
get_source_content() {
    local mirror_base_url=$1
    local codename=$2
    local template=""

    # 核心源
    template+="deb $mirror_base_url/debian/ $codename main contrib non-free non-free-firmware\n"
    template+="deb-src $mirror_base_url/debian/ $codename main contrib non-free non-free-firmware\n\n"

    # 更新源
    template+="deb $mirror_base_url/debian/ $codename-updates main contrib non-free non-free-firmware\n"
    template+="deb-src $mirror_base_url/debian/ $codename-updates main contrib non-free non-free-firmware\n\n"

    # 安全更新源
    if [[ "$mirror_base_url" == "http://deb.debian.org" ]]; then
       # 官方源的security地址不同
       template+="deb $mirror_base_url/debian-security/ $codename-security main contrib non-free non-free-firmware\n"
       template+="deb-src $mirror_base_url/debian-security/ $codename-security main contrib non-free non-free-firmware\n"
    else
       # 国内镜像站的security地址通常这样
       template+="deb $mirror_base_url/debian-security/ $codename-security main contrib non-free non-free-firmware\n"
       template+="deb-src $mirror_base_url/debian-security/ $codename-security main contrib non-free non-free-firmware\n"
    fi

    # backports 源(可选,默认注释)
    template+="\n# backports源 (提供较新版本的软件,可能不稳定)\n"
    template+="# deb $mirror_base_url/debian/ $codename-backports main contrib non-free non-free-firmware\n"
    template+="# deb-src $mirror_base_url/debian/ $codename-backports main contrib non-free non-free-firmware\n"


    echo -e "$template"
}

# --- 主脚本逻辑 ---

check_root # 确保脚本以 root 权限运行

SOURCES_LIST="/etc/apt/sources.list"
BACKUP_FILE="${SOURCES_LIST}.bak.$(date +%Y%m%d%H%M%S)"

log_info "正在检测 Debian 发行版代号..."
DEBIAN_CODENAME=$(get_debian_codename)

if [ -z "$DEBIAN_CODENAME" ]; then
    log_error "未能检测到 Debian 发行版代号。请手动确认或更新您的系统。"
    log_error "脚本可能无法为您生成正确的 sources.list 内容。"
    log_error "请确保您运行的是 Debian 系统,或者手动修改脚本中的 DEBIAN_CODENAME 变量。"
    exit 1
else
    log_info "检测到 Debian 发行版代号为: ${DEBIAN_CODENAME}"
fi

log_info "正在备份原始的 ${SOURCES_LIST} 文件到 ${BACKUP_FILE}..."
if cp "$SOURCES_LIST" "$BACKUP_FILE"; then
    log_info "备份成功。"
else
    log_error "备份失败!请检查权限或磁盘空间。退出。"
    exit 1
fi

echo ""
log_info "请选择您希望使用的 Debian APT 镜像源:"
echo "1) 阿里云镜像站 (https://mirrors.aliyun.com)"
echo "2) 清华大学镜像站 (https://mirrors.tuna.tsinghua.edu.cn)"
echo "3) 中国科学技术大学 (USTC) 镜像站 (https://mirrors.ustc.edu.cn)"
echo "4) 官方 Debian 镜像站 (deb.debian.org)"
echo "5) 退出脚本"
echo ""

read -p "请输入您的选择 (1-5): " CHOICE

MIRROR_URL=""
case "$CHOICE" in
    1) MIRROR_URL="https://mirrors.aliyun.com";;
    2) MIRROR_URL="https://mirrors.tuna.tsinghua.edu.cn";;
    3) MIRROR_URL="https://mirrors.ustc.edu.cn";;
    4) MIRROR_URL="http://deb.debian.org";; # 官方源使用 http 是惯例,因为 GPG 签名确保安全
    5) log_info "脚本已退出。"; exit 0;;
    *) log_error "无效的选择,请重新运行脚本并输入 1-5 之间的数字。"; exit 1;;
esac

log_info "您选择了 $MIRROR_URL 作为镜像源。"
log_info "正在生成新的 sources.list 内容..."

NEW_SOURCES_CONTENT=$(get_source_content "$MIRROR_URL" "$DEBIAN_CODENAME")

log_info "正在写入新内容到 ${SOURCES_LIST}..."
echo "$NEW_SOURCES_CONTENT" | tee "$SOURCES_LIST" > /dev/null
if [ $? -eq 0 ]; then
    log_info "成功更新 ${SOURCES_LIST}。"
else
    log_error "写入 ${SOURCES_LIST} 失败!退出。"
    exit 1
fi

log_info "正在执行 'apt update' 更新软件包列表..."
if apt update; then
    log_info "'apt update' 成功完成。"
    log_warn "建议您现在运行 'apt upgrade -y' 来升级您的系统。"
else
    log_error "'apt update' 失败!请检查您的网络连接或新的源配置。"
    log_error "如果出现 GPG 错误,您可能需要手动导入缺失的密钥。"
fi

log_info "脚本执行完毕。感谢使用!"

0.2 如何使用脚本

  1. 保存脚本: 将上述代码复制并保存到一个文件,例如 change_debian_sources.sh
  2. 添加执行权限: 在终端中运行 chmod +x change_debian_sources.sh
  3. 运行脚本: 使用 sudo 权限运行脚本:sudo ./change_debian_sources.sh
  4. 选择镜像源: 脚本会提示你选择一个镜像源,输入对应的数字即可。
  5. 更新系统: 脚本会自动执行 apt update。建议在脚本完成后手动运行 sudo apt upgrade -y 来升级所有软件包。

1. 基础系统初始化自动化脚本

本节提供一个综合性脚本,用于自动化执行 Debian 系统的基础初始化步骤,包括:

  • 更新系统软件包并安装常用工具(如 Vim, OpenSSH)。
  • 配置静态 IP 地址、子网掩码、网关和 DNS 服务器。
  • 修改 SSH 配置以允许 root 登录和密码认证(注意:此操作会降低安全性,仅建议在受控环境中或临时使用)。

1.1 基础初始化脚本

将以下内容保存为 debian_initial_setup.sh 文件,并赋予执行权限后运行。

#!/bin/bash

# --- 字体颜色定义 ---
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# --- 函数定义 ---
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查是否以root权限运行
check_root() {
    if [[ $EUID -ne 0 ]]; then
        log_error "此脚本必须以 root 用户身份运行。"
        log_error "请使用 'sudo ./debian_initial_setup.sh' 运行,或直接切换到 root 用户再执行。"
        exit 1
    fi
}

# 备份文件函数
backup_file() {
    local file_path=$1
    local backup_path="${file_path}.bak.$(date +%Y%m%d%H%M%S)"
    if [ -f "$file_path" ]; then
        log_info "正在备份 ${file_path} 到 ${backup_path}..."
        if cp "$file_path" "$backup_path"; then
            log_info "备份成功。"
        else
            log_error "备份 ${file_path} 失败!请检查权限或磁盘空间。退出。"
            exit 1
        fi
    else
        log_warn "${file_path} 文件不存在,跳过备份。"
    fi
}

# --- 主脚本逻辑 ---

check_root # 确保脚本以 root 权限运行

log_info "========================================="
log_info "    Debian 服务器初始化及网络配置脚本    "
log_info "========================================="
log_warn "此脚本将修改网络配置和 SSH 配置,可能导致远程连接中断。"
log_warn "请确保有物理访问权限或虚拟机快照以防万一。"
read -p "您确定要继续吗?(y/N): " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
    log_info "用户取消,脚本退出。"
    exit 0
fi

# --- 1. 前期准备 ---
log_info "--- 1. 前期准备:更新系统及安装必要工具 ---"

log_info "正在更新软件包列表..."
if ! apt update; then
    log_error "apt update 失败!请检查网络连接。退出。"
    exit 1
fi

log_info "正在升级所有已安装的软件包..."
# 使用 apt upgrade -y --allow-downgrades --allow-remove-essential --allow-change-held-packages 确保升级不被中断
if ! apt upgrade -y; then
    log_warn "apt upgrade 过程中可能出现警告或错误,但通常不致命。请查看日志。"
fi

log_info "正在安装 Vim 编辑器..."
if ! apt install vim -y; then
    log_error "安装 Vim 失败!退出。"
    exit 1
fi

log_info "正在安装 OpenSSH 服务器..."
if ! apt install openssh-server -y; then
    log_error "安装 OpenSSH 服务器失败!退出。"
    exit 1
fi

log_info "检查 SSH 服务状态..."
systemctl status ssh | grep -q "active (running)"
if [ $? -eq 0 ]; then
    log_info "SSH 服务正在运行。"
else
    log_error "SSH 服务未运行。请手动检查并启动。"
    log_info "尝试启动 SSH 服务..."
    if systemctl start ssh; then
        log_info "SSH 服务已启动。"
    else
        log_error "启动 SSH 服务失败!请检查系统日志。"
        exit 1
    fi
fi

log_info "前期准备完成。"

# --- 2. 修改 IP 地址及网关 ---
log_info "--- 2. 修改 IP 地址及网关 ---"

# 尝试自动检测网卡接口名称
AUTO_DETECTED_INTERFACE=$(ip -o link show | awk -F': ' '{print $2}' | grep -E "^e|^eth|^enp" | grep -v "lo" | head -n 1)

read -p "请输入您的网卡接口名称 (例如: ens32, eth0, enp0s3) [默认: ${AUTO_DETECTED_INTERFACE}]: " NETWORK_INTERFACE
NETWORK_INTERFACE=${NETWORK_INTERFACE:-${AUTO_DETECTED_INTERFACE}}

if [ -z "$NETWORK_INTERFACE" ]; then
    log_error "未检测到或未输入网卡接口名称。退出。"
    exit 1
fi

log_info "将为网卡接口 ${NETWORK_INTERFACE} 配置静态 IP。"

read -p "请输入静态 IP 地址 (例如: 192.168.50.13): " STATIC_IP
read -p "请输入子网掩码 (例如: 255.255.255.0): " NETMASK
read -p "请输入网关地址 (例如: 192.168.50.1): " GATEWAY
read -p "请输入首选 DNS 服务器地址 (例如: 192.168.50.4 或 8.8.8.8): " NAMESERVER1
read -p "请输入备用 DNS 服务器地址 (可选,留空则不设置): " NAMESERVER2


# 备份 /etc/network/interfaces
backup_file "/etc/network/interfaces"

log_info "正在配置 /etc/network/interfaces..."
# 清空并写入新的网络配置
cat << EOF > /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface - configured by script
allow-hotplug ${NETWORK_INTERFACE}
iface ${NETWORK_INTERFACE} inet static
address ${STATIC_IP}
netmask ${NETMASK}
gateway ${GATEWAY}
EOF

log_info "已更新 /etc/network/interfaces。"


# 备份 /etc/resolv.conf
backup_file "/etc/resolv.conf"

log_info "正在配置 /etc/resolv.conf (DNS 服务器)..."
# 清空并写入新的 DNS 配置
echo "nameserver ${NAMESERVER1}" > /etc/resolv.conf
if [ -n "$NAMESERVER2" ]; then
    echo "nameserver ${NAMESERVER2}" >> /etc/resolv.conf
fi

log_info "已更新 /etc/resolv.conf。"

log_info "正在重启网络服务 (此操作可能导致连接中断)..."
if systemctl restart networking; then
    log_info "网络服务重启成功。请检查您的网络连接是否正常。"
else
    log_error "网络服务重启失败!请检查 /etc/network/interfaces 配置。"
    log_error "您可能需要手动修复网络,否则可能无法连接到服务器。"
    exit 1
fi

log_info "IP 地址及网关配置完成。"

# --- 3. 修改 SSH 配置以允许远程连接 ---
log_info "--- 3. 修改 SSH 配置以允许远程连接 ---"
log_warn "此操作将降低 SSH 安全性,允许 root 登录和密码认证!"
log_warn "在生产环境中,强烈建议使用密钥认证并禁用这些选项。"

SSH_CONFIG="/etc/ssh/sshd_config"
backup_file "$SSH_CONFIG"

log_info "正在修改 ${SSH_CONFIG}..."

# 使用 sed 确保这些行存在并被正确设置
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' "$SSH_CONFIG"
# 如果 PermitRootLogin 不存在,则添加
grep -qxF 'PermitRootLogin yes' "$SSH_CONFIG" || echo 'PermitRootLogin yes' >> "$SSH_CONFIG"

sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' "$SSH_CONFIG"
# 如果 PasswordAuthentication 不存在,则添加
grep -qxF 'PasswordAuthentication yes' "$SSH_CONFIG" || echo 'PasswordAuthentication yes' >> "$SSH_CONFIG"


log_info "已更新 SSH 配置。"

log_info "正在重启 SSH 服务..."
if systemctl restart ssh; then
    log_info "SSH 服务重启成功。root 用户和密码认证现已启用。"
else
    log_error "SSH 服务重启失败!请检查 /etc/ssh/sshd_config 或系统日志。"
    exit 1
fi

log_info "SSH 配置修改完成。"

log_info "========================================="
log_info "      Debian 服务器初始化脚本执行完毕!    "
log_info "========================================="
log_info "请立即测试您的网络连接和 SSH 登录。"

1.2 如何使用脚本

  1. 保存脚本: 将上述代码复制并保存到一个文件,例如 debian_initial_setup.sh
  2. 添加执行权限: 在终端中运行 chmod +x debian_initial_setup.sh
  3. 运行脚本: 使用 sudo 权限运行脚本:sudo ./debian_initial_setup.sh
  4. 交互式输入: 脚本会提示你输入网络配置信息,请根据实际情况填写。
  5. 验证: 脚本执行完毕后,请务必测试网络连接和 SSH 登录是否正常。

1.3 脚本功能详解

  • 安全提示与确认: 脚本开始时会发出警告,提示用户此操作可能导致连接中断,并要求用户确认。
  • 日志与权限检查: 沿用统一的 log_infolog_warnlog_error 函数和 check_root 函数。
  • 文件备份: 在修改 /etc/network/interfaces 和 /etc/ssh/sshd_config 之前,脚本会自动创建备份文件。
  • 前期准备:
    • 执行 apt update 和 apt upgrade -y
    • 安装 vim 和 openssh-server
    • 检查 SSH 服务状态,如果未运行则尝试启动。
  • 网络配置:
    • 尝试自动检测网卡接口名称,并允许用户手动输入或确认。
    • 交互式地获取静态 IP、子网掩码、网关和 DNS 服务器地址。
    • 生成并写入新的 /etc/network/interfaces 和 /etc/resolv.conf 配置。
    • 重启 networking 服务以应用更改。
  • SSH 配置:
    • 修改 /etc/ssh/sshd_config,设置 PermitRootLogin yes 和 PasswordAuthentication yes
    • 重启 ssh 服务以使配置生效。
  • 完成提示: 脚本结束时会给出完成提示,并建议用户测试连接。

2. 配置 Oh-My-Zsh

Oh-My-Zsh 是一个流行的 Zsh 配置框架,提供了丰富的插件和主题,可以极大地提升命令行体验。

2.1 安装 Oh-My-Zsh

  1. **安装 Zsh、Git 和 Curl:**​这些是安装 Oh-My-Zsh 所需的依赖。

    sudo apt install zsh git curl -y
    
  2. **设置 Zsh 为默认终端:**​执行此命令后,下次登录时将自动使用 Zsh。

    chsh -s /bin/zsh
    
  3. **安装 Oh-My-Zsh:**​选择以下任一方式进行安装。

    • 使用 Curl 安装:

      sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
      
    • 使用 Wget 安装:

      sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"
      

2.2 安装常用插件

安装 zsh-autosuggestions(命令自动补全)和 zsh-syntax-highlighting(语法高亮)插件。

# 克隆 zsh-autosuggestions 插件
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

# 克隆 zsh-syntax-highlighting 插件
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

2.3 配置 .zshrc

编辑你的 Zsh 配置文件 ~/.zshrc,启用插件和设置主题。

  1. 编辑 .zshrc 文件:

    vim ~/.zshrc
    
  2. **修改或添加以下内容:**​找到 ZSH_THEME 和 plugins 行,进行修改。

    export ZSH="$HOME/.oh-my-zsh" # Oh-My-Zsh 的安装路径,通常无需修改
    ZSH_THEME="bira"              # 设置主题,你可以尝试其他主题,如 "agnoster"
    plugins=(
        git
        zsh-autosuggestions
        zsh-syntax-highlighting
    )
    source $ZSH/oh-my-zsh.sh      # 确保这一行在文件末尾,用于加载 Oh-My-Zsh
    
  3. 使 .zshrc 配置生效:
    在终端中执行以下命令,或重新打开终端。

    source ~/.zshrc
    

3. 配置 Vim

为 Vim 编辑器添加一些基础配置,提升使用体验。

  1. **创建或编辑 Vim 配置文件:**​打开 ~/.vimrc 文件。

    vim ~/.vimrc
    
  2. 添加以下内容:
    这些配置将启用行号显示、设置颜色方案和打开语法高亮。

    set nu                " 显示行号 (number)
    colorscheme desert    " 设置颜色显示方案为 desert,你可以尝试其他方案
    syntax on             " 打开语法高亮
    

4. 安装docker

本节提供一个自动化脚本,用于在 Debian 系统上安装最新版本的 Docker Engine 和 Docker Compose 插件。该脚本处理了依赖安装、旧版本卸载、官方仓库配置以及用户权限设置等步骤,并提供了详细的日志输出。

4.1 Docker 安装脚本

将以下内容保存为 install_docker.sh 文件,并赋予执行权限后运行。

#!/bin/bash

# --- 配置选项 ---
# 是否安装旧版独立的 docker-compose 可执行文件?
# 设置为 true 会同时安装 docker-compose-plugin 和旧版独立文件
# 设置为 false 则只安装 docker-compose-plugin
# 注意:通常情况下,创建一个从 'docker compose' 到 'docker-compose' 的符号链接就足够,
#      无需再安装旧版独立的 docker-compose 二进制文件。
#      此处默认设置为 false,如果需要真正的旧版独立文件,请改为 true。
INSTALL_OLD_DOCKER_COMPOSE_BINARY=false # 保持为 false,使用符号链接兼容

# --- 字体颜色定义 ---
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# --- 函数定义 ---
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[0;33m[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查是否以root权限运行
check_root() {
    if [[ $EUID -ne 0 ]]; then
        log_error "此脚本必须以 root 用户身份运行。"
        log_error "请使用 'sudo ./install_docker_latest.sh' 运行,或直接切换到 root 用户再执行。"
        exit 1
    fi
}

# 添加当前用户到docker组
add_user_to_docker_group() {
    # 尝试获取原始非root用户,如果脚本是通过 sudo 运行,USER 可能是 root
    # 对于直接以 root 登录运行脚本的情况,用户组操作有些特殊,
    # 因为通常我们希望将非 root 用户添加到 docker 组。
    # 这里我们假定 $SUDO_USER 变量能够提供原始的非 root 用户名。
    # 如果是直接以 root 用户登录执行脚本,那么 $SUDO_USER 可能为空,
    # 这种情况下,root 用户本身不需要添加到 docker 组。
    TARGET_USER="${SUDO_USER:-$USER}" # 如果 SUDO_USER 不为空则用它,否则用当前用户

    if [ "$TARGET_USER" == "root" ]; then
        log_warn "当前用户是 root,root 用户通常不需要添加到 'docker' 组。"
        log_warn "如果您希望其他非 root 用户可以执行 docker 命令,请手动将他们添加到 'docker' 组。"
    else
        log_info "将用户 '$TARGET_USER' 添加到 'docker' 组..."
        usermod -aG docker "$TARGET_USER"
        if [ $? -eq 0 ]; then
            log_info "用户 '$TARGET_USER' 已添加到 'docker' 组。您需要注销并重新登录或运行 'newgrp docker' 以应用更改。"
            log_info "运行 'newgrp docker' 以立即应用组更改(可能导致当前shell会话中断)。"
            log_warn "建议在脚本运行完毕后,手动注销并重新登录以确保权限完全生效。"
        else
            log_error "添加用户到 'docker' 组失败。请检查是否已存在该用户或权限问题。"
        fi
    fi
}


# --- 主脚本逻辑 ---

check_root # 确保脚本以 root 权限运行

log_info "正在更新系统软件包列表并安装必要的依赖..."
if ! apt update; then
    log_error "apt update 失败!请检查网络连接或源配置。退出。"
    exit 1
fi
if ! apt install -y ca-certificates curl gnupg lsb-release; then
    log_error "安装必要依赖失败!退出。"
    exit 1
fi

log_info "正在卸载可能存在的旧版 Docker 相关软件包..."
# 尝试卸载可能存在的旧版本Docker
apt remove -y docker docker-engine docker.io containerd runc
apt autoremove -y

log_info "正在设置 Docker 官方 APT 仓库..."

# 创建密钥目录
if ! install -m 0755 -d /etc/apt/keyrings; then
    log_error "创建 /etc/apt/keyrings 目录失败。退出。"
    exit 1
fi

# 下载并添加 GPG 密钥
if ! curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg; then
    log_error "下载或添加 Docker GPG 密钥失败。退出。"
    exit 1
fi
chmod a+r /etc/apt/keyrings/docker.gpg # 确保权限正确

# 添加 Docker APT 仓库
if ! echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null; then
    log_error "添加 Docker APT 仓库失败。退出。"
    exit 1
fi

log_info "正在更新 apt 软件包索引以包含 Docker 仓库..."
if ! apt update; then
    log_error "apt update (包含Docker仓库) 失败!退出。"
    exit 1
fi

log_info "正在安装 Docker Engine (docker-ce, docker-ce-cli, containerd.io) 和 Docker Compose 插件..."
if ! apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then
    log_error "安装 Docker Engine 和 docker-compose-plugin 失败!退出。"
    exit 1
fi

log_info "Docker Engine 和 Docker Compose 插件安装成功。"

# 如果选择安装旧版独立的 docker-compose 可执行文件
if [ "$INSTALL_OLD_DOCKER_COMPOSE_BINARY" = true ]; then
    log_info "正在下载并安装旧版独立的 docker-compose 可执行文件..."
    # 查找适用于当前架构的最新 docker-compose 版本
    DOCKER_COMPOSE_URL=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep browser_download_url | grep "docker-compose-$(uname -s)-$(uname -m)" | cut -d '"' -f 4)
    if [ -z "$DOCKER_COMPOSE_URL" ]; then
        log_error "未能找到最新的 docker-compose 下载链接。跳过旧版独立文件安装。"
    else
        log_info "下载链接: $DOCKER_COMPOSE_URL"
        if ! curl -L "$DOCKER_COMPOSE_URL" -o /usr/local/bin/docker-compose; then
            log_error "下载旧版 docker-compose 可执行文件失败。"
        else
            if ! chmod +x /usr/local/bin/docker-compose; then
                log_error "为旧版 docker-compose 添加执行权限失败。"
            else
                log_info "旧版独立的 docker-compose 安装成功。"
                log_info "验证旧版 docker-compose 版本:"
                # 直接运行旧版 docker-compose
                /usr/local/bin/docker-compose --version
            fi
        fi
    fi
else
    # 在 /usr/local/bin 中创建符号链接,将 docker-compose 指向 docker cli
    log_info "创建 'docker-compose' 符号链接,以兼容旧版命令..."
    if [ -f "/usr/local/bin/docker-compose" ]; then
        log_warn "检测到 /usr/local/bin/docker-compose 已存在,正在删除旧链接/文件..."
        rm /usr/local/bin/docker-compose
    fi

    # 尝试找到 docker 可执行文件的位置,通常在 /usr/bin/docker
    DOCKER_CLI_PATH=$(command -v docker)
    if [ -z "$DOCKER_CLI_PATH" ]; then
        log_error "未找到 'docker' CLI 可执行文件。无法创建 docker-compose 兼容链接。"
    else
        # 创建符号链接,使 docker-compose 调用 'docker compose'
        ln -s "$DOCKER_CLI_PATH" /usr/local/bin/docker-compose
        if [ $? -eq 0 ]; then
            log_info "已成功创建 /usr/local/bin/docker-compose -> $DOCKER_CLI_PATH 的符号链接。"
            log_info "现在可以使用 'docker-compose' 命令来调用 'docker compose' 功能。"
        else
            log_error "创建 'docker-compose' 符号链接失败。"
        fi
    fi
fi

# 添加用户到docker组
add_user_to_docker_group

log_info "验证 Docker Engine 安装..."
if ! docker run hello-world; then
    log_error "Docker Engine 验证失败!请检查 Docker 服务状态。退出。"
    exit 1
fi

log_info "验证 Docker Compose 插件安装 (新方式:docker compose)..."
if ! docker compose version; then
    log_error "Docker Compose 插件验证失败!退出。"
    exit 1
fi

# 验证 docker-compose 兼容链接
if [ -L "/usr/local/bin/docker-compose" ]; then
    log_info "验证 'docker-compose' 兼容性(通过符号链接)..."
    if ! docker-compose version; then
         log_error "'docker-compose' 兼容性验证失败,可能符号链接有问题。"
    else
        log_info "'docker-compose' 命令兼容性良好。"
    fi
fi


log_info "所有操作完成。请在尝试运行 Docker 命令前,重新登录您的会话或运行 'newgrp docker'。"
log_info "感谢使用!"

4.2 如何使用脚本

  1. 保存脚本: 将上述代码复制并保存到一个文件,例如 install_docker.sh
  2. 添加执行权限: 在终端中运行 chmod +x install_docker.sh
  3. 运行脚本: 使用 sudo 权限运行脚本:sudo ./install_docker.sh

4.3 脚本功能详解

  • 配置选项 (INSTALL_OLD_DOCKER_COMPOSE_BINARY):

    • 此变量控制是否安装旧版独立的 docker-compose 二进制文件。
    • 默认设置为 false,这意味着脚本将安装 Docker Compose 插件 (docker-compose-plugin),并创建一个符号链接,使 docker-compose 命令能够调用 docker compose(这是 Docker 官方推荐的新用法)。
    • 如果你有特殊需求,需要旧版独立的 docker-compose 可执行文件,可以将其设置为 true
  • 日志与权限检查:

    • log_infolog_warnlog_error 函数用于输出带有颜色标记的信息、警告和错误,提高脚本的可读性。
    • check_root 函数确保脚本以 root 用户权限运行,这是安装系统级软件所必需的。
  • 添加用户到 Docker 组 (add_user_to_docker_group):

    • 此函数将当前用户(或通过 sudo 运行时的原始用户)添加到 docker 用户组。
    • 重要提示: 将用户添加到 docker 组后,该用户无需 sudo 即可运行 Docker 命令。为了使更改生效,你需要注销并重新登录,或者在当前会话中运行 newgrp docker 命令(这可能会导致当前 shell 会话中断)。
  • 核心安装流程:

    1. 更新依赖: 更新 apt 软件包列表并安装 ca-certificatescurlgnupglsb-release 等必要工具。
    2. 卸载旧版本: 尝试卸载系统中可能存在的旧版 Docker 相关软件包,避免冲突。
    3. 设置 Docker 官方 APT 仓库:
      • 创建 /etc/apt/keyrings 目录。
      • 下载并添加 Docker 官方的 GPG 密钥,用于验证软件包的真实性。
      • 将 Docker 的 APT 仓库地址添加到 /etc/apt/sources.list.d/docker.list 文件中。
    4. 安装 Docker Engine 和 Docker Compose 插件:
      • 再次更新 apt 软件包索引,以包含新的 Docker 仓库。
      • 安装 docker-ce (Docker Engine 社区版), docker-ce-cli (Docker 命令行客户端), containerd.io (容器运行时), docker-buildx-plugin (构建工具) 和 docker-compose-plugin (Docker Compose 插件)。
  • Docker Compose 兼容性处理:

    • 如果 INSTALL_OLD_DOCKER_COMPOSE_BINARY 为 false(默认),脚本会在 /usr/local/bin/ 目录下创建一个名为 docker-compose 的符号链接,指向 docker 可执行文件。
    • 这意味着,即使你输入 docker-compose 命令,它实际上也会调用 docker compose 命令,从而兼容旧的 docker-compose 语法,同时使用新的插件功能。
  • 安装验证:

    • 脚本会运行 docker run hello-world 来验证 Docker Engine 是否正确安装并运行。
    • 接着,它会运行 docker compose version 来验证 Docker Compose 插件是否可用。
    • 如果创建了 docker-compose 符号链接,还会额外验证 docker-compose version 命令的兼容性。
  • 后续步骤:

    • 脚本最后会提示用户,在尝试运行 Docker 命令之前,需要重新登录会话或运行 newgrp docker,以确保用户组权限的完全生效