# Git 部署

> 将你的服务器连接到 GitHub、GitLab、Gitea 或 Forgejo 仓库，每次推送、发布或推送标签时自动部署，并支持受保护路径、重启、CI 检查和一键回滚。

门户中的 **Git Deploy** 标签页可将你的服务器连接到 **GitHub**、**GitLab**、**Gitea** 或 **Forgejo** 上的 Git 仓库。仓库中的文件会自动复制到你的服务器 — 每次推送时、发布版本时、推送标签时，或者点击按钮时。如果你愿意，部署前服务器会被干净地停止，部署后再重新启动。

简而言之：推送你的代码，几秒钟后它就会在你的服务器上运行。不再需要通过 SFTP 上传，不再需要在文件管理器中解压 ZIP 文件，也不会忘记重启。

## 你将学到

- 如何连接仓库，可以使用 Daki Git Deploy GitHub 应用，也可以使用访问令牌
- 部署何时运行，以及如何手动触发部署
- 如何通过受保护路径确保世界存档、数据库和 `.env` 文件的安全
- 重启、部署历史、回滚以及仓库中的状态如何工作
- 如何为你的团队设置正确的权限

---

## 适用人群

- **Discord 机器人以及 Node.js、Python 或 Java 应用的开发者**，代码已托管在 GitHub 或 GitLab 上。
- **Minecraft 服务器网络和游戏服务器团队**，他们在仓库中保存插件、配置或数据包，并希望以可追溯的方式推出更改。
- **团队：** 多人在同一仓库中工作，服务器始终保持最新，子用户权限可精确决定谁可以部署。
- **基于发布版本的项目：** 只有已发布的版本（例如发布版中完成的 `.jar` 文件）会被部署到服务器，而不是每一次进行中的工作。

---

## 开始之前

- 门户中一台显示 **Git Deploy** 标签页的服务器。它位于服务器导航的 **Essentials** 下，默认紧跟在 **Files** 标签页之后。
- 一个位于 GitHub（github.com）、GitLab（gitlab.com 或你自己的实例，包括 `group/subgroup/project` 形式的子组）、Gitea 或 Forgejo（例如 Codeberg 或你自己的实例）上的仓库。公开仓库和私有仓库均可使用。

> **注意:** Daki 会根据服务器类型（Egg）决定是否显示该标签页。如果你看不到 **Git Deploy**，说明该功能未针对你的服务器类型启用。

> **信息:** 本指南使用 Git Deploy 标签页的英文标签。该标签页提供 19 种语言，并跟随你在门户中选择的语言 — 无论哪种语言，按钮的位置都相同。

---

## GitHub 快速开始（推荐）

最快的方式：通过 **Daki Git Deploy** 连接，它是 Daki Hosting 官方的 GitHub 应用。你在 GitHub 上登录并选择你的仓库 — **无需令牌，也无需 Webhook**。

**步骤 1: 打开 Git Deploy 标签页**

在门户中打开你的服务器，然后进入 **Git Deploy** 标签页。在 **Git service** 下，选择 **GitHub**。

**步骤 2: 连接 GitHub**

在 **GitHub access** 框中，点击 **Connect with GitHub**。在 GitHub 上登录并授权该应用。

首次连接时，GitHub 会直接带你进入选择应用可访问仓库的页面（**All repositories** 或仅限选定的仓库）— 一次完成，无需两步。之后你会返回门户，**GitHub access** 框会显示 **Connected via Daki Git Deploy as** 以及你的 GitHub 名称。**Webhook** 框会确认无需任何设置：推送和发布会通过该应用自动送达。

**步骤 3: 选择仓库和分支**

点击 **Choose from my repositories**，然后选择你的仓库 — 或粘贴网页或克隆 URL，或直接输入路径。点击 **Load branches**，并选择要部署的分支。留空则使用仓库的默认分支。

**步骤 4: 选择部署的时机和方式**

