From ef53028be53fac5098a2fc4ccbeb95d82c7391ac Mon Sep 17 00:00:00 2001 From: kdletters Date: Fri, 24 Apr 2026 13:30:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=AF=E5=8A=A8=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md | 44 ++- package.json | 4 +- scripts/dev-rust-stack.ps1 | 342 ------------------ scripts/dev-rust-stack.sh | 6 +- 4 files changed, 31 insertions(+), 365 deletions(-) delete mode 100644 scripts/dev-rust-stack.ps1 diff --git a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md index 08964693..39a15882 100644 --- a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md +++ b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md @@ -1,4 +1,4 @@ -# Rust 本地联调与远端发布脚本方案 +# Rust 本地联调与远端发布脚本方案 日期:`2026-04-22` @@ -9,7 +9,7 @@ 1. 本地一键联调脚本:同时启动本地 SpacetimeDB、Rust `api-server` 与 Web 前端,并通过现有 Vite 代理开关把运行时 API 指向 Rust。 2. Ubuntu 发布包构建脚本:在仓库根目录生成 `build/<当前时间>/` 发布目录,内含前端 release、Linux `api-server`、SpacetimeDB wasm、启动脚本、停止脚本,以及从仓库根目录复制的 `.env` / `.env.local`,并默认通过 `scp` 上传到目标服务器。 -脚本只做部署与联调编排,不改变 HTTP contract、SpacetimeDB schema 命名、对象存储键规划和前端默认 Node 开发入口。 +脚本只做部署与联调编排,不改变 HTTP contract、SpacetimeDB schema 命名和对象存储键规划。 ## 2. 本地脚本 @@ -19,13 +19,7 @@ npm run dev:rust ``` -跨平台 Bash 入口: - -```bash -npm run dev:rust:sh -``` - -Windows 下 `dev:rust:sh`、`deploy:rust:remote` 与 `build:rust:ubuntu` 会通过 `scripts/run-bash-script.mjs` 优先查找 Git Bash;如安装路径不标准,可用 `GENARRATIVE_BASH` 指定 `bash` 可执行文件。 +默认入口直接执行 Bash 版 `scripts/dev-rust-stack.sh`。Windows 下 `dev:rust`、`dev:rust:logs`、`deploy:rust:remote` 与 `build:rust:ubuntu` 会通过 `scripts/run-bash-script.mjs` 优先查找 Git Bash;如安装路径不标准,可用 `GENARRATIVE_BASH` 指定 `bash` 可执行文件。 默认端口: @@ -33,11 +27,12 @@ Windows 下 `dev:rust:sh`、`deploy:rust:remote` 与 `build:rust:ubuntu` 会通 2. Rust `api-server`:`http://127.0.0.1:8082` 3. SpacetimeDB standalone:`http://127.0.0.1:3101` 4. SpacetimeDB database:优先读取仓库根目录 `spacetime.local.json` 的 `database` 字段;没有该字段时才回退到 `genarrative-dev` +5. SpacetimeDB 本地数据与日志目录:`server-rs/.spacetimedb/local` 默认流程: 1. 检查 `cargo`、`node` 与 `spacetime` CLI。 -2. 启动 `spacetime --root-dir server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101`。 +2. 启动 `spacetime --root-dir=server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101`,确保本地数据库与 SpacetimeDB 内部日志不会落到开发者全局目录。 3. 等待 `spacetime server ping http://127.0.0.1:3101` 可用。 4. 执行 `spacetime publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module --yes`。 5. 注入 `GENARRATIVE_API_*` 与 `GENARRATIVE_SPACETIME_*` 后启动 `cargo run -p api-server`;直接运行 `api-server` 时,如未显式设置 `GENARRATIVE_SPACETIME_DATABASE`,服务端也会向上查找 `spacetime.local.json` 作为本地默认库名。 @@ -53,26 +48,35 @@ Vite 代理覆盖范围: 安全边界: 1. 默认不执行 `--clear-database`。 -2. 只有显式传入 `-ClearDatabase` 或 `--clear-database` 才允许清库重发。 -3. 如需要复用已经启动的 SpacetimeDB,可传 `-SkipSpacetime` / `--skip-spacetime`。 -4. 如只想启动进程不发布模块,可传 `-SkipPublish` / `--skip-publish`。 +2. 只有显式传入 `--clear-database` 才允许清库重发。 +3. 如需要复用已经启动的 SpacetimeDB,可传 `--skip-spacetime`。 +4. 如只想启动进程不发布模块,可传 `--skip-publish`。 常用示例: -```powershell -.\scripts\dev-rust-stack.ps1 -.\scripts\dev-rust-stack.ps1 -ApiPort 8090 -SpacetimePort 3110 -Database genarrative-dev -.\scripts\dev-rust-stack.ps1 -SkipSpacetime -SkipPublish -.\scripts\dev-rust-stack.ps1 -ClearDatabase -``` - ```bash +npm run dev:rust ./scripts/dev-rust-stack.sh ./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 --database genarrative-dev ./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish ./scripts/dev-rust-stack.sh --clear-database ``` +日志提取: + +```bash +npm run dev:rust:logs +npm run dev:rust:logs -- --follow +./scripts/spacetime-logs-local.sh --lines 500 --output logs/spacetime/local.log +``` + +日志提取规则: + +1. SpacetimeDB 模块日志以 `spacetime logs ` 为唯一提取入口,脚本不直接读取内部日志文件结构。 +2. 默认读取 `spacetime.local.json` 的 `database` 字段,默认 server 为 `http://127.0.0.1:3101`。 +3. 默认输出到 `logs/spacetime/-.log`,并通过 `tee` 同步显示在终端。 +4. `--follow` 仅用于本地追踪,会持续追加到同一个输出文件;停止时用 `Ctrl+C`。 + 联调排错补充: 1. 如果首页公开广场出现 `上游服务请求失败`,优先检查 `api-server` 错误详情里的 `ws://.../v1/database//subscribe` 是否指向了未发布的库。 diff --git a/package.json b/package.json index d030fec1..59469601 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "dev": "node scripts/dev-node.mjs", - "dev:rust": "powershell -ExecutionPolicy Bypass -File scripts/dev-rust-stack.ps1", - "dev:rust:sh": "node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh", + "dev:rust": "node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh", + "dev:rust:logs": "node scripts/run-bash-script.mjs scripts/spacetime-logs-local.sh", "dev:web": "node scripts/vite-cli.mjs --port=3000 --host=0.0.0.0", "dev:node": "node scripts/dev-node.mjs", "deploy:rust:remote": "node scripts/run-bash-script.mjs scripts/deploy-rust-remote.sh", diff --git a/scripts/dev-rust-stack.ps1 b/scripts/dev-rust-stack.ps1 deleted file mode 100644 index d0d5227b..00000000 --- a/scripts/dev-rust-stack.ps1 +++ /dev/null @@ -1,342 +0,0 @@ -[CmdletBinding()] -param( - [Alias("h")] - [switch]$Help, - [string]$ApiHost = "127.0.0.1", - [int]$ApiPort = 8082, - [string]$WebHost = "0.0.0.0", - [int]$WebPort = 3000, - [string]$SpacetimeHost = "127.0.0.1", - [int]$SpacetimePort = 3101, - [string]$SpacetimeRootDir = "", - [string]$Database = "", - [string]$Log = "info,tower_http=info", - [int]$SpacetimeStartupTimeoutSeconds = 60, - [switch]$SkipSpacetime, - [switch]$SkipPublish, - [switch]$ClearDatabase -) - -$ErrorActionPreference = "Stop" - -function Write-Usage { - @( - 'Usage:', - ' npm run dev:rust', - ' .\scripts\dev-rust-stack.ps1 -ApiPort 8090 -SpacetimePort 3110', - ' .\scripts\dev-rust-stack.ps1 -SkipSpacetime -SkipPublish', - ' .\scripts\dev-rust-stack.ps1 -ClearDatabase', - '', - 'Notes:', - ' 1. Start SpacetimeDB standalone, Rust api-server, and Vite web together.', - ' 2. Publish server-rs/crates/spacetime-module by default, without clearing data.', - ' 3. Only -ClearDatabase appends spacetime publish --clear-database.', - ' 4. Web listens on 0.0.0.0:3000 by default; API listens on 127.0.0.1:8082.' - ) -join [Environment]::NewLine -} - -function Quote-ProcessArgument { - param([string]$Value) - - if ($null -eq $Value) { - return '""' - } - - if ($Value -notmatch '[\s"]') { - return $Value - } - - return '"' + $Value.Replace('"', '\"') + '"' -} - -function Join-ProcessArguments { - param([string[]]$Arguments) - - return (($Arguments | ForEach-Object { Quote-ProcessArgument $_ }) -join " ") -} - -function Resolve-ClientHost { - param([string]$HostName) - - if ($HostName -eq "0.0.0.0" -or $HostName -eq "::") { - return "127.0.0.1" - } - - return $HostName -} - -function Read-LocalSpacetimeDatabase { - param([string]$RepoRoot) - - $localConfigPath = Join-Path $RepoRoot "spacetime.local.json" - if (-not (Test-Path $localConfigPath)) { - return "" - } - - try { - $localConfig = Get-Content -Path $localConfigPath -Encoding UTF8 -Raw | ConvertFrom-Json - $database = [string]$localConfig.database - if (-not [string]::IsNullOrWhiteSpace($database)) { - return $database.Trim() - } - } - catch { - Write-Host "[dev:rust] ignore invalid spacetime.local.json: $($_.Exception.Message)" - } - - return "" -} - -function Start-StackProcess { - param( - [string]$Name, - [string]$FilePath, - [string[]]$Arguments, - [string]$WorkingDirectory, - [hashtable]$Environment - ) - - $argumentLine = Join-ProcessArguments -Arguments $Arguments - Write-Host "[dev:rust] start ${Name}: $FilePath $argumentLine" - - $startInfo = New-Object System.Diagnostics.ProcessStartInfo - $startInfo.FileName = $FilePath - $startInfo.Arguments = $argumentLine - $startInfo.WorkingDirectory = $WorkingDirectory - $startInfo.UseShellExecute = $false - $startInfo.RedirectStandardOutput = $false - $startInfo.RedirectStandardError = $false - $startInfo.RedirectStandardInput = $false - - foreach ($entry in $Environment.GetEnumerator()) { - $startInfo.EnvironmentVariables[$entry.Key] = [string]$entry.Value - } - - $process = New-Object System.Diagnostics.Process - $process.StartInfo = $startInfo - - if (-not $process.Start()) { - throw "Failed to start process: $Name" - } - - return [PSCustomObject]@{ - Name = $Name - Process = $process - } -} - -function Stop-StackProcesses { - param([System.Collections.Generic.List[object]]$Processes) - - for ($index = $Processes.Count - 1; $index -ge 0; $index--) { - $item = $Processes[$index] - $process = $item.Process - - if ($null -ne $process -and -not $process.HasExited) { - Write-Host "[dev:rust] stop $($item.Name) (pid=$($process.Id))" - $taskkillCommand = Get-Command taskkill.exe -ErrorAction SilentlyContinue - if ($null -ne $taskkillCommand) { - & $taskkillCommand.Source /PID $process.Id /T /F *> $null - } - else { - Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue - } - } - } -} - -function Wait-ForSpacetimeServer { - param( - [string]$CommandPath, - [string]$Server, - [int]$TimeoutSeconds, - $ProcessItem - ) - - $deadline = (Get-Date).AddSeconds($TimeoutSeconds) - - while ((Get-Date) -lt $deadline) { - if ($null -ne $ProcessItem -and $ProcessItem.Process.HasExited) { - throw "SpacetimeDB exited before readiness, exit code: $($ProcessItem.Process.ExitCode)" - } - - & $CommandPath server ping $Server *> $null - if ($LASTEXITCODE -eq 0) { - return - } - - Start-Sleep -Milliseconds 500 - } - - throw "Timed out waiting for SpacetimeDB readiness: $Server" -} - -if ($Help) { - Write-Usage - exit 0 -} - -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$repoRoot = Split-Path -Parent $scriptDir -$serverRsDir = Join-Path $repoRoot "server-rs" -$manifestPath = Join-Path $serverRsDir "Cargo.toml" -$modulePath = Join-Path $serverRsDir "crates\spacetime-module" -$viteCliPath = Join-Path $repoRoot "scripts\vite-cli.mjs" - -if ([string]::IsNullOrWhiteSpace($SpacetimeRootDir)) { - $SpacetimeRootDir = Join-Path $serverRsDir ".spacetimedb\local" -} - -if ([string]::IsNullOrWhiteSpace($Database)) { - $Database = Read-LocalSpacetimeDatabase -RepoRoot $repoRoot -} - -if ([string]::IsNullOrWhiteSpace($Database)) { - $Database = "genarrative-dev" -} - -if (-not (Test-Path $manifestPath)) { - throw "Missing server-rs/Cargo.toml, cannot start Rust local stack." -} - -if (-not (Test-Path (Join-Path $modulePath "Cargo.toml"))) { - throw "Missing server-rs/crates/spacetime-module/Cargo.toml, cannot publish SpacetimeDB module." -} - -if (-not (Test-Path $viteCliPath)) { - throw "Missing scripts/vite-cli.mjs, cannot start web frontend." -} - -$cargoCommand = Get-Command cargo -ErrorAction SilentlyContinue -$nodeCommand = Get-Command node -ErrorAction SilentlyContinue -$spacetimeCommand = Get-Command spacetime -ErrorAction SilentlyContinue - -if ($null -eq $cargoCommand) { - throw "Missing cargo. Install Rust toolchain first." -} - -if ($null -eq $nodeCommand) { - throw "Missing node. Install Node.js or use the project bundled runtime first." -} - -if (-not $SkipSpacetime -or -not $SkipPublish) { - if ($null -eq $spacetimeCommand) { - throw "Missing spacetime CLI. Install guide: https://spacetimedb.com/install" - } -} - -$spacetimeServer = "http://$SpacetimeHost`:$SpacetimePort" -$apiTargetHost = Resolve-ClientHost -HostName $ApiHost -$rustServerTarget = "http://$apiTargetHost`:$ApiPort" -$stackProcesses = New-Object System.Collections.Generic.List[object] -$exitCode = 0 - -Write-Host "[dev:rust] repo: $repoRoot" -Write-Host "[dev:rust] web: http://127.0.0.1:$WebPort" -Write-Host "[dev:rust] rust api: $rustServerTarget" -Write-Host "[dev:rust] spacetime: $spacetimeServer" -Write-Host "[dev:rust] database: $Database" - -try { - $spacetimeProcessItem = $null - - if (-not $SkipSpacetime) { - New-Item -ItemType Directory -Force -Path $SpacetimeRootDir | Out-Null - $spacetimeProcessItem = Start-StackProcess ` - -Name "spacetimedb" ` - -FilePath $spacetimeCommand.Source ` - -Arguments @( - "start", - "--edition", "standalone", - "--listen-addr", "$SpacetimeHost`:$SpacetimePort" - ) ` - -WorkingDirectory $serverRsDir ` - -Environment @{} - $stackProcesses.Add($spacetimeProcessItem) - } - - if (-not $SkipPublish) { - Write-Host "[dev:rust] wait for SpacetimeDB readiness" - Wait-ForSpacetimeServer ` - -CommandPath $spacetimeCommand.Source ` - -Server $spacetimeServer ` - -TimeoutSeconds $SpacetimeStartupTimeoutSeconds ` - -ProcessItem $spacetimeProcessItem - - $publishArgs = @( - "publish", - $Database, - "--server", $spacetimeServer, - "--module-path", $modulePath - ) - - if ($ClearDatabase) { - $publishArgs += "--clear-database" - } - - $publishArgs += "--yes" - - Write-Host "[dev:rust] publish SpacetimeDB module: $Database" - & $spacetimeCommand.Source @publishArgs - if ($LASTEXITCODE -ne 0) { - throw "spacetime publish failed, exit code: $LASTEXITCODE" - } - } - - $apiEnvironment = @{ - GENARRATIVE_API_HOST = $ApiHost - GENARRATIVE_API_PORT = "$ApiPort" - GENARRATIVE_API_LOG = $Log - GENARRATIVE_SPACETIME_SERVER_URL = $spacetimeServer - GENARRATIVE_SPACETIME_DATABASE = $Database - } - - $apiProcessItem = Start-StackProcess ` - -Name "api-server" ` - -FilePath $cargoCommand.Source ` - -Arguments @("run", "-p", "api-server", "--manifest-path", $manifestPath) ` - -WorkingDirectory $repoRoot ` - -Environment $apiEnvironment - $stackProcesses.Add($apiProcessItem) - - $webEnvironment = @{ - GENARRATIVE_BACKEND_STACK = "rust" - RUST_SERVER_TARGET = $rustServerTarget - GENARRATIVE_RUNTIME_SERVER_TARGET = $rustServerTarget - VITE_DEV_HOST = $WebHost - } - - $webProcessItem = Start-StackProcess ` - -Name "vite" ` - -FilePath $nodeCommand.Source ` - -Arguments @($viteCliPath, "--port=$WebPort", "--host=$WebHost") ` - -WorkingDirectory $repoRoot ` - -Environment $webEnvironment - $stackProcesses.Add($webProcessItem) - - Write-Host "[dev:rust] local Rust stack is running. Press Ctrl+C to stop all child processes." - - while ($true) { - foreach ($item in $stackProcesses) { - if ($item.Process.HasExited) { - $exitCode = $item.Process.ExitCode - Write-Host "[dev:rust] $($item.Name) exited, code: $exitCode" - throw "Child process exited, shutting down Rust local stack." - } - } - - Start-Sleep -Seconds 1 - } -} -catch { - if ($exitCode -eq 0) { - $exitCode = 1 - } - - Write-Host "[dev:rust] $($_.Exception.Message)" -} -finally { - Stop-StackProcesses -Processes $stackProcesses -} - -exit $exitCode diff --git a/scripts/dev-rust-stack.sh b/scripts/dev-rust-stack.sh index 591c910d..64178ad1 100644 --- a/scripts/dev-rust-stack.sh +++ b/scripts/dev-rust-stack.sh @@ -5,15 +5,17 @@ set -euo pipefail usage() { cat <<'EOF' 用法: - npm run dev:rust:sh + npm run dev:rust ./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 ./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish ./scripts/dev-rust-stack.sh --clear-database + npm run dev:rust:logs -- --follow 说明: 1. 默认同时启动 SpacetimeDB standalone、Rust api-server 与 Vite 前端。 2. 默认会 publish server-rs/crates/spacetime-module,但不会清空数据库。 3. 只有显式传入 --clear-database 时,才会追加 spacetime publish --clear-database。 + 4. SpacetimeDB 默认使用 server-rs/.spacetimedb/local 作为本地数据与日志目录。 EOF } @@ -236,6 +238,7 @@ echo "[dev:rust] web: http://127.0.0.1:${WEB_PORT}" echo "[dev:rust] rust api: ${RUST_SERVER_TARGET}" echo "[dev:rust] spacetime: ${SPACETIME_SERVER}" echo "[dev:rust] database: ${DATABASE}" +echo "[dev:rust] spacetime root: ${SPACETIME_ROOT_DIR}" if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then mkdir -p "${SPACETIME_ROOT_DIR}" @@ -243,6 +246,7 @@ if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then ( cd "${SERVER_RS_DIR}" exec spacetime \ + --root-dir="${SPACETIME_ROOT_DIR}" \ start \ --edition standalone \ --listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}"