mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 17:01:57 -06:00
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:
74
Borealis.ps1
74
Borealis.ps1
@@ -144,6 +144,67 @@ function Write-AgentLog {
|
||||
"[$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
|
||||
|
||||
function Ensure-SystemUtf8CodePage {
|
||||
@@ -1026,7 +1087,7 @@ switch ($choice) {
|
||||
$venvFolder = "Server"
|
||||
$dataSource = "Data"
|
||||
$dataDestination = "$venvFolder\Borealis"
|
||||
$customUIPath = "$dataSource\Server\WebUI"
|
||||
$customUIPath = "$dataSource\Engine\web-interface"
|
||||
$webUIDestination = "$venvFolder\web-interface"
|
||||
$venvPython = Join-Path $venvFolder 'Scripts\python.exe'
|
||||
|
||||
@@ -1076,12 +1137,14 @@ switch ($choice) {
|
||||
}
|
||||
|
||||
Run-Step "Copy Borealis WebUI Files into: $webUIDestination" {
|
||||
Ensure-EngineWebInterface -ProjectRoot $scriptDir
|
||||
if (Test-Path $webUIDestination) {
|
||||
Remove-Item "$webUIDestination\public\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$webUIDestination\src\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1095,6 +1158,8 @@ switch ($choice) {
|
||||
Run-Step "Vite Web Frontend: Start ($borealis_operation_mode)" {
|
||||
Push-Location $webUIDestination
|
||||
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)
|
||||
Pop-Location
|
||||
}
|
||||
@@ -1273,7 +1338,7 @@ switch ($choice) {
|
||||
$dataSource = "Data"
|
||||
$engineSource = "$dataSource\Engine"
|
||||
$engineDataDestination = "$venvFolder\Data\Engine"
|
||||
$webUIFallbackSource = "$dataSource\Server\WebUI"
|
||||
$webUIFallbackSource = "$dataSource\Server\web-interface"
|
||||
$webUIDestination = "$venvFolder\web-interface"
|
||||
$venvPython = Join-Path $venvFolder 'Scripts\python.exe'
|
||||
$engineSourceAbsolute = Join-Path $scriptDir $engineSource
|
||||
@@ -1315,7 +1380,8 @@ switch ($choice) {
|
||||
}
|
||||
|
||||
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) {
|
||||
$webUIDestinationAbsolute = Join-Path $scriptDir $webUIDestination
|
||||
if (Test-Path $webUIDestinationAbsolute) {
|
||||
@@ -1356,6 +1422,8 @@ switch ($choice) {
|
||||
if (Test-Path $webUIDestinationAbsolute) {
|
||||
Push-Location $webUIDestinationAbsolute
|
||||
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)
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
39
Borealis.sh
39
Borealis.sh
@@ -296,9 +296,45 @@ copy_server_payload() {
|
||||
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() {
|
||||
local customUIPath="Data/Server/WebUI"
|
||||
local customUIPath="Data/Engine/web-interface"
|
||||
local webUIDestination="Server/web-interface"
|
||||
ensure_engine_webui_source || return 1
|
||||
mkdir -p "$webUIDestination"
|
||||
rm -rf "${webUIDestination}/public" "${webUIDestination}/src" "${webUIDestination}/build" 2>/dev/null || true
|
||||
cp -r "${customUIPath}/"* "$webUIDestination/"
|
||||
@@ -311,6 +347,7 @@ vite_start() {
|
||||
local webUIDestination="Server/web-interface"
|
||||
local subcmd="build"; [[ "$mode" == "developer" ]] && subcmd="dev"
|
||||
ensure_node_bins
|
||||
ensure_engine_tls_material
|
||||
( cd "$webUIDestination" && nohup "$NPM_BIN" run "$subcmd" >/dev/null 2>&1 & )
|
||||
}
|
||||
|
||||
|
||||
@@ -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_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_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_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` |
|
||||
@@ -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_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 Engine’s 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
|
||||
|
||||
`Data/Engine/services/crypto/certificates.py` mirrors the legacy certificate
|
||||
|
||||
@@ -120,16 +120,14 @@ def _resolve_static_root(project_root: Path) -> Path:
|
||||
project_root / "Engine" / "web-interface" / "build",
|
||||
project_root / "Engine" / "web-interface" / "dist",
|
||||
project_root / "Engine" / "web-interface",
|
||||
project_root / "Data" / "Engine" / "WebUI" / "build",
|
||||
project_root / "Data" / "Engine" / "WebUI",
|
||||
project_root / "Data" / "Engine" / "web-interface" / "build",
|
||||
project_root / "Data" / "Engine" / "web-interface",
|
||||
project_root / "Server" / "web-interface" / "build",
|
||||
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",
|
||||
project_root / "Data" / "Server" / "web-interface" / "build",
|
||||
project_root / "Data" / "Server" / "web-interface",
|
||||
project_root / "Data" / "WebUI" / "build",
|
||||
project_root / "Data" / "WebUI",
|
||||
)
|
||||
|
||||
@@ -46,27 +46,27 @@ def test_static_root_env_override(tmp_path, monkeypatch):
|
||||
monkeypatch.delenv("BOREALIS_ROOT", raising=False)
|
||||
|
||||
|
||||
def test_static_root_falls_back_to_legacy_source(tmp_path, monkeypatch):
|
||||
"""Legacy WebUI source should be served when no build assets exist."""
|
||||
def test_static_root_falls_back_to_engine_source(tmp_path, monkeypatch):
|
||||
"""Engine data assets should serve when no build output exists."""
|
||||
|
||||
legacy_source = tmp_path / "Data" / "Server" / "WebUI"
|
||||
legacy_source.mkdir(parents=True)
|
||||
(legacy_source / "index.html").write_text("<html></html>", encoding="utf-8")
|
||||
engine_source = tmp_path / "Data" / "Engine" / "web-interface"
|
||||
engine_source.mkdir(parents=True)
|
||||
(engine_source / "index.html").write_text("<html></html>", 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()
|
||||
assert settings.flask.static_root == engine_source.resolve()
|
||||
|
||||
monkeypatch.delenv("BOREALIS_ROOT", raising=False)
|
||||
|
||||
|
||||
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 / "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()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
3
Data/Engine/web-interface/.gitignore
vendored
Normal file
3
Data/Engine/web-interface/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
10
Data/Engine/web-interface/README.md
Normal file
10
Data/Engine/web-interface/README.md
Normal 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.
|
||||
@@ -657,8 +657,8 @@ from job_scheduler import set_credential_fetcher as scheduler_set_credential_fet
|
||||
|
||||
_STATIC_CANDIDATES = [
|
||||
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__), '../WebUI/build'),
|
||||
os.path.join(os.path.dirname(__file__), '../web-interface/dist'),
|
||||
os.path.join(os.path.dirname(__file__), '../web-interface'),
|
||||
]
|
||||
|
||||
_resolved_static_folder = None
|
||||
|
||||
Reference in New Issue
Block a user