CICD-docker自动化部署 Gitea 的踩坑实录
摘要:本文记录了在阿里云 Linux 服务器(Docker 20.10)上自动化部署 Gitea 的完整过程,重点解决 Docker Compose 兼容性、网络超时、PATH 环境变量等实际问题,最终实现"零人工干预"的一键部署脚本。
📋 项目背景
需求场景
- 目标环境:阿里云 Linux 服务器(公网 IP:
101.201.xxx.xxx) - 核心诉求:编写一个自动化 Shell 脚本,实现 Gitea 的"零触摸"安装和配置
- 技术要求:
- 自动检测并安装 Docker/Docker Compose(如果缺失)
- 支持 SQLite3/PostgreSQL/MySQL 三种数据库选择
- 实时进度反馈(避免脚本卡住时用户不知道发生了什么)
- 兼容老旧 Docker 版本(当前服务器为 Docker 20.10)
🛠️ 完整部署流程
脚本架构
gitea_auto_deploy.sh
├── check_root() # 权限检查
├── detect_public_ip() # 公网 IP 检测(带超时保护)
├── check_docker() # Docker 环境检测
│ ├── install_docker() # 自动安装 Docker
│ └── install_docker_compose() # 自动安装 Docker Compose
├── create_directories() # 创建目录结构
├── select_database() # 交互式选择数据库类型
├── generate_compose_*() # 生成 docker-compose.yml
│ ├── generate_compose_sqlite()
│ ├── generate_compose_postgres()
│ └── generate_compose_mysql()
├── deploy_gitea() # 启动 Gitea 服务
└── show_info() # 显示部署信息关键代码片段
1. 动态选择 Docker Compose 命令
# 全局变量,在检测阶段确定
COMPOSE_CMD=""
check_docker() {
# ... Docker 检测逻辑 ...
# 智能选择 Compose 命令
if command -v docker &> /dev/null && docker compose version &>/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null && docker-compose --version &>/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
else
install_docker_compose
COMPOSE_CMD="docker-compose"
fi
}
# 后续所有地方统一使用 $COMPOSE_CMD
deploy_gitea() {
cd ${INSTALL_DIR}
$COMPOSE_CMD up -d
}2. 数据库配置生成(以 SQLite3 为例)
version: "3.8"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- SSH_DOMAIN=101.201.xxx.xxx
- SSH_PORT=2222
- DB_TYPE=sqlite3
ports:
- "3000:3000"
- "2222:22"
volumes:
- ./data:/data
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- gitea-net
networks:
gitea-net:
driver: bridge3. 国内镜像加速(CentOS/RHEL)
install_docker_centos() {
echo " 检测到 CentOS/RHEL 系统"
# 卸载旧版本
yum remove -y docker docker-client docker-client-latest \
docker-common docker-latest docker-latest-logrotate \
docker-logrotate docker-engine 2>/dev/null || true
# 安装依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# ✅ 使用阿里云镜像加速
yum-config-manager --add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装 Docker
yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
}📊 部署结果
成功输出示例
=========================================
🎉 Gitea 部署完成!
=========================================
📍 访问地址:
Web: http://101.201.xxx.xxx:3000
SSH: ssh://git@101.201.xxx.xxx:2222/用户名/仓库.git
🔧 常用命令:
查看状态: cd /opt/gitea && docker compose ps
查看日志: cd /opt/gitea && docker compose logs -f
停止服务: cd /opt/gitea && docker compose down
重启服务: cd /opt/gitea && docker compose restart
更新版本: cd /opt/gitea && docker compose pull && docker compose up -d
📝 首次配置:
1. 浏览器访问 http://101.201.xxx.xxx:3000
2. 按照向导完成初始设置
3. 第一个注册的账号将成为管理员
💡 提示:
- 配置文件: /opt/gitea/docker-compose.yml
- 数据目录: /opt/gitea/data
- 如需修改配置,编辑 docker-compose.yml 后执行: docker compose up -d目录结构
/opt/gitea/
├── docker-compose.yml # Docker Compose 配置文件
├── data/ # Gitea 数据目录(Git 仓库、数据库等)
│ └── gitea/
│ ├── conf/
│ ├── git/
│ └── ...
└── config/ # Gitea 配置文件
└── app.ini💡 经验总结
1. 命令可用性检测 ≠ 命令存在性检测
# ❌ 错误:只检查是否存在
if command -v docker compose &> /dev/null; then
# 可能命令存在但不可用(如缺少依赖、权限不足)
fi
# ✅ 正确:实际执行测试
if docker compose version &>/dev/null 2>&1; then
# 确保命令真正可用
fi2. PATH 环境变量的陷阱
~/.docker/cli-plugins/是用户级路径,可能在某些上下文(如sudo、cron job)中不可用/usr/local/bin是系统级标准 PATH,适用于所有场景- 安装二进制工具时,优先选择系统级路径
3. 网络操作的超时保护
任何涉及网络的命令都必须设置超时:
# ❌ 危险:可能无限等待
curl https://example.com
# ✅ 安全:最多等待 10 秒
curl --connect-timeout 5 --max-time 10 https://example.com4. Fail-Fast 原则
# 安装后立即验证,失败则退出
if ! command -v docker-compose &> /dev/null; then
echo "❌ 安装失败"
exit 1 # ← 立即终止,避免后续操作基于错误假设
fi5. 幂等性设计
脚本应该可以安全地多次运行:
# 检查是否已安装,避免重复安装
if command -v docker &> /dev/null; then
echo "✅ Docker 已安装,跳过"
else
install_docker
fi📝 完整脚本
gitea_auto_deploy.sh文件:
#!/bin/bash
# Gitea 自动化部署脚本 (Docker Compose)
# 适用于 Linux 服务器 (Ubuntu/CentOS/Debian)
set -e
# 禁用输出缓冲,确保实时显示
export PYTHONUNBUFFERED=1
echo "========================================="
echo " Gitea 自动化部署脚本"
echo "========================================="
echo ""
echo "⏰ 开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
# ==================== 配置区 ====================
# 修改以下变量以适应你的环境
# SSH 域名或 IP(用于 Git clone 地址显示)
# 优先使用环境变量,否则尝试自动检测,失败则使用默认值
if [ -z "$SSH_DOMAIN" ]; then
echo "🔍 正在检测服务器公网 IP..."
SSH_DOMAIN=$(curl -s --connect-timeout 5 --max-time 10 ifconfig.me 2>/dev/null || \
curl -s --connect-timeout 5 --max-time 10 ipinfo.io/ip 2>/dev/null || \
curl -s --connect-timeout 5 --max-time 10 api.ipify.org 2>/dev/null || \
echo "YOUR_SERVER_IP")
if [ "$SSH_DOMAIN" = "YOUR_SERVER_IP" ]; then
echo "⚠️ 无法自动检测公网 IP,请手动设置"
echo " 方法1: export SSH_DOMAIN=你的公网IP"
echo " 方法2: 编辑脚本第15行"
read -p "请输入你的服务器公网 IP: " SSH_DOMAIN
else
echo "✅ 检测到公网 IP: $SSH_DOMAIN"
fi
fi
# Web 访问端口
WEB_PORT="${WEB_PORT:-3000}"
# SSH 克隆端口
SSH_PORT="${SSH_PORT:-2222}"
# Gitea 版本(留空使用最新稳定版)
GITEA_VERSION="${GITEA_VERSION:-1.23.7}"
# 数据库类型: sqlite3 / mysql / postgres
DB_TYPE="${DB_TYPE:-sqlite3}"
# 安装目录
INSTALL_DIR="/opt/gitea"
# ==================== 函数定义 ====================
check_root() {
if [ "$EUID" -ne 0 ]; then
echo "❌ 请使用 root 权限运行此脚本"
echo " sudo bash $0"
exit 1
fi
}
check_docker() {
echo "🔍 检查 Docker 环境..."
# 检查 Docker 是否安装
if ! command -v docker &> /dev/null; then
echo "⚠️ Docker 未安装,正在自动安装..."
install_docker
else
# 检查 Docker 版本是否满足要求 (>= 20.10)
DOCKER_VERSION=$(docker --version | grep -oP '\d+\.\d+' | head -1)
DOCKER_MAJOR=$(echo $DOCKER_VERSION | cut -d. -f1)
DOCKER_MINOR=$(echo $DOCKER_VERSION | cut -d. -f2)
if [ "$DOCKER_MAJOR" -lt 20 ] || ([ "$DOCKER_MAJOR" -eq 20 ] && [ "$DOCKER_MINOR" -lt 10 ]); then
echo "⚠️ Docker 版本过低 ($DOCKER_VERSION),需要 >= 20.10"
echo " 正在升级 Docker..."
install_docker
else
echo "✅ Docker 已安装 (版本: $DOCKER_VERSION)"
fi
fi
# 检查 Docker 服务是否运行
if ! systemctl is-active --quiet docker; then
echo "⚠️ Docker 服务未运行,正在启动..."
systemctl start docker
systemctl enable docker
echo "✅ Docker 服务已启动并设置为开机自启"
else
echo "✅ Docker 服务运行正常"
fi
# 检查 Docker Compose(支持 V1 和 V2)
COMPOSE_CMD=""
# 先测试 docker compose V2 是否真正可用
if command -v docker &> /dev/null && docker compose version &>/dev/null 2>&1; then
# Docker Compose V2(插件)真正可用
COMPOSE_CMD="docker compose"
COMPOSE_VERSION=$(docker compose version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1)
echo "✅ Docker Compose V2 已安装 (版本: $COMPOSE_VERSION)"
elif command -v docker-compose &> /dev/null && docker-compose --version &>/dev/null 2>&1; then
# Docker Compose V1(独立命令)
COMPOSE_CMD="docker-compose"
COMPOSE_VERSION=$(docker-compose --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1)
echo "✅ Docker Compose V1 已安装 (版本: $COMPOSE_VERSION)"
else
echo "⚠️ Docker Compose 未安装或不可用,正在自动安装..."
install_docker_compose
COMPOSE_CMD="docker-compose"
COMPOSE_VERSION=$(docker-compose --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1)
echo "✅ Docker Compose 安装完成 (版本: $COMPOSE_VERSION)"
fi
# 将当前用户加入 docker 组(避免每次都要 sudo)
if [ -n "$SUDO_USER" ] && ! groups $SUDO_USER 2>/dev/null | grep -q docker; then
echo "🔧 将用户 $SUDO_USER 加入 docker 组..."
usermod -aG docker $SUDO_USER
echo "✅ 已添加,重新登录后生效(或执行: newgrp docker)"
fi
echo ""
}
install_docker() {
echo "📦 开始安装 Docker..."
# 检测操作系统类型
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
else
echo "❌ 无法检测操作系统类型"
exit 1
fi
case $OS in
ubuntu|debian)
install_docker_debian
;;
centos|rhel|fedora|almalinux|rocky)
install_docker_centos
;;
*)
echo "⚠️ 不支持的操作系统: $OS,尝试使用通用安装脚本..."
curl -fsSL https://get.docker.com | sh
;;
esac
# 启动 Docker 服务
systemctl start docker
systemctl enable docker
echo "✅ Docker 安装完成"
}
install_docker_debian() {
echo " 检测到 Debian/Ubuntu 系统"
# 卸载旧版本
apt-get remove -y docker docker-engine docker.io containerd runc 2>/dev/null || true
# 更新包索引
apt-get update
# 安装依赖
apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release
# 添加 Docker GPG 密钥
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null || \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 添加 Docker 仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# 更新包索引并安装 Docker
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
}
install_docker_centos() {
echo " 检测到 CentOS/RHEL 系统"
# 卸载旧版本
yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 2>/dev/null || true
# 安装依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加 Docker 仓库(使用阿里云镜像加速)
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装 Docker
yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
}
install_docker_compose() {
echo "📦 安装 Docker Compose..."
# 获取最新版本的 Docker Compose
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep -oP '"tag_name": "\K(.*)(?=")')
# 下载 Docker Compose
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64" \
-o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
# 创建软链接到 /usr/local/bin
ln -sf $DOCKER_CONFIG/cli-plugins/docker-compose /usr/local/bin/docker-compose
echo "✅ Docker Compose 安装完成 (版本: $COMPOSE_VERSION)"
}
create_directories() {
echo "📁 创建目录结构..."
mkdir -p ${INSTALL_DIR}/{data,config}
chown -R 1000:1000 ${INSTALL_DIR}
echo "✅ 目录创建完成: ${INSTALL_DIR}"
}
generate_compose_sqlite() {
cat > ${INSTALL_DIR}/docker-compose.yml <<EOF
version: "3.8"
services:
gitea:
image: gitea/gitea:${GITEA_VERSION}
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- SSH_DOMAIN=${SSH_DOMAIN}
- SSH_PORT=${SSH_PORT}
- DB_TYPE=sqlite3
ports:
- "${WEB_PORT}:3000"
- "${SSH_PORT}:22"
volumes:
- ./data:/data
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- gitea-net
networks:
gitea-net:
driver: bridge
EOF
}
generate_compose_postgres() {
cat > ${INSTALL_DIR}/docker-compose.yml <<EOF
version: "3.8"
services:
gitea:
image: gitea/gitea:${GITEA_VERSION}
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- SSH_DOMAIN=${SSH_DOMAIN}
- SSH_PORT=${SSH_PORT}
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=${DB_PASSWORD}
ports:
- "${WEB_PORT}:3000"
- "${SSH_PORT}:22"
volumes:
- ./data:/data
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- db
networks:
- gitea-net
db:
image: postgres:14
container_name: gitea-db
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=gitea
volumes:
- ./postgres:/var/lib/postgresql/data
networks:
- gitea-net
networks:
gitea-net:
driver: bridge
EOF
}
generate_compose_mysql() {
cat > ${INSTALL_DIR}/docker-compose.yml <<EOF
version: "3.8"
services:
gitea:
image: gitea/gitea:${GITEA_VERSION}
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- SSH_DOMAIN=${SSH_DOMAIN}
- SSH_PORT=${SSH_PORT}
- GITEA__database__DB_TYPE=mysql
- GITEA__database__HOST=db:3306
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=${DB_PASSWORD}
ports:
- "${WEB_PORT}:3000"
- "${SSH_PORT}:22"
volumes:
- ./data:/data
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- db
networks:
- gitea-net
db:
image: mysql:8
container_name: gitea-db
restart: always
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- MYSQL_USER=gitea
- MYSQL_PASSWORD=${DB_PASSWORD}
- MYSQL_DATABASE=gitea
volumes:
- ./mysql:/var/lib/mysql
networks:
- gitea-net
networks:
gitea-net:
driver: bridge
EOF
}
deploy_gitea() {
echo "🚀 启动 Gitea..."
cd ${INSTALL_DIR}
# 使用检测到的 Compose 命令
if [ -z "$COMPOSE_CMD" ]; then
# 再次检测
if command -v docker compose &> /dev/null; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
echo "❌ 错误:未找到 Docker Compose 命令"
exit 1
fi
fi
echo " 使用命令: $COMPOSE_CMD"
$COMPOSE_CMD up -d
echo ""
echo "⏳ 等待服务启动..."
sleep 5
# 检查容器状态
if $COMPOSE_CMD ps | grep -q "Up"; then
echo "✅ Gitea 启动成功!"
else
echo "❌ Gitea 启动失败,请查看日志:"
$COMPOSE_CMD logs
exit 1
fi
}
show_info() {
echo ""
echo "========================================="
echo " 🎉 Gitea 部署完成!"
echo "========================================="
echo ""
echo "📍 访问地址:"
echo " Web: http://${SSH_DOMAIN}:${WEB_PORT}"
echo " SSH: ssh://git@${SSH_DOMAIN}:${SSH_PORT}/用户名/仓库.git"
echo ""
echo "🔧 常用命令:"
echo " 查看状态: cd ${INSTALL_DIR} && docker compose ps"
echo " 查看日志: cd ${INSTALL_DIR} && docker compose logs -f"
echo " 停止服务: cd ${INSTALL_DIR} && docker compose down"
echo " 重启服务: cd ${INSTALL_DIR} && docker compose restart"
echo " 更新版本: cd ${INSTALL_DIR} && docker compose pull && docker compose up -d"
echo ""
echo "📝 首次配置:"
echo " 1. 浏览器访问 http://${SSH_DOMAIN}:${WEB_PORT}"
echo " 2. 按照向导完成初始设置"
echo " 3. 第一个注册的账号将成为管理员"
echo ""
echo "💡 提示:"
echo " - 配置文件: ${INSTALL_DIR}/docker-compose.yml"
echo " - 数据目录: ${INSTALL_DIR}/data"
echo " - 如需修改配置,编辑 docker-compose.yml 后执行: docker compose up -d"
echo ""
echo "========================================="
}
# ==================== 主流程 ====================
main() {
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 1/6: 检查权限"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
check_root
echo "✅ 权限检查通过"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 2/6: 检测 Docker 环境"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
check_docker
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 3/6: 创建目录结构"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
create_directories
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 4/6: 选择数据库类型"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "选择数据库类型:"
echo " 1) SQLite3 (推荐个人/小团队,无需额外配置)"
echo " 2) PostgreSQL (推荐生产环境)"
echo " 3) MySQL/MariaDB"
read -p "请输入选项 [1-3] (默认 1): " db_choice
case ${db_choice:-1} in
1)
echo "✅ 使用 SQLite3 数据库"
generate_compose_sqlite
;;
2)
echo "⚠️ PostgreSQL 需要设置数据库密码"
read -sp "请输入数据库密码: " DB_PASSWORD
echo ""
generate_compose_postgres
;;
3)
echo "⚠️ MySQL 需要设置数据库密码"
read -sp "请输入数据库密码: " DB_PASSWORD
echo ""
generate_compose_mysql
;;
*)
echo "❌ 无效选项,使用默认 SQLite3"
generate_compose_sqlite
;;
esac
echo "✅ 配置文件生成完成"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 5/6: 启动 Gitea 服务"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
deploy_gitea
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 6/6: 显示部署信息"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
show_info
echo ""
echo "⏰ 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================="
}
# 执行主流程
main使用方法
chmod +x gitea_auto_deploy.sh
sudo bash gitea_auto_deploy.sh🔥 核心难点与解决方案
难点 1:公网 IP 检测超时
问题现象
curl ifconfig.me # 卡在命令行,无响应根本原因
阿里云服务器的出站网络可能受限,或者 ifconfig.me 服务响应慢,导致 curl 无限等待。
解决方案
# 添加超时保护 + 多源 fallback
PUBLIC_IP=$(curl -s --connect-timeout 5 --max-time 10 https://ifconfig.me || \
curl -s --connect-timeout 5 --max-time 10 https://ipinfo.io/ip || \
curl -s --connect-timeout 5 --max-time 10 https://api.ipify.org)
if [ -z "$PUBLIC_IP" ]; then
echo "⚠️ 无法自动检测公网 IP,请手动输入:"
read -p "公网 IP: " PUBLIC_IP
fi关键点:
--connect-timeout 5:连接超时 5 秒--max-time 10:总超时 10 秒- 三个备用 IP 检测服务,提高成功率
难点 2:Docker Compose V1 vs V2 兼容性
问题现象
docker compose up -d
# 输出:unknown shorthand flag: 'd' in -d根本原因
Docker 20.10 虽然安装了 docker-compose-plugin,但 V2 插件并未正确集成。Shell 将 compose 解析为 docker 的参数而非子命令,导致 -d 被当作全局标志处理。
错误的检测方式
# ❌ 只检查命令是否存在,不测试可用性
if command -v docker compose &> /dev/null; then
COMPOSE_CMD="docker compose"
fi正确的检测方式
# ✅ 实际执行命令测试可用性
if command -v docker &> /dev/null && docker compose version &>/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null && docker-compose --version &>/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
else
install_docker_compose # 自动安装
fi关键点:
command -v只能判断命令是否在 PATH 中- 必须实际执行
docker compose version验证功能正常 - 同时支持 V1(
docker-compose)和 V2(docker compose)
难点 3:Docker Compose 安装后找不到命令
问题现象
✅ Docker Compose 安装完成 (版本: v2.23.0)
...
gitea_auto_deploy.sh: line 387: docker-compose: command not found根本原因
脚本将二进制文件下载到 ~/.docker/cli-plugins/docker-compose,但该路径:
- 可能不在当前用户的
$PATH中 - 软链接创建失败或权限不足
- 函数作用域内设置的变量未正确传递到主流程
解决方案
install_docker_compose() {
echo "📦 安装 Docker Compose..."
# 获取最新版本(带超时保护)
COMPOSE_VERSION=$(curl -s --connect-timeout 10 --max-time 30 \
https://api.github.com/repos/docker/compose/releases/latest \
| grep -oP '"tag_name": "\K(.*)(?=")' || echo "v2.23.0")
# ✅ 直接下载到 /usr/local/bin(全局 PATH)
curl -SL --connect-timeout 10 --max-time 300 \
"https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64" \
-o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# ✅ 安装后立即验证
if command -v docker-compose &> /dev/null; then
INSTALLED_VERSION=$(docker-compose --version | grep -oP '\d+\.\d+\.\d+')
echo "✅ Docker Compose 安装完成 (版本: $INSTALLED_VERSION)"
COMPOSE_CMD="docker-compose" # ← 设置全局变量
else
echo "❌ Docker Compose 安装失败"
exit 1
fi
}关键点:
/usr/local/bin是标准 PATH 目录,所有用户都可访问- 避免依赖
~/.docker/cli-plugins/这种用户级路径 - 安装后立即验证,失败则退出(fail-fast 原则)
难点 4:脚本执行过程中的"心跳"日志
用户需求
"我需要知道脚本运行到哪一步了,之前遇到过脚本卡住但没有输出的情况。"
解决方案
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 步骤 2/6: 检测 Docker 环境"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
check_docker() {
echo "🔍 检查 Docker 环境..."
if ! command -v docker &> /dev/null; then
echo "⚠️ Docker 未安装,正在自动安装..."
install_docker
else
DOCKER_VERSION=$(docker --version | grep -oP '\d+\.\d+' | head -1)
echo "✅ Docker 已安装 (版本: $DOCKER_VERSION)"
fi
# 检查 Docker 服务状态
if systemctl is-active --quiet docker; then
echo "✅ Docker 服务运行正常"
else
echo "⚠️ Docker 服务未运行,正在启动..."
systemctl start docker
fi
}效果:
=========================================
Gitea 自动化部署脚本
=========================================
⏰ 开始时间: 2026-01-08 16:25:06
🔍 正在检测服务器公网 IP...
✅ 检测到公网 IP: 101.201.xxx.xxx
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 步骤 1/6: 检查权限
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 权限检查通过
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 步骤 2/6: 检测 Docker 环境
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔍 检查 Docker 环境...
✅ Docker 已安装 (版本: 20.10)
✅ Docker 服务运行正常
⚠️ Docker Compose 未安装或不可用,正在自动安装...
...