@@ -116,17 +116,24 @@ router.get('/new', requireAuth, async (req, res) => {
116116 order : [ [ 'name' , 'ASC' ] ]
117117 } ) ;
118118
119+ // Check if any node in this site has NVIDIA available
120+ const nvidiaAvailable = await Node . count ( {
121+ where : { siteId, nvidiaAvailable : true }
122+ } ) > 0 ;
123+
119124 if ( isApi ) {
120125 return res . json ( {
121126 site_id : site . id ,
122- domains : externalDomains
127+ domains : externalDomains ,
128+ nvidiaAvailable
123129 } ) ;
124130 }
125131 // ----------------------------
126132
127133 return res . render ( 'containers/form' , {
128134 site,
129135 externalDomains,
136+ nvidiaAvailable,
130137 container : undefined ,
131138 req
132139 } ) ;
@@ -272,9 +279,14 @@ router.post('/', async (req, res) => {
272279 try {
273280 let { hostname, template, customTemplate, services, environmentVars, entrypoint,
274281 // Extract specific API fields
275- template_name, repository, branch
282+ template_name, repository, branch,
283+ // NVIDIA GPU passthrough
284+ nvidiaRequested
276285 } = req . body ;
277286
287+ // Normalize NVIDIA requested flag
288+ const wantsNvidia = ! ! nvidiaRequested ;
289+
278290 // --- API Payload Mapping ---
279291 if ( isApi ) {
280292 if ( template_name && ! template ) {
@@ -310,6 +322,18 @@ router.post('/', async (req, res) => {
310322 envVarsJson = JSON . stringify ( envObj ) ;
311323 }
312324 }
325+
326+ // Inject NVIDIA environment variables when GPU passthrough is requested
327+ if ( wantsNvidia ) {
328+ const envObj = envVarsJson ? JSON . parse ( envVarsJson ) : { } ;
329+ if ( ! envObj [ 'NVIDIA_VISIBLE_DEVICES' ] ) {
330+ envObj [ 'NVIDIA_VISIBLE_DEVICES' ] = 'all' ;
331+ }
332+ if ( ! envObj [ 'NVIDIA_DRIVER_CAPABILITIES' ] ) {
333+ envObj [ 'NVIDIA_DRIVER_CAPABILITIES' ] = 'utility compute' ;
334+ }
335+ envVarsJson = JSON . stringify ( envObj ) ;
336+ }
313337
314338 // Resolve Docker image ref from either the dropdown or the custom input
315339 const imageRef = ( template === 'custom' ) ? customTemplate ?. trim ( ) : template ;
@@ -318,14 +342,21 @@ router.post('/', async (req, res) => {
318342 }
319343 const templateName = normalizeDockerRef ( imageRef ) ;
320344
321- const node = await Node . findOne ( {
322- where : {
323- siteId,
324- apiUrl : { [ Sequelize . Op . ne ] : null } ,
325- tokenId : { [ Sequelize . Op . ne ] : null } ,
326- secret : { [ Sequelize . Op . ne ] : null }
327- }
328- } ) ;
345+ // Build node selection criteria
346+ const nodeWhere = {
347+ siteId,
348+ apiUrl : { [ Sequelize . Op . ne ] : null } ,
349+ tokenId : { [ Sequelize . Op . ne ] : null } ,
350+ secret : { [ Sequelize . Op . ne ] : null }
351+ } ;
352+ if ( wantsNvidia ) {
353+ nodeWhere . nvidiaAvailable = true ;
354+ }
355+
356+ const node = await Node . findOne ( { where : nodeWhere } ) ;
357+ if ( ! node && wantsNvidia ) {
358+ throw new Error ( 'NVIDIA requested but no NVIDIA-capable nodes are available in this site' ) ;
359+ }
329360 if ( ! node ) {
330361 throw new Error ( 'No nodes with API access available in this site' ) ;
331362 }
@@ -341,6 +372,7 @@ router.post('/', async (req, res) => {
341372 containerId : null ,
342373 macAddress : null ,
343374 ipv4Address : null ,
375+ nvidiaRequested : wantsNvidia ,
344376 environmentVars : envVarsJson ,
345377 entrypoint : entrypoint && entrypoint . trim ( ) ? entrypoint . trim ( ) : null
346378 } , { transaction : t } ) ;
0 commit comments