From 026d3bf88c7e72d238da603e6e30052118af15dc Mon Sep 17 00:00:00 2001 From: "lasean.zhou" Date: Tue, 14 Apr 2026 17:07:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0exe=E6=89=93=E5=8C=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/install_service.ps1 | 203 +++++++++++++++++++++++++++++++++++++ deploy/服务安装说明.txt | 21 ++++ main.spec | 152 +++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 deploy/install_service.ps1 create mode 100644 deploy/服务安装说明.txt create mode 100644 main.spec diff --git a/deploy/install_service.ps1 b/deploy/install_service.ps1 new file mode 100644 index 0000000..320762f --- /dev/null +++ b/deploy/install_service.ps1 @@ -0,0 +1,203 @@ +# G3SB Backman Windows Service Installation Script +# Run as Administrator + +param( + [Parameter(Mandatory=$false)] + [string]$Action = "install" +) + +$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Host "ERROR: Administrator privileges required" -ForegroundColor Red + Write-Host "Please right-click and select 'Run as administrator'" -ForegroundColor Yellow + Start-Sleep -Seconds 5 + exit 1 +} + +$serviceName = "Backman_Text2sql" +$serviceDisplayName = "Backman Text2SQL Service" +$serviceDescription = "Backman Text2SQL AI Backend Service" + +$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path +# 单文件模式:exe 在 deploy 同级目录 +$exePath = Join-Path $scriptPath "Backman_Text2SQL_Server.exe" + +if (-not (Test-Path $exePath)) { + Write-Host "ERROR: Backman_Text2SQL_Server.exe not found" -ForegroundColor Red + Write-Host "Path: $exePath" -ForegroundColor Yellow + Write-Host "Please run 'pyinstaller --clean main.spec' in project root first" -ForegroundColor Yellow + Start-Sleep -Seconds 5 + exit 1 +} + +$NSSM_URL = "https://nssm.cc/release/nssm-2.24.zip" +$nssmPath = Join-Path $scriptPath "nssm.exe" + +function Get-NSSM { + if (-not (Test-Path $nssmPath)) { + Write-Host "Downloading NSSM..." -ForegroundColor Cyan + $zipPath = Join-Path $env:TEMP "nssm.zip" + try { + Invoke-WebRequest -Uri $NSSM_URL -OutFile $zipPath -UseBasicParsing + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $env:TEMP) + $nssmSource = Get-ChildItem -Path $env:TEMP -Recurse -Filter "nssm.exe" | Where-Object { $_.FullName -like "*win64*" } | Select-Object -First 1 + if ($nssmSource) { + Copy-Item $nssmSource.FullName -Destination $nssmPath -Force + Write-Host "NSSM downloaded to: $nssmPath" -ForegroundColor Green + } else { + throw "nssm.exe not found" + } + Remove-Item $zipPath -Force + Remove-Item (Join-Path $env:TEMP "nssm-2.24") -Recurse -Force + } + catch { + Write-Host "Failed to download NSSM: $_" -ForegroundColor Red + Write-Host "Manual: Visit https://nssm.cc/download and place nssm.exe in this directory" -ForegroundColor Yellow + Start-Sleep -Seconds 5 + exit 1 + } + } else { + Write-Host "NSSM exists: $nssmPath" -ForegroundColor Green + } +} + +function Install-Service { + Write-Host "`n=================================" -ForegroundColor Cyan + Write-Host "Installing G3SB Backman Service" -ForegroundColor Cyan + Write-Host "=================================`n" -ForegroundColor Cyan + + Get-NSSM + $workDir = $scriptPath + + Write-Host "Creating service: $serviceName" -ForegroundColor Cyan + & $nssmPath install $serviceName "$exePath" + + if ($LASTEXITCODE -eq 0) { + & $nssmPath set $serviceName DisplayName $serviceDisplayName + & $nssmPath set $serviceName Description $serviceDescription + & $nssmPath set $serviceName Start SERVICE_AUTO_START + & $nssmPath set $serviceName AppDirectory $workDir + + $logDir = Join-Path $workDir "logs" + if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } + $stdoutLog = Join-Path $logDir "service_stdout.log" + $stderrLog = Join-Path $logDir "service_stderr.log" + & $nssmPath set $serviceName AppStdout $stdoutLog + & $nssmPath set $serviceName AppStderr $stderrLog + & $nssmPath set $serviceName AppRestartDelay 1000 + & $nssmPath set $serviceName AppExit Default Restart + + Write-Host "\n[OK] Service installed successfully!" -ForegroundColor Green + Write-Host " Service Name: $serviceName" -ForegroundColor White + Write-Host " Display Name: $serviceDisplayName" -ForegroundColor White + Write-Host " Executable: $exePath" -ForegroundColor White + + $response = Read-Host "`nStart service now? (Y/n)" + if ([string]::IsNullOrWhiteSpace($response) -or $response -match '^[Yy]$') { + Start-ServiceAction + } + + Write-Host "`nTips:" -ForegroundColor Yellow + Write-Host " Status: .\install_service.ps1 status" -ForegroundColor White + Write-Host " Stop: .\install_service.ps1 stop" -ForegroundColor White + Write-Host " Start: .\install_service.ps1 start" -ForegroundColor White + Write-Host " Uninstall: .\install_service.ps1 uninstall" -ForegroundColor White + } else { + Write-Host "`n[FAIL] Service installation failed!" -ForegroundColor Red + } +} + +function Uninstall-Service { + Write-Host "`n=================================" -ForegroundColor Cyan + Write-Host "Uninstalling G3SB Backman Service" -ForegroundColor Cyan + Write-Host "=================================`n" -ForegroundColor Cyan + + Stop-ServiceAction + Get-NSSM + + $confirm = Read-Host "Confirm uninstall? (y/N)" + if ($confirm -notmatch '^[Yy]$') { + Write-Host "Cancelled" -ForegroundColor Yellow + return + } + + & $nssmPath remove $serviceName confirm + if ($LASTEXITCODE -eq 0) { + Write-Host "`n[OK] Service uninstalled!" -ForegroundColor Green + } else { + Write-Host "`n[FAIL] Uninstall failed!" -ForegroundColor Red + } +} + +function Start-ServiceAction { + Write-Host "`nStarting service $serviceName ..." -ForegroundColor Cyan + try { + Start-Service -Name $serviceName -ErrorAction Stop + Write-Host "[OK] Service started" -ForegroundColor Green + Start-Sleep -Seconds 3 + $status = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + if ($status.Status -eq 'Running') { + Write-Host "[OK] Service running" -ForegroundColor Green + } else { + Write-Host "[WARN] Service status: $($status.Status)" -ForegroundColor Yellow + } + } + catch { + Write-Host "[FAIL] Failed to start: $_" -ForegroundColor Red + } +} + +function Stop-ServiceAction { + Write-Host "`nStopping service $serviceName ..." -ForegroundColor Cyan + try { + Stop-Service -Name $serviceName -Force -ErrorAction Stop + Write-Host "[OK] Service stopped" -ForegroundColor Green + } + catch { + Write-Host "[WARN] Service not running or failed to stop: $_" -ForegroundColor Yellow + } +} + +function Get-ServiceStatus { + Write-Host "`n=================================" -ForegroundColor Cyan + Write-Host "Service Status" -ForegroundColor Cyan + Write-Host "=================================`n" -ForegroundColor Cyan + + try { + $service = Get-Service -Name $serviceName -ErrorAction Stop + Write-Host "Service Name: $($service.Name)" -ForegroundColor White + Write-Host "Display Name: $($service.DisplayName)" -ForegroundColor White + Write-Host "Status: $($service.Status)" -ForegroundColor $(if ($service.Status -eq 'Running') { 'Green' } else { 'Yellow' }) + Write-Host "Start Type: $($service.StartType)" -ForegroundColor White + + if ($service.Status -eq 'Running') { + Write-Host "`n[OK] Service is running" -ForegroundColor Green + Write-Host "URL: http://localhost:8041" -ForegroundColor Cyan + } else { + Write-Host "`n[WARN] Service is not running" -ForegroundColor Yellow + } + } + catch { + Write-Host "[FAIL] Service not installed" -ForegroundColor Red + Write-Host "Install: .\install_service.ps1 install" -ForegroundColor Yellow + } + Write-Host "" +} + +Write-Host "`nG3SB Backman Service Manager" -ForegroundColor Cyan +Write-Host "============================`n" -ForegroundColor Cyan + +switch ($Action.ToLower()) { + "install" { Install-Service } + "uninstall" { Uninstall-Service } + "start" { Start-ServiceAction } + "stop" { Stop-ServiceAction } + "status" { Get-ServiceStatus } + "restart" { Stop-ServiceAction; Start-Sleep -Seconds 2; Start-ServiceAction } + default { + Write-Host "Usage: .\install_service.ps1 [action]" -ForegroundColor Yellow + Write-Host "Actions: install, uninstall, start, stop, restart, status" -ForegroundColor White + } +} +Write-Host "" diff --git a/deploy/服务安装说明.txt b/deploy/服务安装说明.txt new file mode 100644 index 0000000..980ab4a --- /dev/null +++ b/deploy/服务安装说明.txt @@ -0,0 +1,21 @@ +以**管理员身份**打开 PowerShell,然后执行: + +```powershell +# 安装服务 +powershell.exe -ExecutionPolicy Bypass -File .\install_service.ps1 install + +# 查看服务状态 +powershell.exe -ExecutionPolicy Bypass -File .\install_service.ps1 status + +# 启动服务 +powershell.exe -ExecutionPolicy Bypass -File .\install_service.ps1 start + +# 停止服务 +powershell.exe -ExecutionPolicy Bypass -File .\install_service.ps1 stop + +# 重启服务 +powershell.exe -ExecutionPolicy Bypass -File .\install_service.ps1 restart + +# 卸载服务 +powershell.exe -ExecutionPolicy Bypass -File .\install_service.ps1 uninstall +``` \ No newline at end of file diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..962df87 --- /dev/null +++ b/main.spec @@ -0,0 +1,152 @@ +# -*- mode: python ; coding: utf-8 -*- + +import os +from pathlib import Path +from PyInstaller.utils.hooks import collect_data_files, collect_submodules + +# 获取项目根目录(spec 文件所在目录) +import sys +_REPO_DIR = Path(os.getcwd()).resolve() + +# 根目录执行:pyinstaller --clean main.spec +# 收集配置文件夹和数据文件 +datas = [ + # 所有数据文件都不打包,从外部读取 + # ('.env', '.'), + # ('data/schemas', 'data/schemas'), + # ('data/embeddings', 'data/embeddings'), + # ('data/experiences', 'data/experiences'), +] + +# 收集所有需要的隐藏导入 +hiddenimports = [ + # Web 框架 + 'fastapi', + 'fastapi.middleware.cors', + 'fastapi.responses', + 'uvicorn', + 'uvicorn.lifespan.on', + 'uvicorn.config', + 'uvicorn.protocols.http.auto', + 'uvicorn.protocols.websockets.auto', + 'uvicorn.loops.auto', + + # Backend 模块(重要!) + 'main', # backend/main.py + 'backend.main', + 'backend.nl_lite_store', + 'backend.agents', + 'backend.agents.orchestrator', + 'backend.agents.schema_linker', + 'backend.agents.sql_generator', + 'backend.agents.validator', + 'backend.config', + 'backend.config.prompts', + 'backend.config.settings', + 'backend.db', + 'backend.db.engine', + 'backend.db.dbhub_tools', + 'backend.db.dbhub_sql_parser', + 'backend.db.dbhub_allowed_keywords', + 'backend.llm', + 'backend.llm.deepseek_client', + 'backend.schema', + 'backend.schema.manager', + 'backend.schema.loader', + 'backend.schema.indexer', + 'backend.schema.models', + 'backend.utils', + 'backend.utils.dialog_classifier', + 'backend.utils.embedding', + 'backend.utils.fewshot_selector', + 'backend.utils.question_locale', + 'backend.utils.sql_parser', + 'backend.utils.validators', + + # 数据库相关 + 'pymssql', + 'sqlalchemy', + 'sqlalchemy.dialects.mssql.pymssql', + 'sqlalchemy.dialects.mysql', + 'sqlalchemy.dialects.postgresql', + 'sqlglot', + 'sqlglot.dialects', + 'sqlparse', + + # 向量数据库 - 使用 collect_submodules 自动收集所有子模块 + + # Embedding 相关 + 'sentencepiece', + 'accelerate', + 'safetensors', + + # LLM API + 'openai', + + # 数据处理 + 'numpy', + 'jieba', + + # Pydantic + 'pydantic', + 'pydantic_settings', + 'dotenv', + + # 工具库 + 'tenacity', + 'structlog', + 'tqdm', + 'camel_ai', + + # 标准库中可能被遗漏的 + 'email.mime.text', + 'email.mime.multipart', +] + collect_submodules('chromadb') +# 排除不必要的模块以减小体积 +excludes = [ + 'tkinter', + 'matplotlib', + 'scipy', + 'pandas.tests', + 'numpy.testing', + 'jupyter', + 'notebook', + 'IPython', + 'pytest', +] + +a = Analysis( + ['api_server.py'], + pathex=[str(_REPO_DIR)], + binaries=[], + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=excludes, + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, # 单文件模式:将数据文件打包进 exe + [], + name='Backman_Text2SQL_Server', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=False, # 禁用 UPX,避免兼容性问题 + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +)