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