修改启动脚本

This commit is contained in:
2026-04-24 13:30:11 +08:00
parent 49a79aee54
commit ef53028be5
4 changed files with 31 additions and 365 deletions

View File

@@ -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 <database>` 为唯一提取入口,脚本不直接读取内部日志文件结构。
2. 默认读取 `spacetime.local.json``database` 字段,默认 server 为 `http://127.0.0.1:3101`
3. 默认输出到 `logs/spacetime/<database>-<timestamp>.log`,并通过 `tee` 同步显示在终端。
4. `--follow` 仅用于本地追踪,会持续追加到同一个输出文件;停止时用 `Ctrl+C`
联调排错补充:
1. 如果首页公开广场出现 `上游服务请求失败`,优先检查 `api-server` 错误详情里的 `ws://.../v1/database/<database>/subscribe` 是否指向了未发布的库。

View File

@@ -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",

View File

@@ -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

View File

@@ -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}"