Public/Invoke-RunnerTarballDeploy.ps1

# ---------------------------------------------------------------------------
# Invoke-RunnerTarballDeploy
# Ensures a GitHub Actions runner tarball is present in the runner user's
# cache directory on a remote Linux host.
#
# Idempotent: returns immediately if the expected file is already present.
# If absent, any stale actions-runner-*.tar.gz files are purged first so
# the cache never accumulates old versions, then the tarball is fetched
# via curl from $TarUrl.
#
# All SSH commands run as $RunnerUser (via sudoers-permitted
# 'sudo -u $RunnerUser'), so the service user owns the cached file
# without a separate chown step.
#
# $CacheDir defaults to /home/$RunnerUser/cache when not supplied.
# $TarPath is always derived as $CacheDir/<filename from $TarUrl>.
#
# $Label is an optional string prepended to log and error messages
# (e.g. the VM name) so callers can identify which host is being
# addressed when running against multiple VMs.
#
# Complement to Invoke-RunnerTarballEnsure, which stages the tarball on
# the Windows host. This function places it on the Linux VM.
# ---------------------------------------------------------------------------

function Invoke-RunnerTarballDeploy {
    [CmdletBinding()]
    param(
        # Connected SSH.NET SshClient. Caller owns the lifecycle.
        [Parameter(Mandatory)]
        [object] $SshClient,

        # Full download URL of the tarball
        # (e.g. 'https://github.com/.../actions-runner-linux-x64-2.317.0.tar.gz'
        # or 'http://10.10.0.1:8745/actions-runner-linux-x64-2.317.0.tar.gz').
        [Parameter(Mandatory)]
        [string] $TarUrl,

        # Username of the runner service account that will own the cached
        # tarball (e.g. 'u-actions-runner').
        [Parameter(Mandatory)]
        [string] $RunnerUser,

        # Remote cache directory. Defaults to /home/$RunnerUser/cache.
        [Parameter()]
        [string] $CacheDir = '',

        # Optional prefix for log and error messages, e.g. a VM name.
        [Parameter()]
        [string] $Label = ''
    )

    $tarName   = $TarUrl.Split('/')[-1]
    if (-not $CacheDir) { $CacheDir = "/home/${RunnerUser}/cache" }
    $tarPath   = "${CacheDir}/${tarName}"
    $prefix    = if ($Label) { "[$Label] " } else { '' }

    $r = Invoke-SshClientCommand `
             -SshClient $SshClient `
             -Command   "sudo -u $RunnerUser mkdir -p '$CacheDir'" `
             -ErrorAction Stop
    if ($r.ExitStatus -ne 0) {
        throw "${prefix}Failed to create cache directory: $($r.Error)"
    }

    $check = Invoke-SshClientCommand `
                 -SshClient $SshClient `
                 -Command   "sudo -u $RunnerUser test -f '$tarPath'" `
                 -ErrorAction Stop
    if ($check.ExitStatus -eq 0) {
        Write-Host "${prefix}Tarball already cached: $tarName" -ForegroundColor Green
        return
    }

    Write-Host "${prefix}Downloading tarball: $tarName ..." -ForegroundColor Cyan

    # Purge stale versions before downloading so the cache directory does
    # not accumulate old binaries.
    $purge = Invoke-SshClientCommand `
                 -SshClient $SshClient `
                 -Command   "sudo -u $RunnerUser rm -f '$CacheDir'/actions-runner-*.tar.gz" `
                 -ErrorAction Stop
    if ($purge.ExitStatus -ne 0) {
        throw "${prefix}Failed to purge stale tarballs: $($purge.Error)"
    }

    # --connect-timeout: fail fast if TCP handshake stalls (e.g. firewall
    # drop with no RST).
    # --retry 3: covers transient connection failures.
    # --max-time absent: transfer duration is unpredictable for external
    # downloads (GitHub); for local file-server downloads the small size
    # makes it irrelevant.
    $dl = Invoke-SshClientCommand `
              -SshClient $SshClient `
              -Command   ("sudo -u $RunnerUser curl -fsSL " +
                          "--connect-timeout 15 --retry 3 --retry-delay 5 " +
                          "-o '$tarPath' '$TarUrl'") `
              -ErrorAction Stop
    if ($dl.ExitStatus -ne 0) {
        throw "${prefix}curl download failed: $($dl.Error)"
    }

    Write-Host "${prefix}Tarball downloaded: $tarName" -ForegroundColor Green
}