create-dwy 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/bin/index.js +5 -3
- package/package.json +3 -2
- package/src/searchable-select.js +419 -0
- package/src/skills-install.js +48 -7
- package/src/sync-all.js +17 -25
- package/src/sync.js +38 -32
- package/templates/ai-tools/CHANGELOG.md +76 -0
- package/templates/ai-tools/CLAUDE.md +27 -0
- package/templates/ai-tools/VERSION +1 -0
- package/templates/ai-tools/hook-manifests/hooks.json +7 -0
- package/templates/ai-tools/hooks/Git/pre-git-commit-ai-signature-check.sh +57 -0
- package/templates/ai-tools/rules/Android/dwy-android-core.md +215 -4
- package/templates/ai-tools/rules/Android/dwy-android-pitfalls.md +52 -0
- package/templates/ai-tools/rules/Docker/dwy-docker.md +52 -1
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +50 -1
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-design.md +139 -0
- package/templates/ai-tools/rules/HarmonyOS/dwy-harmony-pitfalls.md +72 -0
- package/templates/ai-tools/rules/Python/dwy-python-backend.md +16 -63
- package/templates/ai-tools/rules/Python/dwy-python-base-lib.md +89 -0
- package/templates/ai-tools/rules/Python/dwy-python-pitfalls.md +49 -0
- package/templates/ai-tools/rules/Vue/dwy-vue-base-lib.md +127 -0
- package/templates/ai-tools/rules/Vue/dwy-vue-core.md +22 -86
- package/templates/ai-tools/rules/Vue/dwy-vue-pitfalls.md +66 -0
- package/templates/ai-tools/rules/iOS/dwy-ios-pitfalls.md +7 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dependency-freshness.md +59 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit.md +9 -6
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md +47 -2
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/SKILL.md +511 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-data.md +126 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-network.md +88 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/checks-runtime.md +195 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/references/security-doc-search.md +179 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_base.sh +39 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_capacity.sh +278 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_db.sh +147 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_docker.sh +230 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_env.sh +64 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_https.sh +60 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_logs.sh +209 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_nginx.sh +100 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_resilience.sh +157 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_secrets.sh +265 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_services.sh +94 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/check_ssh.sh +56 -0
- package/templates/ai-tools/skills/unitcircle/uc-deploy-audit/scripts/run_all.sh +105 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-acceptance/SKILL.md +3 -2
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +51 -9
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-commercial/SKILL.md +58 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-competitor/SKILL.md +2 -1
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-explore/SKILL.md +83 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-mvp/SKILL.md +2 -1
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prd/SKILL.md +1 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/SKILL.md +14 -9
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/references/state-and-contract.md +65 -13
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prototype/SKILL.md +30 -11
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-release/SKILL.md +3 -2
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-design/SKILL.md +17 -10
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-dev/SKILL.md +42 -10
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-launch/SKILL.md +22 -10
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-prd/SKILL.md +13 -4
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-ship/SKILL.md +13 -4
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-tasks/SKILL.md +12 -2
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-tdd-dev/SKILL.md +15 -6
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-validate/SKILL.md +2 -1
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-version/SKILL.md +1 -0
- package/templates/ai-tools/skills//345/205/203/345/267/245/345/205/267/dwy-shared/SKILL.md +22 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/references/checks-runtime.md +2 -2
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_docker.sh +3 -3
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_secrets.sh +1 -1
- package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-first/SKILL.md +176 -0
- package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-first/references/chapter-01-mirrors.md +112 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-ideate/SKILL.md +0 -21
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-0to1/SKILL.md +0 -113
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-0to1/templates/STATE.md +0 -56
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dev-qa/SKILL.md +0 -117
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-market-analysis/SKILL.md +0 -92
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-coarse/SKILL.md +0 -72
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-coarse/templates/prd-coarse.md +0 -61
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-detailed/SKILL.md +0 -76
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prd-detailed/templates/prd-detailed.md +0 -119
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prototype/SKILL.md +0 -104
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-spike/SKILL.md +0 -81
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-dev/SKILL.md +0 -212
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-dev/templates/impl-plan.md +0 -115
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tech-feasibility/SKILL.md +0 -105
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-version-plan/SKILL.md +0 -64
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-version-plan/templates/version-plan.md +0 -72
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 环境变量与文件权限检查
|
|
3
|
+
TARGET="$1"; shift
|
|
4
|
+
SSH_OPTS=("$@")
|
|
5
|
+
|
|
6
|
+
ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
|
|
7
|
+
echo "--- 全局搜索 .env 文件位置 (限制深度,跳过 /proc /sys) ---"
|
|
8
|
+
sudo -n find /home /opt /srv /var/www /root /etc -maxdepth 6 -type f \
|
|
9
|
+
\( -name ".env" -o -name ".env.*" -o -name "*.env" \) \
|
|
10
|
+
! -name "*.example" ! -name "*.sample" ! -name "*.template" \
|
|
11
|
+
-printf "%M %u:%g %p (%s bytes)\n" 2>/dev/null \
|
|
12
|
+
|| find /home /opt /srv /var/www -maxdepth 6 -type f \
|
|
13
|
+
\( -name ".env" -o -name ".env.*" \) \
|
|
14
|
+
! -name "*.example" ! -name "*.sample" \
|
|
15
|
+
-printf "%M %u:%g %p (%s bytes)\n" 2>/dev/null \
|
|
16
|
+
|| echo "[!] 无 sudo,部分目录无法扫描"
|
|
17
|
+
|
|
18
|
+
echo ""
|
|
19
|
+
echo "--- nginx 静态根目录是否包含敏感文件 ---"
|
|
20
|
+
NGINX_DUMP=$(sudo -n nginx -T 2>/dev/null || nginx -T 2>/dev/null || true)
|
|
21
|
+
if [[ -n "$NGINX_DUMP" ]]; then
|
|
22
|
+
ROOTS=$(echo "$NGINX_DUMP" | awk '/^[[:space:]]*root /{print $2}' | tr -d ';' | sort -u)
|
|
23
|
+
for r in $ROOTS; do
|
|
24
|
+
echo ">> root = $r"
|
|
25
|
+
[[ -d "$r" ]] || { echo " [!] 目录不存在或不可读"; continue; }
|
|
26
|
+
for s in ".env" ".git" ".DS_Store" "package.json" "composer.json" "wp-config.php"; do
|
|
27
|
+
if [[ -e "$r/$s" ]]; then
|
|
28
|
+
echo " [!] 暴露: $r/$s"
|
|
29
|
+
fi
|
|
30
|
+
done
|
|
31
|
+
done
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
echo ""
|
|
35
|
+
echo "--- 关键文件权限 ---"
|
|
36
|
+
for f in /etc/shadow /etc/passwd /etc/sudoers /etc/ssh/sshd_config /etc/nginx/nginx.conf; do
|
|
37
|
+
ls -l "$f" 2>/dev/null || echo " [i] $f 不可读"
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "--- 应用进程运行用户 (期望非 root) ---"
|
|
42
|
+
ps -eo user,pid,cmd --sort=user 2>/dev/null | grep -E "(node|python|uvicorn|gunicorn|java|php-fpm|fastapi|uwsgi)" | grep -v grep | head -20
|
|
43
|
+
|
|
44
|
+
echo ""
|
|
45
|
+
echo "--- sudo 免密配置 ---"
|
|
46
|
+
sudo -n cat /etc/sudoers 2>/dev/null | grep -v "^#" | grep -v "^$" | head -20 || echo "[i] 无 sudo 权限读取 /etc/sudoers"
|
|
47
|
+
sudo -n ls /etc/sudoers.d/ 2>/dev/null | head -10
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "--- 全局环境变量泄漏排查 (是否有密码/key 写在 /etc/environment 或 /etc/profile) ---"
|
|
51
|
+
for f in /etc/environment /etc/profile /etc/bash.bashrc; do
|
|
52
|
+
if [[ -r "$f" ]]; then
|
|
53
|
+
grep -iE "password|secret|token|api_key|apikey" "$f" 2>/dev/null \
|
|
54
|
+
| sed -E 's/(password|secret|token|api[_-]?key)([^=]*=)([^[:space:]]+)/\1\2<REDACTED>/gi' \
|
|
55
|
+
|| true
|
|
56
|
+
fi
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
echo ""
|
|
60
|
+
echo "--- crontab 中是否有可疑命令 ---"
|
|
61
|
+
sudo -n crontab -l 2>/dev/null | head -20 || crontab -l 2>/dev/null | head -20
|
|
62
|
+
echo "--- /etc/cron.d/ /etc/cron.daily/ ---"
|
|
63
|
+
ls /etc/cron.d/ /etc/cron.daily/ 2>/dev/null | head -20
|
|
64
|
+
REMOTE
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# HTTPS / SSL 证书检查
|
|
3
|
+
TARGET="$1"; shift
|
|
4
|
+
SSH_OPTS=("$@")
|
|
5
|
+
|
|
6
|
+
ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
|
|
7
|
+
echo "--- 检测可能的 HTTPS 站点 (从 nginx 配置) ---"
|
|
8
|
+
DOMAINS=()
|
|
9
|
+
NGINX_DUMP=$(sudo -n nginx -T 2>/dev/null || nginx -T 2>/dev/null || true)
|
|
10
|
+
if [[ -n "$NGINX_DUMP" ]]; then
|
|
11
|
+
while IFS= read -r d; do
|
|
12
|
+
[[ -n "$d" && "$d" != "_" && "$d" != "localhost" ]] && DOMAINS+=("$d")
|
|
13
|
+
done < <(echo "$NGINX_DUMP" | awk '/server_name/ {for(i=2;i<=NF;i++) print $i}' | tr -d ';' | sort -u)
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [[ ${#DOMAINS[@]} -eq 0 ]]; then
|
|
17
|
+
DOMAINS+=("localhost")
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
echo "目标域名: ${DOMAINS[*]}"
|
|
21
|
+
|
|
22
|
+
for D in "${DOMAINS[@]}"; do
|
|
23
|
+
echo ""
|
|
24
|
+
echo "===== $D ====="
|
|
25
|
+
|
|
26
|
+
echo "--- HTTPS 可达性 ---"
|
|
27
|
+
HTTPS_CODE=$(curl -k -sS -o /dev/null -w "%{http_code}" --max-time 5 "https://$D" 2>/dev/null || echo "000")
|
|
28
|
+
HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code} -> %{redirect_url}" --max-time 5 "http://$D" 2>/dev/null || echo "000")
|
|
29
|
+
echo " https://$D -> HTTP $HTTPS_CODE"
|
|
30
|
+
echo " http://$D -> HTTP $HTTP_CODE"
|
|
31
|
+
|
|
32
|
+
echo "--- 证书信息 ---"
|
|
33
|
+
CERT=$(echo | timeout 5 openssl s_client -servername "$D" -connect "${D}:443" -showcerts 2>/dev/null </dev/null)
|
|
34
|
+
if [[ -z "$CERT" ]]; then
|
|
35
|
+
echo " [!] 无法获取证书 (可能未启用 HTTPS 或域名无 DNS)"
|
|
36
|
+
continue
|
|
37
|
+
fi
|
|
38
|
+
echo "$CERT" | openssl x509 -noout -subject -issuer -dates 2>/dev/null
|
|
39
|
+
|
|
40
|
+
echo "--- SAN ---"
|
|
41
|
+
echo "$CERT" | openssl x509 -noout -ext subjectAltName 2>/dev/null | grep -i "DNS:" | head -3
|
|
42
|
+
|
|
43
|
+
echo "--- 剩余天数 ---"
|
|
44
|
+
END=$(echo "$CERT" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
|
45
|
+
if [[ -n "$END" ]]; then
|
|
46
|
+
END_TS=$(date -d "$END" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$END" +%s 2>/dev/null)
|
|
47
|
+
NOW_TS=$(date +%s)
|
|
48
|
+
if [[ -n "$END_TS" ]]; then
|
|
49
|
+
DAYS=$(( (END_TS - NOW_TS) / 86400 ))
|
|
50
|
+
echo " 剩余 $DAYS 天 (过期时间: $END)"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
echo "--- TLS 协议支持 ---"
|
|
55
|
+
for proto in tls1 tls1_1 tls1_2 tls1_3; do
|
|
56
|
+
R=$(echo | timeout 3 openssl s_client -"$proto" -connect "${D}:443" 2>&1 </dev/null | grep -E "Protocol|handshake failure|wrong version" | head -1)
|
|
57
|
+
printf " %-8s %s\n" "$proto" "$R"
|
|
58
|
+
done
|
|
59
|
+
done
|
|
60
|
+
REMOTE
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 日志大小与防爆检查 (只读)
|
|
3
|
+
#
|
|
4
|
+
# 检查范围:
|
|
5
|
+
# - Docker 单容器 json-log 大小
|
|
6
|
+
# - Docker daemon log-opts 是否生效到具体容器
|
|
7
|
+
# - Nginx access/error log 大小 + logrotate 覆盖
|
|
8
|
+
# - systemd journal 占用 + SystemMaxUse 配置
|
|
9
|
+
# - 应用日志目录 (自动探测 /var/log/<svc>、/opt/*/logs、/home/*/logs)
|
|
10
|
+
# - 总日志占用 vs 根盘剩余, 估算撑天数
|
|
11
|
+
|
|
12
|
+
TARGET="$1"; shift
|
|
13
|
+
SSH_OPTS=("$@")
|
|
14
|
+
|
|
15
|
+
ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
|
|
16
|
+
ROOT_FREE_MB=$(df -BM --output=avail / 2>/dev/null | tail -1 | tr -d ' M')
|
|
17
|
+
ROOT_FREE_MB=${ROOT_FREE_MB:-0}
|
|
18
|
+
NOW_TS=$(date +%s)
|
|
19
|
+
|
|
20
|
+
echo "--- 根盘可用 ---"
|
|
21
|
+
df -h / 2>/dev/null
|
|
22
|
+
echo "可用: ${ROOT_FREE_MB} MB"
|
|
23
|
+
|
|
24
|
+
# ============================================================
|
|
25
|
+
echo ""
|
|
26
|
+
echo "--- Docker 容器 json-log 大小 (单文件 Top 20) ---"
|
|
27
|
+
if command -v docker >/dev/null 2>&1; then
|
|
28
|
+
# 收集 daemon log-opts 用于关联判定
|
|
29
|
+
DAEMON_JSON=$(sudo -n cat /etc/docker/daemon.json 2>/dev/null \
|
|
30
|
+
|| cat /etc/docker/daemon.json 2>/dev/null || echo "")
|
|
31
|
+
DAEMON_MAX_SIZE=$(echo "$DAEMON_JSON" \
|
|
32
|
+
| grep -oE '"max-size"[[:space:]]*:[[:space:]]*"[^"]+"' \
|
|
33
|
+
| head -1 | grep -oE '"[^"]+"$' | tr -d '"')
|
|
34
|
+
echo "daemon.json log-opts max-size: ${DAEMON_MAX_SIZE:-(未配置)}"
|
|
35
|
+
echo ""
|
|
36
|
+
|
|
37
|
+
# 列出所有 json-log: <size> <path>
|
|
38
|
+
LOG_LIST=$(sudo -n find /var/lib/docker/containers -maxdepth 2 -name "*-json.log" \
|
|
39
|
+
-printf '%s %p\n' 2>/dev/null \
|
|
40
|
+
|| find /var/lib/docker/containers -maxdepth 2 -name "*-json.log" \
|
|
41
|
+
-printf '%s %p\n' 2>/dev/null)
|
|
42
|
+
|
|
43
|
+
if [[ -z "$LOG_LIST" ]]; then
|
|
44
|
+
echo "[i] 无法读取 /var/lib/docker/containers (需 sudo) 或无 json-log"
|
|
45
|
+
else
|
|
46
|
+
DOCKER_LOG_TOTAL_BYTES=0
|
|
47
|
+
# 排序+top 20, 反查容器名
|
|
48
|
+
while IFS=' ' read -r size path; do
|
|
49
|
+
[[ -z "$size" ]] && continue
|
|
50
|
+
DOCKER_LOG_TOTAL_BYTES=$(( DOCKER_LOG_TOTAL_BYTES + size ))
|
|
51
|
+
cid=$(basename "$(dirname "$path")")
|
|
52
|
+
cid_short="${cid:0:12}"
|
|
53
|
+
name=$(docker inspect -f '{{.Name}}' "$cid" 2>/dev/null | sed 's|^/||')
|
|
54
|
+
[[ -z "$name" ]] && name="<已删除>"
|
|
55
|
+
size_mb=$(( size / 1024 / 1024 ))
|
|
56
|
+
printf " %-32s %6d MB %s\n" "$name" "$size_mb" "$path"
|
|
57
|
+
|
|
58
|
+
# 单文件大小告警
|
|
59
|
+
if [[ "$size_mb" -ge 1024 ]] && [[ -z "$DAEMON_MAX_SIZE" ]]; then
|
|
60
|
+
echo " [!!!] CRITICAL: 单容器日志 ${size_mb} MB 且 daemon 无 log-opts 大小限制"
|
|
61
|
+
elif [[ "$size_mb" -ge 500 ]]; then
|
|
62
|
+
echo " [!!] HIGH: 单容器日志 ${size_mb} MB"
|
|
63
|
+
fi
|
|
64
|
+
done < <(echo "$LOG_LIST" | sort -rn | head -20)
|
|
65
|
+
|
|
66
|
+
DOCKER_LOG_TOTAL_MB=$(( DOCKER_LOG_TOTAL_BYTES / 1024 / 1024 ))
|
|
67
|
+
echo ""
|
|
68
|
+
echo "Docker json-log 合计: ${DOCKER_LOG_TOTAL_MB} MB"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo ""
|
|
72
|
+
echo "--- 各容器是否覆盖 logging driver (compose level 优先于 daemon level) ---"
|
|
73
|
+
docker ps -q 2>/dev/null | while read c; do
|
|
74
|
+
name=$(docker inspect -f '{{.Name}}' "$c" | sed 's|^/||')
|
|
75
|
+
lt=$(docker inspect -f '{{.HostConfig.LogConfig.Type}}' "$c" 2>/dev/null)
|
|
76
|
+
lc=$(docker inspect -f '{{json .HostConfig.LogConfig.Config}}' "$c" 2>/dev/null)
|
|
77
|
+
[[ -z "$lc" || "$lc" == "null" ]] && lc='{}'
|
|
78
|
+
printf " %-32s driver=%-12s opts=%s\n" "$name" "${lt:-?}" "$lc"
|
|
79
|
+
if [[ "$lt" == "json-file" && "$lc" == "{}" && -z "$DAEMON_MAX_SIZE" ]]; then
|
|
80
|
+
echo " [!!] HIGH: 容器无 logging.options 且 daemon 无 max-size, 日志可无限增长"
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
else
|
|
84
|
+
echo "[i] Docker 未安装,跳过容器日志检查"
|
|
85
|
+
DOCKER_LOG_TOTAL_MB=0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# ============================================================
|
|
89
|
+
echo ""
|
|
90
|
+
echo "--- Nginx 日志 ---"
|
|
91
|
+
if [[ -d /var/log/nginx ]]; then
|
|
92
|
+
ls -lh /var/log/nginx/ 2>/dev/null | head -20
|
|
93
|
+
NGINX_LOG_BYTES=$(sudo -n du -sb /var/log/nginx 2>/dev/null | awk '{print $1}' \
|
|
94
|
+
|| du -sb /var/log/nginx 2>/dev/null | awk '{print $1}')
|
|
95
|
+
NGINX_LOG_MB=$(( ${NGINX_LOG_BYTES:-0} / 1024 / 1024 ))
|
|
96
|
+
echo "合计: ${NGINX_LOG_MB} MB"
|
|
97
|
+
|
|
98
|
+
# access.log / error.log 单文件大小
|
|
99
|
+
for f in /var/log/nginx/access.log /var/log/nginx/error.log; do
|
|
100
|
+
[[ -f "$f" ]] || continue
|
|
101
|
+
sz_mb=$(( $(stat -c %s "$f" 2>/dev/null || echo 0) / 1024 / 1024 ))
|
|
102
|
+
if [[ "$sz_mb" -ge 500 ]]; then
|
|
103
|
+
echo "[!!] HIGH: $f 单文件 ${sz_mb} MB"
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
106
|
+
|
|
107
|
+
# logrotate 覆盖
|
|
108
|
+
if [[ -f /etc/logrotate.d/nginx ]]; then
|
|
109
|
+
echo "[OK] /etc/logrotate.d/nginx 存在"
|
|
110
|
+
else
|
|
111
|
+
echo "[!!] HIGH: nginx 已部署但无 /etc/logrotate.d/nginx, 日志可能无限增长"
|
|
112
|
+
fi
|
|
113
|
+
else
|
|
114
|
+
echo "[i] /var/log/nginx 不存在, 跳过"
|
|
115
|
+
NGINX_LOG_MB=0
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# ============================================================
|
|
119
|
+
echo ""
|
|
120
|
+
echo "--- systemd journal 占用 ---"
|
|
121
|
+
if command -v journalctl >/dev/null 2>&1; then
|
|
122
|
+
JOURNAL_USAGE=$(journalctl --disk-usage 2>/dev/null | grep -oE '[0-9.]+[KMG]B?' | tail -1)
|
|
123
|
+
echo "journalctl --disk-usage: ${JOURNAL_USAGE:-?}"
|
|
124
|
+
# 转 MB (粗略)
|
|
125
|
+
case "$JOURNAL_USAGE" in
|
|
126
|
+
*G*) JOURNAL_MB=$(echo "$JOURNAL_USAGE" | sed 's/[GBb]//g' | awk '{print int($1*1024)}') ;;
|
|
127
|
+
*M*) JOURNAL_MB=$(echo "$JOURNAL_USAGE" | sed 's/[MBb]//g' | awk '{print int($1)}') ;;
|
|
128
|
+
*K*) JOURNAL_MB=0 ;;
|
|
129
|
+
*) JOURNAL_MB=0 ;;
|
|
130
|
+
esac
|
|
131
|
+
|
|
132
|
+
# SystemMaxUse 配置
|
|
133
|
+
SMU=$(grep -E "^[[:space:]]*SystemMaxUse" /etc/systemd/journald.conf 2>/dev/null \
|
|
134
|
+
| head -1 | awk -F= '{gsub(/[ \t]/,"",$2); print $2}')
|
|
135
|
+
echo "SystemMaxUse: ${SMU:-(未配置, 默认按磁盘 10% 自动控)}"
|
|
136
|
+
|
|
137
|
+
if [[ "$JOURNAL_MB" -ge 2048 && -z "$SMU" ]]; then
|
|
138
|
+
echo "[!!] HIGH: journal 已 ${JOURNAL_MB} MB 且未配 SystemMaxUse"
|
|
139
|
+
fi
|
|
140
|
+
else
|
|
141
|
+
JOURNAL_MB=0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# ============================================================
|
|
145
|
+
echo ""
|
|
146
|
+
echo "--- 应用日志目录 (自动探测 Top 5) ---"
|
|
147
|
+
APP_LOG_TOTAL_MB=0
|
|
148
|
+
# 优先 sudo, 失败再退化; 不要用 OR fallback (du 退出码可能因部分不可读而非零, 会被双重执行)
|
|
149
|
+
APP_LOG_LIST=$(sudo -n du -smc /var/log/*/ /opt/*/logs /opt/*/log /home/*/logs /srv/*/logs 2>/dev/null \
|
|
150
|
+
| grep -v 'total$' | sort -rn | head -10)
|
|
151
|
+
if [[ -z "$APP_LOG_LIST" ]]; then
|
|
152
|
+
APP_LOG_LIST=$(du -smc /var/log/*/ /opt/*/logs /opt/*/log /home/*/logs /srv/*/logs 2>/dev/null \
|
|
153
|
+
| grep -v 'total$' | sort -rn | head -10)
|
|
154
|
+
fi
|
|
155
|
+
echo "$APP_LOG_LIST"
|
|
156
|
+
APP_LOG_TOTAL_MB=$(echo "$APP_LOG_LIST" | awk '{s+=$1} END{print s+0}')
|
|
157
|
+
|
|
158
|
+
# ============================================================
|
|
159
|
+
echo ""
|
|
160
|
+
echo "--- 日志总占用 vs 根盘剩余 (撑天数粗估) ---"
|
|
161
|
+
TOTAL_LOG_MB=$(( DOCKER_LOG_TOTAL_MB + NGINX_LOG_MB + JOURNAL_MB + APP_LOG_TOTAL_MB ))
|
|
162
|
+
echo "Docker json-log: ${DOCKER_LOG_TOTAL_MB} MB"
|
|
163
|
+
echo "Nginx 日志: ${NGINX_LOG_MB} MB"
|
|
164
|
+
echo "systemd journal: ${JOURNAL_MB} MB"
|
|
165
|
+
echo "应用日志合计: ${APP_LOG_TOTAL_MB} MB"
|
|
166
|
+
echo "日志合计: ${TOTAL_LOG_MB} MB"
|
|
167
|
+
echo "根盘可用: ${ROOT_FREE_MB} MB"
|
|
168
|
+
|
|
169
|
+
# 撑天数估算: 取所有 docker json-log 的 (size / 容器存活天数) 加总作为 daily growth
|
|
170
|
+
GROWTH_MB=0
|
|
171
|
+
if command -v docker >/dev/null 2>&1; then
|
|
172
|
+
for c in $(docker ps -q 2>/dev/null); do
|
|
173
|
+
cid_full=$(docker inspect -f '{{.Id}}' "$c" 2>/dev/null)
|
|
174
|
+
log_path="/var/lib/docker/containers/${cid_full}/${cid_full}-json.log"
|
|
175
|
+
sz=$(sudo -n stat -c %s "$log_path" 2>/dev/null || stat -c %s "$log_path" 2>/dev/null || echo 0)
|
|
176
|
+
started=$(docker inspect -f '{{.State.StartedAt}}' "$c" 2>/dev/null)
|
|
177
|
+
started_ts=$(date -d "$started" +%s 2>/dev/null || echo 0)
|
|
178
|
+
if [[ "$started_ts" -gt 0 && "$sz" -gt 0 ]]; then
|
|
179
|
+
age_sec=$(( NOW_TS - started_ts ))
|
|
180
|
+
[[ "$age_sec" -lt 60 ]] && age_sec=60 # 防除零, 至少按 1 分钟算
|
|
181
|
+
daily_bytes=$(( sz * 86400 / age_sec ))
|
|
182
|
+
daily_mb=$(( daily_bytes / 1024 / 1024 ))
|
|
183
|
+
GROWTH_MB=$(( GROWTH_MB + daily_mb ))
|
|
184
|
+
fi
|
|
185
|
+
done
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
echo "估算 Docker 日志日均增量: ${GROWTH_MB} MB/day (基于容器存活时长)"
|
|
189
|
+
|
|
190
|
+
if [[ "$GROWTH_MB" -gt 0 && "$ROOT_FREE_MB" -gt 0 ]]; then
|
|
191
|
+
DAYS=$(( ROOT_FREE_MB / GROWTH_MB ))
|
|
192
|
+
echo "按当前增速预计可撑: ${DAYS} 天 (粗估, 仅算 docker json-log)"
|
|
193
|
+
if [[ "$DAYS" -lt 30 ]]; then echo "[!!!] CRITICAL: 不足 30 天, 必须立即配 log-opts 或扩盘"
|
|
194
|
+
elif [[ "$DAYS" -lt 90 ]]; then echo "[!!] HIGH: 不足 90 天, 建议尽快补 log-opts max-size"
|
|
195
|
+
else echo "[OK] 撑天数 >= 90 天"
|
|
196
|
+
fi
|
|
197
|
+
else
|
|
198
|
+
echo "[i] 无法估算日均增量 (容器刚起或无日志)"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# ============================================================
|
|
202
|
+
echo ""
|
|
203
|
+
echo "--- 关键服务 logrotate 规则一览 (只读复述) ---"
|
|
204
|
+
for app in nginx postgresql redis docker rsyslog; do
|
|
205
|
+
if [[ -f "/etc/logrotate.d/$app" ]]; then
|
|
206
|
+
echo " [OK] /etc/logrotate.d/$app"
|
|
207
|
+
fi
|
|
208
|
+
done
|
|
209
|
+
REMOTE
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Nginx 配置检查 (只读)
|
|
3
|
+
TARGET="$1"; shift
|
|
4
|
+
SSH_OPTS=("$@")
|
|
5
|
+
|
|
6
|
+
ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
|
|
7
|
+
if ! command -v nginx >/dev/null 2>&1; then
|
|
8
|
+
echo "[i] nginx 未安装,跳过"
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
echo "--- nginx version ---"
|
|
13
|
+
nginx -v 2>&1
|
|
14
|
+
NGINX_VERSION=$(nginx -v 2>&1 | sed -n 's#.*nginx/\([^ ]*\).*#\1#p' | head -1)
|
|
15
|
+
echo "nginx_version_normalized=${NGINX_VERSION:-unknown}"
|
|
16
|
+
echo "official_doc_headers=https://nginx.org/en/docs/http/ngx_http_headers_module.html"
|
|
17
|
+
echo "official_doc_ssl=https://nginx.org/en/docs/http/ngx_http_ssl_module.html"
|
|
18
|
+
if [[ -n "$NGINX_VERSION" ]]; then
|
|
19
|
+
echo "search_hint_1=nginx ${NGINX_VERSION} security advisory"
|
|
20
|
+
echo "search_hint_2=nginx ${NGINX_VERSION} CVE"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo ""
|
|
24
|
+
echo "--- nginx 进程运行用户 ---"
|
|
25
|
+
ps -eo user,pid,cmd | grep "[n]ginx: master" | head -1
|
|
26
|
+
|
|
27
|
+
echo ""
|
|
28
|
+
echo "--- nginx -T (effective config, 需 sudo 或 nginx 用户可读) ---"
|
|
29
|
+
NGINX_DUMP=$(sudo -n nginx -T 2>/dev/null || nginx -T 2>/dev/null || true)
|
|
30
|
+
if [[ -z "$NGINX_DUMP" ]]; then
|
|
31
|
+
echo "[!] 无法 dump nginx 配置,改读 /etc/nginx/"
|
|
32
|
+
ls -la /etc/nginx/ 2>/dev/null
|
|
33
|
+
echo "--- /etc/nginx/nginx.conf (head) ---"
|
|
34
|
+
head -80 /etc/nginx/nginx.conf 2>/dev/null
|
|
35
|
+
echo "--- sites-enabled / conf.d ---"
|
|
36
|
+
for d in /etc/nginx/sites-enabled /etc/nginx/conf.d; do
|
|
37
|
+
[[ -d "$d" ]] && ls -la "$d" 2>/dev/null
|
|
38
|
+
done
|
|
39
|
+
else
|
|
40
|
+
echo "$NGINX_DUMP" | grep -E "^# configuration file" | head -20
|
|
41
|
+
echo ""
|
|
42
|
+
echo "--- KEY DIRECTIVES (filtered from nginx -T) ---"
|
|
43
|
+
echo "$NGINX_DUMP" | grep -Ei "^[[:space:]]*(listen|server_name|return|rewrite|ssl_protocols|ssl_ciphers|ssl_prefer_server_ciphers|ssl_certificate|ssl_certificate_key|server_tokens|access_log|error_log|client_max_body_size|client_body_timeout|add_header|autoindex|location|root|alias|proxy_pass)" \
|
|
44
|
+
| grep -v "^[[:space:]]*#" | head -200
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo "--- HSTS / Security Headers 检查 ---"
|
|
48
|
+
echo "$NGINX_DUMP" | grep -i "Strict-Transport-Security\|X-Frame-Options\|X-Content-Type-Options\|Content-Security-Policy\|Referrer-Policy" || echo "[!] 未找到任何安全 header"
|
|
49
|
+
|
|
50
|
+
echo ""
|
|
51
|
+
echo "--- HTTP→HTTPS 跳转检查 ---"
|
|
52
|
+
echo "$NGINX_DUMP" | awk '/listen[[:space:]]+80/,/}/' | grep -i "return\|rewrite" | head -10 || echo "[!] 80 端口可能未配置强制跳转"
|
|
53
|
+
|
|
54
|
+
echo ""
|
|
55
|
+
echo "--- server_tokens ---"
|
|
56
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*server_tokens" || echo "[!] server_tokens 未显式配置 (默认 on,会暴露版本)"
|
|
57
|
+
|
|
58
|
+
echo ""
|
|
59
|
+
echo "--- access_log / error_log ---"
|
|
60
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*(access_log|error_log)" | head -10
|
|
61
|
+
|
|
62
|
+
echo ""
|
|
63
|
+
echo "--- client_max_body_size (业务非上传 ≤ 10m) ---"
|
|
64
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*client_max_body_size" || echo "[!] 未显式配置 client_max_body_size"
|
|
65
|
+
|
|
66
|
+
echo ""
|
|
67
|
+
echo "--- 限流配置 (limit_req / limit_conn 防 CC / 暴力请求) ---"
|
|
68
|
+
ZONE_DEFS=$(echo "$NGINX_DUMP" | grep -E "^[[:space:]]*limit_(req|conn)_zone" || true)
|
|
69
|
+
if [[ -z "$ZONE_DEFS" ]]; then
|
|
70
|
+
echo "[!] CRITICAL: 未发现任何 limit_req_zone / limit_conn_zone 定义,无 CC 攻击防护"
|
|
71
|
+
else
|
|
72
|
+
echo "已定义 zone:"
|
|
73
|
+
echo "$ZONE_DEFS"
|
|
74
|
+
echo ""
|
|
75
|
+
echo "应用位置 (limit_req / limit_conn):"
|
|
76
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*limit_(req|conn)[[:space:]]" || echo "[!] zone 已定义但未在 server/location 中应用"
|
|
77
|
+
echo ""
|
|
78
|
+
LIMIT_STATUS=$(echo "$NGINX_DUMP" | grep -E "^[[:space:]]*limit_req_status" | head -1)
|
|
79
|
+
echo "limit_req_status: ${LIMIT_STATUS:-(默认 503,建议 429)}"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
echo ""
|
|
83
|
+
echo "--- 慢速攻击防护 (slowloris) ---"
|
|
84
|
+
for d in client_body_timeout client_header_timeout send_timeout keepalive_timeout; do
|
|
85
|
+
val=$(echo "$NGINX_DUMP" | grep -E "^[[:space:]]*${d}[[:space:]]" | head -1)
|
|
86
|
+
echo "${d}: ${val:-(未显式配置,使用默认)}"
|
|
87
|
+
done
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
echo "--- nginx 配置语法检查 ---"
|
|
92
|
+
sudo -n nginx -t 2>&1 || nginx -t 2>&1 || echo "[i] 无法执行 nginx -t"
|
|
93
|
+
|
|
94
|
+
echo ""
|
|
95
|
+
echo "--- 危险路径暴露快测 (本机回环) ---"
|
|
96
|
+
for path in "/.env" "/.git/HEAD" "/.git/config" "/admin" "/phpmyadmin" "/.DS_Store"; do
|
|
97
|
+
code=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 3 "https://localhost${path}" 2>/dev/null || echo "000")
|
|
98
|
+
printf " %-20s -> https://localhost HTTP %s\n" "$path" "$code"
|
|
99
|
+
done
|
|
100
|
+
REMOTE
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 自愈能力 / 资源耗尽防护检查 (只读)
|
|
3
|
+
#
|
|
4
|
+
# 检查范围:
|
|
5
|
+
# B 类: 系统服务开机自启 (sshd / nginx / docker / postgres / redis / fail2ban / 自定义)
|
|
6
|
+
# D 类: 资源耗尽防护 (swap / ulimit / logrotate / 磁盘使用率 / 应用进程内存上限)
|
|
7
|
+
#
|
|
8
|
+
# 服务器意外重启后,这些配置决定了服务能否自动恢复;资源耗尽场景决定了
|
|
9
|
+
# 服务能否在异常下保住基本运行而不被全局拖垮。
|
|
10
|
+
|
|
11
|
+
TARGET="$1"; shift
|
|
12
|
+
SSH_OPTS=("$@")
|
|
13
|
+
|
|
14
|
+
ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
|
|
15
|
+
echo "########## B. 系统服务开机自启 ##########"
|
|
16
|
+
|
|
17
|
+
# 默认必检清单
|
|
18
|
+
DEFAULT_SERVICES=(sshd ssh nginx docker docker.socket postgresql postgres redis redis-server fail2ban supervisor crond cron)
|
|
19
|
+
|
|
20
|
+
echo "--- 默认必检清单 ---"
|
|
21
|
+
for svc in "${DEFAULT_SERVICES[@]}"; do
|
|
22
|
+
# systemctl status 区分: not-found / disabled / enabled / static / masked
|
|
23
|
+
enabled=$(systemctl is-enabled "$svc" 2>/dev/null || echo "n/a")
|
|
24
|
+
active=$(systemctl is-active "$svc" 2>/dev/null || echo "n/a")
|
|
25
|
+
if [[ "$enabled" == "n/a" && "$active" == "n/a" ]]; then
|
|
26
|
+
continue # 该服务未安装,跳过
|
|
27
|
+
fi
|
|
28
|
+
case "$enabled" in
|
|
29
|
+
enabled|enabled-runtime|alias|static)
|
|
30
|
+
icon="[OK]" ;;
|
|
31
|
+
disabled)
|
|
32
|
+
icon="[!!!] CRITICAL: 已部署但未开机自启,服务器重启后不会启动" ;;
|
|
33
|
+
masked)
|
|
34
|
+
icon="[!!!] CRITICAL: 服务被 mask,无法启动" ;;
|
|
35
|
+
*)
|
|
36
|
+
icon="[?] enabled=$enabled" ;;
|
|
37
|
+
esac
|
|
38
|
+
printf " %-20s active=%-10s enabled=%-12s %s\n" "$svc" "$active" "$enabled" "$icon"
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
echo ""
|
|
42
|
+
echo "--- 自动探测: 当前 enabled 的所有 unit (供人工核对应用主进程是否在内) ---"
|
|
43
|
+
systemctl list-unit-files --type=service --state=enabled 2>/dev/null \
|
|
44
|
+
| awk 'NR>1 && $1 != "" {print " " $1}' \
|
|
45
|
+
| head -40 || echo "[i] 无法列出 unit-files"
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo "--- 自动探测: 当前 running 但 disabled 的服务 (重启后会消失) ---"
|
|
49
|
+
running_services=$(systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null | awk '{print $1}')
|
|
50
|
+
for svc in $running_services; do
|
|
51
|
+
enabled=$(systemctl is-enabled "$svc" 2>/dev/null)
|
|
52
|
+
if [[ "$enabled" == "disabled" ]]; then
|
|
53
|
+
echo " [!!] $svc 正在运行但 disabled,重启后会丢失"
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
echo ""
|
|
59
|
+
echo "########## D. 资源耗尽防护 ##########"
|
|
60
|
+
|
|
61
|
+
echo "--- 内存与 swap ---"
|
|
62
|
+
free -h 2>/dev/null || vmstat -s 2>/dev/null | head -10
|
|
63
|
+
SWAP_SIZE=$(awk '/SwapTotal/ {print $2}' /proc/meminfo 2>/dev/null)
|
|
64
|
+
if [[ -z "$SWAP_SIZE" || "$SWAP_SIZE" == "0" ]]; then
|
|
65
|
+
echo "[!] MEDIUM: 未配置 swap,内存吃紧时直接 OOM kill"
|
|
66
|
+
echo " 建议: 至少配置 1-2GB swap (fallocate / dd + mkswap + swapon + /etc/fstab)"
|
|
67
|
+
else
|
|
68
|
+
echo "[OK] swap 已配置: $((SWAP_SIZE / 1024)) MB"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo ""
|
|
72
|
+
echo "--- 根分区使用率 ---"
|
|
73
|
+
df -h / 2>/dev/null
|
|
74
|
+
ROOT_USE=$(df / --output=pcent 2>/dev/null | tail -1 | tr -d ' %')
|
|
75
|
+
if [[ -n "$ROOT_USE" ]]; then
|
|
76
|
+
if [[ "$ROOT_USE" -ge 90 ]]; then
|
|
77
|
+
echo "[!!!] CRITICAL: 根分区使用率 ${ROOT_USE}%, 即将写满"
|
|
78
|
+
elif [[ "$ROOT_USE" -ge 80 ]]; then
|
|
79
|
+
echo "[!] MEDIUM: 根分区使用率 ${ROOT_USE}%, 建议清理"
|
|
80
|
+
else
|
|
81
|
+
echo "[OK] 根分区使用率 ${ROOT_USE}%"
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
echo ""
|
|
86
|
+
echo "--- /var/log 与 /var/lib/docker 大小 ---"
|
|
87
|
+
sudo -n du -sh /var/log /var/lib/docker /var/lib/postgresql 2>/dev/null \
|
|
88
|
+
|| du -sh /var/log /var/lib/docker /var/lib/postgresql 2>/dev/null \
|
|
89
|
+
|| echo "[i] 部分目录不可读"
|
|
90
|
+
|
|
91
|
+
echo ""
|
|
92
|
+
echo "--- logrotate 配置 ---"
|
|
93
|
+
if [[ -f /etc/logrotate.conf ]]; then
|
|
94
|
+
echo "[OK] /etc/logrotate.conf 存在"
|
|
95
|
+
ls /etc/logrotate.d/ 2>/dev/null | head -20 | sed 's|^| |'
|
|
96
|
+
else
|
|
97
|
+
echo "[!] HIGH: /etc/logrotate.conf 不存在,日志可能无限增长"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# 关键服务是否有 logrotate 规则
|
|
101
|
+
echo ""
|
|
102
|
+
echo "--- 关键服务 logrotate 规则 ---"
|
|
103
|
+
for app in nginx postgresql redis docker rsyslog; do
|
|
104
|
+
if [[ -f "/etc/logrotate.d/$app" ]]; then
|
|
105
|
+
echo " [OK] /etc/logrotate.d/$app"
|
|
106
|
+
else
|
|
107
|
+
if systemctl is-enabled "$app" >/dev/null 2>&1 || systemctl is-enabled "${app}-server" >/dev/null 2>&1; then
|
|
108
|
+
echo " [!] $app 已部署但无 logrotate 规则"
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
echo ""
|
|
114
|
+
echo "--- 文件描述符上限 (system + 关键服务) ---"
|
|
115
|
+
echo " 系统 fs.file-max: $(sysctl -n fs.file-max 2>/dev/null)"
|
|
116
|
+
echo " 系统 fs.nr_open: $(sysctl -n fs.nr_open 2>/dev/null)"
|
|
117
|
+
for app in nginx postgres redis-server; do
|
|
118
|
+
pid=$(pgrep -f "[${app:0:1}]${app:1}" | head -1)
|
|
119
|
+
if [[ -n "$pid" ]]; then
|
|
120
|
+
soft=$(awk '/Max open files/ {print $4}' /proc/$pid/limits 2>/dev/null)
|
|
121
|
+
hard=$(awk '/Max open files/ {print $5}' /proc/$pid/limits 2>/dev/null)
|
|
122
|
+
echo " $app (pid $pid): soft=$soft hard=$hard"
|
|
123
|
+
if [[ -n "$soft" && "$soft" -lt 4096 ]]; then
|
|
124
|
+
echo " [!] MEDIUM: ulimit -n 偏低 ($soft),高并发场景可能耗尽"
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
echo ""
|
|
130
|
+
echo "--- /etc/security/limits.conf 自定义条目 ---"
|
|
131
|
+
sudo -n grep -vE "^[[:space:]]*#|^[[:space:]]*$" /etc/security/limits.conf 2>/dev/null \
|
|
132
|
+
|| grep -vE "^[[:space:]]*#|^[[:space:]]*$" /etc/security/limits.conf 2>/dev/null \
|
|
133
|
+
|| echo "[i] limits.conf 不可读"
|
|
134
|
+
ls /etc/security/limits.d/ 2>/dev/null | sed 's|^| /etc/security/limits.d/|'
|
|
135
|
+
|
|
136
|
+
echo ""
|
|
137
|
+
echo "--- 内存压力指标 (PSI) ---"
|
|
138
|
+
if [[ -r /proc/pressure/memory ]]; then
|
|
139
|
+
cat /proc/pressure/memory
|
|
140
|
+
else
|
|
141
|
+
echo "[i] PSI 不可用 (内核 < 4.20 或未启用)"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo ""
|
|
145
|
+
echo "--- 关键容器内存上限 (避免容器吃掉宿主机) ---"
|
|
146
|
+
if command -v docker >/dev/null 2>&1; then
|
|
147
|
+
docker ps -q 2>/dev/null | while read c; do
|
|
148
|
+
name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
|
|
149
|
+
mem=$(docker inspect -f '{{.HostConfig.Memory}}' "$c" 2>/dev/null)
|
|
150
|
+
if [[ "$mem" == "0" ]]; then
|
|
151
|
+
echo " [!] $name 无内存上限,异常时可能耗尽宿主机"
|
|
152
|
+
else
|
|
153
|
+
echo " [OK] $name Memory=$((mem / 1024 / 1024)) MB"
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
fi
|
|
157
|
+
REMOTE
|