召回ubuntu部署脚本

This commit is contained in:
2026-05-09 21:57:04 +08:00
parent 6de676b380
commit 1352d72996
11 changed files with 1146 additions and 13 deletions
+100 -12
View File
@@ -20,7 +20,7 @@
- 按照界面提示进行操作
#### 2.1.2 主要功能
- **1. 安装服务**:自动检查环境、安装依赖、配置服务
- **1. 安装服务**:自动检查环境、安装依赖、配置服务(在线模式)
- **2. 启动服务**:启动已安装的服务
- **3. 停止服务**:停止正在运行的服务
- **4. 重启服务**:重启服务
@@ -31,6 +31,80 @@
- **9. 查看帮助**:显示使用说明
- **A. 健康检查**:执行 HTTP 健康检查,验证服务响应
- **B. 滚动更新**:优雅重启,最小化更新过程中的停机时间
- **C. 离线安装**:使用本地离线包进行安装,无需网络连接
### 2.3 离线部署方式(无外网环境)
当部署环境没有外网连接时,可以使用离线部署方式。项目已预先准备了完整的离线依赖包。
#### 2.3.1 离线包结构
```
offline_packages/
└── windows/
├── python-3.12.2.exe # Python 安装包(可选)
├── Redis-x64-5.0.14.1.msi # Redis 安装包(可选)
├── install_dependencies.bat # 离线依赖安装脚本
├── *.whl # 所有 Python 依赖包
└── requirements.txt # 依赖清单
```
#### 2.3.2 部署步骤
**方法一:使用傻瓜式部署工具(推荐)**
1. **准备安装包**
- 将整个项目目录复制到目标服务器
- 确保 `offline_packages/windows/` 目录完整
2. **运行部署工具**
- 右键点击 `simple_deploy.bat`
- 选择 "以管理员身份运行"
- 选择选项 **C. Install service (offline mode)**
3. **等待安装完成**
- 脚本会自动创建虚拟环境
- 使用本地离线包安装所有依赖
- 配置 Windows 服务
**方法二:手动离线安装**
1. **安装 Python(如果未安装)**
```bash
# 使用离线安装包安装 Python
offline_packages\windows\python-3.12.2.exe /quiet InstallAllUsers=1 PrependPath=1
```
2. **运行离线安装脚本**
```bash
# 进入离线包目录
cd d:\code\myaps_fastapi\offline_packages\windows
# 运行离线安装脚本
install_dependencies.bat
```
3. **配置服务**
```bash
# 使用部署工具配置服务
scripts\deploy\simple_deploy.bat
# 选择选项 1 安装服务(此时依赖已安装)
```
#### 2.3.3 离线包说明
| 文件类型 | 说明 |
|---------|------|
| `.whl` 文件 | Python Wheel 格式的预编译依赖包 |
| `python-*.exe` | Python 官方安装程序 |
| `install_dependencies.bat` | 一键离线安装脚本 |
| `requirements.txt` | 依赖版本清单 |
#### 2.3.4 注意事项
- **Python 版本要求**:离线包基于 Python 3.12 编译,建议使用相同版本
- **包完整性**:确保 `offline_packages/windows/` 目录包含所有 `.whl` 文件
- **权限要求**:所有操作需要管理员权限
- **更新离线包**:如需更新依赖,需要在有网环境下使用 `pip download` 下载新包
### 2.2 传统部署方式
@@ -165,7 +239,20 @@ Get-Content d:\code\myaps_fastapi\logs\service_daemon.log -Tail 20
4. 运行 `start_all.bat` 脚本
5. 访问 `http://localhost` 验证部署
### 4.3 升级部署
### 4.3 离线部署流程(无外网环境)
1. **准备安装包**:将整个项目目录复制到目标服务器
2. **运行部署工具**
- 右键点击 `scripts/deploy/simple_deploy.bat`
- 选择 "以管理员身份运行"
- 选择选项 **C. Install service (offline mode)**
3. **等待安装完成**
- 脚本自动创建虚拟环境
- 使用本地离线包安装所有依赖
- 配置 Windows 服务
4. **启动服务**:选择选项 2 "启动服务"
5. **验证部署**:选择选项 A "健康检查" 或访问 `http://localhost:8000`
### 4.4 升级部署
1. 运行 `simple_deploy.bat`
2. 选择选项 3 "停止服务"(或选项 B "滚动更新"
3. 更新代码
@@ -217,16 +304,17 @@ Get-Content d:\code\myaps_fastapi\logs\service_daemon.log -Tail 20
## 8. 部署方式对比
| 特性 | 傻瓜式部署 | 传统部署 |
|------|-----------|----------|
| 操作难度 | 低(图形界面) | 中(命令行) |
| 自动化程度 | 高(自动检查和安装) | 中(需要手动操作) |
| 多进程支持 | ✅ 内置支持 | ✅ 需要手动配置 |
| 服务管理 | ✅ 完整的服务生命周期管理 | ⚠️ 基本的启动停止 |
| 环境检查 | ✅ 自动检查 | ❌ 需要手动检查 |
| 健康检查 | ✅ 内置 HTTP 检查 | ❌ |
| 滚动更新 | ✅ 优雅重启支持 | ❌ 无 |
| 适合人群 | 无IT基础用户 | 有一定技术基础用户 |
| 特性 | 傻瓜式部署(在线) | 傻瓜式部署(离线) | 传统部署 |
|------|-------------------|-------------------|----------|
| 操作难度 | 低(图形界面) | 低(图形界面) | 中(命令行) |
| 自动化程度 | 高(自动检查和安装) | 高(自动检查和安装) | 中(需要手动操作) |
| 网络需求 | ✅ 需要外网 | ❌ 无需网络 | ❌ 无需网络 |
| 多进程支持 | ✅ 内置支持 | ✅ 内置支持 | ✅ 需要手动配置 |
| 服务管理 | ✅ 完整的服务生命周期管理 | ✅ 完整的服务生命周期管理 | ⚠️ 基本的启动停止 |
| 环境检查 | ✅ 自动检查 | ✅ 自动检查 | ❌ 需要手动检查 |
| 健康检查 | ✅ 内置 HTTP 检查 | ✅ 内置 HTTP 检查 | ❌ 无 |
| 滚动更新 | ✅ 优雅重启支持 | ✅ 优雅重启支持 | ❌ 无 |
| 适合人群 | 无IT基础用户 | 无IT基础用户(离线环境) | 有一定技术基础用户 |
## 9. 系统要求
+106 -1
View File
@@ -75,6 +75,7 @@ echo 8. Clean log files
echo 9. View help
echo A. Health check (HTTP endpoint)
echo B. Rolling update (graceful restart)
echo C. Install service (offline mode)
echo 0. Exit
echo.
echo ============================================================
@@ -102,6 +103,7 @@ if "%choice%"=="8" goto :CLEAN_LOGS
if "%choice%"=="9" goto :HELP
if /i "%choice%"=="A" goto :HEALTH_CHECK
if /i "%choice%"=="B" goto :ROLLING_UPDATE
if /i "%choice%"=="C" goto :INSTALL_OFFLINE
if "%choice%"=="0" goto :EXIT
echo Invalid choice: [%choice%]
@@ -297,7 +299,7 @@ echo Help information
echo ============================================================
echo.
echo 1. Install service: Install service as Windows system service
echo 1. Install service: Install service as Windows system service (online)
echo 2. Start service: Start the installed service
echo 3. Stop service: Stop the running service
echo 4. Restart service: Restart the service
@@ -308,6 +310,7 @@ echo 8. Clean logs: Clean old log files
echo 9. View help: Display this help
echo A. Health check: Perform HTTP health check
echo B. Rolling update: Graceful restart for updates
echo C. Install offline: Install service using local packages (no internet)
echo 0. Exit: Exit the tool
echo.
pause
@@ -392,6 +395,108 @@ echo.
pause
goto :MENU
:INSTALL_OFFLINE
cls
echo ============================================================
echo Install service (offline mode)
echo ============================================================
echo.
echo Note: This mode uses local packages for offline deployment.
echo Ensure offline packages exist in: offline_packages\windows
echo.
%PYTHON_EXE% --version >nul 2>&1
if %errorLevel% neq 0 (
echo Error: Python not found!
pause
goto :MENU
)
set "OFFLINE_PACKAGES_DIR=%PROJECT_ROOT%\offline_packages\windows"
if not exist "%OFFLINE_PACKAGES_DIR%" (
echo Error: Offline packages directory not found!
echo Expected: %OFFLINE_PACKAGES_DIR%
pause
goto :MENU
)
if not exist "%PROJECT_ROOT%\requirements.txt" (
echo Error: requirements.txt not found!
pause
goto :MENU
)
echo [1/4] Creating virtual environment...
if exist "%VENV_DIR%" (
echo Removing existing virtual environment...
rmdir /s /q "%VENV_DIR%"
)
%PYTHON_EXE% -m venv "%VENV_DIR%"
if %errorLevel% neq 0 (
echo Error: Failed to create virtual environment!
pause
goto :MENU
)
echo Virtual environment created successfully
echo.
echo [2/4] Installing dependencies from local packages...
echo This may take a few minutes...
"%VENV_DIR%\Scripts\pip.exe" install --no-index --find-links="%OFFLINE_PACKAGES_DIR%" -r "%PROJECT_ROOT%\requirements.txt"
if %errorLevel% neq 0 (
echo Error: Failed to install dependencies!
echo Please check if all required packages exist in offline_packages\windows
pause
goto :MENU
)
echo Dependencies installed successfully
echo.
echo [3/4] Installing Gunicorn and Uvicorn...
"%VENV_DIR%\Scripts\pip.exe" install --no-index --find-links="%OFFLINE_PACKAGES_DIR%" gunicorn uvicorn[standard]
if %errorLevel% neq 0 (
echo Warning: Failed to install Gunicorn/Uvicorn from offline packages!
echo Trying online installation...
"%VENV_DIR%\Scripts\pip.exe" install gunicorn uvicorn[standard]
if %errorLevel% neq 0 (
echo Error: Failed to install Gunicorn/Uvicorn!
pause
goto :MENU
)
)
echo Gunicorn and Uvicorn installed successfully
if not exist "%PROJECT_ROOT%\logs" (
mkdir "%PROJECT_ROOT%\logs"
)
echo.
echo [4/4] Configuring Windows service...
set "RUN_PS1=%SCRIPT_DIR%..\run.ps1"
"%NSSM_EXE%" install "%SERVICE_NAME%" powershell.exe
"%NSSM_EXE%" set "%SERVICE_NAME%" AppParameters "-ExecutionPolicy Bypass -NoProfile -File %RUN_PS1% -Mode service"
"%NSSM_EXE%" set "%SERVICE_NAME%" AppDirectory "%PROJECT_ROOT%"
"%NSSM_EXE%" set "%SERVICE_NAME%" Start SERVICE_AUTO_START
"%NSSM_EXE%" set "%SERVICE_NAME%" AppStdout "%PROJECT_ROOT%\logs\nssm_stdout.log"
"%NSSM_EXE%" set "%SERVICE_NAME%" AppStderr "%PROJECT_ROOT%\logs\nssm_stderr.log"
"%NSSM_EXE%" set "%SERVICE_NAME%" AppRestartDelay 60000
"%NSSM_EXE%" set "%SERVICE_NAME%" AppThrottle 300000
"%NSSM_EXE%" set "%SERVICE_NAME%" AppExit Default Restart
"%NSSM_EXE%" set "%SERVICE_NAME%" AppExit 1 Restart
"%NSSM_EXE%" set "%SERVICE_NAME%" AppExit 0 Restart
echo.
echo Service installed successfully in offline mode!
echo Service name: %SERVICE_NAME%
echo.
echo Note: If you encounter dependency issues, please ensure:
echo 1. All required .whl packages are in offline_packages\windows
echo 2. Python version matches (Python 3.12 recommended)
echo.
pause
goto :MENU
:EXIT
cls
echo ============================================================
+396
View File
@@ -0,0 +1,396 @@
# MyAPS Ubuntu 部署指南(完全离线版)
## 📁 目录结构
```
scripts/deploy_ubuntu/
├── deploy.sh # 主部署脚本(完全离线)
├── start.sh # 启动服务
├── stop.sh # 停止服务
├── restart.sh # 重启服务
├── status.sh # 查看状态
├── prepare_offline.sh # 打包离线依赖(开发机用)
└── nginx.conf # Nginx 配置模板
```
## 🚀 部署步骤
### 前提条件
目标服务器为 **完全无外网环境**,所有依赖已提前下载。
### 1. 在有外网的开发机准备离线包
```bash
# 在开发机执行(需要外网)
cd /path/to/myaps_fastapi
# 方式一:使用已下载的 Ubuntu 离线包
# 确认 offline_packages/ubuntu/ 目录下有所有依赖
# 方式二:重新下载(需外网)
./scripts/deploy_ubuntu/prepare_offline.sh
```
### 2. 打包整个项目
推荐使用 PowerShell 脚本打包,确保排除目录生效:
```powershell
# 在项目根目录运行
cd D:\code\myaps_fastapi
.\scripts\deploy_ubuntu\package_for_deploy.ps1
```
**打包结果**
- 输出文件:`D:\code\myaps_fastapi_YYYYMMDD.tar.gz`YYYYMMDD 为日期)
- 自动排除以下目录:
| 目录 | 说明 |
|------|------|
| `venv` | Python 虚拟环境(服务器上重新创建) |
| `offline_packages` | 离线依赖包(太大,需单独拷贝) |
| `logs` | 日志目录(无需迁移) |
| `test` | 测试代码(无需部署) |
| `storage` | 本地存储(无需迁移) |
| `backups` | 备份目录(无需迁移) |
| `.git` | Git 仓库 |
| `__pycache__` / `*.pyc` | Python 编译文件 |
### 手动打包命令(Git Bash
如果需要手动打包,可使用以下命令:
```bash
# 进入项目上级目录
cd /d/code/
# 创建临时目录
mkdir myaps_fastapi_temp
# 复制文件(排除目录)
rsync -av --exclude='venv' --exclude='offline_packages' --exclude='logs' \
--exclude='test' --exclude='storage' --exclude='backups' --exclude='.git' \
--exclude='__pycache__' --exclude='*.pyc' myaps_fastapi/ myaps_fastapi_temp/
# 打包
tar -czvf myaps_fastapi.tar.gz myaps_fastapi_temp/
# 清理临时目录
rm -rf myaps_fastapi_temp
```
### 单独打包离线依赖包
```bash
# 打包项目代码(不含离线包)
.\scripts\deploy_ubuntu\package_for_deploy.ps1
# 单独打包离线依赖包
cd D:\code\
tar -czvf offline_packages.tar.gz myaps_fastapi/offline_packages/
```
### 3. 传输到离线服务器
```bash
# 方法一:使用 U 盘/移动硬盘拷贝
# 将 myaps_fastapi.tar.gz 拷贝到服务器
# 方法二:内网 SCP(如果有内网网络)
scp myaps_fastapi.tar.gz root@your-server:/opt/
```
### 4. 在离线服务器解压
```bash
# 在目标服务器执行
cd /opt
tar -xzvf myaps_fastapi.tar.gz
mv myaps_fastapi_temp myaps_api
cd /opt/myaps_api
```
### 5. 配置 .env
编辑 `/opt/myaps_api/.env`,确保以下参数正确:
```ini
# 项目配置
PROJECT_DIR=JYHDXS # 租户目录(CHANGDE/JYHDXS/HACYXS
PROJECT_JSON=dev # dev/prod
# 部署配置
WORKERS=4 # 工作进程数(建议为 CPU 核数)
GUNICORN_BIND=127.0.0.1:8000
GUNICORN_TIMEOUT=30
APP_USER=www-data
APP_ROOT=/opt/myaps_api/myaps_api # 注意:项目实际目录结构为 /opt/myaps_api/myaps_api
# 数据库配置(根据实际环境修改)
MYAPS_DB_HOST=127.0.0.1
MYAPS_DB_PORT=3306
MYAPS_DB_USER=username
MYAPS_DB_PASSWORD=password
MYAPS_DB_SET=database_name
MYAPS_MAIN_DB=database_name
# Redis 配置
REDIS_PORT=6379
REDIS_PASSWORD=
```
### 6. 安装系统依赖(离线方式)
#### 6.1 安装基础依赖
```bash
# 如果服务器从未安装过以下依赖,需要提前准备 deb 包
# 安装 Python 3.12(必须)
# 方法:在有外网的 Ubuntu 机器上下载 deb 包
# apt download python3.12 python3.12-venv python3.12-dev
# 然后拷贝到离线服务器安装
# 安装编译依赖(某些 Python 包需要)
# apt download build-essential libssl-dev libffi-dev
# 安装 MySQL 客户端库
# apt download libmysqlclient-dev
# 安装完成后执行
apt install ./python3.12*.deb ./build-essential*.deb ./libssl-dev*.deb ./libffi-dev*.deb
```
#### 6.2 安装 Redis 数据库(离线方式)
```bash
# 方法一:使用 deb 包完整安装(推荐,最可靠)
# 1. 在有外网的 Ubuntu 机器上下载 Redis 及其所有依赖
mkdir -p /tmp/redis_packages
cd /tmp/redis_packages
# 自动获取并下载所有依赖包(最完整的方式)
apt download $(apt-cache depends redis-server redis-tools | grep -E "Depends|Recommends" | cut -d: -f2 | tr -d '<> ')
# 或者手动下载已知的关键依赖(备选方案)
# apt download redis-server redis-tools libjemalloc2 libhiredis0.14 liblzf1
# 查看下载的文件
ls -la *.deb
# 2. 将所有 deb 包拷贝到离线服务器
# 使用 U 盘/移动硬盘或 scp 命令
# 拷贝到离线服务器的 /opt/redis_packages/ 目录
# 3. 在离线服务器上安装
cd /opt/redis_packages
# 方法 A:使用 dpkg 安装所有包
apt --fix-broken install -y
# 方法 B:如果有部分依赖问题,使用 apt 本地安装(推荐)
# apt install ./*.deb
# 方法二:源码编译安装(适用于无 deb 包的情况)
# 1. 下载 Redis 源码(在有外网的机器上)
# wget https://download.redis.io/releases/redis-7.4.0.tar.gz
# 2. 拷贝到离线服务器并解压
# tar -xzvf redis-7.4.0.tar.gz
# cd redis-7.4.0
# 3. 编译安装(需要先安装 build-essential
# apt install ./build-essential*.deb
# make
# make install
# 4. 创建配置文件和服务
# mkdir -p /etc/redis /var/lib/redis
# cp redis.conf /etc/redis/
# useradd -r -s /bin/false redis
# chown redis:redis /var/lib/redis
# 5. 创建 systemd 服务文件
# cat > /etc/systemd/system/redis.service << 'EOF'
# [Unit]
# Description=Redis In-Memory Data Store
# After=network.target
# [Service]
# User=redis
# Group=redis
# ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
# ExecStop=/usr/local/bin/redis-cli shutdown
# Restart=always
# [Install]
# WantedBy=multi-user.target
# EOF
# 启动 Redis 服务(两种方法都需要)
systemctl daemon-reload
systemctl start redis-server
systemctl enable redis-server
# 验证安装
redis-cli ping
# 输出: PONG
```
#### 常见问题与解决
**问题:安装时提示缺少依赖包**
解决方法:确保使用了上面的方法一,使用 `apt-cache depends` 自动获取所有依赖。
**问题:dpkg 安装后仍有未配置的包**
解决方法:
```bash
# 强制修复配置
apt-get -f install
# 或者再次执行 dpkg 配置
dpkg --configure -a
```
**问题:如何检查已下载的依赖是否完整**
```bash
# 在有外网的机器上模拟安装,检查是否还有缺失
mkdir -p /tmp/test_install
cd /tmp/test_install
cp /tmp/redis_packages/*.deb .
# 使用 apt 模拟安装(不会真正安装)
apt install --dry-run ./*.deb
```
### 7. 执行离线部署
```bash
cd /opt/myaps_api
chmod +x ./scripts/deploy_ubuntu/*.sh
# 首次部署(包含数据库迁移)
./scripts/deploy_ubuntu/deploy.sh -d
# 增量更新(跳过数据库迁移,避免破坏数据)
./scripts/deploy_ubuntu/deploy.sh -d -s
```
**部署选项说明:**
| 选项 | 说明 |
|------|------|
| `-d` | 执行部署 |
| `-s` | 跳过数据库迁移(用于增量更新) |
| `-t <租户>` | 指定租户(如 CHANGDE、JYHDXS、HACYXS |
**示例:**
```bash
# 首次部署,指定租户
./scripts/deploy_ubuntu/deploy.sh -d -t CHANGDE
# 增量更新,跳过迁移
./scripts/deploy_ubuntu/deploy.sh -d -s
# 指定租户且跳过迁移
./scripts/deploy_ubuntu/deploy.sh -d -t CHANGDE -s
```
### 8. 配置 Nginx(可选)
```bash
# 安装 Nginx(提前准备 deb 包)
# apt download nginx
# dpkg -i nginx*.deb
# 复制配置
cp ./scripts/deploy_ubuntu/nginx.conf /etc/nginx/nginx.conf
# 测试配置
nginx -t
# 重启 Nginx
systemctl restart nginx
systemctl enable nginx
```
## 📝 常用命令
```bash
# 启动服务
./scripts/deploy_ubuntu/start.sh
# 停止服务
./scripts/deploy_ubuntu/stop.sh
# 重启服务
./scripts/deploy_ubuntu/restart.sh
# 查看状态
./scripts/deploy_ubuntu/status.sh
# 查看日志
journalctl -u MyAPS_API -f
# 切换租户(修改 .env 后重启)
sed -i 's/PROJECT_DIR=JYHDXS/PROJECT_DIR=CHANGDE/' .env
./scripts/deploy_ubuntu/restart.sh
```
## 📦 离线依赖包说明
### 依赖包位置
```
/opt/myaps_api/offline_packages/ubuntu/python_pkg/
├── fastapi-0.136.1-py3-none-any.whl
├── uvicorn-0.46.0-py3-none-any.whl
├── gunicorn-26.0.0-py3-none-any.whl
├── pandas-3.0.2-cp312-cp312-manylinux_*.whl
├── numpy-2.4.4-cp312-cp312-manylinux_*.whl
└── ... (共 56 个依赖包)
```
### 离线包兼容性
| 类型 | 特征 | 说明 |
|------|------|------|
| `py3-none-any.whl` | 纯 Python 代码 | 跨平台通用 |
| `manylinux_*.whl` | 编译后的二进制 | Ubuntu x86_64 专用 |
## ⚠️ 注意事项
1. **完全离线**:确保服务器无任何外网访问,所有依赖已提前准备
2. **Python 版本**:必须使用 Python 3.12(依赖包为 cp312 版本)
3. **权限问题**:建议使用 `www-data` 用户运行,而非 `root`
4. **数据库连接**:确保 `.env` 中数据库配置正确且可达
5. **防火墙**:确保端口 80/443/8000 已开放
6. **系统依赖**:提前准备 `python3.12`, `build-essential`, `libssl-dev` 等 deb 包
7. **APP_ROOT 配置**:确保 `APP_ROOT=/opt/myaps_api/myaps_api`(项目实际在二级目录)
8. **只读文件系统**:如果 `/opt/myaps_api` 是只读挂载点,脚本会自动使用用户级别 Systemd 服务
## 🔧 故障排查
| 问题 | 解决方法 |
|------|---------|
| 服务启动失败 | `systemctl status MyAPS_API` |
| 日志查看 | `journalctl -u MyAPS_API -f` |
| 端口占用 | `lsof -i :8000` |
| Nginx 配置错误 | `nginx -t` |
| Python 版本不匹配 | 确认安装 Python 3.12 |
| 依赖安装失败 | 检查 offline_packages/ubuntu/ 目录是否完整 |
| 只读文件系统错误 | 检查 `/opt/myaps_api` 挂载状态,使用 `mount` 命令确认 |
| Systemd 服务创建失败 | 脚本会自动回退到用户级别服务,使用 `systemctl --user` 命令 |
## 📋 部署检查清单
- [ ] 项目代码已拷贝到 `/opt/myaps_api/`
- [ ] `.env` 配置文件已正确设置(`APP_ROOT=/opt/myaps_api/myaps_api`
- [ ] 离线依赖包存在于 `offline_packages/ubuntu/python_pkg/`
- [ ] Python 3.12 已安装
- [ ] 数据库服务已启动且可连接
- [ ] Redis 服务已启动且可连接
- [ ] 执行 `deploy.sh -d` 部署成功(首次部署)
- [ ] 执行 `deploy.sh -d -s` 部署成功(增量更新)
- [ ] 服务状态正常(`systemctl --user status MyAPS_API``systemctl status MyAPS_API`
+274
View File
@@ -0,0 +1,274 @@
#!/bin/bash
# ==============================================================================
# MyAPS FastAPI Ubuntu 部署脚本
# 自动从 .env 读取配置参数
# ==============================================================================
set -e
# 读取 .env 配置
read_env() {
# 获取脚本所在目录,用于定位项目根目录
local SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
local PROJECT_DIR_CANDIDATE=$(dirname "$SCRIPT_DIR") # scripts 目录的父目录
# 优先级:1. 当前目录 2. 脚本所在项目目录 3. APP_ROOT 环境变量
local env_path=""
# 1. 检查当前目录
if [ -f ".env" ]; then
env_path=".env"
# 2. 检查脚本所在项目目录
elif [ -f "$PROJECT_DIR_CANDIDATE/.env" ]; then
env_path="$PROJECT_DIR_CANDIDATE/.env"
# 3. 使用 APP_ROOT 环境变量
elif [ -n "$APP_ROOT" ] && [ -f "$APP_ROOT/.env" ]; then
env_path="$APP_ROOT/.env"
# 4. 默认路径
elif [ -f "/opt/myaps_api/myaps_api/.env" ]; then
env_path="/opt/myaps_api/myaps_api/.env"
elif [ -f "/opt/myaps_api/.env" ]; then
env_path="/opt/myaps_api/.env"
fi
if [ -n "$env_path" ]; then
echo " 读取环境变量: $env_path"
# 使用 source 命令读取环境变量(更可靠)
# 创建临时文件,确保文件以换行符结尾
local temp_file=$(mktemp)
cat "$env_path" > "$temp_file"
echo "" >> "$temp_file" # 添加换行符
# 使用 source 读取,但只导入大写字母开头的环境变量
while IFS= read -r line; do
if [[ ! "$line" =~ ^# && "$line" =~ ^[A-Z_]+= ]]; then
export "$line"
fi
done < "$temp_file"
rm -f "$temp_file"
# 验证读取结果
echo " 读取到 PROJECT_DIR: ${PROJECT_DIR:-未设置}"
echo " 读取到 APP_ROOT: ${APP_ROOT:-未设置}"
else
echo "❌ 错误: .env 文件不存在"
exit 1
fi
}
# 主部署函数
deploy() {
echo "========================================"
echo " MyAPS Ubuntu 部署脚本"
echo "========================================"
# 保存命令行传入的租户参数(如果有)
local cmdline_project_dir="$PROJECT_DIR"
# 1. 读取环境变量
echo "[1/6] 读取环境配置..."
read_env
# 如果命令行指定了租户,覆盖 .env 中的配置
if [ -n "$cmdline_project_dir" ]; then
echo " 使用命令行指定的租户: $cmdline_project_dir"
PROJECT_DIR="$cmdline_project_dir"
fi
# 2. 验证必要参数
echo "[2/6] 验证配置参数..."
if [ -z "$PROJECT_DIR" ]; then
echo "❌ 错误: PROJECT_DIR 未设置"
exit 1
fi
# 3. 创建目录结构
echo "[3/6] 创建目录结构..."
# 仅在目录不存在时创建(处理只读文件系统情况)
if [ ! -d "${APP_ROOT:-/opt/myaps_api}/logs" ]; then
mkdir -p "${APP_ROOT:-/opt/myaps_api}/logs" || echo " ⚠️ 无法创建 logs 目录(只读文件系统)"
else
echo " logs 目录已存在"
fi
# 设置运行服务的用户
if id "www-data" &>/dev/null; then
APP_USER="www-data"
echo " 使用 www-data 用户"
else
APP_USER="root"
echo " www-data 用户不存在,使用 root 用户"
fi
# 设置目录权限(跳过,只读文件系统)
# chown -R ${APP_USER}:${APP_USER} "${APP_ROOT:-/opt/myaps_api}"
echo " ⚠️ 跳过权限设置(只读文件系统)"
# 4. 安装依赖
echo "[4/6] 安装 Python 依赖..."
# 检查虚拟环境是否已存在
if [ -d "${APP_ROOT:-/opt/myaps_api}/venv" ]; then
echo " 虚拟环境已存在,跳过创建"
else
python3 -m venv "${APP_ROOT:-/opt/myaps_api}/venv"
fi
source "${APP_ROOT:-/opt/myaps_api}/venv/bin/activate"
if [ -d "offline_packages/ubuntu/python_pkg" ]; then
echo " 使用 Ubuntu 离线依赖包..."
# 先尝试完全离线安装
if pip install --no-index --find-links=offline_packages/ubuntu/python_pkg -r requirements.txt 2>/dev/null; then
echo " ✅ 完全离线安装成功"
else
echo " ⚠️ 部分包需要在线下载..."
pip install --find-links=offline_packages/ubuntu/python_pkg -r requirements.txt
fi
elif [ -d "offline_packages" ]; then
echo " 使用通用离线依赖包..."
pip install --find-links=offline_packages -r requirements.txt
else
echo " 使用在线安装..."
pip install -r requirements.txt
fi
# 数据库迁移
echo "[5/7] 执行数据库迁移..."
if [ "$SKIP_MIGRATE" = true ]; then
echo " ⏭️ 跳过数据库迁移(使用 -s 参数)"
elif [ -f "scripts/migrate/auto_migrate.py" ]; then
echo " 使用自动迁移脚本..."
python scripts/migrate/auto_migrate.py
elif command -v aerich &> /dev/null; then
echo " 使用 Aerich 迁移..."
aerich upgrade
else
echo " ⚠️ 未找到迁移脚本,请手动执行数据库迁移"
fi
deactivate
# 6. 创建 Systemd 服务
echo "[6/7] 创建 Systemd 服务..."
# 检测系统目录是否可写,否则使用用户级别服务
if touch /etc/systemd/system/test_$$.service 2>/dev/null; then
rm -f /etc/systemd/system/test_$$.service
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME:-MyAPS_API}.service"
SYSTEMCTL_CMD="systemctl"
echo " 使用系统级别 Systemd 服务"
else
# 使用用户级别 Systemd 服务
mkdir -p ~/.config/systemd/user/
SERVICE_FILE="$HOME/.config/systemd/user/${SERVICE_NAME:-MyAPS_API}.service"
SYSTEMCTL_CMD="systemctl --user"
echo " 使用用户级别 Systemd 服务(系统只读)"
fi
# 生成 Systemd 服务文件
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=MyAPS_API FastAPI Application
After=network.target mysql.service redis.service
[Service]
Type=notify
WorkingDirectory=${APP_ROOT:-/opt/myaps_api}
# 使用 bash -c 方式启动,先加载 .env 文件再启动 Gunicorn
ExecStart=/bin/bash -c "source ${APP_ROOT:-/opt/myaps_api}/.env && ${APP_ROOT:-/opt/myaps_api}/venv/bin/gunicorn --workers=4 --bind=127.0.0.1:8000 --timeout=30 --worker-class=uvicorn.workers.UvicornWorker --access-logfile=${APP_ROOT:-/opt/myaps_api}/logs/access.log --error-logfile=${APP_ROOT:-/opt/myaps_api}/logs/error.log main:app"
ExecReload=/bin/kill -s HUP \$MAINPID
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
EOF
# 7. 检查并启动 Redis 服务
echo "[7/8] 检查 Redis 服务..."
if systemctl is-active --quiet redis-server; then
echo " ✅ Redis 已运行"
else
echo " 启动 Redis 服务..."
systemctl daemon-reload
systemctl enable redis-server
systemctl start redis-server
# 等待 Redis 启动
sleep 2
if redis-cli ping > /dev/null 2>&1; then
echo " ✅ Redis 启动成功"
else
echo " ⚠️ Redis 启动失败,请手动检查"
fi
fi
# 8. 启动应用服务
echo "[8/8] 启动应用服务..."
$SYSTEMCTL_CMD daemon-reload
$SYSTEMCTL_CMD enable "${SERVICE_NAME:-MyAPS_API}"
$SYSTEMCTL_CMD start "${SERVICE_NAME:-MyAPS_API}"
echo ""
echo "✅ 部署完成!"
echo " 租户: $PROJECT_DIR"
echo " 服务名称: ${SERVICE_NAME:-MyAPS_API}"
echo " 绑定地址: ${GUNICORN_BIND:-127.0.0.1:8000}"
echo ""
echo "查看状态: systemctl status ${SERVICE_NAME:-MyAPS_API}"
echo "查看日志: journalctl -u ${SERVICE_NAME:-MyAPS_API} -f"
echo ""
echo "注意: 如果系统目录只读,将使用用户级别 Systemd 服务"
echo " 使用 'systemctl --user status ${SERVICE_NAME:-MyAPS_API}' 查看状态"
}
# 显示帮助
usage() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -h, --help 显示帮助信息"
echo " -d, --deploy 执行部署"
echo " -t, --tenant 指定租户(可选,默认从 .env 读取)"
echo " -s, --skip-migrate 跳过数据库迁移(适用于增量更新)"
echo ""
echo "示例:"
echo " $0 -d # 使用 .env 中的配置部署(含迁移)"
echo " $0 -d -t CHANGDE # 指定租户部署"
echo " $0 -d -s # 部署但跳过数据库迁移"
echo " $0 -d -t CHANGDE -s # 指定租户部署,跳过迁移"
}
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
-d|--deploy)
DEPLOY=true
shift
;;
-t|--tenant)
PROJECT_DIR="$2"
shift 2
;;
-s|--skip-migrate)
SKIP_MIGRATE=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "未知选项: $1"
usage
exit 1
;;
esac
done
# 执行部署
if [ "$DEPLOY" = true ]; then
deploy
else
usage
fi
+59
View File
@@ -0,0 +1,59 @@
# ==============================================================================
# MyAPS Nginx 配置模板
# ==============================================================================
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# MyAPS 应用配置
server {
listen 80;
server_name localhost;
# 静态文件
location /static/ {
root /opt/myaps;
expires 30d;
}
# 健康检查
location /health {
proxy_pass http://127.0.0.1:8000/health;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# API 代理
location / {
proxy_pass http://127.0.0.1:8000;
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;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
@@ -0,0 +1,85 @@
# MyAPS Project Packaging Script (PowerShell)
# Excludes unnecessary directories and files
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " MyAPS Package Script" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
if (-not (Test-Path "main.py")) {
Write-Host "ERROR: Please run this script from project root directory" -ForegroundColor Red
exit 1
}
$projectDir = Get-Location
$projectName = Split-Path $projectDir -Leaf
$parentDir = Split-Path $projectDir -Parent
$timestamp = Get-Date -Format "yyyyMMdd"
$outputFile = Join-Path $parentDir "${projectName}_${timestamp}.tar.gz"
$tempDir = Join-Path $parentDir "${projectName}_temp"
Write-Host "[1/4] Current project: $projectName" -ForegroundColor Green
# Remove existing temp directory
if (Test-Path $tempDir) {
Write-Host "[2/4] Cleaning up existing temp directory..." -ForegroundColor Green
Remove-Item -Recurse -Force $tempDir
}
Write-Host "[2/4] Copying project files (excluding unwanted items)..." -ForegroundColor Green
# Define items to exclude
$excludeItems = @(
"venv",
"offline_packages",
"logs",
"test",
"storage",
"backups",
".git",
"__pycache__",
".env" # 排除 .env 文件,让服务器使用自己的配置
)
# Get all items in project directory
$items = Get-ChildItem -Path $projectDir
foreach ($item in $items) {
# Skip excluded items
if ($excludeItems -contains $item.Name) {
Write-Host " Skipping: $($item.Name)"
continue
}
# Copy the item
$destPath = Join-Path $tempDir $item.Name
Copy-Item -Path $item.FullName -Destination $destPath -Recurse -Force
}
Write-Host "[3/4] Removing compiled files from subdirectories..." -ForegroundColor Green
# Remove .pyc files from all subdirectories
Get-ChildItem -Path $tempDir -Recurse -Filter "*.pyc" | Remove-Item -Force
Get-ChildItem -Path $tempDir -Recurse -Filter "*.pyo" | Remove-Item -Force
Get-ChildItem -Path $tempDir -Recurse -Filter "__pycache__" -Directory | Remove-Item -Recurse -Force
Write-Host "[4/4] Packing..." -ForegroundColor Green
# Create tar.gz
tar -czvf $outputFile -C $parentDir "${projectName}_temp"
# Clean up
Remove-Item -Recurse -Force $tempDir
Write-Host "" -ForegroundColor Cyan
Write-Host "SUCCESS: Package completed!" -ForegroundColor Green
Write-Host "File: $outputFile" -ForegroundColor Green
Write-Host "" -ForegroundColor Cyan
Write-Host "Excluded items:" -ForegroundColor Yellow
Write-Host "- venv (Virtual environment)"
Write-Host "- offline_packages (Offline dependencies)"
Write-Host "- logs (Log files)"
Write-Host "- test (Test code)"
Write-Host "- storage (Local storage)"
Write-Host "- backups (Backup files)"
Write-Host "- .git (Git repository)"
Write-Host "- __pycache__ and *.pyc (Compiled files)"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# ==============================================================================
# 打包离线依赖包(在有外网的开发机执行)
# ==============================================================================
set -e
echo "========================================"
echo " 准备离线依赖包"
echo "========================================"
# 检查 requirements.txt 是否存在
if [ ! -f "requirements.txt" ]; then
echo "❌ 错误: requirements.txt 不存在"
exit 1
fi
# 创建离线包目录
echo "[1/2] 创建离线包目录..."
mkdir -p offline_packages
# 下载依赖
echo "[2/2] 下载 Python 依赖..."
pip download -r requirements.txt -d offline_packages/
echo ""
echo "✅ 离线依赖包准备完成!"
echo " 目录: $(pwd)/offline_packages/"
echo " 文件数: $(ls offline_packages/ | wc -l)"
echo ""
echo "使用方式:"
echo " 1. 将整个项目目录拷贝到离线服务器"
echo " 2. 运行 deploy.sh -d 进行部署"
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
# ==============================================================================
# 重启 MyAPS 服务
# ==============================================================================
set -e
# 读取 .env 配置
read_env() {
if [ -f ".env" ]; then
while IFS= read -r line; do
if [[ ! "$line" =~ ^# && "$line" =~ ^[A-Z_]+= ]]; then
export "$line"
fi
done < ".env"
fi
}
read_env
echo "🔄 重启 ${SERVICE_NAME:-MyAPS_API} 服务..."
systemctl restart "${SERVICE_NAME:-MyAPS_API}"
echo "✅ 服务已重启"
echo "查看状态: systemctl status ${SERVICE_NAME:-MyAPS_API}"
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
# ==============================================================================
# 启动 MyAPS 服务
# ==============================================================================
set -e
# 读取 .env 配置
read_env() {
if [ -f ".env" ]; then
while IFS= read -r line; do
if [[ ! "$line" =~ ^# && "$line" =~ ^[A-Z_]+= ]]; then
export "$line"
fi
done < ".env"
fi
}
read_env
echo "🚀 启动 ${SERVICE_NAME:-MyAPS_API} 服务..."
systemctl start "${SERVICE_NAME:-MyAPS_API}"
echo "✅ 服务已启动"
echo "查看状态: systemctl status ${SERVICE_NAME:-MyAPS_API}"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# ==============================================================================
# 查看 MyAPS 服务状态
# ==============================================================================
set -e
# 读取 .env 配置
read_env() {
if [ -f ".env" ]; then
while IFS= read -r line; do
if [[ ! "$line" =~ ^# && "$line" =~ ^[A-Z_]+= ]]; then
export "$line"
fi
done < ".env"
fi
}
read_env
echo "📊 ${SERVICE_NAME:-MyAPS_API} 服务状态:"
systemctl status "${SERVICE_NAME:-MyAPS_API}"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# ==============================================================================
# 停止 MyAPS 服务
# ==============================================================================
set -e
# 读取 .env 配置
read_env() {
if [ -f ".env" ]; then
while IFS= read -r line; do
if [[ ! "$line" =~ ^# && "$line" =~ ^[A-Z_]+= ]]; then
export "$line"
fi
done < ".env"
fi
}
read_env
echo "⏹️ 停止 ${SERVICE_NAME:-MyAPS_API} 服务..."
systemctl stop "${SERVICE_NAME:-MyAPS_API}"
echo "✅ 服务已停止"