create-dwy 0.5.0 → 0.6.0
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/bin/index.js +1 -1
- package/package.json +2 -2
- package/src/create.js +61 -37
- package/src/sync.js +213 -53
- package/templates/claude-global/CLAUDE.md +92 -0
- package/templates/claude-global/rules/dwy-android-core.md +610 -0
- package/templates/claude-global/rules/dwy-api-design.md +110 -0
- package/templates/claude-global/rules/dwy-db-migration.md +46 -0
- package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
- package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
- package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
- package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
- package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
- package/templates/claude-global/rules/dwy-git-security.md +53 -0
- package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
- package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
- package/templates/claude-global/rules/dwy-project-structure.md +181 -0
- package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +314 -357
- package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
- package/templates/claude-global/rules/dwy-python-orm.md +61 -0
- package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +116 -79
- package/templates/claude-global/rules/dwy-python-testing.md +53 -0
- package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
- package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
- package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
- package/templates/claude-global/rules/dwy-vue-api.md +146 -0
- package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +361 -152
- package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
- package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
- package/templates/claude-global/settings.json +20 -0
- package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
- package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
- package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
- package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
- package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
- package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
- package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
- package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
- package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
- package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
- package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
- package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
- package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
- package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
- package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
- package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
- package/templates/claude-global/skills/dwy-publish/SKILL.md +135 -0
- package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
- package/templates/project/backend/.dwy.hbs +5 -0
- package/templates/project/backend/.python-version +1 -0
- package/templates/project/backend/CLAUDE.md.hbs +40 -0
- package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
- package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
- package/templates/project/backend/deploy.sh.hbs +18 -0
- package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
- package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
- package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
- package/templates/project/backend/src/app/database.py +10 -0
- package/templates/project/backend/src/app/dependencies.py +5 -0
- package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
- package/templates/project/backend/src/app/models/base.py +5 -0
- package/templates/project/web/CLAUDE.md.hbs +29 -36
- package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
- package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
- package/templates/project/web/apps/web/src/App.vue +9 -0
- package/templates/project/web/apps/web/src/api/client.ts +5 -0
- package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
- package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
- package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
- package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
- package/templates/project/web/apps/web/src/style.css +2 -0
- package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
- package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
- package/templates/project/web/apps/web/tsconfig.app.json +18 -0
- package/templates/project/web/apps/web/tsconfig.json +6 -0
- package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
- package/templates/project/web/package.json.hbs +16 -0
- package/templates/project/web/packages/.gitkeep +0 -0
- package/templates/project/web/pnpm-workspace.yaml +3 -0
- package/templates/claude-global/rules/agent-team-execution.md +0 -138
- package/templates/claude-global/rules/agent-team-patterns.md +0 -157
- package/templates/claude-global/rules/git-security.md +0 -144
- package/templates/claude-global/skills/dwy-auto-sync/SKILL.md +0 -52
- package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
- package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
- package/templates/project/web/.claude/commands/deploy.md +0 -4
- package/templates/project/web/backend/src/app/models/base.py +0 -5
- package/templates/project/web/deploy.sh.hbs +0 -113
- package/templates/project/web/frontend/src/App.vue +0 -3
- package/templates/project/web/frontend/src/api/client.ts +0 -13
- package/templates/project/web/frontend/src/style.css +0 -1
- package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
- package/templates/project/web/frontend/tsconfig.app.json +0 -14
- package/templates/project/web/frontend/tsconfig.json +0 -4
- /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
- /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
- /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
- /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
- /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
- /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
- /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
|
@@ -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,92 @@
|
|
|
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
|
+
|
|
15
|
+
echo ""
|
|
16
|
+
echo "--- nginx 进程运行用户 ---"
|
|
17
|
+
ps -eo user,pid,cmd | grep "[n]ginx: master" | head -1
|
|
18
|
+
|
|
19
|
+
echo ""
|
|
20
|
+
echo "--- nginx -T (effective config, 需 sudo 或 nginx 用户可读) ---"
|
|
21
|
+
NGINX_DUMP=$(sudo -n nginx -T 2>/dev/null || nginx -T 2>/dev/null || true)
|
|
22
|
+
if [[ -z "$NGINX_DUMP" ]]; then
|
|
23
|
+
echo "[!] 无法 dump nginx 配置,改读 /etc/nginx/"
|
|
24
|
+
ls -la /etc/nginx/ 2>/dev/null
|
|
25
|
+
echo "--- /etc/nginx/nginx.conf (head) ---"
|
|
26
|
+
head -80 /etc/nginx/nginx.conf 2>/dev/null
|
|
27
|
+
echo "--- sites-enabled / conf.d ---"
|
|
28
|
+
for d in /etc/nginx/sites-enabled /etc/nginx/conf.d; do
|
|
29
|
+
[[ -d "$d" ]] && ls -la "$d" 2>/dev/null
|
|
30
|
+
done
|
|
31
|
+
else
|
|
32
|
+
echo "$NGINX_DUMP" | grep -E "^# configuration file" | head -20
|
|
33
|
+
echo ""
|
|
34
|
+
echo "--- KEY DIRECTIVES (filtered from nginx -T) ---"
|
|
35
|
+
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)" \
|
|
36
|
+
| grep -v "^[[:space:]]*#" | head -200
|
|
37
|
+
|
|
38
|
+
echo ""
|
|
39
|
+
echo "--- HSTS / Security Headers 检查 ---"
|
|
40
|
+
echo "$NGINX_DUMP" | grep -i "Strict-Transport-Security\|X-Frame-Options\|X-Content-Type-Options\|Content-Security-Policy\|Referrer-Policy" || echo "[!] 未找到任何安全 header"
|
|
41
|
+
|
|
42
|
+
echo ""
|
|
43
|
+
echo "--- HTTP→HTTPS 跳转检查 ---"
|
|
44
|
+
echo "$NGINX_DUMP" | awk '/listen[[:space:]]+80/,/}/' | grep -i "return\|rewrite" | head -10 || echo "[!] 80 端口可能未配置强制跳转"
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo "--- server_tokens ---"
|
|
48
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*server_tokens" || echo "[!] server_tokens 未显式配置 (默认 on,会暴露版本)"
|
|
49
|
+
|
|
50
|
+
echo ""
|
|
51
|
+
echo "--- access_log / error_log ---"
|
|
52
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*(access_log|error_log)" | head -10
|
|
53
|
+
|
|
54
|
+
echo ""
|
|
55
|
+
echo "--- client_max_body_size (对齐 dwy-payload-limits) ---"
|
|
56
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*client_max_body_size" || echo "[!] 未显式配置 client_max_body_size"
|
|
57
|
+
|
|
58
|
+
echo ""
|
|
59
|
+
echo "--- 限流配置 (limit_req / limit_conn 防 CC / 暴力请求) ---"
|
|
60
|
+
ZONE_DEFS=$(echo "$NGINX_DUMP" | grep -E "^[[:space:]]*limit_(req|conn)_zone" || true)
|
|
61
|
+
if [[ -z "$ZONE_DEFS" ]]; then
|
|
62
|
+
echo "[!] CRITICAL: 未发现任何 limit_req_zone / limit_conn_zone 定义,无 CC 攻击防护"
|
|
63
|
+
else
|
|
64
|
+
echo "已定义 zone:"
|
|
65
|
+
echo "$ZONE_DEFS"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "应用位置 (limit_req / limit_conn):"
|
|
68
|
+
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*limit_(req|conn)[[:space:]]" || echo "[!] zone 已定义但未在 server/location 中应用"
|
|
69
|
+
echo ""
|
|
70
|
+
LIMIT_STATUS=$(echo "$NGINX_DUMP" | grep -E "^[[:space:]]*limit_req_status" | head -1)
|
|
71
|
+
echo "limit_req_status: ${LIMIT_STATUS:-(默认 503,建议 429)}"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo ""
|
|
75
|
+
echo "--- 慢速攻击防护 (slowloris) ---"
|
|
76
|
+
for d in client_body_timeout client_header_timeout send_timeout keepalive_timeout; do
|
|
77
|
+
val=$(echo "$NGINX_DUMP" | grep -E "^[[:space:]]*${d}[[:space:]]" | head -1)
|
|
78
|
+
echo "${d}: ${val:-(未显式配置,使用默认)}"
|
|
79
|
+
done
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
echo ""
|
|
83
|
+
echo "--- nginx 配置语法检查 ---"
|
|
84
|
+
sudo -n nginx -t 2>&1 || nginx -t 2>&1 || echo "[i] 无法执行 nginx -t"
|
|
85
|
+
|
|
86
|
+
echo ""
|
|
87
|
+
echo "--- 危险路径暴露快测 (本机回环) ---"
|
|
88
|
+
for path in "/.env" "/.git/HEAD" "/.git/config" "/admin" "/phpmyadmin" "/.DS_Store"; do
|
|
89
|
+
code=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 3 "https://localhost${path}" 2>/dev/null || echo "000")
|
|
90
|
+
printf " %-20s -> https://localhost HTTP %s\n" "$path" "$code"
|
|
91
|
+
done
|
|
92
|
+
REMOTE
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 自愈能力 / 资源耗尽防护检查 (只读)
|
|
3
|
+
#
|
|
4
|
+
# 检查范围:
|
|
5
|
+
# B 类: 系统服务开机自启 (sshd / nginx / docker / postgres / redis / frps / 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 frps frpc 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
|