提交 664d318d authored 作者: 陈泽健's avatar 陈泽健

feat(service-monitor): P3级检测模块移植-文件权限检测/定时任务预期检测

新增 2 个检测模块(阶段四):
- 54_file_permission_check.sh:文件权限检测(检查关键文件可执行权限)
- 55_cron_expected.sh:定时任务预期检测(检查预期crontab条目存在)

配套更新:
- check_modules.py 追加 2 个模块注册
- display_names.py 追加 10 个 KEY 映射

文档:PRD_需求文档_P3级移植.md + PRD_计划执行_P3级移植.md
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 dc42cffe
# 需求文档:P3 级检测模块移植(文件权限检测 + 定时任务预期检测)
## 文档信息
- 创建时间:2026-07-22
- 需求来源:用户需求
- 关联模块:service_monitor
- 父需求:`PRD_需求文档_检测项丰富化移植.md`(阶段四)
---
## 1. 背景与目标
### 1.1 背景
服务监测模块已完成阶段一(补全缺失项 + 重构 depth 脚本)、阶段二(安全合规深度 + 中间件连接)和阶段三(P2 级移植:系统类型识别 + 平台服务检测 + 配置IP检测 + Console配置检测)。
### 1.2 目标
移植参考目录中适合巡检架构的 2 个检测模块:
1. **文件权限检测**:检查关键文件/目录的权限是否正确(run.sh 应可执行、配置文件不应过宽),只检测不修复
2. **定时任务预期检测**:检查预期 crontab 条目是否存在(MySQL备份、ujava2守护),只检测不修改
### 1.3 不纳入本次的 P3 模块
| 模块 | 原因 |
|------|------|
| FastDFS 功能验证 | 逻辑复杂(上传/下载/MD5/HTTP),需额外评估 |
| Android 设备检测 | 依赖 Windows adb.exe,在 Linux 巡检目标上无法运行 |
| 数据备份检测 | 属于运维操作(远程拉取+打包下载),非巡检 |
| 配置日志导出 | 同上,属于运维操作 |
| 修复能力检测 | 交互式修复工具,不是检测模块 |
---
## 2. 模块一:文件权限检测(54_file_permission_check.sh)
### 2.1 检测项列表
| KEY | 中文名 | 说明 |
|-----|--------|------|
| `FILE_PERM_STATUS` | 文件权限检测状态 | OK/WARN |
| `FILE_PERM_STATUS_LEVEL` | 文件权限检测状态等级 | 正常/警告 |
| `FILE_PERM_TOTAL_CHECKS` | 检查文件数 | 整数 |
| `FILE_PERM_WRONG_COUNT` | 权限异常数 | 整数 |
| `FILE_PERM_WRONG_LIST` | 权限异常明细 | JSON 数组字符串 |
### 2.2 文件权限检查清单
#### 新平台(new)
| 路径 | 期望权限 | 说明 |
|------|---------|------|
| `/data/services/api/start.sh` | 可执行(755) | API 总启动脚本 |
| `/data/services/api/auth/*/run.sh` | 可执行(755) | SSO 服务启动脚本 |
| `/data/services/api/java-meeting/*/run.sh` | 可执行(755) | 会议服务启动脚本 |
| `/data/services/api/python-cmdb/*.sh` | 可执行(755) | 运维后端脚本 |
| `/data/services/api/python-voice/*.sh` | 可执行(755) | 语音服务脚本 |
| `/data/services/scripts/*.sh` | 可执行(755) | 服务脚本目录 |
| `/data/third_party/paperless/run.sh` | 可执行(755) | 无纸化启动脚本 |
| `/data/third_party/paperless/start.sh` | 可执行(755) | 无纸化启动脚本 |
| `/data/third_party/wifi-local/startDB.sh` | 可执行(755) | WiFi 数据库启动脚本 |
| `/etc/rc.d/rc.local` | 可执行(755) | 开机自启脚本 |
| `/data/middleware/redis/redis.conf` | 644 | Redis 配置文件 |
| `/data/middleware/mysql/conf/my.cnf` | 644 | MySQL 配置文件 |
#### 传统平台(old)
| 路径 | 期望权限 | 说明 |
|------|---------|------|
| `/var/www/java/start.sh` | 可执行(755) | Java 启动脚本(meeting) |
| `/var/www/java/unifiedPlatform/start.sh` | 可执行(755) | 统一平台启动脚本(unified) |
| `/var/www/java/*/run.sh` | 可执行(755) | 服务启动脚本 |
| `/var/www/html/start.sh` | 可执行(755) | upython 启动脚本 |
| `/var/www/paperless/run.sh` | 可执行(755) | 无纸化启动脚本 |
| `/var/www/paperless/start.sh` | 可执行(755) | 无纸化启动脚本 |
| `/var/www/wifi-local/startDB.sh` | 可执行(755) | WiFi 数据库启动脚本 |
| `/etc/rc.d/rc.local` | 可执行(755) | 开机自启脚本 |
| `/var/www/redis/redis-*.conf` | 644 | Redis 配置文件 |
| `/usr/local/docker/mysql/my.cnf` | 644 | MySQL 配置文件 |
### 2.3 检测逻辑
```bash
# 对每个目标路径检查权限
for path in "${CHECK_PATHS[@]}"; do
# 通配符展开
for file in $path; do
[ ! -e "$file" ] && continue
local perm
perm=$(stat -c '%a' "$file" 2>/dev/null)
# .sh 文件期望可执行(含 x 权限)
if [[ "$file" == *.sh || "$file" == *rc.local ]]; then
if [ $((perm & 0111)) -eq 0 ]; then
# 不可执行
wrong_list+=(...)
fi
# 配置文件期望 644(不可执行)
else
if [ $((perm & 0111)) -ne 0 ]; then
# 可执行(过宽)
wrong_list+=(...)
fi
fi
done
done
```
### 2.4 依赖
- 命令:`stat``find`
- 无需凭据
- 需要先运行 `50_system_profile.sh` 获取平台类型
---
## 3. 模块二:定时任务预期检测(55_cron_expected.sh)
### 3.1 检测项列表
| KEY | 中文名 | 说明 |
|-----|--------|------|
| `CRON_EXPECTED_STATUS` | 定时任务预期检测状态 | OK/WARN |
| `CRON_EXPECTED_STATUS_LEVEL` | 定时任务预期检测状态等级 | 正常/警告 |
| `CRON_EXPECTED_TOTAL` | 预期任务数 | 整数 |
| `CRON_EXPECTED_MISS_COUNT` | 缺失任务数 | 整数 |
| `CRON_EXPECTED_MISS_LIST` | 缺失任务列表 | JSON 数组字符串 |
### 3.2 预期 crontab 条目
| 条目 | 适用平台 | 说明 |
|------|---------|------|
| `bash /usr/local/docker/UbainsmysqlBakUp.sh` | 通用 | MySQL 每日自动备份 |
| `/data/services/scripts/ujava2-startup.sh` | 新平台 | ujava2 每3分钟守护启动 |
### 3.3 检测逻辑
```bash
# 获取当前 crontab
current_crontab=$(crontab -l 2>/dev/null)
# 逐条检查预期任务是否存在
for expected in "${EXPECTED_ENTRIES[@]}"; do
if ! echo "$current_crontab" | grep -qF "$expected"; then
miss_list+=("$expected")
fi
done
```
### 3.4 依赖
- 命令:`crontab``grep`
- 无需凭据
- 需要先运行 `50_system_profile.sh` 获取平台类型
- 只检测不修改
---
## 4. 技术方案
### 4.1 模块注册
```python
CheckModule("54_file_permission_check", "文件权限检测", "system",
{SUITE_FULL}, "54_file_permission_check.sh"),
CheckModule("55_cron_expected", "定时任务预期检测", "system",
{SUITE_FULL}, "55_cron_expected.sh"),
```
### 4.2 显示名映射
新增约 12 个 KEY 映射:
- 文件权限检测:5 个
- 定时任务预期检测:5 个
### 4.3 配置模板
`config.sh.template` 新增:
- 预期 crontab 条目列表
---
## 5. 验收标准
| 标准 | 验证方法 |
|------|---------|
| 2 个检测脚本创建 | 语法检查 `bash -n` 通过 |
| check_modules.py 注册 | `get_suite('full')` 包含新模块 |
| display_names.py 映射 | 新 KEY 有中文名 |
| 单元测试通过 | pytest 全绿 |
| 已有测试不受影响 | 218 个测试全绿 |
---
## 6. 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 部分路径不存在 | 检测项跳过 | 存在性检查,graceful 降级 |
| stat 命令参数差异 | 权限获取失败 | 兼容 GNU 和 BSD stat |
| crontab 为空 | 全部标记缺失 | 区分"无 crontab"和"缺失条目" |
#!/bin/bash
################################################################################
# 54_file_permission_check.sh — 文件权限检测
# 功能: 检查关键文件/目录的权限是否正确
# 参考: FilePermission.psm1
# 日期: 2026-07-22
# 说明: 只检测不修复,发现权限异常标记为警告
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== 平台类型识别 ====================
detect_platform_type() {
[ -d "/data/services" ] && echo "new" || echo "old"
}
# ==================== 权限检查逻辑 ====================
# 检查单个文件权限
# 返回: 0=正常, 1=异常
check_file_permission() {
local file="$1"
local expect_exec="$2" # "yes" or "no"
[ ! -e "$file" ] && return 0 # 文件不存在,跳过(不计入异常)
local perm
# 兼容 GNU stat 和 BSD stat
perm=$(stat -c '%a' "$file" 2>/dev/null || stat -f '%Lp' "$file" 2>/dev/null)
if [ -z "$perm" ]; then
return 0 # 无法获取权限,跳过
fi
local is_exec=0
[ $((perm & 0111)) -ne 0 ] && is_exec=1
if [ "$expect_exec" = "yes" ] && [ $is_exec -eq 0 ]; then
# 应可执行但不可执行
return 1
elif [ "$expect_exec" = "no" ] && [ $is_exec -ne 0 ]; then
# 应不可执行但可执行
return 1
fi
return 0
}
# ==================== 主检测流程 ====================
main() {
log_info "开始文件权限检测..."
set +e
local platform_type
platform_type=$(detect_platform_type)
local total_checks=0
local wrong_count=0
local wrong_list=""
# 定义检查路径(格式: path:expect_exec)
# yes=应可执行, no=应不可执行
local -a check_paths
if [ "$platform_type" = "new" ]; then
check_paths=(
"/data/services/api/start.sh:yes"
"/data/services/api/auth/auth-sso-auth/run.sh:yes"
"/data/services/api/auth/auth-sso-gateway/run.sh:yes"
"/data/services/api/auth/auth-sso-system/run.sh:yes"
"/data/services/api/java-meeting/java-meeting2.0/run.sh:yes"
"/data/services/api/java-meeting/java-meeting-extapi/run.sh:yes"
"/data/services/api/java-meeting/java-message-scheduling/run.sh:yes"
"/data/services/api/java-meeting/java-mqtt/run.sh:yes"
"/data/services/api/java-meeting/java-quartz/run.sh:yes"
"/data/third_party/paperless/run.sh:yes"
"/data/third_party/paperless/start.sh:yes"
"/etc/rc.d/rc.local:yes"
)
else
check_paths=(
"/var/www/java/start.sh:yes"
"/var/www/java/api-java-meeting2.0/run.sh:yes"
"/var/www/java/external-meeting-api/run.sh:yes"
"/var/www/html/start.sh:yes"
"/var/www/paperless/run.sh:yes"
"/var/www/paperless/start.sh:yes"
"/etc/rc.d/rc.local:yes"
)
fi
for entry in "${check_paths[@]}"; do
local path expect_exec
path="${entry%:*}"
expect_exec="${entry#*:}"
total_checks=$((total_checks + 1))
if ! check_file_permission "$path" "$expect_exec"; then
wrong_count=$((wrong_count + 1))
local escaped_path
escaped_path=$(echo "$path" | sed 's/"/\\"/g')
[ -n "$wrong_list" ] && wrong_list="${wrong_list},"
wrong_list="${wrong_list}{\"path\":\"${escaped_path}\",\"expected_exec\":\"${expect_exec}\"}"
fi
done
# 输出结果
if [ "$wrong_count" -eq 0 ]; then
output_result "FILE_PERM_STATUS" "OK"
output_result "FILE_PERM_STATUS_LEVEL" "正常"
else
output_result "FILE_PERM_STATUS" "WARN"
output_result "FILE_PERM_STATUS_LEVEL" "警告"
fi
output_result "FILE_PERM_TOTAL_CHECKS" "$total_checks"
output_result "FILE_PERM_WRONG_COUNT" "$wrong_count"
if [ -n "$wrong_list" ]; then
output_result "FILE_PERM_WRONG_LIST" "[${wrong_list}]"
else
output_result "FILE_PERM_WRONG_LIST" "[]"
fi
log_info "文件权限检测完成"
}
main
\ No newline at end of file
#!/bin/bash
################################################################################
# 55_cron_expected.sh — 定时任务预期检测
# 功能: 检查预期 crontab 条目是否存在
# 参考: check_server_health.sh (test_scheduled_tasks)
# 日期: 2026-07-22
# 说明: 只检测不修改,发现缺失标记为警告
################################################################################
# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"
# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
source "$LIB_DIR/lib/config.sh"
else
echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
exit 1
fi
if [ -f "$LIB_DIR/lib/common.sh" ]; then
source "$LIB_DIR/lib/common.sh"
else
echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
exit 1
fi
# ==================== 平台类型识别 ====================
detect_platform_type() {
[ -d "/data/services" ] && echo "new" || echo "old"
}
# ==================== 主检测流程 ====================
main() {
log_info "开始定时任务预期检测..."
set +e
local platform_type
platform_type=$(detect_platform_type)
# 预期 crontab 条目(匹配命令部分,不含时间)
local -a expected_entries
expected_entries=(
"bash /usr/local/docker/UbainsmysqlBakUp.sh"
)
# 新平台额外期望 ujava2 守护脚本
if [ "$platform_type" = "new" ]; then
expected_entries+=("/data/services/scripts/ujava2-startup.sh")
fi
# 获取当前 crontab
local current_crontab
current_crontab=$(crontab -l 2>/dev/null || echo "")
local total_expected=${#expected_entries[@]}
local miss_count=0
local miss_list=""
for entry in "${expected_entries[@]}"; do
if ! echo "$current_crontab" | grep -qF "$entry"; then
miss_count=$((miss_count + 1))
local escaped_entry
escaped_entry=$(echo "$entry" | sed 's/"/\\"/g')
[ -n "$miss_list" ] && miss_list="${miss_list},"
miss_list="${miss_list}\"${escaped_entry}\""
fi
done
# 输出结果
if [ "$miss_count" -eq 0 ]; then
output_result "CRON_EXPECTED_STATUS" "OK"
output_result "CRON_EXPECTED_STATUS_LEVEL" "正常"
else
output_result "CRON_EXPECTED_STATUS" "WARN"
output_result "CRON_EXPECTED_STATUS_LEVEL" "警告"
fi
output_result "CRON_EXPECTED_TOTAL" "$total_expected"
output_result "CRON_EXPECTED_MISS_COUNT" "$miss_count"
if [ -n "$miss_list" ]; then
output_result "CRON_EXPECTED_MISS_LIST" "[${miss_list}]"
else
output_result "CRON_EXPECTED_MISS_LIST" "[]"
fi
log_info "定时任务预期检测完成"
}
main
\ No newline at end of file
......@@ -87,6 +87,10 @@ ALL_MODULES: List[CheckModule] = [
{SUITE_FULL}, "52_config_ip_check.sh"),
CheckModule("53_config_console_check", "Console配置检测", "system",
{SUITE_FULL}, "53_config_console_check.sh"),
CheckModule("54_file_permission_check", "文件权限检测", "system",
{SUITE_FULL}, "54_file_permission_check.sh"),
CheckModule("55_cron_expected", "定时任务预期检测", "system",
{SUITE_FULL}, "55_cron_expected.sh"),
# ---- service 类 ----
CheckModule("20_docker_basic", "Docker基础", "service",
{SUITE_QUICK, SUITE_FULL}, "20_docker_basic.sh"),
......
......@@ -757,4 +757,18 @@ DISPLAY_NAMES: Dict[str, str] = {
"CFG_CONSOLE_TOTAL_FILES": "扫描文件数",
"CFG_CONSOLE_FOUND_COUNT": "发现问题数",
"CFG_CONSOLE_FOUND_FILES": "问题文件列表",
# ---- 54 文件权限检测 ----
"FILE_PERM_STATUS": "文件权限检测状态",
"FILE_PERM_STATUS_LEVEL": "文件权限检测状态等级",
"FILE_PERM_TOTAL_CHECKS": "检查文件数",
"FILE_PERM_WRONG_COUNT": "权限异常数",
"FILE_PERM_WRONG_LIST": "权限异常明细",
# ---- 55 定时任务预期检测 ----
"CRON_EXPECTED_STATUS": "定时任务预期检测状态",
"CRON_EXPECTED_STATUS_LEVEL": "定时任务预期检测状态等级",
"CRON_EXPECTED_TOTAL": "预期任务数",
"CRON_EXPECTED_MISS_COUNT": "缺失任务数",
"CRON_EXPECTED_MISS_LIST": "缺失任务列表",
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论