在 **When to deploy** 下选择一种模式（例如 **Every push**）。然后设置目标文件夹、你的[受保护路径](#protected-paths)以及[重启行为](#restart-behavior)。

**步骤 5: 保存**

点击底部栏中的 **Save settings**。就这样：标签页顶部会显示 **Automatic deploys on**，你的下一次推送会自动部署。如需立即部署，请点击右上角的 **Deploy now**。

- **允许其他仓库：** **Choose repositories on GitHub** 会再次打开 GitHub 上的仓库选择页面，你可以在那里选择允许 **Daki Git Deploy** 访问的仓库。
- **移除连接：** 点击 **Disconnect**。之后服务器将不再从私有仓库部署。

> **提示:** 你永远不需要续期任何东西。每次部署时，门户都会从 GitHub 获取一个新的短期密钥（有效期 1 小时），该密钥只能访问你服务器的那一个仓库。这一过程在后台完成。

---

## 使用访问令牌快速开始

适用于 **GitLab**、**Gitea** 和 **Forgejo** — 如果你更喜欢，GitHub 也可以这样连接。你需要保存一个个人访问令牌。公开仓库无需任何令牌即可使用。

**步骤 1: 选择 Git 服务**

打开你的服务器 → **Git Deploy**，然后选择 **Git service**。对于你自己的 GitLab 实例、Gitea 或 Forgejo，请输入服务器 URL（例如 `https://codeberg.org` 或 `https://git.example.com`）。对于 gitlab.com，留空即可。

**步骤 2: 输入仓库**

以路径（`owner/name`）的形式输入仓库，或直接粘贴其网页或克隆 URL。对于**公开**仓库，只需这些即可。未设置令牌时，访问框会显示 **No token – public repositories only**。

**步骤 3: 为私有仓库保存令牌**

使用 GitHub 时，请先点击 **GitHub access** 框中的 **Use an own access token instead**。然后在你的 Git 提供商处创建令牌 — 门户中的 **Create a token on …** 链接会直接带你到正确的页面 — 然后将其保存在 **Personal access token** 下。门户会立即检查该令牌。所需权限请参见[令牌权限](#token-permissions)。

**步骤 4: 选择设置并保存**

选择部署时机、目标文件夹、受保护路径以及重启行为，然后点击 **Save settings**。

**步骤 5: 添加 Webhook**

**Webhook** 框会显示 **Payload URL** 和 **Secret**，每项旁边都有复制按钮。按照[设置 Webhook](#set-up-the-webhook) 中的说明将它们添加到你的仓库中。框下方的 **Last delivery** 会显示 Webhook 是否已成功送达。

**步骤 6: 开启自动部署**

打开 **Automatic deploys active** 开关。

### 令牌权限

| 提供商 | 令牌类型 | 部署所需权限 | 仓库中状态所需的额外权限 |
| :--- | :--- | :--- | :--- |
| GitHub | 细粒度令牌，仅限此仓库 | *Contents: Read-only*（若使用 **Wait for CI checks**，还需 *Actions: Read-only*） | *Commit statuses* 和 *Deployments*：*Read and write* |
| GitLab | 个人、项目或群组访问令牌 | `read_api` + `read_repository` | 将 `read_api` 换成 `api`，角色为 Developer |
| Gitea / Forgejo | Settings → Applications | `repository: Read`、`user: Read` | `repository: Read and Write` |

如果只缺少状态的写入权限，你的服务器仍会被部署 — 部署日志中会显示一条说明。

### 设置 Webhook

**GitHub**

在你的仓库中，进入 **Settings → Webhooks → Add webhook**：

1. 粘贴门户中的 **Payload URL** 和 **Secret**。
2. 将 content type 设置为 `application/json`。
3. 在事件下，选择 **Just the push event**。在 release 模式下，改为选择 **Releases**。

**GitLab**

在你的项目中，进入 **Settings → Webhooks**：

1. 将 **Payload URL** 粘贴到 URL 中，并将 **Secret** 粘贴到 **Secret token** 中。
2. 在 triggers 下，勾选 **Push events**。在 tag 模式下，勾选 **Tag push events**；在 release 模式下，勾选 **Releases events**。

**Gitea / Forgejo**

在你的仓库中，进入 **Settings → Webhooks → Add webhook**，并选择 Gitea 或 Forgejo：

1. 粘贴 **Payload URL**，将 content type 设置为 `application/json`，并输入 **Secret**。
2. 选择 **Push events**；在 release 模式下，选择 **Custom events → Release**。

> **提示:** 你的 Git 提供商无法访问门户，例如你的 Gitea 实例运行在家庭网络中？请改为在 **Also check … for updates** 下选择一个检查间隔（例如 **Also check Gitea for updates**）。第一次检查会将当前状态部署一次。

---

## 部署何时运行

在 **When to deploy** 下，选择以下四种模式之一：

| 模式 | 部署内容 |
| :--- | :--- |
| **Manual** | 仅当有人点击 **Deploy now** 时。 |
| **Every push** | 每次推送到所选分支时。 |
| **Releases** | 仅当发布一个版本时。可以部署该版本的**源代码**，也可以部署**发布文件**（例如 `*.jar`）— 可选择以固定名称保存（例如 `server.jar`），如果是 `.zip` 文件，还可以选择解压。可以包含或排除预发布版本。 |
| **Tags** | 推送与某个模式匹配的标签时，例如 `v*`。 |

### 触发方式

- **Webhook：** 你的 Git 提供商会立即报告更改，部署会在几秒钟内开始。使用 GitHub 应用时，这一过程会自动完成，无需你自己配置 Webhook。
- **更新检查（轮询）：** 可选，适用于 Git 提供商无法访问门户的情况。检查间隔从 5 分钟到 1 天不等。
- **Deploy now：** 随时点击按钮即可部署。在 release 模式下，你可以选择特定的版本。
- **跳过推送：** 在提交信息中加入 `[skip deploy]`，该次推送将不会被部署。`[deploy skip]` 和 `[no deploy]` 同样有效。

### 等待 CI 检查

开启 **Wait for CI checks**（仅在 **Every push** 模式下可用）后，只有当提交的 CI 通过（显示为绿色）时才会部署该提交。支持 GitHub Actions、GitLab CI 以及 Gitea/Forgejo Actions。如果 CI 失败，则跳过部署。

---

## 所有设置说明

| 分组 | 设置 | 作用 |
| :--- | :--- | :--- |
| 仓库 | **Git service** | GitHub、GitLab、Gitea 或 Forgejo |
| 仓库 | **Server URL** | 仅适用于自托管实例；必须能从互联网访问 |
| 仓库 | **Repository** | `owner/name` 或 URL；**Choose from my repositories** 会显示列表 |
| 仓库 | **Branch** | 留空 = 默认分支；**Load branches** 会显示列表 |
| 何时部署 | **Manual** / **Every push** / **Releases** / **Tags** | 参见[部署何时运行](#when-a-deploy-runs) |
| 何时部署 | **Wait for CI checks** | 仅在 **Every push** 下可用 |
| 何时部署 | **Include pre-releases**、**What to deploy**（**Source code** / **Release file**）、**Save as**、**Extract .zip files instead of uploading them** | 仅在 **Releases** 下可用 |
| 何时部署 | **Tag pattern** | 仅在 **Tags** 下可用，例如 `v*`；留空 = 每个标签 |
| 何时部署 | **Also check … for updates**（以你的 Git 服务命名，例如 **Also check GitHub for updates**） | 轮询：**Never (webhook only)**、5/10/15/30 分钟、1/3/6/12/24 小时 |
| 何时部署 | **Automatic deploys active** | 关闭后会暂停 Webhook 和轮询；**Deploy now** 仍可使用 |
| 何时部署 | **Show deploys in the repository** | 提交上的状态，参见[仓库中的状态](#status-in-your-repository) |
| 文件 | **Folder in the repository** | 仅部署某个子文件夹，例如 `dist`；留空 = 全部内容 |
| 文件 | **Target folder on the server** | 文件存放的位置；`/` = 服务器的主文件夹 |
| 文件 | **Protected paths** | 每行一条规则，参见[受保护路径](#protected-paths) |
| 文件 | **Remove files that were deleted from the repository** | 可选的清理功能，见下文 |
| 重启 | **Restart if running** / **Always start** / **Do not restart** | 参见[重启行为](#restart-behavior) |
| 重启 | **If the server does not stop** | 立即强制终止，或在 5 秒、10 秒（默认：**Kill after 10 seconds**）、15 秒、30 秒、1 分钟、2 分钟或 5 分钟后终止 |

更改仅在你点击 **Save settings** 后生效。有未保存的内容时，底部会显示一个栏，其中包含 **Discard** 和 **Save settings**，并且 **Deploy now** 会被锁定。

**Remove files that were deleted from the repository** 只会删除之前某次部署自己写入的文件 — 永远不会删除受保护路径，也不会删除你自己创建的文件。空文件夹会保留。

---

## 受保护路径

**Protected paths** 下列出的文件和文件夹在部署时**永远不会被覆盖或删除**。如果它们尚不存在，则会被创建一次。非常适合游戏世界、数据库和密码。

每行写一条规则，路径相对于目标文件夹。空行和以 `#` 开头的行会被忽略。`*` 永远不会跨越 `/` 匹配。

| 规则 | 保护的内容 |
| :--- | :--- |
| `world/` | 任意深度下所有名为 `world` 的文件夹 |
| `/world/` | 仅目标文件夹中直接存在的 `world` |
| `*.db` | 任意位置所有以 `.db` 结尾的文件 |
| `.env` | 任意位置所有名为 `.env` 的文件 |
| `/config.yml` | 仅目标文件夹中直接存在的 `config.yml` |
| `plugins/*/config.yml` | `plugins` 每个直接子文件夹中的 `config.yml` |

常见示例：

**Minecraft**

```

world/

world_nether/

world_the_end/

plugins/*/config.yml

whitelist.json

ops.json

banned-*.json

```

**Discord 机器人 / Node.js**

```

.env

*.db

*.sqlite

node_modules/

data/

```

**Python**

```

.env

*.db

venv/

```

---

## 重启行为

| 选项 | 发生的情况 |
| :--- | :--- |
| **Restart if running** | 写入文件之前停止服务器，之后再重新启动。关闭的服务器保持关闭。 |
| **Always start** | 与上面相同，但关闭的服务器会在部署后被启动。 |
| **Do not restart** | 服务器保持运行，同时替换文件。 |

**If the server does not stop:** 某些程序（尤其是 Node.js 应用）会忽略停止信号。此时服务器会在你选择的时间后被强制终止（立即、5 秒到 5 分钟；默认为 **Kill after 10 seconds**），以免部署一直挂起。立即强制终止最快，但服务器没有时间保存数据。

> **注意:** 两道安全保障：如果服务器根本没有关闭，**不会更改任何文件**。如果服务器已被停止但部署失败，服务器会被**重新启动**。

---

## 部署过程中会发生什么

1. 某个触发源（推送、发布、标签、更新检查或按钮）创建一次部署。
2. 如果开启了 **Wait for CI checks**，则等待 CI 检查变为绿色（**Waiting**）。
3. **门户**从你的 Git 提供商下载压缩包。你的凭据永远不会离开门户，也不会出现在你的服务器上。
4. 压缩包被处理：只保留所选文件夹，过滤掉受保护路径，并丢弃符号链接等不安全内容。
5. 根据你的设置，服务器被停止（如有需要，在所选时间后被强制终止）。
6. 文件被写入目标文件夹；可选地，删除已被移除的文件。
7. 根据你的设置，服务器被重新启动。
8. 结果和完整日志会出现在历史记录中；如已启用，还会作为提交上的状态显示。

一次部署通常需要 **10–40 秒**，另加服务器停止所需的时间。

---

## 历史记录、日志与回滚

标签页底部的 **Deployments** 列表显示每次部署的状态、分支和提交（例如 `main @ 75cd0a1`）、编号、触发方式、发起人、时间以及提交信息。标签页顶部会显示服务器最近一次部署的时间。

| 状态 | 含义 |
| :--- | :--- |
| **Waiting** | 等待 CI 检查 |
| **Scheduled** | 已排队，即将开始 |
| **Running** | 部署进行中 |
| **Deployed** | 成功完成 |
| **Failed** | 出现问题 — 日志会显示详情 |
| **Skipped** | 未部署，例如 CI 失败或提交信息包含 `[skip deploy]` |
| **Cancelled** | 开始前已取消 |

- **日志：** 每次部署都有一份实时日志，部署运行时会持续更新。**Hide log** 可再次将其关闭。
- **一键回滚：** 在较早的部署上点击 **Deploy again**，即可再次部署该状态。
- **取消：** 等待中或已排队的部署可以被取消。

多台服务器可以同时部署，但每台服务器同一时间只运行一次部署。新的触发会替换仍在等待的旧部署。

---

## 仓库中的状态

开启 **Show deploys in the repository** 后，你的提交会显示一个状态 **Daki Hosting / *你的服务器名称***：部署进行时为黄色，之后显示绿色勾或红色叉，并附带一个指向 Git Deploy 标签页的链接。

- 在 GitHub 上，部署还会出现在 **Deployments** 下，并以你的服务器名称作为环境。
- 使用 GitHub 应用时，状态由 **Daki Git Deploy** 写入并显示其标志 — 无需其他设置。使用令牌时，显示令牌所属账户的头像，并且该令牌需要写入权限（参见[令牌权限](#token-permissions)）。
- 你可以按服务器关闭此功能。公开的仅有状态和链接 — 不包含日志和错误详情。

---

## 团队与子用户权限

[子用户](https://daki.cc/zh/wiki/guides/subusers)拥有各自的权限组 **Git Deploy**：

| 权限 | 允许的操作 |
| :--- | :--- |
| **view** (`github.read`) | 查看设置、Webhook 地址以及带日志的历史记录 |
| **deploy** (`github.deploy`) | **Deploy now**、**Deploy again** 以及取消部署 |
| **change settings** (`github.update`) | 仓库、访问、Webhook 以及所有设置 |

子用户默认**没有** Git Deploy 访问权限 — 由你决定谁获得哪项权限。服务器所有者始终拥有全部权限。

---

## 安全性

- 令牌和密钥以**加密**方式存储，并且**永远不会发送到你的浏览器**。
- 使用 GitHub 应用时，部署根本不需要你的长期令牌：每次部署都使用一个短期密钥（1 小时），且该密钥**只能访问你服务器的仓库**。
- 你自己的 GitHub 访问权限决定你可以连接哪些仓库。任何人都无法将他人的仓库拉取到自己的服务器上。
- Webhook 使用每台服务器各自专属的密钥进行**签名**。密钥错误或针对其他仓库的请求会被拒绝。
- 只有保存令牌的人才能更改仓库，因此子用户无法使用所有者的令牌加载其他私有仓库。
- 如果你更改 Git 服务或服务器 URL，保存时已保存的令牌会被自动删除。
- 受保护路径永远不会被覆盖，**Remove files that were deleted from the repository** 只会删除 Git Deploy 自己写入的内容。
- 仓库中公开的状态不包含日志和错误详情。

---

## 已知限制

- 每台服务器只能连接一个仓库，并使用一种访问方式。
- 每台服务器同一时间只运行一次部署。
- 仓库中的状态仅在部署真正开始后才会出现，等待 CI 期间不会显示。
- 发布文件仅在为 `.zip` 文件时才会被解压；`.tar.gz` 会作为文件直接上传。
- 在 **Remove files that were deleted from the repository** 之后，空文件夹会保留。
- 仓库中的符号链接不会被部署。
- GitLab 没有预发布版本；发布日期为未来日期的版本会被视为预发布。
- GitLab：上传到项目中的发布文件（`/uploads/…`）无法使用令牌从私有项目下载；指向软件包仓库的链接可以正常使用。
- **Connect with GitHub** 仅适用于 GitHub；GitLab、Gitea 和 Forgejo 使用令牌。

---

## 常见问题与故障排除

**我需要定期续期令牌吗？**

使用 GitHub 应用时不需要：门户会在每次部署时自动获取新密钥。你自己的令牌在你在 Git 提供商处创建时设定的有效期内有效；过期后，只需保存一个新的令牌即可。

**我的仓库不在列表中（GitHub 应用）**

点击 **Choose repositories on GitHub**，并允许该应用访问该仓库。

**部署一直停留在 Scheduled**

请稍等片刻。如果几分钟后仍然如此，请[联系支持](https://daki.cc/zh/wiki/guides/support)。

**Webhook 没有送达，或 GitHub 显示错误 401**

密钥不匹配。请复制门户中的密钥，并在 Webhook 中重新输入。**New secret** 会生成一个新密钥 — 然后请同时在你的 Git 提供商处更改它。

**为什么我的部署被跳过了？**

可能的原因：CI 检查失败、提交信息包含 `[skip deploy]`，或者更新的触发源替换了仍在等待的部署。原因会显示在该部署的行中。

**我的提交上看不到状态**

必须开启 **Show deploys in the repository**。如果使用令牌，该令牌还需要写入权限（参见[令牌权限](#token-permissions)）— 部署日志会显示原因。

**部署失败，提示 “The node rejected the request”**

你的服务器类型不允许某些文件，或者磁盘已满。日志会显示详细信息。

**我自己的 Gitea 实例 URL 被拒绝**

自托管实例必须能从互联网访问。`192.168.x.x` 这类家庭网络地址无法使用。

**如何回滚？**

在历史记录中，点击你想要回到的较早部署上的 **Deploy again**。

**我的游戏世界或数据库会被覆盖吗？**

如果它们列在 **Protected paths** 中（例如 `world/` 或 `*.db`），则不会。

**我团队中谁可以部署？**

只有在服务器子用户设置的 **Git Deploy** 组中拥有 **deploy** 权限的人可以部署。服务器所有者始终拥有全部权限。

---

## 下一步

- [子用户](https://daki.cc/zh/wiki/guides/subusers) — 为你的团队授予访问权限，并决定谁可以部署。
- [环境变量](https://daki.cc/zh/wiki/guides/environment-variables) — 将令牌和密码保留在仓库之外。
- [启动设置](https://daki.cc/zh/wiki/guides/startup-settings) — 设置你所部署的代码运行时使用的启动命令。
- [备份](https://daki.cc/zh/wiki/guides/backups) — 将服务器的自动备份保存到 Google Drive。

---
Source: https://daki.cc/zh/wiki/guides/git-deploy
