create-dwy 0.5.1 → 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.
Files changed (156) hide show
  1. package/bin/index.js +1 -1
  2. package/package.json +2 -2
  3. package/src/create.js +61 -37
  4. package/src/sync.js +212 -61
  5. package/templates/claude-global/CLAUDE.md +92 -0
  6. package/templates/claude-global/rules/dwy-android-core.md +610 -0
  7. package/templates/claude-global/rules/dwy-api-design.md +110 -0
  8. package/templates/claude-global/rules/dwy-db-migration.md +46 -0
  9. package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
  10. package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
  11. package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
  12. package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
  13. package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
  14. package/templates/claude-global/rules/dwy-git-security.md +53 -0
  15. package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
  16. package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
  17. package/templates/claude-global/rules/dwy-project-structure.md +181 -0
  18. package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +292 -358
  19. package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
  20. package/templates/claude-global/rules/dwy-python-orm.md +61 -0
  21. package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +99 -72
  22. package/templates/claude-global/rules/dwy-python-testing.md +53 -0
  23. package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
  24. package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
  25. package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
  26. package/templates/claude-global/rules/dwy-vue-api.md +146 -0
  27. package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +338 -152
  28. package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
  29. package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
  30. package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
  31. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
  32. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
  33. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
  34. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
  35. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
  36. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
  37. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
  38. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
  39. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
  40. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
  41. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
  42. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
  43. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
  44. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
  45. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
  46. package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
  47. package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
  48. package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
  49. package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
  50. package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
  51. package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
  52. package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
  53. package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
  54. package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
  55. package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
  56. package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
  57. package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
  58. package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
  59. package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
  60. package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
  61. package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
  62. package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
  63. package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
  64. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
  65. package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
  66. package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
  67. package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
  68. package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
  69. package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
  70. package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
  71. package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
  72. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
  73. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
  74. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
  75. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
  76. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
  77. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
  78. package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
  79. package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
  80. package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
  81. package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
  82. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
  83. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
  84. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
  85. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
  86. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
  87. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
  88. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
  89. package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
  90. package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
  91. package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
  92. package/templates/claude-global/skills/{dwy-release → dwy-publish}/SKILL.md +42 -18
  93. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
  94. package/templates/project/backend/.dwy.hbs +5 -0
  95. package/templates/project/backend/.python-version +1 -0
  96. package/templates/project/backend/CLAUDE.md.hbs +40 -0
  97. package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
  98. package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
  99. package/templates/project/backend/deploy.sh.hbs +18 -0
  100. package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
  101. package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
  102. package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
  103. package/templates/project/backend/src/app/database.py +10 -0
  104. package/templates/project/backend/src/app/dependencies.py +5 -0
  105. package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
  106. package/templates/project/backend/src/app/models/base.py +5 -0
  107. package/templates/project/web/CLAUDE.md.hbs +29 -36
  108. package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
  109. package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
  110. package/templates/project/web/apps/web/src/App.vue +9 -0
  111. package/templates/project/web/apps/web/src/api/client.ts +5 -0
  112. package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
  113. package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
  114. package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
  115. package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
  116. package/templates/project/web/apps/web/src/style.css +2 -0
  117. package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
  118. package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
  119. package/templates/project/web/apps/web/tsconfig.app.json +18 -0
  120. package/templates/project/web/apps/web/tsconfig.json +6 -0
  121. package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
  122. package/templates/project/web/package.json.hbs +16 -0
  123. package/templates/project/web/packages/.gitkeep +0 -0
  124. package/templates/project/web/pnpm-workspace.yaml +3 -0
  125. package/templates/claude-global/hooks/post-sync-claude-global.sh +0 -72
  126. package/templates/claude-global/rules/agent-team-execution.md +0 -138
  127. package/templates/claude-global/rules/agent-team-patterns.md +0 -157
  128. package/templates/claude-global/rules/git-commit-convention.md +0 -106
  129. package/templates/claude-global/rules/git-security.md +0 -144
  130. package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
  131. package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
  132. package/templates/project/web/.claude/commands/deploy.md +0 -4
  133. package/templates/project/web/backend/src/app/models/base.py +0 -5
  134. package/templates/project/web/deploy.sh.hbs +0 -113
  135. package/templates/project/web/frontend/src/App.vue +0 -3
  136. package/templates/project/web/frontend/src/api/client.ts +0 -13
  137. package/templates/project/web/frontend/src/style.css +0 -1
  138. package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
  139. package/templates/project/web/frontend/tsconfig.app.json +0 -14
  140. package/templates/project/web/frontend/tsconfig.json +0 -4
  141. /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
  142. /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
  143. /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
  144. /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
  145. /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
  146. /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
  147. /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
  148. /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
  149. /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
  150. /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
  151. /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
  152. /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
  153. /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
  154. /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
  155. /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
  156. /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