- upgrade.sh/rollback.sh: backup→pull→rolling restart→healthcheck→auto-rollback - backup.sh/restore.sh: mysqldump+redis BGSAVE+config tar, SHA256 manifest, restore with checksum verification - healthcheck.sh: Docker/container/MySQL/Redis/HTTP/disk checks, JSON output to .deploy-state/ - doctor.sh: sanitized diagnostics archive, vendor API TCP connectivity, cert expiry - export-offline-bundle.sh: docker pull+save for all profile images, load-images.sh, SHA256 - configure.sh: interactive/non-interactive mode, MySQL/Redis mode selection, domain prompts - enable-service.sh: domain validation, docker pull + compose up, healthcheck - disable-service.sh: compose stop+rm, profile removal, render-config - renew-cert.sh: acme.sh/certbot, --dry-run, backup old cert, nginx reload on success - alert-webhook.sh: WeCom/DingTalk/Feishu webhook, message sanitization - bench.sh: ab/wrk/curl benchmark, JSON report with docker stats - rotate-secrets.sh: JWT and internal token rotation - vendor credential templates: push.env and store-submit.env with full credential comments - render-config.sh: auto-sync SDK URL env vars (SDK_FILE_SERVICE_URL, SDK_IM_API_URL, SDK_IM_WS_URL) - All scripts pass bash -n syntax check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 行
4.0 KiB
Bash
可执行文件
114 行
4.0 KiB
Bash
可执行文件
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
. "$ROOT_DIR/scripts/lib.sh"
|
|
load_env
|
|
|
|
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
|
|
DIAG_DIR="$ROOT_DIR/dist/doctor-${TIMESTAMP}"
|
|
DIAG_ARCHIVE="$ROOT_DIR/dist/doctor-${TIMESTAMP}.tar.gz"
|
|
|
|
audit "doctor" "STARTED" "collecting diagnostics"
|
|
progress "doctor" "STARTED" "collecting diagnostics"
|
|
|
|
mkdir -p "$DIAG_DIR"
|
|
|
|
# System info
|
|
{
|
|
printf '=== System Info ===\n'
|
|
uname -a 2>/dev/null || true
|
|
printf '\n=== Docker Version ===\n'
|
|
docker version 2>/dev/null || true
|
|
printf '\n=== Disk Usage ===\n'
|
|
df -h "$ROOT_DIR" 2>/dev/null || true
|
|
printf '\n=== Memory ===\n'
|
|
free -h 2>/dev/null || vm_stat 2>/dev/null || true
|
|
} > "$DIAG_DIR/system.txt"
|
|
|
|
# Container states
|
|
compose ps 2>/dev/null > "$DIAG_DIR/containers.txt" || true
|
|
|
|
# Last healthcheck (already sanitized — no secrets)
|
|
[ -f "$ROOT_DIR/.deploy-state/last-healthcheck.json" ] && \
|
|
cp "$ROOT_DIR/.deploy-state/last-healthcheck.json" "$DIAG_DIR/"
|
|
|
|
# Deploy state
|
|
cp "$ROOT_DIR/.deploy-state/progress.md" "$DIAG_DIR/" 2>/dev/null || true
|
|
|
|
# Sanitized env (strip secrets)
|
|
grep -v -E '(PASSWORD|SECRET|TOKEN|KEY|SMTP|AUTH)' "$ROOT_DIR/.env" 2>/dev/null \
|
|
> "$DIAG_DIR/env-sanitized.txt" || true
|
|
grep -v -E '(PASSWORD|SECRET|TOKEN|KEY|SMTP|AUTH)' "$ROOT_DIR/config/xuqm.env" 2>/dev/null \
|
|
>> "$DIAG_DIR/env-sanitized.txt" || true
|
|
|
|
# Recent audit log (last 200 lines)
|
|
tail -200 "$ROOT_DIR/logs/audit.log" 2>/dev/null > "$DIAG_DIR/audit-recent.log" || true
|
|
|
|
# Recent container logs (last 100 lines per service, sanitized)
|
|
for svc in tenant-service file-service nginx im-service push-service update-service license-service; do
|
|
CTR="$(compose ps -q "$svc" 2>/dev/null | head -1 || true)"
|
|
if [ -n "$CTR" ]; then
|
|
docker logs --tail=100 "$CTR" 2>&1 | \
|
|
grep -v -E '(password|secret|token|Authorization)' \
|
|
> "$DIAG_DIR/log-${svc}.txt" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Vendor API connectivity checks (DNS + TCP only, no credentials sent)
|
|
{
|
|
printf '=== Vendor API Connectivity ===\n'
|
|
vendor_check() {
|
|
local name="$1"
|
|
local host="$2"
|
|
local port="${3:-443}"
|
|
printf '\n%s (%s:%s): ' "$name" "$host" "$port"
|
|
if command -v nc >/dev/null 2>&1; then
|
|
if nc -z -w5 "$host" "$port" 2>/dev/null; then
|
|
printf 'TCP OK\n'
|
|
else
|
|
printf 'TCP FAIL\n'
|
|
fi
|
|
elif command -v curl >/dev/null 2>&1; then
|
|
HTTP_CODE="$(curl -skL -o /dev/null -w '%{http_code}' --max-time 5 "https://$host" 2>/dev/null || echo '000')"
|
|
printf 'HTTP %s\n' "$HTTP_CODE"
|
|
else
|
|
printf 'SKIPPED (no nc/curl)\n'
|
|
fi
|
|
}
|
|
|
|
if [ "${ENABLE_PUSH:-false}" = "true" ]; then
|
|
vendor_check "Huawei Push" "push-api.cloud.huawei.com" 443
|
|
vendor_check "Xiaomi Push" "api.xmpush.xiaomi.com" 443
|
|
vendor_check "OPPO Push" "api.push.oppomobile.com" 443
|
|
vendor_check "vivo Push" "api-push.vivo.com.cn" 443
|
|
vendor_check "APNs" "api.push.apple.com" 443
|
|
fi
|
|
if [ "${ENABLE_UPDATE:-false}" = "true" ]; then
|
|
vendor_check "Huawei AppGallery" "connect-api.cloud.huawei.com" 443
|
|
vendor_check "Xiaomi Store" "api.developer.xiaomi.com" 443
|
|
vendor_check "OPPO Store" "oop-openapi.oppomobile.com" 443
|
|
vendor_check "vivo Store" "developer.vivo.com.cn" 443
|
|
vendor_check "Honor Store" "developer.hihonor.com" 443
|
|
fi
|
|
} > "$DIAG_DIR/vendor-connectivity.txt" 2>&1
|
|
|
|
# Certificate expiry check
|
|
if [ -n "${CONSOLE_DOMAIN:-}" ] && command -v openssl >/dev/null 2>&1; then
|
|
HOST="$(printf '%s' "${CONSOLE_DOMAIN#https://}" | cut -d'/' -f1)"
|
|
{
|
|
printf '=== TLS Certificate Expiry ===\n'
|
|
printf '%s\n' "$HOST"
|
|
openssl s_client -connect "${HOST}:443" -servername "$HOST" </dev/null 2>/dev/null | \
|
|
openssl x509 -noout -dates 2>/dev/null || printf 'Could not check certificate\n'
|
|
} > "$DIAG_DIR/cert-expiry.txt" 2>&1
|
|
fi
|
|
|
|
# Package into archive
|
|
tar -czf "$DIAG_ARCHIVE" -C "$ROOT_DIR/dist" "doctor-${TIMESTAMP}"
|
|
rm -rf "$DIAG_DIR"
|
|
|
|
audit "doctor" "DONE" "archive=$DIAG_ARCHIVE"
|
|
progress "doctor" "DONE" "archive=$DIAG_ARCHIVE"
|
|
printf 'Diagnostics collected: %s\n' "$DIAG_ARCHIVE"
|