提交 5af4f8bd authored 作者: 陈泽健's avatar 陈泽健

feat(server-check): 新增NTP服务与文件权限检测功能

- 新增对目标服务器NTP/Chrony服务的安装与运行状态检测
- 增加服务器时间与本地时间一致性校验逻辑
- 实现关键配置文件与目录的权限检查功能
- 引入现场数据备份功能,支持传统平台服务数据打包导出
- 更新主检测流程以集成新增检测项及结果展示
- 修复文档中关于文件权限检测的描述错误
- 补充DataBakup函数实现,包括目录复制、数据库导出与压缩下载
- 调整日志记录结构以支持新检测模块的结果追踪
- 优化远程命令执行与本地文件下载的容错处理机制
上级 849b58fa
......@@ -204,5 +204,24 @@
2、数据库用户权限需要进入umysql容器内,数据库账号为root,密码为dNrprU&2S
注意:此检测函数需要在日志导出函数前执行,并且main主函数和日志记录函数都需要补充调用!将文件权限打印出来!
##### 现场数据备份(待实现):
函数名称:DataBakup
先判断目标服务器是新统一平台还是传统平台,再备份对应平台的服务包与配置文件等数据,最后在目标服务器上压缩成tar.gz格式文件导出到电脑上。
传统平台:
如果有ujava容器:
1、将/var/www/java目录复制到/home/bakup目录下
如果有upython容器:
1、将/var/www/html目录复制到/home/bakup目录下
如果有cardtable容器:
1、将/var/www/wifi-local目录复制到/home/bakup目录下
如果有paperless容器:
1、将/var/www/paperless目录复制到/home/bakup目录下
共有:
1、将/var/www/emqx和/var/www/redis目录复制到/home/bakup目录下
2、数据库备份,账号为root,密码为dNrprU&2S,数据库容器是umysql容器,数据库是ubains和devops,数据库备份完成后也复制到/home/bakup目录下
最后将/home/bakup目录压缩成tar.gz格式文件并导出,文件命名补充时间戳,导出完成后清理/home目录下的这个备份文件。
##### 服务自检报告输出(✅ 已实现):
将服务自检的所有操作步骤与结果输出到日志文件中!
\ No newline at end of file
......@@ -35,7 +35,7 @@
将 plink.exe 放在脚本同目录下
脚本会自动检测并使用本地的可执行文件
下载地址(可在有网络的电脑下载后贝):
下载地址(可在有网络的电脑下载后贝):
https://the.earth.li/~sgtatham/putty/latest/w64/plink.exe
方式2: 已安装 PuTTY 到系统 PATH
......@@ -1516,7 +1516,9 @@ function Show-HealthReport {
[array]$UpythonVoiceResults,
[array]$DNSResults,
[hashtable]$ResourceResults,
[hashtable]$LogExportResults
[hashtable]$LogExportResults,
[hashtable]$NTPResults,
[hashtable]$FilePermResults
)
Write-Host ""
......@@ -1685,7 +1687,15 @@ function Show-HealthReport {
}
# 服务器NTP服务分析结果
Write-Host "【NTP 服务检测】" -ForegroundColor Yellow
if ($NTPResults) { Write-Host " $($NTPResults.Status): $($NTPResults.Detail)" } else { Write-Host " 未执行" -ForegroundColor Yellow }
Write-Host ""
# 配置文件权限检测
Write-Host "【文件权限检测】" -ForegroundColor Yellow
if ($FilePermResults) { Write-Host " $($FilePermResults.Summary)" } else { Write-Host " 未执行" -ForegroundColor Yellow }
Write-Host ""
# 总结
Write-Host "==================================================================" -ForegroundColor Cyan
Write-Host "【检测总结】" -ForegroundColor Yellow
......@@ -1885,53 +1895,36 @@ function Check-NTPService {
[string]$Username,
[string]$Password
)
$summary = @{ Status = '未执行'; Detail = '' }
Write-Host "开始检测目标服务器的NTP服务..." -ForegroundColor Yellow
# 检测是否安装了 NTP 或 Chrony 服务
Write-Log -Level "INFO" -Message "[NTP] 检测服务是否安装 (ntp/chronyd)"
$Command = "systemctl list-units --type=service | grep -E 'ntp|chronyd'"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $Command
Write-Log -Level "INFO" -Message "[NTP] systemctl 输出: $([string]::Join(' ', $Result.Output))"
if ($Result.ExitCode -eq 0 -and $Result.Output -match "ntp|chronyd") {
Write-Host "目标服务器已安装 NTP 或 Chrony 服务." -ForegroundColor Green
# 检测 NTP 服务状态
Write-Log -Level "SUCCESS" -Message "[NTP] 已检测到 NTP/Chrony 服务"
$StatusCommand = "timedatectl status"
$StatusResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $StatusCommand
Write-Log -Level "INFO" -Message "[NTP] timedatectl 输出: $([string]::Join(' ', $StatusResult.Output))"
if ($StatusResult.ExitCode -eq 0) {
Write-Host "NTP 服务状态: " -ForegroundColor Cyan
Write-Host $StatusResult.Output
# 检测服务器时间与北京时间是否一致
$TimeCommand = "date +%s"
$TimeCommand = "date +%s"; Write-Log -Level "INFO" -Message "[NTP] 读取服务器时间: $TimeCommand"
$ServerTimeResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $TimeCommand
Write-Log -Level "INFO" -Message "[NTP] 服务器时间戳: $([string]::Join(' ', $ServerTimeResult.Output))"
if ($ServerTimeResult.ExitCode -eq 0) {
$ServerTimeUTC = [int]$ServerTimeResult.Output.Trim()
# Adjust server time to local timezone (Asia/Shanghai)
$ServerTime = $ServerTimeUTC + (8 * 3600) # Add 8 hours for CST
$LocalTime = [int](Get-Date -UFormat %s)
$TimeDifference = [math]::Abs($ServerTime - $LocalTime)
if ($TimeDifference -le 5) {
Write-Host "服务器时间与北京时间一致." -ForegroundColor Green
} else {
Write-Warning "服务器时间与北京时间存在偏差: $TimeDifference 秒"
}
} else {
Write-Warning "无法获取目标服务器时间."
}
} else {
Write-Warning "无法获取 NTP 服务状态."
}
$ServerTimeUTC = [int]$ServerTimeResult.Output.Trim(); $ServerTime = $ServerTimeUTC + (8 * 3600)
$LocalTime = [int](Get-Date -UFormat %s); $TimeDifference = [math]::Abs($ServerTime - $LocalTime)
Write-Log -Level "INFO" -Message "[NTP] 本地时间戳: $LocalTime, 差值: $TimeDifference 秒"
if ($TimeDifference -le 5) { Write-Log -Level "SUCCESS" -Message "[NTP] 服务器时间与北京时间一致" }
else { Write-Log -Level "WARN" -Message "[NTP] 时间偏差 ${TimeDifference} 秒" }
} else { Write-Log -Level "ERROR" -Message "[NTP] 获取服务器时间失败" }
} else { Write-Log -Level "ERROR" -Message "[NTP] timedatectl 不可用" }
} else {
Write-Warning "目标服务器未安装 NTP 或 Chrony 服务."
Write-Log -Level "WARN" -Message "[NTP] 未检测到 NTP/Chrony 服务"
}
Write-Host "NTP 服务检测完成." -ForegroundColor Green
Write-Log -Level "INFO" -Message "[NTP] 检测结束"
}
# ================================
......@@ -1991,20 +1984,139 @@ function Check-FilePermissions {
)
}
Write-Log -Level "INFO" -Message "[PERM] 目标列表生成 (平台: $PlatformType)"
foreach ($path in $targets) { Write-Log -Level "INFO" -Message "[PERM] 待检查: $path" }
$lines = @()
foreach ($path in $targets) {
$cmd = "if ls -l $path 2>/dev/null; then echo '__PERM_OK__'; else echo '__PERM_MISS__ $path'; fi"
Write-Log -Level "INFO" -Message "[PERM] 执行: $cmd"
$res = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
$out = ($res.Output -join "`n")
$out = ($res.Output -join " ")
Write-Log -Level "INFO" -Message "[PERM] 输出: $out"
if ($out -match "__PERM_OK__") {
($out -split "`r?`n") | Where-Object { $_ -match "^[-dl]" } | ForEach-Object {
Write-Log -Level 'SUCCESS' -Message "权限: $_"
($res.Output) | Where-Object { $_ -match "^[-dl]" } | ForEach-Object {
Write-Log -Level 'SUCCESS' -Message "[PERM] 权限: $_"; $lines += $_
}
} else { Write-Log -Level 'ERROR' -Message "[PERM] 未找到: $path"; $lines += "MISS $path" }
}
Write-Log -Level "INFO" -Message "[PERM] 检测结束: 共 $($lines.Count) 项"
}
# ================================
# 检测 DataBakup 服务 (待实现)
# ================================
function DataBakup {
param (
[Parameter(Mandatory=$true)] [hashtable]$Server,
[Parameter(Mandatory=$true)] [ValidateSet('new','old')] [string]$PlatformType,
[Parameter(Mandatory=$true)] [hashtable]$SystemInfo
)
Write-Log -Level "INFO" -Message "开始现场数据备份 (平台: $PlatformType) ..."
$bakDir = "/home/bakup"
$cmds = @(
"set -e",
"mkdir -p $bakDir"
)
$hasUjava = ($SystemInfo.ContainsKey('ujava') -and $SystemInfo['ujava'])
$hasUpython = ($SystemInfo.ContainsKey('upython') -and $SystemInfo['upython'])
$hasCardtable = ($SystemInfo.ContainsKey('cardtable') -and $SystemInfo['cardtable'])
$hasPaperless = ($SystemInfo.ContainsKey('paperless') -and $SystemInfo['paperless'])
$hasUmysql = ($SystemInfo.ContainsKey('umysql') -and $SystemInfo['umysql'])
# 仅实现传统平台备份
if ($PlatformType -eq 'old') {
# 总是尝试复制关键目录,无论容器检测结果
$cmds += "[ -d /var/www/java ] && cp -a /var/www/java $bakDir/"
$cmds += "[ -d /var/www/html ] && cp -a /var/www/html $bakDir/"
# 其他按需目录
if ($hasCardtable) { $cmds += "[ -d /var/www/wifi-local ] && cp -a /var/www/wifi-local $bakDir/" }
if ($hasPaperless) { $cmds += "[ -d /var/www/paperless ] && cp -a /var/www/paperless $bakDir/" }
$cmds += "[ -d /var/www/emqx ] && cp -a /var/www/emqx $bakDir/"
$cmds += "[ -d /var/www/redis ] && cp -a /var/www/redis $bakDir/"
} else {
Write-Log -Level "WARN" -Message "新统一平台备份暂未实现,已跳过"
}
# 数据库备份 (umysql 容器内)
if ($hasUmysql -and $PlatformType -eq 'old') {
$dbUser = "root"; $dbPass = "dNrprU&2S"; $dbs = @("ubains","devops")
$cmds += "mkdir -p /tmp/bak_sql"
foreach ($db in $dbs) {
# 在 bash 的单引号上下文中安全地插入密码: -p'password'
$passEsc = "'\'" + "'" + $dbPass + "'\'" + "'" # 生成: '\''password'\'' -> 等效于 bash 中的 'password'
$mysqldumpCmd = "if docker ps --format '{{.Names}}' | grep -q '^umysql$'; then docker exec umysql sh -c 'mysqldump -u" + $dbUser + " -p" + $passEsc + " " + $db + " > /tmp/bak_sql/" + $db + "_$(date +%Y%m%d%H%M%S).sql'; fi"
$cmds += $mysqldumpCmd
}
$cmds += "if ls /tmp/bak_sql/*.sql >/dev/null 2>&1; then cp -a /tmp/bak_sql/*.sql $bakDir/; fi || true"
}
Write-Log -Level "INFO" -Message "[BAK] 创建备份目录: $bakDir"
# 展示计划复制的目录
Write-Log -Level "INFO" -Message "[BAK] 平台: $PlatformType, 容器: ujava=$hasUjava upython=$hasUpython cardtable=$hasCardtable paperless=$hasPaperless umysql=$hasUmysql"
foreach ($c in $cmds) { Write-Log -Level "INFO" -Message "[BAK] 计划执行: $c" }
$joined = ($cmds -join '; ')
Write-Log -Level "INFO" -Message "[BAK] 执行远程备份命令"
$res = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $joined
$bakOutput = if ($res.Output) { [string]::Join(' ', $res.Output) } else { '' }
Write-Log -Level "INFO" -Message "[BAK] 备份输出: $bakOutput"
if ($res.ExitCode -ne 0) { Write-Log -Level "ERROR" -Message "[BAK] 远程备份步骤失败"; return @{ Summary = "失败" } }
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"; $tarName = "bakup_${timestamp}.tar.gz"; $tarPath = "/home/$tarName"
$packCmd = "set -e; tar -czf $tarPath -C /home bakup"
Write-Log -Level "INFO" -Message "[BAK] 压缩命令: $packCmd"
$res2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $packCmd
$packOutput = if ($res2.Output) { [string]::Join(' ', $res2.Output) } else { '' }
Write-Log -Level "INFO" -Message "[BAK] 压缩输出: $packOutput"
if ($res2.ExitCode -ne 0) { Write-Log -Level "ERROR" -Message "[BAK] 压缩备份失败"; return @{ Summary = "失败" } }
if (-not $script:PSCP_PATH) {
Write-Log -Level "ERROR" -Message "[BAK] 未找到 pscp.exe,无法下载备份文件"; $downloadSummary = "未下载"
} else {
# 使用 ASCII 路径避免编码问题
$localOutDir = Join-Path $SCRIPT_DIR "Downloads"
if (-not (Test-Path $localOutDir)) { New-Item -ItemType Directory -Path $localOutDir | Out-Null }
$localFile = Join-Path $localOutDir $tarName
# 检查磁盘空间(至少比远端文件大小大 10% 余量;无法获取时仅检查 1GB 余量)
$drive = Get-PSDrive -Name (Split-Path $localOutDir -Qualifier)
if ($drive -and ($drive.Free -lt 1GB)) {
Write-Log -Level "WARN" -Message "[BAK] 本地磁盘可用空间不足 1GB,可能导致下载失败"
}
# 构造下载命令,添加 -batch 防交互
$pscpCmd = "`"$($script:PSCP_PATH)`" -batch -scp -P $($Server.Port) -pw `"$($Server.Pass)`" $($Server.User)@$($Server.IP):$tarPath `"$localFile`""
Write-Log -Level "INFO" -Message "[BAK] 下载命令: $pscpCmd"
$dl = & powershell -NoProfile -Command $pscpCmd
if ($LASTEXITCODE -ne 0) {
Write-Log -Level "ERROR" -Message "[BAK] 下载备份失败,尝试使用 TEMP 目录重试"
$fallbackDir = Join-Path $env:TEMP "ubains_downloads"
if (-not (Test-Path $fallbackDir)) { New-Item -ItemType Directory -Path $fallbackDir | Out-Null }
$fallbackFile = Join-Path $fallbackDir $tarName
$pscpCmd2 = "`"$($script:PSCP_PATH)`" -batch -scp -P $($Server.Port) -pw `"$($Server.Pass)`" $($Server.User)@$($Server.IP):$tarPath `"$fallbackFile`""
Write-Log -Level "INFO" -Message "[BAK] 重试下载命令: $pscpCmd2"
$dl2 = & powershell -NoProfile -Command $pscpCmd2
if ($LASTEXITCODE -ne 0) {
Write-Log -Level "ERROR" -Message "[BAK] 重试下载失败"
$downloadSummary = "下载失败"
} else {
Write-Log -Level "SUCCESS" -Message "[BAK] 备份已下载: $fallbackFile"
$downloadSummary = "已下载(TEMP)"
}
} else {
Write-Log -Level 'ERROR' -Message "未找到文件: $path"
Write-Log -Level "SUCCESS" -Message "[BAK] 备份已下载: $localFile"
$downloadSummary = "已下载"
}
}
Write-Log -Level "INFO" -Message "文件权限检测完成"
$cleanCmd = "rm -rf $bakDir; rm -f $tarPath; rm -rf /tmp/bak_sql"; Write-Log -Level "INFO" -Message "[BAK] 清理命令: $cleanCmd"
[void] (Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cleanCmd)
Write-Log -Level "INFO" -Message "[BAK] 清理完成"
Write-Log -Level "INFO" -Message "[BAK] 备份流程完成"
return @{ Summary = "完成 ($downloadSummary)" }
}
# ================================
......@@ -2125,11 +2237,20 @@ function Main {
# 检测 NTP 服务
Write-Log -Level "INFO" -Message "开始检测 NTP 服务..."
Check-NTPService -ServerIP $server.IP -Username $server.User -Password $server.Pass
$ntpResults = Check-NTPService -ServerIP $server.IP -Username $server.User -Password $server.Pass
Write-Log -Level "INFO" -Message "NTP 服务检测完成."
# 文件权限检测 (在日志导出之前执行)
Check-FilePermissions -Server $server -PlatformType $platformType -SystemInfo $systemInfo
# 文件权限检测
$filePermResults = Check-FilePermissions -Server $server -PlatformType $platformType -SystemInfo $systemInfo
# 现场数据备份 (可选)
$bakChoice = Read-Host "是否执行现场数据备份并下载? (y/n) [默认: n]"
$bakupResults = $null
if ($bakChoice -eq 'y' -or $bakChoice -eq 'Y') {
$bakupResults = DataBakup -Server $server -PlatformType $platformType -SystemInfo $systemInfo
} else {
Write-Log -Level "INFO" -Message "跳过现场数据备份"
}
# 询问是否导出日志
Write-Host ""
......@@ -2152,7 +2273,9 @@ function Main {
-UpythonVoiceResults $upythonVoiceResults `
-DNSResults $dnsResults `
-ResourceResults $resourceResults `
-LogExportResults $logExportResults
-LogExportResults $logExportResults `
-NTPResults $ntpResults `
-FilePermResults $filePermResults
}
# 执行主函数
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论