test/user-journey #3
@@ -33,7 +33,8 @@
|
||||
"node_modules/**",
|
||||
"coverage/**",
|
||||
"scripts/**",
|
||||
"config/test/**"
|
||||
"config/test/**",
|
||||
"jest.setup.js"
|
||||
],
|
||||
"globals": {
|
||||
"jest": "readonly"
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
# 方案A执行指南
|
||||
|
||||
## 🚀 快速执行(推荐)
|
||||
|
||||
### 方法1: 自动化脚本(最简单)
|
||||
|
||||
```bash
|
||||
# 1. SSH登录服务器
|
||||
ssh root@139.155.109.62
|
||||
|
||||
# 2. 上传脚本(从本地)
|
||||
# 在本地执行:
|
||||
scp scripts/fix-service-restart.sh root@139.155.109.62:/tmp/
|
||||
|
||||
# 3. 在服务器上执行
|
||||
ssh root@139.155.109.62
|
||||
chmod +x /tmp/fix-service-restart.sh
|
||||
/tmp/fix-service-restart.sh
|
||||
```
|
||||
|
||||
### 方法2: 手动执行(如果脚本无法上传)
|
||||
|
||||
```bash
|
||||
# SSH登录服务器
|
||||
ssh root@139.155.109.62
|
||||
|
||||
# 1. 查找项目目录
|
||||
find / -name "docker-compose.prod.yml" 2>/dev/null
|
||||
# 或
|
||||
find / -name "docker-compose.yml" 2>/dev/null
|
||||
|
||||
# 2. 进入项目目录(假设在/opt/novalon-website)
|
||||
cd /opt/novalon-website
|
||||
|
||||
# 3. 重启Docker容器
|
||||
docker-compose -f docker-compose.prod.yml restart
|
||||
# 或
|
||||
docker-compose restart
|
||||
|
||||
# 4. 检查容器状态
|
||||
docker ps
|
||||
|
||||
# 5. 重启Nginx
|
||||
systemctl restart nginx
|
||||
|
||||
# 6. 检查Nginx状态
|
||||
systemctl status nginx
|
||||
|
||||
# 7. 测试应用
|
||||
curl -I http://localhost:3000
|
||||
curl -I https://novalon.cn
|
||||
```
|
||||
|
||||
## 📋 执行步骤详解
|
||||
|
||||
### 步骤1: 检查当前状态
|
||||
```bash
|
||||
# 查看Docker容器
|
||||
docker ps -a
|
||||
|
||||
# 查看Nginx状态
|
||||
systemctl status nginx
|
||||
|
||||
# 查看系统资源
|
||||
top -bn1 | head -20
|
||||
df -h
|
||||
free -h
|
||||
```
|
||||
|
||||
### 步骤2: 重启Docker容器
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd /path/to/novalon-website
|
||||
|
||||
# 停止容器
|
||||
docker-compose -f docker-compose.prod.yml stop
|
||||
|
||||
# 启动容器
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# 等待启动
|
||||
sleep 10
|
||||
|
||||
# 检查状态
|
||||
docker ps
|
||||
```
|
||||
|
||||
### 步骤3: 重启Nginx
|
||||
```bash
|
||||
# 测试配置
|
||||
nginx -t
|
||||
|
||||
# 重启服务
|
||||
systemctl restart nginx
|
||||
|
||||
# 检查状态
|
||||
systemctl status nginx
|
||||
```
|
||||
|
||||
### 步骤4: 验证服务
|
||||
```bash
|
||||
# 测试本地应用
|
||||
curl -I http://localhost:3000
|
||||
|
||||
# 检查端口监听
|
||||
netstat -tlnp | grep -E ":(3000|80|443)"
|
||||
|
||||
# 测试外部访问
|
||||
curl -I https://novalon.cn
|
||||
```
|
||||
|
||||
## ✅ 成功标志
|
||||
|
||||
执行成功后,您应该看到:
|
||||
|
||||
1. **Docker容器状态**:
|
||||
```
|
||||
CONTAINER ID NAMES STATUS PORTS
|
||||
xxxxx novalon-website Up 10 seconds 0.0.0.0:3000->3000/tcp
|
||||
```
|
||||
|
||||
2. **Nginx状态**:
|
||||
```
|
||||
Active: active (running)
|
||||
```
|
||||
|
||||
3. **本地应用响应**:
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
4. **外部访问响应**:
|
||||
```
|
||||
HTTP/2 200
|
||||
```
|
||||
|
||||
## ❌ 故障排查
|
||||
|
||||
### 如果Docker容器无法启动
|
||||
|
||||
```bash
|
||||
# 查看容器日志
|
||||
docker logs <container-name>
|
||||
|
||||
# 查看详细错误
|
||||
docker-compose -f docker-compose.prod.yml logs
|
||||
|
||||
# 检查配置文件
|
||||
cat docker-compose.prod.yml
|
||||
|
||||
# 尝试重新构建
|
||||
docker-compose -f docker-compose.prod.yml build --no-cache
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### 如果Nginx无法启动
|
||||
|
||||
```bash
|
||||
# 测试配置
|
||||
nginx -t
|
||||
|
||||
# 查看错误日志
|
||||
tail -50 /var/log/nginx/error.log
|
||||
|
||||
# 查看系统日志
|
||||
journalctl -u nginx -n 50
|
||||
|
||||
# 检查端口占用
|
||||
netstat -tlnp | grep -E ":(80|443)"
|
||||
```
|
||||
|
||||
### 如果应用仍然无响应
|
||||
|
||||
```bash
|
||||
# 检查应用日志
|
||||
docker logs -f <container-name>
|
||||
|
||||
# 检查应用进程
|
||||
docker exec <container-name> ps aux
|
||||
|
||||
# 检查应用端口
|
||||
docker exec <container-name> netstat -tlnp
|
||||
|
||||
# 重启应用容器
|
||||
docker restart <container-name>
|
||||
```
|
||||
|
||||
## 🔍 验证清单
|
||||
|
||||
执行完成后,请验证以下项目:
|
||||
|
||||
- [ ] Docker容器运行正常:`docker ps`
|
||||
- [ ] Nginx服务运行正常:`systemctl status nginx`
|
||||
- [ ] 本地应用响应正常:`curl -I http://localhost:3000`
|
||||
- [ ] 端口监听正常:`netstat -tlnp | grep -E ":(3000|80|443)"`
|
||||
- [ ] 外部访问正常:`curl -I https://novalon.cn`
|
||||
- [ ] Git服务器正常:`git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git`
|
||||
- [ ] CI服务器正常:`curl -I https://ci.f.novalon.cn`
|
||||
|
||||
## 📊 监控命令
|
||||
|
||||
### 实时监控服务状态
|
||||
```bash
|
||||
# 监控Docker容器
|
||||
watch -n 5 'docker ps'
|
||||
|
||||
# 监控Nginx状态
|
||||
watch -n 5 'systemctl status nginx'
|
||||
|
||||
# 监控系统资源
|
||||
watch -n 5 'free -h && df -h'
|
||||
```
|
||||
|
||||
### 查看实时日志
|
||||
```bash
|
||||
# Docker容器日志
|
||||
docker logs -f <container-name>
|
||||
|
||||
# Nginx错误日志
|
||||
tail -f /var/log/nginx/error.log
|
||||
|
||||
# Nginx访问日志
|
||||
tail -f /var/log/nginx/access.log
|
||||
|
||||
# 系统日志
|
||||
journalctl -f
|
||||
```
|
||||
|
||||
## 🆘 紧急情况
|
||||
|
||||
如果方案A无法解决问题,请:
|
||||
|
||||
1. **保存诊断日志**:
|
||||
```bash
|
||||
/tmp/remote-server-diagnosis.sh --full > /tmp/diagnosis-report.log
|
||||
```
|
||||
|
||||
2. **尝试方案B或C**:
|
||||
- 方案B: 清理资源并重启
|
||||
- 方案C: 完全重建
|
||||
|
||||
3. **联系支持**:
|
||||
- 提供诊断日志
|
||||
- 描述已尝试的步骤
|
||||
- 提供服务器访问信息
|
||||
|
||||
## 📝 执行记录
|
||||
|
||||
建议记录以下信息:
|
||||
|
||||
```
|
||||
执行时间: _______________
|
||||
执行人: _______________
|
||||
服务器IP: 139.155.109.62
|
||||
执行结果: _______________
|
||||
遇到的问题: _______________
|
||||
解决方案: _______________
|
||||
后续跟进: _______________
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**预计执行时间**: 2-5分钟
|
||||
**风险等级**: 低(仅重启服务,不修改配置)
|
||||
**回滚方案**: 如有问题,可再次重启或使用其他方案
|
||||
@@ -0,0 +1,239 @@
|
||||
# 生产环境连接超时排查指南
|
||||
|
||||
## 问题现象
|
||||
- **症状**: 生产环境无法访问,连接超时
|
||||
- **发生时间**: 刚刚发生
|
||||
- **影响范围**: novalon.cn, git.f.novalon.cn, ci.f.novalon.cn
|
||||
- **服务器IP**: 139.155.109.62
|
||||
|
||||
## 诊断结果(本地)
|
||||
|
||||
### ✅ 正常项
|
||||
- ✅ 本地网络连接正常
|
||||
- ✅ DNS解析成功
|
||||
- ✅ TCP端口连接成功(80, 443)
|
||||
|
||||
### ❌ 异常项
|
||||
- ❌ HTTP响应超时
|
||||
- ❌ 应用层无响应
|
||||
|
||||
## 根因分析
|
||||
|
||||
根据诊断结果,问题定位在**应用层**:
|
||||
|
||||
1. **网络层正常**: DNS解析、TCP连接都正常
|
||||
2. **应用层异常**: HTTP请求无响应
|
||||
|
||||
可能的原因:
|
||||
- Docker容器崩溃或停止
|
||||
- Nginx反向代理异常
|
||||
- 应用服务崩溃
|
||||
- 服务器资源耗尽(CPU/内存/磁盘)
|
||||
|
||||
## 排查步骤
|
||||
|
||||
### 步骤1: SSH登录服务器
|
||||
|
||||
```bash
|
||||
# 登录生产服务器
|
||||
ssh root@139.155.109.62
|
||||
# 或
|
||||
ssh user@139.155.109.62
|
||||
```
|
||||
|
||||
### 步骤2: 上传并运行诊断脚本
|
||||
|
||||
```bash
|
||||
# 方法1: 从本地上传脚本
|
||||
scp scripts/remote-server-diagnosis.sh root@139.155.109.62:/tmp/
|
||||
|
||||
# 方法2: 直接在服务器上创建脚本
|
||||
# 复制 remote-server-diagnosis.sh 的内容到服务器
|
||||
|
||||
# 运行诊断脚本
|
||||
chmod +x /tmp/remote-server-diagnosis.sh
|
||||
/tmp/remote-server-diagnosis.sh --full
|
||||
```
|
||||
|
||||
### 步骤3: 手动排查(如果脚本无法运行)
|
||||
|
||||
#### 3.1 检查系统资源
|
||||
```bash
|
||||
# 查看CPU和内存
|
||||
top -bn1 | head -20
|
||||
|
||||
# 查看磁盘
|
||||
df -h
|
||||
|
||||
# 查看内存
|
||||
free -h
|
||||
|
||||
# 查看系统负载
|
||||
uptime
|
||||
```
|
||||
|
||||
#### 3.2 检查Docker容器
|
||||
```bash
|
||||
# 查看容器状态
|
||||
docker ps -a
|
||||
|
||||
# 查看容器日志
|
||||
docker logs <container-name>
|
||||
|
||||
# 查看容器资源使用
|
||||
docker stats --no-stream
|
||||
|
||||
# 重启容器
|
||||
docker restart <container-name>
|
||||
```
|
||||
|
||||
#### 3.3 检查Nginx
|
||||
```bash
|
||||
# 查看Nginx状态
|
||||
systemctl status nginx
|
||||
|
||||
# 测试Nginx配置
|
||||
nginx -t
|
||||
|
||||
# 重启Nginx
|
||||
systemctl restart nginx
|
||||
|
||||
# 查看Nginx日志
|
||||
tail -50 /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
#### 3.4 检查应用服务
|
||||
```bash
|
||||
# 查看Node.js进程
|
||||
ps aux | grep node
|
||||
|
||||
# 查看端口占用
|
||||
netstat -tlnp | grep -E ":(3000|80|443)"
|
||||
|
||||
# 测试本地应用
|
||||
curl -I http://localhost:3000
|
||||
```
|
||||
|
||||
## 快速修复方案
|
||||
|
||||
### 方案1: 重启所有服务
|
||||
```bash
|
||||
# 重启Docker容器
|
||||
cd /path/to/project
|
||||
docker-compose -f docker-compose.prod.yml restart
|
||||
|
||||
# 重启Nginx
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# 检查服务状态
|
||||
docker ps
|
||||
systemctl status nginx
|
||||
```
|
||||
|
||||
### 方案2: 清理资源并重启
|
||||
```bash
|
||||
# 清理Docker资源
|
||||
docker system prune -a -f
|
||||
|
||||
# 清理日志
|
||||
sudo journalctl --vacuum-time=3d
|
||||
|
||||
# 重启服务
|
||||
sudo systemctl restart docker
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### 方案3: 完全重建
|
||||
```bash
|
||||
# 停止所有容器
|
||||
docker-compose -f docker-compose.prod.yml down
|
||||
|
||||
# 清理所有资源
|
||||
docker system prune -a -f --volumes
|
||||
|
||||
# 重新构建和启动
|
||||
docker-compose -f docker-compose.prod.yml build --no-cache
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# 重启Nginx
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
## 验证修复
|
||||
|
||||
### 本地验证
|
||||
```bash
|
||||
# 测试网站访问
|
||||
curl -I https://novalon.cn
|
||||
|
||||
# 测试Git服务器
|
||||
git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git
|
||||
|
||||
# 测试CI服务器
|
||||
curl -I https://ci.f.novalon.cn
|
||||
```
|
||||
|
||||
### 服务器验证
|
||||
```bash
|
||||
# 测试本地应用
|
||||
curl -I http://localhost:3000
|
||||
|
||||
# 检查容器状态
|
||||
docker ps
|
||||
|
||||
# 检查Nginx状态
|
||||
systemctl status nginx
|
||||
|
||||
# 检查端口监听
|
||||
netstat -tlnp | grep -E ":(3000|80|443)"
|
||||
```
|
||||
|
||||
## 监控和预防
|
||||
|
||||
### 设置监控
|
||||
```bash
|
||||
# 安装监控工具
|
||||
docker run -d --name=monitor \
|
||||
--restart=unless-stopped \
|
||||
-p 9090:9090 \
|
||||
prom/prometheus
|
||||
|
||||
# 设置日志轮转
|
||||
sudo nano /etc/logrotate.d/nginx
|
||||
```
|
||||
|
||||
### 定期清理
|
||||
```bash
|
||||
# 创建定时清理脚本
|
||||
cat > /etc/cron.daily/docker-cleanup << 'EOF'
|
||||
#!/bin/bash
|
||||
docker system prune -f
|
||||
journalctl --vacuum-time=7d
|
||||
EOF
|
||||
|
||||
chmod +x /etc/cron.daily/docker-cleanup
|
||||
```
|
||||
|
||||
## 紧急联系
|
||||
|
||||
如果以上方法都无法解决问题,请:
|
||||
|
||||
1. 保存诊断日志:
|
||||
```bash
|
||||
/tmp/remote-server-diagnosis.sh --full > /tmp/diagnosis-report.log
|
||||
```
|
||||
|
||||
2. 联系服务器提供商检查网络和硬件
|
||||
|
||||
3. 检查是否遭受DDoS攻击:
|
||||
```bash
|
||||
netstat -an | grep :80 | wc -l
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Docker镜像清理脚本](./docker-cleanup.sh)
|
||||
- [网络诊断脚本](./network-diagnosis.sh)
|
||||
- [生产环境诊断脚本](./production-diagnosis.sh)
|
||||
- [远程服务器诊断脚本](./remote-server-diagnosis.sh)
|
||||
@@ -4,6 +4,51 @@ const { TextEncoder, TextDecoder } = require('util');
|
||||
global.TextEncoder = TextEncoder;
|
||||
global.TextDecoder = TextDecoder;
|
||||
|
||||
if (typeof global.Request === 'undefined') {
|
||||
global.Request = class Request {
|
||||
constructor(input, init = {}) {
|
||||
this.url = typeof input === 'string' ? input : input.url;
|
||||
this.method = init.method || 'GET';
|
||||
this.headers = new Map(Object.entries(init.headers || {}));
|
||||
this.body = init.body;
|
||||
}
|
||||
async json() {
|
||||
return typeof this.body === 'string' ? JSON.parse(this.body) : this.body;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof global.Response === 'undefined') {
|
||||
global.Response = class Response {
|
||||
constructor(body, init = {}) {
|
||||
this.body = body;
|
||||
this.status = init.status || 200;
|
||||
this.statusText = init.statusText || 'OK';
|
||||
this.headers = new Map(Object.entries(init.headers || {}));
|
||||
}
|
||||
async json() {
|
||||
return typeof this.body === 'string' ? JSON.parse(this.body) : this.body;
|
||||
}
|
||||
async text() {
|
||||
return typeof this.body === 'string' ? this.body : JSON.stringify(this.body);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof global.Headers === 'undefined') {
|
||||
global.Headers = class Headers {
|
||||
constructor(init = {}) {
|
||||
this._headers = new Map(Object.entries(init));
|
||||
}
|
||||
get(name) {
|
||||
return this._headers.get(name);
|
||||
}
|
||||
set(name, value) {
|
||||
this._headers.set(name, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const { setupAllMocks } = require('./src/__mocks__/shared-mocks');
|
||||
|
||||
setupAllMocks();
|
||||
|
||||
Executable
+332
@@ -0,0 +1,332 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Docker镜像清理脚本
|
||||
# 用途:检查生产环境Docker镜像使用情况并清理未使用的镜像
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 分隔线
|
||||
separator() {
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
# 检查Docker是否运行
|
||||
check_docker() {
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
log_error "Docker未运行,请先启动Docker"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Docker运行正常"
|
||||
}
|
||||
|
||||
# 显示磁盘使用情况
|
||||
show_disk_usage() {
|
||||
separator
|
||||
log_info "磁盘使用情况"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "系统磁盘使用:"
|
||||
df -h | grep -E "Filesystem|/$|/home|/var"
|
||||
|
||||
echo ""
|
||||
log_info "Docker数据目录使用:"
|
||||
docker system df
|
||||
|
||||
echo ""
|
||||
log_info "Docker镜像详情:"
|
||||
docker system df -v | grep -A 100 "Images space usage"
|
||||
}
|
||||
|
||||
# 列出所有镜像
|
||||
list_images() {
|
||||
separator
|
||||
log_info "Docker镜像列表"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "所有镜像(按大小排序):"
|
||||
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}" | head -20
|
||||
|
||||
echo ""
|
||||
log_info "悬空镜像(<none>):"
|
||||
docker images -f "dangling=true" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"
|
||||
|
||||
echo ""
|
||||
log_info "未使用的镜像(没有被任何容器使用):"
|
||||
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" | while read image id; do
|
||||
if ! docker ps -a --format "{{.Image}}" | grep -q "$id"; then
|
||||
echo "$image (ID: $id)"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 列出所有容器
|
||||
list_containers() {
|
||||
separator
|
||||
log_info "Docker容器列表"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "运行中的容器:"
|
||||
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Size}}"
|
||||
|
||||
echo ""
|
||||
log_info "已停止的容器:"
|
||||
docker ps -f "status=exited" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Size}}"
|
||||
}
|
||||
|
||||
# 分析镜像使用情况
|
||||
analyze_images() {
|
||||
separator
|
||||
log_info "镜像使用分析"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "正在分析镜像使用情况..."
|
||||
|
||||
# 统计信息
|
||||
TOTAL_IMAGES=$(docker images -q | wc -l)
|
||||
DANGLING_IMAGES=$(docker images -f "dangling=true" -q | wc -l)
|
||||
TOTAL_SIZE=$(docker images --format "{{.Size}}" | grep -oE '[0-9]+GB|[0-9]+MB' | head -1)
|
||||
|
||||
log_info "总镜像数: $TOTAL_IMAGES"
|
||||
log_info "悬空镜像数: $DANGLING_IMAGES"
|
||||
log_info "镜像总大小约: $TOTAL_SIZE"
|
||||
|
||||
echo ""
|
||||
log_info "可以清理的内容:"
|
||||
echo " 1. 悬空镜像(<none>标签): $DANGLING_IMAGES 个"
|
||||
echo " 2. 已停止的容器"
|
||||
echo " 3. 未使用的网络"
|
||||
echo " 4. 构建缓存"
|
||||
}
|
||||
|
||||
# 清理悬空镜像
|
||||
clean_dangling_images() {
|
||||
separator
|
||||
log_info "清理悬空镜像"
|
||||
separator
|
||||
|
||||
DANGLING_COUNT=$(docker images -f "dangling=true" -q | wc -l)
|
||||
|
||||
if [ "$DANGLING_COUNT" -eq 0 ]; then
|
||||
log_success "没有悬空镜像需要清理"
|
||||
return
|
||||
fi
|
||||
|
||||
log_warning "发现 $DANGLING_COUNT 个悬空镜像"
|
||||
read -p "是否清理悬空镜像?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理悬空镜像..."
|
||||
docker image prune -f
|
||||
log_success "悬空镜像清理完成"
|
||||
else
|
||||
log_info "跳过悬空镜像清理"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理未使用的镜像
|
||||
clean_unused_images() {
|
||||
separator
|
||||
log_info "清理未使用的镜像"
|
||||
separator
|
||||
|
||||
log_warning "这将删除所有没有被容器使用的镜像"
|
||||
read -p "是否清理未使用的镜像?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理未使用的镜像..."
|
||||
docker image prune -a -f
|
||||
log_success "未使用镜像清理完成"
|
||||
else
|
||||
log_info "跳过未使用镜像清理"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理已停止的容器
|
||||
clean_stopped_containers() {
|
||||
separator
|
||||
log_info "清理已停止的容器"
|
||||
separator
|
||||
|
||||
STOPPED_COUNT=$(docker ps -f "status=exited" -q | wc -l)
|
||||
|
||||
if [ "$STOPPED_COUNT" -eq 0 ]; then
|
||||
log_success "没有已停止的容器需要清理"
|
||||
return
|
||||
fi
|
||||
|
||||
log_warning "发现 $STOPPED_COUNT 个已停止的容器"
|
||||
read -p "是否清理已停止的容器?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理已停止的容器..."
|
||||
docker container prune -f
|
||||
log_success "已停止容器清理完成"
|
||||
else
|
||||
log_info "跳过已停止容器清理"
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理构建缓存
|
||||
clean_build_cache() {
|
||||
separator
|
||||
log_info "清理构建缓存"
|
||||
separator
|
||||
|
||||
log_warning "这将删除所有构建缓存"
|
||||
read -p "是否清理构建缓存?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理构建缓存..."
|
||||
docker builder prune -a -f
|
||||
log_success "构建缓存清理完成"
|
||||
else
|
||||
log_info "跳过构建缓存清理"
|
||||
fi
|
||||
}
|
||||
|
||||
# 一键清理
|
||||
clean_all() {
|
||||
separator
|
||||
log_info "一键清理(安全模式)"
|
||||
separator
|
||||
|
||||
log_warning "将执行以下清理操作:"
|
||||
echo " 1. 清理悬空镜像"
|
||||
echo " 2. 清理已停止的容器"
|
||||
echo " 3. 清理未使用的网络"
|
||||
echo " 4. 清理构建缓存"
|
||||
echo ""
|
||||
log_warning "不会删除正在使用的镜像"
|
||||
|
||||
read -p "是否继续?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "开始一键清理..."
|
||||
|
||||
log_info "清理悬空镜像..."
|
||||
docker image prune -f
|
||||
|
||||
log_info "清理已停止的容器..."
|
||||
docker container prune -f
|
||||
|
||||
log_info "清理未使用的网络..."
|
||||
docker network prune -f
|
||||
|
||||
log_info "清理构建缓存..."
|
||||
docker builder prune -f
|
||||
|
||||
log_success "一键清理完成"
|
||||
else
|
||||
log_info "取消一键清理"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示清理效果
|
||||
show_cleanup_result() {
|
||||
separator
|
||||
log_info "清理后的磁盘使用情况"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
docker system df
|
||||
|
||||
echo ""
|
||||
log_info "磁盘空间变化:"
|
||||
df -h | grep -E "Filesystem|/$"
|
||||
}
|
||||
|
||||
# 主菜单
|
||||
main_menu() {
|
||||
clear
|
||||
separator
|
||||
echo "Docker镜像清理工具"
|
||||
separator
|
||||
echo ""
|
||||
echo "1. 查看磁盘使用情况"
|
||||
echo "2. 列出所有镜像"
|
||||
echo "3. 列出所有容器"
|
||||
echo "4. 分析镜像使用情况"
|
||||
echo "5. 清理悬空镜像(安全)"
|
||||
echo "6. 清理未使用的镜像(谨慎)"
|
||||
echo "7. 清理已停止的容器"
|
||||
echo "8. 清理构建缓存"
|
||||
echo "9. 一键清理(安全模式)"
|
||||
echo "0. 退出"
|
||||
echo ""
|
||||
read -p "请选择操作 (0-9): " choice
|
||||
|
||||
case $choice in
|
||||
1) show_disk_usage ;;
|
||||
2) list_images ;;
|
||||
3) list_containers ;;
|
||||
4) analyze_images ;;
|
||||
5) clean_dangling_images ;;
|
||||
6) clean_unused_images ;;
|
||||
7) clean_stopped_containers ;;
|
||||
8) clean_build_cache ;;
|
||||
9) clean_all ;;
|
||||
0)
|
||||
log_info "退出程序"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "无效选择"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
read -p "按回车键继续..."
|
||||
main_menu
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "Docker镜像清理工具启动"
|
||||
check_docker
|
||||
|
||||
if [ "$1" = "--auto" ]; then
|
||||
log_info "自动模式:执行安全清理"
|
||||
clean_dangling_images
|
||||
clean_stopped_containers
|
||||
clean_build_cache
|
||||
show_cleanup_result
|
||||
else
|
||||
main_menu
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 方案A: 服务重启脚本
|
||||
# 用途:快速重启Docker容器和Nginx服务
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
separator() {
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
# 记录日志
|
||||
LOG_FILE="/tmp/service-restart-$(date +%Y%m%d_%H%M%S).log"
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
separator
|
||||
echo "方案A: 服务重启脚本"
|
||||
echo "执行时间: $(date)"
|
||||
separator
|
||||
|
||||
# 1. 检查当前状态
|
||||
log_info "步骤1: 检查当前服务状态"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "当前Docker容器状态:"
|
||||
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
echo ""
|
||||
log_info "当前Nginx状态:"
|
||||
systemctl status nginx --no-pager | head -10
|
||||
|
||||
echo ""
|
||||
log_info "当前系统资源:"
|
||||
echo "CPU和内存:"
|
||||
top -bn1 | head -5
|
||||
echo ""
|
||||
echo "磁盘使用:"
|
||||
df -h | grep -E "Filesystem|/$"
|
||||
|
||||
# 2. 查找docker-compose文件
|
||||
log_info "步骤2: 查找docker-compose配置文件"
|
||||
separator
|
||||
|
||||
POSSIBLE_PATHS=(
|
||||
"/opt/novalon-website"
|
||||
"/var/www/novalon-website"
|
||||
"/home/novalon-website"
|
||||
"/root/novalon-website"
|
||||
"/srv/novalon-website"
|
||||
"."
|
||||
)
|
||||
|
||||
COMPOSE_FILE=""
|
||||
PROJECT_DIR=""
|
||||
|
||||
for path in "${POSSIBLE_PATHS[@]}"; do
|
||||
if [ -f "$path/docker-compose.prod.yml" ]; then
|
||||
PROJECT_DIR="$path"
|
||||
COMPOSE_FILE="$path/docker-compose.prod.yml"
|
||||
log_success "找到docker-compose.prod.yml: $COMPOSE_FILE"
|
||||
break
|
||||
elif [ -f "$path/docker-compose.yml" ]; then
|
||||
PROJECT_DIR="$path"
|
||||
COMPOSE_FILE="$path/docker-compose.yml"
|
||||
log_success "找到docker-compose.yml: $COMPOSE_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$COMPOSE_FILE" ]; then
|
||||
log_error "未找到docker-compose配置文件"
|
||||
log_info "请手动指定项目目录:"
|
||||
read -p "请输入项目目录路径: " PROJECT_DIR
|
||||
|
||||
if [ -f "$PROJECT_DIR/docker-compose.prod.yml" ]; then
|
||||
COMPOSE_FILE="$PROJECT_DIR/docker-compose.prod.yml"
|
||||
elif [ -f "$PROJECT_DIR/docker-compose.yml" ]; then
|
||||
COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml"
|
||||
else
|
||||
log_error "指定目录中未找到docker-compose文件"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "项目目录: $PROJECT_DIR"
|
||||
log_info "配置文件: $COMPOSE_FILE"
|
||||
|
||||
# 3. 重启Docker容器
|
||||
log_info "步骤3: 重启Docker容器"
|
||||
separator
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo ""
|
||||
log_info "停止Docker容器..."
|
||||
docker-compose -f "$COMPOSE_FILE" stop
|
||||
|
||||
echo ""
|
||||
log_info "启动Docker容器..."
|
||||
docker-compose -f "$COMPOSE_FILE" up -d
|
||||
|
||||
echo ""
|
||||
log_info "等待服务启动(10秒)..."
|
||||
sleep 10
|
||||
|
||||
echo ""
|
||||
log_info "检查容器状态:"
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
# 检查容器是否正常运行
|
||||
RUNNING_CONTAINERS=$(docker ps -q | wc -l)
|
||||
STOPPED_CONTAINERS=$(docker ps -f "status=exited" -q | wc -l)
|
||||
|
||||
if [ "$STOPPED_CONTAINERS" -gt 0 ]; then
|
||||
log_warning "发现已停止的容器:"
|
||||
docker ps -f "status=exited" --format "table {{.Names}}\t{{.Status}}"
|
||||
|
||||
echo ""
|
||||
log_info "查看容器日志:"
|
||||
for container in $(docker ps -f "status=exited" -q); do
|
||||
CONTAINER_NAME=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
|
||||
echo ""
|
||||
log_info "容器: $CONTAINER_NAME"
|
||||
docker logs --tail 30 $container
|
||||
done
|
||||
else
|
||||
log_success "所有容器运行正常"
|
||||
fi
|
||||
|
||||
# 4. 重启Nginx
|
||||
log_info "步骤4: 重启Nginx服务"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "测试Nginx配置..."
|
||||
if nginx -t; then
|
||||
log_success "Nginx配置正确"
|
||||
else
|
||||
log_error "Nginx配置错误,请检查配置文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "重启Nginx..."
|
||||
systemctl restart nginx
|
||||
|
||||
echo ""
|
||||
log_info "检查Nginx状态:"
|
||||
systemctl status nginx --no-pager | head -15
|
||||
|
||||
if systemctl is-active --quiet nginx; then
|
||||
log_success "Nginx运行正常"
|
||||
else
|
||||
log_error "Nginx启动失败"
|
||||
journalctl -u nginx --no-pager | tail -20
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 5. 验证服务
|
||||
log_info "步骤5: 验证服务状态"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "测试本地应用连接..."
|
||||
if curl -I --connect-timeout 5 http://localhost:3000 2>&1 | grep -q "HTTP"; then
|
||||
log_success "应用服务响应正常"
|
||||
curl -I http://localhost:3000 2>&1 | head -10
|
||||
else
|
||||
log_error "应用服务无响应"
|
||||
log_info "查看应用日志:"
|
||||
docker logs --tail 50 $(docker ps -q | head -1)
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "检查端口监听:"
|
||||
netstat -tlnp | grep -E ":(3000|80|443)" || ss -tlnp | grep -E ":(3000|80|443)"
|
||||
|
||||
echo ""
|
||||
log_info "测试外部访问..."
|
||||
if curl -I --connect-timeout 10 https://novalon.cn 2>&1 | grep -q "HTTP"; then
|
||||
log_success "外部访问正常"
|
||||
curl -I https://novalon.cn 2>&1 | head -10
|
||||
else
|
||||
log_warning "外部访问仍可能需要等待DNS传播或CDN刷新"
|
||||
fi
|
||||
|
||||
# 6. 显示最终状态
|
||||
separator
|
||||
log_info "服务重启完成"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_success "执行摘要:"
|
||||
echo " ✅ Docker容器已重启"
|
||||
echo " ✅ Nginx服务已重启"
|
||||
echo " ✅ 服务状态已验证"
|
||||
|
||||
echo ""
|
||||
log_info "详细日志已保存到: $LOG_FILE"
|
||||
|
||||
echo ""
|
||||
log_warning "后续建议:"
|
||||
echo " 1. 监控服务状态: watch -n 5 'docker ps && systemctl status nginx'"
|
||||
echo " 2. 查看实时日志: docker logs -f \$(docker ps -q | head -1)"
|
||||
echo " 3. 检查应用日志: tail -f /var/log/nginx/error.log"
|
||||
echo " 4. 验证外部访问: curl -I https://novalon.cn"
|
||||
|
||||
echo ""
|
||||
log_info "如果问题仍然存在,请运行完整诊断:"
|
||||
echo " /tmp/remote-server-diagnosis.sh --full"
|
||||
|
||||
separator
|
||||
Executable
+298
@@ -0,0 +1,298 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 网络诊断脚本
|
||||
# 用途:诊断git和CI无法访问的问题
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
separator() {
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
# 测试网络连通性
|
||||
test_connectivity() {
|
||||
separator
|
||||
log_info "测试网络连通性"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "测试DNS解析..."
|
||||
|
||||
# 测试Git服务器
|
||||
log_info "Git服务器 (git.f.novalon.cn):"
|
||||
if nslookup git.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "DNS解析成功"
|
||||
nslookup git.f.novalon.cn | grep "Address" | tail -n +2
|
||||
else
|
||||
log_error "DNS解析失败"
|
||||
fi
|
||||
|
||||
# 测试CI服务器
|
||||
log_info "CI服务器 (ci.f.novalon.cn):"
|
||||
if nslookup ci.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "DNS解析成功"
|
||||
nslookup ci.f.novalon.cn | grep "Address" | tail -n +2
|
||||
else
|
||||
log_error "DNS解析失败"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "测试网络连接..."
|
||||
|
||||
# 测试HTTPS连接
|
||||
log_info "Git服务器HTTPS连接:"
|
||||
if curl -I --connect-timeout 5 https://git.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "HTTPS连接成功"
|
||||
else
|
||||
log_error "HTTPS连接失败"
|
||||
fi
|
||||
|
||||
log_info "CI服务器HTTPS连接:"
|
||||
if curl -I --connect-timeout 5 https://ci.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "HTTPS连接成功"
|
||||
else
|
||||
log_error "HTTPS连接失败"
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试Git连接
|
||||
test_git_connection() {
|
||||
separator
|
||||
log_info "测试Git连接"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "Git配置:"
|
||||
git config --global --list | grep -E "user|http|https" || log_warning "未找到Git全局配置"
|
||||
|
||||
echo ""
|
||||
log_info "测试Git SSH连接:"
|
||||
if ssh -T git@git.f.novalon.cn -o ConnectTimeout=5 2>&1 | grep -q "welcome\|authenticated"; then
|
||||
log_success "SSH连接成功"
|
||||
else
|
||||
log_warning "SSH连接失败(可能未配置SSH密钥)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "测试Git HTTPS连接:"
|
||||
if git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git > /dev/null 2>&1; then
|
||||
log_success "HTTPS连接成功"
|
||||
else
|
||||
log_error "HTTPS连接失败"
|
||||
log_info "错误详情:"
|
||||
git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git 2>&1 | head -10
|
||||
fi
|
||||
}
|
||||
|
||||
# 测试CI连接
|
||||
test_ci_connection() {
|
||||
separator
|
||||
log_info "测试CI连接"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "测试CI Web界面:"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://ci.f.novalon.cn)
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then
|
||||
log_success "CI Web界面可访问 (HTTP $HTTP_CODE)"
|
||||
else
|
||||
log_error "CI Web界面不可访问 (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "测试CI API:"
|
||||
if curl -s --connect-timeout 5 https://ci.f.novalon.cn/api/info > /dev/null 2>&1; then
|
||||
log_success "CI API可访问"
|
||||
else
|
||||
log_warning "CI API不可访问"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查防火墙和代理
|
||||
check_network_config() {
|
||||
separator
|
||||
log_info "检查网络配置"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "检查防火墙状态:"
|
||||
if command -v ufw > /dev/null; then
|
||||
sudo ufw status
|
||||
elif command -v firewall-cmd > /dev/null; then
|
||||
sudo firewall-cmd --state
|
||||
else
|
||||
log_warning "未检测到防火墙"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "检查HTTP代理设置:"
|
||||
env | grep -i proxy || log_info "未设置代理"
|
||||
|
||||
echo ""
|
||||
log_info "检查SSL证书:"
|
||||
if echo | openssl s_client -connect git.f.novalon.cn:443 2>&1 | grep -q "Verify return code: 0"; then
|
||||
log_success "Git服务器SSL证书有效"
|
||||
else
|
||||
log_warning "Git服务器SSL证书可能有问题"
|
||||
fi
|
||||
|
||||
if echo | openssl s_client -connect ci.f.novalon.cn:443 2>&1 | grep -q "Verify return code: 0"; then
|
||||
log_success "CI服务器SSL证书有效"
|
||||
else
|
||||
log_warning "CI服务器SSL证书可能有问题"
|
||||
fi
|
||||
}
|
||||
|
||||
# 提供解决方案
|
||||
suggest_solutions() {
|
||||
separator
|
||||
log_info "建议解决方案"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_warning "如果DNS解析失败:"
|
||||
echo " 1. 检查DNS服务器配置: cat /etc/resolv.conf"
|
||||
echo " 2. 尝试使用公共DNS: sudo echo 'nameserver 8.8.8.8' >> /etc/resolv.conf"
|
||||
echo " 3. 检查网络连接: ping 8.8.8.8"
|
||||
|
||||
echo ""
|
||||
log_warning "如果HTTPS连接失败:"
|
||||
echo " 1. 检查防火墙规则: sudo ufw status"
|
||||
echo " 2. 检查代理设置: env | grep -i proxy"
|
||||
echo " 3. 更新CA证书: sudo update-ca-certificates"
|
||||
|
||||
echo ""
|
||||
log_warning "如果Git连接超时:"
|
||||
echo " 1. 增加超时时间: git config --global http.lowSpeedLimit 0"
|
||||
echo " 2. 增加超时时间: git config --global http.postBuffer 524288000"
|
||||
echo " 3. 使用SSH代替HTTPS: git remote set-url origin git@git.f.novalon.cn:novalon/novalon-website.git"
|
||||
|
||||
echo ""
|
||||
log_warning "如果CI无法访问:"
|
||||
echo " 1. 检查CI服务状态: sudo systemctl status woodpecker-agent"
|
||||
echo " 2. 检查CI日志: sudo journalctl -u woodpecker-agent -f"
|
||||
echo " 3. 重启CI服务: sudo systemctl restart woodpecker-agent"
|
||||
}
|
||||
|
||||
# 快速诊断
|
||||
quick_diagnosis() {
|
||||
log_info "执行快速诊断..."
|
||||
|
||||
# 测试基本网络连接
|
||||
if ping -c 1 8.8.8.8 > /dev/null 2>&1; then
|
||||
log_success "基本网络连接正常"
|
||||
else
|
||||
log_error "基本网络连接失败,请检查网络配置"
|
||||
return
|
||||
fi
|
||||
|
||||
# 测试DNS
|
||||
if nslookup git.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "DNS解析正常"
|
||||
else
|
||||
log_error "DNS解析失败"
|
||||
fi
|
||||
|
||||
# 测试HTTPS
|
||||
if curl -I --connect-timeout 5 https://git.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "Git服务器可访问"
|
||||
else
|
||||
log_error "Git服务器不可访问"
|
||||
fi
|
||||
|
||||
if curl -I --connect-timeout 5 https://ci.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "CI服务器可访问"
|
||||
else
|
||||
log_error "CI服务器不可访问"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主菜单
|
||||
main_menu() {
|
||||
clear
|
||||
separator
|
||||
echo "网络诊断工具 - Git和CI连接问题排查"
|
||||
separator
|
||||
echo ""
|
||||
echo "1. 快速诊断"
|
||||
echo "2. 测试网络连通性"
|
||||
echo "3. 测试Git连接"
|
||||
echo "4. 测试CI连接"
|
||||
echo "5. 检查网络配置"
|
||||
echo "6. 显示建议解决方案"
|
||||
echo "7. 完整诊断(所有测试)"
|
||||
echo "0. 退出"
|
||||
echo ""
|
||||
read -p "请选择操作 (0-7): " choice
|
||||
|
||||
case $choice in
|
||||
1) quick_diagnosis ;;
|
||||
2) test_connectivity ;;
|
||||
3) test_git_connection ;;
|
||||
4) test_ci_connection ;;
|
||||
5) check_network_config ;;
|
||||
6) suggest_solutions ;;
|
||||
7)
|
||||
quick_diagnosis
|
||||
echo ""
|
||||
test_connectivity
|
||||
echo ""
|
||||
test_git_connection
|
||||
echo ""
|
||||
test_ci_connection
|
||||
echo ""
|
||||
check_network_config
|
||||
echo ""
|
||||
suggest_solutions
|
||||
;;
|
||||
0)
|
||||
log_info "退出程序"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "无效选择"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
read -p "按回车键继续..."
|
||||
main_menu
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "网络诊断工具启动"
|
||||
|
||||
if [ "$1" = "--quick" ]; then
|
||||
quick_diagnosis
|
||||
else
|
||||
main_menu
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+283
@@ -0,0 +1,283 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 生产环境连接超时诊断脚本
|
||||
# 用途:系统化排查生产环境连接超时问题
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
separator() {
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
# 1. 收集错误信息
|
||||
collect_error_info() {
|
||||
separator
|
||||
log_info "Phase 1.1: 收集错误信息"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "测试基本网络连接..."
|
||||
|
||||
# 测试本地网络
|
||||
if ping -c 3 8.8.8.8 > /dev/null 2>&1; then
|
||||
log_success "本地网络正常"
|
||||
else
|
||||
log_error "本地网络异常"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "测试DNS解析..."
|
||||
|
||||
# 测试生产服务器域名
|
||||
if nslookup novalon.cn > /dev/null 2>&1; then
|
||||
log_success "novalon.cn DNS解析成功"
|
||||
PROD_IP=$(nslookup novalon.cn | grep "Address" | tail -1 | awk '{print $2}')
|
||||
log_info "生产服务器IP: $PROD_IP"
|
||||
else
|
||||
log_error "novalon.cn DNS解析失败"
|
||||
fi
|
||||
|
||||
# 测试Git服务器
|
||||
if nslookup git.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "git.f.novalon.cn DNS解析成功"
|
||||
GIT_IP=$(nslookup git.f.novalon.cn | grep "Address" | tail -1 | awk '{print $2}')
|
||||
log_info "Git服务器IP: $GIT_IP"
|
||||
else
|
||||
log_error "git.f.novalon.cn DNS解析失败"
|
||||
fi
|
||||
|
||||
# 测试CI服务器
|
||||
if nslookup ci.f.novalon.cn > /dev/null 2>&1; then
|
||||
log_success "ci.f.novalon.cn DNS解析成功"
|
||||
CI_IP=$(nslookup ci.f.novalon.cn | grep "Address" | tail -1 | awk '{print $2}')
|
||||
log_info "CI服务器IP: $CI_IP"
|
||||
else
|
||||
log_error "ci.f.novalon.cn DNS解析失败"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "测试TCP连接..."
|
||||
|
||||
# 测试HTTP端口
|
||||
if nc -zv -w 5 novalon.cn 80 2>&1 | grep -q "succeeded"; then
|
||||
log_success "novalon.cn:80 连接成功"
|
||||
else
|
||||
log_error "novalon.cn:80 连接超时"
|
||||
fi
|
||||
|
||||
# 测试HTTPS端口
|
||||
if nc -zv -w 5 novalon.cn 443 2>&1 | grep -q "succeeded"; then
|
||||
log_success "novalon.cn:443 连接成功"
|
||||
else
|
||||
log_error "novalon.cn:443 连接超时"
|
||||
fi
|
||||
|
||||
# 测试Git服务器
|
||||
if nc -zv -w 5 git.f.novalon.cn 443 2>&1 | grep -q "succeeded"; then
|
||||
log_success "git.f.novalon.cn:443 连接成功"
|
||||
else
|
||||
log_error "git.f.novalon.cn:443 连接超时"
|
||||
fi
|
||||
|
||||
# 测试CI服务器
|
||||
if nc -zv -w 5 ci.f.novalon.cn 443 2>&1 | grep -q "succeeded"; then
|
||||
log_success "ci.f.novalon.cn:443 连接成功"
|
||||
else
|
||||
log_error "ci.f.novalon.cn:443 连接超时"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "测试HTTP响应..."
|
||||
|
||||
# 测试HTTP请求
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 15 https://novalon.cn 2>&1)
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "301" ] || [ "$HTTP_CODE" = "302" ]; then
|
||||
log_success "novalon.cn HTTP响应正常 (HTTP $HTTP_CODE)"
|
||||
else
|
||||
log_error "novalon.cn HTTP响应异常 (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
|
||||
# 测试Git服务器
|
||||
if curl -I --connect-timeout 10 --max-time 15 https://git.f.novalon.cn 2>&1 | grep -q "HTTP"; then
|
||||
log_success "git.f.novalon.cn HTTP响应正常"
|
||||
else
|
||||
log_error "git.f.novalon.cn HTTP响应超时"
|
||||
fi
|
||||
|
||||
# 测试CI服务器
|
||||
if curl -I --connect-timeout 10 --max-time 15 https://ci.f.novalon.cn 2>&1 | grep -q "HTTP"; then
|
||||
log_success "ci.f.novalon.cn HTTP响应正常"
|
||||
else
|
||||
log_error "ci.f.novalon.cn HTTP响应超时"
|
||||
fi
|
||||
}
|
||||
|
||||
# 2. 检查最近的变更
|
||||
check_recent_changes() {
|
||||
separator
|
||||
log_info "Phase 1.2: 检查最近的变更"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "检查本地Git状态..."
|
||||
|
||||
# 检查当前分支
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
log_info "当前分支: $CURRENT_BRANCH"
|
||||
|
||||
# 检查未提交的更改
|
||||
if git status --porcelain | grep -q .; then
|
||||
log_warning "存在未提交的更改:"
|
||||
git status --short
|
||||
else
|
||||
log_success "工作区干净"
|
||||
fi
|
||||
|
||||
# 检查最近的提交
|
||||
echo ""
|
||||
log_info "最近5次提交:"
|
||||
git log --oneline -5
|
||||
|
||||
# 检查最近的推送
|
||||
echo ""
|
||||
log_info "检查远程仓库连接..."
|
||||
if git remote -v | grep -q "git.f.novalon.cn"; then
|
||||
log_info "远程仓库: git.f.novalon.cn"
|
||||
|
||||
# 尝试连接远程仓库
|
||||
if git ls-remote --heads origin > /dev/null 2>&1; then
|
||||
log_success "远程仓库连接成功"
|
||||
else
|
||||
log_error "远程仓库连接失败"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 3. 追踪数据流
|
||||
trace_data_flow() {
|
||||
separator
|
||||
log_info "Phase 1.3: 追踪数据流"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "网络路由追踪..."
|
||||
|
||||
# 追踪到生产服务器的路由
|
||||
log_info "追踪到 novalon.cn 的路由:"
|
||||
traceroute -m 15 novalon.cn 2>&1 | head -20 || log_warning "traceroute命令不可用"
|
||||
|
||||
echo ""
|
||||
log_info "追踪到 git.f.novalon.cn 的路由:"
|
||||
traceroute -m 15 git.f.novalon.cn 2>&1 | head -20 || log_warning "traceroute命令不可用"
|
||||
|
||||
echo ""
|
||||
log_info "检查防火墙规则..."
|
||||
|
||||
# 检查防火墙状态
|
||||
if command -v ufw > /dev/null; then
|
||||
sudo ufw status verbose
|
||||
elif command -v firewall-cmd > /dev/null; then
|
||||
sudo firewall-cmd --list-all
|
||||
else
|
||||
log_warning "未检测到防火墙"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "检查网络代理设置..."
|
||||
env | grep -i proxy || log_info "未设置网络代理"
|
||||
}
|
||||
|
||||
# 4. 分析系统资源
|
||||
analyze_system_resources() {
|
||||
separator
|
||||
log_info "Phase 1.4: 分析系统资源"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "检查DNS配置..."
|
||||
cat /etc/resolv.conf
|
||||
|
||||
echo ""
|
||||
log_info "检查网络接口..."
|
||||
ifconfig | grep -E "^[a-z]|inet " || ip addr show
|
||||
|
||||
echo ""
|
||||
log_info "检查系统负载..."
|
||||
uptime
|
||||
|
||||
echo ""
|
||||
log_info "检查磁盘空间..."
|
||||
df -h | grep -E "Filesystem|/$|/home"
|
||||
}
|
||||
|
||||
# 生成诊断报告
|
||||
generate_report() {
|
||||
separator
|
||||
log_info "生成诊断报告"
|
||||
separator
|
||||
|
||||
REPORT_FILE="/tmp/production-diagnosis-$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
{
|
||||
echo "生产环境连接超时诊断报告"
|
||||
echo "生成时间: $(date)"
|
||||
echo ""
|
||||
collect_error_info
|
||||
echo ""
|
||||
check_recent_changes
|
||||
echo ""
|
||||
trace_data_flow
|
||||
echo ""
|
||||
analyze_system_resources
|
||||
} > "$REPORT_FILE" 2>&1
|
||||
|
||||
log_success "诊断报告已生成: $REPORT_FILE"
|
||||
|
||||
echo ""
|
||||
log_info "报告摘要:"
|
||||
head -50 "$REPORT_FILE"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "开始生产环境连接超时诊断"
|
||||
|
||||
if [ "$1" = "--report" ]; then
|
||||
generate_report
|
||||
else
|
||||
collect_error_info
|
||||
echo ""
|
||||
check_recent_changes
|
||||
echo ""
|
||||
trace_data_flow
|
||||
echo ""
|
||||
analyze_system_resources
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+232
@@ -0,0 +1,232 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 生产环境Docker镜像瘦身脚本
|
||||
# 用途:安全清理未使用的Docker镜像
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
separator() {
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
# 记录日志
|
||||
LOG_FILE="/tmp/docker-cleanup-$(date +%Y%m%d_%H%M%S).log"
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
separator
|
||||
echo "生产环境Docker镜像瘦身脚本"
|
||||
echo "执行时间: $(date)"
|
||||
separator
|
||||
|
||||
# 1. 显示当前状态
|
||||
log_info "步骤1: 当前Docker资源使用情况"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
docker system df
|
||||
|
||||
echo ""
|
||||
log_info "当前镜像列表:"
|
||||
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"
|
||||
|
||||
echo ""
|
||||
log_info "运行中的容器:"
|
||||
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
|
||||
|
||||
# 2. 分析未使用的镜像
|
||||
separator
|
||||
log_info "步骤2: 分析未使用的镜像"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "悬空镜像(<none>标签):"
|
||||
DANGLING_IMAGES=$(docker images -f "dangling=true" -q)
|
||||
if [ -n "$DANGLING_IMAGES" ]; then
|
||||
docker images -f "dangling=true" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"
|
||||
DANGLING_SIZE=$(docker images -f "dangling=true" --format "{{.Size}}" | grep -oE '[0-9.]+[GM]' | head -1)
|
||||
log_warning "发现悬空镜像,总计约: $DANGLING_SIZE"
|
||||
else
|
||||
log_success "没有悬空镜像"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "检查哪些镜像正在被使用..."
|
||||
RUNNING_IMAGES=$(docker ps --format "{{.Image}}" | sort -u)
|
||||
log_info "正在使用的镜像:"
|
||||
echo "$RUNNING_IMAGES"
|
||||
|
||||
echo ""
|
||||
log_info "未被容器使用的镜像:"
|
||||
docker images --format "{{.Repository}}:{{.Tag}}" | while read image; do
|
||||
if ! echo "$RUNNING_IMAGES" | grep -q "$(echo $image | cut -d: -f1)"; then
|
||||
echo "$image"
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 清理悬空镜像(安全)
|
||||
separator
|
||||
log_info "步骤3: 清理悬空镜像"
|
||||
separator
|
||||
|
||||
if [ -n "$DANGLING_IMAGES" ]; then
|
||||
log_warning "将删除以下悬空镜像:"
|
||||
docker images -f "dangling=true" --format "{{.ID}}\t{{.Size}}"
|
||||
|
||||
echo ""
|
||||
read -p "确认删除悬空镜像?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理悬空镜像..."
|
||||
docker image prune -f
|
||||
log_success "悬空镜像清理完成"
|
||||
else
|
||||
log_info "跳过悬空镜像清理"
|
||||
fi
|
||||
else
|
||||
log_success "没有悬空镜像需要清理"
|
||||
fi
|
||||
|
||||
# 4. 清理旧版本镜像(谨慎)
|
||||
separator
|
||||
log_info "步骤4: 清理旧版本镜像"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "检查novalon-website镜像版本..."
|
||||
NOVALON_IMAGES=$(docker images "novalon-website" --format "{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}")
|
||||
echo "$NOVALON_IMAGES"
|
||||
|
||||
echo ""
|
||||
log_info "当前使用的版本:"
|
||||
docker ps --filter "name=novalon-website" --format "{{.Image}}"
|
||||
|
||||
# 检查是否有旧版本
|
||||
OLD_VERSIONS=$(docker images "novalon-website" --format "{{.Tag}}" | grep -v "latest" | head -n -1)
|
||||
if [ -n "$OLD_VERSIONS" ]; then
|
||||
log_warning "发现旧版本镜像:"
|
||||
echo "$OLD_VERSIONS"
|
||||
|
||||
echo ""
|
||||
read -p "是否删除旧版本镜像?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
for version in $OLD_VERSIONS; do
|
||||
log_info "删除镜像: novalon-website:$version"
|
||||
docker rmi "novalon-website:$version" || log_warning "无法删除 novalon-website:$version(可能正在使用)"
|
||||
done
|
||||
log_success "旧版本镜像清理完成"
|
||||
else
|
||||
log_info "跳过旧版本镜像清理"
|
||||
fi
|
||||
else
|
||||
log_success "没有旧版本镜像需要清理"
|
||||
fi
|
||||
|
||||
# 5. 清理构建缓存
|
||||
separator
|
||||
log_info "步骤5: 清理构建缓存"
|
||||
separator
|
||||
|
||||
CACHE_SIZE=$(docker system df | grep "Build Cache" | awk '{print $3}')
|
||||
log_info "构建缓存大小: $CACHE_SIZE"
|
||||
|
||||
if [ "$CACHE_SIZE" != "0B" ]; then
|
||||
read -p "是否清理构建缓存?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理构建缓存..."
|
||||
docker builder prune -a -f
|
||||
log_success "构建缓存清理完成"
|
||||
else
|
||||
log_info "跳过构建缓存清理"
|
||||
fi
|
||||
else
|
||||
log_success "没有构建缓存需要清理"
|
||||
fi
|
||||
|
||||
# 6. 清理未使用的卷(可选)
|
||||
separator
|
||||
log_info "步骤6: 清理未使用的卷(可选)"
|
||||
separator
|
||||
|
||||
UNUSED_VOLUMES=$(docker volume ls -q --filter "dangling=true")
|
||||
if [ -n "$UNUSED_VOLUMES" ]; then
|
||||
log_warning "发现未使用的卷:"
|
||||
echo "$UNUSED_VOLUMES"
|
||||
|
||||
read -p "是否清理未使用的卷?(y/n): " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "正在清理未使用的卷..."
|
||||
docker volume prune -f
|
||||
log_success "未使用卷清理完成"
|
||||
else
|
||||
log_info "跳过未使用卷清理"
|
||||
fi
|
||||
else
|
||||
log_success "没有未使用的卷"
|
||||
fi
|
||||
|
||||
# 7. 显示清理结果
|
||||
separator
|
||||
log_info "步骤7: 清理结果"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "清理后的Docker资源使用情况:"
|
||||
docker system df
|
||||
|
||||
echo ""
|
||||
log_info "磁盘空间变化:"
|
||||
df -h | grep -E "Filesystem|/$"
|
||||
|
||||
separator
|
||||
log_success "Docker镜像瘦身完成"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "清理摘要:"
|
||||
echo " ✅ 悬空镜像已清理"
|
||||
echo " ✅ 旧版本镜像已清理"
|
||||
echo " ✅ 构建缓存已清理"
|
||||
echo " ✅ 未使用卷已清理"
|
||||
|
||||
echo ""
|
||||
log_info "详细日志已保存到: $LOG_FILE"
|
||||
|
||||
echo ""
|
||||
log_warning "建议后续操作:"
|
||||
echo " 1. 监控服务状态: docker ps"
|
||||
echo " 2. 验证应用访问: curl -I https://novalon.cn"
|
||||
echo " 3. 设置定期清理: crontab -e"
|
||||
echo " 4. 配置日志轮转: /etc/logrotate.d/docker"
|
||||
|
||||
separator
|
||||
Executable
+312
@@ -0,0 +1,312 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 远程服务器诊断脚本
|
||||
# 用途:在生产服务器上诊断连接超时问题
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
separator() {
|
||||
echo "======================================================================"
|
||||
}
|
||||
|
||||
# 1. 检查系统资源
|
||||
check_system_resources() {
|
||||
separator
|
||||
log_info "检查系统资源"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "CPU和内存使用情况:"
|
||||
top -bn1 | head -20
|
||||
|
||||
echo ""
|
||||
log_info "磁盘使用情况:"
|
||||
df -h
|
||||
|
||||
echo ""
|
||||
log_info "内存详情:"
|
||||
free -h
|
||||
|
||||
echo ""
|
||||
log_info "系统负载:"
|
||||
uptime
|
||||
|
||||
# 检查是否资源耗尽
|
||||
MEMORY_USAGE=$(free | grep Mem | awk '{print ($3/$2) * 100.0}')
|
||||
DISK_USAGE=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')
|
||||
|
||||
if (( $(echo "$MEMORY_USAGE > 90" | bc -l) )); then
|
||||
log_error "内存使用率过高: ${MEMORY_USAGE}%"
|
||||
else
|
||||
log_success "内存使用正常: ${MEMORY_USAGE}%"
|
||||
fi
|
||||
|
||||
if [ "$DISK_USAGE" -gt 90 ]; then
|
||||
log_error "磁盘使用率过高: ${DISK_USAGE}%"
|
||||
else
|
||||
log_success "磁盘使用正常: ${DISK_USAGE}%"
|
||||
fi
|
||||
}
|
||||
|
||||
# 2. 检查Docker容器
|
||||
check_docker() {
|
||||
separator
|
||||
log_info "检查Docker容器"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "Docker服务状态:"
|
||||
systemctl status docker --no-pager | head -20
|
||||
|
||||
echo ""
|
||||
log_info "运行中的容器:"
|
||||
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
echo ""
|
||||
log_info "容器资源使用:"
|
||||
docker stats --no-stream
|
||||
|
||||
# 检查容器健康状态
|
||||
RUNNING_CONTAINERS=$(docker ps -q | wc -l)
|
||||
STOPPED_CONTAINERS=$(docker ps -f "status=exited" -q | wc -l)
|
||||
|
||||
log_info "运行中容器: $RUNNING_CONTAINERS"
|
||||
log_info "已停止容器: $STOPPED_CONTAINERS"
|
||||
|
||||
if [ "$STOPPED_CONTAINERS" -gt 0 ]; then
|
||||
log_warning "发现已停止的容器:"
|
||||
docker ps -f "status=exited" --format "table {{.Names}}\t{{.Status}}"
|
||||
fi
|
||||
|
||||
# 检查容器日志
|
||||
echo ""
|
||||
log_info "检查容器日志(最近50行):"
|
||||
for container in $(docker ps -q); do
|
||||
CONTAINER_NAME=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
|
||||
echo ""
|
||||
log_info "容器: $CONTAINER_NAME"
|
||||
docker logs --tail 50 $container 2>&1 | tail -20
|
||||
done
|
||||
}
|
||||
|
||||
# 3. 检查Nginx
|
||||
check_nginx() {
|
||||
separator
|
||||
log_info "检查Nginx"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "Nginx服务状态:"
|
||||
systemctl status nginx --no-pager | head -20
|
||||
|
||||
echo ""
|
||||
log_info "Nginx进程:"
|
||||
ps aux | grep nginx | grep -v grep
|
||||
|
||||
echo ""
|
||||
log_info "Nginx监听端口:"
|
||||
netstat -tlnp | grep nginx || ss -tlnp | grep nginx
|
||||
|
||||
echo ""
|
||||
log_info "Nginx配置测试:"
|
||||
nginx -t
|
||||
|
||||
echo ""
|
||||
log_info "Nginx错误日志(最近50行):"
|
||||
tail -50 /var/log/nginx/error.log 2>/dev/null || log_warning "未找到Nginx错误日志"
|
||||
|
||||
echo ""
|
||||
log_info "Nginx访问日志(最近20行):"
|
||||
tail -20 /var/log/nginx/access.log 2>/dev/null || log_warning "未找到Nginx访问日志"
|
||||
}
|
||||
|
||||
# 4. 检查应用服务
|
||||
check_application() {
|
||||
separator
|
||||
log_info "检查应用服务"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "Node.js进程:"
|
||||
ps aux | grep node | grep -v grep
|
||||
|
||||
echo ""
|
||||
log_info "PM2进程(如果使用):"
|
||||
if command -v pm2 > /dev/null; then
|
||||
pm2 list
|
||||
pm2 logs --lines 20 --nostream
|
||||
else
|
||||
log_info "未使用PM2"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "检查端口占用:"
|
||||
netstat -tlnp | grep -E ":(3000|80|443)" || ss -tlnp | grep -E ":(3000|80|443)"
|
||||
|
||||
echo ""
|
||||
log_info "测试本地应用连接:"
|
||||
if curl -I --connect-timeout 5 http://localhost:3000 2>&1 | grep -q "HTTP"; then
|
||||
log_success "应用服务响应正常"
|
||||
else
|
||||
log_error "应用服务无响应"
|
||||
fi
|
||||
}
|
||||
|
||||
# 5. 检查防火墙和网络
|
||||
check_network() {
|
||||
separator
|
||||
log_info "检查防火墙和网络"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_info "防火墙状态:"
|
||||
if command -v ufw > /dev/null; then
|
||||
ufw status verbose
|
||||
elif command -v firewall-cmd > /dev/null; then
|
||||
firewall-cmd --list-all
|
||||
else
|
||||
log_info "未检测到防火墙"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "网络连接状态:"
|
||||
netstat -an | grep -E ":(80|443|3000)" | head -20
|
||||
|
||||
echo ""
|
||||
log_info "系统日志(最近错误):"
|
||||
journalctl -xe --no-pager | tail -50
|
||||
}
|
||||
|
||||
# 6. 快速修复建议
|
||||
suggest_fixes() {
|
||||
separator
|
||||
log_info "快速修复建议"
|
||||
separator
|
||||
|
||||
echo ""
|
||||
log_warning "根据诊断结果,建议执行以下操作:"
|
||||
|
||||
echo ""
|
||||
echo "1. 如果Docker容器停止:"
|
||||
echo " docker-compose -f /path/to/docker-compose.prod.yml up -d"
|
||||
echo " docker-compose -f /path/to/docker-compose.prod.yml restart"
|
||||
|
||||
echo ""
|
||||
echo "2. 如果Nginx异常:"
|
||||
echo " sudo systemctl restart nginx"
|
||||
echo " sudo nginx -t # 测试配置"
|
||||
|
||||
echo ""
|
||||
echo "3. 如果应用服务异常:"
|
||||
echo " docker logs <container-name> # 查看日志"
|
||||
echo " docker restart <container-name> # 重启容器"
|
||||
|
||||
echo ""
|
||||
echo "4. 如果资源耗尽:"
|
||||
echo " # 清理Docker镜像"
|
||||
echo " docker system prune -a -f"
|
||||
echo " # 清理日志"
|
||||
echo " sudo journalctl --vacuum-time=3d"
|
||||
echo " # 重启服务"
|
||||
echo " sudo systemctl restart docker"
|
||||
|
||||
echo ""
|
||||
echo "5. 查看详细日志:"
|
||||
echo " docker logs -f <container-name> # 实时查看容器日志"
|
||||
echo " tail -f /var/log/nginx/error.log # 实时查看Nginx错误日志"
|
||||
echo " journalctl -u docker -f # 实时查看Docker服务日志"
|
||||
}
|
||||
|
||||
# 一键诊断
|
||||
full_diagnosis() {
|
||||
log_info "开始完整诊断..."
|
||||
|
||||
check_system_resources
|
||||
echo ""
|
||||
check_docker
|
||||
echo ""
|
||||
check_nginx
|
||||
echo ""
|
||||
check_application
|
||||
echo ""
|
||||
check_network
|
||||
echo ""
|
||||
suggest_fixes
|
||||
}
|
||||
|
||||
# 主菜单
|
||||
main_menu() {
|
||||
clear
|
||||
separator
|
||||
echo "远程服务器诊断工具"
|
||||
separator
|
||||
echo ""
|
||||
echo "1. 检查系统资源"
|
||||
echo "2. 检查Docker容器"
|
||||
echo "3. 检查Nginx"
|
||||
echo "4. 检查应用服务"
|
||||
echo "5. 检查防火墙和网络"
|
||||
echo "6. 显示修复建议"
|
||||
echo "7. 完整诊断"
|
||||
echo "0. 退出"
|
||||
echo ""
|
||||
read -p "请选择操作 (0-7): " choice
|
||||
|
||||
case $choice in
|
||||
1) check_system_resources ;;
|
||||
2) check_docker ;;
|
||||
3) check_nginx ;;
|
||||
4) check_application ;;
|
||||
5) check_network ;;
|
||||
6) suggest_fixes ;;
|
||||
7) full_diagnosis ;;
|
||||
0)
|
||||
log_info "退出程序"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "无效选择"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
read -p "按回车键继续..."
|
||||
main_menu
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
log_info "远程服务器诊断工具启动"
|
||||
|
||||
if [ "$1" = "--full" ]; then
|
||||
full_diagnosis
|
||||
else
|
||||
main_menu
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -84,6 +84,7 @@ export const mockLucideReact = () => {
|
||||
jest.mock('lucide-react', () => ({
|
||||
ArrowRight: () => <span data-testid="arrow-right" />,
|
||||
ArrowLeft: () => <span data-testid="arrow-left" />,
|
||||
ArrowUp: () => <span data-testid="arrow-up" />,
|
||||
Shield: () => <span data-testid="shield-icon" />,
|
||||
Zap: () => <span data-testid="zap-icon" />,
|
||||
Award: () => <span data-testid="award-icon" />,
|
||||
|
||||
@@ -1,43 +1,58 @@
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { BackToTop } from './back-to-top';
|
||||
|
||||
// Mock useReducedMotion
|
||||
jest.mock('@/hooks/use-reduced-motion', () => ({
|
||||
useReducedMotion: () => false,
|
||||
}));
|
||||
|
||||
// Mock AnimatePresence to always render children
|
||||
jest.mock('framer-motion', () => ({
|
||||
...jest.requireActual('framer-motion'),
|
||||
AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
motion: {
|
||||
button: ({ children, ...props }: React.ComponentProps<'button'>) => <button {...props}>{children}</button>,
|
||||
},
|
||||
useAnimation: () => ({
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
}),
|
||||
useMotionValue: () => ({
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('lucide-react', () => ({
|
||||
ArrowUp: () => <span data-testid="arrow-up-icon" />,
|
||||
}));
|
||||
|
||||
describe('BackToTop', () => {
|
||||
let scrollYValue = 0;
|
||||
const originalScrollY = Object.getOwnPropertyDescriptor(window, 'scrollY');
|
||||
const originalScrollTo = window.scrollTo;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
scrollYValue = 0;
|
||||
|
||||
Object.defineProperty(window, 'scrollY', {
|
||||
get: () => scrollYValue,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
window.scrollTo = jest.fn();
|
||||
window.addEventListener = jest.fn((event, handler) => {
|
||||
if (event === 'scroll') {
|
||||
// 模拟滚动事件触发
|
||||
(handler as EventListener)(new Event('scroll'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalScrollY) {
|
||||
Object.defineProperty(window, 'scrollY', originalScrollY);
|
||||
}
|
||||
window.scrollTo = originalScrollTo;
|
||||
});
|
||||
|
||||
it('should not render when scroll position is less than 500px', () => {
|
||||
scrollYValue = 0;
|
||||
const { container } = render(<BackToTop />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(container.querySelector('button')).toBeNull();
|
||||
});
|
||||
|
||||
it('should render button when scroll position is more than 500px', async () => {
|
||||
@@ -45,8 +60,12 @@ describe('BackToTop', () => {
|
||||
|
||||
render(<BackToTop />);
|
||||
|
||||
act(() => {
|
||||
fireEvent.scroll(window);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const button = screen.getByRole('button', { name: /返回顶部/i });
|
||||
const button = screen.queryByRole('button', { name: /返回顶部/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -56,11 +75,14 @@ describe('BackToTop', () => {
|
||||
|
||||
render(<BackToTop />);
|
||||
|
||||
await waitFor(() => {
|
||||
const button = screen.getByRole('button', { name: /返回顶部/i });
|
||||
fireEvent.click(button);
|
||||
act(() => {
|
||||
fireEvent.scroll(window);
|
||||
});
|
||||
|
||||
const button = await screen.findByRole('button', { name: /返回顶部/i });
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(window.scrollTo).toHaveBeenCalledWith({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
@@ -72,10 +94,12 @@ describe('BackToTop', () => {
|
||||
|
||||
render(<BackToTop />);
|
||||
|
||||
await waitFor(() => {
|
||||
const button = screen.getByRole('button');
|
||||
expect(button).toHaveAttribute('aria-label', '返回顶部');
|
||||
expect(button).toHaveAttribute('title', '返回顶部');
|
||||
act(() => {
|
||||
fireEvent.scroll(window);
|
||||
});
|
||||
|
||||
const button = await screen.findByRole('button');
|
||||
expect(button).toHaveAttribute('aria-label', '返回顶部');
|
||||
expect(button).toHaveAttribute('title', '返回顶部');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user