mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-12-15 23:25:48 -07:00
Fixed Launch Script / Fixed Site List / Fixed Database Behavior
This commit is contained in:
87
Borealis.ps1
87
Borealis.ps1
@@ -1160,8 +1160,95 @@ switch ($choice) {
|
|||||||
|
|
||||||
Run-Step "Create Borealis Engine Virtual Python Environment" {
|
Run-Step "Create Borealis Engine Virtual Python Environment" {
|
||||||
$venvActivate = Join-Path $venvFolder 'Scripts\Activate'
|
$venvActivate = Join-Path $venvFolder 'Scripts\Activate'
|
||||||
|
$pyvenvCfg = Join-Path $venvFolder 'pyvenv.cfg'
|
||||||
|
$expectedPython = $pythonExe
|
||||||
|
$expectedPythonNorm = $null
|
||||||
|
$expectedHomeNorm = $null
|
||||||
|
try {
|
||||||
|
if (Test-Path $pythonExe -PathType Leaf) {
|
||||||
|
$expectedPython = (Resolve-Path $pythonExe -ErrorAction Stop).ProviderPath
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$expectedPython = $pythonExe
|
||||||
|
}
|
||||||
|
if ($expectedPython) {
|
||||||
|
$expectedPythonNorm = $expectedPython.ToLowerInvariant()
|
||||||
|
try {
|
||||||
|
$expectedHome = Split-Path -Path $expectedPython -Parent
|
||||||
|
} catch {
|
||||||
|
$expectedHome = $null
|
||||||
|
}
|
||||||
|
if ($expectedHome) {
|
||||||
|
$expectedHomeNorm = $expectedHome.ToLowerInvariant()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$venvNeedsUpgrade = $false
|
||||||
|
if (Test-Path $pyvenvCfg -PathType Leaf) {
|
||||||
|
try {
|
||||||
|
$cfgLines = Get-Content -Path $pyvenvCfg -ErrorAction Stop
|
||||||
|
$cfgMap = @{}
|
||||||
|
foreach ($line in $cfgLines) {
|
||||||
|
$trimmed = $line.Trim()
|
||||||
|
if (-not $trimmed -or $trimmed.StartsWith('#')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$parts = $trimmed -split '=', 2
|
||||||
|
if ($parts.Count -ne 2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$cfgMap[$parts[0].Trim().ToLowerInvariant()] = $parts[1].Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
$cfgExecutable = $cfgMap['executable']
|
||||||
|
$cfgHome = $cfgMap['home']
|
||||||
|
|
||||||
|
if ($cfgExecutable -and -not (Test-Path $cfgExecutable -PathType Leaf)) {
|
||||||
|
$venvNeedsUpgrade = $true
|
||||||
|
} elseif ($cfgHome -and -not (Test-Path $cfgHome -PathType Container)) {
|
||||||
|
$venvNeedsUpgrade = $true
|
||||||
|
} else {
|
||||||
|
if ($cfgExecutable -and $expectedPythonNorm) {
|
||||||
|
try {
|
||||||
|
$resolvedExe = (Resolve-Path $cfgExecutable -ErrorAction Stop).ProviderPath
|
||||||
|
} catch {
|
||||||
|
$resolvedExe = $cfgExecutable
|
||||||
|
}
|
||||||
|
if ($resolvedExe) {
|
||||||
|
$resolvedExeNorm = $resolvedExe.ToLowerInvariant()
|
||||||
|
} else {
|
||||||
|
$resolvedExeNorm = $null
|
||||||
|
}
|
||||||
|
if ($resolvedExeNorm -and $resolvedExeNorm -ne $expectedPythonNorm) {
|
||||||
|
$venvNeedsUpgrade = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $venvNeedsUpgrade -and $cfgHome -and $expectedHomeNorm) {
|
||||||
|
try {
|
||||||
|
$resolvedHome = (Resolve-Path $cfgHome -ErrorAction Stop).ProviderPath
|
||||||
|
} catch {
|
||||||
|
$resolvedHome = $cfgHome
|
||||||
|
}
|
||||||
|
if ($resolvedHome) {
|
||||||
|
$resolvedHomeNorm = $resolvedHome.ToLowerInvariant()
|
||||||
|
} else {
|
||||||
|
$resolvedHomeNorm = $null
|
||||||
|
}
|
||||||
|
if ($resolvedHomeNorm -and $resolvedHomeNorm -ne $expectedHomeNorm) {
|
||||||
|
$venvNeedsUpgrade = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$venvNeedsUpgrade = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (-not (Test-Path $venvActivate)) {
|
if (-not (Test-Path $venvActivate)) {
|
||||||
& $pythonExe -m venv $venvFolder | Out-Null
|
& $pythonExe -m venv $venvFolder | Out-Null
|
||||||
|
} elseif ($venvNeedsUpgrade) {
|
||||||
|
Write-Host "Detected relocated Engine virtual environment. Rebuilding interpreter bindings..." -ForegroundColor Yellow
|
||||||
|
& $pythonExe -m venv --upgrade $venvFolder | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
$engineDataRoot = Join-Path $venvFolder 'Data'
|
$engineDataRoot = Join-Path $venvFolder 'Data'
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -95,13 +95,13 @@ class AssemblyDatabaseManager:
|
|||||||
"""Ensure all databases exist, apply schema, and mirror to the runtime directory."""
|
"""Ensure all databases exist, apply schema, and mirror to the runtime directory."""
|
||||||
|
|
||||||
for domain in AssemblyDomain:
|
for domain in AssemblyDomain:
|
||||||
|
self._ensure_runtime_database(domain)
|
||||||
conn = self._open_connection(domain)
|
conn = self._open_connection(domain)
|
||||||
try:
|
try:
|
||||||
self._apply_schema(conn)
|
self._apply_schema(conn)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
self._mirror_database(domain)
|
|
||||||
|
|
||||||
def reset_domain(self, domain: AssemblyDomain) -> None:
|
def reset_domain(self, domain: AssemblyDomain) -> None:
|
||||||
"""Remove all assemblies and payload metadata for the specified domain."""
|
"""Remove all assemblies and payload metadata for the specified domain."""
|
||||||
@@ -284,15 +284,43 @@ class AssemblyDatabaseManager:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
self._mirror_database(domain)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Internal helpers
|
# Internal helpers
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
def _ensure_runtime_database(self, domain: AssemblyDomain) -> None:
|
||||||
|
"""Ensure the runtime database exists by copying the staging asset when needed."""
|
||||||
|
|
||||||
|
paths = self._paths[domain]
|
||||||
|
runtime_db = paths.runtime
|
||||||
|
staging_db = paths.staging
|
||||||
|
runtime_db.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if runtime_db.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
if staging_db.exists():
|
||||||
|
for suffix in ("", "-wal", "-shm"):
|
||||||
|
staging_candidate = staging_db.parent / f"{staging_db.name}{suffix}"
|
||||||
|
runtime_candidate = runtime_db.parent / f"{runtime_db.name}{suffix}"
|
||||||
|
if staging_candidate.exists():
|
||||||
|
try:
|
||||||
|
shutil.copy2(staging_candidate, runtime_candidate)
|
||||||
|
except Exception as exc: # pragma: no cover - best effort seed
|
||||||
|
self._logger.debug(
|
||||||
|
"Failed to seed runtime assembly database file %s -> %s: %s",
|
||||||
|
staging_candidate,
|
||||||
|
runtime_candidate,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
runtime_db.touch()
|
||||||
|
|
||||||
def _open_connection(self, domain: AssemblyDomain, *, readonly: bool = False) -> sqlite3.Connection:
|
def _open_connection(self, domain: AssemblyDomain, *, readonly: bool = False) -> sqlite3.Connection:
|
||||||
|
self._ensure_runtime_database(domain)
|
||||||
paths = self._paths[domain]
|
paths = self._paths[domain]
|
||||||
flags = "ro" if readonly else "rwc"
|
flags = "ro" if readonly else "rwc"
|
||||||
uri = f"file:{paths.staging.as_posix()}?mode={flags}&cache=shared"
|
uri = f"file:{paths.runtime.as_posix()}?mode={flags}&cache=shared"
|
||||||
conn = sqlite3.connect(uri, uri=True, timeout=30)
|
conn = sqlite3.connect(uri, uri=True, timeout=30)
|
||||||
if readonly:
|
if readonly:
|
||||||
conn.isolation_level = None
|
conn.isolation_level = None
|
||||||
@@ -313,26 +341,6 @@ class AssemblyDatabaseManager:
|
|||||||
cur.execute(statement)
|
cur.execute(statement)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def _mirror_database(self, domain: AssemblyDomain) -> None:
|
|
||||||
paths = self._paths[domain]
|
|
||||||
staging_db = paths.staging
|
|
||||||
runtime_db = paths.runtime
|
|
||||||
runtime_db.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
for suffix in ("", "-wal", "-shm"):
|
|
||||||
staging_candidate = staging_db.parent / f"{staging_db.name}{suffix}"
|
|
||||||
runtime_candidate = runtime_db.parent / f"{runtime_db.name}{suffix}"
|
|
||||||
if staging_candidate.exists():
|
|
||||||
try:
|
|
||||||
shutil.copy2(staging_candidate, runtime_candidate)
|
|
||||||
except Exception as exc: # pragma: no cover - best effort mirror
|
|
||||||
self._logger.debug(
|
|
||||||
"Failed to mirror assembly database file %s -> %s: %s",
|
|
||||||
staging_candidate,
|
|
||||||
runtime_candidate,
|
|
||||||
exc,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _migrate_legacy_schema(self, cur: sqlite3.Cursor) -> None:
|
def _migrate_legacy_schema(self, cur: sqlite3.Cursor) -> None:
|
||||||
"""Upgrade legacy assembly/payload tables to the consolidated schema."""
|
"""Upgrade legacy assembly/payload tables to the consolidated schema."""
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PayloadManager:
|
|||||||
assembly_guid: Optional[str] = None,
|
assembly_guid: Optional[str] = None,
|
||||||
extension: Optional[str] = None,
|
extension: Optional[str] = None,
|
||||||
) -> PayloadDescriptor:
|
) -> PayloadDescriptor:
|
||||||
"""Persist payload content and mirror it to the runtime directory."""
|
"""Persist payload content inside the runtime directory only."""
|
||||||
|
|
||||||
resolved_guid = self._normalise_guid(assembly_guid or uuid.uuid4().hex)
|
resolved_guid = self._normalise_guid(assembly_guid or uuid.uuid4().hex)
|
||||||
resolved_extension = self._normalise_extension(extension or self._default_extension(payload_type))
|
resolved_extension = self._normalise_extension(extension or self._default_extension(payload_type))
|
||||||
@@ -49,23 +49,15 @@ class PayloadManager:
|
|||||||
data = content.encode("utf-8") if isinstance(content, str) else bytes(content)
|
data = content.encode("utf-8") if isinstance(content, str) else bytes(content)
|
||||||
checksum = hashlib.sha256(data).hexdigest()
|
checksum = hashlib.sha256(data).hexdigest()
|
||||||
|
|
||||||
staging_dir = self._payload_dir(self._staging_root, resolved_guid)
|
|
||||||
runtime_dir = self._payload_dir(self._runtime_root, resolved_guid)
|
runtime_dir = self._payload_dir(self._runtime_root, resolved_guid)
|
||||||
staging_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
runtime_dir.mkdir(parents=True, exist_ok=True)
|
runtime_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
file_name = f"payload{resolved_extension}"
|
file_name = f"payload{resolved_extension}"
|
||||||
staging_path = staging_dir / file_name
|
|
||||||
runtime_path = runtime_dir / file_name
|
runtime_path = runtime_dir / file_name
|
||||||
|
|
||||||
with staging_path.open("wb") as handle:
|
with runtime_path.open("wb") as handle:
|
||||||
handle.write(data)
|
handle.write(data)
|
||||||
|
|
||||||
try:
|
|
||||||
shutil.copy2(staging_path, runtime_path)
|
|
||||||
except Exception as exc: # pragma: no cover - best effort mirror
|
|
||||||
self._logger.debug("Failed to mirror payload %s to runtime copy: %s", resolved_guid, exc)
|
|
||||||
|
|
||||||
descriptor = PayloadDescriptor(
|
descriptor = PayloadDescriptor(
|
||||||
assembly_guid=resolved_guid,
|
assembly_guid=resolved_guid,
|
||||||
payload_type=payload_type,
|
payload_type=payload_type,
|
||||||
@@ -85,35 +77,40 @@ class PayloadManager:
|
|||||||
checksum = hashlib.sha256(data).hexdigest()
|
checksum = hashlib.sha256(data).hexdigest()
|
||||||
now = _dt.datetime.utcnow()
|
now = _dt.datetime.utcnow()
|
||||||
|
|
||||||
staging_path = self._payload_dir(self._staging_root, descriptor.assembly_guid) / descriptor.file_name
|
|
||||||
runtime_path = self._payload_dir(self._runtime_root, descriptor.assembly_guid) / descriptor.file_name
|
runtime_path = self._payload_dir(self._runtime_root, descriptor.assembly_guid) / descriptor.file_name
|
||||||
staging_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
runtime_path.parent.mkdir(parents=True, exist_ok=True)
|
runtime_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with staging_path.open("wb") as handle:
|
with runtime_path.open("wb") as handle:
|
||||||
handle.write(data)
|
handle.write(data)
|
||||||
|
|
||||||
try:
|
|
||||||
shutil.copy2(staging_path, runtime_path)
|
|
||||||
except Exception as exc: # pragma: no cover - best effort mirror
|
|
||||||
self._logger.debug("Failed to mirror payload %s during update: %s", descriptor.assembly_guid, exc)
|
|
||||||
|
|
||||||
descriptor.size_bytes = len(data)
|
descriptor.size_bytes = len(data)
|
||||||
descriptor.checksum = checksum
|
descriptor.checksum = checksum
|
||||||
descriptor.updated_at = now
|
descriptor.updated_at = now
|
||||||
return descriptor
|
return descriptor
|
||||||
|
|
||||||
def read_payload_bytes(self, descriptor: PayloadDescriptor) -> bytes:
|
def read_payload_bytes(self, descriptor: PayloadDescriptor) -> bytes:
|
||||||
"""Retrieve payload content from the staging copy."""
|
"""Retrieve payload content from the runtime copy, falling back to staging if required."""
|
||||||
|
|
||||||
|
runtime_path = self._payload_dir(self._runtime_root, descriptor.assembly_guid) / descriptor.file_name
|
||||||
|
if runtime_path.exists():
|
||||||
|
return runtime_path.read_bytes()
|
||||||
staging_path = self._payload_dir(self._staging_root, descriptor.assembly_guid) / descriptor.file_name
|
staging_path = self._payload_dir(self._staging_root, descriptor.assembly_guid) / descriptor.file_name
|
||||||
return staging_path.read_bytes()
|
if staging_path.exists():
|
||||||
|
data = staging_path.read_bytes()
|
||||||
|
try:
|
||||||
|
shutil.copy2(staging_path, runtime_path)
|
||||||
|
except Exception as exc: # pragma: no cover - best effort seed
|
||||||
|
self._logger.debug("Failed to seed runtime payload %s from staging: %s", descriptor.assembly_guid, exc)
|
||||||
|
return data
|
||||||
|
raise FileNotFoundError(f"Payload content missing for {descriptor.assembly_guid}")
|
||||||
|
|
||||||
def ensure_runtime_copy(self, descriptor: PayloadDescriptor) -> None:
|
def ensure_runtime_copy(self, descriptor: PayloadDescriptor) -> None:
|
||||||
"""Ensure the runtime payload copy matches the staging content."""
|
"""Ensure the runtime payload copy exists."""
|
||||||
|
|
||||||
staging_path = self._payload_dir(self._staging_root, descriptor.assembly_guid) / descriptor.file_name
|
|
||||||
runtime_path = self._payload_dir(self._runtime_root, descriptor.assembly_guid) / descriptor.file_name
|
runtime_path = self._payload_dir(self._runtime_root, descriptor.assembly_guid) / descriptor.file_name
|
||||||
|
if runtime_path.exists():
|
||||||
|
return
|
||||||
|
staging_path = self._payload_dir(self._staging_root, descriptor.assembly_guid) / descriptor.file_name
|
||||||
if not staging_path.exists():
|
if not staging_path.exists():
|
||||||
self._logger.warning("Payload missing on disk; guid=%s path=%s", descriptor.assembly_guid, staging_path)
|
self._logger.warning("Payload missing on disk; guid=%s path=%s", descriptor.assembly_guid, staging_path)
|
||||||
return
|
return
|
||||||
@@ -124,20 +121,19 @@ class PayloadManager:
|
|||||||
self._logger.debug("Failed to mirror payload %s via ensure_runtime_copy: %s", descriptor.assembly_guid, exc)
|
self._logger.debug("Failed to mirror payload %s via ensure_runtime_copy: %s", descriptor.assembly_guid, exc)
|
||||||
|
|
||||||
def delete_payload(self, descriptor: PayloadDescriptor) -> None:
|
def delete_payload(self, descriptor: PayloadDescriptor) -> None:
|
||||||
"""Remove staging and runtime payload files."""
|
"""Remove runtime payload files without mutating staging copies."""
|
||||||
|
|
||||||
for root in (self._staging_root, self._runtime_root):
|
dir_path = self._payload_dir(self._runtime_root, descriptor.assembly_guid)
|
||||||
dir_path = self._payload_dir(root, descriptor.assembly_guid)
|
file_path = dir_path / descriptor.file_name
|
||||||
file_path = dir_path / descriptor.file_name
|
try:
|
||||||
try:
|
if file_path.exists():
|
||||||
if file_path.exists():
|
file_path.unlink()
|
||||||
file_path.unlink()
|
if dir_path.exists() and not any(dir_path.iterdir()):
|
||||||
if dir_path.exists() and not any(dir_path.iterdir()):
|
dir_path.rmdir()
|
||||||
dir_path.rmdir()
|
except Exception as exc: # pragma: no cover - best effort cleanup
|
||||||
except Exception as exc: # pragma: no cover - best effort cleanup
|
self._logger.debug(
|
||||||
self._logger.debug(
|
"Failed to remove runtime payload directory %s: %s", descriptor.assembly_guid, exc
|
||||||
"Failed to remove payload directory %s (%s): %s", descriptor.assembly_guid, root, exc
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Helper methods
|
# Helper methods
|
||||||
|
|||||||
@@ -24,14 +24,15 @@ def test_payload_manager_store_update_delete(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
staging_path = staging_root / "abc123" / descriptor.file_name
|
staging_path = staging_root / "abc123" / descriptor.file_name
|
||||||
runtime_path = runtime_root / "abc123" / descriptor.file_name
|
runtime_path = runtime_root / "abc123" / descriptor.file_name
|
||||||
assert staging_path.is_file()
|
|
||||||
assert runtime_path.is_file()
|
assert runtime_path.is_file()
|
||||||
|
assert not staging_path.exists()
|
||||||
assert descriptor.size_bytes == len(content)
|
assert descriptor.size_bytes == len(content)
|
||||||
|
|
||||||
updated = manager.update_payload(descriptor, content + "-v2")
|
updated = manager.update_payload(descriptor, content + "-v2")
|
||||||
assert updated.size_bytes == len(content + "-v2")
|
assert updated.size_bytes == len(content + "-v2")
|
||||||
assert staging_path.read_text(encoding="utf-8").endswith("-v2")
|
assert runtime_path.read_text(encoding="utf-8").endswith("-v2")
|
||||||
|
assert not staging_path.exists()
|
||||||
|
|
||||||
manager.delete_payload(descriptor)
|
manager.delete_payload(descriptor)
|
||||||
assert not staging_path.exists()
|
|
||||||
assert not runtime_path.exists()
|
assert not runtime_path.exists()
|
||||||
|
assert not staging_path.exists()
|
||||||
|
|||||||
@@ -202,7 +202,6 @@ export default function SiteList({ onOpenDevicesForSite }) {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* AG Grid */}
|
{/* AG Grid */}
|
||||||
}
|
|
||||||
<Box sx={{ px: { xs: 2, md: 3 }, pb: 3, flexGrow: 1, display: "flex", flexDirection: "column" }}>
|
<Box sx={{ px: { xs: 2, md: 3 }, pb: 3, flexGrow: 1, display: "flex", flexDirection: "column" }}>
|
||||||
<Box
|
<Box
|
||||||
className={themeClassName}
|
className={themeClassName}
|
||||||
|
|||||||
Reference in New Issue
Block a user