setup-mysql-cli.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #!/bin/bash
  2. # shellcheck disable=SC1003 # 本脚本有意输出 shell 续行反斜杠,触发 SC1003 属预期
  3. # ============================================
  4. # MySQL CLI 工具安装脚本(官方 MySQL 客户端)
  5. # 从 Oracle MySQL 官方仓库安装真正的 MySQL 客户端
  6. # 替代 Debian Trixie 默认的 mariadb-client
  7. # ============================================
  8. set -e
  9. MYSQL_VERSION="8.4.4"
  10. BIN_INSTALL_DIR="/usr/local/bin"
  11. # 颜色定义
  12. GREEN='\033[0;32m'
  13. YELLOW='\033[1;33m'
  14. RED='\033[0;31m'
  15. NC='\033[0m'
  16. info() { echo -e "${GREEN}[info]${NC} $1"; }
  17. warn() { echo -e "${YELLOW}[warn]${NC} $1"; }
  18. error() { echo -e "${RED}[error]${NC} $1"; }
  19. # ------------------------------------------
  20. # 检查是否已安装正确的 MySQL 客户端
  21. # ------------------------------------------
  22. check_mysql_client() {
  23. if command -v mysql >/dev/null 2>&1; then
  24. local ver
  25. ver=$(mysql --version 2>&1)
  26. if echo "$ver" | grep -qi "mariadb"; then
  27. warn "当前是 MariaDB 客户端: $ver"
  28. warn "(MariaDB 用 --ssl=0,MySQL 用 --ssl-mode=DISABLED)"
  29. warn "将替换为官方 MySQL 客户端..."
  30. return 1
  31. fi
  32. if mysql --help 2>&1 | grep -q "ssl-mode"; then
  33. info "✅ 官方 MySQL 客户端已安装: $ver"
  34. return 0
  35. else
  36. warn "MySQL 客户端版本过旧,不支持 --ssl-mode"
  37. return 1
  38. fi
  39. fi
  40. return 1
  41. }
  42. # ------------------------------------------
  43. # 清理磁盘空间
  44. # ------------------------------------------
  45. cleanup_space() {
  46. local needed_kb=${1:-51200} # 默认需要 50MB
  47. local avail_kb
  48. avail_kb=$(df /var/cache/apt/archives/ 2>/dev/null | awk 'NR==2 {print $4}') # 单位 KB
  49. if [ -z "$avail_kb" ] || [ "$avail_kb" -lt "$needed_kb" ]; then
  50. warn "磁盘空间不足,尝试清理..."
  51. apt-get clean 2>/dev/null || true
  52. rm -rf /tmp/* /var/tmp/* 2>/dev/null || true
  53. avail_kb=$(df /var/cache/apt/archives/ 2>/dev/null | awk 'NR==2 {print $4}')
  54. if [ -n "$avail_kb" ] && [ "$avail_kb" -lt "$needed_kb" ]; then
  55. warn "清理后空间仍不足 ($((avail_kb / 1024)) MB 可用)"
  56. return 1
  57. fi
  58. fi
  59. return 0
  60. }
  61. # ------------------------------------------
  62. # 方式一:从 MySQL 官方 APT 仓库安装
  63. # ------------------------------------------
  64. install_from_apt() {
  65. info "方式一:从 MySQL 官方 APT 仓库安装..."
  66. # 检查磁盘空间
  67. if ! cleanup_space; then
  68. warn "磁盘空间不足,跳过 APT 安装方式"
  69. return 1
  70. fi
  71. # 确保 wget 可用
  72. if ! command -v wget >/dev/null 2>&1; then
  73. apt-get update -qq && apt-get install -y -qq wget 2>/dev/null || {
  74. warn "wget 不可用,跳过 APT 安装方式"
  75. return 1
  76. }
  77. fi
  78. local mysql_repo_deb="/tmp/mysql-apt-config.deb"
  79. # 清理旧配置
  80. rm -f /etc/apt/sources.list.d/mysql.list /etc/apt/sources.list.d/mysql-*.list
  81. # 下载 MySQL APT 配置包
  82. info "下载 MySQL APT 配置包..."
  83. wget -q "https://repo.mysql.com/mysql-apt-config_0.8.33-1_all.deb" -O "$mysql_repo_deb" || {
  84. warn "下载 MySQL APT 配置包失败"
  85. return 1
  86. }
  87. # 非交互式安装配置包
  88. DEBIAN_FRONTEND=noninteractive dpkg -i "$mysql_repo_deb" 2>/dev/null || true
  89. # 修正 sources.list 中的 codename
  90. # MySQL APT 仓库不支持 Debian Trixie,强制使用 bookworm
  91. if [ -f /etc/apt/sources.list.d/mysql.list ]; then
  92. sed -i "s/$(lsb_release -sc 2>/dev/null || echo 'unknown')/bookworm/g" \
  93. /etc/apt/sources.list.d/mysql.list 2>/dev/null || true
  94. fi
  95. # 更新 APT 缓存
  96. info "更新 APT 缓存..."
  97. apt-get update -qq 2>/dev/null || {
  98. warn "APT 更新失败"
  99. return 1
  100. }
  101. # 安装 MySQL 客户端(优先 mysql-client,备用 mysql-community-client)
  102. info "安装 mysql-client..."
  103. apt-get install -y -qq mysql-client 2>/dev/null || {
  104. warn "mysql-client 安装失败,尝试 mysql-community-client..."
  105. apt-get install -y -qq mysql-community-client 2>/dev/null || {
  106. warn "APT 安装 MySQL 客户端失败"
  107. return 1
  108. }
  109. }
  110. return 0
  111. }
  112. # ------------------------------------------
  113. # 方式二:直接下载 MySQL 客户端二进制
  114. # ------------------------------------------
  115. install_from_binary() {
  116. info "方式二:直接下载 MySQL 客户端二进制..."
  117. # 检查磁盘空间(二进制包大约 300MB 解压,但只需要 2 个文件 ~5MB)
  118. local needed_kb=102400 # 100MB for download + extract
  119. local avail_kb
  120. avail_kb=$(df /tmp 2>/dev/null | awk 'NR==2 {print $4}')
  121. if [ -n "$avail_kb" ] && [ "$avail_kb" -lt "$needed_kb" ]; then
  122. error "磁盘空间不足 ($((avail_kb / 1024)) MB 可用,需要 ~100 MB)"
  123. return 1
  124. fi
  125. # 确保 wget 可用
  126. if ! command -v wget >/dev/null 2>&1; then
  127. error "wget 不可用,无法下载"
  128. return 1
  129. fi
  130. local tmp_dir
  131. tmp_dir=$(mktemp -d)
  132. local tarball="${tmp_dir}/mysql-${MYSQL_VERSION}-linux-glibc2.28-x86_64.tar.xz"
  133. local base_url="https://dev.mysql.com/get/Downloads/MySQL-8.4"
  134. info "下载 MySQL ${MYSQL_VERSION} 客户端 (约 300MB)..."
  135. wget -q --show-progress "${base_url}/$(basename "$tarball")" -O "$tarball" || {
  136. error "下载失败"
  137. rm -rf "$tmp_dir"
  138. return 1
  139. }
  140. # 确保 xz 解压工具可用(.tar.xz 格式需要)
  141. if ! command -v xz >/dev/null 2>&1; then
  142. warn "xz 未安装,尝试安装 xz-utils..."
  143. apt-get install -y -qq xz-utils 2>/dev/null || {
  144. warn "apt 安装 xz-utils 失败,改用 Python lzma 模块解压..."
  145. # Python 内置了 lzma 模块,无需额外安装
  146. }
  147. fi
  148. info "解压(仅提取 mysql 和 mysqladmin)..."
  149. local mysql_bin mysqladmin_bin
  150. local extract_dir="${tmp_dir}/extract"
  151. mkdir -p "$extract_dir"
  152. if command -v xz >/dev/null 2>&1; then
  153. # 有 xz:直接用 tar 解压
  154. mysql_bin=$(tar -tJf "$tarball" 2>/dev/null | grep -E 'bin/mysql$' | head -1)
  155. mysqladmin_bin=$(tar -tJf "$tarball" 2>/dev/null | grep -E 'bin/mysqladmin$' | head -1)
  156. if [ -n "$mysql_bin" ] && [ -n "$mysqladmin_bin" ]; then
  157. tar -xJf "$tarball" -C "$extract_dir" "$mysql_bin" "$mysqladmin_bin"
  158. else
  159. tar -xJf "$tarball" -C "$extract_dir"
  160. fi
  161. else
  162. # 没有 xz:用 Python lzma 模块解压
  163. info "使用 Python lzma 模块解压..."
  164. python3 -c "
  165. import lzma, tarfile, sys, os
  166. tarball = '$tarball'
  167. extract_to = '$extract_dir'
  168. with lzma.open(tarball) as f:
  169. with tarfile.open(fileobj=f, mode='r|') as tar:
  170. for member in tar:
  171. # 只提取 bin/mysql 和 bin/mysqladmin
  172. if member.name.endswith('/bin/mysql') or member.name.endswith('/bin/mysqladmin'):
  173. tar.extract(member, path=extract_to)
  174. " 2>/dev/null || {
  175. # 如果 Python 也失败,解压全部
  176. info "Python 选择性提取失败,解压全部文件..."
  177. python3 -c "
  178. import lzma, tarfile
  179. tarball = '$tarball'
  180. extract_to = '$extract_dir'
  181. with lzma.open(tarball) as f:
  182. with tarfile.open(fileobj=f, mode='r|') as tar:
  183. tar.extractall(path=extract_to)
  184. " 2>/dev/null || {
  185. error "Python 解压也失败"
  186. rm -rf "$tmp_dir"
  187. return 1
  188. }
  189. }
  190. fi
  191. # 从解压目录复制二进制文件
  192. local mysql_src mysqladmin_src
  193. mysql_src=$(find "$extract_dir" -name 'mysql' -type f -path '*/bin/mysql' | head -1)
  194. mysqladmin_src=$(find "$extract_dir" -name 'mysqladmin' -type f -path '*/bin/mysqladmin' | head -1)
  195. if [ -n "$mysql_src" ] && [ -n "$mysqladmin_src" ]; then
  196. cp "$mysql_src" "${BIN_INSTALL_DIR}/mysql"
  197. cp "$mysqladmin_src" "${BIN_INSTALL_DIR}/mysqladmin"
  198. else
  199. # 最后尝试:直接找任何 mysql 二进制
  200. mysql_src=$(find "$extract_dir" -name 'mysql' -type f | head -1)
  201. mysqladmin_src=$(find "$extract_dir" -name 'mysqladmin' -type f | head -1)
  202. if [ -z "$mysql_src" ]; then
  203. error "解压后找不到 mysql 二进制文件"
  204. rm -rf "$tmp_dir"
  205. return 1
  206. fi
  207. cp "$mysql_src" "${BIN_INSTALL_DIR}/mysql"
  208. [ -n "$mysqladmin_src" ] && cp "$mysqladmin_src" "${BIN_INSTALL_DIR}/mysqladmin"
  209. fi
  210. chmod +x "${BIN_INSTALL_DIR}/mysql" "${BIN_INSTALL_DIR}/mysqladmin"
  211. rm -rf "$tmp_dir"
  212. info "二进制安装完成"
  213. return 0
  214. }
  215. # ------------------------------------------
  216. # 方式三:提示手动安装
  217. # ------------------------------------------
  218. manual_install_hint() {
  219. echo ""
  220. warn "============================================"
  221. warn " 自动安装均失败,请手动下载安装:"
  222. warn "============================================"
  223. echo ""
  224. echo " 1. 进入容器: docker compose exec dev bash"
  225. echo ""
  226. echo " 2. 下载二进制:"
  227. echo " wget https://dev.mysql.com/get/Downloads/MySQL-8.4/mysql-${MYSQL_VERSION}-linux-glibc2.28-x86_64.tar.xz"
  228. echo ""
  229. echo " 3. 解压并复制:"
  230. echo " tar -xf mysql-${MYSQL_VERSION}-linux-glibc2.28-x86_64.tar.xz \\"
  231. echo ' --strip-components 1 -C /usr/local/bin \'
  232. echo " mysql-${MYSQL_VERSION}-linux-glibc2.28-x86_64/bin/mysql \\"
  233. echo " mysql-${MYSQL_VERSION}-linux-glibc2.28-x86_64/bin/mysqladmin"
  234. echo ""
  235. echo " 4. 验证:"
  236. echo " mysql --version"
  237. echo " # 应输出: mysql Ver 8.4.4 for Linux on x86_64 ..."
  238. echo ""
  239. echo " 5. 连接测试:"
  240. echo " mysql -h mysql -P 3306 -u root -proot --ssl-mode=DISABLED"
  241. echo ""
  242. }
  243. # ==========================================
  244. # 主流程
  245. # ==========================================
  246. echo ""
  247. echo "======================================"
  248. echo " MySQL CLI 安装 (官方客户端)"
  249. echo "======================================"
  250. echo ""
  251. # 步骤 1:检查是否已安装正确的版本
  252. if check_mysql_client; then
  253. exit 0
  254. fi
  255. # 步骤 2:清理旧版 MariaDB 客户端(确保后续 mysql 命令走新版本)
  256. if dpkg -l mariadb-client 2>/dev/null | grep -q "^ii"; then
  257. info "移除旧版 MariaDB 客户端..."
  258. apt-get remove -y -qq mariadb-client mariadb-client-core 2>/dev/null || true
  259. fi
  260. # 步骤 3:优先从官方 APT 仓库安装
  261. if install_from_apt; then
  262. info "✅ 方式一成功:从 APT 仓库安装"
  263. else
  264. warn "方式一失败,尝试方式二(二进制下载)..."
  265. if install_from_binary; then
  266. info "✅ 方式二成功:从二进制下载安装"
  267. else
  268. error "❌ 所有安装方案均失败"
  269. manual_install_hint
  270. exit 1
  271. fi
  272. fi
  273. # 步骤 4:最终验证
  274. echo ""
  275. if command -v mysql >/dev/null 2>&1; then
  276. mysql_ver=$(mysql --version)
  277. info "✅ MySQL 客户端已就绪"
  278. info "📊 版本: $mysql_ver"
  279. echo ""
  280. echo "📋 使用说明:"
  281. echo " 连接命令: mysql -h <主机> -P <端口> -u <用户> -p<密码> --ssl-mode=DISABLED"
  282. echo " Docker 内连接: mysql -h mysql -P 3306 -u root -proot --ssl-mode=DISABLED"
  283. echo ""
  284. else
  285. error "❌ MySQL 客户端安装失败(未知错误)"
  286. exit 1
  287. fi