# CI/CD 部署指南 (OneDev + Docker Compose)

## 1. 简介

本文档旨在指导如何为 `mqtt-charging-system` 项目（包含 `springboot-init-main` 后端和 `charging_web_app` 前端）配置一套基于 OneDev 和 Docker Compose 的持续集成与持续部署 (CI/CD) 流程。实现目标是当代码提交到 OneDev 内置 Git 仓库的特定分支后，自动触发 OneDev 流水线，该流水线负责在部署服务器上构建应用 Docker 镜像，并使用 Docker Compose 重启包括数据库、MQTT Broker 和应用在内的所有服务。

**采用技术栈：**

*   **CI/CD 平台与代码仓库**: OneDev (自建，包含内置 Git 服务)
*   **构建与部署脚本**: `.onedev-buildspec.yml` (OneDev 流水线定义)
*   **容器化**: Docker
*   **服务编排**: Docker Compose
*   **部署目标**: 运行 OneDev Agent 和 Docker 的 Linux 服务器

## 2. 先决条件

在开始配置此部署流程之前，请确保以下条件已满足：

*   **OneDev 服务器**: 
    *   已在一台服务器上成功安装并运行 OneDev 服务。参考 OneDev 官方文档 ([https://onedev.io/docs/installation](https://onedev.io/docs/installation)) 进行安装。
    *   OneDev 服务器应具有足够的资源（CPU、内存、磁盘）。
*   **线上应用/部署服务器 (OneDev Agent 服务器)**:
    *   一台 Linux 服务器，用于运行 OneDev Agent、构建 Docker 镜像以及通过 Docker Compose 运行所有服务容器 (数据库、Broker、后端、前端)。此服务器可以是 OneDev 服务器本身（不推荐用于生产），也可以是独立的机器。
    *   服务器已安装 Docker Engine, Docker Compose, 和 Git。
    *   服务器防火墙已配置，允许访问应用所需端口（例如后端 7529，前端 3000，MQTT 1883, 18083 等），以及 OneDev Agent 与 OneDev Server 通信的端口。
*   **对 Docker, Docker Compose, Git, YAML 和 OneDev 有基本了解**。

## 3. CI/CD 流程概览

1.  **代码提交**: 开发者将代码改动推送到 OneDev 内置 Git 仓库的 `master` 分支 (或其他配置的触发分支)。
2.  **OneDev 流水线触发**: OneDev 检测到其内置仓库的变更，根据 `.onedev-buildspec.yml` 中的定义触发相应的流水线。
3.  **Agent 执行作业**: OneDev Server 将流水线中的作业分配给匹配的 Agent (运行在部署服务器上)。
4.  **流水线执行 (`.onedev-buildspec.yml`)**:
    *   **检出代码**: Agent 从仓库拉取最新的代码。
    *   **构建应用 Docker 镜像**: Agent 在本地构建后端和前端应用的新 Docker 镜像 (例如 `mqtt-springboot-app:latest`, `mqtt-nextjs-app:latest`)。
    *   **准备 `.env` 文件**: Agent 从 OneDev 项目配置的 Secrets 中获取敏感数据 (如数据库密码)，并生成 `.env` 文件。
    *   **重启服务**: Agent 使用项目中的 `docker-compose.yml` 和新生成的 `.env` 文件，通过 `docker-compose` 命令停止旧服务，并基于新的镜像和配置启动所有服务。
5.  **监控与日志**: 开发者可以在 OneDev 的 Web 界面上实时监控流水线执行状态、查看各步骤的详细日志。

## 4. 详细步骤

### 步骤 4.1: OneDev 服务器安装与配置

1.  **安装 OneDev**: 
    *   访问 OneDev 官方文档 ([https://onedev.io/docs/installation](https://onedev.io/docs/installation))，选择适合您环境的安装方法 (例如 Docker 运行，或直接部署 WAR 包)。
    *   **强烈建议使用 Docker 运行 OneDev Server**，方便管理和升级。官方文档提供了 Docker 命令示例。
    *   确保 OneDev 数据持久化配置正确。
2.  **初始化 OneDev**: 首次访问 OneDev URL，按照向导完成管理员账户设置和基本配置。

### 步骤 4.2: OneDev Agent 安装与配置 (在部署服务器上)

OneDev Agent 负责执行流水线中定义的作业。您可以选择直接在部署服务器上运行 Agent (通过 `java -jar agent.jar ...`)，或者更推荐的方式是 **通过 Docker 容器运行 Agent**。您已确认使用 Docker 容器运行 Agent。

1.  **登录 OneDev**: 使用管理员账户登录您的 OneDev 服务器。
2.  **导航到 Agents 管理页面**: 通常在 `Administration` -> `Agents` (或者新版可能是 `Administration` -> `Job Executors`)。
3.  **连接新 Agent/Executor 指引**: OneDev 界面会提供如何连接新 Agent/Executor 的指引。选择 "Connect an agent by running agent docker container" (或类似描述)。

4.  **运行 Agent Docker 容器 (在部署服务器上)**:
    *   OneDev 会提供一个 `docker run` 命令示例，类似如下 (请从您的 OneDev 界面复制准确命令，它会包含特定的服务器 URL 和 Agent 令牌):
        ```bash
        docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v <path_to_agent_data_on_host>:/opt/onedev/agent <onedev_agent_image> <OneDev Server URL> <Agent Token>
        ```
    *   **关键参数解释**:
        *   `--rm`: 容器停止后自动删除 (可选，但常用于 Agent)。
        *   `-v /var/run/docker.sock:/var/run/docker.sock`: **非常重要 (Docker-out-of-Docker - DooD)**。这将宿主机的 Docker socket 挂载到 Agent 容器内部。这使得 Agent 容器可以控制宿主机的 Docker daemon 来执行 Docker 命令 (例如 `docker build`, `docker-compose up`)。我们当前的 `.onedev-buildspec.yml` 依赖此设置来在宿主机本地构建镜像和运行 Docker Compose。
        *   `-v <path_to_agent_data_on_host>:/opt/onedev/agent` (或 OneDev 推荐的其他挂载点): 用于持久化 Agent 的配置和工作数据。例如，您可以设置为 `-v /opt/onedev_agent_data:/opt/onedev/agent`。
        *   `<onedev_agent_image>`: OneDev 官方提供的 Agent Docker 镜像，例如 `1dev/agent` 或 `onedev/agent` (请参考 OneDev 界面的准确镜像名称)。
        *   `<OneDev Server URL>` 和 `<Agent Token>`: 由您的 OneDev 服务器提供，用于 Agent 连接和认证。
    *   **Agent 容器所需的工具**: 
        *   OneDev 官方的 Agent 镜像通常已包含 `git`。
        *   为了执行 `.onedev-buildspec.yml` 中的 `docker-compose` 命令，您需要确保 Agent 使用的 Docker 镜像**内部也包含 `docker-compose` CLI** (或者较新版本的 `docker compose` 插件)。如果官方 Agent 镜像不包含，您可能需要：
            *   在 `.onedev-buildspec.yml` 的 `executeShell` 步骤开始前，先在 Agent 容器内安装 `docker-compose`。
            *   或者，构建一个自定义的 Agent Docker 镜像，该镜像基于 OneDev 官方 Agent 镜像，并额外安装 `docker-compose`。
            *   或者，修改 `.onedev-buildspec.yml` 中的命令为 `docker compose` (注意空格)，如果 Agent 容器的 Docker CLI 支持 Compose V2 插件。
        *   我们当前的 `.onedev-buildspec.yml` 中的 `executeShell` 使用 `docker-compose` (带连字符)。

5.  **为 Agent 添加标签**: 
    *   Agent Docker 容器启动并成功连接到 OneDev Server 后，它会出现在 OneDev 的 Agents/Executors 列表中。
    *   点击该 Agent/Executor，进入其配置页面，添加一个标签，例如 `agent-for-mqtt-power`。这个标签将用于 `.onedev-buildspec.yml` 中的 `agentMatcher`，以确保作业在此 Agent 上运行。

6.  **确保宿主机 Docker 环境**: Agent 容器通过 DooD 控制宿主机的 Docker，所以确保部署服务器（宿主机）上已正确安装和配置了 Docker Engine 和 Docker Compose (如果 `docker-compose.yml` 依赖于宿主机上的 `docker-compose` CLI 版本特性)。

7.  **持久化运行 Agent Docker 容器 (推荐)**:
    *   为了确保 Agent 在服务器重启后也能自动运行，建议使用 Docker 的重启策略或通过 `systemd` 管理 Agent Docker 容器的启动。
    *   **使用 Docker 重启策略**:
        在 `docker run` 命令中添加 `--restart unless-stopped` 或 `--restart always`。
        ```bash
        docker run --restart unless-stopped --name onedev-agent-mqtt -d \
          -v /var/run/docker.sock:/var/run/docker.sock \
          -v /opt/onedev_agent_data_mqtt:/opt/onedev/agent \
          <onedev_agent_image> <OneDev Server URL> <Agent Token>
        ```
        *   `--name onedev-agent-mqtt`: 为容器指定一个名称，方便管理。
        *   `-d`: 后台运行容器。
    *   **通过 systemd 管理 (更灵活，但略复杂)**: 您可以创建一个 systemd 服务单元来启动和管理这个 Agent Docker 容器。
        *   示例 (`/etc/systemd/system/onedev-agent-docker.service`):
            ```ini
            [Unit]
            Description=OneDev Agent Docker Container
            Requires=docker.service
            After=docker.service

            [Service]
            Restart=always
            ExecStartPre=-/usr/bin/docker stop onedev-agent-mqtt
            ExecStartPre=-/usr/bin/docker rm onedev-agent-mqtt
            ExecStart=/usr/bin/docker run --name onedev-agent-mqtt \
                        -v /var/run/docker.sock:/var/run/docker.sock \
                        -v /opt/onedev_agent_data_mqtt:/opt/onedev/agent \
                        <onedev_agent_image> <OneDev Server URL> <Agent Token>
            ExecStop=/usr/bin/docker stop onedev-agent-mqtt

            [Install]
            WantedBy=multi-user.target
            ```
            *   **重要**: 替换占位符。确保路径和镜像是正确的。
            *   然后 `sudo systemctl daemon-reload`, `sudo systemctl start onedev-agent-docker`, `sudo systemctl enable onedev-agent-docker`。

### 步骤 4.3: 在 OneDev 中创建项目并配置 Secrets

1.  **创建项目**: 
    *   登录 OneDev，点击 `+ New Project`。
    *   填写项目名称 (例如 `mqtt-charging-system`)。
    *   **代码库**: 
        *   选择 `Create an empty repository` (如果您想在 OneDev 中开始一个全新的项目，并将 `master` 作为默认分支) 或 `Import from an existing repository` (如果您想将现有的 Git 仓库——例如本地的或之前 Gitea 上的，其主分支为 `master`——导入到 OneDev 中进行管理)。**本项目将使用 OneDev 内置的 Git 服务。**
    *   (可选) 配置项目的默认分支为 `master` (如果通过导入创建且需要更改，或新建时指定)。
2.  **配置项目 Secrets**: 这些 Secrets 将被 `.onedev-buildspec.yml` 用于生成 `.env` 文件。
    *   进入项目 -> `Settings` -> `Secrets`。
    *   点击 `New Secret`。
    *   为每个需要的敏感信息创建一个 Secret。**确保 Secret 名称与 `.onedev-buildspec.yml` 中 `@secrets.<SecretName>@` 的 `<SecretName>` 部分完全一致**。
        *   `MYSQL_ROOT_PASSWORD`: 您的 MySQL root 密码。
        *   `MYSQL_DATABASE`: 数据库名称 (例如 `mqtt_power`)。
        *   `MYSQL_USER`: 应用连接数据库的用户名 (例如 `appuser`)。
        *   `MYSQL_PASSWORD`: 应用连接数据库的密码。
        *   (可选) `EMQX_ADMIN_PASSWORD`: 如果 EMQX 需要管理密码。
    *   勾选 `Available to all branches` (除非您有特定分支的特殊需求，例如只允许 `master` 分支的构建访问生产密码)。
    *   确保这些 Secrets **不允许在构建日志中显示**。

### 步骤 4.4: 将项目代码和配置文件推送到仓库

1.  确保您的项目代码已克隆/推送到 OneDev 内置的 Git 仓库中，并且根目录包含以下文件：
    *   **`.onedev-buildspec.yml`**: (已创建) 定义 CI/CD 流水线。
    *   **`docker-compose.yml`**: (已创建和修改) 定义所有服务，应用镜像使用本地构建名称。
    *   **`springboot-init-main/Dockerfile`**: (已创建) 用于构建后端镜像。
    *   **`charging_web_app/Dockerfile`**: (已创建) 用于构建前端镜像。
    *   **`springboot-init-main/src/main/resources/application.yml`**: (已修改) 数据库和 MQTT 连接信息通过环境变量配置。
2.  将这些文件提交并推送到 OneDev 内置 Git 仓库的 `master` 分支。

### 步骤 4.5: 触发与监控部署

1.  **触发构建**: 
    *   当您向 OneDev 内置仓库的 `master` 分支 (或其他在 OneDev 项目构建设置中配置的触发分支) 推送代码时，OneDev 应该会自动触发 `.onedev-buildspec.yml` 中定义的流水线。
    *   您也可以在 OneDev 项目的 `Builds` 页面手动触发特定分支 (如 `master`) 的构建。
2.  **监控构建**: 
    *   在 OneDev 项目的 `Builds` 页面，您可以看到正在运行和已完成的构建列表。
    *   点击特定的构建，可以查看其详细步骤、可视化流程、以及每个步骤的实时日志输出。
    *   如果构建失败，日志中会包含错误信息，帮助您排查问题。
3.  **验证部署**: 构建成功完成后，在部署服务器上：
    *   使用 `docker ps -a` 查看所有容器的状态。
    *   使用 `docker-compose logs -f <service_name>` (在 Agent 的工作区项目目录下) 或 `docker logs <container_name_or_id>` 查看各服务日志。
    *   尝试访问您的前端和后端应用。

## 5. `.onedev-buildspec.yml` 文件详解 (关键点)

(已在之前创建，此处为回顾和说明)

*   **`version: 8`**: 指定 buildspec 的版本。
*   **`jobs`**: 定义一个或多个作业。
    *   **`name`**: 作业的名称。
    *   **`agentMatcher`**: 指定运行此作业的 Agent 必须拥有的标签 (例如 `agent-for-mqtt-power`)。
    *   **`steps`**: 作业中执行的步骤。
        *   **`checkout`**: 检出代码到 Agent 的工作区。
        *   **`setupVariables`**: 定义变量，可以引用 Secrets (`@secrets.MySecret@`) 或其他 OneDev 内置变量 (`@commit_hash@` 等)。
        *   **`buildImage`**: OneDev 内置步骤，用于构建 Docker 镜像。
            *   `dockerfile`: Dockerfile 的路径。
            *   `imageName`, `imageTag`: 构建的镜像名称和标签。
            *   `buildPath`: Docker build context 路径。
        *   **`writeFile`**: 将内容写入 Agent 工作区的文件 (例如生成 `.env` 文件)。
        *   **`executeShell`**: 执行 shell 命令 (例如 `docker-compose up -d`)。
    *   **`dependencies`**: 定义作业之间的依赖关系 (例如，部署作业依赖于构建作业)。
    *   **`triggers`**: 定义触发作业的条件 (例如，特定路径的文件变更、其他作业成功等)。

## 6. 回滚策略

*   **重新运行旧的成功构建**: OneDev 保存了所有构建的历史记录。如果新部署出现问题，您可以找到上一个基于 `master` 分支的成功部署构建，并点击 "Rebuild" (或类似选项) 来重新运行该构建，从而回滚到之前的状态。
*   **代码回滚并触发新构建**: 在 Git 中将 `master` 分支的代码回滚到某个稳定版本，然后推送到 OneDev，OneDev 将会基于旧代码重新构建和部署。
*   **(如果镜像打了 commit hash 标签)**: 如果您的镜像标签包含 commit hash (例如 `@commit_hash@`变量)，并且您知道某个稳定版本的 commit hash，理论上可以手动修改部署服务器上的 `docker-compose.yml` (或通过 OneDev 参数化构建传递镜像标签) 来指向旧镜像并重启，但这通常不如通过 OneDev 流水线管理方便。

## 7. 注意事项与常见问题

*   **Agent 资源与权限**: 确保 OneDev Agent 运行的用户有足够的权限（特别是 Docker 命令执行权限）和系统资源（CPU, 内存, 磁盘空间）来完成构建和部署任务。
*   **Secrets 管理**: 严格管理 OneDev 中的 Secrets，不要在日志中暴露它们。
*   **`.onedev-buildspec.yml` 语法**: YAML 格式对缩进敏感，编写时需注意。
*   **Docker 构建上下文**: `buildImage` 步骤中的 `buildPath` 很重要，它决定了 Docker 构建时的上下文环境。
*   **网络配置**: 确保 OneDev Server, OneDev Agent, 以及部署服务器上的 Docker 容器之间网络通畅。
*   **首次构建**: 第一次触发构建时，请密切关注 OneDev 中的日志，以便快速定位和解决可能出现的配置问题。
*   **日志**: OneDev 提供了非常详细的构建日志，是排查问题的首要工具。

---

本文档提供了使用 OneDev (及其内置 Git 服务) 和 Docker Compose 配置 CI/CD 的框架。OneDev 提供了强大的可视化管理和流水线定义能力，可以显著简化部署流程的管理和监控。 