Public/Storage/New-VergeNASService.ps1

function New-VergeNASService {
    <#
    .SYNOPSIS
        Deploys a new NAS service VM in VergeOS.

    .DESCRIPTION
        New-VergeNASService deploys a new NAS (Network Attached Storage) service VM
        using the Services recipe. NAS services manage volumes and file shares
        (CIFS/SMB and NFS) in VergeOS.

    .PARAMETER Name
        The name for the new NAS service. This will be the name of both the
        recipe instance and the underlying VM.

    .PARAMETER Hostname
        The hostname for the NAS service VM. If not specified, defaults to the
        Name parameter value (with invalid characters removed).

    .PARAMETER Network
        The network to connect the NAS service to. Can be a network name, key,
        or network object. If not specified, defaults to 'Internal' or the first
        available internal network.

    .PARAMETER Cores
        The number of CPU cores for the NAS service VM. Default is 4.

    .PARAMETER MemoryGB
        The amount of RAM in GB for the NAS service VM. Default is 8.

    .PARAMETER AutoUpdate
        If specified, the NAS service VM will automatically update when powered off.

    .PARAMETER PassThru
        Return the created NAS service object.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        New-VergeNASService -Name "NAS01"

        Deploys a new NAS service named "NAS01" with default settings.

    .EXAMPLE
        New-VergeNASService -Name "FileServer" -Network "Internal" -PassThru

        Deploys a new NAS service on the Internal network and returns the object.

    .EXAMPLE
        New-VergeNASService -Name "NAS02" -Cores 8 -MemoryGB 16

        Deploys a NAS service with 8 CPU cores and 16 GB RAM.

    .EXAMPLE
        New-VergeNASService -Name "NAS03" -Hostname "fileserver" -AutoUpdate

        Deploys a NAS service with a custom hostname and auto-update enabled.

    .OUTPUTS
        None by default. Verge.NASService when -PassThru is specified.

    .NOTES
        The Services recipe must be available in the system. NAS services are
        specialized VMs that manage NAS volumes and file shares. Once deployed,
        use Get-VergeNASService to view the service and New-VergeVolume to create
        volumes on the service.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateLength(1, 128)]
        [ValidatePattern('^[a-zA-Z0-9][a-zA-Z0-9\-_. ]*$')]
        [string]$Name,

        [Parameter()]
        [ValidateLength(1, 63)]
        [ValidatePattern('^[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$')]
        [string]$Hostname,

        [Parameter()]
        [object]$Network,

        [Parameter()]
        [ValidateRange(1, 128)]
        [int]$Cores = 4,

        [Parameter()]
        [ValidateRange(1, 1024)]
        [int]$MemoryGB = 8,

        [Parameter()]
        [switch]$AutoUpdate,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }
    }

    process {
        # Find the Services recipe
        $recipeQuery = @{
            filter = "name eq 'Services'"
            fields = 'id,name,description,version'
        }

        try {
            Write-Verbose "Looking up Services recipe"
            $recipe = Invoke-VergeAPI -Method GET -Endpoint 'vm_recipes' -Query $recipeQuery -Connection $Server
        }
        catch {
            throw [System.InvalidOperationException]::new(
                "Failed to find Services recipe: $($_.Exception.Message)"
            )
        }

        if (-not $recipe) {
            throw [System.InvalidOperationException]::new(
                "Services recipe not found. Ensure the Services recipe is available in the system."
            )
        }

        # Handle array response
        if ($recipe -is [array]) {
            $recipe = $recipe[0]
        }

        Write-Verbose "Found Services recipe: $($recipe.name) (ID: $($recipe.id))"

        # Check if a NAS service with this name already exists
        try {
            $existing = Get-VergeNASService -Name $Name -Server $Server -ErrorAction SilentlyContinue
            if ($existing) {
                throw [System.InvalidOperationException]::new(
                    "A NAS service with name '$Name' already exists."
                )
            }
        }
        catch [System.InvalidOperationException] {
            throw
        }
        catch {
            # Continue if not found
        }

        # Determine hostname (default to Name with invalid characters removed)
        if (-not $Hostname) {
            $Hostname = $Name -replace '[^a-zA-Z0-9\-]', '' -replace '^-+', '' -replace '-+$', ''
            if ($Hostname.Length -gt 63) {
                $Hostname = $Hostname.Substring(0, 63)
            }
            if (-not $Hostname) {
                $Hostname = 'nas'
            }
        }

        # Resolve network
        $networkKey = $null
        if ($Network) {
            if ($Network -is [int]) {
                $networkKey = $Network
            }
            elseif ($Network -is [string]) {
                # Look up network by name
                $netObj = Get-VergeNetwork -Name $Network -Server $Server -ErrorAction SilentlyContinue
                if (-not $netObj) {
                    throw [System.ArgumentException]::new("Network '$Network' not found.")
                }
                $networkKey = $netObj.Key
            }
            elseif ($Network.Key) {
                $networkKey = $Network.Key
            }
        }
        else {
            # Try to find 'Internal' network or first available internal network
            $netObj = Get-VergeNetwork -Name 'Internal' -Server $Server -ErrorAction SilentlyContinue
            if (-not $netObj) {
                $allNets = Get-VergeNetwork -Server $Server
                $netObj = $allNets | Where-Object { $_.Type -eq 'Internal' } | Select-Object -First 1
            }
            if ($netObj) {
                $networkKey = $netObj.Key
                Write-Verbose "Using network '$($netObj.Name)' (Key: $networkKey)"
            }
        }

        if (-not $networkKey) {
            throw [System.InvalidOperationException]::new(
                "No network specified and no Internal network found. Use -Network to specify a network."
            )
        }

        # Build request body for vm_recipe_instances
        $body = @{
            recipe  = $recipe.id
            name    = $Name
            answers = @{
                HOSTNAME         = $Hostname
                YB_HOSTNAME      = $Hostname
                YB_CPU_CORES     = $Cores
                YB_RAM           = ($MemoryGB * 1024)  # Convert GB to MB
                YB_NIC_1         = $networkKey.ToString()
                YB_NIC_1_IP_TYPE = 'dhcp'
                YB_TIMEZONE      = 'America/New_York'
                YB_NTP           = 'time.nist.gov 0.pool.ntp.org 1.pool.ntp.org'
                YB_DOMAINNAME    = ''
            }
        }

        if ($AutoUpdate) {
            $body['auto_update'] = $true
        }

        # Confirm action
        if ($PSCmdlet.ShouldProcess($Name, "Deploy NAS service VM (Hostname: $Hostname, Network: $networkKey, Cores: $Cores, RAM: ${MemoryGB}GB)")) {
            try {
                Write-Verbose "Deploying NAS service VM '$Name' with hostname '$Hostname'"
                $response = Invoke-VergeAPI -Method POST -Endpoint 'vm_recipe_instances' -Body $body -Connection $Server

                Write-Verbose "NAS service VM deployment initiated"

                if ($PassThru) {
                    # Wait for the service to be created
                    $maxAttempts = 15
                    $attempt = 0
                    $service = $null

                    while ($attempt -lt $maxAttempts -and -not $service) {
                        $attempt++
                        Start-Sleep -Seconds 2
                        Write-Verbose "Waiting for NAS service to be created (attempt $attempt of $maxAttempts)"
                        $service = Get-VergeNASService -Name $Name -Server $Server -ErrorAction SilentlyContinue
                    }

                    if ($service) {
                        Write-Output $service
                    }
                    else {
                        Write-Warning "NAS service deployment initiated but service '$Name' not yet found. It may still be creating."
                    }
                }
            }
            catch {
                $PSCmdlet.ThrowTerminatingError(
                    [System.Management.Automation.ErrorRecord]::new(
                        [System.InvalidOperationException]::new("Failed to deploy NAS service '$Name': $($_.Exception.Message)"),
                        'NASServiceDeployFailed',
                        [System.Management.Automation.ErrorCategory]::InvalidOperation,
                        $Name
                    )
                )
            }
        }
    }
}