diff --git a/Makefile b/Makefile index dd8cbad..1eeb9d5 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,19 @@ help: @echo " all-down Stop all services" @echo "" @echo "Service-specific Commands:" - @echo " minecraft-up Start Minecraft server" - @echo " minecraft-down Stop Minecraft server" - @echo " minecraft-logs View Minecraft logs" - @echo " minecraft-restart Restart Minecraft server" + @echo " Minecraft:" + @echo " minecraft-up Start Minecraft server" + @echo " minecraft-down Stop Minecraft server" + @echo " minecraft-logs View Minecraft logs" + @echo " minecraft-restart Restart Minecraft server" + @echo " minecraft-status Show server status" + @echo " minecraft-setup Initialize environment" + @echo " minecraft-mods-download Download mods from Modrinth" + @echo " minecraft-mods-list List installed mods" + @echo " minecraft-mods-update Update all mods" + @echo " minecraft-backup Create full backup" + @echo " minecraft-backup-world Backup world data only" + @echo " minecraft-backup-list List available backups" @echo "" @echo " teamspeak-up Start TeamSpeak server" @echo " teamspeak-down Stop TeamSpeak server" @@ -87,6 +96,36 @@ minecraft-restart: @cd minecraft && docker compose restart @echo "✓ Minecraft server restarted" +minecraft-status: + @cd minecraft && ./scripts/monitor.sh status + +minecraft-setup: + @cd minecraft && ./scripts/setup.sh + +minecraft-mods-download: + @cd minecraft && ./scripts/mod-manager.sh download + +minecraft-mods-list: + @cd minecraft && ./scripts/mod-manager.sh list + +minecraft-mods-update: + @cd minecraft && ./scripts/mod-manager.sh update + +minecraft-mods-check: + @cd minecraft && ./scripts/mod-manager.sh check + +minecraft-backup: + @cd minecraft && ./scripts/backup.sh backup all + +minecraft-backup-world: + @cd minecraft && ./scripts/backup.sh backup world + +minecraft-backup-list: + @cd minecraft && ./scripts/backup.sh list + +minecraft-backup-cleanup: + @cd minecraft && ./scripts/backup.sh cleanup + # TeamSpeak teamspeak-up: @cd teamspeak && docker compose up -d diff --git a/minecraft/CHANGELOG.md b/minecraft/CHANGELOG.md new file mode 100644 index 0000000..e4602d9 --- /dev/null +++ b/minecraft/CHANGELOG.md @@ -0,0 +1,184 @@ +# Minecraft 自动化方案重构日志 + +## 2025-12-09 - 自动化架构重构 + +### 🎯 重构目标 + +整合原有的本地部署方案(`src/automatic/`)和 Docker Compose 方案,提供统一的自动化管理系统。 + +### ✨ 新增功能 + +#### 1. 统一的脚本体系 + +创建 `scripts/` 目录,包含以下模块: + +- **utils.sh** - 通用工具库 + - 彩色日志输出 + - Docker 环境检查 + - 容器状态管理 + - 文件备份工具 + - 网络连接检测 + +- **setup.sh** - 环境初始化 + - 系统环境检查 + - 目录结构初始化 + - 配置文件验证 + - 权限自动修复 + +- **mod-manager.sh** - Mods 管理 + - 从 Modrinth 自动下载 + - 批量更新 mods + - 列出已安装 mods + - 清理和状态检查 + +- **backup.sh** - 备份管理 + - 世界数据备份 + - 配置文件备份 + - Mods 备份 + - 备份恢复功能 + - 自动清理旧备份 + +- **monitor.sh** - 服务器监控 + - 容器状态检查 + - 资源使用监控 + - 在线玩家查询 + - 日志分析 + - 持续监控模式 + +#### 2. Makefile 集成 + +在根目录 `Makefile` 中新增命令: + +**服务器管理** +- `make minecraft-status` - 查看服务器状态 +- `make minecraft-setup` - 初始化环境 + +**Mods 管理** +- `make minecraft-mods-download` - 下载 mods +- `make minecraft-mods-list` - 列出 mods +- `make minecraft-mods-update` - 更新 mods +- `make minecraft-mods-check` - 检查状态 + +**备份管理** +- `make minecraft-backup` - 完整备份 +- `make minecraft-backup-world` - 备份世界 +- `make minecraft-backup-list` - 列出备份 +- `make minecraft-backup-cleanup` - 清理备份 + +#### 3. 完整的文档 + +重写 `minecraft/README.md`: +- 详细的快速开始指南 +- 完整的命令参考 +- 高级用法示例 +- 故障排查指南 +- 迁移指南 + +### 🔄 架构改进 + +#### 从旧方案继承的优点 + +1. **日志系统** + - 保留了 `logger.sh` 的彩色输出设计 + - 增强了日志功能(系统信息、时间戳、文件记录) + +2. **Mods 下载逻辑** + - 基于 `download-mods.sh` 改进 + - 统一使用 `extras/mods.txt` 格式 + - 增加错误处理和重试机制 + +3. **部署流程** + - 参考 `deploy.sh` 的备份逻辑 + - 适配 Docker Compose 环境 + +#### 新增的优势 + +1. **Docker 优先** + - 完全容器化部署 + - 一致的运行环境 + - 简化依赖管理 + +2. **模块化设计** + - 每个脚本职责单一 + - 通过 `utils.sh` 共享通用功能 + - 易于维护和扩展 + +3. **统一管理** + - Makefile 统一入口 + - 一致的命令格式 + - 与其他服务(TeamSpeak、Nextcloud)集成 + +### 📂 目录变更 + +``` +旧结构: +minecraft/ +├── src/automatic/ +│ ├── deploy.sh +│ ├── download-mods.sh +│ ├── logger.sh +│ └── requirements.txt + +新结构: +minecraft/ +├── scripts/ # 新增:统一的脚本目录 +│ ├── utils.sh +│ ├── setup.sh +│ ├── mod-manager.sh +│ ├── backup.sh +│ └── monitor.sh +├── extras/ +│ └── mods.txt # 统一的 mods 配置 +├── backups/ # 新增:自动备份目录 +├── logs/ # 新增:脚本日志目录 +└── src/automatic/ # 保留(供参考) +``` + +### 🔧 技术细节 + +1. **错误处理** + - 所有脚本使用 `set -e` + - 完善的返回码检查 + - 详细的错误消息 + +2. **跨平台兼容** + - macOS 和 Linux 兼容的命令 + - 自动检测 `docker compose` vs `docker-compose` + - 处理不同的 `stat` 命令格式 + +3. **安全性** + - 敏感信息通过 `.env` 管理 + - RCON 密码验证 + - 备份前的确认机制 + +### 📋 迁移建议 + +如果使用旧的 `src/automatic/` 脚本: + +1. 旧脚本仍可使用(未删除) +2. 建议迁移到新的 Docker 方案 +3. 新方案提供更多自动化功能 +4. 通过 Makefile 统一管理更便捷 + +### 🎯 后续计划 + +- [ ] 添加定时备份的 systemd/cron 模板 +- [ ] 集成 Prometheus 指标监控 +- [ ] 添加自动更新检查 +- [ ] Web 控制面板集成 +- [ ] 多服务器管理支持 + +### 📝 配置兼容性 + +- ✅ `docker-compose.yml` - 无变化 +- ✅ `.env` - 无变化 +- ✅ `configs/` - 无变化 +- ✅ `mods/` - 无变化 +- ✅ `extras/mods.txt` - 格式与旧 `requirements.txt` 兼容 + +### 🙏 致谢 + +重构整合了原有设计的精华: +- 日志系统的设计理念 +- Modrinth API 集成逻辑 +- 部署流程的最佳实践 diff --git a/minecraft/README.md b/minecraft/README.md index 3343afb..bc44238 100644 --- a/minecraft/README.md +++ b/minecraft/README.md @@ -1,15 +1,316 @@ -# Automa Minecraft +# Automa Minecraft 服务器 + +基于 Docker Compose 的 Minecraft Fabric 1.21.1 服务器,提供完整的自动化管理方案。 + +## 📁 目录结构 ``` -mc-fabric-docker/ -├── docker-compose.yml -├── .env # 必改:UID、GID、RCON_PASSWORD、TZ -├── mods/ # 放你的所有 mods jar -├── configs/ -│ ├── server.properties # 服务器配置 -│ ├── whitelist.json # 白名单(示例) -│ └── ops.json # OP(示例) -├── data/ # 自动生成:世界、备份、日志 -└── extras/ - └── mods.txt # 可选:Modrinth 自动下载模组 +minecraft/ +├── docker-compose.yml # Docker Compose 配置 +├── .env # 环境变量配置(需自定义) +├── configs/ # 服务器配置文件 +│ ├── server.properties # 服务器属性配置 +│ └── whitelist.json # 白名单配置 +├── mods/ # Mods 存放目录 +├── data/ # 持久化数据(世界、日志等) +├── backups/ # 自动备份目录 +├── logs/ # 自动化脚本日志 +├── extras/ +│ └── mods.txt # Modrinth Mods 列表 +└── scripts/ # 自动化脚本 + ├── utils.sh # 工具库 + ├── setup.sh # 环境初始化 + ├── mod-manager.sh # Mods 管理 + ├── backup.sh # 备份管理 + └── monitor.sh # 服务器监控 ``` + +## 🚀 快速开始 + +### 1. 环境初始化 + +```bash +# 检查环境并初始化目录结构 +make minecraft-setup +``` + +### 2. 配置服务器 + +编辑 `.env` 文件,设置必要的配置: + +```bash +# 用户权限(使用 id 命令查看) +UID=1000 +GID=1000 + +# RCON 密码(远程管理) +RCON_PASSWORD=your_secure_password + +# 时区 +TZ=Asia/Shanghai +``` + +### 3. 下载 Mods(可选) + +```bash +# 从 Modrinth 下载 mods(根据 extras/mods.txt) +make minecraft-mods-download + +# 或手动将 mods 放入 mods/ 目录 +``` + +### 4. 启动服务器 + +```bash +# 启动服务器 +make minecraft-up + +# 查看日志 +make minecraft-logs +``` + +## 📋 常用命令 + +### 服务器管理 + +```bash +make minecraft-up # 启动服务器 +make minecraft-down # 停止服务器 +make minecraft-restart # 重启服务器 +make minecraft-logs # 查看实时日志 +make minecraft-status # 查看服务器状态 +``` + +### Mods 管理 + +```bash +make minecraft-mods-download # 下载所有 mods +make minecraft-mods-list # 列出已安装的 mods +make minecraft-mods-update # 更新所有 mods +make minecraft-mods-check # 检查 mods 状态 +``` + +Mods 配置在 `extras/mods.txt` 中,每行一个 Modrinth slug: + +``` +fabric-api +sodium +lithium +iris +``` + +### 备份管理 + +```bash +make minecraft-backup # 完整备份(世界、配置、mods) +make minecraft-backup-world # 仅备份世界数据 +make minecraft-backup-list # 列出所有备份 +make minecraft-backup-cleanup # 清理旧备份 +``` + +备份存储在 `backups/` 目录,按类型分类: +- `backups/worlds/` - 世界数据备份 +- `backups/configs/` - 配置文件备份 +- `backups/mods/` - Mods 备份 + +## 🔧 高级用法 + +### 直接使用脚本 + +所有自动化脚本位于 `scripts/` 目录: + +```bash +# 环境初始化 +./scripts/setup.sh + +# Mods 管理 +./scripts/mod-manager.sh download # 下载 mods +./scripts/mod-manager.sh list # 列出 mods +./scripts/mod-manager.sh update # 更新 mods +./scripts/mod-manager.sh clean # 清理 mods + +# 备份管理 +./scripts/backup.sh backup all # 完整备份 +./scripts/backup.sh backup world # 仅备份世界 +./scripts/backup.sh list # 列出备份 +./scripts/backup.sh restore # 恢复备份 +./scripts/backup.sh cleanup 10 # 保留最近10个备份 + +# 服务器监控 +./scripts/monitor.sh status # 完整状态 +./scripts/monitor.sh resources # 资源使用 +./scripts/monitor.sh players # 在线玩家 +./scripts/monitor.sh logs 50 # 最近50行日志 +./scripts/monitor.sh watch 10 # 持续监控(每10秒) +``` + +### 自定义配置 + +#### 修改服务器配置 + +编辑 `configs/server.properties`,然后重启服务器: + +```bash +vim configs/server.properties +make minecraft-restart +``` + +#### 添加新 Mods + +1. 在 Modrinth 找到 mod 的 slug(URL中的ID) +2. 添加到 `extras/mods.txt` +3. 运行下载命令: + +```bash +make minecraft-mods-download +make minecraft-restart +``` + +#### 配置白名单 + +编辑 `configs/whitelist.json`,并在 `.env` 或 `configs/server.properties` 中启用白名单: + +```properties +white-list=true +enforce-whitelist=true +``` + +## 🔍 监控与维护 + +### 实时监控 + +```bash +# 查看完整状态(容器、资源、玩家、错误) +make minecraft-status + +# 持续监控模式(每5秒刷新) +cd minecraft && ./scripts/monitor.sh watch 5 +``` + +### 日志管理 + +```bash +# 查看 Docker 容器日志 +make minecraft-logs + +# 查看自动化脚本日志 +ls -lh logs/ +tail -f logs/automation-*.log +``` + +### 定期备份 + +建议配置 cron 任务定期备份: + +```bash +# 每天凌晨3点备份世界数据 +0 3 * * * cd /path/to/automa && make minecraft-backup-world + +# 每周清理旧备份(保留最近10个) +0 4 * * 0 cd /path/to/automa/minecraft && ./scripts/backup.sh cleanup 10 +``` + +## 📊 性能优化 + +项目已包含性能优化 mods: +- **Sodium** - 渲染优化 +- **Lithium** - 服务器性能优化 +- **Iris** - 着色器支持 + +内存配置在 `docker-compose.yml` 中: + +```yaml +environment: + MEMORY: "4G" # 最大内存 + INIT_MEMORY: "2G" # 初始内存 +``` + +## 🛡️ 安全建议 + +1. **修改 RCON 密码**:在 `.env` 中设置强密码 +2. **配置防火墙**:仅开放必要端口(25565, 25575) +3. **启用白名单**:在 `configs/server.properties` 中配置 +4. **定期备份**:使用自动化备份脚本 +5. **监控日志**:定期检查错误日志 + +## 🔄 迁移指南 + +### 从旧版本迁移 + +如果你使用的是 `src/automatic/` 下的旧脚本: + +1. 新方案使用 Docker Compose,更易部署和维护 +2. Mods 管理统一使用 `extras/mods.txt` 格式 +3. 所有自动化功能集成到 `scripts/` 目录 +4. 通过 Makefile 统一管理 + +迁移步骤: + +```bash +# 1. 备份旧数据 +cp -r old_server_dir/world minecraft/data/ + +# 2. 复制 mods(如果手动管理) +cp -r old_mods_dir/* minecraft/mods/ + +# 3. 初始化新环境 +make minecraft-setup + +# 4. 启动服务器 +make minecraft-up +``` + +## 📝 故障排查 + +### 容器无法启动 + +```bash +# 检查 Docker 服务 +docker info + +# 查看容器日志 +make minecraft-logs + +# 检查环境配置 +cat .env +``` + +### Mods 下载失败 + +```bash +# 检查网络连接 +curl -I https://api.modrinth.com + +# 查看详细日志 +cat logs/automation-*.log + +# 手动下载 mods 放入 mods/ 目录 +``` + +### 性能问题 + +```bash +# 查看资源使用 +cd minecraft && ./scripts/monitor.sh resources + +# 调整内存配置 +vim docker-compose.yml # 修改 MEMORY 和 INIT_MEMORY + +# 重启服务器 +make minecraft-restart +``` + +## 📚 相关资源 + +- [Docker 文档](https://docs.docker.com/) +- [itzg/minecraft-server 镜像](https://github.com/itzg/docker-minecraft-server) +- [Modrinth](https://modrinth.com/) - Mods 下载平台 +- [Fabric](https://fabricmc.net/) - Mod 加载器 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 📄 许可 + +MIT License diff --git a/minecraft/scripts/backup.sh b/minecraft/scripts/backup.sh new file mode 100755 index 0000000..7b27651 --- /dev/null +++ b/minecraft/scripts/backup.sh @@ -0,0 +1,376 @@ +#!/usr/bin/env bash +# Minecraft 服务器备份管理 +# 支持世界数据、配置文件、mods 的备份和恢复 + +set -e + +# 加载工具库 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/utils.sh" + +# ============================================ +# 配置变量 +# ============================================ +readonly PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +readonly BACKUP_DIR="$PROJECT_ROOT/backups" +readonly DATA_DIR="$PROJECT_ROOT/data" +readonly MODS_DIR="$PROJECT_ROOT/mods" +readonly CONFIGS_DIR="$PROJECT_ROOT/configs" +readonly CONTAINER_NAME="mc-fabric-1.21.1" + +# ============================================ +# 备份函数 +# ============================================ + +# 备份世界数据 +backup_world() { + log_info "备份世界数据..." + + local world_dir="$DATA_DIR/world" + + if [[ ! -d "$world_dir" ]]; then + log_warning "世界数据不存在: $world_dir" + return 1 + fi + + # 通知服务器保存 + if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log_info "通知服务器保存世界..." + docker exec "$CONTAINER_NAME" rcon-cli save-all flush 2>/dev/null || true + sleep 3 + fi + + # 创建备份 + local backup_file=$(create_backup "$world_dir" "$BACKUP_DIR/worlds") + + if [[ -n "$backup_file" ]]; then + log_success "世界备份完成" + return 0 + else + log_error "世界备份失败" + return 1 + fi +} + +# 备份配置文件 +backup_configs() { + log_info "备份配置文件..." + + if [[ ! -d "$CONFIGS_DIR" ]]; then + log_warning "配置目录不存在: $CONFIGS_DIR" + return 1 + fi + + local backup_file=$(create_backup "$CONFIGS_DIR" "$BACKUP_DIR/configs") + + if [[ -n "$backup_file" ]]; then + log_success "配置备份完成" + return 0 + else + log_error "配置备份失败" + return 1 + fi +} + +# 备份 mods +backup_mods() { + log_info "备份 Mods..." + + if [[ ! -d "$MODS_DIR" || -z "$(ls -A "$MODS_DIR" 2>/dev/null)" ]]; then + log_warning "Mods 目录为空" + return 1 + fi + + local backup_file=$(create_backup "$MODS_DIR" "$BACKUP_DIR/mods") + + if [[ -n "$backup_file" ]]; then + log_success "Mods 备份完成" + return 0 + else + log_error "Mods 备份失败" + return 1 + fi +} + +# 完整备份 +backup_all() { + log_info "开始完整备份..." + log_separator + + local success=0 + local failed=0 + + # 备份世界 + if backup_world; then + ((success++)) + else + ((failed++)) + fi + + log_separator + + # 备份配置 + if backup_configs; then + ((success++)) + else + ((failed++)) + fi + + log_separator + + # 备份 mods + if backup_mods; then + ((success++)) + else + ((failed++)) + fi + + log_separator + log_info "备份完成 - 成功: $success, 失败: $failed" + + return $failed +} + +# ============================================ +# 恢复函数 +# ============================================ + +# 列出可用备份 +list_backups() { + local type="${1:-all}" + + log_info "可用备份:" + + case "$type" in + world | worlds) + list_backup_type "worlds" "世界数据" + ;; + config | configs) + list_backup_type "configs" "配置文件" + ;; + mods) + list_backup_type "mods" "Mods" + ;; + all | *) + list_backup_type "worlds" "世界数据" + list_backup_type "configs" "配置文件" + list_backup_type "mods" "Mods" + ;; + esac +} + +# 列出特定类型的备份 +list_backup_type() { + local subdir="$1" + local name="$2" + local backup_path="$BACKUP_DIR/$subdir" + + echo "" + log_info "=== $name ===" + + if [[ ! -d "$backup_path" ]]; then + log_warning "无可用备份" + return + fi + + local count=0 + while IFS= read -r file; do + local filename=$(basename "$file") + local size=$(du -h "$file" | cut -f1) + local date=$(echo "$filename" | grep -oP '\d{8}-\d{6}' || echo "未知") + + printf " ${CYAN}•${NC} %-60s %8s\n" "$filename" "$size" + ((count++)) + done < <(find "$backup_path" -name "*.tar.gz" -type f 2>/dev/null | sort -r) + + if [[ $count -eq 0 ]]; then + log_warning "无可用备份" + else + log_info "共 $count 个备份" + fi +} + +# 恢复备份 +restore_backup() { + local backup_file="$1" + + if [[ ! -f "$backup_file" ]]; then + log_error "备份文件不存在: $backup_file" + return 1 + fi + + log_warning "恢复备份将覆盖现有数据" + log_info "备份文件: $(basename "$backup_file")" + + # 确定恢复目标 + local target_dir="" + if [[ "$backup_file" =~ /worlds/ ]]; then + target_dir="$DATA_DIR" + elif [[ "$backup_file" =~ /configs/ ]]; then + target_dir=$(dirname "$CONFIGS_DIR") + elif [[ "$backup_file" =~ /mods/ ]]; then + target_dir=$(dirname "$MODS_DIR") + else + log_error "无法确定备份类型" + return 1 + fi + + log_info "恢复目标: $target_dir" + + # 解压备份 + if tar -xzf "$backup_file" -C "$target_dir"; then + log_success "备份恢复完成" + return 0 + else + log_error "备份恢复失败" + return 1 + fi +} + +# ============================================ +# 清理函数 +# ============================================ + +# 清理旧备份 +cleanup_backups() { + local keep="${1:-5}" + + log_info "清理旧备份(保留最近 $keep 个)..." + log_separator + + cleanup_old_backups "$BACKUP_DIR/worlds" "$keep" + cleanup_old_backups "$BACKUP_DIR/configs" "$keep" + cleanup_old_backups "$BACKUP_DIR/mods" "$keep" + + log_success "备份清理完成" +} + +# 显示备份统计 +show_backup_stats() { + log_info "备份统计信息" + log_separator + + local types=("worlds:世界数据" "configs:配置文件" "mods:Mods") + + for type_pair in "${types[@]}"; do + local type="${type_pair%%:*}" + local name="${type_pair##*:}" + local backup_path="$BACKUP_DIR/$type" + + if [[ -d "$backup_path" ]]; then + local count=$(find "$backup_path" -name "*.tar.gz" 2>/dev/null | wc -l) + local total_size=$(du -sh "$backup_path" 2>/dev/null | cut -f1) + + log_info "$name: $count 个备份, 总大小: $total_size" + else + log_info "$name: 无备份" + fi + done + + log_separator + + if [[ -d "$BACKUP_DIR" ]]; then + local total_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1) + log_info "备份总大小: $total_size" + fi +} + +# ============================================ +# 主函数 +# ============================================ + +show_usage() { + cat < 恢复指定备份 + cleanup [num] 清理旧备份(默认保留5个) + stats 显示备份统计信息 + help 显示此帮助信息 + +示例: + $(basename "$0") backup all # 完整备份 + $(basename "$0") backup world # 仅备份世界 + $(basename "$0") list # 列出所有备份 + $(basename "$0") cleanup 10 # 保留最近10个备份 + +备份位置: $BACKUP_DIR +EOF +} + +main() { + local command="${1:-help}" + shift || true + + # 初始化日志 + init_log "Minecraft 备份管理" + log_system_info + + case "$command" in + backup) + local type="${1:-all}" + case "$type" in + world | worlds) + backup_world + ;; + config | configs) + backup_configs + ;; + mods) + backup_mods + ;; + all | *) + backup_all + ;; + esac + ;; + + list) + list_backups "${1:-all}" + ;; + + restore) + local file="$1" + if [[ -z "$file" ]]; then + log_error "请指定备份文件" + exit 1 + fi + restore_backup "$file" + ;; + + cleanup) + cleanup_backups "${1:-5}" + ;; + + stats) + show_backup_stats + ;; + + help | --help | -h) + show_usage + ;; + + *) + log_error "未知命令: $command" + echo "" + show_usage + exit 1 + ;; + esac + + show_log_file +} + +# 执行主函数 +main "$@" diff --git a/minecraft/scripts/mod-manager.sh b/minecraft/scripts/mod-manager.sh new file mode 100755 index 0000000..9260b71 --- /dev/null +++ b/minecraft/scripts/mod-manager.sh @@ -0,0 +1,344 @@ +#!/usr/bin/env bash +# Minecraft Mods 管理器 +# 支持从 Modrinth 下载、更新、清理 mods + +set -e + +# 加载工具库 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/utils.sh" + +# ============================================ +# 配置变量 +# ============================================ +readonly PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +readonly MODS_DIR="$PROJECT_ROOT/mods" +readonly MODS_LIST="$PROJECT_ROOT/extras/mods.txt" +readonly MC_VERSION="1.21.1" +readonly LOADER="fabric" +readonly MODRINTH_API="https://api.modrinth.com/v2" + +# ============================================ +# Modrinth API 函数 +# ============================================ + +# 获取项目信息 +get_project_info() { + local slug="$1" + local response + + response=$(curl -s "$MODRINTH_API/project/$slug") + + if [[ $? -ne 0 || -z "$response" ]]; then + return 1 + fi + + echo "$response" +} + +# 获取最新版本 +get_latest_version() { + local slug="$1" + local game_version="$2" + local loader="$3" + + local url="$MODRINTH_API/project/$slug/version" + url+="?game_versions=%5B%22${game_version}%22%5D" + url+="&loaders=%5B%22${loader}%22%5D" + + local response=$(curl -s "$url") + + if [[ $? -ne 0 || -z "$response" || "$response" == "[]" ]]; then + return 1 + fi + + echo "$response" +} + +# 解析下载信息 +parse_download_info() { + local json="$1" + + # 提取文件名和下载链接 + local filename=$(echo "$json" | grep -o '"filename":"[^"]*"' | head -1 | cut -d'"' -f4) + local url=$(echo "$json" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4) + + if [[ -z "$filename" || -z "$url" ]]; then + return 1 + fi + + echo "$filename|$url" +} + +# ============================================ +# Mods 下载函数 +# ============================================ + +# 下载单个 mod +download_mod() { + local slug="$1" + local mod_name="${2:-$slug}" + + log_info "处理: $mod_name ($slug)" + + # 获取版本信息 + local versions=$(get_latest_version "$slug" "$MC_VERSION" "$LOADER") + + if [[ $? -ne 0 ]]; then + log_error "未找到适配版本: $slug" + return 1 + fi + + # 解析下载信息 + local download_info=$(parse_download_info "$versions") + + if [[ $? -ne 0 ]]; then + log_error "解析下载信息失败: $slug" + return 1 + fi + + local filename=$(echo "$download_info" | cut -d'|' -f1) + local download_url=$(echo "$download_info" | cut -d'|' -f2) + + # 检查文件是否已存在 + if [[ -f "$MODS_DIR/$filename" ]]; then + log_info "已存在: $filename" + return 0 + fi + + # 下载文件 + log_info "下载: $filename" + + if curl -L -o "$MODS_DIR/$filename" "$download_url" 2>/dev/null; then + # 验证文件大小 + local size=$(stat -f%z "$MODS_DIR/$filename" 2>/dev/null || stat -c%s "$MODS_DIR/$filename" 2>/dev/null) + + if [[ $size -gt 1000 ]]; then + log_success "下载完成: $filename ($(($size / 1024)) KB)" + return 0 + else + log_error "文件大小异常: $filename ($size bytes)" + rm -f "$MODS_DIR/$filename" + return 1 + fi + else + log_error "下载失败: $slug" + return 1 + fi +} + +# 从列表下载所有 mods +download_all_mods() { + if [[ ! -f "$MODS_LIST" ]]; then + log_error "Mods 列表不存在: $MODS_LIST" + return 1 + fi + + log_info "开始批量下载 mods" + log_info "Minecraft 版本: $MC_VERSION" + log_info "加载器: $LOADER" + log_info "目标目录: $MODS_DIR" + + mkdir -p "$MODS_DIR" + + local success_count=0 + local fail_count=0 + local skip_count=0 + + while IFS= read -r line; do + # 跳过空行和注释 + line=$(echo "$line" | xargs) + if [[ -z "$line" || "$line" =~ ^# ]]; then + continue + fi + + log_separator + + if download_mod "$line"; then + ((success_count++)) + else + ((fail_count++)) + fi + + # 限流,避免API限制 + sleep 1 + + done <"$MODS_LIST" + + log_separator + log_info "下载完成 - 成功: $success_count, 失败: $fail_count" + + return 0 +} + +# ============================================ +# Mods 管理函数 +# ============================================ + +# 列出已安装的 mods +list_mods() { + log_info "已安装的 Mods:" + + if [[ ! -d "$MODS_DIR" || -z "$(ls -A "$MODS_DIR" 2>/dev/null)" ]]; then + log_warning "未找到任何 mods" + return 0 + fi + + local count=0 + while IFS= read -r file; do + local filename=$(basename "$file") + local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null) + local size_kb=$(($size / 1024)) + + printf " ${GREEN}•${NC} %-50s %8s KB\n" "$filename" "$size_kb" + ((count++)) + done < <(find "$MODS_DIR" -name "*.jar" -type f 2>/dev/null | sort) + + log_info "总计: $count 个 mods" +} + +# 清理 mods +clean_mods() { + local backup="${1:-true}" + + if [[ ! -d "$MODS_DIR" ]]; then + log_info "Mods 目录不存在,无需清理" + return 0 + fi + + local count=$(find "$MODS_DIR" -name "*.jar" 2>/dev/null | wc -l) + + if [[ $count -eq 0 ]]; then + log_info "没有 mods 需要清理" + return 0 + fi + + log_warning "准备清理 $count 个 mods" + + # 创建备份 + if [[ "$backup" == "true" ]]; then + create_backup "$MODS_DIR" + fi + + # 删除所有 jar 文件 + find "$MODS_DIR" -name "*.jar" -type f -delete + + log_success "Mods 清理完成" +} + +# 更新所有 mods +update_mods() { + log_info "更新所有 mods" + + # 备份并清理现有 mods + clean_mods true + + # 重新下载 + download_all_mods +} + +# 检查 mods 状态 +check_mods() { + log_info "检查 Mods 状态" + + if [[ ! -d "$MODS_DIR" ]]; then + log_warning "Mods 目录不存在" + return 1 + fi + + local jar_count=$(find "$MODS_DIR" -name "*.jar" 2>/dev/null | wc -l) + local total_size=$(du -sh "$MODS_DIR" 2>/dev/null | cut -f1) + + log_info "Mods 数量: $jar_count" + log_info "总大小: $total_size" + + # 检查必需的核心 mods + local required_mods=("fabric-api") + local missing_count=0 + + for mod in "${required_mods[@]}"; do + if ! find "$MODS_DIR" -name "*${mod}*.jar" | grep -q .; then + log_warning "缺少核心 mod: $mod" + ((missing_count++)) + fi + done + + if [[ $missing_count -gt 0 ]]; then + log_warning "缺少 $missing_count 个核心 mods,建议运行下载命令" + else + log_success "核心 mods 完整" + fi + + return 0 +} + +# ============================================ +# 主函数 +# ============================================ + +show_usage() { + cat </dev/null || echo "$uptime") + + printf "${CYAN}启动时间:${NC} %s\n" "$uptime" + + # 端口映射 + local ports=$(docker port "$CONTAINER_NAME" 2>/dev/null | grep "25565" | head -1) + printf "${CYAN}服务端口:${NC} %s\n" "${ports:-未映射}" + + # 容器 IP + local container_ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CONTAINER_NAME") + printf "${CYAN}容器 IP:${NC} %s\n" "${container_ip:-未知}" + + log_separator +} + +# ============================================ +# 玩家信息 +# ============================================ + +show_players() { + log_info "在线玩家" + log_separator + + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log_warning "容器未运行" + return 1 + fi + + # 尝试使用 rcon 获取玩家列表 + local player_list=$(docker exec "$CONTAINER_NAME" rcon-cli list 2>/dev/null || echo "无法获取玩家信息") + + if [[ "$player_list" =~ "There are" ]]; then + echo "$player_list" + else + log_warning "无法获取玩家信息(可能需要配置 RCON)" + fi + + log_separator +} + +# ============================================ +# 日志监控 +# ============================================ + +show_recent_logs() { + local lines="${1:-20}" + + log_info "最近 $lines 行日志" + log_separator + + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log_warning "容器未运行" + return 1 + fi + + docker logs --tail "$lines" "$CONTAINER_NAME" 2>&1 + + log_separator +} + +# 监控日志中的错误 +check_errors() { + log_info "检查错误日志" + log_separator + + if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log_warning "容器未运行" + return 1 + fi + + local error_count=$(docker logs --tail 1000 "$CONTAINER_NAME" 2>&1 | grep -ic "error" || echo "0") + local warn_count=$(docker logs --tail 1000 "$CONTAINER_NAME" 2>&1 | grep -ic "warn" || echo "0") + + if [[ $error_count -gt 0 ]]; then + log_warning "发现 $error_count 个错误" + else + log_success "无错误日志" + fi + + if [[ $warn_count -gt 0 ]]; then + log_warning "发现 $warn_count 个警告" + fi + + log_separator +} + +# ============================================ +# 完整状态报告 +# ============================================ + +show_full_status() { + log_info "=== Minecraft 服务器完整状态 ===" + log_separator + + # 检查容器 + if ! check_container; then + return 1 + fi + + log_separator + + # 服务器信息 + show_server_info + + # 资源使用 + show_resource_usage + + # 玩家信息 + show_players + + # 错误检查 + check_errors + + log_success "状态检查完成" +} + +# ============================================ +# 持续监控 +# ============================================ + +watch_mode() { + local interval="${1:-5}" + + log_info "开始持续监控(每 ${interval}s 刷新,Ctrl+C 退出)" + log_separator + + while true; do + clear + echo "=== Minecraft 服务器监控 ===" + echo "刷新时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "" + + show_server_info 2>/dev/null || true + show_resource_usage 2>/dev/null || true + show_players 2>/dev/null || true + + sleep "$interval" + done +} + +# ============================================ +# 主函数 +# ============================================ + +show_usage() { + cat </dev/null | wc -l) + + if [[ $mods_count -eq 0 ]]; then + log_warning "未找到任何 mods" + log_info "建议运行: make minecraft-mods-download" + else + log_success "发现 $mods_count 个 mods" + fi +} + +# ============================================ +# 权限设置 +# ============================================ + +fix_permissions() { + log_info "修复文件权限..." + + # 确保脚本可执行 + find "$PROJECT_ROOT/scripts" -name "*.sh" -type f -exec chmod +x {} \; + + # 确保数据目录可写 + if [[ -d "$PROJECT_ROOT/data" ]]; then + chmod -R 755 "$PROJECT_ROOT/data" + fi + + log_success "权限修复完成" +} + +# ============================================ +# 显示配置摘要 +# ============================================ + +show_summary() { + log_separator + log_success "初始化完成!" + echo "" + + log_info "配置摘要:" + log_info " 项目目录: $PROJECT_ROOT" + log_info " Docker Compose: $(get_docker_compose_cmd)" + log_info " Mods 数量: $(find "$PROJECT_ROOT/mods" -name "*.jar" 2>/dev/null | wc -l)" + + echo "" + log_info "后续步骤:" + log_info " 1. 检查并编辑 .env 文件,设置 RCON_PASSWORD" + log_info " 2. 如需下载 mods: make minecraft-mods-download" + log_info " 3. 启动服务器: make minecraft-up" + log_info " 4. 查看日志: make minecraft-logs" + + echo "" + show_log_file +} + +# ============================================ +# 主函数 +# ============================================ + +main() { + # 初始化日志 + init_log "Minecraft 服务器初始化" + log_system_info + + log_info "开始初始化 Minecraft 服务器环境..." + log_separator + + # 执行初始化步骤 + check_prerequisites + init_directories + check_env_config + init_mods + fix_permissions + + # 显示摘要 + show_summary +} + +# 执行主函数 +main "$@" diff --git a/minecraft/scripts/utils.sh b/minecraft/scripts/utils.sh new file mode 100755 index 0000000..f33ce4a --- /dev/null +++ b/minecraft/scripts/utils.sh @@ -0,0 +1,339 @@ +#!/usr/bin/env bash +# Minecraft 自动化工具 - 通用工具库 +# 提供日志、环境检查、Docker操作等通用函数 + +# ============================================ +# 颜色定义 +# ============================================ +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly CYAN='\033[0;36m' +readonly NC='\033[0m' # No Color + +# ============================================ +# 全局变量 +# ============================================ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +LOG_DIR="$PROJECT_ROOT/logs" +LOG_FILE="$LOG_DIR/automation-$(date +%Y%m%d).log" + +# 确保日志目录存在 +mkdir -p "$LOG_DIR" + +# ============================================ +# 日志函数 +# ============================================ + +# 初始化日志文件 +init_log() { + local title="${1:-Minecraft 自动化任务}" + { + echo "==========================================" + echo "$title" + echo "开始时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "工作目录: $(pwd)" + echo "用户: $(whoami)" + echo "==========================================" + echo "" + } >>"$LOG_FILE" +} + +# 记录信息 +log_info() { + local msg="$1" + local timestamp=$(date '+%H:%M:%S') + echo -e "${BLUE}[INFO]${NC} ${timestamp} $msg" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $msg" >>"$LOG_FILE" +} + +# 记录成功 +log_success() { + local msg="$1" + local timestamp=$(date '+%H:%M:%S') + echo -e "${GREEN}[✓]${NC} ${timestamp} $msg" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $msg" >>"$LOG_FILE" +} + +# 记录警告 +log_warning() { + local msg="$1" + local timestamp=$(date '+%H:%M:%S') + echo -e "${YELLOW}[⚠]${NC} ${timestamp} $msg" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARNING] $msg" >>"$LOG_FILE" +} + +# 记录错误 +log_error() { + local msg="$1" + local timestamp=$(date '+%H:%M:%S') + echo -e "${RED}[✗]${NC} ${timestamp} $msg" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $msg" >>"$LOG_FILE" +} + +# 记录命令执行 +log_command() { + local cmd="$1" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [COMMAND] $cmd" >>"$LOG_FILE" +} + +# 记录分隔线 +log_separator() { + echo "" | tee -a "$LOG_FILE" + echo "==========================================">>"$LOG_FILE" + echo "" | tee -a "$LOG_FILE" +} + +# 显示日志位置 +show_log_file() { + log_info "详细日志: $LOG_FILE" +} + +# ============================================ +# 环境检查函数 +# ============================================ + +# 检查命令是否存在 +check_command() { + local cmd="$1" + if ! command -v "$cmd" &>/dev/null; then + log_error "未找到命令: $cmd" + return 1 + fi + return 0 +} + +# 检查Docker环境 +check_docker() { + log_info "检查 Docker 环境..." + + if ! check_command docker; then + log_error "Docker 未安装。请访问: https://docs.docker.com/get-docker/" + return 1 + fi + + if ! docker info &>/dev/null; then + log_error "Docker 服务未运行" + return 1 + fi + + if ! command -v docker compose &>/dev/null && ! command -v docker-compose &>/dev/null; then + log_error "Docker Compose 未安装" + return 1 + fi + + log_success "Docker 环境正常" + return 0 +} + +# 检查文件是否存在 +check_file() { + local file="$1" + local desc="${2:-文件}" + + if [[ ! -f "$file" ]]; then + log_error "$desc 不存在: $file" + return 1 + fi + return 0 +} + +# 检查目录是否存在 +check_dir() { + local dir="$1" + local desc="${2:-目录}" + + if [[ ! -d "$dir" ]]; then + log_error "$desc 不存在: $dir" + return 1 + fi + return 0 +} + +# ============================================ +# Docker操作函数 +# ============================================ + +# 获取Docker Compose命令 +get_docker_compose_cmd() { + if command -v docker &>/dev/null && docker compose version &>/dev/null; then + echo "docker compose" + elif command -v docker-compose &>/dev/null; then + echo "docker-compose" + else + log_error "未找到 Docker Compose" + return 1 + fi +} + +# 检查容器状态 +check_container_status() { + local container_name="${1:-mc-fabric-1.21.1}" + + if docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then + echo "running" + return 0 + elif docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then + echo "stopped" + return 1 + else + echo "not_found" + return 2 + fi +} + +# 等待容器启动 +wait_for_container() { + local container_name="${1:-mc-fabric-1.21.1}" + local timeout="${2:-60}" + local elapsed=0 + + log_info "等待容器启动: $container_name" + + while [[ $elapsed -lt $timeout ]]; do + if docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then + log_success "容器已启动" + return 0 + fi + sleep 2 + ((elapsed += 2)) + echo -n "." + done + + echo "" + log_error "容器启动超时" + return 1 +} + +# 执行Docker Compose命令 +docker_compose_exec() { + local cmd="$1" + shift + local compose_cmd=$(get_docker_compose_cmd) + + if [[ -z "$compose_cmd" ]]; then + return 1 + fi + + log_command "$compose_cmd $cmd $*" + cd "$PROJECT_ROOT" && $compose_cmd $cmd "$@" +} + +# ============================================ +# 文件操作函数 +# ============================================ + +# 创建备份 +create_backup() { + local source="$1" + local backup_dir="${2:-$PROJECT_ROOT/backups}" + local name=$(basename "$source") + local timestamp=$(date +%Y%m%d-%H%M%S) + local backup_file="$backup_dir/${name}-${timestamp}.tar.gz" + + mkdir -p "$backup_dir" + + log_info "创建备份: $name" + + if [[ -d "$source" ]]; then + if tar -czf "$backup_file" -C "$(dirname "$source")" "$name" 2>/dev/null; then + local size=$(du -h "$backup_file" | cut -f1) + log_success "备份完成: $(basename "$backup_file") ($size)" + echo "$backup_file" + return 0 + fi + elif [[ -f "$source" ]]; then + if cp "$source" "$backup_file"; then + log_success "备份完成: $(basename "$backup_file")" + echo "$backup_file" + return 0 + fi + fi + + log_error "备份失败: $source" + return 1 +} + +# 清理旧备份 +cleanup_old_backups() { + local backup_dir="$1" + local keep_count="${2:-5}" + + if [[ ! -d "$backup_dir" ]]; then + return 0 + fi + + log_info "清理旧备份(保留最近 $keep_count 个)" + + local backup_count=$(find "$backup_dir" -name "*.tar.gz" | wc -l) + + if [[ $backup_count -le $keep_count ]]; then + log_info "当前备份数: $backup_count,无需清理" + return 0 + fi + + find "$backup_dir" -name "*.tar.gz" -type f -printf '%T@ %p\n' | \ + sort -rn | \ + tail -n +$((keep_count + 1)) | \ + cut -d' ' -f2- | \ + while read -r file; do + rm -f "$file" + log_info "删除旧备份: $(basename "$file")" + done + + log_success "备份清理完成" +} + +# ============================================ +# 网络检查函数 +# ============================================ + +# 检查网络连接 +check_network() { + local url="${1:-https://api.modrinth.com/v2/project/fabric-api}" + + if curl -s --connect-timeout 5 "$url" >/dev/null; then + return 0 + else + log_warning "网络连接失败: $url" + return 1 + fi +} + +# ============================================ +# 系统信息函数 +# ============================================ + +# 记录系统信息 +log_system_info() { + { + echo "=== 系统信息 ===" + echo "主机: $(hostname)" + echo "系统: $(uname -s) $(uname -r)" + echo "架构: $(uname -m)" + + if command -v docker &>/dev/null; then + echo "Docker: $(docker --version 2>/dev/null || echo '未安装')" + fi + + if command -v free &>/dev/null; then + echo "内存: $(free -h | grep Mem: | awk '{print $3 "/" $2}')" + fi + + echo "磁盘: $(df -h "$PROJECT_ROOT" | tail -1 | awk '{print $3 "/" $2 " (已用 " $5 ")"}')" + echo "====================" + } >>"$LOG_FILE" +} + +# ============================================ +# 主函数 +# ============================================ + +# 如果直接执行此脚本 +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + echo "这是一个工具库,请通过 source 命令加载" + echo "用法: source $(basename "$0")" + exit 1 +fi