Modules/businessdev.ALbuild.Containers/Private/Get-BcHostFreeDiskGb.ps1

function Get-BcHostFreeDiskGb {
    <#
    .SYNOPSIS
        Free/total space (GB) on the drive Docker stores containers on - for the container disk preflight
        and failure diagnostics.
    .DESCRIPTION
        A Business Central container copies the service tier, web client and apps into its writable layer
        and restores the demo database - many GB - on the drive that holds Docker's data-root. On a
        nearly-full drive the service tier cannot start and the container exits with a cryptic
        "Failed to start service" after a long wait. This resolves that drive via 'docker info'
        (DockerRootDir), falling back to the system drive when it cannot be read, and returns its free and
        total GiB. Returns $null only when no drive/space could be determined (so callers skip the check
        rather than block on a false negative).
    .PARAMETER DockerExecutable
        The Docker executable to query (default 'docker').
    .OUTPUTS
        PSCustomObject { Drive; FreeGb; TotalGb } or $null.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param([string] $DockerExecutable = 'docker')

    $rootDir = $null
    try {
        $info = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -Arguments @('info', '--format', '{{.DockerRootDir}}')
        if ($info.Success) { $rootDir = "$($info.StdOut)".Trim() }
    }
    catch { Write-Verbose "Could not read Docker's data-root directory: $($_.Exception.Message)" }

    # Fall back to the system drive; Docker's data-root is on it by default anyway.
    $probe = if (-not [string]::IsNullOrWhiteSpace($rootDir)) { $rootDir }
    else { ([System.Environment]::GetEnvironmentVariable('SystemDrive') + '\') }

    try {
        $driveRoot = [System.IO.Path]::GetPathRoot($probe)
        if ([string]::IsNullOrWhiteSpace($driveRoot)) { return $null }
        $di = New-Object System.IO.DriveInfo($driveRoot)
        if (-not $di.IsReady) { return $null }
        return [PSCustomObject]@{
            Drive   = $di.Name
            FreeGb  = [math]::Round($di.AvailableFreeSpace / 1GB, 1)
            TotalGb = [math]::Round($di.TotalSize / 1GB, 1)
        }
    }
    catch { Write-Verbose "Could not read free disk space for '$probe': $($_.Exception.Message)"; return $null }
}

function Get-BcLowDiskHint {
    <#
    .SYNOPSIS
        Returns an actionable "low disk space" hint string when the Docker drive is below a threshold,
        else an empty string. Appended to container-readiness failure messages so the true cause
        (a full drive, which makes the BC service tier fail to start) is visible instead of a bare
        "Failed to start service".
    .PARAMETER DockerExecutable
        The Docker executable to query (default 'docker').
    .PARAMETER ThresholdGb
        Emit the hint when free space is below this many GB. Default 20.
    .OUTPUTS
        System.String - the hint (with a leading newline) or ''.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [string] $DockerExecutable = 'docker',
        [int] $ThresholdGb = 20
    )

    $disk = Get-BcHostFreeDiskGb -DockerExecutable $DockerExecutable
    if ($disk -and $disk.FreeGb -lt $ThresholdGb) {
        return "$([Environment]::NewLine)Likely cause: low disk space - only $($disk.FreeGb) GB free on drive $($disk.Drive). A BC container needs well over that to restore its database and start the service tier. Free space on that drive (e.g. 'docker system prune -a --volumes', remove old BC versions under the artifact cache) or move Docker's data-root / the artifact cache to a larger drive."
    }
    return ''
}