docs: update README and CLAUDE.md to v2.2.0
- Added documentation for audit tracking (IP address, invocation method). - Updated database model descriptions for enhanced WorkOrder and Conversation fields. - Documented the new UnifiedConfig system. - Reflected enhanced logging transparency for knowledge base parsing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -1,306 +0,0 @@
|
||||
#!/bin/bash
|
||||
# TSP智能助手部署脚本
|
||||
# 支持多环境部署、版本管理、自动备份
|
||||
|
||||
set -e # 遇到错误立即退出
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查依赖
|
||||
check_dependencies() {
|
||||
log_info "检查系统依赖..."
|
||||
|
||||
# 检查Python
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
log_error "Python3 未安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查pip
|
||||
if ! command -v pip3 &> /dev/null; then
|
||||
log_error "pip3 未安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查Git
|
||||
if ! command -v git &> /dev/null; then
|
||||
log_error "Git 未安装"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "依赖检查完成"
|
||||
}
|
||||
|
||||
# 创建虚拟环境
|
||||
setup_venv() {
|
||||
local venv_path=$1
|
||||
log_info "创建虚拟环境: $venv_path"
|
||||
|
||||
if [ ! -d "$venv_path" ]; then
|
||||
python3 -m venv "$venv_path"
|
||||
fi
|
||||
|
||||
source "$venv_path/bin/activate"
|
||||
pip install --upgrade pip
|
||||
log_info "虚拟环境设置完成"
|
||||
}
|
||||
|
||||
# 安装依赖
|
||||
install_dependencies() {
|
||||
log_info "安装Python依赖..."
|
||||
pip install -r requirements.txt
|
||||
log_info "依赖安装完成"
|
||||
}
|
||||
|
||||
# 数据库迁移
|
||||
run_migrations() {
|
||||
log_info "运行数据库迁移..."
|
||||
|
||||
# 检查数据库文件
|
||||
if [ ! -f "tsp_assistant.db" ]; then
|
||||
log_info "初始化数据库..."
|
||||
python init_database.py
|
||||
fi
|
||||
|
||||
log_info "数据库迁移完成"
|
||||
}
|
||||
|
||||
# 创建systemd服务文件
|
||||
create_systemd_service() {
|
||||
local service_name=$1
|
||||
local app_path=$2
|
||||
local service_file="/etc/systemd/system/${service_name}.service"
|
||||
|
||||
log_info "创建systemd服务文件: $service_file"
|
||||
|
||||
sudo tee "$service_file" > /dev/null <<EOF
|
||||
[Unit]
|
||||
Description=TSP智能助手服务
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=$app_path
|
||||
Environment=PATH=$app_path/venv/bin
|
||||
ExecStart=$app_path/venv/bin/python start_dashboard.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable "$service_name"
|
||||
log_info "systemd服务创建完成"
|
||||
}
|
||||
|
||||
# 创建nginx配置
|
||||
create_nginx_config() {
|
||||
local domain=$1
|
||||
local app_port=$2
|
||||
local config_file="/etc/nginx/sites-available/tsp_assistant"
|
||||
|
||||
log_info "创建nginx配置: $config_file"
|
||||
|
||||
sudo tee "$config_file" > /dev/null <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name $domain;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:$app_port;
|
||||
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;
|
||||
}
|
||||
|
||||
location /static {
|
||||
alias $app_path/src/web/static;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# 启用站点
|
||||
sudo ln -sf "$config_file" /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
log_info "nginx配置完成"
|
||||
}
|
||||
|
||||
# 主部署函数
|
||||
deploy() {
|
||||
local environment=${1:-production}
|
||||
local domain=${2:-localhost}
|
||||
local app_port=${3:-5000}
|
||||
|
||||
log_info "开始部署TSP智能助手到 $environment 环境"
|
||||
|
||||
# 设置部署路径
|
||||
case $environment in
|
||||
development)
|
||||
DEPLOY_PATH="./dev_deploy"
|
||||
SERVICE_NAME=""
|
||||
;;
|
||||
staging)
|
||||
DEPLOY_PATH="/opt/tsp_assistant_staging"
|
||||
SERVICE_NAME="tsp_assistant_staging"
|
||||
;;
|
||||
production)
|
||||
DEPLOY_PATH="/opt/tsp_assistant"
|
||||
SERVICE_NAME="tsp_assistant"
|
||||
;;
|
||||
*)
|
||||
log_error "未知环境: $environment"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# 检查依赖
|
||||
check_dependencies
|
||||
|
||||
# 创建部署目录
|
||||
log_info "创建部署目录: $DEPLOY_PATH"
|
||||
sudo mkdir -p "$DEPLOY_PATH"
|
||||
sudo chown $USER:$USER "$DEPLOY_PATH"
|
||||
|
||||
# 复制文件
|
||||
log_info "复制应用文件..."
|
||||
cp -r . "$DEPLOY_PATH/"
|
||||
cd "$DEPLOY_PATH"
|
||||
|
||||
# 设置虚拟环境
|
||||
setup_venv "venv"
|
||||
|
||||
# 安装依赖
|
||||
install_dependencies
|
||||
|
||||
# 运行迁移
|
||||
run_migrations
|
||||
|
||||
# 创建服务文件(非开发环境)
|
||||
if [ "$environment" != "development" ] && [ -n "$SERVICE_NAME" ]; then
|
||||
create_systemd_service "$SERVICE_NAME" "$DEPLOY_PATH"
|
||||
create_nginx_config "$domain" "$app_port"
|
||||
fi
|
||||
|
||||
log_info "部署完成!"
|
||||
|
||||
if [ "$environment" != "development" ]; then
|
||||
log_info "启动服务..."
|
||||
sudo systemctl start "$SERVICE_NAME"
|
||||
sudo systemctl status "$SERVICE_NAME"
|
||||
else
|
||||
log_info "开发环境部署完成,使用以下命令启动:"
|
||||
log_info "cd $DEPLOY_PATH && source venv/bin/activate && python start_dashboard.py"
|
||||
fi
|
||||
}
|
||||
|
||||
# 回滚函数
|
||||
rollback() {
|
||||
local backup_name=$1
|
||||
|
||||
if [ -z "$backup_name" ]; then
|
||||
log_error "请指定备份名称"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "回滚到备份: $backup_name"
|
||||
|
||||
# 停止服务
|
||||
sudo systemctl stop tsp_assistant
|
||||
|
||||
# 恢复备份
|
||||
if [ -d "backups/$backup_name" ]; then
|
||||
sudo rm -rf /opt/tsp_assistant
|
||||
sudo cp -r "backups/$backup_name" /opt/tsp_assistant
|
||||
sudo chown -R www-data:www-data /opt/tsp_assistant
|
||||
|
||||
# 重启服务
|
||||
sudo systemctl start tsp_assistant
|
||||
log_info "回滚完成"
|
||||
else
|
||||
log_error "备份不存在: $backup_name"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 版本检查
|
||||
check_version() {
|
||||
log_info "检查版本信息..."
|
||||
if [ -f "version.json" ]; then
|
||||
local version=$(python3 -c "import json; print(json.load(open('version.json'))['version'])" 2>/dev/null || echo "unknown")
|
||||
log_info "当前版本: $version"
|
||||
else
|
||||
log_warn "版本文件不存在"
|
||||
fi
|
||||
}
|
||||
|
||||
# 创建部署包
|
||||
create_deployment_package() {
|
||||
local package_name="tsp_assistant_$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||
log_info "创建部署包: $package_name"
|
||||
|
||||
# 排除不需要的文件
|
||||
tar --exclude='.git' \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='.env' \
|
||||
--exclude='logs/*' \
|
||||
--exclude='backups/*' \
|
||||
--exclude='dev_deploy' \
|
||||
-czf "$package_name" .
|
||||
|
||||
log_info "部署包创建完成: $package_name"
|
||||
echo "$package_name"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
case ${1:-deploy} in
|
||||
deploy)
|
||||
check_version
|
||||
deploy "$2" "$3" "$4"
|
||||
;;
|
||||
rollback)
|
||||
rollback "$2"
|
||||
;;
|
||||
package)
|
||||
create_deployment_package
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {deploy|rollback|package} [environment] [domain] [port]"
|
||||
echo "环境: development, staging, production"
|
||||
echo ""
|
||||
echo "命令说明:"
|
||||
echo " deploy - 部署到指定环境"
|
||||
echo " rollback - 回滚到指定备份"
|
||||
echo " package - 创建部署包"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,204 +0,0 @@
|
||||
#!/bin/bash
|
||||
# TSP智能助手Docker部署脚本
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查Docker和Docker Compose
|
||||
check_dependencies() {
|
||||
log_info "检查依赖..."
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker未安装,请先安装Docker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
log_error "Docker Compose未安装,请先安装Docker Compose"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "依赖检查通过"
|
||||
}
|
||||
|
||||
# 创建必要的目录
|
||||
create_directories() {
|
||||
log_info "创建必要的目录..."
|
||||
|
||||
mkdir -p logs/nginx
|
||||
mkdir -p monitoring/grafana/provisioning/datasources
|
||||
mkdir -p monitoring/grafana/provisioning/dashboards
|
||||
mkdir -p ssl
|
||||
mkdir -p data
|
||||
mkdir -p backups
|
||||
mkdir -p uploads
|
||||
mkdir -p config
|
||||
|
||||
log_success "目录创建完成"
|
||||
}
|
||||
|
||||
# 构建镜像
|
||||
build_images() {
|
||||
log_info "构建Docker镜像..."
|
||||
|
||||
# 构建主应用镜像
|
||||
docker-compose build --no-cache tsp-assistant
|
||||
|
||||
log_success "镜像构建完成"
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
start_services() {
|
||||
log_info "启动服务..."
|
||||
|
||||
# 启动基础服务(MySQL, Redis)
|
||||
docker-compose up -d mysql redis
|
||||
|
||||
# 等待数据库启动
|
||||
log_info "等待数据库启动..."
|
||||
sleep 30
|
||||
|
||||
# 启动主应用
|
||||
docker-compose up -d tsp-assistant
|
||||
|
||||
# 启动其他服务
|
||||
docker-compose up -d nginx prometheus grafana
|
||||
|
||||
log_success "服务启动完成"
|
||||
}
|
||||
|
||||
# 检查服务状态
|
||||
check_services() {
|
||||
log_info "检查服务状态..."
|
||||
|
||||
sleep 10
|
||||
|
||||
# 检查主应用
|
||||
if curl -f http://localhost:5000/api/health &> /dev/null; then
|
||||
log_success "TSP助手服务正常"
|
||||
else
|
||||
log_warning "TSP助手服务可能未完全启动"
|
||||
fi
|
||||
|
||||
# 检查Nginx
|
||||
if curl -f http://localhost/health &> /dev/null; then
|
||||
log_success "Nginx服务正常"
|
||||
else
|
||||
log_warning "Nginx服务可能未完全启动"
|
||||
fi
|
||||
|
||||
# 检查Prometheus
|
||||
if curl -f http://localhost:9090 &> /dev/null; then
|
||||
log_success "Prometheus服务正常"
|
||||
else
|
||||
log_warning "Prometheus服务可能未完全启动"
|
||||
fi
|
||||
|
||||
# 检查Grafana
|
||||
if curl -f http://localhost:3000 &> /dev/null; then
|
||||
log_success "Grafana服务正常"
|
||||
else
|
||||
log_warning "Grafana服务可能未完全启动"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示服务信息
|
||||
show_info() {
|
||||
log_info "服务访问信息:"
|
||||
echo " TSP助手: http://localhost:5000"
|
||||
echo " Nginx代理: http://localhost"
|
||||
echo " Prometheus: http://localhost:9090"
|
||||
echo " Grafana: http://localhost:3000 (admin/admin123456)"
|
||||
echo " MySQL: localhost:3306 (root/root123456)"
|
||||
echo " Redis: localhost:6379 (密码: redis123456)"
|
||||
echo ""
|
||||
log_info "查看日志命令:"
|
||||
echo " docker-compose logs -f tsp-assistant"
|
||||
echo " docker-compose logs -f mysql"
|
||||
echo " docker-compose logs -f redis"
|
||||
echo " docker-compose logs -f nginx"
|
||||
}
|
||||
|
||||
# 停止服务
|
||||
stop_services() {
|
||||
log_info "停止服务..."
|
||||
docker-compose down
|
||||
log_success "服务已停止"
|
||||
}
|
||||
|
||||
# 清理资源
|
||||
cleanup() {
|
||||
log_info "清理Docker资源..."
|
||||
docker system prune -f
|
||||
log_success "清理完成"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
case "${1:-start}" in
|
||||
"start")
|
||||
check_dependencies
|
||||
create_directories
|
||||
build_images
|
||||
start_services
|
||||
check_services
|
||||
show_info
|
||||
;;
|
||||
"stop")
|
||||
stop_services
|
||||
;;
|
||||
"restart")
|
||||
stop_services
|
||||
sleep 5
|
||||
start_services
|
||||
check_services
|
||||
show_info
|
||||
;;
|
||||
"cleanup")
|
||||
stop_services
|
||||
cleanup
|
||||
;;
|
||||
"logs")
|
||||
docker-compose logs -f "${2:-tsp-assistant}"
|
||||
;;
|
||||
"status")
|
||||
docker-compose ps
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {start|stop|restart|cleanup|logs|status}"
|
||||
echo " start - 启动所有服务"
|
||||
echo " stop - 停止所有服务"
|
||||
echo " restart - 重启所有服务"
|
||||
echo " cleanup - 清理Docker资源"
|
||||
echo " logs - 查看日志 (可选指定服务名)"
|
||||
echo " status - 查看服务状态"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
@@ -1,277 +0,0 @@
|
||||
#!/bin/bash
|
||||
# TSP智能助手监控脚本
|
||||
|
||||
# 配置变量
|
||||
APP_NAME="tsp_assistant"
|
||||
SERVICE_NAME="tsp_assistant"
|
||||
HEALTH_URL="http://localhost:5000/api/health"
|
||||
LOG_FILE="./logs/monitor.log"
|
||||
ALERT_EMAIL="admin@example.com"
|
||||
ALERT_PHONE="13800138000"
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] INFO${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARN${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 发送告警
|
||||
send_alert() {
|
||||
local message=$1
|
||||
local level=$2
|
||||
|
||||
log_error "告警: $message"
|
||||
|
||||
# 发送邮件告警
|
||||
if command -v mail &> /dev/null; then
|
||||
echo "$message" | mail -s "[$level] TSP助手告警" "$ALERT_EMAIL"
|
||||
fi
|
||||
|
||||
# 发送短信告警(需要配置短信服务)
|
||||
# curl -X POST "https://api.sms.com/send" \
|
||||
# -d "phone=$ALERT_PHONE" \
|
||||
# -d "message=$message"
|
||||
}
|
||||
|
||||
# 检查服务状态
|
||||
check_service_status() {
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查健康状态
|
||||
check_health() {
|
||||
local response_code
|
||||
response_code=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null)
|
||||
|
||||
if [ "$response_code" = "200" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查响应时间
|
||||
check_response_time() {
|
||||
local response_time
|
||||
response_time=$(curl -s -o /dev/null -w "%{time_total}" "$HEALTH_URL" 2>/dev/null)
|
||||
|
||||
# 响应时间超过5秒认为异常
|
||||
if (( $(echo "$response_time > 5.0" | bc -l) )); then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查系统资源
|
||||
check_system_resources() {
|
||||
local cpu_usage
|
||||
local memory_usage
|
||||
local disk_usage
|
||||
|
||||
# CPU使用率
|
||||
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | awk -F'%' '{print $1}')
|
||||
|
||||
# 内存使用率
|
||||
memory_usage=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100.0}')
|
||||
|
||||
# 磁盘使用率
|
||||
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||
|
||||
# 检查阈值
|
||||
if (( $(echo "$cpu_usage > 80" | bc -l) )); then
|
||||
send_alert "CPU使用率过高: ${cpu_usage}%" "HIGH"
|
||||
fi
|
||||
|
||||
if (( $(echo "$memory_usage > 80" | bc -l) )); then
|
||||
send_alert "内存使用率过高: ${memory_usage}%" "HIGH"
|
||||
fi
|
||||
|
||||
if [ "$disk_usage" -gt 80 ]; then
|
||||
send_alert "磁盘使用率过高: ${disk_usage}%" "HIGH"
|
||||
fi
|
||||
|
||||
log_info "系统资源 - CPU: ${cpu_usage}%, 内存: ${memory_usage}%, 磁盘: ${disk_usage}%"
|
||||
}
|
||||
|
||||
# 检查日志错误
|
||||
check_log_errors() {
|
||||
local log_file="./logs/tsp_assistant.log"
|
||||
local error_count
|
||||
|
||||
if [ -f "$log_file" ]; then
|
||||
# 检查最近5分钟的错误日志
|
||||
error_count=$(tail -n 100 "$log_file" | grep -c "ERROR" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$error_count" -gt 10 ]; then
|
||||
send_alert "最近5分钟错误日志过多: $error_count 条" "MEDIUM"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查数据库连接
|
||||
check_database() {
|
||||
local db_file="./tsp_assistant.db"
|
||||
|
||||
if [ -f "$db_file" ]; then
|
||||
# 检查数据库文件大小
|
||||
local db_size
|
||||
db_size=$(du -h "$db_file" | cut -f1)
|
||||
log_info "数据库大小: $db_size"
|
||||
|
||||
# 检查数据库是否可读
|
||||
if ! sqlite3 "$db_file" "SELECT 1;" > /dev/null 2>&1; then
|
||||
send_alert "数据库连接失败" "CRITICAL"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# 自动重启服务
|
||||
restart_service() {
|
||||
log_warn "尝试重启服务..."
|
||||
|
||||
sudo systemctl restart "$SERVICE_NAME"
|
||||
sleep 10
|
||||
|
||||
if check_service_status && check_health; then
|
||||
log_info "服务重启成功"
|
||||
return 0
|
||||
else
|
||||
log_error "服务重启失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 主监控循环
|
||||
monitor_loop() {
|
||||
local consecutive_failures=0
|
||||
local max_failures=3
|
||||
|
||||
while true; do
|
||||
log_info "开始监控检查..."
|
||||
|
||||
# 检查服务状态
|
||||
if ! check_service_status; then
|
||||
log_error "服务未运行"
|
||||
send_alert "TSP助手服务未运行" "CRITICAL"
|
||||
consecutive_failures=$((consecutive_failures + 1))
|
||||
else
|
||||
# 检查健康状态
|
||||
if ! check_health; then
|
||||
log_error "健康检查失败"
|
||||
send_alert "TSP助手健康检查失败" "HIGH"
|
||||
consecutive_failures=$((consecutive_failures + 1))
|
||||
else
|
||||
# 检查响应时间
|
||||
if ! check_response_time; then
|
||||
log_warn "响应时间过长"
|
||||
send_alert "TSP助手响应时间过长" "MEDIUM"
|
||||
fi
|
||||
|
||||
consecutive_failures=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查系统资源
|
||||
check_system_resources
|
||||
|
||||
# 检查日志错误
|
||||
check_log_errors
|
||||
|
||||
# 检查数据库
|
||||
check_database
|
||||
|
||||
# 连续失败处理
|
||||
if [ "$consecutive_failures" -ge "$max_failures" ]; then
|
||||
log_error "连续失败次数达到阈值,尝试重启服务"
|
||||
if restart_service; then
|
||||
consecutive_failures=0
|
||||
else
|
||||
send_alert "TSP助手服务重启失败,需要人工干预" "CRITICAL"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 等待下次检查
|
||||
sleep 60
|
||||
done
|
||||
}
|
||||
|
||||
# 一次性检查
|
||||
single_check() {
|
||||
log_info "执行一次性健康检查..."
|
||||
|
||||
if check_service_status; then
|
||||
log_info "✓ 服务运行正常"
|
||||
else
|
||||
log_error "✗ 服务未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if check_health; then
|
||||
log_info "✓ 健康检查通过"
|
||||
else
|
||||
log_error "✗ 健康检查失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if check_response_time; then
|
||||
log_info "✓ 响应时间正常"
|
||||
else
|
||||
log_warn "⚠ 响应时间过长"
|
||||
fi
|
||||
|
||||
check_system_resources
|
||||
check_log_errors
|
||||
check_database
|
||||
|
||||
log_info "健康检查完成"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
# 创建日志目录
|
||||
mkdir -p logs
|
||||
|
||||
case ${1:-monitor} in
|
||||
monitor)
|
||||
log_info "启动TSP助手监控服务..."
|
||||
monitor_loop
|
||||
;;
|
||||
check)
|
||||
single_check
|
||||
;;
|
||||
restart)
|
||||
restart_service
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {monitor|check|restart}"
|
||||
echo " monitor - 持续监控模式"
|
||||
echo " check - 一次性健康检查"
|
||||
echo " restart - 重启服务"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
@@ -1,285 +0,0 @@
|
||||
@echo off
|
||||
REM TSP智能助手快速更新脚本 (Windows)
|
||||
REM 支持热更新和完整更新
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM 颜色定义
|
||||
set "GREEN=[32m"
|
||||
set "YELLOW=[33m"
|
||||
set "RED=[31m"
|
||||
set "NC=[0m"
|
||||
|
||||
REM 配置变量
|
||||
set "APP_NAME=tsp_assistant"
|
||||
set "DEPLOY_PATH=."
|
||||
set "BACKUP_PATH=.\backups"
|
||||
set "HEALTH_URL=http://localhost:5000/api/health"
|
||||
|
||||
REM 解析参数
|
||||
set "ACTION=%1"
|
||||
set "SOURCE_PATH=%2"
|
||||
set "ENVIRONMENT=%3"
|
||||
|
||||
if "%ACTION%"=="" (
|
||||
echo 用法: %0 {check^|hot-update^|full-update^|auto-update^|rollback} [源路径] [环境]
|
||||
echo.
|
||||
echo 命令说明:
|
||||
echo check - 检查更新可用性
|
||||
echo hot-update - 热更新(不重启服务)
|
||||
echo full-update - 完整更新(重启服务)
|
||||
echo auto-update - 自动更新(智能选择)
|
||||
echo rollback - 回滚到指定备份
|
||||
echo.
|
||||
echo 环境: development, staging, production
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%ENVIRONMENT%"=="" set "ENVIRONMENT=production"
|
||||
if "%SOURCE_PATH%"=="" set "SOURCE_PATH=."
|
||||
|
||||
REM 日志函数
|
||||
:log_info
|
||||
echo %GREEN%[INFO]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
:log_warn
|
||||
echo %YELLOW%[WARN]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
:log_error
|
||||
echo %RED%[ERROR]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
REM 检查更新可用性
|
||||
:check_update
|
||||
call :log_info "检查更新可用性..."
|
||||
if not exist "%SOURCE_PATH%\version.json" (
|
||||
call :log_error "源路径中未找到版本文件"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 比较版本
|
||||
for /f "tokens=2 delims=:" %%a in ('findstr "version" "%SOURCE_PATH%\version.json"') do (
|
||||
set "NEW_VERSION=%%a"
|
||||
set "NEW_VERSION=!NEW_VERSION: =!"
|
||||
set "NEW_VERSION=!NEW_VERSION:"=!"
|
||||
set "NEW_VERSION=!NEW_VERSION:,=!"
|
||||
)
|
||||
|
||||
if exist "version.json" (
|
||||
for /f "tokens=2 delims=:" %%a in ('findstr "version" "version.json"') do (
|
||||
set "CURRENT_VERSION=%%a"
|
||||
set "CURRENT_VERSION=!CURRENT_VERSION: =!"
|
||||
set "CURRENT_VERSION=!CURRENT_VERSION:"=!"
|
||||
set "CURRENT_VERSION=!CURRENT_VERSION:,=!"
|
||||
)
|
||||
) else (
|
||||
set "CURRENT_VERSION=unknown"
|
||||
)
|
||||
|
||||
if "!NEW_VERSION!"=="!CURRENT_VERSION!" (
|
||||
call :log_info "没有更新可用 (当前版本: !CURRENT_VERSION!)"
|
||||
) else (
|
||||
call :log_info "发现更新: !CURRENT_VERSION! -> !NEW_VERSION!"
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 创建备份
|
||||
:create_backup
|
||||
set "TIMESTAMP=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%"
|
||||
set "TIMESTAMP=!TIMESTAMP: =0!"
|
||||
set "BACKUP_NAME=%APP_NAME%_backup_!TIMESTAMP!"
|
||||
|
||||
call :log_info "创建备份: !BACKUP_NAME!"
|
||||
|
||||
if not exist "%BACKUP_PATH%" mkdir "%BACKUP_PATH%"
|
||||
mkdir "%BACKUP_PATH%\!BACKUP_NAME!"
|
||||
|
||||
REM 备份应用文件
|
||||
if exist "%DEPLOY_PATH%" (
|
||||
call :log_info "备份应用文件..."
|
||||
xcopy "%DEPLOY_PATH%\*" "%BACKUP_PATH%\!BACKUP_NAME!\" /E /I /Y
|
||||
)
|
||||
|
||||
REM 备份数据库
|
||||
if exist "%DEPLOY_PATH%\tsp_assistant.db" (
|
||||
call :log_info "备份数据库..."
|
||||
mkdir "%BACKUP_PATH%\!BACKUP_NAME!\database"
|
||||
copy "%DEPLOY_PATH%\tsp_assistant.db" "%BACKUP_PATH%\!BACKUP_NAME!\database\"
|
||||
)
|
||||
|
||||
call :log_info "备份完成: !BACKUP_NAME!"
|
||||
echo !BACKUP_NAME!
|
||||
goto :eof
|
||||
|
||||
REM 热更新
|
||||
:hot_update
|
||||
call :log_info "开始热更新..."
|
||||
|
||||
REM 支持热更新的文件列表
|
||||
set "HOT_UPDATE_FILES=src\web\static\js\dashboard.js src\web\static\css\style.css src\web\templates\dashboard.html src\web\app.py"
|
||||
|
||||
set "UPDATED_COUNT=0"
|
||||
for %%f in (%HOT_UPDATE_FILES%) do (
|
||||
if exist "%SOURCE_PATH%\%%f" (
|
||||
call :log_info "更新文件: %%f"
|
||||
if not exist "%DEPLOY_PATH%\%%f" mkdir "%DEPLOY_PATH%\%%f" 2>nul
|
||||
copy "%SOURCE_PATH%\%%f" "%DEPLOY_PATH%\%%f" /Y >nul
|
||||
set /a UPDATED_COUNT+=1
|
||||
)
|
||||
)
|
||||
|
||||
if !UPDATED_COUNT! gtr 0 (
|
||||
call :log_info "热更新完成,更新了 !UPDATED_COUNT! 个文件"
|
||||
) else (
|
||||
call :log_info "没有文件需要热更新"
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 完整更新
|
||||
:full_update
|
||||
call :log_info "开始完整更新..."
|
||||
|
||||
REM 创建备份
|
||||
call :create_backup
|
||||
set "BACKUP_NAME=!BACKUP_NAME!"
|
||||
|
||||
REM 停止服务(如果运行中)
|
||||
call :log_info "停止服务..."
|
||||
taskkill /f /im python.exe 2>nul || echo 服务未运行
|
||||
|
||||
REM 更新文件
|
||||
call :log_info "更新应用文件..."
|
||||
if exist "%DEPLOY_PATH%" rmdir /s /q "%DEPLOY_PATH%"
|
||||
mkdir "%DEPLOY_PATH%"
|
||||
xcopy "%SOURCE_PATH%\*" "%DEPLOY_PATH%\" /E /I /Y
|
||||
|
||||
REM 安装依赖
|
||||
call :log_info "安装依赖..."
|
||||
cd "%DEPLOY_PATH%"
|
||||
if exist "requirements.txt" (
|
||||
pip install -r requirements.txt
|
||||
)
|
||||
|
||||
REM 运行数据库迁移
|
||||
call :log_info "运行数据库迁移..."
|
||||
if exist "init_database.py" (
|
||||
python init_database.py
|
||||
)
|
||||
|
||||
REM 启动服务
|
||||
call :log_info "启动服务..."
|
||||
start /b python start_dashboard.py
|
||||
|
||||
REM 等待服务启动
|
||||
call :log_info "等待服务启动..."
|
||||
timeout /t 15 /nobreak >nul
|
||||
|
||||
REM 健康检查
|
||||
call :log_info "执行健康检查..."
|
||||
set "RETRY_COUNT=0"
|
||||
set "MAX_RETRIES=10"
|
||||
|
||||
:health_check_loop
|
||||
if !RETRY_COUNT! geq !MAX_RETRIES! (
|
||||
call :log_error "健康检查失败,开始回滚..."
|
||||
call :rollback !BACKUP_NAME!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
curl -f "%HEALTH_URL%" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
call :log_info "健康检查通过!"
|
||||
call :log_info "更新成功!"
|
||||
call :log_info "备份名称: !BACKUP_NAME!"
|
||||
exit /b 0
|
||||
) else (
|
||||
call :log_warn "健康检查失败,重试中... (!RETRY_COUNT!/!MAX_RETRIES!)"
|
||||
set /a RETRY_COUNT+=1
|
||||
timeout /t 5 /nobreak >nul
|
||||
goto :health_check_loop
|
||||
)
|
||||
|
||||
REM 回滚
|
||||
:rollback
|
||||
set "BACKUP_NAME=%1"
|
||||
if "%BACKUP_NAME%"=="" (
|
||||
call :log_error "请指定备份名称"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
call :log_info "开始回滚到备份: !BACKUP_NAME!"
|
||||
|
||||
if not exist "%BACKUP_PATH%\!BACKUP_NAME!" (
|
||||
call :log_error "备份不存在: !BACKUP_NAME!"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 停止服务
|
||||
call :log_info "停止服务..."
|
||||
taskkill /f /im python.exe 2>nul || echo 服务未运行
|
||||
|
||||
REM 恢复文件
|
||||
call :log_info "恢复文件..."
|
||||
if exist "%DEPLOY_PATH%" rmdir /s /q "%DEPLOY_PATH%"
|
||||
mkdir "%DEPLOY_PATH%"
|
||||
xcopy "%BACKUP_PATH%\!BACKUP_NAME!\*" "%DEPLOY_PATH%\" /E /I /Y
|
||||
|
||||
REM 恢复数据库
|
||||
if exist "%BACKUP_PATH%\!BACKUP_NAME!\database\tsp_assistant.db" (
|
||||
call :log_info "恢复数据库..."
|
||||
copy "%BACKUP_PATH%\!BACKUP_NAME!\database\tsp_assistant.db" "%DEPLOY_PATH%\"
|
||||
)
|
||||
|
||||
REM 启动服务
|
||||
call :log_info "启动服务..."
|
||||
cd "%DEPLOY_PATH%"
|
||||
start /b python start_dashboard.py
|
||||
|
||||
REM 等待服务启动
|
||||
timeout /t 15 /nobreak >nul
|
||||
|
||||
REM 健康检查
|
||||
curl -f "%HEALTH_URL%" >nul 2>&1
|
||||
if !errorlevel! equ 0 (
|
||||
call :log_info "回滚成功!"
|
||||
) else (
|
||||
call :log_error "回滚后健康检查失败"
|
||||
exit /b 1
|
||||
)
|
||||
goto :eof
|
||||
|
||||
REM 自动更新
|
||||
:auto_update
|
||||
call :log_info "开始自动更新..."
|
||||
|
||||
REM 尝试热更新
|
||||
call :hot_update
|
||||
if !errorlevel! equ 0 (
|
||||
call :log_info "热更新成功"
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
REM 热更新失败,进行完整更新
|
||||
call :log_info "热更新失败,进行完整更新..."
|
||||
call :full_update
|
||||
goto :eof
|
||||
|
||||
REM 主逻辑
|
||||
if "%ACTION%"=="check" (
|
||||
call :check_update
|
||||
) else if "%ACTION%"=="hot-update" (
|
||||
call :hot_update
|
||||
) else if "%ACTION%"=="full-update" (
|
||||
call :full_update
|
||||
) else if "%ACTION%"=="auto-update" (
|
||||
call :auto_update
|
||||
) else if "%ACTION%"=="rollback" (
|
||||
call :rollback "%SOURCE_PATH%"
|
||||
) else (
|
||||
call :log_error "未知操作: %ACTION%"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
endlocal
|
||||
@@ -1,477 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
TSP智能助手更新管理器
|
||||
支持热更新、版本管理、回滚等功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
class UpdateManager:
|
||||
"""更新管理器"""
|
||||
|
||||
def __init__(self, config_file: str = "update_config.json"):
|
||||
self.config_file = config_file
|
||||
self.config = self._load_config()
|
||||
self.version_manager = None
|
||||
|
||||
# 初始化版本管理器
|
||||
try:
|
||||
from version import VersionManager
|
||||
self.version_manager = VersionManager()
|
||||
except ImportError:
|
||||
print("警告: 版本管理器不可用")
|
||||
|
||||
def _load_config(self) -> Dict:
|
||||
"""加载更新配置"""
|
||||
default_config = {
|
||||
"app_name": "tsp_assistant",
|
||||
"deploy_path": "/opt/tsp_assistant",
|
||||
"backup_path": "./backups",
|
||||
"service_name": "tsp_assistant",
|
||||
"health_url": "http://localhost:5000/api/health",
|
||||
"update_timeout": 300,
|
||||
"rollback_enabled": True,
|
||||
"auto_backup": True,
|
||||
"hot_update_enabled": True,
|
||||
"environments": {
|
||||
"development": {
|
||||
"path": "./dev_deploy",
|
||||
"service_name": "",
|
||||
"auto_restart": False
|
||||
},
|
||||
"staging": {
|
||||
"path": "/opt/tsp_assistant_staging",
|
||||
"service_name": "tsp_assistant_staging",
|
||||
"auto_restart": True
|
||||
},
|
||||
"production": {
|
||||
"path": "/opt/tsp_assistant",
|
||||
"service_name": "tsp_assistant",
|
||||
"auto_restart": True
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if os.path.exists(self.config_file):
|
||||
try:
|
||||
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
# 合并默认配置
|
||||
default_config.update(config)
|
||||
except Exception as e:
|
||||
print(f"加载配置文件失败: {e}")
|
||||
|
||||
return default_config
|
||||
|
||||
def _save_config(self):
|
||||
"""保存配置"""
|
||||
try:
|
||||
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.config, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"保存配置文件失败: {e}")
|
||||
|
||||
def check_update_available(self, source_path: str) -> Tuple[bool, str, str]:
|
||||
"""检查是否有更新可用"""
|
||||
if not self.version_manager:
|
||||
return False, "unknown", "unknown"
|
||||
|
||||
current_version = self.version_manager.get_version()
|
||||
|
||||
# 检查源路径的版本
|
||||
try:
|
||||
source_version_file = os.path.join(source_path, "version.json")
|
||||
if os.path.exists(source_version_file):
|
||||
with open(source_version_file, 'r', encoding='utf-8') as f:
|
||||
source_info = json.load(f)
|
||||
source_version = source_info.get("version", "unknown")
|
||||
else:
|
||||
return False, current_version, "unknown"
|
||||
except Exception as e:
|
||||
print(f"检查源版本失败: {e}")
|
||||
return False, current_version, "unknown"
|
||||
|
||||
# 比较版本
|
||||
if source_version != current_version:
|
||||
return True, current_version, source_version
|
||||
|
||||
return False, current_version, source_version
|
||||
|
||||
def create_backup(self, environment: str = "production") -> str:
|
||||
"""创建备份"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_name = f"{self.config['app_name']}_backup_{timestamp}"
|
||||
backup_path = os.path.join(self.config["backup_path"], backup_name)
|
||||
|
||||
print(f"创建备份: {backup_name}")
|
||||
|
||||
# 创建备份目录
|
||||
os.makedirs(backup_path, exist_ok=True)
|
||||
|
||||
# 获取部署路径
|
||||
env_config = self.config["environments"].get(environment, {})
|
||||
deploy_path = env_config.get("path", self.config["deploy_path"])
|
||||
|
||||
# 备份应用文件
|
||||
if os.path.exists(deploy_path):
|
||||
print("备份应用文件...")
|
||||
shutil.copytree(deploy_path, os.path.join(backup_path, "app"))
|
||||
|
||||
# 备份数据库
|
||||
db_file = os.path.join(deploy_path, "tsp_assistant.db")
|
||||
if os.path.exists(db_file):
|
||||
print("备份数据库...")
|
||||
os.makedirs(os.path.join(backup_path, "database"), exist_ok=True)
|
||||
shutil.copy2(db_file, os.path.join(backup_path, "database", "tsp_assistant.db"))
|
||||
|
||||
# 保存备份信息
|
||||
backup_info = {
|
||||
"backup_name": backup_name,
|
||||
"backup_path": backup_path,
|
||||
"timestamp": timestamp,
|
||||
"environment": environment,
|
||||
"version": self.version_manager.get_version() if self.version_manager else "unknown",
|
||||
"git_commit": self._get_git_commit(deploy_path)
|
||||
}
|
||||
|
||||
with open(os.path.join(backup_path, "backup_info.json"), 'w', encoding='utf-8') as f:
|
||||
json.dump(backup_info, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"备份完成: {backup_name}")
|
||||
return backup_name
|
||||
|
||||
def _get_git_commit(self, path: str) -> str:
|
||||
"""获取Git提交哈希"""
|
||||
try:
|
||||
result = subprocess.run(['git', 'rev-parse', 'HEAD'],
|
||||
cwd=path, capture_output=True, text=True)
|
||||
return result.stdout.strip()[:8] if result.returncode == 0 else "unknown"
|
||||
except:
|
||||
return "unknown"
|
||||
|
||||
def hot_update(self, source_path: str, environment: str = "production") -> bool:
|
||||
"""热更新(不重启服务)"""
|
||||
if not self.config["hot_update_enabled"]:
|
||||
print("热更新未启用")
|
||||
return False
|
||||
|
||||
print("开始热更新...")
|
||||
|
||||
env_config = self.config["environments"].get(environment, {})
|
||||
deploy_path = env_config.get("path", self.config["deploy_path"])
|
||||
|
||||
# 检查哪些文件可以热更新
|
||||
hot_update_files = [
|
||||
"src/web/static/js/dashboard.js",
|
||||
"src/web/static/css/style.css",
|
||||
"src/web/templates/dashboard.html",
|
||||
"src/web/app.py",
|
||||
"src/knowledge_base/knowledge_manager.py",
|
||||
"src/dialogue/realtime_chat.py"
|
||||
]
|
||||
|
||||
updated_files = []
|
||||
for file_path in hot_update_files:
|
||||
source_file = os.path.join(source_path, file_path)
|
||||
target_file = os.path.join(deploy_path, file_path)
|
||||
|
||||
if os.path.exists(source_file):
|
||||
# 检查文件是否有变化
|
||||
if not os.path.exists(target_file) or not self._files_equal(source_file, target_file):
|
||||
print(f"更新文件: {file_path}")
|
||||
os.makedirs(os.path.dirname(target_file), exist_ok=True)
|
||||
shutil.copy2(source_file, target_file)
|
||||
updated_files.append(file_path)
|
||||
|
||||
if updated_files:
|
||||
print(f"热更新完成,更新了 {len(updated_files)} 个文件")
|
||||
return True
|
||||
else:
|
||||
print("没有文件需要热更新")
|
||||
return False
|
||||
|
||||
def _files_equal(self, file1: str, file2: str) -> bool:
|
||||
"""比较两个文件是否相等"""
|
||||
try:
|
||||
with open(file1, 'rb') as f1, open(file2, 'rb') as f2:
|
||||
return f1.read() == f2.read()
|
||||
except:
|
||||
return False
|
||||
|
||||
def full_update(self, source_path: str, environment: str = "production",
|
||||
create_backup: bool = True) -> bool:
|
||||
"""完整更新(重启服务)"""
|
||||
print("开始完整更新...")
|
||||
|
||||
env_config = self.config["environments"].get(environment, {})
|
||||
deploy_path = env_config.get("path", self.config["deploy_path"])
|
||||
service_name = env_config.get("service_name", self.config["service_name"])
|
||||
auto_restart = env_config.get("auto_restart", True)
|
||||
|
||||
# 创建备份
|
||||
backup_name = None
|
||||
if create_backup and self.config["auto_backup"]:
|
||||
backup_name = self.create_backup(environment)
|
||||
|
||||
try:
|
||||
# 停止服务
|
||||
if auto_restart and service_name:
|
||||
print(f"停止服务: {service_name}")
|
||||
subprocess.run(['sudo', 'systemctl', 'stop', service_name], check=True)
|
||||
|
||||
# 更新文件
|
||||
print("更新应用文件...")
|
||||
if os.path.exists(deploy_path):
|
||||
shutil.rmtree(deploy_path)
|
||||
os.makedirs(deploy_path, exist_ok=True)
|
||||
shutil.copytree(source_path, deploy_path, dirs_exist_ok=True)
|
||||
|
||||
# 设置权限
|
||||
subprocess.run(['sudo', 'chown', '-R', 'www-data:www-data', deploy_path], check=True)
|
||||
|
||||
# 安装依赖
|
||||
print("安装依赖...")
|
||||
requirements_file = os.path.join(deploy_path, "requirements.txt")
|
||||
if os.path.exists(requirements_file):
|
||||
subprocess.run(['sudo', '-u', 'www-data', 'python', '-m', 'pip', 'install', '-r', requirements_file],
|
||||
cwd=deploy_path, check=True)
|
||||
|
||||
# 运行数据库迁移
|
||||
print("运行数据库迁移...")
|
||||
init_script = os.path.join(deploy_path, "init_database.py")
|
||||
if os.path.exists(init_script):
|
||||
subprocess.run(['sudo', '-u', 'www-data', 'python', init_script],
|
||||
cwd=deploy_path, check=True)
|
||||
|
||||
# 启动服务
|
||||
if auto_restart and service_name:
|
||||
print(f"启动服务: {service_name}")
|
||||
subprocess.run(['sudo', 'systemctl', 'start', service_name], check=True)
|
||||
|
||||
# 等待服务启动
|
||||
print("等待服务启动...")
|
||||
time.sleep(15)
|
||||
|
||||
# 健康检查
|
||||
if self._health_check():
|
||||
print("更新成功!")
|
||||
return True
|
||||
else:
|
||||
print("健康检查失败,开始回滚...")
|
||||
if backup_name:
|
||||
self.rollback(backup_name, environment)
|
||||
return False
|
||||
else:
|
||||
print("更新完成(未重启服务)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"更新失败: {e}")
|
||||
if backup_name:
|
||||
print("开始回滚...")
|
||||
self.rollback(backup_name, environment)
|
||||
return False
|
||||
|
||||
def _health_check(self) -> bool:
|
||||
"""健康检查"""
|
||||
health_url = self.config["health_url"]
|
||||
max_retries = 10
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
response = requests.get(health_url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
retry_count += 1
|
||||
print(f"健康检查失败,重试中... ({retry_count}/{max_retries})")
|
||||
time.sleep(5)
|
||||
|
||||
return False
|
||||
|
||||
def rollback(self, backup_name: str, environment: str = "production") -> bool:
|
||||
"""回滚到指定备份"""
|
||||
print(f"开始回滚到备份: {backup_name}")
|
||||
|
||||
env_config = self.config["environments"].get(environment, {})
|
||||
deploy_path = env_config.get("path", self.config["deploy_path"])
|
||||
service_name = env_config.get("service_name", self.config["service_name"])
|
||||
auto_restart = env_config.get("auto_restart", True)
|
||||
|
||||
backup_path = os.path.join(self.config["backup_path"], backup_name)
|
||||
|
||||
if not os.path.exists(backup_path):
|
||||
print(f"备份不存在: {backup_name}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 停止服务
|
||||
if auto_restart and service_name:
|
||||
print(f"停止服务: {service_name}")
|
||||
subprocess.run(['sudo', 'systemctl', 'stop', service_name], check=True)
|
||||
|
||||
# 恢复文件
|
||||
print("恢复文件...")
|
||||
app_backup_path = os.path.join(backup_path, "app")
|
||||
if os.path.exists(app_backup_path):
|
||||
if os.path.exists(deploy_path):
|
||||
shutil.rmtree(deploy_path)
|
||||
shutil.copytree(app_backup_path, deploy_path)
|
||||
|
||||
# 恢复数据库
|
||||
db_backup_path = os.path.join(backup_path, "database", "tsp_assistant.db")
|
||||
if os.path.exists(db_backup_path):
|
||||
print("恢复数据库...")
|
||||
shutil.copy2(db_backup_path, os.path.join(deploy_path, "tsp_assistant.db"))
|
||||
|
||||
# 设置权限
|
||||
subprocess.run(['sudo', 'chown', '-R', 'www-data:www-data', deploy_path], check=True)
|
||||
|
||||
# 启动服务
|
||||
if auto_restart and service_name:
|
||||
print(f"启动服务: {service_name}")
|
||||
subprocess.run(['sudo', 'systemctl', 'start', service_name], check=True)
|
||||
|
||||
# 等待服务启动
|
||||
time.sleep(15)
|
||||
|
||||
# 健康检查
|
||||
if self._health_check():
|
||||
print("回滚成功!")
|
||||
return True
|
||||
else:
|
||||
print("回滚后健康检查失败")
|
||||
return False
|
||||
else:
|
||||
print("回滚完成(未重启服务)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"回滚失败: {e}")
|
||||
return False
|
||||
|
||||
def list_backups(self) -> List[Dict]:
|
||||
"""列出所有备份"""
|
||||
backups = []
|
||||
backup_dir = self.config["backup_path"]
|
||||
|
||||
if os.path.exists(backup_dir):
|
||||
for item in os.listdir(backup_dir):
|
||||
backup_path = os.path.join(backup_dir, item)
|
||||
if os.path.isdir(backup_path):
|
||||
info_file = os.path.join(backup_path, "backup_info.json")
|
||||
if os.path.exists(info_file):
|
||||
try:
|
||||
with open(info_file, 'r', encoding='utf-8') as f:
|
||||
backup_info = json.load(f)
|
||||
backups.append(backup_info)
|
||||
except:
|
||||
pass
|
||||
|
||||
return sorted(backups, key=lambda x: x.get("timestamp", ""), reverse=True)
|
||||
|
||||
def auto_update(self, source_path: str, environment: str = "production") -> bool:
|
||||
"""自动更新(智能选择热更新或完整更新)"""
|
||||
print("开始自动更新...")
|
||||
|
||||
# 检查是否有更新
|
||||
has_update, current_version, new_version = self.check_update_available(source_path)
|
||||
if not has_update:
|
||||
print("没有更新可用")
|
||||
return True
|
||||
|
||||
print(f"发现更新: {current_version} -> {new_version}")
|
||||
|
||||
# 尝试热更新
|
||||
if self.hot_update(source_path, environment):
|
||||
print("热更新成功")
|
||||
return True
|
||||
|
||||
# 热更新失败,进行完整更新
|
||||
print("热更新失败,进行完整更新...")
|
||||
return self.full_update(source_path, environment)
|
||||
|
||||
def main():
|
||||
"""命令行接口"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='TSP智能助手更新管理器')
|
||||
parser.add_argument('action', choices=['check', 'hot-update', 'full-update', 'auto-update', 'rollback', 'list-backups'],
|
||||
help='要执行的操作')
|
||||
parser.add_argument('--source', help='源路径')
|
||||
parser.add_argument('--environment', choices=['development', 'staging', 'production'],
|
||||
default='production', help='目标环境')
|
||||
parser.add_argument('--backup', help='备份名称(用于回滚)')
|
||||
parser.add_argument('--no-backup', action='store_true', help='跳过备份')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
um = UpdateManager()
|
||||
|
||||
if args.action == 'check':
|
||||
if not args.source:
|
||||
print("错误: 需要指定源路径")
|
||||
sys.exit(1)
|
||||
|
||||
has_update, current, new = um.check_update_available(args.source)
|
||||
if has_update:
|
||||
print(f"有更新可用: {current} -> {new}")
|
||||
else:
|
||||
print(f"没有更新可用 (当前版本: {current})")
|
||||
|
||||
elif args.action == 'hot-update':
|
||||
if not args.source:
|
||||
print("错误: 需要指定源路径")
|
||||
sys.exit(1)
|
||||
|
||||
success = um.hot_update(args.source, args.environment)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.action == 'full-update':
|
||||
if not args.source:
|
||||
print("错误: 需要指定源路径")
|
||||
sys.exit(1)
|
||||
|
||||
success = um.full_update(args.source, args.environment, not args.no_backup)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.action == 'auto-update':
|
||||
if not args.source:
|
||||
print("错误: 需要指定源路径")
|
||||
sys.exit(1)
|
||||
|
||||
success = um.auto_update(args.source, args.environment)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.action == 'rollback':
|
||||
if not args.backup:
|
||||
print("错误: 需要指定备份名称")
|
||||
sys.exit(1)
|
||||
|
||||
success = um.rollback(args.backup, args.environment)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.action == 'list-backups':
|
||||
backups = um.list_backups()
|
||||
if backups:
|
||||
print("可用备份:")
|
||||
for backup in backups:
|
||||
print(f" {backup['backup_name']} - {backup['timestamp']} - {backup.get('version', 'unknown')}")
|
||||
else:
|
||||
print("没有找到备份")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,273 +0,0 @@
|
||||
#!/bin/bash
|
||||
# TSP智能助手升级脚本
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# 配置变量
|
||||
APP_NAME="tsp_assistant"
|
||||
BACKUP_DIR="./backups"
|
||||
DEPLOY_PATH="/opt/tsp_assistant"
|
||||
SERVICE_NAME="tsp_assistant"
|
||||
HEALTH_URL="http://localhost:5000/api/health"
|
||||
|
||||
# 检查参数
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "用法: $0 <新版本路径> [选项]"
|
||||
echo "选项:"
|
||||
echo " --force 强制升级,跳过确认"
|
||||
echo " --no-backup 跳过备份"
|
||||
echo " --rollback 回滚到指定备份"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_VERSION_PATH=$1
|
||||
FORCE_UPGRADE=false
|
||||
SKIP_BACKUP=false
|
||||
ROLLBACK_MODE=false
|
||||
|
||||
# 解析参数
|
||||
while [[ $# -gt 1 ]]; do
|
||||
case $2 in
|
||||
--force)
|
||||
FORCE_UPGRADE=true
|
||||
;;
|
||||
--no-backup)
|
||||
SKIP_BACKUP=true
|
||||
;;
|
||||
--rollback)
|
||||
ROLLBACK_MODE=true
|
||||
;;
|
||||
*)
|
||||
log_error "未知选项: $2"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# 回滚功能
|
||||
rollback() {
|
||||
local backup_name=$1
|
||||
|
||||
if [ -z "$backup_name" ]; then
|
||||
log_error "请指定备份名称"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_step "开始回滚到备份: $backup_name"
|
||||
|
||||
# 检查备份是否存在
|
||||
if [ ! -d "$BACKUP_DIR/$backup_name" ]; then
|
||||
log_error "备份不存在: $backup_name"
|
||||
log_info "可用备份列表:"
|
||||
ls -la "$BACKUP_DIR" | grep backup
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 停止服务
|
||||
log_info "停止服务..."
|
||||
sudo systemctl stop "$SERVICE_NAME" || true
|
||||
|
||||
# 恢复文件
|
||||
log_info "恢复文件..."
|
||||
sudo rm -rf "$DEPLOY_PATH"
|
||||
sudo cp -r "$BACKUP_DIR/$backup_name" "$DEPLOY_PATH"
|
||||
sudo chown -R www-data:www-data "$DEPLOY_PATH"
|
||||
|
||||
# 恢复数据库
|
||||
if [ -f "$BACKUP_DIR/$backup_name/database/tsp_assistant.db" ]; then
|
||||
log_info "恢复数据库..."
|
||||
sudo cp "$BACKUP_DIR/$backup_name/database/tsp_assistant.db" "$DEPLOY_PATH/"
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
log_info "启动服务..."
|
||||
sudo systemctl start "$SERVICE_NAME"
|
||||
|
||||
# 等待服务启动
|
||||
sleep 10
|
||||
|
||||
# 健康检查
|
||||
if curl -f "$HEALTH_URL" > /dev/null 2>&1; then
|
||||
log_info "回滚成功!"
|
||||
else
|
||||
log_error "回滚后健康检查失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 创建备份
|
||||
create_backup() {
|
||||
local timestamp=$(date +"%Y%m%d_%H%M%S")
|
||||
local backup_name="${APP_NAME}_backup_${timestamp}"
|
||||
local backup_path="$BACKUP_DIR/$backup_name"
|
||||
|
||||
log_step "创建备份: $backup_name"
|
||||
|
||||
# 创建备份目录
|
||||
mkdir -p "$backup_path"
|
||||
|
||||
# 备份应用文件
|
||||
if [ -d "$DEPLOY_PATH" ]; then
|
||||
log_info "备份应用文件..."
|
||||
cp -r "$DEPLOY_PATH"/* "$backup_path/"
|
||||
fi
|
||||
|
||||
# 备份数据库
|
||||
if [ -f "$DEPLOY_PATH/tsp_assistant.db" ]; then
|
||||
log_info "备份数据库..."
|
||||
mkdir -p "$backup_path/database"
|
||||
cp "$DEPLOY_PATH/tsp_assistant.db" "$backup_path/database/"
|
||||
fi
|
||||
|
||||
# 保存备份信息
|
||||
cat > "$backup_path/backup_info.json" << EOF
|
||||
{
|
||||
"backup_name": "$backup_name",
|
||||
"backup_path": "$backup_path",
|
||||
"timestamp": "$timestamp",
|
||||
"version": "$(cd "$DEPLOY_PATH" && python version.py version 2>/dev/null || echo "unknown")",
|
||||
"git_commit": "$(cd "$DEPLOY_PATH" && git rev-parse HEAD 2>/dev/null | cut -c1-8 || echo "unknown")"
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "备份完成: $backup_name"
|
||||
echo "$backup_name"
|
||||
}
|
||||
|
||||
# 升级功能
|
||||
upgrade() {
|
||||
local new_version_path=$1
|
||||
|
||||
log_step "开始升级TSP智能助手"
|
||||
|
||||
# 检查新版本路径
|
||||
if [ ! -d "$new_version_path" ]; then
|
||||
log_error "新版本路径不存在: $new_version_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查当前版本
|
||||
if [ -d "$DEPLOY_PATH" ]; then
|
||||
local current_version=$(cd "$DEPLOY_PATH" && python version.py version 2>/dev/null || echo "unknown")
|
||||
log_info "当前版本: $current_version"
|
||||
else
|
||||
log_warn "当前部署路径不存在: $DEPLOY_PATH"
|
||||
fi
|
||||
|
||||
# 检查新版本
|
||||
local new_version=$(cd "$new_version_path" && python version.py version 2>/dev/null || echo "unknown")
|
||||
log_info "新版本: $new_version"
|
||||
|
||||
# 确认升级
|
||||
if [ "$FORCE_UPGRADE" = false ]; then
|
||||
echo -n "确认升级到版本 $new_version? (y/N): "
|
||||
read -r response
|
||||
if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
||||
log_info "升级取消"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建备份
|
||||
local backup_name=""
|
||||
if [ "$SKIP_BACKUP" = false ]; then
|
||||
backup_name=$(create_backup)
|
||||
fi
|
||||
|
||||
# 停止服务
|
||||
log_step "停止服务..."
|
||||
sudo systemctl stop "$SERVICE_NAME" || true
|
||||
|
||||
# 升级文件
|
||||
log_step "升级应用文件..."
|
||||
sudo rm -rf "$DEPLOY_PATH"
|
||||
sudo mkdir -p "$DEPLOY_PATH"
|
||||
sudo cp -r "$new_version_path"/* "$DEPLOY_PATH/"
|
||||
sudo chown -R www-data:www-data "$DEPLOY_PATH"
|
||||
|
||||
# 安装依赖
|
||||
log_step "安装依赖..."
|
||||
cd "$DEPLOY_PATH"
|
||||
sudo -u www-data python -m pip install -r requirements.txt
|
||||
|
||||
# 运行数据库迁移
|
||||
log_step "运行数据库迁移..."
|
||||
sudo -u www-data python init_database.py || true
|
||||
|
||||
# 启动服务
|
||||
log_step "启动服务..."
|
||||
sudo systemctl start "$SERVICE_NAME"
|
||||
|
||||
# 等待服务启动
|
||||
log_info "等待服务启动..."
|
||||
sleep 15
|
||||
|
||||
# 健康检查
|
||||
log_step "执行健康检查..."
|
||||
local retry_count=0
|
||||
local max_retries=10
|
||||
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
if curl -f "$HEALTH_URL" > /dev/null 2>&1; then
|
||||
log_info "健康检查通过!"
|
||||
break
|
||||
else
|
||||
log_warn "健康检查失败,重试中... ($((retry_count + 1))/$max_retries)"
|
||||
retry_count=$((retry_count + 1))
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
log_error "健康检查失败,开始回滚..."
|
||||
if [ -n "$backup_name" ]; then
|
||||
rollback "$backup_name"
|
||||
else
|
||||
log_error "没有备份可回滚"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "升级成功!"
|
||||
log_info "新版本: $new_version"
|
||||
if [ -n "$backup_name" ]; then
|
||||
log_info "备份名称: $backup_name"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
if [ "$ROLLBACK_MODE" = true ]; then
|
||||
rollback "$NEW_VERSION_PATH"
|
||||
else
|
||||
upgrade "$NEW_VERSION_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main
|
||||
Reference in New Issue
Block a user