Files
Aether/deploy.sh

234 lines
6.5 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# 智能部署脚本 - 自动检测依赖/代码/迁移变化
#
# 用法:
# 部署/更新: ./deploy.sh (自动检测所有变化)
# 强制重建: ./deploy.sh --rebuild-base
# 强制全部重建: ./deploy.sh --force
set -e
cd "$(dirname "$0")"
# 兼容 docker-compose 和 docker compose
if command -v docker-compose &> /dev/null; then
DC="docker-compose -f docker-compose.build.yml"
else
DC="docker compose -f docker-compose.build.yml"
fi
# 缓存文件
HASH_FILE=".deps-hash"
CODE_HASH_FILE=".code-hash"
MIGRATION_HASH_FILE=".migration-hash"
# 计算依赖文件的哈希值(包含 Dockerfile.base.local
calc_deps_hash() {
cat pyproject.toml frontend/package.json frontend/package-lock.json Dockerfile.base.local 2>/dev/null | md5sum | cut -d' ' -f1
}
# 计算代码文件的哈希值(包含 Dockerfile.app.local
calc_code_hash() {
{
cat Dockerfile.app.local 2>/dev/null
find src -type f -name "*.py" 2>/dev/null | sort | xargs cat 2>/dev/null
find frontend/src -type f \( -name "*.vue" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) 2>/dev/null | sort | xargs cat 2>/dev/null
} | md5sum | cut -d' ' -f1
}
# 计算迁移文件的哈希值
calc_migration_hash() {
find alembic/versions -name "*.py" -type f 2>/dev/null | sort | xargs cat 2>/dev/null | md5sum | cut -d' ' -f1
}
# 检查依赖是否变化
check_deps_changed() {
local current_hash=$(calc_deps_hash)
if [ -f "$HASH_FILE" ]; then
local saved_hash=$(cat "$HASH_FILE")
if [ "$current_hash" = "$saved_hash" ]; then
return 1
fi
fi
return 0
}
# 检查代码是否变化
check_code_changed() {
local current_hash=$(calc_code_hash)
if [ -f "$CODE_HASH_FILE" ]; then
local saved_hash=$(cat "$CODE_HASH_FILE")
if [ "$current_hash" = "$saved_hash" ]; then
return 1
fi
fi
return 0
}
# 检查迁移是否变化
check_migration_changed() {
local current_hash=$(calc_migration_hash)
if [ -f "$MIGRATION_HASH_FILE" ]; then
local saved_hash=$(cat "$MIGRATION_HASH_FILE")
if [ "$current_hash" = "$saved_hash" ]; then
return 1
fi
fi
return 0
}
# 保存哈希
save_deps_hash() { calc_deps_hash > "$HASH_FILE"; }
save_code_hash() { calc_code_hash > "$CODE_HASH_FILE"; }
save_migration_hash() { calc_migration_hash > "$MIGRATION_HASH_FILE"; }
# 构建基础镜像
build_base() {
echo ">>> Building base image (dependencies)..."
docker build -f Dockerfile.base.local -t aether-base:latest .
save_deps_hash
}
# 构建应用镜像
build_app() {
echo ">>> Building app image (code only)..."
docker build -f Dockerfile.app.local -t aether-app:latest .
save_code_hash
}
# 运行数据库迁移
run_migration() {
echo ">>> Running database migration..."
# 尝试运行 upgrade head捕获错误
UPGRADE_OUTPUT=$($DC exec -T app alembic upgrade head 2>&1) && {
echo "$UPGRADE_OUTPUT"
save_migration_hash
return 0
}
# 检查是否是因为找不到旧版本(基线重置场景)
if echo "$UPGRADE_OUTPUT" | grep -q "Can't locate revision"; then
echo ">>> Detected baseline reset: old revision not found in migrations"
echo ">>> Clearing old version and stamping to new baseline..."
# 先清除旧的版本记录,再 stamp 到新基线
$DC exec -T app python -c "
from sqlalchemy import create_engine, text
import os
engine = create_engine(os.environ['DATABASE_URL'])
with engine.connect() as conn:
conn.execute(text('DELETE FROM alembic_version'))
conn.commit()
print('Old version cleared')
"
# 获取最新的迁移版本(匹配 revision_id (head) 格式)
LATEST_VERSION=$($DC exec -T app alembic heads 2>/dev/null | grep -oE '^[0-9a-zA-Z_]+' | head -1)
if [ -n "$LATEST_VERSION" ]; then
$DC exec -T app alembic stamp "$LATEST_VERSION"
echo ">>> Database stamped to $LATEST_VERSION"
save_migration_hash
else
echo ">>> ERROR: Could not determine latest migration version"
exit 1
fi
else
# 其他错误,直接输出并退出
echo "$UPGRADE_OUTPUT"
exit 1
fi
}
# 强制全部重建
if [ "$1" = "--force" ] || [ "$1" = "-f" ]; then
echo ">>> Force rebuilding everything..."
build_base
build_app
$DC up -d --force-recreate
sleep 3
run_migration
docker image prune -f
echo ">>> Done!"
$DC ps
exit 0
fi
# 强制重建基础镜像
if [ "$1" = "--rebuild-base" ] || [ "$1" = "-r" ]; then
build_base
echo ">>> Base image rebuilt. Run ./deploy.sh to deploy."
exit 0
fi
# 拉取最新代码
echo ">>> Pulling latest code..."
git pull
# 标记是否需要重启
NEED_RESTART=false
BASE_REBUILT=false
# 检查基础镜像是否存在,或依赖是否变化
if ! docker image inspect aether-base:latest >/dev/null 2>&1; then
echo ">>> Base image not found, building..."
build_base
BASE_REBUILT=true
NEED_RESTART=true
elif check_deps_changed; then
echo ">>> Dependencies changed, rebuilding base image..."
build_base
BASE_REBUILT=true
NEED_RESTART=true
else
echo ">>> Dependencies unchanged."
fi
# 检查代码或迁移是否变化,或者 base 重建了app 依赖 base
# 注意:迁移文件打包在镜像中,所以迁移变化也需要重建 app 镜像
MIGRATION_CHANGED=false
if check_migration_changed; then
MIGRATION_CHANGED=true
fi
if ! docker image inspect aether-app:latest >/dev/null 2>&1; then
echo ">>> App image not found, building..."
build_app
NEED_RESTART=true
elif [ "$BASE_REBUILT" = true ]; then
echo ">>> Base image rebuilt, rebuilding app image..."
build_app
NEED_RESTART=true
elif check_code_changed; then
echo ">>> Code changed, rebuilding app image..."
build_app
NEED_RESTART=true
elif [ "$MIGRATION_CHANGED" = true ]; then
echo ">>> Migration files changed, rebuilding app image..."
build_app
NEED_RESTART=true
else
echo ">>> Code unchanged."
fi
# 只在有变化时重启
if [ "$NEED_RESTART" = true ]; then
echo ">>> Restarting services..."
$DC up -d
else
echo ">>> No changes detected, skipping restart."
fi
# 检查迁移变化(如果前面已经检测到变化并重建了镜像,这里直接运行迁移)
if [ "$MIGRATION_CHANGED" = true ]; then
echo ">>> Running database migration..."
sleep 3
run_migration
else
echo ">>> Migration unchanged."
fi
# 清理
docker image prune -f >/dev/null 2>&1 || true
echo ">>> Done!"
$DC ps