通过脚本在 Debian/Ubuntu 系统上一键切换 APT 源
如Dockerfile中 使用某个源(比如 aliyun)
假设你的目录结构是:
.
├── Dockerfile
└── switch-apt-source.sh
FROM ubuntu:22.04# 把脚本拷贝到镜像中
COPY switch-apt-source.sh /usr/local/bin/switch-apt-source.sh# 赋可执行权限并执行脚本,切换为 aliyun 源,然后更新并安装示例软件
RUN chmod +x /usr/local/bin/switch-apt-source.sh \&& /usr/local/bin/switch-apt-source.sh -s aliyun --clean-cache --remove-extra-sources -y \&& apt-get install -y curl vim \&& rm -rf /var/lib/apt/lists/*
使用指南
脚本版本: switch-apt-source.sh v1.0.7
可用镜像源:
-
huawei (HTTP)-华为(默认)
-
aliyun (HTTP)-阿里
-
ustc (HTTPS)-中科大
-
tsinghua (HTTPS)-清华
-
official (HTTP)-官方
Usage: ./apt.sh [ -s <source_name> ] [ -r ] [ --dry-run ] [ --clean-cache ] [ --remove-extra-sources ] [-y]
-h 帮助
-s|–source 指定要切换的镜像源(默认为 huawei)
-r|–with-src 额外写入 deb-src 源行(源码包)
–dry-run 仅打印最终会写入的内容,不实际覆盖源文件
–clean-cache 切换完源后自动执行 apt-get update && apt-get autoclean
–remove-extra-sources 移除 /etc/apt/sources.list.d/ 目录下的所有额外源
-y|–assume-yes 自动回答所有提示为 yes(适用于自动化场景)
示例:
./apt.sh -h
./apt.sh -s aliyun
./apt.sh --source tsinghua --with-src -y
./apt.sh -s ustc --dry-run
./apt.sh -s official --clean-cache --remove-extra-sources -y
#!/usr/bin/env bash
#
# switch-apt-source.sh
# 用于在 Debian/Ubuntu 系统上一键切换 APT 源
#
# Usage:
# ./switch-apt-source.sh -s <source_name> [-r] [--dry-run] [--clean-cache] [--remove-extra-sources] [-y]
#
# 可选 source_name:
# huawei -> 华为云镜像 (HTTP)
# aliyun -> 阿里云镜像 (HTTP)
# tsinghua -> 清华大学 TUNA 镜像 (HTTPS)
# ustc -> 中国科学技术大学镜像 (HTTPS)
# official -> 官方默认镜像 (HTTP)
# (默认使用 huawei)
#
# 主要优化:
# 1. 智能处理 HTTPS 源依赖问题
# 2. 减少不必要的包安装
# 3. 提高切换速度
# 4. 增强最小化系统兼容性set -euo pipefail#################################
# 0. 常量与全局变量 #
#################################
SCRIPT_VERSION="1.0.7"
BACKUP_DIR="/var/backups/apt-sources"
SRC_FILE="/etc/apt/sources.list"
SRC_DIR="/etc/apt/sources.list.d"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)# 颜色定义
RED='\033[1;31m'
GREEN='\033[1;32m'
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color#################################
# 1. 镜像源配置 #
#################################
declare -A MIRROR_CONFIG=(# 名称 Debian基础URL Debian安全URL Ubuntu基础URL Ubuntu安全URL[huawei]="http://repo.huaweicloud.com/debian http://repo.huaweicloud.com/debian-security http://repo.huaweicloud.com/ubuntu http://repo.huaweicloud.com/ubuntu"[aliyun]="http://mirrors.aliyun.com/debian http://mirrors.aliyun.com/debian-security http://mirrors.aliyun.com/ubuntu http://mirrors.aliyun.com/ubuntu"[tsinghua]="https://mirrors.tuna.tsinghua.edu.cn/debian https://mirrors.tuna.tsinghua.edu.cn/debian-security https://mirrors.tuna.tsinghua.edu.cn/ubuntu https://mirrors.tuna.tsinghua.edu.cn/ubuntu"[ustc]="https://mirrors.ustc.edu.cn/debian https://mirrors.ustc.edu.cn/debian-security https://mirrors.ustc.edu.cn/ubuntu https://mirrors.ustc.edu.cn/ubuntu"[official]="http://deb.debian.org/debian http://security.debian.org/debian-security http://archive.ubuntu.com/ubuntu http://security.ubuntu.com/ubuntu"
)# HTTP 回退源(当 HTTPS 失败时使用)
declare -A HTTP_FALLBACKS=([tsinghua]="http://mirrors.tuna.tsinghua.edu.cn"[ustc]="http://mirrors.ustc.edu.cn"
)# 定义 HTTP 源列表
HTTP_SOURCES=("huawei" "aliyun" "official")#################################
# 2. 函数:打印使用说明 #
#################################
print_usage() {echo -e "${BLUE}脚本版本: switch-apt-source.sh v${SCRIPT_VERSION}${NC}"echo -e "${BLUE}可用镜像源:${NC}"for source in "${!MIRROR_CONFIG[@]}"; doif [[ " ${HTTP_SOURCES[*]} " =~ " ${source} " ]]; thenecho " - $source (HTTP)"elseecho " - $source (HTTPS)"fidonecat << EOFUsage: $0 [ -s <source_name> ] [ -r ] [ --dry-run ] [ --clean-cache ] [ --remove-extra-sources ] [-y]-s|--source 指定要切换的镜像源(默认为 huawei)-r|--with-src 额外写入 deb-src 源行(源码包)--dry-run 仅打印最终会写入的内容,不实际覆盖源文件--clean-cache 切换完源后自动执行 apt-get update && apt-get autoclean--remove-extra-sources 移除 /etc/apt/sources.list.d/ 目录下的所有额外源-y|--assume-yes 自动回答所有提示为 yes(适用于自动化场景)示例:$0 -s aliyun$0 --source tsinghua --with-src -y$0 -s ustc --dry-run$0 -s official --clean-cache --remove-extra-sources -y
EOFexit 0
}#################################
# 3. 函数:列出可选镜像源 #
#################################
list_sources() {echo -e "${BLUE}支持的镜像源列表:${NC}"for source in "${!MIRROR_CONFIG[@]}"; doif [[ " ${HTTP_SOURCES[*]} " =~ " ${source} " ]]; thenecho " - $source (HTTP)"elseecho " - $source (HTTPS)"fidoneexit 0
}#################################
# 4. 函数:检查 HTTPS 依赖 #
#################################
check_https_dependencies() {# 仅对 HTTPS 源检查依赖if [[ " ${HTTP_SOURCES[*]} " =~ " ${SOURCE_NAME} " ]]; thenreturn 0 # HTTP 源不需要额外依赖fi# 检查是否已安装 ca-certificatesif ! dpkg -s ca-certificates &>/dev/null; thenecho -e "${RED}错误:系统缺少 ca-certificates 包,无法访问 HTTPS 源${NC}"echo -e "${YELLOW}请先安装 ca-certificates:apt-get install -y ca-certificates${NC}"# 检查是否有 HTTP 回退源if [[ -n "${HTTP_FALLBACKS[$SOURCE_NAME]:-}" ]]; thenif [[ "$ASSUME_YES" == true ]]; thenecho -e "${YELLOW}自动回退到 HTTP 源...${NC}"BASE_URL="http://${BASE_URL#*://}"SECURITY_URL="http://${SECURITY_URL#*://}"return 0elseecho -e "${YELLOW}是否回退到 HTTP 源?(y/N)${NC}"read -rp " " ynif [[ "$yn" =~ ^[Yy]$ ]]; thenecho -e "${YELLOW}正在回退到 HTTP 源...${NC}"BASE_URL="http://${BASE_URL#*://}"SECURITY_URL="http://${SECURITY_URL#*://}"return 0fififiexit 1fireturn 0
}
#################################
# 5. 参数解析 #
#################################
SOURCE_NAME="huawei" # 默认华为源
ADD_DEBSRC=false # 是否写入 deb-src
DRY_RUN=false # 是否仅打印,不实际覆盖
CLEAN_CACHE=false # 是否切换完后自动清理缓存
REMOVE_EXTRA_SOURCES=false # 是否移除额外源
ASSUME_YES=false # 是否自动回答 yeswhile [[ $# -gt 0 ]]; docase "$1" in-s|--source)if [[ -n "${2-}" && ! "$2" =~ ^- ]]; thenSOURCE_NAME="$2"shiftelseecho -e "${RED}错误:-s/--source 选项缺少参数${NC}"exit 1fi;;--source=*)SOURCE_NAME="${1#*=}";;-r|--with-src)ADD_DEBSRC=true;;--dry-run)DRY_RUN=true;;--clean-cache)CLEAN_CACHE=true;;--remove-extra-sources)REMOVE_EXTRA_SOURCES=true;;-y|--assume-yes)ASSUME_YES=true;;-l|--list)list_sources;;-h|--help)print_usage;;*)echo -e "${RED}错误:未知选项 '$1'${NC}"print_usageexit 1;;esacshift
done#################################
# 6. 验证镜像源名称是否有效 #
#################################
if [[ -z "${MIRROR_CONFIG[$SOURCE_NAME]-}" ]]; thenecho -e "${RED}错误:不支持的镜像源 '${SOURCE_NAME}'${NC}"echo -e "${BLUE}可用源:${NC}"for source in "${!MIRROR_CONFIG[@]}"; doecho " - $source"doneexit 1
fi#################################
# 7. 检查是否以 root 执行 #
#################################
if [[ $EUID -ne 0 ]]; thenecho -e "${RED}错误:请使用 root 权限运行此脚本${NC}"exit 1
fi#################################
# 8. 获取操作系统信息 #
#################################
if [[ ! -f /etc/os-release ]]; thenecho -e "${RED}错误:无法识别操作系统,请确保是 Debian/Ubuntu 系统。${NC}"exit 1
fi# 加载系统信息
. /etc/os-release
DIST_ID=$(echo "${ID:-unknown}" | tr '[:upper:]' '[:lower:]')
DIST_CODENAME=$(echo "${VERSION_CODENAME:-}" | tr '[:upper:]' '[:lower:]')if [[ -z "$DIST_CODENAME" ]]; then# 尝试使用 lsb_release 获取版本代号if command -v lsb_release &>/dev/null; thenDIST_CODENAME=$(lsb_release -cs)elseecho -e "${YELLOW}警告:无法获取系统版本代号,使用默认值 bookworm${NC}"DIST_CODENAME="bookworm"fi
fi# 获取 Debian 主版本号
DEB_MAJOR_VERSION=""
if [[ "$DIST_ID" == "debian" ]]; thenDEB_MAJOR_VERSION=$(echo "${VERSION_ID:-}" | cut -d. -f1)
fi# 系统架构
DIST_ARCH=$(dpkg --print-architecture)#################################
# 9. 解析镜像配置 #
#################################
read -r -a URLS <<< "${MIRROR_CONFIG[$SOURCE_NAME]}"
if [[ ${#URLS[@]} -lt 4 ]]; thenecho -e "${RED}错误:镜像源配置不完整,请检查脚本配置${NC}"exit 1
fi# 根据系统类型选择 URL
if [[ "$DIST_ID" == "debian" ]]; thenBASE_URL="${URLS[0]}"SECURITY_URL="${URLS[1]}"
elseBASE_URL="${URLS[2]}"SECURITY_URL="${URLS[3]}"
fi# 检查 HTTPS 依赖
HAS_CURL=false
if check_https_dependencies; thenHAS_CURL=true
fi#################################
# 10. 设置组件和安全源路径 #
#################################
if [[ "$DIST_ID" == "debian" ]]; thenCOMPONENTS="main contrib non-free"# Debian 12+ 默认再加 non-free-firmwareif [[ -n "$DEB_MAJOR_VERSION" && "$DEB_MAJOR_VERSION" -ge 12 ]]; thenCOMPONENTS="${COMPONENTS} non-free-firmware"fi# 安全源路径:Debian 9 及更早 "<codename>/updates",10+ "<codename>-security"if [[ -n "$DEB_MAJOR_VERSION" && "$DEB_MAJOR_VERSION" -lt 10 ]]; thenSECURITY_SUITE="${DIST_CODENAME}/updates"elseSECURITY_SUITE="${DIST_CODENAME}-security"fi
else# Ubuntu 系列COMPONENTS="main restricted universe multiverse"SECURITY_SUITE="${DIST_CODENAME}-security"
fi#################################
# 11. 备份与旧备份自动清理 #
#################################
mkdir -p "$BACKUP_DIR"# 自动清理旧备份
if [[ $(find "$BACKUP_DIR" -type f -name 'sources.list.bak.*' 2>/dev/null | wc -l) -gt 0 ]]; thenfind "$BACKUP_DIR" -type f -name 'sources.list.bak.*' -mtime +30 -delete 2>/dev/null || true
fiBACKUP_FILE="${BACKUP_DIR}/sources.list.bak.${TIMESTAMP}"# 检查源文件状态
if [[ ! -e "$SRC_FILE" ]]; then[[ "$ASSUME_YES" == false ]] && echo -e "${YELLOW}源文件不存在,自动创建新文件${NC}"touch "$SRC_FILE"chmod 644 "$SRC_FILE"
elif [[ ! -s "$SRC_FILE" ]]; then[[ "$ASSUME_YES" == false ]] && echo -e "${YELLOW}源文件为空,继续写入新配置${NC}"
fi# 文件权限检查
if [[ -e "$SRC_FILE" && ! -w "$SRC_FILE" ]]; thenchmod u+w "$SRC_FILE"
fiecho "正在备份原文件: ${SRC_FILE} -> ${BACKUP_FILE}"
cp -f "$SRC_FILE" "$BACKUP_FILE"#################################
# 12. 处理额外源 (可选) #
#################################
if [[ "$REMOVE_EXTRA_SOURCES" == true ]]; then[[ "$ASSUME_YES" == false ]] && echo -e "${YELLOW}正在移除额外源...${NC}"if [[ -d "$SRC_DIR" ]]; thenBACKUP_EXTRA_DIR="${BACKUP_DIR}/sources.list.d.${TIMESTAMP}"mkdir -p "$BACKUP_EXTRA_DIR"if find "$SRC_DIR" -maxdepth 1 -type f | grep -q .; thenmv -f "${SRC_DIR}"/* "$BACKUP_EXTRA_DIR/" 2>/dev/null || true[[ "$ASSUME_YES" == false ]] && echo -e "${GREEN}已备份并移除额外源到: ${BACKUP_EXTRA_DIR}${NC}"fifi
fi#################################
# 13. 生成临时文件并写入新源 #
#################################
TMPFILE=$(mktemp "/tmp/sources.list.XXXXXX")
chmod 644 "$TMPFILE"{echo "# ${SOURCE_NAME} 镜像源 (自动生成) - ${PRETTY_NAME} [${DIST_ARCH}]"echo "# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')"echo "# 脚本版本: switch-apt-source.sh v${SCRIPT_VERSION}"echo "# 协议: $(echo "$BASE_URL" | cut -d: -f1)"echoecho "deb [arch=${DIST_ARCH}] ${BASE_URL} ${DIST_CODENAME} ${COMPONENTS}"$ADD_DEBSRC && echo "deb-src [arch=${DIST_ARCH}] ${BASE_URL} ${DIST_CODENAME} ${COMPONENTS}"echoecho "deb [arch=${DIST_ARCH}] ${SECURITY_URL} ${SECURITY_SUITE} ${COMPONENTS}"$ADD_DEBSRC && echo "deb-src [arch=${DIST_ARCH}] ${SECURITY_URL} ${SECURITY_SUITE} ${COMPONENTS}"echoecho "deb [arch=${DIST_ARCH}] ${BASE_URL} ${DIST_CODENAME}-updates ${COMPONENTS}"$ADD_DEBSRC && echo "deb-src [arch=${DIST_ARCH}] ${BASE_URL} ${DIST_CODENAME}-updates ${COMPONENTS}"echoecho "deb [arch=${DIST_ARCH}] ${BASE_URL} ${DIST_CODENAME}-backports ${COMPONENTS}"$ADD_DEBSRC && echo "deb-src [arch=${DIST_ARCH}] ${BASE_URL} ${DIST_CODENAME}-backports ${COMPONENTS}"
} > "$TMPFILE"#################################
# 14. Dry-run 模式处理 #
#################################
if [[ "$DRY_RUN" == true ]]; thenecho -e "\n${BLUE}---- Dry-run 模式:以下内容将写入 ${SRC_FILE} ----${NC}"cat "$TMPFILE"rm -f "$TMPFILE"echo -e "\n${GREEN}✓ Dry-run 完成,未实际覆盖任何文件。${NC}"exit 0
fi#################################
# 15. 原子替换 sources.list #
#################################
mv -f "$TMPFILE" "$SRC_FILE"echo -e "\n${GREEN}✓ 已成功将 APT 源切换为: ${SOURCE_NAME}${NC}"
echo "主仓库: ${BASE_URL}"
echo "安全更新: ${SECURITY_URL}"
echo "组件: ${COMPONENTS}"
$ADD_DEBSRC && echo "已额外写入 deb-src 行。"#################################
# 16. 后续操作提示 #
#################################
echo -e "\n${BLUE}▷ 下一步操作建议:${NC}"
echo " 1. 更新软件包列表: apt update"
echo " 2. 升级所有软件包: apt upgrade"echo -e "\n${BLUE}▷ 回滚方法:${NC}"
echo " cp -v ${BACKUP_FILE} ${SRC_FILE}"
[[ "$REMOVE_EXTRA_SOURCES" == true ]] && echo " mv ${BACKUP_EXTRA_DIR}/* ${SRC_DIR}/"#################################
# 17. 清理缓存(可选) #
#################################
if [[ "$CLEAN_CACHE" == true ]]; thenecho -e "\n${BLUE}正在执行 apt-get update && apt-get autoclean ...${NC}"if apt-get update -y; thenapt-get autoclean -yecho -e "${GREEN}✓ 缓存清理完成。${NC}"elseecho -e "${YELLOW}警告:apt-get update 失败,跳过 autoclean${NC}"fi
fiexit 0