Merge pull request #140 from bunny-lab-io:codex/review-engine-based-system-files

Stage Engine web UI from legacy assets
This commit is contained in:
2025-10-25 16:13:17 -06:00
committed by GitHub
8 changed files with 158 additions and 21 deletions

View File

@@ -144,6 +144,67 @@ function Write-AgentLog {
"[$ts] $Message" | Out-File -FilePath $path -Append -Encoding UTF8 "[$ts] $Message" | Out-File -FilePath $path -Append -Encoding UTF8
} }
function Ensure-EngineTlsMaterial {
param(
[string]$PythonPath,
[string]$CertificateRoot
)
if (-not (Test-Path $CertificateRoot)) {
New-Item -Path $CertificateRoot -ItemType Directory -Force | Out-Null
}
if (Test-Path $PythonPath) {
$code = @'
from Data.Engine.services.crypto import certificates
certificates.ensure_certificate()
'@
try {
& $PythonPath -c $code | Out-Null
} catch {
Write-Host "Failed to pre-generate Engine TLS certificates: $($_.Exception.Message)" -ForegroundColor Yellow
}
}
$env:BOREALIS_CERT_DIR = $CertificateRoot
$env:BOREALIS_TLS_CERT = Join-Path $CertificateRoot 'borealis-server-cert.pem'
$env:BOREALIS_TLS_KEY = Join-Path $CertificateRoot 'borealis-server-key.pem'
$env:BOREALIS_TLS_BUNDLE = Join-Path $CertificateRoot 'borealis-server-bundle.pem'
}
function Ensure-EngineWebInterface {
param(
[string]$ProjectRoot
)
$engineSource = Join-Path $ProjectRoot 'Data\Engine\web-interface'
$legacySource = Join-Path $ProjectRoot 'Data\Server\WebUI'
if (-not (Test-Path $legacySource)) {
return
}
$enginePackage = Join-Path $engineSource 'package.json'
$engineSrcDir = Join-Path $engineSource 'src'
if ((Test-Path $enginePackage) -and (Test-Path $engineSrcDir)) {
return
}
if (-not (Test-Path $engineSource)) {
New-Item -Path $engineSource -ItemType Directory -Force | Out-Null
}
$preserve = @('.gitignore','README.md')
Get-ChildItem -Path $engineSource -Force | Where-Object { $preserve -notcontains $_.Name } |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item (Join-Path $legacySource '*') $engineSource -Recurse -Force
if (-not (Test-Path $enginePackage)) {
throw "Failed to stage Engine web interface into '$engineSource'."
}
}
$script:Utf8CodePageChanged = $false $script:Utf8CodePageChanged = $false
function Ensure-SystemUtf8CodePage { function Ensure-SystemUtf8CodePage {
@@ -1026,7 +1087,7 @@ switch ($choice) {
$venvFolder = "Server" $venvFolder = "Server"
$dataSource = "Data" $dataSource = "Data"
$dataDestination = "$venvFolder\Borealis" $dataDestination = "$venvFolder\Borealis"
$customUIPath = "$dataSource\Server\WebUI" $customUIPath = "$dataSource\Engine\web-interface"
$webUIDestination = "$venvFolder\web-interface" $webUIDestination = "$venvFolder\web-interface"
$venvPython = Join-Path $venvFolder 'Scripts\python.exe' $venvPython = Join-Path $venvFolder 'Scripts\python.exe'
@@ -1076,12 +1137,14 @@ switch ($choice) {
} }
Run-Step "Copy Borealis WebUI Files into: $webUIDestination" { Run-Step "Copy Borealis WebUI Files into: $webUIDestination" {
Ensure-EngineWebInterface -ProjectRoot $scriptDir
if (Test-Path $webUIDestination) { if (Test-Path $webUIDestination) {
Remove-Item "$webUIDestination\public\*" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$webUIDestination\public\*" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$webUIDestination\src\*" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$webUIDestination\src\*" -Recurse -Force -ErrorAction SilentlyContinue
} else { } else {
New-Item -Path $webUIDestination -ItemType Directory -Force | Out-Null New-Item -Path $webUIDestination -ItemType Directory -Force | Out-Null
} }
if (-not (Test-Path $customUIPath)) { throw "Web interface source '$customUIPath' not found." }
Copy-Item "$customUIPath\*" $webUIDestination -Recurse -Force Copy-Item "$customUIPath\*" $webUIDestination -Recurse -Force
} }
@@ -1095,6 +1158,8 @@ switch ($choice) {
Run-Step "Vite Web Frontend: Start ($borealis_operation_mode)" { Run-Step "Vite Web Frontend: Start ($borealis_operation_mode)" {
Push-Location $webUIDestination Push-Location $webUIDestination
if ($borealis_operation_mode -eq "developer") { $viteSubCommand = "dev" } else { $viteSubCommand = "build" } if ($borealis_operation_mode -eq "developer") { $viteSubCommand = "dev" } else { $viteSubCommand = "build" }
$certRoot = Join-Path $scriptDir 'Certificates\Server'
Ensure-EngineTlsMaterial -PythonPath $venvPython -CertificateRoot $certRoot
Start-Process -NoNewWindow -FilePath $npmCmd -ArgumentList @("run",$viteSubCommand) Start-Process -NoNewWindow -FilePath $npmCmd -ArgumentList @("run",$viteSubCommand)
Pop-Location Pop-Location
} }
@@ -1273,7 +1338,7 @@ switch ($choice) {
$dataSource = "Data" $dataSource = "Data"
$engineSource = "$dataSource\Engine" $engineSource = "$dataSource\Engine"
$engineDataDestination = "$venvFolder\Data\Engine" $engineDataDestination = "$venvFolder\Data\Engine"
$webUIFallbackSource = "$dataSource\Server\WebUI" $webUIFallbackSource = "$dataSource\Server\web-interface"
$webUIDestination = "$venvFolder\web-interface" $webUIDestination = "$venvFolder\web-interface"
$venvPython = Join-Path $venvFolder 'Scripts\python.exe' $venvPython = Join-Path $venvFolder 'Scripts\python.exe'
$engineSourceAbsolute = Join-Path $scriptDir $engineSource $engineSourceAbsolute = Join-Path $scriptDir $engineSource
@@ -1315,7 +1380,8 @@ switch ($choice) {
} }
Run-Step "Copy Borealis Engine WebUI Files into: $webUIDestination" { Run-Step "Copy Borealis Engine WebUI Files into: $webUIDestination" {
$engineWebUISource = Join-Path $engineSourceAbsolute 'WebUI' Ensure-EngineWebInterface -ProjectRoot $scriptDir
$engineWebUISource = Join-Path $engineSourceAbsolute 'web-interface'
if (Test-Path $engineWebUISource) { if (Test-Path $engineWebUISource) {
$webUIDestinationAbsolute = Join-Path $scriptDir $webUIDestination $webUIDestinationAbsolute = Join-Path $scriptDir $webUIDestination
if (Test-Path $webUIDestinationAbsolute) { if (Test-Path $webUIDestinationAbsolute) {
@@ -1356,6 +1422,8 @@ switch ($choice) {
if (Test-Path $webUIDestinationAbsolute) { if (Test-Path $webUIDestinationAbsolute) {
Push-Location $webUIDestinationAbsolute Push-Location $webUIDestinationAbsolute
if ($engineOperationMode -eq "developer") { $viteSubCommand = "dev" } else { $viteSubCommand = "build" } if ($engineOperationMode -eq "developer") { $viteSubCommand = "dev" } else { $viteSubCommand = "build" }
$certRoot = Join-Path $scriptDir 'Certificates\Server'
Ensure-EngineTlsMaterial -PythonPath $venvPython -CertificateRoot $certRoot
Start-Process -NoNewWindow -FilePath $npmCmd -ArgumentList @("run", $viteSubCommand) Start-Process -NoNewWindow -FilePath $npmCmd -ArgumentList @("run", $viteSubCommand)
Pop-Location Pop-Location
} }

View File

@@ -296,9 +296,45 @@ copy_server_payload() {
fi fi
} }
ensure_engine_tls_material() {
local python_bin="${SCRIPT_DIR}/Server/bin/python3"
local cert_dir="${SCRIPT_DIR}/Certificates/Server"
mkdir -p "$cert_dir"
if [[ -x "$python_bin" ]]; then
"$python_bin" - <<'PY'
from Data.Engine.services.crypto import certificates
certificates.ensure_certificate()
PY
fi
export BOREALIS_CERT_DIR="$cert_dir"
export BOREALIS_TLS_CERT="${cert_dir}/borealis-server-cert.pem"
export BOREALIS_TLS_KEY="${cert_dir}/borealis-server-key.pem"
}
ensure_engine_webui_source() {
local engineSource="Data/Engine/web-interface"
local legacySource="Data/Server/WebUI"
if [[ -d "${engineSource}/src" && -f "${engineSource}/package.json" ]]; then
return 0
fi
if [[ ! -d "$legacySource" ]]; then
echo "${RED}Legacy WebUI source '$legacySource' not found.${RESET}" >&2
return 1
fi
mkdir -p "$engineSource"
find "$engineSource" -mindepth 1 -maxdepth 1 \
! -name '.gitignore' ! -name 'README.md' -exec rm -rf {} +
cp -a "$legacySource/." "$engineSource/"
if [[ ! -f "${engineSource}/package.json" ]]; then
echo "${RED}Failed to stage Engine web interface into '$engineSource'.${RESET}" >&2
return 1
fi
}
prepare_webui() { prepare_webui() {
local customUIPath="Data/Server/WebUI" local customUIPath="Data/Engine/web-interface"
local webUIDestination="Server/web-interface" local webUIDestination="Server/web-interface"
ensure_engine_webui_source || return 1
mkdir -p "$webUIDestination" mkdir -p "$webUIDestination"
rm -rf "${webUIDestination}/public" "${webUIDestination}/src" "${webUIDestination}/build" 2>/dev/null || true rm -rf "${webUIDestination}/public" "${webUIDestination}/src" "${webUIDestination}/build" 2>/dev/null || true
cp -r "${customUIPath}/"* "$webUIDestination/" cp -r "${customUIPath}/"* "$webUIDestination/"
@@ -311,6 +347,7 @@ vite_start() {
local webUIDestination="Server/web-interface" local webUIDestination="Server/web-interface"
local subcmd="build"; [[ "$mode" == "developer" ]] && subcmd="dev" local subcmd="build"; [[ "$mode" == "developer" ]] && subcmd="dev"
ensure_node_bins ensure_node_bins
ensure_engine_tls_material
( cd "$webUIDestination" && nohup "$NPM_BIN" run "$subcmd" >/dev/null 2>&1 & ) ( cd "$webUIDestination" && nohup "$NPM_BIN" run "$subcmd" >/dev/null 2>&1 & )
} }

View File

@@ -42,7 +42,7 @@ The Engine mirrors the legacy defaults so it can boot without additional configu
| `BOREALIS_ROOT` | Overrides automatic project root detection. Useful when running from a packaged location. | Directory two levels above `Data/Engine/` | | `BOREALIS_ROOT` | Overrides automatic project root detection. Useful when running from a packaged location. | Directory two levels above `Data/Engine/` |
| `BOREALIS_DATABASE_PATH` | Path to the SQLite database. | `<project_root>/database.db` | | `BOREALIS_DATABASE_PATH` | Path to the SQLite database. | `<project_root>/database.db` |
| `BOREALIS_ENGINE_AUTO_MIGRATE` | Run Engine-managed schema migrations during bootstrap (`true`/`false`). | `true` | | `BOREALIS_ENGINE_AUTO_MIGRATE` | Run Engine-managed schema migrations during bootstrap (`true`/`false`). | `true` |
| `BOREALIS_STATIC_ROOT` | Directory that serves static assets for the SPA. | First existing path among `Engine/web-interface/build`, `Engine/web-interface/dist`, `Data/Engine/WebUI/build`, `Data/Server/web-interface/build`, `Data/Server/WebUI/build`, `Data/WebUI/build` | | `BOREALIS_STATIC_ROOT` | Directory that serves static assets for the SPA. | First existing path among `Engine/web-interface/build`, `Engine/web-interface/dist`, `Data/Engine/web-interface/build`, `Data/Server/WebUI/build`, `Data/Server/web-interface/build`, `Data/WebUI/build` |
| `BOREALIS_CORS_ALLOWED_ORIGINS` | Comma-delimited list of origins granted CORS access. Use `*` for all origins. | `*` | | `BOREALIS_CORS_ALLOWED_ORIGINS` | Comma-delimited list of origins granted CORS access. Use `*` for all origins. | `*` |
| `BOREALIS_FLASK_SECRET_KEY` | Secret key for Flask session signing. | `change-me` | | `BOREALIS_FLASK_SECRET_KEY` | Secret key for Flask session signing. | `change-me` |
| `BOREALIS_DEBUG` | Enables debug logging, disables secure-cookie requirements, and allows Werkzeug debug mode. | `false` | | `BOREALIS_DEBUG` | Enables debug logging, disables secure-cookie requirements, and allows Werkzeug debug mode. | `false` |
@@ -55,6 +55,12 @@ The Engine mirrors the legacy defaults so it can boot without additional configu
| `BOREALIS_CERTIFICATES_ROOT` | Overrides where TLS certificates (root CA + leaf) are stored. | `<project_root>/Certificates` | | `BOREALIS_CERTIFICATES_ROOT` | Overrides where TLS certificates (root CA + leaf) are stored. | `<project_root>/Certificates` |
| `BOREALIS_SERVER_CERT_ROOT` | Directly points to the Engine server certificate directory if certificates are staged elsewhere. | `<project_root>/Certificates/Server` | | `BOREALIS_SERVER_CERT_ROOT` | Directly points to the Engine server certificate directory if certificates are staged elsewhere. | `<project_root>/Certificates/Server` |
The launch scripts (`Borealis.ps1` / `Borealis.sh`) automatically synchronize
`Data/Server/WebUI` into `Data/Engine/web-interface` when the Engines copy is
missing. The repository keeps that directory mostly empty (except for
documentation) so Git history does not duplicate the large SPA payload, but the
runtime staging still ensures Vite reads from the Engine tree.
## TLS and transport stack ## TLS and transport stack
`Data/Engine/services/crypto/certificates.py` mirrors the legacy certificate `Data/Engine/services/crypto/certificates.py` mirrors the legacy certificate

View File

@@ -120,16 +120,14 @@ def _resolve_static_root(project_root: Path) -> Path:
project_root / "Engine" / "web-interface" / "build", project_root / "Engine" / "web-interface" / "build",
project_root / "Engine" / "web-interface" / "dist", project_root / "Engine" / "web-interface" / "dist",
project_root / "Engine" / "web-interface", project_root / "Engine" / "web-interface",
project_root / "Data" / "Engine" / "WebUI" / "build", project_root / "Data" / "Engine" / "web-interface" / "build",
project_root / "Data" / "Engine" / "WebUI", project_root / "Data" / "Engine" / "web-interface",
project_root / "Server" / "web-interface" / "build", project_root / "Server" / "web-interface" / "build",
project_root / "Server" / "web-interface", project_root / "Server" / "web-interface",
project_root / "Server" / "WebUI" / "build",
project_root / "Server" / "WebUI",
project_root / "Data" / "Server" / "web-interface" / "build",
project_root / "Data" / "Server" / "web-interface",
project_root / "Data" / "Server" / "WebUI" / "build", project_root / "Data" / "Server" / "WebUI" / "build",
project_root / "Data" / "Server" / "WebUI", project_root / "Data" / "Server" / "WebUI",
project_root / "Data" / "Server" / "web-interface" / "build",
project_root / "Data" / "Server" / "web-interface",
project_root / "Data" / "WebUI" / "build", project_root / "Data" / "WebUI" / "build",
project_root / "Data" / "WebUI", project_root / "Data" / "WebUI",
) )

View File

@@ -46,27 +46,27 @@ def test_static_root_env_override(tmp_path, monkeypatch):
monkeypatch.delenv("BOREALIS_ROOT", raising=False) monkeypatch.delenv("BOREALIS_ROOT", raising=False)
def test_static_root_falls_back_to_legacy_source(tmp_path, monkeypatch): def test_static_root_falls_back_to_engine_source(tmp_path, monkeypatch):
"""Legacy WebUI source should be served when no build assets exist.""" """Engine data assets should serve when no build output exists."""
legacy_source = tmp_path / "Data" / "Server" / "WebUI" engine_source = tmp_path / "Data" / "Engine" / "web-interface"
legacy_source.mkdir(parents=True) engine_source.mkdir(parents=True)
(legacy_source / "index.html").write_text("<html></html>", encoding="utf-8") (engine_source / "index.html").write_text("<html></html>", encoding="utf-8")
monkeypatch.setenv("BOREALIS_ROOT", str(tmp_path)) monkeypatch.setenv("BOREALIS_ROOT", str(tmp_path))
monkeypatch.delenv("BOREALIS_STATIC_ROOT", raising=False) monkeypatch.delenv("BOREALIS_STATIC_ROOT", raising=False)
settings = load_environment() settings = load_environment()
assert settings.flask.static_root == legacy_source.resolve() assert settings.flask.static_root == engine_source.resolve()
monkeypatch.delenv("BOREALIS_ROOT", raising=False) monkeypatch.delenv("BOREALIS_ROOT", raising=False)
def test_static_root_considers_runtime_copy(tmp_path, monkeypatch): def test_static_root_considers_runtime_copy(tmp_path, monkeypatch):
"""Runtime Server/WebUI copies should be considered when Data assets are missing.""" """Runtime Server/web-interface copies should be considered when Data assets are missing."""
runtime_source = tmp_path / "Server" / "WebUI" runtime_source = tmp_path / "Server" / "web-interface"
runtime_source.mkdir(parents=True) runtime_source.mkdir(parents=True)
(runtime_source / "index.html").write_text("runtime", encoding="utf-8") (runtime_source / "index.html").write_text("runtime", encoding="utf-8")
@@ -76,7 +76,22 @@ def test_static_root_considers_runtime_copy(tmp_path, monkeypatch):
settings = load_environment() settings = load_environment()
assert settings.flask.static_root == runtime_source.resolve() assert settings.flask.static_root == runtime_source.resolve()
monkeypatch.delenv("BOREALIS_ROOT", raising=False)
def test_static_root_falls_back_to_legacy_assets(tmp_path, monkeypatch):
"""Legacy Data/Server/WebUI assets remain a valid fallback."""
legacy_source = tmp_path / "Data" / "Server" / "WebUI"
legacy_source.mkdir(parents=True)
(legacy_source / "index.html").write_text("legacy", encoding="utf-8")
monkeypatch.setenv("BOREALIS_ROOT", str(tmp_path))
monkeypatch.delenv("BOREALIS_STATIC_ROOT", raising=False)
settings = load_environment()
assert settings.flask.static_root == legacy_source.resolve()
monkeypatch.delenv("BOREALIS_ROOT", raising=False) monkeypatch.delenv("BOREALIS_ROOT", raising=False)

3
Data/Engine/web-interface/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*
!.gitignore
!README.md

View File

@@ -0,0 +1,10 @@
# Engine Web Interface Staging
The Engine server reuses the existing Vite single-page application that still lives under
`Data/Server/WebUI`. At runtime the launch scripts copy those assets into this directory so the
Engine can stage its own copy without disturbing the legacy server.
The repository intentionally ignores the staged files to avoid duplicating tens of thousands of
lines (and large binary assets) in source control. If you need to refresh the Engine copy, run one
of the launch scripts (`Borealis.ps1` or `Borealis.sh`) or copy the assets manually from
`Data/Server/WebUI` into this folder.

View File

@@ -657,8 +657,8 @@ from job_scheduler import set_credential_fetcher as scheduler_set_credential_fet
_STATIC_CANDIDATES = [ _STATIC_CANDIDATES = [
os.path.join(os.path.dirname(__file__), '../web-interface/build'), os.path.join(os.path.dirname(__file__), '../web-interface/build'),
os.path.join(os.path.dirname(__file__), 'WebUI/build'), os.path.join(os.path.dirname(__file__), '../web-interface/dist'),
os.path.join(os.path.dirname(__file__), '../WebUI/build'), os.path.join(os.path.dirname(__file__), '../web-interface'),
] ]
_resolved_static_folder = None _resolved_static_folder = None