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