@@ -29,7 +29,7 @@ const https = require('https');
2929
3030// Load models from parent directory
3131const db = require ( path . join ( __dirname , '..' , 'models' ) ) ;
32- const { Container, Node, Site } = db ;
32+ const { Container, Node, Site, Volume , ContainerVolume } = db ;
3333
3434// Load utilities
3535const { parseArgs } = require ( path . join ( __dirname , '..' , 'utils' , 'cli' ) ) ;
@@ -174,6 +174,25 @@ async function main() {
174174 const containerId = parseInt ( args [ 'container-id' ] , 10 ) ;
175175 console . log ( `Starting container creation for container ID: ${ containerId } ` ) ;
176176
177+ // Parse new volume arguments
178+ const newVolumes = [ ] ;
179+ for ( const key in args ) {
180+ if ( key === 'new-volume' ) {
181+ const values = Array . isArray ( args [ key ] ) ? args [ key ] : [ args [ key ] ] ;
182+ for ( const val of values ) {
183+ try {
184+ newVolumes . push ( JSON . parse ( decodeURIComponent ( val ) ) ) ;
185+ } catch ( e ) {
186+ console . error ( `Failed to parse new-volume argument: ${ val } ` ) ;
187+ }
188+ }
189+ }
190+ }
191+
192+ if ( newVolumes . length > 0 ) {
193+ console . log ( `Will create ${ newVolumes . length } new volume(s)` ) ;
194+ }
195+
177196 // Load the container record with its node and site
178197 const container = await Container . findByPk ( containerId , {
179198 include : [ {
@@ -385,6 +404,156 @@ async function main() {
385404 await container . update ( { containerId : vmid } ) ;
386405 console . log ( `Container VMID ${ vmid } stored in database` ) ;
387406
407+ // Create new volumes if any were requested
408+ if ( newVolumes . length > 0 ) {
409+ console . log ( `Creating ${ newVolumes . length } new volume(s)...` ) ;
410+
411+ // Check node has placeholder
412+ if ( ! node . placeholderCtId ) {
413+ throw new Error ( `Node ${ node . name } does not have a placeholder container. Create one first.` ) ;
414+ }
415+
416+ // Find storage for volumes
417+ const storages = await client . datastores ( node . name , 'rootdir' , true ) ;
418+ if ( storages . length === 0 ) {
419+ throw new Error ( 'No storage available for volumes' ) ;
420+ }
421+ const volumeStorage = storages [ 0 ] . storage ;
422+
423+ for ( const volSpec of newVolumes ) {
424+ console . log ( ` Creating volume "${ volSpec . name } " (${ volSpec . sizeGb } GB)...` ) ;
425+
426+ // Allocate the disk on the placeholder
427+ const volumeId = await client . allocateDisk (
428+ node . name ,
429+ volumeStorage ,
430+ node . placeholderCtId ,
431+ volSpec . sizeGb
432+ ) ;
433+ console . log ( ` Allocated: ${ volumeId } ` ) ;
434+
435+ // Create volume record in database
436+ const volume = await Volume . create ( {
437+ name : volSpec . name ,
438+ username : container . username ,
439+ proxmoxVolume : volumeId ,
440+ sizeGb : volSpec . sizeGb ,
441+ siteId : site . id ,
442+ nodeId : node . id
443+ } ) ;
444+ console . log ( ` Volume record created: ID ${ volume . id } ` ) ;
445+
446+ // Attach to placeholder temporarily
447+ const placeholderMp = await client . findNextMountPoint ( node . name , node . placeholderCtId ) ;
448+ const placeholderMountPath = `/${ container . username } /${ volSpec . name } ` ;
449+ await client . updateLxcConfig ( node . name , node . placeholderCtId , {
450+ [ placeholderMp ] : `${ volumeId } ,mp=${ placeholderMountPath } `
451+ } ) ;
452+ console . log ( ` Attached to placeholder at ${ placeholderMp } ` ) ;
453+
454+ // Create ContainerVolume record for attachment
455+ await ContainerVolume . create ( {
456+ containerId : container . id ,
457+ volumeId : volume . id ,
458+ mountPath : volSpec . mountPath
459+ } ) ;
460+ console . log ( ` Queued for attachment at ${ volSpec . mountPath } ` ) ;
461+ }
462+ }
463+
464+ // Attach volumes if any were requested (including newly created ones)
465+ const volumeAttachments = await ContainerVolume . findAll ( {
466+ where : { containerId : container . id } ,
467+ include : [ {
468+ model : Volume ,
469+ as : 'volume' ,
470+ include : [ { model : Node , as : 'node' } ]
471+ } ]
472+ } ) ;
473+
474+ if ( volumeAttachments . length > 0 ) {
475+ console . log ( `Attaching ${ volumeAttachments . length } volume(s)...` ) ;
476+
477+ for ( const attachment of volumeAttachments ) {
478+ const volume = attachment . volume ;
479+ const mountPath = attachment . mountPath ;
480+
481+ console . log ( ` Attaching volume "${ volume . name } " at ${ mountPath } ` ) ;
482+
483+ // Check if volume is on same node
484+ if ( volume . nodeId !== node . id ) {
485+ console . log ( ` Volume is on different node (${ volume . node . name } ), migrating to ${ node . name } ...` ) ;
486+
487+ // Get the source node
488+ const sourceNode = await Node . findByPk ( volume . nodeId ) ;
489+ if ( ! sourceNode || ! sourceNode . placeholderCtId ) {
490+ throw new Error ( `Source node for volume "${ volume . name } " not found or has no placeholder` ) ;
491+ }
492+
493+ // Get API client for source node
494+ const sourceClient = await sourceNode . api ( ) ;
495+
496+ // Find the volume on source placeholder
497+ const sourceMp = await sourceClient . findMountPointForVolume ( sourceNode . name , sourceNode . placeholderCtId , volume . proxmoxVolume ) ;
498+ if ( ! sourceMp ) {
499+ throw new Error ( `Volume "${ volume . name } " not found on source placeholder container` ) ;
500+ }
501+
502+ // Strategy: Move volume to a temporary minimal container, migrate it, then extract
503+ // For now, we'll use a simpler approach: create the volume fresh on target and warn about data loss
504+ // TODO: Implement proper storage-level migration when Proxmox supports it better
505+
506+ // Alternative: Use pct move command with --target-node option (requires shared storage)
507+ // For local storage, we need to:
508+ // 1. Create a temp container with just this volume on source
509+ // 2. Migrate the temp container to target
510+ // 3. Move volume from temp to target placeholder
511+ // 4. Delete temp container
512+
513+ // Check if target node has placeholder
514+ if ( ! node . placeholderCtId ) {
515+ throw new Error ( `Target node ${ node . name } does not have a placeholder container configured` ) ;
516+ }
517+
518+ // For MVP: Use backup/restore approach through shared storage or error out
519+ // This is complex and depends on infrastructure setup
520+ throw new Error (
521+ `Cross-node volume migration for "${ volume . name } " requires manual intervention. ` +
522+ `Volume is on node "${ sourceNode . name } " but container is being created on "${ node . name } ". ` +
523+ `Please create the container on the same node as the volume, or migrate the volume manually using Proxmox.`
524+ ) ;
525+ }
526+
527+ // Find the mount point on the placeholder container
528+ const placeholderCtId = node . placeholderCtId ;
529+ if ( ! placeholderCtId ) {
530+ throw new Error ( `Node ${ node . name } does not have a placeholder container configured` ) ;
531+ }
532+
533+ const sourceMp = await client . findMountPointForVolume ( node . name , placeholderCtId , volume . proxmoxVolume ) ;
534+ if ( ! sourceMp ) {
535+ throw new Error ( `Volume "${ volume . name } " not found on placeholder container` ) ;
536+ }
537+
538+ // Find next available mount point on target container
539+ const targetMp = await client . findNextMountPoint ( node . name , vmid ) ;
540+
541+ // Move volume from placeholder to new container
542+ console . log ( ` Moving ${ sourceMp } from placeholder CT ${ placeholderCtId } to ${ targetMp } on CT ${ vmid } ` ) ;
543+ const moveUpid = await client . moveVolume ( node . name , placeholderCtId , sourceMp , vmid , targetMp ) ;
544+ await client . waitForTask ( node . name , moveUpid ) ;
545+
546+ // Update the mount path on the target container
547+ await client . updateLxcConfig ( node . name , vmid , {
548+ [ targetMp ] : `${ volume . proxmoxVolume } ,mp=${ mountPath } `
549+ } ) ;
550+
551+ console . log ( ` Volume "${ volume . name } " attached at ${ mountPath } ` ) ;
552+ }
553+
554+ console . log ( 'All volumes attached successfully' ) ;
555+ }
556+
388557 // Start the container
389558 console . log ( 'Starting container...' ) ;
390559 const startUpid = await client . startLxc ( node . name , vmid ) ;
0 commit comments