支持的操作系统
Gorelo 仅支持供应商(Microsoft/Apple)当前支持的操作系统。先决条件检查
Gorelo 为 Windows 和 macOS 提供了先决条件检查。它会确认设备满足要求并且可以访问 Gorelo 的服务,最后给出明确的 PASS 或 FAIL 结果。 两个脚本都会要求你选择 Gorelo 区域:USW(美国西部)或 AUE(澳大利亚东部)。然后,脚本会检查该区域对应的 Gorelo 服务器、IoT Hub 和下载源。以交互方式运行脚本时,脚本会提示你选择一个区域。要跳过提示(例如用于无人值守运行),请事先将GORELO_REGION 环境变量设置为 USW 或 AUE。
- Windows
- macOS
- 在遇到代理问题的计算机上打开提升权限的 PowerShell 窗口。
- 复制下面的整个脚本,将其粘贴到窗口中,然后按 Enter:
gorelo-check-windows.ps1
$scriptContent = @'
# Gorelo Agent Pre-Installation Check Script
# Updated: 2026-03-22
# Checks system requirements, connectivity (including Cloudflare R2), TLS, and permissions
function Write-ColorOutput {
param (
[string]$Message,
[string]$Color = "White",
[bool]$IsError = $false
)
if ($IsError) {
Write-Host $Message -ForegroundColor Red
} else {
Write-Host $Message -ForegroundColor $Color
}
}
$hasError = $false
Write-Host ""
Write-Host "======================================" -ForegroundColor Cyan
Write-Host " Gorelo Agent Pre-Installation Check" -ForegroundColor Cyan
Write-Host "======================================" -ForegroundColor Cyan
Write-Host ""
# --- REGION SELECTION ---
# Run via Invoke-Expression, so there are no script parameters. Set $env:GORELO_REGION = "USW" or "AUE"
# beforehand to skip the prompt (e.g. for unattended runs).
$regions = @{
"USW" = "usw"
"AUE" = "aue"
}
$region = $env:GORELO_REGION
if ($region) { $region = $region.Trim().ToUpper() }
while (-not $regions.ContainsKey("$region")) {
Write-Host "Select your Gorelo region:" -ForegroundColor Cyan
Write-Host " 1) USW (United States West)"
Write-Host " 2) AUE (Australia East)"
$choice = (Read-Host "Enter 1 or 2 (or USW / AUE)").Trim().ToUpper()
switch ($choice) {
"1" { $region = "USW" }
"2" { $region = "AUE" }
default { $region = $choice }
}
if (-not $regions.ContainsKey("$region")) {
Write-ColorOutput "Invalid selection '$choice'. Please choose USW or AUE." -Color Yellow
}
}
$regionCode = $regions[$region]
Write-ColorOutput "INFO: Region selected: $region" -Color White
Write-Host ""
# --- SYSTEM CHECKS ---
Write-Host "[System Checks]" -ForegroundColor Cyan
# OS Version
$osInfo = Get-CimInstance Win32_OperatingSystem
Write-ColorOutput "INFO: $($osInfo.Caption) ($($osInfo.Version))" -Color White
# PowerShell version
$requiredVersion = [version]'5.1'
$installedVersion = $PSVersionTable.PSVersion
if ($installedVersion -lt $requiredVersion) {
Write-ColorOutput "FAIL: PowerShell version $($installedVersion) is below required $requiredVersion" -IsError $true
$hasError = $true
} else {
Write-ColorOutput "PASS: PowerShell version $($installedVersion)" -Color Green
}
# 64-bit check
if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64") {
Write-ColorOutput "PASS: 64-bit processor architecture" -Color Green
} else {
Write-ColorOutput "FAIL: Non-64-bit architecture detected: $($env:PROCESSOR_ARCHITECTURE)" -IsError $true
$hasError = $true
}
# 64-bit Program Files
if ($Env:ProgramW6432) {
Write-ColorOutput "PASS: 64-bit Program Files folder exists ($($Env:ProgramW6432))" -Color Green
} else {
Write-ColorOutput "FAIL: 64-bit Program Files folder not found" -IsError $true
$hasError = $true
}
# System drive space
$systemDrive = $env:SystemDrive
if ($systemDrive) {
$driveInfo = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Name -eq $systemDrive[0] }
if ($driveInfo) {
$freeSpaceMB = [math]::Round($driveInfo.Free / 1MB, 2)
if ($freeSpaceMB -le 300) {
Write-ColorOutput "FAIL: Free space is less than 300 MB (Current: $freeSpaceMB MB)" -IsError $true
$hasError = $true
} else {
Write-ColorOutput "PASS: System drive space ($freeSpaceMB MB free)" -Color Green
}
} else {
Write-ColorOutput "FAIL: Unable to get drive info" -IsError $true
$hasError = $true
}
}
# Administrator check
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-ColorOutput "PASS: Running as Administrator" -Color Green
} else {
Write-ColorOutput "FAIL: Not running as Administrator (required for installation)" -IsError $true
$hasError = $true
}
Write-Host ""
Write-Host "[TLS / Security Checks]" -ForegroundColor Cyan
# TLS 1.2 check
$tlsProtocols = [Net.ServicePointManager]::SecurityProtocol
if ($tlsProtocols -band [Net.SecurityProtocolType]::Tls12) {
Write-ColorOutput "PASS: TLS 1.2 is enabled in SecurityProtocol ($tlsProtocols)" -Color Green
} else {
Write-ColorOutput "WARN: TLS 1.2 not in default SecurityProtocol ($tlsProtocols). Enabling..." -Color Yellow
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Write-ColorOutput "PASS: TLS 1.2 enabled successfully" -Color Green
} catch {
Write-ColorOutput "FAIL: Could not enable TLS 1.2. Error: $_" -IsError $true
$hasError = $true
}
}
# Ensure TLS 1.2 is active for all subsequent web requests
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Write-Host ""
Write-Host "[Connectivity Checks]" -ForegroundColor Cyan
# URL connectivity checks
$urls = @(
@{
Url = "https://www.google.com/"
ExpectedStatus = 200
Description = "Internet connectivity"
},
@{
Url = "https://gw.$regionCode.gorelo.tech/rmm-login/api/login/_health"
ExpectedStatus = 200
Description = "Gorelo Gateway ($region)"
},
@{
Url = "https://sr.rmm.pod1.$regionCode.gorelo.tech/api/_health"
ExpectedStatus = 200
Description = "Gorelo API ($region)"
},
@{
Url = "https://hub.rmm.pod1.$regionCode.gorelo.tech/hc"
ExpectedStatus = 200
Description = "Gorelo Hub ($region)"
}
)
$urlChecksPassed = $true
foreach ($urlCheck in $urls) {
try {
$response = Invoke-WebRequest -Uri $urlCheck.Url -Method Get -UseBasicParsing -TimeoutSec 15
if ($response.StatusCode -eq $urlCheck.ExpectedStatus) {
Write-ColorOutput "PASS: $($urlCheck.Description) ($($urlCheck.Url))" -Color Green
} else {
Write-ColorOutput "WARN: $($urlCheck.Description) returned $($response.StatusCode) (expected $($urlCheck.ExpectedStatus))" -Color Yellow
}
} catch {
Write-ColorOutput "FAIL: $($urlCheck.Description) ($($urlCheck.Url)). Error: $($_.Exception.Message)" -IsError $true
$urlChecksPassed = $false
$hasError = $true
}
}
# Cloudflare R2 connectivity (the installer download source)
Write-Host ""
Write-Host "[Cloudflare R2 Download Check]" -ForegroundColor Cyan
$r2Domain = "600c23d50c6ee4462ecb200a47f6b627.r2.cloudflarestorage.com"
try {
$dnsResult = [System.Net.Dns]::GetHostAddresses($r2Domain)
if ($dnsResult.Count -gt 0) {
Write-ColorOutput "PASS: DNS resolution for Cloudflare R2 ($r2Domain -> $($dnsResult[0]))" -Color Green
} else {
Write-ColorOutput "FAIL: DNS resolution returned no addresses for $r2Domain" -IsError $true
$hasError = $true
}
} catch {
Write-ColorOutput "FAIL: Cannot resolve Cloudflare R2 domain ($r2Domain). Error: $($_.Exception.Message)" -IsError $true
$hasError = $true
}
# Test HTTPS connection to R2 (HEAD request to bucket root - will get 403 which is fine, proves connectivity)
try {
$r2Response = Invoke-WebRequest -Uri "https://$r2Domain/" -Method Head -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop
Write-ColorOutput "PASS: HTTPS connection to Cloudflare R2" -Color Green
} catch [System.Net.WebException] {
$statusCode = $null
if ($_.Exception.Response) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
if ($statusCode -eq 400 -or $statusCode -eq 403 -or $statusCode -eq 404 -or $statusCode -eq 405) {
# 400/403/404/405 means we connected successfully - just not authorized (expected without presigned URL)
Write-ColorOutput "PASS: HTTPS connection to Cloudflare R2 (got HTTP $statusCode - connection works, auth expected to fail without presigned URL)" -Color Green
} else {
Write-ColorOutput "FAIL: HTTPS connection to Cloudflare R2 ($r2Domain). Error: $($_.Exception.Message)" -IsError $true
$hasError = $true
}
} catch {
Write-ColorOutput "FAIL: HTTPS connection to Cloudflare R2 ($r2Domain). Error: $($_.Exception.Message)" -IsError $true
$hasError = $true
}
# Azure IoT Hub connectivity (needed post-install for agent communication)
Write-Host ""
Write-Host "[Post-Install Connectivity (IoT Hub)]" -ForegroundColor Cyan
$iotHubsByRegion = @{
"USW" = "gorelo.azure-devices.net"
"AUE" = "gorelo-aue.azure-devices.net"
}
$iotHubDomains = @(
$iotHubsByRegion[$region]
)
foreach ($domain in $iotHubDomains) {
try {
$dnsResult = [System.Net.Dns]::GetHostAddresses($domain)
if ($dnsResult.Count -gt 0) {
Write-ColorOutput "PASS: DNS resolution for IoT Hub ($domain)" -Color Green
}
} catch {
Write-ColorOutput "WARN: Cannot resolve IoT Hub domain ($domain). Agent may not connect after install." -Color Yellow
}
}
Write-Host ""
Write-Host "[Permission Checks]" -ForegroundColor Cyan
# Registry read/write
try {
$registryPath = "HKLM:\SOFTWARE\Gorelo"
if (-not (Test-Path $registryPath)) {
New-Item -Path $registryPath -Force -ErrorAction Stop | Out-Null
}
Set-ItemProperty -Path $registryPath -Name "PreCheck" -Value "test" -Type String -ErrorAction Stop
$testVal = (Get-ItemProperty -Path $registryPath -Name "PreCheck" -ErrorAction Stop).PreCheck
Remove-ItemProperty -Path $registryPath -Name "PreCheck" -Force -ErrorAction Stop
Write-ColorOutput "PASS: Registry read/write permission" -Color Green
} catch {
Write-ColorOutput "FAIL: Registry read/write permission. Error: $_" -IsError $true
$hasError = $true
}
# Event log
$logName = 'Application'
$sourceName = 'GoreloRmmTest'
try {
if (-not [System.Diagnostics.EventLog]::SourceExists($sourceName)) {
New-EventLog -LogName $logName -Source $sourceName -ErrorAction Stop
}
Write-EventLog -LogName $logName -Source $sourceName -EventId 0 -EntryType Information -Message "Gorelo pre-check test event" -ErrorAction Stop
Write-ColorOutput "PASS: Event log read/write permission" -Color Green
} catch {
Write-ColorOutput "FAIL: Event log read/write permission. Error: $_" -IsError $true
$hasError = $true
}
# PATH check
$path = [System.Environment]::GetEnvironmentVariable("PATH")
$system32Check = $path -like "*system32*"
$psCheck = $path -like "*System32\WindowsPowerShell\v1.0*"
if ($system32Check -and $psCheck) {
Write-ColorOutput "PASS: PATH contains system32 and PowerShell paths" -Color Green
} else {
$missing = @()
if (-not $system32Check) { $missing += "system32" }
if (-not $psCheck) { $missing += "WindowsPowerShell" }
Write-ColorOutput "FAIL: PATH missing: $($missing -join ', ')" -IsError $true
$hasError = $true
}
Write-Host ""
Write-Host "[Time Sync Check]" -ForegroundColor Cyan
# NTP time check
function Get-NTPTime {
try {
$NTPServer = "time.windows.com"
$NTPData = New-Object byte[] 48
$NTPData[0] = 27
$Socket = New-Object Net.Sockets.Socket([Net.Sockets.AddressFamily]::InterNetwork, [Net.Sockets.SocketType]::Dgram, [Net.Sockets.ProtocolType]::Udp)
$Socket.ReceiveTimeout = 5000
$Socket.SendTimeout = 5000
$Socket.Connect($NTPServer, 123)
$null = $Socket.Send($NTPData)
$null = $Socket.Receive($NTPData)
$Socket.Close()
$IntPart = [BitConverter]::ToUInt32($NTPData[43..40], 0)
$FracPart = [BitConverter]::ToUInt32($NTPData[47..44], 0)
$Milliseconds = ($IntPart * 1000) + (($FracPart * 1000) / 0x100000000)
return (New-Object DateTime(1900, 1, 1, 0, 0, 0, [DateTimeKind]::Utc)).AddMilliseconds($Milliseconds)
} catch {
return $null
}
}
$ntpTime = Get-NTPTime
if ($ntpTime) {
$currentTimeZone = Get-TimeZone
$expectedTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($ntpTime, $currentTimeZone)
$currentTime = Get-Date
$timeDifference = ($currentTime - $expectedTime).TotalMinutes
if ([Math]::Abs($timeDifference) -gt 5) {
Write-ColorOutput "FAIL: Time mismatch! System: $($currentTime.ToString('yyyy-MM-dd HH:mm:ss')) | NTP: $($expectedTime.ToString('yyyy-MM-dd HH:mm:ss')) | Diff: $([Math]::Round($timeDifference, 1)) min" -IsError $true
$hasError = $true
} else {
Write-ColorOutput "PASS: Time sync OK (diff: $([Math]::Round($timeDifference, 1)) min) | TZ: $($currentTimeZone.Id)" -Color Green
}
} else {
Write-ColorOutput "WARN: Could not reach NTP server (time.windows.com:123 UDP) - skipping time check" -Color Yellow
}
# --- EXISTING INSTALLATION CHECK ---
Write-Host ""
Write-Host "[Existing Installation]" -ForegroundColor Cyan
$goreloDir = "$Env:ProgramW6432\Gorelo"
if (Test-Path $goreloDir) {
$goreloProcesses = @("Gorelo.Rmm.Installer", "Gorelo.RemoteManagement.Shell", "Gorelo.RemoteManagement.Agent")
$processList = (Get-Process).ProcessName
$runningProcesses = $goreloProcesses | Where-Object { $_ -in $processList }
if ($runningProcesses.Count -gt 0) {
Write-ColorOutput "INFO: Gorelo agent is already installed and running ($($runningProcesses -join ', '))" -Color White
} else {
Write-ColorOutput "INFO: Gorelo directory exists but agent processes are not running" -Color Yellow
}
} else {
Write-ColorOutput "INFO: No existing Gorelo installation found (clean install)" -Color White
}
# --- SUMMARY ---
Write-Host ""
Write-Host "======================================" -ForegroundColor Cyan
if ($hasError) {
Write-ColorOutput "RESULT: FAIL - One or more checks failed" -IsError $true
Write-Host "Fix the issues above and re-run this script." -ForegroundColor Yellow
} else {
Write-ColorOutput "RESULT: PASS - All checks passed. Ready to install." -Color Green
}
Write-Host "======================================" -ForegroundColor Cyan
Write-Host ""
'@
$scriptPath = Join-Path $env:TEMP "gorelo-check-windows.ps1"
Set-Content -Path $scriptPath -Value $scriptContent
& $scriptPath
Remove-Item $scriptPath -Force
- 出现提示时选择你的区域(USW 或 AUE)。
- 使用下表解决任何失败,并确保你的修复在 PowerShell 会话和重启之后依然有效。
| 检查 | ❌ 失败消息 | ✅ 故障排除 |
|---|---|---|
| PowerShell 版本检查 | FAIL: PowerShell version X.X is below the required version 5.1 |
|
| 系统驱动器空间检查 |
|
|
| 处理器体系结构检查 | FAIL: Non-64-bit processor architecture detected: X |
|
| 注册表权限检查 | FAIL: Registry read/write permission check. Error: [specific error] |
|
| URL 可访问性检查 | FAIL: URL check for [Description] ([URL]). Error: [specific error] | 检查你所选区域的 Gorelo 网关、API 和 Hub 服务器。
|
| Cloudflare R2 下载检查 |
|
|
| 事件日志权限检查 | FAIL: Event log read/write permission check. Error: [specific error] |
|
| 环境变量检查 |
|
|
| 时间同步检查 | FAIL: Time mismatch detected! [time details] |
|
gorelo.azure-devices.net,AUE 为 gorelo-aue.azure-devices.net)。如果 IoT Hub 无法解析,脚本会显示警告而不是失败。该警告不会阻止安装程序检查通过,但代理可能在安装后无法连接,因此值得修复。- 在遇到代理问题的 Mac 上打开终端。
- 复制下面的整个命令,将其粘贴到窗口中,然后按 Enter。由于检查需要以 root 身份运行,系统会要求你输入密码。
gorelo-check-mac.sh
cat > /tmp/gorelo-check-mac.sh <<'EOF'
#!/bin/bash
# Gorelo Agent Pre-Installation Check Script (macOS)
# Updated: 2026-09-23
# Checks system requirements, connectivity (including Cloudflare R2), TLS, and permissions
#
# Run: curl -fsSL <raw-url>/gorelo-check-mac.sh | sudo bash
# Skip the region prompt: curl -fsSL <raw-url>/gorelo-check-mac.sh | sudo GORELO_REGION=AUE bash
#
# Written for the bash 3.2 that ships with macOS (no associative arrays).
RED=$'\033[31m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; CYAN=$'\033[36m'; RESET=$'\033[0m'
has_error=0
pass() { echo "${GREEN}PASS: $*${RESET}"; }
fail() { echo "${RED}FAIL: $*${RESET}"; has_error=1; }
warn() { echo "${YELLOW}WARN: $*${RESET}"; }
info() { echo "INFO: $*"; }
section() { echo; echo "${CYAN}[$*]${RESET}"; }
echo
echo "${CYAN}======================================${RESET}"
echo "${CYAN} Gorelo Agent Pre-Installation Check${RESET}"
echo "${CYAN}======================================${RESET}"
echo
# --- REGION SELECTION ---
# When piped (curl | bash) stdin is the script itself, so the prompt reads from /dev/tty.
region=$(echo "${GORELO_REGION:-}" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')
while [ "$region" != "USW" ] && [ "$region" != "AUE" ]; do
if [ ! -r /dev/tty ]; then
echo "${RED}No terminal to prompt on. Set GORELO_REGION=USW or GORELO_REGION=AUE.${RESET}"
exit 2
fi
echo "${CYAN}Select your Gorelo region:${RESET}"
echo " 1) USW (United States West)"
echo " 2) AUE (Australia East)"
printf "Enter 1 or 2 (or USW / AUE): "
read -r choice < /dev/tty
choice=$(echo "$choice" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')
case "$choice" in
1) region="USW" ;;
2) region="AUE" ;;
*) region="$choice" ;;
esac
if [ "$region" != "USW" ] && [ "$region" != "AUE" ]; then
echo "${YELLOW}Invalid selection '$choice'. Please choose USW or AUE.${RESET}"
fi
done
if [ "$region" = "USW" ]; then
region_code="usw"
iot_hub="gorelo.azure-devices.net"
else
region_code="aue"
iot_hub="gorelo-aue.azure-devices.net"
fi
info "Region selected: $region"
# --- SYSTEM CHECKS ---
section "System Checks"
info "macOS $(sw_vers -productVersion) (build $(sw_vers -buildVersion))"
arch=$(uname -m)
if [ "$arch" = "arm64" ] || [ "$arch" = "x86_64" ]; then
pass "64-bit processor architecture ($arch)"
else
fail "Unsupported architecture detected: $arch"
fi
# Free space on the system volume (df -k: 1K blocks, column 4 = available)
free_kb=$(df -k / | awk 'NR==2 {print $4}')
if [ -n "$free_kb" ]; then
free_mb=$((free_kb / 1024))
if [ "$free_mb" -le 300 ]; then
fail "Free space is less than 300 MB (Current: $free_mb MB)"
else
pass "System drive space ($free_mb MB free)"
fi
else
fail "Unable to get drive info"
fi
if [ "$(id -u)" -eq 0 ]; then
pass "Running as root (sudo)"
else
fail "Not running as root (required for installation) - re-run with sudo"
fi
# --- TLS / SECURITY CHECKS ---
section "TLS / Security Checks"
if curl -s -o /dev/null --tlsv1.2 --max-time 15 "https://www.google.com/"; then
pass "TLS 1.2 connections work ($(curl --version | head -n 1 | awk '{print $1, $2}'))"
else
fail "Could not make a TLS 1.2 connection"
fi
# --- CONNECTIVITY CHECKS ---
section "Connectivity Checks"
check_url() {
# $1 = url, $2 = description, $3 = expected status
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "$1")
if [ "$code" = "000" ]; then
fail "$2 ($1). Could not connect"
elif [ "$code" = "$3" ]; then
pass "$2 ($1)"
else
warn "$2 returned $code (expected $3)"
fi
}
check_url "https://www.google.com/" "Internet connectivity" 200
check_url "https://gw.$region_code.gorelo.tech/rmm-login/api/login/_health" "Gorelo Gateway ($region)" 200
check_url "https://sr.rmm.pod1.$region_code.gorelo.tech/api/_health" "Gorelo API ($region)" 200
check_url "https://hub.rmm.pod1.$region_code.gorelo.tech/hc" "Gorelo Hub ($region)" 200
# --- CLOUDFLARE R2 (installer download source, same for all regions) ---
section "Cloudflare R2 Download Check"
r2_domain="600c23d50c6ee4462ecb200a47f6b627.r2.cloudflarestorage.com"
r2_ip=$(dscacheutil -q host -a name "$r2_domain" | awk '/^ip_address:/ {print $2; exit}')
if [ -n "$r2_ip" ]; then
pass "DNS resolution for Cloudflare R2 ($r2_domain -> $r2_ip)"
else
fail "Cannot resolve Cloudflare R2 domain ($r2_domain)"
fi
# HEAD to the bucket root: 400/403/404/405 proves connectivity (no presigned URL, so auth fails)
r2_code=$(curl -s -o /dev/null -I -w "%{http_code}" --max-time 15 "https://$r2_domain/")
case "$r2_code" in
2??) pass "HTTPS connection to Cloudflare R2" ;;
400|403|404|405) pass "HTTPS connection to Cloudflare R2 (got HTTP $r2_code - connection works, auth expected to fail without presigned URL)" ;;
*) fail "HTTPS connection to Cloudflare R2 ($r2_domain). Got: $r2_code" ;;
esac
# --- IOT HUB (needed post-install for agent communication) ---
section "Post-Install Connectivity (IoT Hub)"
if dscacheutil -q host -a name "$iot_hub" | grep -q '^ip_address:'; then
pass "DNS resolution for IoT Hub ($iot_hub)"
else
warn "Cannot resolve IoT Hub domain ($iot_hub). Agent may not connect after install."
fi
# --- PERMISSION CHECKS ---
section "Permission Checks"
test_dir="/Library/Gorelo"
created_dir=0
if [ ! -d "$test_dir" ]; then
mkdir -p "$test_dir" 2>/dev/null && created_dir=1
fi
test_file="$test_dir/.precheck-$$"
if echo "test" > "$test_file" 2>/dev/null && [ "$(cat "$test_file")" = "test" ]; then
pass "Write permission to $test_dir"
else
fail "Write permission to $test_dir"
fi
rm -f "$test_file" 2>/dev/null
[ "$created_dir" -eq 1 ] && rmdir "$test_dir" 2>/dev/null
if [ -w /Library/LaunchDaemons ]; then
pass "Write permission to /Library/LaunchDaemons"
else
fail "Write permission to /Library/LaunchDaemons"
fi
case ":$PATH:" in
*:/usr/bin:*) pass "PATH contains /usr/bin" ;;
*) fail "PATH missing /usr/bin" ;;
esac
# --- TIME SYNC CHECK ---
section "Time Sync Check"
# sntp prints e.g. "+0.012345 +/- 0.0123 time.apple.com 17.253.x.x"; first field = offset in seconds
offset=$(sntp -t 5 time.apple.com 2>/dev/null | awk '$1 ~ /^[+-]?[0-9.]+$/ {print $1; exit}')
if [ -n "$offset" ]; then
abs_min=$(awk -v o="$offset" 'BEGIN { if (o < 0) o = -o; printf "%.1f", o / 60 }')
if awk -v m="$abs_min" 'BEGIN { exit !(m > 5) }'; then
fail "Time mismatch! Offset from NTP: $abs_min min | System: $(date '+%Y-%m-%d %H:%M:%S')"
else
pass "Time sync OK (diff: $abs_min min) | TZ: $(readlink /etc/localtime | sed 's#.*/zoneinfo/##')"
fi
else
warn "Could not reach NTP server (time.apple.com:123 UDP) - skipping time check"
fi
# --- EXISTING INSTALLATION ---
section "Existing Installation"
if [ -d "/Library/Gorelo" ]; then
running=$(pgrep -il gorelo | awk '{print $2}' | sort -u | tr '\n' ' ')
if [ -n "$running" ]; then
info "Gorelo agent is already installed and running ($running)"
else
echo "${YELLOW}INFO: Gorelo directory exists but agent processes are not running${RESET}"
fi
else
info "No existing Gorelo installation found (clean install)"
fi
# --- SUMMARY ---
echo
echo "${CYAN}======================================${RESET}"
if [ "$has_error" -eq 1 ]; then
echo "${RED}RESULT: FAIL - One or more checks failed${RESET}"
echo "${YELLOW}Fix the issues above and re-run this script.${RESET}"
else
echo "${GREEN}RESULT: PASS - All checks passed. Ready to install.${RESET}"
fi
echo "${CYAN}======================================${RESET}"
echo
exit "$has_error"
EOF
chmod +x /tmp/gorelo-check-mac.sh
sudo /tmp/gorelo-check-mac.sh
rm -f /tmp/gorelo-check-mac.sh
- 出现提示时选择你的区域(USW 或 AUE)。
- 使用下表解决任何失败,并确保你的修复在终端会话和重启之后依然有效。
| 检查 | ❌ 失败消息 | ✅ 故障排除 |
|---|---|---|
| 处理器体系结构检查 | FAIL: Unsupported architecture detected: [arch] |
|
| 磁盘空间检查 |
|
|
| 管理员权限检查 | FAIL: Not running as root (required for installation) - re-run with sudo |
|
| TLS 检查 | FAIL: Could not make a TLS 1.2 connection |
|
| Gorelo 服务器检查(Gateway / API / Hub) | FAIL: [Description] ([URL]). Could not connect | 检查你所选区域的 Gorelo 网关、API 和 Hub 服务器。
|
| Cloudflare R2 下载检查 |
|
|
| 安装文件夹权限检查 |
|
|
| PATH 检查 | FAIL: PATH missing /usr/bin |
|
| 时间同步检查 | FAIL: Time mismatch! Offset from NTP: X min |
|
gorelo.azure-devices.net,AUE 为 gorelo-aue.azure-devices.net)。如果 IoT Hub 无法解析,脚本会显示警告而不是失败。该警告不会阻止安装程序检查通过,但代理可能在安装后无法连接,因此值得修复。代理已安装但资产显示离线
Gorelo 中资产的在线或离线状态来自 Gorelo Agent。Gorelo Connect 是一个独立的远程控制组件。即使 Gorelo Agent 无法连接到 Gorelo 云,Gorelo Connect 仍可以运行,你也可以启动远程会话。请参阅 Gorelo Agent 与 Gorelo Connect。 如果 Gorelo Agent 已安装,但资产在资产列表中显示离线:- 在受影响的设备上运行先决条件检查。
- 查看 Gorelo 网关、API 和 hub 服务器的 URL 检查。失败表示防火墙、代理或 DNS 设置阻止了 Gorelo Agent。
- 查看 IoT Hub DNS 警告。如果设备无法解析
gorelo.azure-devices.net(USW)或gorelo-aue.azure-devices.net(AUE),即使其他互联网流量和 Gorelo Connect 正常工作,Gorelo Agent 也可能无法连接。 - 查看时间同步检查。修复任何时间不匹配。
- 检查 Gorelo Agent 日志中的连接错误。有关 Windows 和 macOS 上代理日志的位置,请参阅日志。
日志
- Windows
- macOS
Gorelo 将所有内容记录到:随着安装过程的进行,安装程序会在每个阶段创建额外的文件夹,里面有日志文件:InstallerHandler > Installer > Shell > Agent最常见的问题会出现在以下日志中:如果 Gorelo 支持人员要求日志文件,请将整个 LogFiles 文件夹作为 .zip 发送。
%programfiles%\Gorelo\LogFiles\
%programfiles%\Gorelo\LogFiles\Installer\diagnostics**.log
%programfiles%\Gorelo\LogFiles\Agent\diagnostics**.log
Gorelo 将所有内容记录到:最常见的问题会出现在以下日志中:如果 Gorelo 支持人员要求日志文件,请将整个 LogFiles 文件夹作为 .zip 发送。
/Library/Gorelo/LogFiles/
/Library/Gorelo/LogFiles/ServiceLogs/installer_logs_info.log
/Library/Gorelo/LogFiles/ServiceLogs/installer_logs_error.log
/Library/Gorelo/LogFiles/ServiceLogs/agent_logs_info.log
/Library/Gorelo/LogFiles/ServiceLogs/agent_logs_error.log