-
Notifications
You must be signed in to change notification settings - Fork 310
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·161 lines (143 loc) · 5.38 KB
/
Copy pathdeploy.sh
File metadata and controls
executable file
·161 lines (143 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/bin/bash
set -euo pipefail
# ==================== 配置 ====================
# 部署方式:本地开发完成后将项目目录上传至服务器(scp/FTP/面板均可),
# 再在服务器上执行本脚本完成构建与启动——脚本不再从 git 拉取代码。
PROJECT_NAME="FastapiAdmin"
WORK_DIR="$(cd "$(dirname "$0")" && pwd)"
DOCKER_DIR="${WORK_DIR}/docker"
ENV_FILE="${DOCKER_DIR}/.env"
COLOR_GREEN='\033[0;32m'; COLOR_BLUE='\033[0;34m'; COLOR_YELLOW='\033[0;33m'; COLOR_RED='\033[0;31m'; COLOR_RESET='\033[0m'
log() {
local color; local level=${2:-INFO}
case $level in
INFO) color="${COLOR_BLUE}" ;; WARN) color="${COLOR_YELLOW}" ;;
ERROR) color="${COLOR_RED}" ;; SUCCESS) color="${COLOR_GREEN}" ;;
*) color="${COLOR_RESET}" ;;
esac
echo -e "${color}[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${1}${COLOR_RESET}"
}
# ==================== 核心函数 ====================
load_env() {
if [ -f "${ENV_FILE}" ]; then
set -a; source "${ENV_FILE}"; set +a
elif [ -f "${DOCKER_DIR}/.env.example" ]; then
cp "${DOCKER_DIR}/.env.example" "${ENV_FILE}"
set -a; source "${ENV_FILE}"; set +a
log "已从 .env.example 生成 ${ENV_FILE},请先填写密码等配置后重新执行" "WARN"
exit 1
else
log ".env 文件不存在" "ERROR"; exit 1
fi
log "✅ 环境变量已加载" "SUCCESS"
}
check_deps() {
for dir in "${DOCKER_DIR}/mysql/data" "${DOCKER_DIR}/redis/data" "${DOCKER_DIR}/mysql/init" "${DOCKER_DIR}/redis/conf" "${WORK_DIR}/backend/static/upload"; do
[ -d "$dir" ] || mkdir -p "$dir"
done
local missing=()
for cmd in docker; do
command -v $cmd &>/dev/null || missing+=($cmd)
done
if ! docker compose version &>/dev/null && ! docker-compose --version &>/dev/null; then
missing+=("docker compose")
fi
if [ ${#missing[@]} -gt 0 ]; then
log "缺少依赖: ${missing[*]}" "ERROR"; exit 1
fi
log "✅ 依赖检查通过" "SUCCESS"
}
check_code() {
# 代码由用户上传,这里仅校验关键文件齐全
local missing=()
[ -f "${WORK_DIR}/backend/main.py" ] || missing+=("backend/main.py")
[ -f "${DOCKER_DIR}/backend/Dockerfile" ] || missing+=("docker/backend/Dockerfile")
[ -f "${DOCKER_DIR}/redis/conf/redis.conf" ] || missing+=("docker/redis/conf/redis.conf")
if [ ${#missing[@]} -gt 0 ]; then
log "代码不完整,缺少: ${missing[*]}(请重新上传后重试)" "ERROR"; exit 1
fi
log "✅ 代码完整性检查通过" "SUCCESS"
}
build_image() {
cd "${DOCKER_DIR}"
export DOCKER_BUILDKIT=1
docker compose build || { log "镜像构建失败" "ERROR"; exit 1; }
log "✅ 镜像构建完成" "SUCCESS"
}
start_service() {
cd "${DOCKER_DIR}"
docker compose up -d --force-recreate || { log "容器启动失败" "ERROR"; exit 1; }
log "⏳ 等待服务就绪..."
for i in $(seq 1 30); do
if docker compose ps mysql --format '{{.Status}}' 2>/dev/null | grep -q "healthy"; then
log "✅ MySQL 已就绪" "SUCCESS"; break
fi
sleep 2
done
docker compose ps
log "✅ 服务启动完成" "SUCCESS"
}
stop_service() {
cd "${DOCKER_DIR}" 2>/dev/null || true
docker compose down 2>/dev/null || true
log "✅ 服务已停止" "SUCCESS"
}
show_logs() {
cd "${DOCKER_DIR}"
docker compose ps --format "table {{.Service}}\t{{.Name}}\t{{.Status}}\t{{.Ports}}"
echo "--- 最近 50 行日志 ---"
docker compose logs --tail=50 2>/dev/null
}
verify() {
local ok=true
for svc in mysql redis backend nginx; do
local st=$(docker compose ps "$svc" --format '{{.Status}}' 2>/dev/null || echo "not found")
if echo "$st" | grep -qE "Up|healthy"; then
log "✅ $svc: $st" "SUCCESS"
else
log "❌ $svc: $st" "ERROR"; ok=false
fi
done
$ok && log "✅ 所有服务正常" "SUCCESS" || log "⚠️ 部分服务异常" "WARN"
}
cleanup() {
# 仅清理悬空镜像/网络/构建缓存;-a 会删掉服务器上其他项目的未使用镜像,慎用
docker image prune -f >/dev/null 2>&1 || true
docker builder prune -f >/dev/null 2>&1 || true
log "✅ 构建缓存已清理" "SUCCESS"
}
# ==================== 主流程 ====================
full_deploy() {
log "========== 🚀 开始完整部署 ==========" "INFO"
log "时间: $(date '+%Y-%m-%d %H:%M:%S')"
load_env
check_deps
check_code
build_image
start_service
verify
show_logs
cleanup
log "========== 🎉 部署完成 ==========" "SUCCESS"
}
# ==================== 入口 ====================
trap 'stop_service; exit 130' INT TERM
case ${1:-} in
start) load_env; start_service ;;
stop) load_env; stop_service ;;
restart) load_env; cd "${DOCKER_DIR}"; docker compose restart; docker compose ps ;;
logs) load_env; show_logs ;;
verify) load_env; verify ;;
clean) cleanup ;;
help|-h|--help)
echo "用法: $0 [命令]"
echo " 无参数 完整部署(校验代码→构建→启动→清理;代码需先上传至服务器)"
echo " start 启动服务"
echo " stop 停止服务"
echo " restart 重启服务"
echo " logs 查看日志"
echo " verify 验证服务"
echo " clean 清理构建缓存"
;;
*) full_deploy "$@" ;;
esac