gcx/frontend/admin-web/deploy.sh

576 lines
17 KiB
Bash
Executable File

#!/bin/bash
# =============================================================================
# Genex Admin Web - 部署管理脚本
# =============================================================================
#
# Docker 命令:
# ./deploy.sh build 仅构建 Docker 镜像
# ./deploy.sh start 构建并启动服务 (默认)
# ./deploy.sh stop 停止服务
# ./deploy.sh restart 重启服务
# ./deploy.sh logs 查看服务日志
# ./deploy.sh status 查看服务状态
# ./deploy.sh clean 清理容器和镜像
#
# Nginx + SSL 命令 (需要 root 权限):
# sudo ./deploy.sh nginx install [domain] [email] 安装 Nginx + SSL 证书
# sudo ./deploy.sh nginx ssl [domain] [email] 仅申请/续期 SSL 证书
# ./deploy.sh nginx status 查看 Nginx 状态
# ./deploy.sh nginx reload 重载 Nginx 配置
#
# 默认域名: admin.gogenex.cn
# 默认邮箱: admin@gogenex.cn
#
# =============================================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 项目信息
PROJECT_NAME="genex-admin-web"
IMAGE_NAME="genex-admin-web"
CONTAINER_NAME="genex-admin-web"
DEFAULT_PORT=3000
DEFAULT_DOMAIN="admin.gogenex.cn"
DEFAULT_EMAIL="admin@gogenex.cn"
# 脚本目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# =============================================================================
# Docker 环境检查
# =============================================================================
# 检查 Docker 是否安装
check_docker() {
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装,请先安装 Docker"
exit 1
fi
if ! docker info &> /dev/null; then
log_error "Docker 服务未运行,请启动 Docker"
exit 1
fi
log_success "Docker 检查通过"
}
# 检查 Docker Compose 是否安装
check_docker_compose() {
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
log_error "Docker Compose 未安装"
exit 1
fi
log_success "Docker Compose 检查通过 ($COMPOSE_CMD)"
}
# =============================================================================
# Docker 服务管理
# =============================================================================
# 构建镜像
build() {
log_info "开始构建 Docker 镜像..."
$COMPOSE_CMD build --no-cache
log_success "镜像构建完成"
}
# 启动服务
start() {
log_info "开始部署 Genex Admin Web..."
# 检查端口是否被占用
PORT=${PORT:-$DEFAULT_PORT}
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
log_warn "端口 $PORT 已被占用,尝试停止旧服务..."
stop
fi
# 构建并启动
$COMPOSE_CMD up -d --build
# 等待服务启动
log_info "等待服务启动..."
sleep 5
# 检查服务状态
if docker ps | grep -q $CONTAINER_NAME; then
log_success "服务部署成功!"
log_info "访问地址: http://localhost:$PORT"
log_info "健康检查: http://localhost:$PORT/api/health"
else
log_error "服务启动失败,请查看日志: ./deploy.sh logs"
exit 1
fi
}
# 停止服务
stop() {
log_info "停止服务..."
$COMPOSE_CMD down
log_success "服务已停止"
}
# 重启服务
restart() {
log_info "重启服务..."
stop
start
}
# 查看日志
logs() {
$COMPOSE_CMD logs -f
}
# 清理
clean() {
log_info "清理容器和镜像..."
# 停止并删除容器
$COMPOSE_CMD down --rmi local --volumes --remove-orphans
# 删除悬空镜像
docker image prune -f
log_success "清理完成"
}
# 显示状态
status() {
log_info "服务状态:"
docker ps -a --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
}
# =============================================================================
# Nginx + SSL 证书管理
# =============================================================================
# 安装 Nginx + SSL 证书 (完整流程)
# 流程:
# 1. 安装 nginx 和 certbot (如未安装)
# 2. 部署 HTTP 临时配置 (用于 Let's Encrypt 验证)
# 3. 使用 certbot webroot 方式申请 SSL 证书
# 4. 部署完整 HTTPS 配置 (从 nginx/ 目录读取)
# 5. 设置 certbot 自动续期定时任务
cmd_nginx_install() {
local domain="${1:-$DEFAULT_DOMAIN}"
local email="${2:-$DEFAULT_EMAIL}"
local conf_file="$SCRIPT_DIR/nginx/${domain}.conf"
log_info "为域名 $domain 安装 Nginx + Let's Encrypt SSL..."
# 检查 HTTPS 配置模板是否存在
if [ ! -f "$conf_file" ]; then
log_error "Nginx 配置文件不存在: $conf_file"
log_error "请先在 nginx/ 目录下创建 ${domain}.conf"
log_info ""
log_info "可以复制模板并修改:"
log_info " cp nginx/admin.gogenex.cn.conf nginx/${domain}.conf"
log_info " 然后替换其中的域名和端口"
exit 1
fi
# 检查 root 权限
if [ "$EUID" -ne 0 ]; then
log_error "需要 root 权限运行"
log_info "用法: sudo ./deploy.sh nginx install $domain $email"
exit 1
fi
echo ""
echo "============================================"
echo " Nginx + SSL 安装"
echo "============================================"
echo " 域名: $domain"
echo " 邮箱: $email"
echo " 端口: $DEFAULT_PORT"
echo " 配置: $conf_file"
echo "============================================"
echo ""
# ---- 步骤 1: 安装依赖 ----
log_info "[1/5] 检查并安装依赖..."
if ! command -v nginx &> /dev/null; then
log_info "正在安装 Nginx..."
apt update && apt install -y nginx
systemctl enable nginx
systemctl start nginx
fi
log_success "Nginx 已就绪 ($(nginx -v 2>&1))"
if ! command -v certbot &> /dev/null; then
log_info "正在安装 Certbot..."
apt install -y certbot python3-certbot-nginx
fi
log_success "Certbot 已就绪 ($(certbot --version 2>&1))"
# ---- 步骤 2: 部署 HTTP 临时配置 ----
log_info "[2/5] 部署 HTTP 临时配置 (用于证书验证)..."
# 创建 certbot webroot 目录
mkdir -p /var/www/certbot
# 写入 HTTP-only 临时配置
cat > /etc/nginx/sites-available/$domain << HTTPEOF
# 临时 HTTP 配置 (用于 Let's Encrypt 证书申请)
# 申请成功后将被 HTTPS 配置替换
server {
listen 80;
listen [::]:80;
server_name $domain;
# Let's Encrypt ACME 验证
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# 临时反向代理 (证书申请前也可访问)
location / {
proxy_pass http://127.0.0.1:${DEFAULT_PORT};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
HTTPEOF
# 启用站点配置
ln -sf /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/
# 移除默认站点 (避免冲突)
rm -f /etc/nginx/sites-enabled/default
# 测试并重载 Nginx
nginx -t && systemctl reload nginx
log_success "HTTP 配置完成 (http://$domain)"
# ---- 步骤 3: 申请 SSL 证书 ----
log_info "[3/5] 申请 Let's Encrypt SSL 证书..."
if [ -d "/etc/letsencrypt/live/$domain" ]; then
log_warn "证书已存在,跳过申请"
log_info "如需重新申请: sudo certbot certonly --force-renewal -d $domain"
else
echo ""
log_warn "请确保以下条件已满足:"
log_warn " 1. DNS A 记录 $domain 已指向本服务器 IP"
log_warn " 2. 服务器 80 端口可从外网访问 (防火墙已放行)"
echo ""
read -p "确认以上条件已满足,继续申请证书? (y/n): " confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
log_info "已跳过证书申请,当前为 HTTP 模式"
log_info "稍后申请证书: sudo ./deploy.sh nginx ssl $domain"
return 0
fi
# 使用 webroot 方式申请证书
certbot certonly --webroot --webroot-path=/var/www/certbot \
--email $email --agree-tos --no-eff-email \
-d $domain
log_success "SSL 证书申请成功!"
fi
# ---- 步骤 4: 部署 HTTPS 完整配置 ----
log_info "[4/5] 部署 HTTPS 完整配置..."
# 从项目 nginx/ 目录复制完整 HTTPS 配置
cp "$conf_file" /etc/nginx/sites-available/$domain
# 测试并重载
nginx -t && systemctl reload nginx
log_success "HTTPS 配置已部署"
# ---- 步骤 5: 配置自动续期 ----
log_info "[5/5] 配置证书自动续期..."
# certbot 安装时通常已自动配置 systemd timer 或 cron
# 这里确保 renew hook 能正确重载 nginx
if [ ! -f /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh ]; then
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'HOOKEOF'
#!/bin/bash
# Let's Encrypt 证书续期后自动重载 Nginx
systemctl reload nginx
HOOKEOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
log_success "自动续期钩子已配置"
else
log_success "自动续期钩子已存在"
fi
# 测试续期 (dry-run)
certbot renew --dry-run 2>/dev/null && log_success "自动续期测试通过" || log_warn "自动续期测试未通过,请检查 certbot 配置"
# ---- 完成 ----
echo ""
echo "============================================"
echo -e " ${GREEN}Nginx + SSL 配置完成!${NC}"
echo "============================================"
echo ""
echo -e " HTTPS 访问: ${BLUE}https://$domain${NC}"
echo -e " HTTP 自动跳转到 HTTPS"
echo ""
echo " Nginx 日志:"
echo " access: /var/log/nginx/${domain}.access.log"
echo " error: /var/log/nginx/${domain}.error.log"
echo ""
echo " 证书路径:"
echo " cert: /etc/letsencrypt/live/$domain/fullchain.pem"
echo " key: /etc/letsencrypt/live/$domain/privkey.pem"
echo ""
echo " 证书续期: certbot 已配置自动续期 (每天检查)"
echo " 手动续期: sudo certbot renew"
echo ""
}
# 仅申请/续期 SSL 证书
cmd_nginx_ssl() {
local domain="${1:-$DEFAULT_DOMAIN}"
local email="${2:-$DEFAULT_EMAIL}"
local conf_file="$SCRIPT_DIR/nginx/${domain}.conf"
# 检查 root 权限
if [ "$EUID" -ne 0 ]; then
log_error "需要 root 权限运行"
log_info "用法: sudo ./deploy.sh nginx ssl $domain $email"
exit 1
fi
if [ -d "/etc/letsencrypt/live/$domain" ]; then
log_info "证书已存在,尝试续期..."
certbot renew --cert-name $domain
log_success "证书续期完成"
else
log_info "$domain 申请 SSL 证书..."
# 确保 webroot 目录存在
mkdir -p /var/www/certbot
certbot certonly --webroot --webroot-path=/var/www/certbot \
--email $email --agree-tos --no-eff-email \
-d $domain
log_success "SSL 证书申请成功!"
fi
# 如果 HTTPS 配置模板存在,部署它
if [ -f "$conf_file" ]; then
log_info "部署 HTTPS 配置..."
cp "$conf_file" /etc/nginx/sites-available/$domain
nginx -t && systemctl reload nginx
log_success "HTTPS 配置已部署: https://$domain"
else
log_warn "HTTPS 配置模板不存在: $conf_file"
log_warn "请手动配置 Nginx"
fi
}
# 查看 Nginx 状态
cmd_nginx_status() {
echo ""
echo "============================================"
echo " Nginx 状态"
echo "============================================"
echo ""
# Nginx 服务状态
if systemctl is-active nginx &>/dev/null; then
echo -e " Nginx 服务: ${GREEN}运行中${NC}"
else
echo -e " Nginx 服务: ${RED}未运行${NC}"
fi
# 域名配置
echo ""
echo " 已配置站点:"
for conf in /etc/nginx/sites-enabled/*; do
if [ -f "$conf" ]; then
local name=$(basename "$conf")
echo " - $name"
fi
done
# SSL 证书状态
echo ""
echo " SSL 证书:"
if command -v certbot &>/dev/null; then
certbot certificates 2>/dev/null | grep -E "(Certificate Name|Expiry Date|Domains)" | sed 's/^/ /'
else
echo " certbot 未安装"
fi
echo ""
}
# 重载 Nginx 配置
cmd_nginx_reload() {
if [ "$EUID" -ne 0 ]; then
log_error "需要 root 权限: sudo ./deploy.sh nginx reload"
exit 1
fi
nginx -t && systemctl reload nginx
log_success "Nginx 配置已重载"
}
# =============================================================================
# 帮助信息
# =============================================================================
show_help() {
echo ""
echo "Genex Admin Web 部署脚本"
echo ""
echo "使用方法: ./deploy.sh [命令]"
echo ""
echo "Docker 命令:"
echo " build 仅构建 Docker 镜像"
echo " start 构建并启动服务 (默认)"
echo " stop 停止服务"
echo " restart 重启服务"
echo " logs 查看服务日志"
echo " status 查看服务状态"
echo " clean 清理容器和镜像"
echo ""
echo "Nginx + SSL 命令:"
echo " nginx install [domain] [email] 安装 Nginx + Let's Encrypt SSL"
echo " nginx ssl [domain] [email] 申请/续期 SSL 证书"
echo " nginx status 查看 Nginx 和证书状态"
echo " nginx reload 重载 Nginx 配置"
echo ""
echo " help 显示此帮助信息"
echo ""
echo "环境变量:"
echo " PORT 服务端口 (默认: $DEFAULT_PORT)"
echo ""
echo "默认配置:"
echo " 域名: $DEFAULT_DOMAIN"
echo " 邮箱: $DEFAULT_EMAIL"
echo ""
echo "示例:"
echo " ./deploy.sh start # 默认端口 3000 启动"
echo " PORT=8080 ./deploy.sh start # 指定端口 8080 启动"
echo " sudo ./deploy.sh nginx install # 安装 Nginx + SSL (默认域名)"
echo " sudo ./deploy.sh nginx install myapp.com a@b.c # 自定义域名和邮箱"
echo " sudo ./deploy.sh nginx ssl # 续期证书"
echo ""
echo "首次部署完整流程:"
echo " 1. ./deploy.sh start # 启动 Docker 容器"
echo " 2. sudo ./deploy.sh nginx install # 安装 Nginx + SSL"
echo " 3. 访问 https://$DEFAULT_DOMAIN"
echo ""
}
# =============================================================================
# 主入口
# =============================================================================
main() {
# 切换到脚本所在目录
cd "$SCRIPT_DIR"
# 执行命令
case "${1:-start}" in
build)
check_docker
check_docker_compose
build
;;
start)
check_docker
check_docker_compose
start
;;
stop)
check_docker
check_docker_compose
stop
;;
restart)
check_docker
check_docker_compose
restart
;;
logs)
check_docker
check_docker_compose
logs
;;
status)
check_docker
status
;;
clean)
check_docker
check_docker_compose
clean
;;
nginx)
case "${2:-install}" in
install)
cmd_nginx_install "$3" "$4"
;;
ssl)
cmd_nginx_ssl "$3" "$4"
;;
status)
cmd_nginx_status
;;
reload)
cmd_nginx_reload
;;
*)
log_error "未知 nginx 命令: $2"
echo "用法: ./deploy.sh nginx [install|ssl|status|reload] [domain] [email]"
exit 1
;;
esac
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
}
main "$@"