setup-mysql-cli.sh 10 KB

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