跳转至

scripts\fetch-monitoring.ps1

源码路径:scripts\fetch-monitoring.ps1 · 216 行

# 下载监控栈组件到 libs:VictoriaMetrics / Loki / Grafana Alloy。
#
# 为什么**单独一个脚本**而不是加进 fetch-libs.ps1:
#   那个文件里「函数定义」和「主体执行」是交错的(函数之间夹着 body),
#   往里插一个带 -Switch 的分支,调度块放哪都不对 ——
#   放文件尾:主体先跑了;放 param 后:它依赖的 Fetch-File 还没定义。
#   与其继续搬块,不如独立成一个文件,两边都不受影响。
#
# HardwareExporterWindows **故意不自动下载**:我没核实到它的仓库地址,
# 不猜 URL(猜错的话你会照抄一个 404 进配置)。脚本末尾会告诉你手动放哪。
#
# 用法:
#   .\scripts\fetch-monitoring.ps1                 # 下到 <仓库根>\dist\libs
#   .\scripts\fetch-monitoring.ps1 -Root C:\somewhere
#   .\scripts\fetch-monitoring.ps1 -Force          # 已存在也重下
#
# 版本不硬编码:从 GitHub API 取 latest release 再挑资产。
# 写死版本号的脚本会随上游发版慢慢烂掉。

[CmdletBinding()]
param(
    [string]$Root = '',
    [switch]$Force,
    # GitHub 加速前缀。'auto' = 先直连,失败再依次试下面的镜像。
    # 也可以指定一个,例如 -Mirror https://ghfast.top
    [string]$Mirror = 'auto',
    # 只下某一个组件(按目录名),例如 -Only victoriametrics
    [string]$Only = ''
)

$ErrorActionPreference = 'Stop'

# 镜像前缀表(按尝试顺序)。每个前缀的用法是 <前缀>/<原始 GitHub URL>。
# 放在这里而不是写死一个:任何一个都可能哪天挂掉,依次重试比改代码强。
$MirrorList = switch ($Mirror) {
    'auto' { @('', 'https://ghfast.top', 'https://ghproxy.net', 'https://gh-proxy.com') }
    ''     { @('') }
    default  { @($Mirror) }
}

$root    = if ($Root) { $Root } else { Split-Path $PSScriptRoot -Parent }
$libsDir = Join-Path $root 'libs'
$cache   = Join-Path $root '.cache'
foreach ($d in @($libsDir, $cache)) { New-Item -ItemType Directory -Force -Path $d | Out-Null }

Write-Host "程序根: $root" -ForegroundColor Cyan
Write-Host ''

function Get-LatestAsset {
    param([string]$Repo, [string]$Pattern, [string]$Exclude = "")
    $api = "https://api.github.com/repos/$Repo/releases/latest"
    try {
        $r = Invoke-RestMethod -Uri $api -Headers @{ 'User-Agent' = 'nfarms-fetch' } -TimeoutSec 40
    } catch {
        Write-Host "  取 $Repo 的 latest release 失败: $($_.Exception.Message)" -ForegroundColor Yellow
        return $null
    }
    # **排除项是必须的**,而且得支持多个:
    #   `...-cluster.zip` = 多节点版本
    #   `...-fips.zip`    = FIPS 加密合规版
    # 通配符会贪婪地先匹到它们 —— 实测先匹到 cluster,排除后又匹到 fips。
    # 两个都不是单机生产该用的东西。Exclude 用逗号分隔多个模式。
    # `{tag}` 换成实际 tag,这样能**精确拼出**文件名,不靠通配。
    # 这一条是踩了三次才定下来的:
    #   通配 victoria-metrics-windows-amd64-*.zip → 匹到 cluster
    #   排除 cluster 后又匹到 fips
    #   再排除 fips 后又匹到 enterprise(没 license 直接拒绝启动)
    # 而同一 release 里 OSS 版是 7.12 MB、enterprise 是 14.58 MB ——
    # 通配 + Select -First 1 永远在赌资产顺序。
    $Pattern = $Pattern.Replace("{tag}", $r.tag_name)
    $a = $r.assets | Where-Object {
        $nm = $_.name
        if ($nm -notlike $Pattern) { return $false }
        foreach ($ex in ($Exclude -split ',')) {
            if ($ex.Trim() -and $nm -like $ex.Trim()) { return $false }
        }
        return $true
    } | Select-Object -First 1
    if (-not $a) {
        Write-Host "  $Repo $($r.tag_name) 里没有匹配 '$Pattern' 的资产" -ForegroundColor Yellow
        return $null
    }
    [pscustomobject]@{
        Url       = $a.browser_download_url
        Name      = $a.name
        Version   = $r.tag_name
        SizeMB    = [math]::Round($a.size / 1MB, 1)
        SizeBytes = [int64]$a.size
    }
}

