fix(launcher): run NAV2 modules through host runtime

This commit is contained in:
2026-09-01 18:17:22 +09:00
parent 4ca0575b32
commit 9e7f4b02c7
5 changed files with 77 additions and 4 deletions
+19
View File
@@ -5,3 +5,22 @@ out/
.env.*
*.log
.DS_Store
# Direct `tsc -p` output. Electron Vite owns application compilation.
/electron.vite.config.js
/electron.vite.config.d.ts
/src/main/*.js
/src/main/*.d.ts
/src/main/**/*.js
/src/main/**/*.d.ts
/src/preload/*.js
/src/preload/*.d.ts
/src/preload/**/*.js
/src/preload/**/*.d.ts
/src/renderer/src/*.js
/src/renderer/src/*.jsx
/src/renderer/src/*.d.ts
/src/renderer/src/**/*.js
/src/renderer/src/**/*.jsx
/src/renderer/src/**/*.d.ts
*.tsbuildinfo
+52 -1
View File
@@ -176,6 +176,28 @@ function containerName(folderName: string): string | null {
return /^[A-Za-z0-9_.-]+-[A-Za-z0-9_.-]+_v[A-Za-z0-9_.-]+$/.test(name) ? name : null
}
function isNav2ModuleFolder(folderName: string): boolean {
return /^\[lms-nav2-[^\]]+\].+_v[^/]+_ws$/.test(folderName)
}
function resolveLocalNav2RuntimeImage(module: CourseModule, body: Record<string, unknown>, rosParams: string): string {
const configured = module.dockerImage?.trim() ?? ''
if (configured && /^[A-Za-z0-9._/:@-]+$/.test(configured)) return configured
const gazeboModule = /^\[lms-nav2-gazebo-/i.test(module.folderName)
|| (/lms-nav2-mkmini/i.test(module.folderName) && /(?:^|\s)simulator:=gazebo(?:\s|$)/.test(rosParams))
const runtimeImages = Array.isArray(body.runtimeImages) ? body.runtimeImages : []
const matched = runtimeImages.find((value) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
const image = (value as RuntimeImageArtifact).image?.trim() ?? ''
return gazeboModule
? /nav2.*gazebo|gazebo.*nav2/i.test(image)
: /nav2/i.test(image) && !/gazebo|ros-bridge/i.test(image)
}) as RuntimeImageArtifact | undefined
return matched?.image?.trim() || (gazeboModule ? 'future-nav2-gazebo:humble' : 'future-nav2:humble')
}
function nav2LaunchSpec(folderName: string): { packageName: string; launchFile: string } | null {
const module = folderName.match(/^\[lms-nav2-(?:gazebo-)?([a-z]+)\]/)?.[1]
const specs: Record<string, { packageName: string; launchFile: string }> = {
@@ -930,7 +952,6 @@ export class LmsWebBridge {
const module = this.moduleFromBody(body)
const root = expandWorkspaceRoot(typeof body.workspaceRoot === 'string' ? body.workspaceRoot : undefined)
const target = resolve(root, module.folderName)
const runtimeConfigRoot = resolve(homedir(), 'autonomy-studio', 'runtime', 'config')
if (!existsSync(target)) throw new Error('설치된 모듈을 찾을 수 없습니다.')
const container = containerName(module.folderName)
if (!container) throw new Error('모듈 컨테이너 이름이 올바르지 않습니다.')
@@ -940,6 +961,23 @@ export class LmsWebBridge {
const rosParams = typeof body.rosParams === 'string' ? body.rosParams.trim() : ''
const rosDomainId = typeof body.rosDomainId === 'number' || typeof body.rosDomainId === 'string' ? String(body.rosDomainId).replace(/[^0-9]/g, '') || '0' : '0'
const gpuEnabled = body.gpuEnabled === true
if (isNav2ModuleFolder(module.folderName)) {
const runtimeScript = resolve(homedir(), 'autonomy-studio', 'runtime', 'nav2', 'docker', 'docker_run_nav2.sh')
if (!existsSync(runtimeScript)) throw new Error(`NAV2 런타임 스크립트를 찾을 수 없습니다: ${runtimeScript}`)
await command('bash', [
runtimeScript,
container,
root,
module.folderName,
resolveLocalNav2RuntimeImage(module, body, rosParams),
rosDomainId,
gpuEnabled ? 'true' : 'false',
...rosParams.split(/\s+/).filter(Boolean),
], undefined, 45_000)
return { ok: true, message: '실행 요청을 보냈습니다.' }
}
const runtimeConfigRoot = resolve(homedir(), 'autonomy-studio', 'runtime', 'config')
await mkdir(runtimeConfigRoot, { recursive: true })
const args = ['run', '-d', '--name', container, '--network', 'host', '-v', `${target}:/autoware:rw`, '-v', `${runtimeConfigRoot}:/autoware/config:rw`, '-v', '/etc/localtime:/etc/localtime:ro', '-e', `ROS_DOMAIN_ID=${rosDomainId}`, '-e', 'RCUTILS_COLORIZED_OUTPUT=1', '-e', 'ROS_LOCALHOST_ONLY=1']
if (process.env.DISPLAY) args.push('-e', `DISPLAY=${process.env.DISPLAY}`, '-v', '/tmp/.X11-unix:/tmp/.X11-unix')
@@ -957,6 +995,19 @@ export class LmsWebBridge {
const container = containerName(module.folderName)
if (!container) throw new Error('모듈 컨테이너 이름이 올바르지 않습니다.')
const rosDomainId = typeof body.rosDomainId === 'number' || typeof body.rosDomainId === 'string' ? String(body.rosDomainId).replace(/[^0-9]/g, '') || '0' : '0'
if (isNav2ModuleFolder(module.folderName)) {
const runtimeScript = resolve(homedir(), 'autonomy-studio', 'runtime', 'nav2', 'docker', 'docker_build_nav2.sh')
if (!existsSync(runtimeScript)) throw new Error(`NAV2 빌드 스크립트를 찾을 수 없습니다: ${runtimeScript}`)
const rosParams = typeof body.rosParams === 'string' ? body.rosParams.trim() : ''
await command('bash', [
runtimeScript,
root,
module.folderName,
resolveLocalNav2RuntimeImage(module, body, rosParams),
rosDomainId,
], undefined, 900_000)
return { ok: true, message: '빌드가 완료되었습니다.' }
}
await command('docker', [
'run', '--rm', '--name', `${container}-build`, '--volume', `${target}:/autoware:rw`, '-u', 'root', '--net=host',
'-e', `ROS_DOMAIN_ID=${rosDomainId}`, '-v', '/etc/localtime:/etc/localtime:ro', '--entrypoint', '/bin/bash', AUTOWARE_IMAGE,
+3 -1
View File
@@ -2,7 +2,9 @@
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
"compilerOptions": {
"composite": true,
"composite": false,
"incremental": false,
"noEmit": true,
"moduleResolution": "bundler",
"types": ["electron-vite/node"]
}
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -2,7 +2,9 @@
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
"include": ["src/renderer/src/**/*"],
"compilerOptions": {
"composite": true,
"composite": false,
"incremental": false,
"noEmit": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"paths": {