diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e188a08 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +node_modules +dist +.git +.gitignore +.env +.env.local +*.log +.DS_Store +data +*.db +*.sqlite +*.sqlite3 +tests +.vscode +.idea +*.md +!README.md +coverage +.nyc_output + diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..d0f5c5f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,28 @@ +name: Deploy to Server + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Deploy to server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.SERVER_SSH_KEY }} + script: | + cd /path/to/cloud-mcp + git pull origin main + ./deploy.sh --rebuild + diff --git a/.gitignore b/.gitignore index 76faad8..6c673e3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ data/ *.sqlite *.sqlite3 +# Deployment +deploy.log +*.log + diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..ed1f034 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,308 @@ +# 部署文档 + +## 概述 + +本项目支持使用 Docker 进行部署,可以通过手动脚本或 Gitea Webhook 自动部署。 + +## 前置要求 + +- Docker 和 Docker Compose 已安装 +- Git 已安装 +- 服务器有足够的资源(建议至少 512MB 内存) + +## 快速开始 + +### 1. 克隆项目到服务器 + +```bash +git clone /path/to/cloud-mcp +cd /path/to/cloud-mcp +``` + +### 2. 配置环境变量 + +复制环境变量模板并编辑: + +```bash +cp env.template .env +nano .env # 或使用你喜欢的编辑器 +``` + +配置必要的环境变量(NAS、服务器、路由器等)。 + +### 3. 创建数据目录 + +```bash +mkdir -p data +``` + +### 4. 首次部署 + +```bash +./deploy.sh --rebuild +``` + +## 部署方式 + +### 方式一:手动部署脚本 + +使用 `deploy.sh` 脚本进行手动部署: + +```bash +# 基本部署(使用缓存) +./deploy.sh + +# 从 Git 拉取最新代码并部署 +./deploy.sh --pull + +# 强制重新构建(不使用缓存) +./deploy.sh --rebuild + +# 拉取代码并重新构建 +./deploy.sh --pull --rebuild +``` + +### 方式二:Gitea Webhook 自动部署 + +#### 1. 配置部署脚本 + +编辑 `deploy-gitea.sh`,更新以下变量: + +```bash +PROJECT_DIR="/path/to/cloud-mcp" # 你的项目路径 +BRANCH="main" # 或 "master" +``` + +#### 2. 在 Gitea 中配置 Webhook + +1. 进入仓库:**Settings** -> **Webhooks** -> **Add Webhook** +2. 配置如下: + - **Target URL**: `http://your-server:port/gitea-webhook` (如果使用 webhook 服务器) + - 或者直接在服务器上设置 Git Hook +3. **Content Type**: `application/json` +4. **Secret**: (可选) 设置一个密钥 +5. **Events**: 选择 `Push` 事件 + +#### 3. 设置 Git Hook(推荐) + +在服务器上设置 Git post-receive hook: + +```bash +cd /path/to/cloud-mcp/.git/hooks +cat > post-receive << 'EOF' +#!/bin/bash +cd /path/to/cloud-mcp +/path/to/cloud-mcp/deploy-gitea.sh +EOF +chmod +x post-receive +``` + +或者使用 Gitea 的 Webhook 触发脚本: + +```bash +# 安装 webhook 服务器(如 webhook) +# 然后配置 webhook 调用 deploy-gitea.sh +``` + +#### 4. 测试自动部署 + +```bash +# 在本地推送代码 +git push origin main + +# 在服务器上查看日志 +tail -f /path/to/cloud-mcp/deploy.log +``` + +## Docker Compose 命令 + +### 基本操作 + +```bash +# 启动服务 +docker-compose up -d + +# 停止服务 +docker-compose down + +# 查看日志 +docker-compose logs -f + +# 重启服务 +docker-compose restart + +# 查看状态 +docker-compose ps +``` + +### 手动更新 + +```bash +# 拉取最新代码 +git pull origin main + +# 重新构建并启动 +docker-compose build --no-cache +docker-compose up -d +``` + +## 部署脚本命令 + +`deploy-gitea.sh` 支持多种操作: + +```bash +# 完整部署(默认) +./deploy-gitea.sh + +# 仅拉取代码 +./deploy-gitea.sh --pull-only + +# 重新构建 +./deploy-gitea.sh --rebuild + +# 查看状态 +./deploy-gitea.sh --status + +# 查看日志 +./deploy-gitea.sh --logs + +# 停止服务 +./deploy-gitea.sh --stop + +# 启动服务 +./deploy-gitea.sh --start + +# 重启服务 +./deploy-gitea.sh --restart +``` + +## 数据持久化 + +数据存储在 `./data` 目录,通过 Docker volume 挂载: + +- 代码片段:`data/codeSnippets.json` +- 笔记:`data/notes.json` +- 任务:`data/tasks.json` +- 其他数据文件... + +**重要**:定期备份 `data` 目录! + +## 监控和维护 + +### 查看容器状态 + +```bash +docker ps --filter "name=cloud-mcp" +``` + +### 查看日志 + +```bash +# 实时日志 +docker logs -f cloud-mcp + +# 最近 100 行 +docker logs --tail 100 cloud-mcp +``` + +### 进入容器 + +```bash +docker exec -it cloud-mcp /bin/sh +``` + +### 清理资源 + +```bash +# 清理未使用的镜像 +docker image prune -f + +# 清理未使用的容器和网络 +docker system prune -f +``` + +## 故障排查 + +### 容器无法启动 + +1. 检查日志:`docker logs cloud-mcp` +2. 检查环境变量:确保 `.env` 文件配置正确 +3. 检查端口冲突:确保没有其他服务占用端口 +4. 检查磁盘空间:`df -h` + +### 数据丢失 + +1. 检查 volume 挂载:`docker inspect cloud-mcp | grep Mounts` +2. 检查数据目录权限 +3. 从备份恢复数据 + +### 自动部署不工作 + +1. 检查 webhook 配置 +2. 检查脚本权限:`chmod +x deploy-gitea.sh` +3. 检查日志:`tail -f deploy.log` +4. 手动测试脚本:`./deploy-gitea.sh --status` + +## 安全建议 + +1. **不要将 `.env` 文件提交到 Git** +2. 使用强密码和 SSH 密钥 +3. 定期更新 Docker 镜像 +4. 限制服务器访问权限 +5. 使用防火墙限制端口访问 +6. 定期备份数据 + +## 性能优化 + +1. **资源限制**:在 `docker-compose.yml` 中添加资源限制: + +```yaml +services: + cloud-mcp: + # ... + deploy: + resources: + limits: + cpus: '1' + memory: 512M + reservations: + cpus: '0.5' + memory: 256M +``` + +2. **日志轮转**:配置 Docker 日志驱动限制日志大小 + +3. **健康检查**:已包含在 `docker-compose.yml` 中 + +## 更新流程 + +### 开发流程 + +1. 在本地开发并测试 +2. 提交代码到 Git +3. 推送到 Gitea 仓库 +4. Webhook 自动触发部署(如果配置) +5. 或手动运行 `./deploy-gitea.sh` + +### 回滚 + +如果需要回滚到之前的版本: + +```bash +# 查看提交历史 +git log + +# 回滚到指定提交 +git checkout + +# 重新部署 +./deploy-gitea.sh --rebuild +``` + +## 支持 + +如有问题,请查看: +- 项目 README.md +- 测试文档:tests/README.md +- 日志文件:deploy.log + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d236b0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Dockerfile for Cloud MCP Server +FROM oven/bun:1 AS base + +WORKDIR /app + +# Copy package files +COPY package.json bun.lockb* ./ + +# Install dependencies +RUN bun install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the project +RUN bun run build + +# Production stage +FROM oven/bun:1-slim + +WORKDIR /app + +# Copy package files and install production dependencies +COPY package.json bun.lockb* ./ +RUN bun install --frozen-lockfile --production + +# Copy built files from base stage +COPY --from=base /app/dist ./dist +COPY --from=base /app/src ./src + +# Create data directory for persistent storage +RUN mkdir -p /app/data + +# Expose port (if needed for health checks) +EXPOSE 3000 + +# Set environment variables +ENV NODE_ENV=production + +# Run the MCP server +CMD ["bun", "run", "src/index.ts"] + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ae8587 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +# Makefile for Cloud MCP Deployment + +.PHONY: help build up down restart logs status deploy clean + +# Default target +help: + @echo "Cloud MCP Deployment Commands:" + @echo " make build - Build Docker image" + @echo " make up - Start container" + @echo " make down - Stop container" + @echo " make restart - Restart container" + @echo " make logs - Show container logs" + @echo " make status - Show container status" + @echo " make deploy - Full deployment (pull, build, restart)" + @echo " make clean - Clean up unused Docker resources" + +build: + docker-compose build + +up: + docker-compose up -d + +down: + docker-compose down + +restart: + docker-compose restart + +logs: + docker-compose logs -f + +status: + docker-compose ps + @echo "" + @docker logs --tail 20 cloud-mcp 2>/dev/null || echo "Container not running" + +deploy: + @./deploy.sh --pull --rebuild + +clean: + docker system prune -f + diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..6f5f132 --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,113 @@ +# 快速部署指南 + +## 服务器端设置(一次性) + +### 1. 克隆项目 + +```bash +git clone /opt/cloud-mcp +cd /opt/cloud-mcp +``` + +### 2. 配置环境变量 + +```bash +cp env.template .env +nano .env # 编辑配置 +``` + +### 3. 设置脚本权限 + +```bash +chmod +x deploy.sh deploy-gitea.sh +``` + +### 4. 首次部署 + +```bash +./deploy.sh --rebuild +``` + +## Gitea Webhook 自动部署设置 + +### 方法一:使用 Git Hook(推荐) + +```bash +# 在服务器上设置 post-receive hook +cd /opt/cloud-mcp +git config receive.denyCurrentBranch ignore + +# 创建 hook 脚本 +cat > .git/hooks/post-receive << 'EOF' +#!/bin/bash +cd /opt/cloud-mcp +git checkout -f +./deploy-gitea.sh +EOF + +chmod +x .git/hooks/post-receive +``` + +### 方法二:使用 Gitea Webhook + +1. 在 Gitea 仓库设置中添加 Webhook +2. URL: `http://your-server:port/hooks/deploy` (需要 webhook 服务器) +3. 或使用 SSH 方式触发部署脚本 + +## 日常使用 + +### 手动部署 + +```bash +# 在服务器上 +cd /opt/cloud-mcp +./deploy-gitea.sh +``` + +### 查看状态 + +```bash +./deploy-gitea.sh --status +``` + +### 查看日志 + +```bash +./deploy-gitea.sh --logs +# 或 +docker logs -f cloud-mcp +``` + +## 使用 Makefile(可选) + +```bash +make deploy # 完整部署 +make status # 查看状态 +make logs # 查看日志 +make restart # 重启 +make down # 停止 +make up # 启动 +``` + +## 更新流程 + +1. **本地开发** → 提交代码 → 推送到 Gitea +2. **自动触发** → Webhook/Hook 自动运行 `deploy-gitea.sh` +3. **完成** → 容器自动更新并重启 + +## 故障排查 + +```bash +# 检查容器状态 +docker ps -a | grep cloud-mcp + +# 查看详细日志 +docker logs cloud-mcp + +# 检查部署日志 +tail -f deploy.log + +# 手动重启 +./deploy-gitea.sh --restart +``` + diff --git a/deploy-gitea.sh b/deploy-gitea.sh new file mode 100755 index 0000000..2acb0a3 --- /dev/null +++ b/deploy-gitea.sh @@ -0,0 +1,238 @@ +#!/bin/bash + +# Gitea Webhook Deployment Script +# This script is triggered by Gitea webhook on push events +# Configure in Gitea: Repository -> Settings -> Webhooks -> Add Webhook + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +PROJECT_DIR="/path/to/cloud-mcp" # Update this to your project path +CONTAINER_NAME="cloud-mcp" +COMPOSE_FILE="docker-compose.yml" +BRANCH="main" # or "master" + +# Log file +LOG_FILE="${PROJECT_DIR}/deploy.log" + +# Functions +log() { + local level=$1 + shift + local message="$@" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE" +} + +log_info() { + log "INFO" "${GREEN}$@${NC}" +} + +log_warn() { + log "WARN" "${YELLOW}$@${NC}" +} + +log_error() { + log "ERROR" "${RED}$@${NC}" +} + +# Check if running in correct directory +check_directory() { + if [ ! -f "$PROJECT_DIR/docker-compose.yml" ]; then + log_error "Project directory not found: $PROJECT_DIR" + exit 1 + fi + cd "$PROJECT_DIR" + log_info "Working directory: $(pwd)" +} + +# Check if Docker is running +check_docker() { + if ! docker info > /dev/null 2>&1; then + log_error "Docker is not running" + exit 1 + fi + log_info "Docker is running" +} + +# Pull latest code +pull_latest() { + log_info "Pulling latest code from repository..." + + # Fetch latest changes + git fetch origin "$BRANCH" || { + log_error "Failed to fetch from repository" + exit 1 + } + + # Check if there are updates + LOCAL=$(git rev-parse @) + REMOTE=$(git rev-parse "origin/${BRANCH}") + BASE=$(git merge-base @ "origin/${BRANCH}") + + if [ "$LOCAL" = "$REMOTE" ]; then + log_info "Already up to date" + return 1 + elif [ "$LOCAL" = "$BASE" ]; then + log_info "New commits found, pulling..." + git pull origin "$BRANCH" || { + log_error "Failed to pull from repository" + exit 1 + } + return 0 + else + log_warn "Local branch is ahead or diverged. Resetting to remote..." + git reset --hard "origin/${BRANCH}" || { + log_error "Failed to reset branch" + exit 1 + } + return 0 + fi +} + +# Build Docker image +build_image() { + log_info "Building Docker image..." + docker-compose -f "$COMPOSE_FILE" build --no-cache || { + log_error "Failed to build Docker image" + exit 1 + } + log_info "Docker image built successfully" +} + +# Stop existing container +stop_container() { + log_info "Stopping existing container..." + docker-compose -f "$COMPOSE_FILE" down || { + log_warn "Failed to stop container (might not exist)" + } + log_info "Container stopped" +} + +# Start container +start_container() { + log_info "Starting container..." + docker-compose -f "$COMPOSE_FILE" up -d || { + log_error "Failed to start container" + exit 1 + } + log_info "Container started" +} + +# Verify deployment +verify_deployment() { + log_info "Verifying deployment..." + sleep 3 + + if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log_info "Container is running" + + # Check container health + local status=$(docker inspect --format='{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null) + if [ "$status" = "running" ]; then + log_info "Deployment successful!" + return 0 + else + log_error "Container is not running (status: $status)" + return 1 + fi + else + log_error "Container not found" + return 1 + fi +} + +# Show container logs +show_logs() { + log_info "Recent container logs:" + docker logs --tail 30 "$CONTAINER_NAME" 2>&1 || log_warn "Could not fetch logs" +} + +# Cleanup old images (optional) +cleanup_images() { + log_info "Cleaning up unused Docker images..." + docker image prune -f || log_warn "Failed to cleanup images" +} + +# Main deployment flow +main() { + log_info "=========================================" + log_info "Starting deployment process" + log_info "=========================================" + + check_directory + check_docker + + # Pull latest code + if ! pull_latest; then + log_info "No updates to deploy" + exit 0 + fi + + # Build and deploy + build_image + stop_container + start_container + + # Verify + if verify_deployment; then + show_logs + cleanup_images + log_info "=========================================" + log_info "Deployment completed successfully!" + log_info "=========================================" + else + log_error "Deployment verification failed" + show_logs + exit 1 + fi +} + +# Handle script arguments +case "${1:-}" in + --pull-only) + check_directory + pull_latest + ;; + --rebuild) + check_directory + check_docker + build_image + stop_container + start_container + verify_deployment + ;; + --status) + check_directory + docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + show_logs + ;; + --logs) + check_directory + docker logs -f "$CONTAINER_NAME" + ;; + --stop) + check_directory + stop_container + ;; + --start) + check_directory + start_container + ;; + --restart) + check_directory + stop_container + start_container + ;; + *) + main + ;; +esac + diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..0e50c27 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# Cloud MCP Deployment Script +# This script builds and deploys the MCP server using Docker +# Usage: ./deploy.sh [--pull] [--rebuild] + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +CONTAINER_NAME="cloud-mcp" +IMAGE_NAME="cloud-mcp" +COMPOSE_FILE="docker-compose.yml" + +# Functions +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if Docker is running +check_docker() { + if ! docker info > /dev/null 2>&1; then + log_error "Docker is not running. Please start Docker and try again." + exit 1 + fi + log_info "Docker is running" +} + +# Pull latest code from git (if --pull flag is set) +pull_latest() { + if [[ "$1" == "--pull" ]] || [[ "$*" == *"--pull"* ]]; then + log_info "Pulling latest code from git..." + git pull origin main || git pull origin master || log_warn "Failed to pull from git, continuing with local code" + fi +} + +# Build Docker image +build_image() { + local rebuild=false + if [[ "$*" == *"--rebuild"* ]]; then + rebuild=true + fi + + log_info "Building Docker image..." + + if [ "$rebuild" = true ]; then + log_info "Force rebuilding image (no cache)..." + docker-compose -f "$COMPOSE_FILE" build --no-cache + else + docker-compose -f "$COMPOSE_FILE" build + fi + + log_info "Docker image built successfully" +} + +# Stop existing container +stop_container() { + if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + log_info "Stopping existing container..." + docker-compose -f "$COMPOSE_FILE" down + log_info "Container stopped" + else + log_info "No existing container found" + fi +} + +# Start container +start_container() { + log_info "Starting container..." + docker-compose -f "$COMPOSE_FILE" up -d + log_info "Container started" +} + +# Show container status +show_status() { + log_info "Container status:" + docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + + log_info "Container logs (last 20 lines):" + docker logs --tail 20 "${CONTAINER_NAME}" 2>&1 || log_warn "Could not fetch logs" +} + +# Main deployment flow +main() { + log_info "Starting deployment..." + + check_docker + pull_latest "$@" + build_image "$@" + stop_container + start_container + + # Wait a moment for container to start + sleep 2 + + show_status + + log_info "Deployment completed!" + log_info "To view logs: docker logs -f ${CONTAINER_NAME}" + log_info "To stop: docker-compose -f ${COMPOSE_FILE} down" +} + +# Run main function +main "$@" + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..90d3329 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.8" + +services: + cloud-mcp: + build: + context: . + dockerfile: Dockerfile + container_name: cloud-mcp + restart: unless-stopped + volumes: + # Mount data directory for persistent storage + - ./data:/app/data + # Mount .env file if exists (optional, can use environment variables instead) + - ./.env:/app/.env:ro + environment: + - NODE_ENV=production + # Add your environment variables here or use .env file + # - NAS_HOST=${NAS_HOST} + # - SERVER_HOST=${SERVER_HOST} + # etc. + stdin_open: true + tty: true + # Health check (optional) + healthcheck: + test: ["CMD", "bun", "--version"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s