lumen-app 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lumen_app/__init__.py +7 -0
- lumen_app/core/__init__.py +0 -0
- lumen_app/core/config.py +661 -0
- lumen_app/core/installer.py +274 -0
- lumen_app/core/loader.py +45 -0
- lumen_app/core/router.py +87 -0
- lumen_app/core/server.py +389 -0
- lumen_app/core/service.py +49 -0
- lumen_app/core/tests/__init__.py +1 -0
- lumen_app/core/tests/test_core_integration.py +561 -0
- lumen_app/core/tests/test_env_checker.py +487 -0
- lumen_app/proto/README.md +12 -0
- lumen_app/proto/ml_service.proto +88 -0
- lumen_app/proto/ml_service_pb2.py +66 -0
- lumen_app/proto/ml_service_pb2.pyi +136 -0
- lumen_app/proto/ml_service_pb2_grpc.py +251 -0
- lumen_app/server.py +362 -0
- lumen_app/utils/env_checker.py +752 -0
- lumen_app/utils/installation/__init__.py +25 -0
- lumen_app/utils/installation/env_manager.py +152 -0
- lumen_app/utils/installation/micromamba_installer.py +459 -0
- lumen_app/utils/installation/package_installer.py +149 -0
- lumen_app/utils/installation/verifier.py +95 -0
- lumen_app/utils/logger.py +181 -0
- lumen_app/utils/mamba/cuda.yaml +12 -0
- lumen_app/utils/mamba/default.yaml +6 -0
- lumen_app/utils/mamba/openvino.yaml +7 -0
- lumen_app/utils/mamba/tensorrt.yaml +13 -0
- lumen_app/utils/package_resolver.py +309 -0
- lumen_app/utils/preset_registry.py +219 -0
- lumen_app/web/__init__.py +3 -0
- lumen_app/web/api/__init__.py +1 -0
- lumen_app/web/api/config.py +229 -0
- lumen_app/web/api/hardware.py +201 -0
- lumen_app/web/api/install.py +608 -0
- lumen_app/web/api/server.py +253 -0
- lumen_app/web/core/__init__.py +1 -0
- lumen_app/web/core/server_manager.py +348 -0
- lumen_app/web/core/state.py +264 -0
- lumen_app/web/main.py +145 -0
- lumen_app/web/models/__init__.py +28 -0
- lumen_app/web/models/config.py +63 -0
- lumen_app/web/models/hardware.py +64 -0
- lumen_app/web/models/install.py +134 -0
- lumen_app/web/models/server.py +95 -0
- lumen_app/web/static/assets/index-CGuhGHC9.css +1 -0
- lumen_app/web/static/assets/index-DN6HmxWS.js +56 -0
- lumen_app/web/static/index.html +14 -0
- lumen_app/web/static/vite.svg +1 -0
- lumen_app/web/websockets/__init__.py +1 -0
- lumen_app/web/websockets/logs.py +159 -0
- lumen_app-0.4.2.dist-info/METADATA +23 -0
- lumen_app-0.4.2.dist-info/RECORD +56 -0
- lumen_app-0.4.2.dist-info/WHEEL +5 -0
- lumen_app-0.4.2.dist-info/entry_points.txt +3 -0
- lumen_app-0.4.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
"""Installation API endpoints - simplified one-click setup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
|
11
|
+
from lumen_resources import LumenConfig, load_and_validate_config
|
|
12
|
+
|
|
13
|
+
from lumen_app.core.installer import CoreInstaller
|
|
14
|
+
from lumen_app.utils.env_checker import (
|
|
15
|
+
DependencyInstaller,
|
|
16
|
+
EnvironmentChecker,
|
|
17
|
+
)
|
|
18
|
+
from lumen_app.utils.installation import MicromambaInstaller, MicromambaStatus
|
|
19
|
+
from lumen_app.utils.logger import get_logger
|
|
20
|
+
from lumen_app.utils.preset_registry import PresetRegistry
|
|
21
|
+
from lumen_app.web.core.state import app_state
|
|
22
|
+
from lumen_app.web.models.install import (
|
|
23
|
+
InstallLogsResponse,
|
|
24
|
+
InstallSetupRequest,
|
|
25
|
+
InstallStatusResponse,
|
|
26
|
+
InstallStep,
|
|
27
|
+
InstallTaskListResponse,
|
|
28
|
+
InstallTaskResponse,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = get_logger("lumen.web.api.install")
|
|
32
|
+
router = APIRouter()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.get("/status", response_model=InstallStatusResponse)
|
|
36
|
+
async def get_install_status():
|
|
37
|
+
"""Get current installation status of the system."""
|
|
38
|
+
logger.info("Checking installation status")
|
|
39
|
+
|
|
40
|
+
# Check micromamba
|
|
41
|
+
cache_dir = Path("~/.lumen").expanduser()
|
|
42
|
+
micromamba_installer = MicromambaInstaller(cache_dir)
|
|
43
|
+
micromamba_result = micromamba_installer.check()
|
|
44
|
+
micromamba_installed = micromamba_result.status == MicromambaStatus.INSTALLED
|
|
45
|
+
micromamba_path = (
|
|
46
|
+
micromamba_result.executable_path if micromamba_installed else None
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Check environment (TODO: implement actual check)
|
|
50
|
+
environment_exists = False
|
|
51
|
+
environment_name = None
|
|
52
|
+
environment_path = None
|
|
53
|
+
|
|
54
|
+
# Check drivers (placeholder)
|
|
55
|
+
drivers_checked = False
|
|
56
|
+
drivers = {}
|
|
57
|
+
|
|
58
|
+
missing_components = []
|
|
59
|
+
if not micromamba_installed:
|
|
60
|
+
missing_components.append("micromamba")
|
|
61
|
+
if not environment_exists:
|
|
62
|
+
missing_components.append("environment")
|
|
63
|
+
|
|
64
|
+
return InstallStatusResponse(
|
|
65
|
+
micromamba_installed=micromamba_installed,
|
|
66
|
+
micromamba_path=micromamba_path,
|
|
67
|
+
environment_exists=environment_exists,
|
|
68
|
+
environment_name=environment_name,
|
|
69
|
+
environment_path=environment_path,
|
|
70
|
+
drivers_checked=drivers_checked,
|
|
71
|
+
drivers=drivers,
|
|
72
|
+
ready_for_preset=None if missing_components else "cpu",
|
|
73
|
+
missing_components=missing_components,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.post("/setup", response_model=InstallTaskResponse)
|
|
78
|
+
async def start_installation(
|
|
79
|
+
request: InstallSetupRequest, background_tasks: BackgroundTasks
|
|
80
|
+
):
|
|
81
|
+
"""Start a complete installation setup for the selected preset.
|
|
82
|
+
|
|
83
|
+
This will automatically install all required components:
|
|
84
|
+
1. micromamba (if not present)
|
|
85
|
+
2. conda environment (if not exists)
|
|
86
|
+
3. required drivers for the preset
|
|
87
|
+
"""
|
|
88
|
+
logger.info(f"Starting installation setup for preset: {request.preset}")
|
|
89
|
+
|
|
90
|
+
# Validate preset
|
|
91
|
+
if not PresetRegistry.preset_exists(request.preset):
|
|
92
|
+
raise HTTPException(status_code=400, detail=f"Unknown preset: {request.preset}")
|
|
93
|
+
|
|
94
|
+
# Create task
|
|
95
|
+
task_id = str(uuid.uuid4())
|
|
96
|
+
current_time = time.time()
|
|
97
|
+
|
|
98
|
+
# Determine installation steps based on current status and preset
|
|
99
|
+
steps = await _plan_installation_steps(request)
|
|
100
|
+
|
|
101
|
+
task = InstallTaskResponse(
|
|
102
|
+
task_id=task_id,
|
|
103
|
+
preset=request.preset,
|
|
104
|
+
status="pending",
|
|
105
|
+
progress=0,
|
|
106
|
+
current_step="Initializing...",
|
|
107
|
+
steps=steps,
|
|
108
|
+
created_at=current_time,
|
|
109
|
+
updated_at=current_time,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Store task in state
|
|
113
|
+
await app_state.store_install_task(task_id, task)
|
|
114
|
+
|
|
115
|
+
# Start background installation
|
|
116
|
+
background_tasks.add_task(_run_installation, task_id, request)
|
|
117
|
+
|
|
118
|
+
return task
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@router.get("/tasks", response_model=InstallTaskListResponse)
|
|
122
|
+
async def list_install_tasks():
|
|
123
|
+
"""List all installation tasks."""
|
|
124
|
+
tasks = await app_state.get_all_install_tasks()
|
|
125
|
+
return InstallTaskListResponse(tasks=tasks, total=len(tasks))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@router.get("/tasks/{task_id}", response_model=InstallTaskResponse)
|
|
129
|
+
async def get_install_task(task_id: str):
|
|
130
|
+
"""Get installation task status and progress."""
|
|
131
|
+
task = await app_state.get_install_task(task_id)
|
|
132
|
+
|
|
133
|
+
if not task:
|
|
134
|
+
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
|
|
135
|
+
|
|
136
|
+
return task
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.get("/tasks/{task_id}/logs", response_model=InstallLogsResponse)
|
|
140
|
+
async def get_install_logs(task_id: str, tail: int = 100):
|
|
141
|
+
"""Get installation task logs."""
|
|
142
|
+
task = await app_state.get_install_task(task_id)
|
|
143
|
+
|
|
144
|
+
if not task:
|
|
145
|
+
raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
|
|
146
|
+
|
|
147
|
+
logs = await app_state.get_install_task_logs(task_id)
|
|
148
|
+
|
|
149
|
+
# Return last N lines
|
|
150
|
+
logs_to_return = logs[-tail:] if tail > 0 else logs
|
|
151
|
+
|
|
152
|
+
return InstallLogsResponse(
|
|
153
|
+
task_id=task_id, logs=logs_to_return, total_lines=len(logs)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ===== Internal helper functions =====
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def _plan_installation_steps(request: InstallSetupRequest) -> list[InstallStep]:
|
|
161
|
+
"""Plan installation steps based on current state and requirements."""
|
|
162
|
+
steps = []
|
|
163
|
+
|
|
164
|
+
# Step 1: Check/install micromamba
|
|
165
|
+
resolved_cache_dir = Path(request.cache_dir).expanduser()
|
|
166
|
+
micromamba_installer = MicromambaInstaller(resolved_cache_dir)
|
|
167
|
+
micromamba_result = micromamba_installer.check()
|
|
168
|
+
if (
|
|
169
|
+
micromamba_result.status != MicromambaStatus.INSTALLED
|
|
170
|
+
or request.force_reinstall
|
|
171
|
+
):
|
|
172
|
+
steps.append(
|
|
173
|
+
InstallStep(
|
|
174
|
+
name="Install micromamba",
|
|
175
|
+
status="pending",
|
|
176
|
+
progress=0,
|
|
177
|
+
message="Micromamba package manager will be installed",
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
steps.append(
|
|
182
|
+
InstallStep(
|
|
183
|
+
name="Check micromamba",
|
|
184
|
+
status="pending",
|
|
185
|
+
progress=0,
|
|
186
|
+
message="Verify micromamba installation",
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Step 2: Create environment
|
|
191
|
+
steps.append(
|
|
192
|
+
InstallStep(
|
|
193
|
+
name=f"Create environment '{request.environment_name}'",
|
|
194
|
+
status="pending",
|
|
195
|
+
progress=0,
|
|
196
|
+
message="Create conda environment for Lumen",
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Step 3: Install drivers (if needed)
|
|
201
|
+
preset_info = PresetRegistry.get_preset(request.preset)
|
|
202
|
+
if preset_info and preset_info.requires_drivers:
|
|
203
|
+
# Check what drivers are needed
|
|
204
|
+
report = EnvironmentChecker.check_preset(request.preset)
|
|
205
|
+
if not report.ready:
|
|
206
|
+
missing_drivers = [
|
|
207
|
+
d.name for d in report.drivers if d.status.value != "available"
|
|
208
|
+
]
|
|
209
|
+
if missing_drivers:
|
|
210
|
+
steps.append(
|
|
211
|
+
InstallStep(
|
|
212
|
+
name="Install drivers",
|
|
213
|
+
status="pending",
|
|
214
|
+
progress=0,
|
|
215
|
+
message=f"Install required drivers: {', '.join(missing_drivers)}",
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Step 4: Install Lumen packages
|
|
220
|
+
steps.append(
|
|
221
|
+
InstallStep(
|
|
222
|
+
name="Install Lumen packages",
|
|
223
|
+
status="pending",
|
|
224
|
+
progress=0,
|
|
225
|
+
message="Install required Lumen packages",
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Step 5: Verify installation
|
|
230
|
+
steps.append(
|
|
231
|
+
InstallStep(
|
|
232
|
+
name="Verify installation",
|
|
233
|
+
status="pending",
|
|
234
|
+
progress=0,
|
|
235
|
+
message="Verify all components are installed correctly",
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return steps
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def _run_installation(task_id: str, request: InstallSetupRequest):
|
|
243
|
+
"""Background task to run the installation process."""
|
|
244
|
+
logger.info(f"Running installation task {task_id} for preset {request.preset}")
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
await _update_task(
|
|
248
|
+
task_id, status="running", current_step="Starting installation..."
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
task = await app_state.get_install_task(task_id)
|
|
252
|
+
if not task:
|
|
253
|
+
logger.error(f"Task {task_id} not found")
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
total_steps = len(task.steps)
|
|
257
|
+
current_step_idx = 0
|
|
258
|
+
resolved_cache_dir = str(Path(request.cache_dir).expanduser())
|
|
259
|
+
|
|
260
|
+
# Step 1: Check/Install micromamba
|
|
261
|
+
if task.steps[current_step_idx].name.startswith("Install micromamba"):
|
|
262
|
+
await _execute_step(task_id, current_step_idx, total_steps)
|
|
263
|
+
success = await _install_micromamba(task_id, resolved_cache_dir)
|
|
264
|
+
if not success:
|
|
265
|
+
await _update_task(
|
|
266
|
+
task_id,
|
|
267
|
+
status="failed",
|
|
268
|
+
error="Failed to install micromamba",
|
|
269
|
+
completed_at=time.time(),
|
|
270
|
+
)
|
|
271
|
+
return
|
|
272
|
+
current_step_idx += 1
|
|
273
|
+
elif task.steps[current_step_idx].name.startswith("Check micromamba"):
|
|
274
|
+
await _execute_step(task_id, current_step_idx, total_steps, quick=True)
|
|
275
|
+
current_step_idx += 1
|
|
276
|
+
|
|
277
|
+
# Step 2: Create environment
|
|
278
|
+
if current_step_idx < total_steps:
|
|
279
|
+
await _execute_step(task_id, current_step_idx, total_steps)
|
|
280
|
+
success = await _create_environment(
|
|
281
|
+
task_id, request.environment_name, resolved_cache_dir
|
|
282
|
+
)
|
|
283
|
+
if not success:
|
|
284
|
+
await _update_task(
|
|
285
|
+
task_id,
|
|
286
|
+
status="failed",
|
|
287
|
+
error="Failed to create environment",
|
|
288
|
+
completed_at=time.time(),
|
|
289
|
+
)
|
|
290
|
+
return
|
|
291
|
+
current_step_idx += 1
|
|
292
|
+
|
|
293
|
+
# Step 3: Install drivers (if needed)
|
|
294
|
+
if (
|
|
295
|
+
current_step_idx < total_steps
|
|
296
|
+
and task.steps[current_step_idx].name == "Install drivers"
|
|
297
|
+
):
|
|
298
|
+
await _execute_step(task_id, current_step_idx, total_steps)
|
|
299
|
+
success = await _install_drivers(
|
|
300
|
+
task_id, request.preset, request.environment_name, resolved_cache_dir
|
|
301
|
+
)
|
|
302
|
+
if not success:
|
|
303
|
+
await _update_task(
|
|
304
|
+
task_id,
|
|
305
|
+
status="failed",
|
|
306
|
+
error="Failed to install drivers",
|
|
307
|
+
completed_at=time.time(),
|
|
308
|
+
)
|
|
309
|
+
return
|
|
310
|
+
current_step_idx += 1
|
|
311
|
+
|
|
312
|
+
# Step 4: Install Lumen packages
|
|
313
|
+
if (
|
|
314
|
+
current_step_idx < total_steps
|
|
315
|
+
and task.steps[current_step_idx].name == "Install Lumen packages"
|
|
316
|
+
):
|
|
317
|
+
await _execute_step(task_id, current_step_idx, total_steps)
|
|
318
|
+
success = await _install_lumen_packages(
|
|
319
|
+
task_id, resolved_cache_dir, request.preset
|
|
320
|
+
)
|
|
321
|
+
if not success:
|
|
322
|
+
await _update_task(
|
|
323
|
+
task_id,
|
|
324
|
+
status="failed",
|
|
325
|
+
error="Failed to install Lumen packages",
|
|
326
|
+
completed_at=time.time(),
|
|
327
|
+
)
|
|
328
|
+
return
|
|
329
|
+
current_step_idx += 1
|
|
330
|
+
|
|
331
|
+
# Step 5: Verify installation
|
|
332
|
+
if current_step_idx < total_steps:
|
|
333
|
+
await _execute_step(task_id, current_step_idx, total_steps)
|
|
334
|
+
success = await _verify_installation(
|
|
335
|
+
task_id, resolved_cache_dir, request.environment_name
|
|
336
|
+
)
|
|
337
|
+
if not success:
|
|
338
|
+
await _update_task(
|
|
339
|
+
task_id,
|
|
340
|
+
status="failed",
|
|
341
|
+
error="Failed to verify installation",
|
|
342
|
+
completed_at=time.time(),
|
|
343
|
+
)
|
|
344
|
+
return
|
|
345
|
+
current_step_idx += 1
|
|
346
|
+
|
|
347
|
+
# Complete
|
|
348
|
+
await _update_task(
|
|
349
|
+
task_id,
|
|
350
|
+
status="completed",
|
|
351
|
+
progress=100,
|
|
352
|
+
current_step="Installation completed successfully",
|
|
353
|
+
completed_at=time.time(),
|
|
354
|
+
)
|
|
355
|
+
logger.info(f"Installation task {task_id} completed successfully")
|
|
356
|
+
|
|
357
|
+
except Exception as e:
|
|
358
|
+
logger.error(f"Installation task {task_id} failed: {e}", exc_info=True)
|
|
359
|
+
await _update_task(
|
|
360
|
+
task_id,
|
|
361
|
+
status="failed",
|
|
362
|
+
error=str(e),
|
|
363
|
+
completed_at=time.time(),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
async def _execute_step(
|
|
368
|
+
task_id: str, step_idx: int, total_steps: int, quick: bool = False
|
|
369
|
+
):
|
|
370
|
+
"""Mark a step as running and update progress."""
|
|
371
|
+
task = await app_state.get_install_task(task_id)
|
|
372
|
+
if not task:
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
# Update step status
|
|
376
|
+
task.steps[step_idx].status = "running"
|
|
377
|
+
task.steps[step_idx].started_at = time.time()
|
|
378
|
+
|
|
379
|
+
# Calculate overall progress
|
|
380
|
+
base_progress = int((step_idx / total_steps) * 100)
|
|
381
|
+
|
|
382
|
+
await _update_task(
|
|
383
|
+
task_id,
|
|
384
|
+
current_step=task.steps[step_idx].message,
|
|
385
|
+
progress=base_progress,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Simulate quick check
|
|
389
|
+
if quick:
|
|
390
|
+
await asyncio.sleep(0.5)
|
|
391
|
+
await _complete_current_step(task_id)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
async def _install_micromamba(task_id: str, cache_dir: str) -> bool:
|
|
395
|
+
"""Install micromamba."""
|
|
396
|
+
await _append_log(task_id, "Installing micromamba...")
|
|
397
|
+
|
|
398
|
+
try:
|
|
399
|
+
micromamba_installer = MicromambaInstaller(cache_dir)
|
|
400
|
+
exe_path = micromamba_installer.install(dry_run=False)
|
|
401
|
+
|
|
402
|
+
message = f"Micromamba installed successfully at {exe_path}"
|
|
403
|
+
await _append_log(task_id, message)
|
|
404
|
+
await _complete_current_step(task_id)
|
|
405
|
+
return True
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
error_msg = f"Failed to install micromamba: {e}"
|
|
409
|
+
await _append_log(task_id, error_msg)
|
|
410
|
+
await _fail_current_step(task_id, error_msg)
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
async def _create_environment(task_id: str, env_name: str, cache_dir: str) -> bool:
|
|
415
|
+
"""Create conda environment."""
|
|
416
|
+
await _append_log(task_id, f"Creating environment: {env_name}")
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
installer = CoreInstaller(cache_dir=cache_dir, env_name=env_name)
|
|
420
|
+
success, message = installer.create_environment(
|
|
421
|
+
config_filename="default.yaml",
|
|
422
|
+
dry_run=False,
|
|
423
|
+
)
|
|
424
|
+
await _append_log(task_id, message)
|
|
425
|
+
|
|
426
|
+
if success:
|
|
427
|
+
await _complete_current_step(task_id)
|
|
428
|
+
return True
|
|
429
|
+
|
|
430
|
+
await _fail_current_step(task_id, message)
|
|
431
|
+
return False
|
|
432
|
+
except Exception as e:
|
|
433
|
+
error_msg = f"Failed to create environment: {e}"
|
|
434
|
+
await _append_log(task_id, error_msg)
|
|
435
|
+
await _fail_current_step(task_id, error_msg)
|
|
436
|
+
return False
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
async def _install_drivers(
|
|
440
|
+
task_id: str, preset: str, env_name: str, cache_dir: str
|
|
441
|
+
) -> bool:
|
|
442
|
+
"""Install required drivers for the preset."""
|
|
443
|
+
await _append_log(task_id, f"Installing drivers for preset: {preset}")
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
# Get required drivers
|
|
447
|
+
report = EnvironmentChecker.check_preset(preset)
|
|
448
|
+
missing_drivers = [
|
|
449
|
+
d
|
|
450
|
+
for d in report.drivers
|
|
451
|
+
if d.status.value != "available" and d.installable_via_mamba
|
|
452
|
+
]
|
|
453
|
+
|
|
454
|
+
if not missing_drivers:
|
|
455
|
+
await _append_log(task_id, "All drivers already available")
|
|
456
|
+
await _complete_current_step(task_id)
|
|
457
|
+
return True
|
|
458
|
+
|
|
459
|
+
micromamba_installer = MicromambaInstaller(cache_dir)
|
|
460
|
+
micromamba_path = str(micromamba_installer.get_executable())
|
|
461
|
+
root_prefix = str(Path(cache_dir).expanduser() / "micromamba")
|
|
462
|
+
installer = DependencyInstaller(
|
|
463
|
+
micromamba_path=micromamba_path,
|
|
464
|
+
root_prefix=root_prefix,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
for driver in missing_drivers:
|
|
468
|
+
await _append_log(task_id, f"Installing {driver.name}...")
|
|
469
|
+
|
|
470
|
+
success, message = installer.install_driver(
|
|
471
|
+
driver_name=driver.name,
|
|
472
|
+
env_name=env_name,
|
|
473
|
+
dry_run=False,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
await _append_log(task_id, message)
|
|
477
|
+
|
|
478
|
+
if not success:
|
|
479
|
+
await _fail_current_step(task_id, f"Failed to install {driver.name}")
|
|
480
|
+
return False
|
|
481
|
+
|
|
482
|
+
await _complete_current_step(task_id)
|
|
483
|
+
return True
|
|
484
|
+
|
|
485
|
+
except Exception as e:
|
|
486
|
+
error_msg = f"Failed to install drivers: {e}"
|
|
487
|
+
await _append_log(task_id, error_msg)
|
|
488
|
+
await _fail_current_step(task_id, error_msg)
|
|
489
|
+
return False
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
async def _install_lumen_packages(task_id: str, cache_dir: str, preset: str) -> bool:
|
|
493
|
+
"""Install Lumen packages derived from lumen-config.yaml.
|
|
494
|
+
|
|
495
|
+
Downloads wheels from GitHub Releases and installs with device-specific extras.
|
|
496
|
+
"""
|
|
497
|
+
await _append_log(task_id, "Installing Lumen packages from GitHub Releases...")
|
|
498
|
+
|
|
499
|
+
try:
|
|
500
|
+
config_path = Path(cache_dir).expanduser() / "lumen-config.yaml"
|
|
501
|
+
if not config_path.exists():
|
|
502
|
+
message = f"Config file not found: {config_path}"
|
|
503
|
+
await _append_log(task_id, message)
|
|
504
|
+
await _fail_current_step(task_id, message)
|
|
505
|
+
return False
|
|
506
|
+
|
|
507
|
+
lumen_config: LumenConfig = load_and_validate_config(str(config_path))
|
|
508
|
+
|
|
509
|
+
# Get device config for the preset
|
|
510
|
+
device_config = PresetRegistry.create_config(preset)
|
|
511
|
+
await _append_log(task_id, f"Using preset: {preset}")
|
|
512
|
+
|
|
513
|
+
# Read region from config
|
|
514
|
+
region = lumen_config.metadata.region
|
|
515
|
+
await _append_log(task_id, f"Using region: {region.value}")
|
|
516
|
+
|
|
517
|
+
installer = CoreInstaller(cache_dir=cache_dir, region=region)
|
|
518
|
+
|
|
519
|
+
success, message = installer.install_lumen_packages(lumen_config, device_config)
|
|
520
|
+
await _append_log(task_id, message)
|
|
521
|
+
|
|
522
|
+
if success:
|
|
523
|
+
await _complete_current_step(task_id)
|
|
524
|
+
return True
|
|
525
|
+
|
|
526
|
+
await _fail_current_step(task_id, message)
|
|
527
|
+
return False
|
|
528
|
+
except Exception as e:
|
|
529
|
+
error_msg = f"Failed to install Lumen packages: {e}"
|
|
530
|
+
await _append_log(task_id, error_msg)
|
|
531
|
+
await _fail_current_step(task_id, error_msg)
|
|
532
|
+
return False
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
async def _verify_installation(task_id: str, cache_dir: str, env_name: str) -> bool:
|
|
536
|
+
"""Verify installation status."""
|
|
537
|
+
await _append_log(task_id, "Verifying installation...")
|
|
538
|
+
|
|
539
|
+
try:
|
|
540
|
+
installer = CoreInstaller(cache_dir=cache_dir, env_name=env_name)
|
|
541
|
+
success, message = installer.verify_installation()
|
|
542
|
+
await _append_log(task_id, message)
|
|
543
|
+
|
|
544
|
+
if success:
|
|
545
|
+
await _complete_current_step(task_id)
|
|
546
|
+
return True
|
|
547
|
+
|
|
548
|
+
await _fail_current_step(task_id, message)
|
|
549
|
+
return False
|
|
550
|
+
except Exception as e:
|
|
551
|
+
error_msg = f"Failed to verify installation: {e}"
|
|
552
|
+
await _append_log(task_id, error_msg)
|
|
553
|
+
await _fail_current_step(task_id, error_msg)
|
|
554
|
+
return False
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
async def _update_task(task_id: str, **updates):
|
|
558
|
+
"""Update task fields."""
|
|
559
|
+
task = await app_state.get_install_task(task_id)
|
|
560
|
+
if not task:
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
for key, value in updates.items():
|
|
564
|
+
if hasattr(task, key):
|
|
565
|
+
setattr(task, key, value)
|
|
566
|
+
|
|
567
|
+
task.updated_at = time.time()
|
|
568
|
+
await app_state.store_install_task(task_id, task)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
async def _complete_current_step(task_id: str):
|
|
572
|
+
"""Mark current running step as completed."""
|
|
573
|
+
task = await app_state.get_install_task(task_id)
|
|
574
|
+
if not task:
|
|
575
|
+
return
|
|
576
|
+
|
|
577
|
+
for step in task.steps:
|
|
578
|
+
if step.status == "running":
|
|
579
|
+
step.status = "completed"
|
|
580
|
+
step.progress = 100
|
|
581
|
+
step.completed_at = time.time()
|
|
582
|
+
break
|
|
583
|
+
|
|
584
|
+
await app_state.store_install_task(task_id, task)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
async def _fail_current_step(task_id: str, error_msg: str):
|
|
588
|
+
"""Mark current running step as failed."""
|
|
589
|
+
task = await app_state.get_install_task(task_id)
|
|
590
|
+
if not task:
|
|
591
|
+
return
|
|
592
|
+
|
|
593
|
+
for step in task.steps:
|
|
594
|
+
if step.status == "running":
|
|
595
|
+
step.status = "failed"
|
|
596
|
+
step.message = error_msg
|
|
597
|
+
step.completed_at = time.time()
|
|
598
|
+
break
|
|
599
|
+
|
|
600
|
+
await app_state.store_install_task(task_id, task)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
async def _append_log(task_id: str, message: str):
|
|
604
|
+
"""Append a log message to the task."""
|
|
605
|
+
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
606
|
+
log_line = f"[{timestamp}] {message}"
|
|
607
|
+
await app_state.append_install_log(task_id, log_line)
|
|
608
|
+
logger.info(f"Task {task_id}: {message}")
|