提交 420f5c98 authored 作者: 陈泽健's avatar 陈泽健

feat(service-health): 所有修复项执行前增加用户确认交互,默认不自动修复

- check_server_health.ps1: 对外服务修复(新/传统平台)增加 Read-Host 确认
- check_server_health.sh: 新增 confirm_repair 函数,DNS/NTP/Redis/Emqx/Console 修复项增加确认
- DNSCheck.psm1: DNS修复增加确认交互
- NTPCheck.psm1: NTP修复增加确认交互
- ServerResourceAnalysis.psm1: 防火墙修复增加确认交互
- ContainerCheck.psm1: Redis修复增加高风险确认(需输入 yes)
- ServiceCheck.psm1: 对外服务修复增加确认交互

支持 AUTO_REPAIR=yes 环境变量跳过确认用于无人值守场景
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 0da15710
......@@ -523,8 +523,17 @@ function Main {
}
if ($extSvc -and -not $extSvc.Running) {
Write-Log -Level "WARN" -Message "[EXT] 检测到对外服务进程未运行,准备执行远程修复 (fix_external_service_disconnect)"
Write-Log -Level "WARN" -Message "[EXT] 检测到对外服务进程未运行"
Write-Host " 检测到对外服务进程未运行,是否执行远程修复?" -ForegroundColor Yellow
$repairChoice = Read-Host " 执行修复? (y/n) [默认: n]"
if ($repairChoice -eq "y" -or $repairChoice -eq "Y") {
Write-Log -Level "INFO" -Message "[EXT] 用户确认执行远程修复 (fix_external_service_disconnect)"
$global:ExternalServiceRepairResult = Repair-ExternalMeetingService -Server $server
} else {
Write-Log -Level "INFO" -Message "[EXT] 用户取消修复操作,跳过"
$global:ExternalServiceRepairResult = @{ Status = "SKIPPED"; Message = "用户取消修复" }
}
}
}
}
......@@ -571,8 +580,17 @@ function Main {
}
if ($extSvc -and -not $extSvc.Running) {
Write-Log -Level "WARN" -Message "[EXT] 检测到对外服务进程未运行,准备执行远程修复 (fix_external_service_disconnect)"
Write-Log -Level "WARN" -Message "[EXT] 检测到对外服务进程未运行"
Write-Host " 检测到对外服务进程未运行,是否执行远程修复?" -ForegroundColor Yellow
$repairChoice = Read-Host " 执行修复? (y/n) [默认: n]"
if ($repairChoice -eq "y" -or $repairChoice -eq "Y") {
Write-Log -Level "INFO" -Message "[EXT] 用户确认执行远程修复 (fix_external_service_disconnect)"
$global:ExternalServiceRepairResult = Repair-ExternalMeetingService -Server $server
} else {
Write-Log -Level "INFO" -Message "[EXT] 用户取消修复操作,跳过"
$global:ExternalServiceRepairResult = @{ Status = "SKIPPED"; Message = "用户取消修复" }
}
}
}
......
......@@ -133,37 +133,85 @@ get_primary_ip() {
# 执行修复脚本:同目录 issue_handler.sh
run_issue_handler() {
local action="$1"
local platform="${2:-auto}"
local extra="${3:-}"
local action=$1
local platform=${2:-auto}
local extra=${3:-}
local issue="$SCRIPT_DIR/issue_handler.sh"
if [[ ! -f "$issue" ]]; then
log ERROR "[修复] 未找到 issue_handler.sh:$issue"
local issue=$SCRIPT_DIR/issue_handler.sh”
if [[ ! -f $issue ]]; then
log ERROR “[修复] 未找到 issue_handler.sh:$issue
return 1
fi
chmod +x "$issue" 2>/dev/null || true
chmod +x $issue 2>/dev/null || true
# dos2unix 可选
if command_exists dos2unix; then
dos2unix "$issue" >/dev/null 2>&1 || true
dos2unix $issue >/dev/null 2>&1 || true
fi
local cmd="$issue --action $action --platform $platform $extra"
log WARN "[修复] 执行:$cmd"
local cmd=$issue --action $action --platform $platform $extra
log WARN “[修复] 执行:$cmd
# ❌ 不要再把输出单独重定向到 $LOG_FILE
# ✅ 由脚本开头的 exec+tee 统一实现实时打印 + 写日志”
if bash -c "$cmd"; then
log SUCCESS "[修复] 执行成功:$action"
# ✅ 由脚本开头的 exec+tee 统一实现实时打印 + 写日志”
if bash -c $cmd; then
log SUCCESS “[修复] 执行成功:$action
return 0
else
log ERROR "[修复] 执行失败:$action(详见日志:$LOG_FILE)"
log ERROR “[修复] 执行失败:$action(详见日志:$LOG_FILE)”
return 1
fi
}
# ------------------------------
# 用户确认修复操作
# 用途:在所有修复项执行前要求用户明确确认
# 参数:
# $1 - 修复项名称(如 “DNS配置修复”)
# $2 - 修复详情描述(可选)
# 返回:
# 0 - 用户确认执行修复
# 1 - 用户取消修复
# 说明:
# - 支持环境变量 AUTO_REPAIR=yes 跳过确认(用于无人值守场景)
# - 确认结果会记录到日志中
# ------------------------------
confirm_repair() {
local repair_name=$1
local detail_msg=${2:-}
# 自动模式:通过环境变量 AUTO_REPAIR=yes 跳过确认
if [[${AUTO_REPAIR:-no}== “yes” ]]; then
log WARN “[确认] 自动修复模式已启用,自动执行: $repair_name
return 0
fi
# 显示确认提示
log WARN “┌──────────────────────────────────────────────────────────────”
log WARN “│ [修复确认] $repair_name
if [[ -n$detail_msg]]; then
log WARN “│ 详情: $detail_msg
fi
log WARN “│ ⚠ 注意: 修复操作可能修改系统配置,请确认后执行”
log WARN “└──────────────────────────────────────────────────────────────”
echo -n “ 是否执行以上修复? (y/N): “
local response
read response
case$responsein
[yY][eE][sS]|[yY])
log INFO “[确认] 用户确认执行修复: $repair_name
return 0
;;
*)
log INFO “[确认] 用户取消修复: $repair_name
return 1
;;
esac
}
# ------------------------------
# 1) 平台识别
# ------------------------------
......@@ -562,7 +610,11 @@ repair_dns_if_needed() {
if [[ "$status" == "OK" ]]; then
return 0
fi
log WARN "[DNS] 检测到 DNS 异常($status),触发修复:fix_dns_config"
log WARN "[DNS] 检测到 DNS 异常($status)"
# ✅ 增加用户确认交互
if confirm_repair "DNS配置修复" "将执行 fix_dns_config 修复DNS配置(可能修改 /etc/resolv.conf)"; then
run_issue_handler "fix_dns_config" "auto" "--non-interactive --yes" || true
log INFO "[DNS] 修复后复检..."
......@@ -575,6 +627,10 @@ repair_dns_if_needed() {
else
log WARN "[DNS] 复检仍异常:$post(需人工排查)"
fi
else
log INFO "[DNS] 用户取消修复,跳过"
report_kv_set "dns.repair" "SKIPPED"
fi
}
# ------------------------------
......@@ -884,7 +940,11 @@ repair_ntp_if_needed() {
if [[ "$status" == "OK" ]]; then
return 0
fi
log WARN "[NTP] 状态=$status,触发修复:fix_ntp_config"
log WARN "[NTP] 状态=$status"
# ✅ 增加用户确认交互
if confirm_repair "NTP配置修复" "将执行 fix_ntp_config 修复NTP服务配置(可能修改 chrony/ntp 配置文件并重启服务)"; then
run_issue_handler "fix_ntp_config" "auto" "--ntp-auto" || true
log INFO "[NTP] 修复后复检..."
......@@ -895,7 +955,13 @@ repair_ntp_if_needed() {
if [[ "$post" == "OK" ]]; then
log SUCCESS "[NTP] 复检成功:已恢复正常"
else
log WARN "[NTP] 复检仍异常:$post"
log WARN "[NTP] 复检仍异常:$post(需人工排查)"
fi
else
log INFO "[NTP] 用户取消修复,跳过"
report_kv_set "ntp.repair" "SKIPPED"
fi
}
fi
}
......@@ -1079,9 +1145,13 @@ collect_container_info() {
report_kv_set "redis.uredis_stopped" "$uredis_stopped"
if [[ "$uredis_running" -eq 0 && "$uredis_stopped" -eq 1 && "$redis_running" -eq 0 ]]; then
log ERROR "[Redis] 判定 Redis 容器异常:uredis 未运行且无其他 redis 容器运行,触发修复"
log ERROR "[Redis] 判定 Redis 容器异常:uredis 未运行且无其他 redis 容器运行"
report_kv_set "redis.exception" "true"
# ✅ 增加用户确认交互(Redis修复涉及清空数据,必须明确确认)
if confirm_repair "Redis容器修复" "⚠ 高风险操作:将执行 redis_container_exception 修复Redis容器(可能清空数据目录)"; then
run_issue_handler "redis_container_exception" "auto" "--non-interactive --yes" || true
if docker ps --format '{{.Names}}' | grep -w uredis >/dev/null 2>&1; then
log SUCCESS "[Redis] 复检成功:uredis 已运行"
report_kv_set "redis.recheck" "OK"
......@@ -1089,6 +1159,10 @@ collect_container_info() {
log WARN "[Redis] 复检失败:uredis 仍未运行(需人工排查)"
report_kv_set "redis.recheck" "FAIL"
fi
else
log INFO "[Redis] 用户取消修复,跳过(Redis容器异常未处理)"
report_kv_set "redis.repair" "SKIPPED"
fi
else
log INFO "[Redis] 未检测到需要自动修复的 Redis 容器异常"
report_kv_set "redis.exception" "false"
......@@ -1521,6 +1595,9 @@ test_config_console() {
local fixed_files=()
local error_files=()
# ✅ 新增:用户确认标志(首次发现时询问,后续使用同一决策)
local CONSOLE_REPAIR_CONFIRMED="pending"
# console配置的正则表达式(匹配console后跟:或=,然后是true)
local console_re='console[[:space:]]*[:=][[:space:]]*true'
......@@ -1563,6 +1640,19 @@ test_config_console() {
if [[ "$has_console_true" -eq 1 ]]; then
log INFO "[CONSOLE] 发现console=true: $f"
# ✅ 首次发现console=true时,询问用户是否修复
if [[ "$CONSOLE_REPAIR_CONFIRMED" == "pending" ]]; then
if confirm_repair "Console配置修复" "将把所有配置文件中的 console=true 改为 console=false(会自动备份原文件为 .bak)"; then
CONSOLE_REPAIR_CONFIRMED="yes"
else
CONSOLE_REPAIR_CONFIRMED="no"
log INFO "[CONSOLE] 用户取消修复,跳过所有console配置修复"
break
fi
fi
# ✅ 用户确认后才执行修复
if [[ "$CONSOLE_REPAIR_CONFIRMED" == "yes" ]]; then
# 备份文件
local backup_file="${f}.bak"
if cp "$f" "$backup_file" 2>/dev/null; then
......@@ -1582,6 +1672,7 @@ test_config_console() {
error_files+=("$f")
fi
fi
fi
done < <($list_cmd)
report_kv_set "console.total_files" "$total_files"
......@@ -1721,8 +1812,11 @@ check_emqx_container_exception() {
done <<<"$stopped_txt"
if [[ "$uemqx_running" -eq 0 && "$uemqx_stopped" -eq 1 && "$has_emqx_running" -eq 0 ]]; then
log ERROR "[Emqx] 判定 Emqx 容器异常:uemqx 未运行且无其他 emqx 容器运行,触发修复"
log ERROR "[Emqx] 判定 Emqx 容器异常:uemqx 未运行且无其他 emqx 容器运行"
report_kv_set "emqx.exception" "true"
# ✅ 增加用户确认交互
if confirm_repair "Emqx容器修复" "将执行 emqx_container_exception 修复Emqx容器"; then
run_issue_handler "emqx_container_exception" "auto" "--non-interactive --yes" || true
if docker ps --format '{{.Names}}' | grep -w uemqx >/dev/null 2>&1; then
......@@ -1732,6 +1826,14 @@ check_emqx_container_exception() {
log WARN "[Emqx] 复检失败:uemqx 仍未运行(需人工排查)"
report_kv_set "emqx.recheck" "FAIL"
fi
else
log INFO "[Emqx] 用户取消修复,跳过(Emqx容器异常未处理)"
report_kv_set "emqx.repair" "SKIPPED"
fi
else
log WARN "[Emqx] 复检失败:uemqx 仍未运行(需人工排查)"
report_kv_set "emqx.recheck" "FAIL"
fi
else
log INFO "[Emqx] 未检测到需要自动修复的 Emqx 容器异常"
report_kv_set "emqx.exception" "false"
......
......@@ -373,8 +373,18 @@ function Test-ContainerInformation {
}
}
if ($redisNeedRepair) {
Write-Log -Level "ERROR" -Message "[Redis] 检测到 Redis 容器异常:uredis 未运行,且无其他 redis 命名容器运行,开始执行远端修复"
if ($redisNeedRepair) {
Write-Log -Level "ERROR" -Message "[Redis] 检测到 Redis 容器异常:uredis 未运行,且无其他 redis 命名容器运行"
Write-Host "===========================================" -ForegroundColor Red
Write-Host " ** 高风险操作警告 **" -ForegroundColor Red -BackgroundColor Black
Write-Host " 即将清空 Redis 数据目录 (/usr/local/uredis/data) 并重启 uredis 容器" -ForegroundColor Yellow
Write-Host " 此操作不可逆,请谨慎确认!" -ForegroundColor Red
Write-Host "===========================================" -ForegroundColor Red
Write-Host "检测到 Redis 容器异常,是否执行远程修复?" -ForegroundColor Yellow
$repairChoice = Read-Host "执行修复? 请输入 yes 确认 (yes/n) [默认: n]"
if ($repairChoice -eq "yes") {
Write-Log -Level "INFO" -Message "[Redis] 用户输入 'yes' 确认执行 Redis 容器修复 (redis_container_exception)"
$repairItem = [ordered]@{
Check = "Redis容器修复"
......@@ -425,12 +435,20 @@ function Test-ContainerInformation {
$repairItem.Status = "失败"
$repairItem.Details = "远程修复失败:$errMsg"
}
}
catch {
} catch {
Write-Log -Level "ERROR" -Message "[Redis] 调用 Upload_the_repair_script 异常:$($_.Exception.Message)"
$repairItem.Status = "异常"
$repairItem.Details = "调用修复脚本异常:$($_.Exception.Message)"
}
} else {
Write-Log -Level "INFO" -Message "[Redis] 用户取消修复操作,跳过 Redis 容器修复"
$repairItem = [ordered]@{
Check = "Redis容器修复"
Status = "已跳过"
Details = "用户取消修复"
Success = $false
}
}
$results += $repairItem
}
......
# ==============================================================================
# DNSCheck.psm1
# ------------------------------------------------------------------------------
# DNS 检测模块
# DNS 检测模块
#
# .SYNOPSIS
# 提供 DNS 配置检测与修复功能
# 提供 DNS 配置检测与修复功能
#
# .DESCRIPTION
# 本模块用于检测和修复服务器 DNS 解析功能。
# 包括 DNS 配置文件检测、域名解析测试、网络连通性验证等功能。
# 本模块用于检测和修复服务器 DNS 解析功能。
# 包括 DNS 配置文件检测、域名解析测试、网络连通性验证等功能。
#
# 主要功能:
# - DNS 配置检测(读取远程 /etc/resolv.conf)
# - DNS 连通性测试(nslookup/host 命令)
# - 网络连通性测试(ping 命令)
# - DNS 配置修复
# - 修复后复检
# 主要功能:
# - DNS 配置检测(读取远程 /etc/resolv.conf)
# - DNS 连通性测试(nslookup/host 命令)
# - 网络连通性测试(ping 命令)
# - DNS 配置修复
# - 修复后复检
#
# 依赖要求:
# - 需要主脚本提供 Invoke-SSHCommand 函数
# - 需要主脚本提供 Write-Log 函数
# - 需要主脚本提供 Upload_the_repair_script 函数
# - 需要全局配置变量 $DNSTestDomains
# 依赖要求:
# - 需要主脚本提供 Invoke-SSHCommand 函数
# - 需要主脚本提供 Write-Log 函数
# - 需要主脚本提供 Upload_the_repair_script 函数
# - 需要全局配置变量 $DNSTestDomains
#
# .EXAMPLE
# $results = Test-DNSResolution -Server $server
#
# .NOTES
# 版本:1.0.0
# 作者:自动化运维团队
# 创建日期:2026-02-06
# 版本:1.0.0
# 作者:自动化运维团队
# 创建日期:2026-02-06
#
# ==============================================================================
# 导入依赖的公共模块
# 导入依赖的公共模块
$ModuleDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$CommonModulePath = Join-Path $ModuleDir "Common.psm1"
if (Test-Path $CommonModulePath) {
Import-Module $CommonModulePath -Force -Global -ErrorAction SilentlyContinue
}
#region DNS 检测函数
#region DNS 检测函数
# ==============================================================================
# 检测 DNS 解析功能
# 检测 DNS 解析功能
# ==============================================================================
function Test-DNSResolution {
<#
.SYNOPSIS
检测 DNS 解析功能
检测 DNS 解析功能
.DESCRIPTION
综合检测服务器 DNS 解析功能,包括:
1. DNS 配置文件检测(/etc/resolv.conf)
2. DNS 解析测试(nslookup/host 命令)
3. 网络连通性测试(ping 命令)
4. 自动修复和复检
综合检测服务器 DNS 解析功能,包括:
1. DNS 配置文件检测(/etc/resolv.conf)
2. DNS 解析测试(nslookup/host 命令)
3. 网络连通性测试(ping 命令)
4. 自动修复和复检
.PARAMETER Server
服务器信息哈希表,包含 IP、User、Pass、Port 等连接信息
服务器信息哈希表,包含 IP、User、Pass、Port 等连接信息
.EXAMPLE
Test-DNSResolution -Server $server
.OUTPUTS
System.Collections.Hashtable[]
返回检测结果数组,每个元素包含 Check、Status、Details、Success 字段
返回检测结果数组,每个元素包含 Check、Status、Details、Success 字段
#>
param(
[Parameter(Mandatory=$false)]
......@@ -73,14 +73,14 @@ function Test-DNSResolution {
)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 检测 DNS 解析功能 =========="
Write-Log -Level "INFO" -Message "========== 检测 DNS 解析功能 =========="
$results = @()
# ==============================================================================
# 1. 检查 DNS 配置文件
# 1. 检查 DNS 配置文件
# ==============================================================================
Write-Log -Level "INFO" -Message "检查 DNS 配置文件..."
Write-Log -Level "INFO" -Message "检查 DNS 配置文件..."
$resolvCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "cat /etc/resolv.conf 2>/dev/null | grep -E '^nameserver' | head -n 3"
$dnsServers = @()
......@@ -93,38 +93,38 @@ function Test-DNSResolution {
}
if ($dnsServers.Count -gt 0) {
Write-Log -Level "SUCCESS" -Message " 检测到 DNS 服务器: $($dnsServers -join ', ')"
Write-Log -Level "SUCCESS" -Message " 检测到 DNS 服务器: $($dnsServers -join ', ')"
$results += @{
Check = "DNS配置"
Status = "正常"
Details = "DNS服务器: $($dnsServers -join ', ')"
Check = "DNS配置"
Status = "正常"
Details = "DNS服务器: $($dnsServers -join ', ')"
Success = $true
}
}
else {
Write-Log -Level "WARN" -Message " 未检测到 DNS 服务器配置"
Write-Log -Level "WARN" -Message " 未检测到 DNS 服务器配置"
$results += @{
Check = "DNS配置"
Status = "异常"
Details = "未找到DNS服务器配置"
Check = "DNS配置"
Status = "异常"
Details = "未找到DNS服务器配置"
Success = $false
}
}
}
else {
Write-Log -Level "WARN" -Message " 无法读取 DNS 配置文件"
Write-Log -Level "WARN" -Message " 无法读取 DNS 配置文件"
$results += @{
Check = "DNS配置"
Status = "异常"
Details = "无法读取 /etc/resolv.conf"
Check = "DNS配置"
Status = "异常"
Details = "无法读取 /etc/resolv.conf"
Success = $false
}
}
# ==============================================================================
# 2. 测试 DNS 解析功能
# 2. 测试 DNS 解析功能
# ==============================================================================
Write-Log -Level "INFO" -Message "测试 DNS 解析功能..."
Write-Log -Level "INFO" -Message "测试 DNS 解析功能..."
$dnsTestSuccess = 0
$dnsTestTotal = $DNSTestDomains.Count
......@@ -134,41 +134,41 @@ function Test-DNSResolution {
if ($testResult.ExitCode -eq 0 -and $testResult.Output -match 'Name:|Address:') {
$dnsTestSuccess++
Write-Log -Level "SUCCESS" -Message " [OK] $domain : 解析成功"
Write-Log -Level "SUCCESS" -Message " [OK] $domain : 解析成功"
}
else {
# 尝试使用 host 命令
# 尝试使用 host 命令
$testCmd2 = "host $domain 2>&1 | head -n 1"
$testResult2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $testCmd2
if ($testResult2.ExitCode -eq 0 -and $testResult2.Output -match 'has address|has IPv4') {
$dnsTestSuccess++
Write-Log -Level "SUCCESS" -Message " [OK] $domain : 解析成功"
Write-Log -Level "SUCCESS" -Message " [OK] $domain : 解析成功"
}
else {
Write-Log -Level "ERROR" -Message " [FAIL] $domain : 解析失败"
Write-Log -Level "ERROR" -Message " [FAIL] $domain : 解析失败"
}
}
}
$dnsTestStatus = if ($dnsTestSuccess -eq $dnsTestTotal) { "正常" } elseif ($dnsTestSuccess -gt 0) { "部分正常" } else { "异常" }
$dnsTestStatus = if ($dnsTestSuccess -eq $dnsTestTotal) { "正常" } elseif ($dnsTestSuccess -gt 0) { "部分正常" } else { "异常" }
$dnsTestColor = if ($dnsTestSuccess -eq $dnsTestTotal) { "SUCCESS" } elseif ($dnsTestSuccess -gt 0) { "WARN" } else { "ERROR" }
Write-Log -Level $dnsTestColor -Message " DNS 解析测试结果: $dnsTestSuccess/$dnsTestTotal 成功"
Write-Log -Level $dnsTestColor -Message " DNS 解析测试结果: $dnsTestSuccess/$dnsTestTotal 成功"
$results += @{
Check = "DNS解析"
Check = "DNS解析"
Status = $dnsTestStatus
Details = "测试域名解析: $dnsTestSuccess/$dnsTestTotal 成功"
Details = "测试域名解析: $dnsTestSuccess/$dnsTestTotal 成功"
Success = ($dnsTestSuccess -gt 0)
SuccessCount = $dnsTestSuccess
TotalCount = $dnsTestTotal
}
# ==============================================================================
# 3. 测试 ping 连通性(可选,验证DNS解析的IP是否可达)
# 3. 测试 ping 连通性(可选,验证DNS解析的IP是否可达)
# ==============================================================================
Write-Log -Level "INFO" -Message "测试网络连通性..."
Write-Log -Level "INFO" -Message "测试网络连通性..."
$pingSuccess = 0
$pingTotal = 0
......@@ -179,23 +179,23 @@ function Test-DNSResolution {
$pingTotal++
if ($pingResult.ExitCode -eq 0 -and $pingResult.Output -match '0% packet loss|packets transmitted') {
$pingSuccess++
Write-Log -Level "SUCCESS" -Message " [OK] $domain : 网络连通正常"
Write-Log -Level "SUCCESS" -Message " [OK] $domain : 网络连通正常"
}
else {
Write-Log -Level "WARN" -Message " [WARN] $domain : 网络连通异常或超时"
Write-Log -Level "WARN" -Message " [WARN] $domain : 网络连通异常或超时"
}
}
if ($pingTotal -gt 0) {
$pingStatus = if ($pingSuccess -eq $pingTotal) { "正常" } elseif ($pingSuccess -gt 0) { "部分正常" } else { "异常" }
$pingStatus = if ($pingSuccess -eq $pingTotal) { "正常" } elseif ($pingSuccess -gt 0) { "部分正常" } else { "异常" }
$pingColor = if ($pingSuccess -eq $pingTotal) { "SUCCESS" } elseif ($pingSuccess -gt 0) { "WARN" } else { "ERROR" }
Write-Log -Level $pingColor -Message " 网络连通性测试结果: $pingSuccess/$pingTotal 成功"
Write-Log -Level $pingColor -Message " 网络连通性测试结果: $pingSuccess/$pingTotal 成功"
$results += @{
Check = "网络连通性"
Check = "网络连通性"
Status = $pingStatus
Details = "Ping测试: $pingSuccess/$pingTotal 成功"
Details = "Ping测试: $pingSuccess/$pingTotal 成功"
Success = ($pingSuccess -gt 0)
SuccessCount = $pingSuccess
TotalCount = $pingTotal
......@@ -203,50 +203,55 @@ function Test-DNSResolution {
}
# ==============================================================================
# 4. 如有 DNS 解析异常,则触发远程修复
# 4. 如有 DNS 解析异常,则触发远程修复
# ==============================================================================
$needRepair = $false
foreach ($item in $results) {
if ($item.Check -eq 'DNS配置' -and -not $item.Success) { $needRepair = $true; break }
if ($item.Check -eq 'DNS解析' -and -not $item.Success) { $needRepair = $true; break }
if ($item.Check -eq 'DNS配置' -and -not $item.Success) { $needRepair = $true; break }
if ($item.Check -eq 'DNS解析' -and -not $item.Success) { $needRepair = $true; break }
}
if ($needRepair) {
Write-Log -Level "WARN" -Message "[DNS] 检测到 DNS 解析异常,准备执行远程修复 (fix_dns_config)"
Write-Log -Level "WARN" -Message "[DNS] 检测到 DNS 解析异常"
Write-Host " 检测到 DNS 解析异常,是否执行远程修复 (fix_dns_config)?" -ForegroundColor Yellow
$repairDnsChoice = Read-Host " 执行修复? (y/n) [默认: n]"
if ($repairDnsChoice -eq "y" -or $repairDnsChoice -eq "Y") {
Write-Log -Level "INFO" -Message "[DNS] 用户确认执行远程修复 (fix_dns_config)"
try {
$serverForRepair = @{ IP = $Server.IP; User = $Server.User; Pass = $Server.Pass; Port = $Server.Port }
$repairRes = Upload_the_repair_script -Server $serverForRepair -Action "fix_dns_config" -Platform "auto" -RemoteDir "/home/repair_scripts"
$repairItem = [ordered]@{
Check = "DNS修复"
Status = "未执行"
Check = "DNS修复"
Status = "未执行"
Details = ""
Success = $false
}
if ($repairRes -and $repairRes['Success']) {
Write-Log -Level "SUCCESS" -Message "[DNS] 远程 DNS 修复已执行成功 (fix_dns_config)"
$repairItem.Status = "已执行"
$repairItem.Details = "远程脚本执行成功 (fix_dns_config)"
Write-Log -Level "SUCCESS" -Message "[DNS] 远程 DNS 修复已执行成功 (fix_dns_config)"
$repairItem.Status = "已执行"
$repairItem.Details = "远程脚本执行成功 (fix_dns_config)"
$repairItem.Success = $true
# 简单复检:尝试解析一个域名
Write-Log -Level "INFO" -Message "[DNS] 修复后复检 DNS 解析..."
# 简单复检:尝试解析一个域名
Write-Log -Level "INFO" -Message "[DNS] 修复后复检 DNS 解析..."
$postCmd = "nslookup www.baidu.com 2>&1 | head -n 5 | grep -E 'Name:|Address:' | head -n 2"
$postResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $postCmd
if ($postResult.ExitCode -eq 0 -and $postResult.Output -match 'Name:|Address:') {
Write-Log -Level "SUCCESS" -Message "[DNS] 复检成功,DNS 解析已恢复正常 (www.baidu.com)"
$repairItem.Details += " | 复检成功 (www.baidu.com)"
Write-Log -Level "SUCCESS" -Message "[DNS] 复检成功,DNS 解析已恢复正常 (www.baidu.com)"
$repairItem.Details += " | 复检成功 (www.baidu.com)"
}
else {
Write-Log -Level "WARN" -Message "[DNS] 复检仍失败,请人工进一步排查"
$repairItem.Status = "部分成功"
$repairItem.Details += " | 复检仍失败,请人工排查"
Write-Log -Level "WARN" -Message "[DNS] 复检仍失败,请人工进一步排查"
$repairItem.Status = "部分成功"
$repairItem.Details += " | 复检仍失败,请人工排查"
}
}
else {
$errMsg = "未知错误"
$errMsg = "未知错误"
if ($repairRes -is [hashtable]) {
if ($repairRes.ContainsKey('Error') -and $repairRes['Error']) { $errMsg = [string]::Join(' ', $repairRes['Error']) }
elseif ($repairRes.ContainsKey('Output') -and $repairRes['Output']) { $errMsg = [string]::Join(' ', $repairRes['Output']) }
......@@ -254,19 +259,28 @@ function Test-DNSResolution {
} elseif ($repairRes) {
$errMsg = $repairRes.ToString()
}
Write-Log -Level "ERROR" -Message "[DNS] 远程 DNS 修复执行失败: $errMsg"
$repairItem.Status = "失败"
$repairItem.Details = "远程修复失败: $errMsg"
Write-Log -Level "ERROR" -Message "[DNS] 远程 DNS 修复执行失败: $errMsg"
$repairItem.Status = "失败"
$repairItem.Details = "远程修复失败: $errMsg"
}
$results += $repairItem
}
catch {
Write-Log -Level "ERROR" -Message "[DNS] 调用 Upload_the_repair_script 异常: $($_.Exception.Message)"
Write-Log -Level "ERROR" -Message "[DNS] 调用 Upload_the_repair_script 异常: $($_.Exception.Message)"
$results += @{
Check = "DNS修复"
Status = "异常"
Details = "调用修复脚本异常: $($_.Exception.Message)"
Success = $false
}
}
} else {
Write-Log -Level "INFO" -Message "[DNS] 用户取消修复,跳过"
$results += @{
Check = "DNS修复"
Status = "异常"
Details = "调用修复脚本异常: $($_.Exception.Message)"
Check = "DNS修复"
Status = "已跳过"
Details = "用户取消修复"
Success = $false
}
}
......@@ -278,7 +292,7 @@ function Test-DNSResolution {
#endregion
# ==============================================================================
# 瀵煎嚭妯″潡鍑芥暟
# 瀵煎嚭妯″潡鍑芥暟
# ==============================================================================
Export-ModuleMember -Function @(
'Test-DNSResolution'
......
......@@ -205,11 +205,16 @@ function Test-NTPService {
# 检测到异常/未安装/偏移,上传并执行修复脚本
# ==============================================================================
if ($needRepair) {
Write-Log -Level "INFO" -Message "[NTP] 准备自动修复: ./issue_handler.sh --action fix_ntp_config --ntp-auto"
Write-Log -Level "WARN" -Message "[NTP] 检测到NTP服务异常"
Write-Host " 检测到NTP服务异常,是否执行远程修复 (fix_ntp_config)?" -ForegroundColor Yellow
$repairNtpChoice = Read-Host " 执行修复? (y/n) [默认: n]"
if ($repairNtpChoice -eq "y" -or $repairNtpChoice -eq "Y") {
Write-Log -Level "INFO" -Message "[NTP] 用户确认执行修复 (fix_ntp_config)"
try {
$repairRes = Upload_the_repair_script -Server $serverForRepair -Action "fix_ntp_config" -Platform "auto" -RemoteDir "/home/repair_scripts"
if ($repairRes -and $repairRes['Success']) {
Write-Log -Level "SUCCESS" -Message "[NTP] 自动修复命令执行成功 (fix_ntp_config)"
Write-Log -Level "SUCCESS" -Message "[NTP] 远程修复命令执行成功 (fix_ntp_config)"
# 修复后验证 NTP 状态和时间
Write-Log -Level "INFO" -Message "[NTP] 修复后验证..."
......@@ -256,12 +261,17 @@ function Test-NTPService {
} elseif ($repairRes) {
$errMsg = $repairRes.ToString()
}
Write-Log -Level "ERROR" -Message "[NTP] 自动修复执行失败: $errMsg"
Write-Log -Level "ERROR" -Message "[NTP] 远程修复执行失败: $errMsg"
}
} catch {
# 捕获 Upload_the_repair_script 调用异常,避免泄漏 .Error 信息
Write-Log -Level "ERROR" -Message "[NTP] 调用 Upload_the_repair_script 异常: $($_.Exception.Message)"
}
} else {
Write-Log -Level "INFO" -Message "[NTP] 用户取消修复,跳过"
$summary.Status = '异常(未修复)'
$summary.Detail = '用户取消修复'
}
}
return $summary
......
<#
.SYNOPSIS
服务器资源分析模块
服务器资源分析模块
.DESCRIPTION
本模块用于通过SSH连接远程服务器,收集和分析服务器资源使用情况。
主要功能包括:
- 操作系统信息收集(版本、发行版)
- CPU 使用率分析(使用率、核心数)
- 内存使用率分析(总量、已用、百分比)
- 磁盘使用分析(各分区使用情况)
- 防火墙状态检测(状态、开放端口)
- 系统负载分析(1/5/15分钟负载)
依赖项:
- 需要主脚本提供 Invoke-SSHCommand 函数(用于执行SSH命令)
- 需要主脚本提供 Write-Log 函数(用于日志记录)
- 需要主脚本提供 Upload_the_repair_script 函数(用于远端修复)
本模块用于通过SSH连接远程服务器,收集和分析服务器资源使用情况。
主要功能包括:
- 操作系统信息收集(版本、发行版)
- CPU 使用率分析(使用率、核心数)
- 内存使用率分析(总量、已用、百分比)
- 磁盘使用分析(各分区使用情况)
- 防火墙状态检测(状态、开放端口)
- 系统负载分析(1/5/15分钟负载)
依赖项:
- 需要主脚本提供 Invoke-SSHCommand 函数(用于执行SSH命令)
- 需要主脚本提供 Write-Log 函数(用于日志记录)
- 需要主脚本提供 Upload_the_repair_script 函数(用于远端修复)
.EXAMPLE
PS C:\> Import-Module ServerResourceAnalysis.psm1
PS C:\> Test-ServerResources -Server $serverInfo
.NOTES
Version: 1.0.0
Author: Ubains DevOps Team
Creation Date: 2024-02-06
Copyright: (c) 2024 Ubains. All rights reserved.
#>
# 导入依赖的公共模块
# 导入依赖的公共模块
$ModuleDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$CommonModulePath = Join-Path $ModuleDir "Common.psm1"
if (Test-Path $CommonModulePath) {
Import-Module $CommonModulePath -Force -Global -ErrorAction SilentlyContinue
}
#region Server Resource Analysis Functions
<#
.SYNOPSIS
测试服务器资源使用情况
测试服务器资源使用情况
.DESCRIPTION
通过SSH连接到远程服务器,收集和分析系统资源使用情况。
检测内容包括操作系统信息、架构、CPU使用率、内存使用情况、磁盘空间、防火墙状态和系统负载。
通过SSH连接到远程服务器,收集和分析系统资源使用情况。
检测内容包括操作系统信息、架构、CPU使用率、内存使用情况、磁盘空间、防火墙状态和系统负载。
.PARAMETER Server
服务器连接信息哈希表,包含以下键:
- IP: 服务器IP地址
- User: SSH用户名
- Pass: SSH密码
- Port: SSH端口号(可选,默认22)
服务器连接信息哈希表,包含以下键:
- IP: 服务器IP地址
- User: SSH用户名
- Pass: SSH密码
- Port: SSH端口号(可选,默认22)
.EXAMPLE
$server = @{
IP = "192.168.1.100"
User = "root"
Pass = "password"
Port = 22
}
$results = Test-ServerResources -Server $server
.OUTPUTS
System.Collections.Hashtable
返回包含以下键的哈希表:
- OS: 操作系统信息(Info, Status, Success)
- Architecture: 架构信息(Arch, Kernel, Status, Success)
- CPU: CPU使用情况(Usage, Cores, Status, Success)
- Memory: 内存使用情况(Total, Used, Percent, Status, Success)
- Disk: 磁盘信息数组(Device, Size, Used, Percent, MountPoint, Status)
- Firewall: 防火墙信息(Active, Type, OpenPorts, Status, Repair)
返回包含以下键的哈希表:
- OS: 操作系统信息(Info, Status, Success)
- Architecture: 架构信息(Arch, Kernel, Status, Success)
- CPU: CPU使用情况(Usage, Cores, Status, Success)
- Memory: 内存使用情况(Total, Used, Percent, Status, Success)
- Disk: 磁盘信息数组(Device, Size, Used, Percent, MountPoint, Status)
- Firewall: 防火墙信息(Active, Type, OpenPorts, Status, Repair)
#>
function Test-ServerResources {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[hashtable]$Server
)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 服务器资源分析 =========="
# 初始化结果哈希表
Write-Log -Level "INFO" -Message "========== 服务器资源分析 =========="
# 初始化结果哈希表
$results = @{
OS = $null
Architecture = $null
CPU = $null
Memory = $null
Disk = @()
Firewall = $null
}
#region 1. 检测操作系统信息
Write-Log -Level "INFO" -Message "检测操作系统信息..."
#region 1. 检测操作系统信息
Write-Log -Level "INFO" -Message "检测操作系统信息..."
$osCmd = "cat /etc/os-release 2>/dev/null | grep -E '^(NAME|VERSION)=' | head -n 2 || cat /etc/redhat-release 2>/dev/null || uname -o"
$osResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $osCmd
if ($osResult.ExitCode -eq 0 -and $osResult.Output) {
$osInfo = ($osResult.Output -split "`n" | Where-Object { $_ -match '\S' }) -join " | "
$osInfo = $osInfo -replace 'NAME=|VERSION=|"', ''
Write-Log -Level "SUCCESS" -Message " 操作系统: $osInfo"
Write-Log -Level "SUCCESS" -Message " 操作系统: $osInfo"
$results.OS = @{
Info = $osInfo
Status = "正常"
Status = "正常"
Success = $true
}
}
else {
Write-Log -Level "WARN" -Message " 无法获取操作系统信息"
Write-Log -Level "WARN" -Message " 无法获取操作系统信息"
$results.OS = @{
Info = "未知"
Status = "未知"
Info = "未知"
Status = "未知"
Success = $false
}
}
#endregion 1. 检测操作系统信息
#region 2. 检测服务器架构
Write-Log -Level "INFO" -Message "检测服务器架构..."
#endregion 1. 检测操作系统信息
#region 2. 检测服务器架构
Write-Log -Level "INFO" -Message "检测服务器架构..."
$archCmd = "uname -m && uname -r"
$archResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $archCmd
if ($archResult.ExitCode -eq 0 -and $archResult.Output) {
$archLines = $archResult.Output -split "`n" | Where-Object { $_ -match '\S' }
$arch = if ($archLines.Count -ge 1) { $archLines[0].Trim() } else { "未知" }
$kernel = if ($archLines.Count -ge 2) { $archLines[1].Trim() } else { "未知" }
Write-Log -Level "SUCCESS" -Message " 架构: $arch | 内核: $kernel"
$arch = if ($archLines.Count -ge 1) { $archLines[0].Trim() } else { "未知" }
$kernel = if ($archLines.Count -ge 2) { $archLines[1].Trim() } else { "未知" }
Write-Log -Level "SUCCESS" -Message " 架构: $arch | 内核: $kernel"
$results.Architecture = @{
Arch = $arch
Kernel = $kernel
Status = "正常"
Status = "正常"
Success = $true
}
}
else {
Write-Log -Level "WARN" -Message " 无法获取架构信息"
Write-Log -Level "WARN" -Message " 无法获取架构信息"
$results.Architecture = @{
Arch = "未知"
Kernel = "未知"
Status = "未知"
Arch = "未知"
Kernel = "未知"
Status = "未知"
Success = $false
}
}
#endregion 2. 检测服务器架构
#region 3. 检测 CPU 使用情况
Write-Log -Level "INFO" -Message "检测 CPU 使用情况..."
#endregion 2. 检测服务器架构
#region 3. 检测 CPU 使用情况
Write-Log -Level "INFO" -Message "检测 CPU 使用情况..."
$cpuCmd = "top -bn1 | grep 'Cpu(s)' | awk '{print `$2+`$4}' 2>/dev/null || mpstat 1 1 2>/dev/null | tail -n 1 | awk '{print 100-`$NF}'"
$cpuResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cpuCmd
$cpuUsage = 0
if ($cpuResult.ExitCode -eq 0 -and $cpuResult.Output) {
$cpuLine = ($cpuResult.Output -split "`n" | Where-Object { $_ -match '^\d' } | Select-Object -First 1)
if ($cpuLine) {
try {
$cpuUsage = [math]::Round([double]$cpuLine.Trim(), 1)
}
catch {
$cpuUsage = 0
}
}
}
# 获取 CPU 核心数
# 获取 CPU 核心数
$cpuCoresCmd = "nproc 2>/dev/null || grep -c processor /proc/cpuinfo"
$cpuCoresResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cpuCoresCmd
$cpuCores = 0
if ($cpuCoresResult.ExitCode -eq 0 -and $cpuCoresResult.Output) {
try {
$cpuCores = [int]($cpuCoresResult.Output -split "`n" | Where-Object { $_ -match '^\d+$' } | Select-Object -First 1).Trim()
}
catch {
$cpuCores = 0
}
}
$cpuStatus = if ($cpuUsage -lt 70) { "正常" } elseif ($cpuUsage -lt 90) { "警告" } else { "危险" }
$cpuStatus = if ($cpuUsage -lt 70) { "正常" } elseif ($cpuUsage -lt 90) { "警告" } else { "危险" }
$cpuColor = if ($cpuUsage -lt 70) { "SUCCESS" } elseif ($cpuUsage -lt 90) { "WARN" } else { "ERROR" }
Write-Log -Level $cpuColor -Message " CPU 使用率: ${cpuUsage}% (核心数: $cpuCores) [$cpuStatus]"
Write-Log -Level $cpuColor -Message " CPU 使用率: ${cpuUsage}% (核心数: $cpuCores) [$cpuStatus]"
$results.CPU = @{
Usage = $cpuUsage
Cores = $cpuCores
Status = $cpuStatus
Success = ($cpuUsage -lt 90)
}
#endregion 3. 检测 CPU 使用情况
#region 4. 检测内存使用情况
Write-Log -Level "INFO" -Message "检测内存使用情况..."
# 使用 awk 直接读取 /proc/meminfo(单行命令,兼葂dd plink SSH 传输)
#endregion 3. 检测 CPU 使用情况
#region 4. 检测内存使用情况
Write-Log -Level "INFO" -Message "检测内存使用情况..."
# 使用 awk 直接读取 /proc/meminfo(单行命令,兼葂dd plink SSH 传输)
$memCmd = 'awk "/MemTotal/{total=\$2} /MemAvailable/{avail=\$2} /MemFree/{free=\$2} END{if(avail==\"\"||avail==0){avail=free}; used=total-avail; pct=0; if(total>0){pct=used*100/total}; printf \"%.2f,%.2f,%.1f\", total/1048576, used/1048576, pct}" /proc/meminfo'
$memResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $memCmd
$memTotal = 0.0; $memUsed = 0.0; $memPercent = 0.0
if ($memResult.ExitCode -eq 0 -and $memResult.Output) {
$line = ($memResult.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1)
if ($line) {
$parts = ($line.Trim() -replace "`r","") -split ','
if ($parts.Count -ge 3) {
try {
$ci = [System.Globalization.CultureInfo]::InvariantCulture
$memTotal = [double]::Parse($parts[0], $ci)
$memUsed = [double]::Parse($parts[1], $ci)
$memPercent = [double]::Parse($parts[2], $ci)
} catch {
Write-Log -Level "WARN" -Message "free 输出解析失败: $line"
Write-Log -Level "WARN" -Message "free 输出解析失败: $line"
}
}
}
}
if ($memTotal -le 0) {
$fallbackCmd = @'
total_kb=0; avail_kb=0
while IFS=: read k v; do
case "$k" in
"MemTotal") total_kb=${v//[^0-9]/};;
"MemAvailable") avail_kb=${v//[^0-9]/};;
"MemFree") if [ -z "$avail_kb" ] || [ "$avail_kb" -eq 0 ]; then avail_kb=${v//[^0-9]/}; fi;;
esac
done < /proc/meminfo
used_kb=$(( total_kb - avail_kb ))
pct=0
if [ "$total_kb" -gt 0 ]; then pct=$(( used_kb * 100 / total_kb )); fi
tot_gb=$(( total_kb / 1024 / 1024 ))
use_gb=$(( used_kb / 1024 / 1024 ))
printf "%d,%d,%d\n" "$tot_gb" "$use_gb" "$pct"
'@
$fbRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fallbackCmd
if ($fbRes.ExitCode -eq 0 -and $fbRes.Output) {
$fbLine = ($fbRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1)
if ($fbLine) {
$fbParts = ($fbLine.Trim() -replace "`r","") -split ','
if ($fbParts.Count -ge 3) {
$memTotal = [double]$fbParts[0]
$memUsed = [double]$fbParts[1]
$memPercent = [double]$fbParts[2]
}
}
}
}
if ($memTotal -gt 0) {
if ($memUsed -lt 0) { $memUsed = 0 }
if ($memUsed -gt $memTotal) { $memUsed = $memTotal }
$memPercent = [math]::Round(($memUsed / $memTotal) * 100, 1)
$memTotal = [math]::Round($memTotal, 2)
$memUsed = [math]::Round($memUsed, 2)
} else {
Write-Log -Level "WARN" -Message "内存信息获取失败"
Write-Log -Level "WARN" -Message "内存信息获取失败"
}
$memStatus = if ($memPercent -lt 70) { "正常" } elseif ($memPercent -lt 90) { "警告" } else { "危险" }
$memStatus = if ($memPercent -lt 70) { "正常" } elseif ($memPercent -lt 90) { "警告" } else { "危险" }
$memColor = if ($memPercent -lt 70) { "SUCCESS" } elseif ($memPercent -lt 90) { "WARN" } else { "ERROR" }
Write-Log -Level $memColor -Message " 内存使用: ${memUsed}GB / ${memTotal}GB (${memPercent}%) [$memStatus]"
Write-Log -Level $memColor -Message " 内存使用: ${memUsed}GB / ${memTotal}GB (${memPercent}%) [$memStatus]"
$results.Memory = @{ Total = $memTotal; Used = $memUsed; Percent = $memPercent; Status = $memStatus; Success = ($memPercent -lt 90) }
#endregion 4. 检测内存使用情况
#region 5. 检测磁盘空间情况
Write-Log -Level "INFO" -Message "检测磁盘空间情况..."
#endregion 4. 检测内存使用情况
#region 5. 检测磁盘空间情况
Write-Log -Level "INFO" -Message "检测磁盘空间情况..."
$diskCmd = "df -h | grep -E '^/dev/' | awk '{print `$1,`$2,`$3,`$5,`$6}'"
$diskResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $diskCmd
$diskList = @()
$diskWarning = $false
if ($diskResult.ExitCode -eq 0 -and $diskResult.Output) {
$diskLines = $diskResult.Output -split "`n" | Where-Object { $_ -match '\S' }
foreach ($line in $diskLines) {
$parts = $line -split '\s+'
if ($parts.Count -ge 5) {
$device = $parts[0]
$size = $parts[1]
$used = $parts[2]
$usePercent = $parts[3] -replace '%', ''
$mountPoint = $parts[4]
try {
$usePercentNum = [int]$usePercent
}
catch {
$usePercentNum = 0
}
$diskStatus = if ($usePercentNum -lt 70) { "正常" } elseif ($usePercentNum -lt 90) { "警告" } else { "危险" }
$diskStatus = if ($usePercentNum -lt 70) { "正常" } elseif ($usePercentNum -lt 90) { "警告" } else { "危险" }
$diskColor = if ($usePercentNum -lt 70) { "SUCCESS" } elseif ($usePercentNum -lt 90) { "WARN" } else { "ERROR" }
if ($usePercentNum -ge 70) {
$diskWarning = $true
}
Write-Log -Level $diskColor -Message " 磁盘 $mountPoint : ${used}/${size} (${usePercent}%) [$diskStatus]"
Write-Log -Level $diskColor -Message " 磁盘 $mountPoint : ${used}/${size} (${usePercent}%) [$diskStatus]"
$diskList += @{
Device = $device
Size = $size
Used = $used
Percent = $usePercentNum
MountPoint = $mountPoint
Status = $diskStatus
}
}
}
}
else {
Write-Log -Level "WARN" -Message " 无法获取磁盘信息"
Write-Log -Level "WARN" -Message " 无法获取磁盘信息"
}
$results.Disk = $diskList
#endregion 5. 检测磁盘空间情况
#region 6. 检测防火墙开放端口情况
Write-Log -Level "INFO" -Message "检测防火墙开放端口..."
#endregion 5. 检测磁盘空间情况
#region 6. 检测防火墙开放端口情况
Write-Log -Level "INFO" -Message "检测防火墙开放端口..."
$firewallStatusCmd = "systemctl is-active firewalld 2>/dev/null || service iptables status 2>/dev/null | head -n 1 || echo 'unknown'"
$firewallStatusResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $firewallStatusCmd
$firewallActive = $false
$firewallType = "unknown"
$statusLine = ($firewallStatusResult.Output | Select-Object -First 1)
if ($statusLine) { $statusLine = $statusLine.Trim().ToLower() } else { $statusLine = "unknown" }
if ($statusLine -eq "active") {
$firewallActive = $true
$firewallType = "firewalld"
} elseif ($statusLine -eq "inactive" -or $statusLine -eq "failed" -or $statusLine -eq "unknown") {
$ipLine = ($firewallStatusResult.Output | Select-Object -Last 1)
if ($ipLine) {
$ipl = $ipLine.Trim().ToLower()
if ($ipl -match '\brunning\b' -or $ipl -match '\bok\b') {
$firewallActive = $true
$firewallType = "iptables"
} elseif ($ipl -match 'stopped|not running|inactive|failed') {
$firewallActive = $false
$firewallType = "iptables"
}
}
}
$openPorts = @()
if ($firewallActive) {
if ($firewallType -eq "firewalld") {
$portsCmd = "firewall-cmd --list-ports 2>/dev/null && firewall-cmd --list-services 2>/dev/null"
$portsResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $portsCmd
if ($portsResult.ExitCode -eq 0 -and $portsResult.Output) {
$openPorts = ($portsResult.Output -split "`n" | Where-Object { $_ -match '\S' }) -join ", "
}
} else {
$portsCmd = "iptables -L INPUT -n 2>/dev/null | grep ACCEPT | grep -oP 'dpt:\d+' | cut -d: -f2 | sort -u | head -n 50"
$portsResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $portsCmd
if ($portsResult.ExitCode -eq 0 -and $portsResult.Output) {
$openPorts = ($portsResult.Output -split "`n" | Where-Object { $_ -match '^\d+$' }) -join ", "
}
}
} else {
$openPorts = "防火墙未启用"
$openPorts = "防火墙未启用"
}
$results.Firewall = @{
Active = $firewallActive
Type = $firewallType
OpenPorts = $openPorts
Status = if ($firewallActive) { "已启用" } else { "未启用" }
Status = if ($firewallActive) { "已启用" } else { "未启用" }
Pre = @{
Active = $firewallActive
Type = $firewallType
OpenPorts = $openPorts
}
}
if ($firewallActive) {
Write-Log -Level "INFO" -Message ("[FIREWALL] 当前状态: 已启用 ({0})" -f $firewallType)
Write-Log -Level "INFO" -Message ("[FIREWALL] 当前状态: 已启用 ({0})" -f $firewallType)
if ($openPorts -and $openPorts -ne "") {
Write-Log -Level "INFO" -Message ("[FIREWALL] 开放端口/服务: {0}" -f $openPorts)
Write-Log -Level "INFO" -Message ("[FIREWALL] 开放端口/服务: {0}" -f $openPorts)
}
} else {
Write-Log -Level "WARN" -Message ("[FIREWALL] 当前状态: 未启用 ({0})" -f $firewallType)
Write-Log -Level "WARN" -Message ("[FIREWALL] 当前状态: 未启用 ({0})" -f $firewallType)
}
# 触发远端修复
# 防火墙自动修复(增加用户确认交互)
if (-not $firewallActive -or ($firewallType -eq "unknown")) {
Write-Log -Level "WARN" -Message "[FIREWALL] 检测到防火墙未启用或状态异常,准备执行远端修复"
Write-Host "检测到防火墙未启用或状态异常,是否执行远程修复?" -ForegroundColor Yellow
$repairChoice = Read-Host "执行修复? (y/n) [默认: n]"
if ($repairChoice -eq "y" -or $repairChoice -eq "Y") {
Write-Log -Level "INFO" -Message "[FIREWALL] 用户确认执行远程修复 (fix_port_access)"
try {
$serverForRepair = @{ IP = $Server.IP; User = $Server.User; Pass = $Server.Pass; Port = $Server.Port }
Write-Log -Level "INFO" -Message "[FIREWALL] 触发远端修复: ./issue_handler.sh --action fix_port_access --platform auto --non-interactive"
Write-Log -Level "INFO" -Message "[FIREWALL] 调用自动修复: ./issue_handler.sh --action fix_port_access --platform auto --non-interactive"
$fwRepairRes = Upload_the_repair_script -Server $serverForRepair -Action "fix_port_access" -Platform "auto" -RemoteDir "/home/repair_scripts"
$results.Firewall.Repair = @{ Attempted = $true; Succeeded = $false; Message = "fix_port_access (platform=auto)" }
if ($fwRepairRes -and $fwRepairRes['Success']) {
Write-Log -Level "SUCCESS" -Message "[FIREWALL] 远端修复已执行成功 (fix_port_access)"
Write-Log -Level "SUCCESS" -Message "[FIREWALL] 自动修复脚本执行成功 (fix_port_access)"
$results.Firewall.Repair.Succeeded = $true
# 修复后复检
# 修复后复检
$firewallActive = $false; $firewallType = "unknown"
$firewallStatusResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $firewallStatusCmd
$statusLine = ($firewallStatusResult.Output | Select-Object -First 1)
......@@ -433,196 +824,366 @@ printf "%d,%d,%d\n" "$tot_gb" "$use_gb" "$pct"
$results.Firewall.Active = $firewallActive
$results.Firewall.Type = $firewallType
$results.Firewall.OpenPorts = $openPorts
$results.Firewall.Status = if ($firewallActive) { "已启用" } else { "未启用" }
$results.Firewall.Status = if ($firewallActive) { "已启用" } else { "未启用" }
if ($firewallActive) {
Write-Log -Level "INFO" -Message ("[FIREWALL] 修复后状态: 已启用 ({0})" -f $firewallType)
Write-Log -Level "INFO" -Message ("[FIREWALL] 修复后状态: 已启用 ({0})" -f $firewallType)
if ($openPorts -and $openPorts -ne "") {
Write-Log -Level "INFO" -Message ("[FIREWALL] 修复后开放端口/服务: {0}" -f $openPorts)
Write-Log -Level "INFO" -Message ("[FIREWALL] 修复后开放端口/服务: {0}" -f $openPorts)
}
} else {
Write-Log -Level "WARN" -Message ("[FIREWALL] 修复后状态仍为未启用 ({0})" -f $firewallType)
Write-Log -Level "WARN" -Message ("[FIREWALL] 修复后状态仍为未启用 ({0})" -f $firewallType)
}
} else {
$errMsg = "未知错误"
$errMsg = "未知错误"
if ($fwRepairRes -is [hashtable]) {
if ($fwRepairRes.ContainsKey('Error') -and $fwRepairRes['Error']) { $errMsg = [string]::Join(' ', $fwRepairRes['Error']) }
elseif ($fwRepairRes.ContainsKey('Output') -and $fwRepairRes['Output']) { $errMsg = [string]::Join(' ', $fwRepairRes['Output']) }
elseif ($fwRepairRes.ContainsKey('Message') -and $fwRepairRes['Message']) { $errMsg = $fwRepairRes['Message'] }
} elseif ($fwRepairRes) { $errMsg = $fwRepairRes.ToString() }
Write-Log -Level "ERROR" -Message "[FIREWALL] 远端修复执行失败: $errMsg"
$results.Firewall.Repair.Message = "修复失败: $errMsg"
Write-Log -Level "ERROR" -Message "[FIREWALL] 自动修复执行失败: $errMsg"
$results.Firewall.Repair.Message = "修复失败: $errMsg"
}
} catch {
Write-Log -Level "ERROR" -Message "[FIREWALL] 调用 Upload_the_repair_script 异常: $($_.Exception.Message)"
$results.Firewall.Repair = @{ Attempted = $true; Succeeded = $false; Message = "异常: $($_.Exception.Message)" }
Write-Log -Level "ERROR" -Message "[FIREWALL] 调用 Upload_the_repair_script 异常: $($_.Exception.Message)"
$results.Firewall.Repair = @{ Attempted = $true; Succeeded = $false; Message = "异常: $($_.Exception.Message)" }
}
} else {
Write-Log -Level "INFO" -Message "[FIREWALL] 用户取消修复操作,跳过防火墙修复"
$results.Firewall.Repair = @{ Attempted = $false; Succeeded = $false; Message = "用户取消修复" }
}
}
#endregion 6. 检测防火墙开放端口情况
#endregion 6. 检测防火墙开放端口情况
#region 7. 检测系统负载
Write-Log -Level "INFO" -Message "检测系统负载..."
#region 7. 检测系统负载
Write-Log -Level "INFO" -Message "检测系统负载..."
$loadCmd = "uptime | awk -F'load average:' '{print `$2}' | tr -d ' '"
$loadResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $loadCmd
if ($loadResult.ExitCode -eq 0 -and $loadResult.Output) {
$loadAvg = ($loadResult.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1).Trim()
$loadParts = $loadAvg -split ','
if ($loadParts.Count -ge 1) {
$load1 = $loadParts[0].Trim()
Write-Log -Level "INFO" -Message " 系统负载 (1/5/15分钟): $loadAvg"
Write-Log -Level "INFO" -Message " 系统负载 (1/5/15分钟): $loadAvg"
}
}
#endregion 7. 检测系统负载
#region 8. inode 浣跨敤鐜囨娴 (PRD 2.5)
Write-Log -Level "INFO" -Message "妫娴 inode 浣跨敤鐜..."
#endregion 7. 检测系统负载
#region 8. inode 浣跨敤鐜囨��娴� (PRD 2.5)
Write-Log -Level "INFO" -Message "妫�娴� inode 浣跨敤鐜�..."
$inodeCmd = "df -i | grep -E '^/dev/' | awk '{print `$1,`$2,`$3,`$4,`$5,`$6}'"
$inodeResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $inodeCmd
$inodeList = @()
if ($inodeResult.ExitCode -eq 0 -and $inodeResult.Output) {
$inodeLines = $inodeResult.Output -split "`n" | Where-Object { $_ -match '\S' }
foreach ($line in $inodeLines) {
$parts = $line -split '\s+'
if ($parts.Count -ge 6) {
$device = $parts[0]
$iTotal = $parts[1]
$iUsed = $parts[2]
$iFree = $parts[3]
$iPercent = $parts[4] -replace '%', ''
$mountPoint = $parts[5]
# 杩囨护 tmpfs/overlay 绛夎櫄鎷熸枃浠剁郴缁
# 杩囨护 tmpfs/overlay 绛夎櫄鎷熸枃浠剁郴缁�
if ($device -match 'tmpfs|overlay') { continue }
try { $iPercentNum = [int]$iPercent } catch { $iPercentNum = 0 }
$inodeStatus = if ($iPercentNum -lt 70) { "姝e父" } elseif ($iPercentNum -lt 90) { "璀﹀憡" } else { "寮傚父" }
$inodeStatus = if ($iPercentNum -lt 70) { "姝e父" } elseif ($iPercentNum -lt 90) { "璀﹀憡" } else { "寮傚父" }
$inodeColor = if ($iPercentNum -lt 70) { "SUCCESS" } elseif ($iPercentNum -lt 90) { "WARN" } else { "ERROR" }
Write-Log -Level $inodeColor -Message " inode $mountPoint : ${iUsed}/${iTotal} (${iPercent}%) [$inodeStatus]"
$inodeList += @{
Device = $device
Total = $iTotal
Used = $iUsed
Percent = $iPercentNum
MountPoint = $mountPoint
Status = $inodeStatus
}
}
}
}
$results.Inode = $inodeList
#endregion 8. inode 浣跨敤鐜囨娴
#region 9. 鍙鎸傝浇妫娴 (PRD 2.5)
Write-Log -Level "INFO" -Message "妫娴嬪彧璇绘寕杞..."
#endregion 8. inode 浣跨敤鐜囨��娴�
#region 9. 鍙�璇绘寕杞芥��娴� (PRD 2.5)
Write-Log -Level "INFO" -Message "妫�娴嬪彧璇绘寕杞�..."
$roCmd = "mount | grep ' ro[, ]' | grep -vE 'proc|sys|dev|cgroup|tmpfs' || echo 'NO_RO'"
$roResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $roCmd
$roList = @()
if ($roResult.ExitCode -eq 0 -and $roResult.Output) {
$roOutput = $roResult.Output -join ""
if ($roOutput -match 'NO_RO') {
Write-Log -Level "SUCCESS" -Message " 鏃犳剰澶栧彧璇绘寕杞"
Write-Log -Level "SUCCESS" -Message " 鏃犳剰澶栧彧璇绘寕杞�"
} else {
$roLines = $roResult.Output -split "`n" | Where-Object { $_ -match '\S' }
foreach ($line in $roLines) {
Write-Log -Level "WARN" -Message " 鍙戠幇鍙鎸傝浇: $line"
Write-Log -Level "WARN" -Message " 鍙戠幇鍙�璇绘寕杞�: $line"
$roList += $line
}
}
}
$results.ReadOnlyMounts = $roList
#endregion 9. 鍙鎸傝浇妫娴
#region 10. TCP 鐘舵佸垎甯冩娴 (PRD 2.5)
Write-Log -Level "INFO" -Message "妫娴 TCP 鐘舵佸垎甯..."
#endregion 9. 鍙�璇绘寕杞芥��娴�
#region 10. TCP 鐘舵�佸垎甯冩��娴� (PRD 2.5)
Write-Log -Level "INFO" -Message "妫�娴� TCP 鐘舵�佸垎甯�..."
$tcpCmd = "netstat -ant 2>/dev/null | awk '{print `$6}' | sort | uniq -c | sort -rn"
$tcpResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $tcpCmd
$tcpStatus = @{}
if ($tcpResult.ExitCode -eq 0 -and $tcpResult.Output) {
$tcpLines = $tcpResult.Output -split "`n" | Where-Object { $_ -match '\S' }
foreach ($line in $tcpLines) {
if ($line -match '^\s*(\d+)\s+(\S+)') {
$count = [int]$matches[1]
$state = $matches[2]
$tcpStatus[$state] = $count
}
}
# 妫鏌ュ紓甯哥姸鎬
# 妫�鏌ュ紓甯哥姸鎬�
$closeWait = if ($tcpStatus['CLOSE_WAIT']) { $tcpStatus['CLOSE_WAIT'] } else { 0 }
$timeWait = if ($tcpStatus['TIME_WAIT']) { $tcpStatus['TIME_WAIT'] } else { 0 }
$tcpWarning = ""
if ($closeWait -gt 100) { $tcpWarning += "CLOSE_WAIT=$closeWait (>100) " }
if ($timeWait -gt 1000) { $tcpWarning += "TIME_WAIT=$timeWait (>1000) " }
if ($tcpWarning) {
Write-Log -Level "WARN" -Message " TCP鐘舵佸紓甯: $tcpWarning"
Write-Log -Level "WARN" -Message " TCP鐘舵�佸紓甯�: $tcpWarning"
} else {
Write-Log -Level "SUCCESS" -Message " TCP鐘舵佹甯: CLOSE_WAIT=$closeWait TIME_WAIT=$timeWait"
Write-Log -Level "SUCCESS" -Message " TCP鐘舵�佹�e父: CLOSE_WAIT=$closeWait TIME_WAIT=$timeWait"
}
}
$results.TCPStatus = $tcpStatus
#endregion 10. TCP 鐘舵佸垎甯冩娴
#region 11. 鍍靛案杩涚▼鍜 TOP5 妫娴 (PRD 2.5)
Write-Log -Level "INFO" -Message "妫娴嬪兊灏歌繘绋..."
#endregion 10. TCP 鐘舵�佸垎甯冩��娴�
#region 11. 鍍靛案杩涚▼鍜� TOP5 妫�娴� (PRD 2.5)
Write-Log -Level "INFO" -Message "妫�娴嬪兊灏歌繘绋�..."
$zombieCmd = "ps aux | awk '`$8~/Z/' | grep -v grep || echo 'NO_ZOMBIE'"
$zombieResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $zombieCmd
$zombieCount = 0
$zombieList = @()
if ($zombieResult.ExitCode -eq 0 -and $zombieResult.Output) {
$zOutput = $zombieResult.Output -join ""
if ($zOutput -match 'NO_ZOMBIE') {
Write-Log -Level "SUCCESS" -Message " 鏃犲兊灏歌繘绋"
Write-Log -Level "SUCCESS" -Message " 鏃犲兊灏歌繘绋�"
} else {
$zLines = $zombieResult.Output -split "`n" | Where-Object { $_ -match '\S' }
$zombieCount = $zLines.Count
Write-Log -Level "WARN" -Message " 鍙戠幇 $zombieCount 涓兊灏歌繘绋"
Write-Log -Level "WARN" -Message " 鍙戠幇 $zombieCount 涓�鍍靛案杩涚▼"
$zombieList = $zLines
}
}
$results.ZombieProcesses = @{ Count = $zombieCount; List = $zombieList }
# TOP5 杩涚▼锛堟寜鍐呭瓨鍜孋PU锛
Write-Log -Level "INFO" -Message "鑾峰彇 TOP5 杩涚▼..."
# TOP5 杩涚▼锛堟寜鍐呭瓨鍜孋PU锛�
Write-Log -Level "INFO" -Message "鑾峰彇 TOP5 杩涚▼..."
$topCmd = "ps aux --sort=-%mem | head -n 6 | awk '{print `$1,`$2,`$3,`$4,`$11}'"
$topResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $topCmd
$topProcesses = @()
if ($topResult.ExitCode -eq 0 -and $topResult.Output) {
$topLines = $topResult.Output -split "`n" | Where-Object { $_ -match '\S' }
foreach ($line in $topLines) {
$topProcesses += $line.Trim()
}
Write-Log -Level "INFO" -Message " TOP5杩涚▼锛堟寜鍐呭瓨锛:"
Write-Log -Level "INFO" -Message " TOP5杩涚▼锛堟寜鍐呭瓨锛�:"
foreach ($p in $topProcesses | Select-Object -Skip 1) {
Write-Log -Level "INFO" -Message " $p"
}
}
$results.TopProcesses = $topProcesses
#endregion 11. 鍍靛案杩涚▼鍜 TOP5 妫娴
#endregion 11. 鍍靛案杩涚▼鍜� TOP5 妫�娴�
return $results
}
#endregion Server Resource Analysis Functions
# ==============================================================================
# 瀵煎嚭妯″潡鍑芥暟
# 瀵煎嚭妯″潡鍑芥暟
# ==============================================================================
Export-ModuleMember -Function @(
'Test-ServerResources'
)
......@@ -559,6 +559,20 @@ function Repair-ExternalMeetingService {
[hashtable]$Server
)
Write-Host "检测到对外服务未运行,是否执行远程修复?" -ForegroundColor Yellow
$repairChoice = Read-Host "执行修复? (y/n) [默认: n]"
if ($repairChoice -ne "y" -and $repairChoice -ne "Y") {
Write-Log -Level "INFO" -Message "[EXT] 用户取消修复操作,跳过对外服务修复"
return [pscustomobject]@{
Target = "external-meeting-api"
Attempted = $false
Success = $false
Detail = "用户取消修复"
}
}
Write-Log -Level "INFO" -Message "[EXT] 用户确认执行远程修复 (fix_external_service_disconnect)"
Write-Log -Level "INFO" -Message "[EXT] 准备自动修复: ./issue_handler.sh --action fix_external_service_disconnect"
$serverForRepair = $Server
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论