$comps = @(
    @{ Name = 'VictoriaMetrics'; Dir = 'victoriametrics'; Repo = 'VictoriaMetrics/VictoriaMetrics'
       Asset = 'victoria-metrics-windows-amd64-{tag}.zip'; Exclude = ''; Keep = 'victoria-metrics*.exe' }
    @{ Name = 'Loki';            Dir = 'loki';            Repo = 'grafana/loki'
       Asset = 'loki-windows-amd64.exe.zip';          Exclude = ''; Keep = 'loki.exe' }
    @{ Name = 'Grafana Alloy';   Dir = 'alloy';           Repo = 'grafana/alloy'
       Asset = 'alloy-windows-amd64.exe.zip';         Exclude = ''; Keep = 'alloy.exe' }
)

foreach ($c in $comps) {
    if ($Only -and $c.Dir -ne $Only) { continue }
    $dest = Join-Path $libsDir $c.Dir
    $have = if (Test-Path $dest) {
        Get-ChildItem $dest -File -Filter '*.exe' -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -like $c.Keep } | Select-Object -First 1
    } else { $null }

    if ($have -and -not $Force) {
        Write-Host "  [跳过] $($c.Name) 已存在: $($have.FullName)" -ForegroundColor DarkGray
        continue
    }

    $asset = Get-LatestAsset -Repo $c.Repo -Pattern $c.Asset -Exclude $c.Exclude
    if (-not $asset) { continue }

    Write-Host "  $($c.Name) $($asset.Version)  ($($asset.SizeMB) MB)" -ForegroundColor Cyan
    $zip = Join-Path $cache $asset.Name

    # **按精确字节数校验,不是"存在且非空"**。
    # 只判非空会把半截文件永久缓存:实测一次下载断在 4.01 MB(期望 15.2 MB),
    # 下次运行看到"文件在、大小非零"就跳过下载,然后解压报一句莫名其妙的
    # "End of Central Directory record could not be found"。
    # 那次失败排查了半天,根因就是这个守卫太松。
    $needDownload = $Force -or -not (Test-Path $zip)
    if (-not $needDownload) {
        $actual = (Get-Item $zip).Length
        if ($actual -ne $asset.SizeBytes) {
            Write-Host "    缓存不完整($([math]::Round($actual/1MB,2)) MB / 期望 $($asset.SizeMB) MB),删除重下" -ForegroundColor Yellow
            Remove-Item $zip -Force -ErrorAction SilentlyContinue
            $needDownload = $true
        }
    }
    if ($needDownload) {
        # 依次试每个镜像,**只要拿到字节数正确的文件就停**。
        # 校验放在循环里而不是外面:直连常常"下了一半才断",不校验就会
        # 拿半截文件去解压,然后报一句看不懂的 zip 错误。
        $got = $false
        foreach ($m in $MirrorList) {
            $url = if ($m) { "$m/$($asset.Url)" } else { $asset.Url }
            $tag = if ($m) { $m } else { '直连' }
            Write-Host "    尝试 $tag" -ForegroundColor DarkGray
            $old = $ProgressPreference
            # 不关进度条,上百 MB 的文件会慢好几倍 —— 实测差别,不是洁癖
            $ProgressPreference = 'SilentlyContinue'
            try {
                Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing -TimeoutSec 1800
            } catch {
                Write-Host "      失败: $($_.Exception.Message)" -ForegroundColor DarkGray
            } finally {
                $ProgressPreference = $old
            }
            if ((Test-Path $zip) -and (Get-Item $zip).Length -eq $asset.SizeBytes) {
                Write-Host "      成功($($asset.SizeMB) MB)" -ForegroundColor Green
                $got = $true
                break
            }
            # 短了就是没下全:删掉,换下一个镜像(留着会被下次误判成已下好)
            if (Test-Path $zip) {
                $short = [math]::Round((Get-Item $zip).Length / 1MB, 2)
                Write-Host "      不完整($short / $($asset.SizeMB) MB)" -ForegroundColor Yellow
                Remove-Item $zip -Force -ErrorAction SilentlyContinue
            }
        }
        if (-not $got) {
            Write-Host "    所有镜像都没成功。请手动下载后放进 .cache:" -ForegroundColor Red
            Write-Host "      $($asset.Name)   ($($asset.SizeMB) MB)" -ForegroundColor Red
            continue
        }

        # 下完再校验一次:连接被重置时 Invoke-WebRequest 有时**不报错**,
        # 只是文件短了一截。不校验就会一路走到解压才炸。
        if (-not (Test-Path $zip) -or (Get-Item $zip).Length -ne $asset.SizeBytes) {
            $got = if (Test-Path $zip) { [math]::Round((Get-Item $zip).Length/1MB,2) } else { 0 }
            Write-Host "    下载不完整:$got MB / 期望 $($asset.SizeMB) MB —— 已删除,请重试或换网络" -ForegroundColor Red
            Remove-Item $zip -Force -ErrorAction SilentlyContinue
            continue
        }
    }

    New-Item -ItemType Directory -Force -Path $dest | Out-Null
    try {
        Expand-Archive -LiteralPath $zip -DestinationPath $dest -Force
    } catch {
        Write-Host "    解压失败: $($_.Exception.Message)" -ForegroundColor Red
        continue
    }

    # 上游 zip 常多套一层目录:把里面的文件提到 dest 根(与 fetch-libs 处理 ffmpeg 一致)
    Get-ChildItem $dest -Directory -ErrorAction SilentlyContinue | ForEach-Object {
        Get-ChildItem $_.FullName -File | Move-Item -Destination $dest -Force
        Remove-Item $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
    }
    # 只留主程序,别把整个发行包摊在 libs 里
    Get-ChildItem $dest -File | Where-Object { $_.Name -notlike $c.Keep } |
        Remove-Item -Force -ErrorAction SilentlyContinue

    $exe = Get-ChildItem $dest -File -Filter '*.exe' -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -like $c.Keep } | Select-Object -First 1
    if ($exe) {
        Write-Host "    -> $($exe.FullName)  ($([math]::Round($exe.Length/1MB,1)) MB)" -ForegroundColor Green
    } else {
        Write-Host "    解压后没找到匹配 '$($c.Keep)' 的可执行文件 —— 上游改了资产结构?" -ForegroundColor Yellow
    }
}

Write-Host ''
Write-Host '  Grafana 本体**不在自动下载里**:' -ForegroundColor Yellow
Write-Host '    体积大(150MB+),且通常是独立安装的服务。从 https://grafana.com/grafana/download?platform=windows 装。' -ForegroundColor Yellow
Write-Host '    装好后本程序只需知道它的地址(Grafana 页里填 URL)。' -ForegroundColor Yellow
Write-Host ''
Write-Host '  HardwareExporterWindows **需要你手动放**:' -ForegroundColor Yellow
Write-Host "    我没核实到它的仓库地址,不猜 URL。下载后解到:$libsDir\hardware-exporter\" -ForegroundColor Yellow
Write-Host '    放好后 Environment 页会探测到它。' -ForegroundColor Yellow
Write-Host ''
Write-Host '监控栈处理完成。' -ForegroundColor Green