Public/System/Update-VergeCertificate.ps1

function Update-VergeCertificate {
    <#
    .SYNOPSIS
        Renews or regenerates a VergeOS SSL/TLS certificate.

    .DESCRIPTION
        Update-VergeCertificate triggers renewal of a Let's Encrypt certificate
        or regeneration of a self-signed certificate. This is useful when:
        - A Let's Encrypt certificate is approaching expiration
        - You want to force renewal before automatic renewal
        - You need to regenerate a self-signed certificate

    .PARAMETER Certificate
        A certificate object from Get-VergeCertificate. Accepts pipeline input.

    .PARAMETER Key
        The key (ID) of the certificate to renew/regenerate.

    .PARAMETER Domain
        The domain of the certificate to renew/regenerate.

    .PARAMETER Force
        Force renewal even if the certificate is not near expiration.

    .PARAMETER PassThru
        Return the updated certificate object.

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

    .EXAMPLE
        Update-VergeCertificate -Key 1

        Renews/regenerates the certificate with Key 1.

    .EXAMPLE
        Get-VergeCertificate -Type LetsEncrypt | Update-VergeCertificate

        Renews all Let's Encrypt certificates.

    .EXAMPLE
        Get-VergeCertificate | Where-Object { $_.DaysUntilExpiry -lt 30 } | Update-VergeCertificate

        Renews all certificates expiring within 30 days.

    .EXAMPLE
        Update-VergeCertificate -Domain "example.com" -PassThru

        Renews a certificate by domain and returns the updated certificate.

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

    .NOTES
        For Let's Encrypt certificates, ensure DNS and HTTP validation can succeed.
        Self-signed certificates will be regenerated with a new key pair.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByKey')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByCertificate')]
        [PSTypeName('Verge.Certificate')]
        [PSCustomObject]$Certificate,

        [Parameter(Mandatory, ParameterSetName = 'ByKey')]
        [int]$Key,

        [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByDomain')]
        [string]$Domain,

        [Parameter()]
        [switch]$Force,

        [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 {
        # Resolve certificate based on parameter set
        $targetCerts = switch ($PSCmdlet.ParameterSetName) {
            'ByKey' {
                Get-VergeCertificate -Key $Key -Server $Server
            }
            'ByDomain' {
                Get-VergeCertificate -Domain $Domain -Server $Server
            }
            'ByCertificate' {
                $Certificate
            }
        }

        foreach ($targetCert in $targetCerts) {
            if (-not $targetCert) {
                continue
            }

            # Build display string
            $certDisplay = "$($targetCert.Domain) (Key: $($targetCert.Key), Type: $($targetCert.Type))"

            # Check if renewal makes sense (warn but allow with -Force)
            if (-not $Force -and $targetCert.DaysUntilExpiry -and $targetCert.DaysUntilExpiry -gt 30) {
                Write-Warning "Certificate '$($targetCert.Domain)' expires in $($targetCert.DaysUntilExpiry) days. Use -Force to renew anyway."
                if (-not $PSCmdlet.ShouldProcess($certDisplay, "Renew Certificate (expires in $($targetCert.DaysUntilExpiry) days)")) {
                    continue
                }
            }

            $actionVerb = switch ($targetCert.TypeValue) {
                'letsencrypt' { 'Renew' }
                'self_signed' { 'Regenerate' }
                default { 'Update' }
            }

            if ($PSCmdlet.ShouldProcess($certDisplay, "$actionVerb Certificate")) {
                try {
                    $actionVerbing = switch ($targetCert.TypeValue) {
                        'letsencrypt' { 'Renewing' }
                        'self_signed' { 'Regenerating' }
                        default { 'Updating' }
                    }
                    $actionVerbed = switch ($targetCert.TypeValue) {
                        'letsencrypt' { 'renewed' }
                        'self_signed' { 'regenerated' }
                        default { 'updated' }
                    }

                    Write-Verbose "$actionVerbing certificate '$($targetCert.Domain)' (Key: $($targetCert.Key))"

                    # Send renewal request
                    $body = @{
                        renew = $true
                    }

                    $null = Invoke-VergeAPI -Method PUT -Endpoint "certificates/$($targetCert.Key)" -Body $body -Connection $Server

                    Write-Verbose "Certificate '$($targetCert.Domain)' $actionVerbed successfully"

                    if ($PassThru) {
                        # Wait for renewal to process
                        Start-Sleep -Seconds 2
                        Get-VergeCertificate -Key $targetCert.Key -Server $Server
                    }
                }
                catch {
                    $errorMessage = $_.Exception.Message
                    $actionVerbLower = $actionVerb.ToLower()
                    if ($errorMessage -match 'validation' -or $errorMessage -match 'challenge') {
                        Write-Error -Message "Failed to $actionVerbLower certificate '$($targetCert.Domain)': ACME validation failed. Check DNS/HTTP configuration." -ErrorId 'CertificateRenewalFailed'
                    }
                    else {
                        Write-Error -Message "Failed to $actionVerbLower certificate '$($targetCert.Domain)': $errorMessage" -ErrorId 'CertificateRenewalFailed'
                    }
                }
            }
        }
    }
}