deerflow-code/offline-backend-20260512/switch_database_to_mysql.ps1
2026-09-07 18:24:55 +08:00

216 lines
7.7 KiB
PowerShell

param(
[string]$MysqlDatabaseUrl = $env:MYSQL_DATABASE_URL,
[string]$MysqlPipPackages = $(if ($env:MYSQL_PIP_PACKAGES) { $env:MYSQL_PIP_PACKAGES } else { "asyncmy==0.2.10" }),
[string]$CreateDatabase = $(if ($env:CREATE_DATABASE) { $env:CREATE_DATABASE } else { "1" }),
[string]$DropEmptyTargetSchema = $(if ($env:DROP_EMPTY_TARGET_SCHEMA) { $env:DROP_EMPTY_TARGET_SCHEMA } else { "1" }),
[string]$TruncateTarget = $(if ($env:TRUNCATE_TARGET) { $env:TRUNCATE_TARGET } else { "0" }),
[string]$SqliteDb = $(if ($env:SQLITE_DB) { $env:SQLITE_DB } else { "" }),
[string]$ConfigFile = $(if ($env:DEER_FLOW_CONFIG_PATH) { $env:DEER_FLOW_CONFIG_PATH } else { "" }),
[string]$EnvFile = $(if ($env:ENV_FILE) { $env:ENV_FILE } else { "" }),
[string]$PythonExe = $(if ($env:PYTHON_EXE) { $env:PYTHON_EXE } else { "" })
)
$ErrorActionPreference = "Stop"
function Die([string]$Message) {
Write-Error $Message
exit 1
}
function Require-Command([string]$Name) {
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
Die "command not found: $Name"
}
}
function Require-File([string]$Path) {
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
Die "required file not found: $Path"
}
}
function Require-Dir([string]$Path) {
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
Die "required directory not found: $Path"
}
}
function Resolve-ExistingFile([string]$Path) {
return (Resolve-Path -LiteralPath $Path).Path
}
function Get-EnvFileValue([string]$Path, [string]$Key) {
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return ""
}
$line = Get-Content -LiteralPath $Path |
Where-Object { $_ -match "^\s*$([regex]::Escape($Key))=" } |
Select-Object -Last 1
if (-not $line) {
return ""
}
return ($line -replace "^\s*$([regex]::Escape($Key))=", "")
}
function Upsert-EnvFileValue([string]$Path, [string]$Key, [string]$Value) {
$existing = @()
if (Test-Path -LiteralPath $Path -PathType Leaf) {
$existing = Get-Content -LiteralPath $Path | Where-Object { $_ -notmatch "^\s*$([regex]::Escape($Key))=" }
}
$existing + "$Key=$Value" | Set-Content -LiteralPath $Path -Encoding UTF8
}
function Is-Enabled([string]$Value) {
return @("1", "true", "yes", "on") -contains $Value.ToLowerInvariant()
}
function Run-Native([string]$Command, [string[]]$Arguments) {
& $Command @Arguments
if ($LASTEXITCODE -ne 0) {
Die "$Command failed with exit code $LASTEXITCODE"
}
}
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$BackendDir = Join-Path $ScriptDir "backend"
$DefaultDeerFlowHome = Join-Path $BackendDir ".deer-flow"
if (-not $ConfigFile) {
$ConfigFile = Join-Path $ScriptDir "config.yaml"
}
if (-not $EnvFile) {
$EnvFile = Join-Path $ScriptDir ".env"
}
if (-not $SqliteDb) {
$SqliteDb = Join-Path $DefaultDeerFlowHome "data\deerflow.db"
}
if ($PythonExe) {
Require-File $PythonExe
$PythonExe = Resolve-ExistingFile $PythonExe
} else {
Require-Command uv
}
Require-File $EnvFile
Require-File $ConfigFile
Require-File $SqliteDb
Require-File (Join-Path $BackendDir "pyproject.toml")
Require-File (Join-Path $BackendDir "scripts\migrate_business_sqlite_to_mysql.py")
Require-File (Join-Path $BackendDir "scripts\update_database_config.py")
Require-File (Join-Path $ScriptDir "extensions_config.json")
Require-File (Join-Path $ScriptDir "skill_evolution.json")
Require-Dir (Join-Path $ScriptDir "skills")
if (-not $MysqlDatabaseUrl) {
$MysqlDatabaseUrl = Get-EnvFileValue $EnvFile "MYSQL_DATABASE_URL"
}
if (-not $MysqlDatabaseUrl) {
Die "MYSQL_DATABASE_URL is required. Example: .\switch_database_to_mysql.ps1 -MysqlDatabaseUrl 'mysql+asyncmy://user:pass@127.0.0.1:3306/deerflow?charset=utf8mb4'"
}
$ConfigFile = Resolve-ExistingFile $ConfigFile
$EnvFile = Resolve-ExistingFile $EnvFile
$SqliteDb = Resolve-ExistingFile $SqliteDb
$DeerFlowHome = if ($env:DEER_FLOW_HOME) { $env:DEER_FLOW_HOME } else { $DefaultDeerFlowHome }
New-Item -ItemType Directory -Force -Path $DeerFlowHome | Out-Null
$DeerFlowHome = (Resolve-Path -LiteralPath $DeerFlowHome).Path
$BackupRoot = Join-Path $DeerFlowHome "backups"
New-Item -ItemType Directory -Force -Path $BackupRoot | Out-Null
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$BackupDir = Join-Path $BackupRoot "mysql-switch-$Timestamp"
New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null
Copy-Item -LiteralPath $ConfigFile -Destination (Join-Path $BackupDir "config.yaml.bak") -Force
Copy-Item -LiteralPath $EnvFile -Destination (Join-Path $BackupDir "env.bak") -Force
Copy-Item -LiteralPath $SqliteDb -Destination (Join-Path $BackupDir "deerflow.db.bak") -Force
$env:MYSQL_DATABASE_URL = $MysqlDatabaseUrl
$env:DEER_FLOW_PROJECT_ROOT = $ScriptDir
$env:DEER_FLOW_HOME = $DeerFlowHome
$env:DEER_FLOW_CONFIG_PATH = $ConfigFile
$env:DEER_FLOW_EXTENSIONS_CONFIG_PATH = Join-Path $ScriptDir "extensions_config.json"
$env:DEER_FLOW_SKILL_EVOLUTION_CONFIG_PATH = Join-Path $ScriptDir "skill_evolution.json"
$env:DEER_FLOW_SKILLS_PATH = Join-Path $ScriptDir "skills"
$PythonPathParts = @(
$BackendDir,
(Join-Path $BackendDir "packages\harness")
)
if ($env:PYTHONPATH) {
$PythonPathParts += $env:PYTHONPATH
}
$env:PYTHONPATH = ($PythonPathParts -join [IO.Path]::PathSeparator)
Push-Location $BackendDir
try {
$packages = @($MysqlPipPackages -split "\s+" | Where-Object { $_ })
if ($PythonExe) {
if ($packages.Count -gt 0) {
Write-Host "Installing MySQL driver into selected Python environment: $MysqlPipPackages"
Run-Native $PythonExe (@("-m", "pip", "install") + $packages)
}
Run-Native $PythonExe @("-c", "import asyncmy")
} else {
if (-not (Test-Path -LiteralPath (Join-Path $BackendDir ".venv") -PathType Container)) {
Write-Host "Creating local uv virtual environment..."
Run-Native "uv" @("venv")
}
if ($packages.Count -gt 0) {
Write-Host "Installing MySQL driver into local backend environment: $MysqlPipPackages"
Run-Native "uv" (@("pip", "install") + $packages)
}
Run-Native "uv" @("run", "--no-sync", "python", "-c", "import asyncmy")
}
$migrationArgs = @(
"scripts\migrate_business_sqlite_to_mysql.py",
"--sqlite", $SqliteDb,
"--mysql-url", $MysqlDatabaseUrl
)
if (Is-Enabled $CreateDatabase) {
$migrationArgs += "--create-database"
}
if (Is-Enabled $DropEmptyTargetSchema) {
$migrationArgs += "--drop-empty-target-schema"
}
if (Is-Enabled $TruncateTarget) {
$migrationArgs += "--truncate-target"
}
Write-Host "Backups written to: $BackupDir"
Write-Host "Migrating business tables to MySQL..."
if ($PythonExe) {
Run-Native $PythonExe $migrationArgs
} else {
Run-Native "uv" (@("run", "--no-sync", "python") + $migrationArgs)
}
$configArgs = @(
"scripts\update_database_config.py",
"--config", $ConfigFile,
"--mysql-url-env", "MYSQL_DATABASE_URL",
"--checkpointer-sqlite", $SqliteDb
)
if ($PythonExe) {
Run-Native $PythonExe $configArgs
} else {
Run-Native "uv" (@("run", "--no-sync", "python") + $configArgs)
}
Upsert-EnvFileValue $EnvFile "MYSQL_DATABASE_URL" $MysqlDatabaseUrl
}
finally {
Pop-Location
}
Write-Host ""
Write-Host "MySQL switch completed."
Write-Host " Config updated: $ConfigFile"
Write-Host " Env updated: $EnvFile"
Write-Host " Backup dir: $BackupDir"
Write-Host ""
Write-Host "Start the local backend from backend directory, for example:"
Write-Host " cd backend"
Write-Host " `$env:PYTHONPATH='.'"
Write-Host " uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001"