9 Commits

Author SHA1 Message Date
Voson
f36b0159bd feat(gitea): 新增 Rust 后端工作流模板并更新现有模板
- 添加完整的 Rust 后端 CI/CD 工作流模板
- 更新 Android、Go、Node.js、微信小程序工作流模板
- 优化工作流生成器配置
- 更新 Gitea skill 主文档
2026-01-29 22:52:32 +08:00
voson
740ff4e1d8 chore(config): 更新模型配置为 moonshotai-cn/kimi-k2.5 2026-01-29 13:16:32 +08:00
voson
135ea99deb feat(git): add SSH keychain configuration guide
- Add comprehensive SSH key management with macOS Keychain

- Include troubleshooting for password prompt issues

- Document 1Password agent conflicts and solutions
2026-01-29 11:57:24 +08:00
voson
6bc2506842 chore(config): 切换默认模型为 gpt-5.2 2026-01-28 15:27:26 +08:00
voson
c0b5f15895 feat(gitea): 添加 SSH 密钥管理功能 2026-01-28 10:57:24 +08:00
voson
32d674a4c0 fix(gitea): 修复 delete-runner API 端点错误
- 修复 delete-runner.md 中的 API 端点从 /api/v1/admin/runners 改为 /api/v1/admin/actions/runners
- 更新 SKILL.md 和 repository-operations.md 文档
- 更新 opencode.json 配置
2026-01-28 08:48:04 +08:00
voson
02d870a2d6 docs(gitea): 添加 Docker Mode 镜像选择说明
- 必须使用包含 Docker CLI 的镜像 (catthehacker/ubuntu:act-*)
- 添加镜像对比表格和工作原理说明
- 添加常见错误解决方案
- 更新版本至 1.2
2026-01-24 12:14:35 +08:00
Voson
417f3e6d2b fix(gitea): Docker runner 使用 catthehacker/ubuntu:act-* 镜像替代 node:16-bullseye
- 解决 docker/login-action 等 actions 因缺少 docker CLI 而失败的问题
- catthehacker/ubuntu:act-* 镜像预装 Docker CLI、Buildx 等 CI/CD 工具
- 添加镜像选择的注释说明
2026-01-24 12:13:18 +08:00
Voson
ece54efc14 feat(gitea): 添加 runner 恢复功能文档和快速恢复命令 2026-01-23 18:24:31 +08:00
15 changed files with 1578 additions and 102 deletions

View File

@@ -1,12 +1,14 @@
{
"$schema": "https://opencode.ai/config.json",
"mcp": {},
"model": "moonshotai-cn/kimi-k2.5",
"permission": "allow",
"plugin": ["@mohak34/opencode-notifier@latest"],
"plugin": [
"@mohak34/opencode-notifier@latest"
],
"provider": {
"opencode": {
"models": {
"claude-opus-4-5": {
"claude-sonnet-4-5": {
"options": {
"thinking": {
"type": "enabled",
@@ -15,11 +17,6 @@
}
}
}
},
"zhipuai-coding-plan": {
"options": {
"apiKey": "0f76aea86295476dbfa98724013b0fe8.o2EaJVqcl4Cf7WLP"
}
}
}
}

View File

@@ -13,6 +13,7 @@ You are an expert in Git version control and repository management.
|------|------|
| [Commit Workflow](./commit-workflow.md) | 提交暂存文件,自动生成提交信息并创建版本标签 |
| [Push Workflow](./push-workflow.md) | 提交并推送到远程仓库的完整工作流 |
| [SSH Keychain](./ssh-keychain.md) | SSH 密钥管理与 macOS Keychain 自动解锁配置 |
## Core Principles

209
skill/git/ssh-keychain.md Normal file
View File

@@ -0,0 +1,209 @@
---
name: ssh-keychain
description: SSH key management with macOS Keychain for password-protected keys
---
# SSH 密钥与 macOS Keychain 管理
处理带密码的 SSH 密钥,配置自动解锁避免重复输入密码。
## 问题场景
生成带密码的 SSH 密钥后,每次使用都需要输入密码:
```bash
git pull
# Enter passphrase for /Users/xxx/.ssh/id_xxx:
```
## 解决方案
### 1. 生成带密码的 SSH 密钥
```bash
# 生成密钥(推荐使用 ed25519
ssh-keygen -t ed25519 -f ~/.ssh/id_custom -N "your-passphrase"
# 或使用随机密码
PASSPHRASE=$(openssl rand -base64 32)
ssh-keygen -t ed25519 -f ~/.ssh/id_custom -N "$PASSPHRASE"
```
### 2. 配置 SSH 使用 Keychain关键步骤
编辑 `~/.ssh/config`
```ssh-config
Host gitea.example.com
HostName gitea.example.com
User git
IdentityFile ~/.ssh/id_custom
IdentitiesOnly yes
AddKeysToAgent yes
UseKeychain yes
```
**重要配置项:**
- `AddKeysToAgent yes` - 自动添加到 ssh-agent
- `UseKeychain yes` - 使用 macOS Keychain 存储密码
- **不要**使用 `IdentityAgent none`(会禁用 Keychain
### 3. 添加密钥到 ssh-agent 并保存到 Keychain
```bash
# 方法 1使用 --apple-use-keychain (macOS 12+)
ssh-add --apple-use-keychain ~/.ssh/id_custom
# 方法 2使用 -K 选项 (macOS 11 及更早)
ssh-add -K ~/.ssh/id_custom
```
输入密码后,系统会保存到 Keychain以后自动解锁。
### 4. 验证配置
```bash
# 检查已加载的密钥
ssh-add -l
# 测试 SSH 连接
ssh -T git@gitea.example.com
```
如果显示 `Hi there, xxx! You've successfully authenticated` 且**没有提示输入密码**,则配置成功。
## 完整工作流程示例
### 场景:配置 Gitea SSH 访问
```bash
# 1. 生成带密码的密钥
PASSPHRASE="bkt/52MFsLVSRSHvIXv2WTCKEUaPhD0btDghUY6RnQI="
ssh-keygen -t ed25519 -f ~/.ssh/id_gitea_new -N "$PASSPHRASE"
# 2. 配置 SSH
# 编辑 ~/.ssh/config添加
cat >> ~/.ssh/config << 'EOF'
Host gitea.refining.dev
HostName gitea.refining.dev
User git
IdentityFile ~/.ssh/id_gitea_new
IdentitiesOnly yes
AddKeysToAgent yes
UseKeychain yes
EOF
# 3. 添加到 Keychain
ssh-add --apple-use-keychain ~/.ssh/id_gitea_new
# 输入密码: bkt/52MFsLVSRSHvIXv2WTCKEUaPhD0btDghUY6RnQI=
# 4. 验证
ssh -T git@gitea.refining.dev
# 5. 后续使用 git 无需输入密码
git pull
git push
```
## 常见错误与解决
### 错误 1仍然提示输入密码
**原因:** `~/.ssh/config` 中配置了 `IdentityAgent none`
**解决:** 移除 `IdentityAgent none`,添加 `UseKeychain yes`
```ssh-config
# ❌ 错误配置
Host gitea.example.com
IdentityFile ~/.ssh/id_custom
IdentityAgent none # 这会禁用 Keychain
# ✅ 正确配置
Host gitea.example.com
IdentityFile ~/.ssh/id_custom
AddKeysToAgent yes
UseKeychain yes
```
### 错误 21Password SSH agent 冲突
如果系统使用 1Password 的 SSH agent`IdentityAgent "~/Library/Group Containers/.../agent.sock"`
```ssh-config
Host *
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
Host gitea.example.com
HostName gitea.example.com
User git
IdentityFile ~/.ssh/id_custom
IdentitiesOnly yes
AddKeysToAgent yes
UseKeychain yes
# 不使用 1Password agent直接使用系统 Keychain
```
### 错误 3ssh-add 命令不存在
某些 macOS 版本可能不支持 `--apple-use-keychain`
```bash
# 创建或编辑 ~/.ssh/config添加全局配置
Host *
UseKeychain yes
AddKeysToAgent yes
# 然后直接使用
ssh-add ~/.ssh/id_custom
```
## 密钥管理最佳实践
### 文件保存位置
将密钥备份到安全位置(如 iCloud
```bash
# 复制到 iCloud Key 文件夹
KEY_FOLDER="$HOME/Library/Mobile Documents/com~apple~CloudDocs/key"
cp ~/.ssh/id_custom "$KEY_FOLDER/id_custom_$(date +%Y%m%d)"
cp ~/.ssh/id_custom.pub "$KEY_FOLDER/id_custom_$(date +%Y%m%d).pub"
# 创建密码说明文件
cat > "$KEY_FOLDER/id_custom_$(date +%Y%m%d)_info.txt" << EOF
SSH Key: id_custom
Generated: $(date)
Passphrase: $PASSPHRASE
Host: gitea.example.com
EOF
```
### 定期检查
```bash
# 列出所有已加载的密钥
ssh-add -l
# 检查 SSH 配置
ssh -G gitea.example.com
# 测试连接
ssh -vT git@gitea.example.com
```
### 安全建议
1. **始终使用带密码的密钥** - 防止私钥泄露后被直接使用
2. **不要将私钥提交到 Git** - 添加到 `.gitignore`
3. **定期轮换密钥** - 建议每 6-12 个月
4. **为不同服务使用不同密钥** - 隔离风险
## 快速参考
| 命令 | 用途 |
|------|------|
| `ssh-add --apple-use-keychain ~/.ssh/id_custom` | 添加密钥到 Keychain |
| `ssh-add -l` | 列出已加载的密钥 |
| `ssh-add -D` | 删除所有已加载的密钥 |
| `ssh -T git@host` | 测试 SSH 连接 |
| `ssh -G host` | 查看 SSH 配置 |

View File

@@ -7,15 +7,43 @@ description: Comprehensive Gitea management tool for creating runners, workflows
完整的 Gitea 管理工具,提供 Runner、Workflow、仓库的创建和管理功能。
## 最新改进 (2026-01-23)
### 仓库创建功能增强(简洁高效版)
1. **智能解析**:自动识别 `组织/仓库` 格式,优先使用指定组织而非默认组织
2. **简化验证**默认假设组织存在API创建失败时清晰提示如何创建组织
3. **默认私有**:除非明确指定,所有仓库默认创建为私有
4. **Git集成**自动检查当前目录是否为Git仓库提供一键初始化、提交、推送
5. **错误处理**详细的Token验证、权限检查API错误时给出具体解决方案
6. **工作目录澄清**:仓库操作可在任何目录执行,配置操作在 `~/.config/gitea/`
7. **流程优化**减少不必要的预先验证API失败时再提示更简洁高效
### 使用示例
```bash
# 智能解析组织/仓库格式
/gitea-create-repo shigongcao/shigongcao
# 创建公开仓库
/gitea-create-repo org/project public
# 自动初始化并推送
/gitea-create-repo org/project --init
```
## 工作目录
**重要:该技能和所有相关命令的工作目录统一为:**
**重要:Gitea 配置和 Runner 相关操作的工作目录为 `~/.config/gitea/`,但仓库操作可以在任何目录执行。**
### macOS / Linux
### 配置和 Runner 目录
```bash
~/.config/gitea/
```
### 仓库操作目录
可以在任何项目目录执行,技能会自动:
1.`~/.config/gitea/config.env` 读取配置
2. 在当前目录创建仓库、初始化 Git 等
### Windows
```powershell
# PowerShell
@@ -28,7 +56,9 @@ $env:USERPROFILE\.config\gitea\
C:\Users\YourUsername\.config\gitea\
```
所有配置文件、Runner 目录、日志文件都存储在此目录下。请确保该目录具有适当的读写权限。
**重要区分**
- **配置/Runner 操作**:在 `~/.config/gitea/` 目录执行
- **仓库操作**:可在任何项目目录执行(自动加载配置)
**目录结构(所有平台通用):**
```
@@ -40,9 +70,9 @@ C:\Users\YourUsername\.config\gitea\
```
**平台兼容性:**
- **macOS**: `~/.config/gitea/`完全支持
- **Linux**: `~/.config/gitea/`完全支持
- **Windows**: `%USERPROFILE%\.config\gitea\`Act Runner 支持,但该技能的命令和脚本需要适配
- **macOS**: `~/.config/gitea/`配置目录),任意目录(仓库操作
- **Linux**: `~/.config/gitea/`配置目录),任意目录(仓库操作
- **Windows**: `%USERPROFILE%\.config\gitea\`配置目录),任意目录(仓库操作
**Windows 用户注意事项:**
- Gitea Act Runner 官方支持 Windows 平台(包括 Host 模式)
@@ -57,6 +87,7 @@ C:\Users\YourUsername\.config\gitea\
| 功能模块 | 文档 | 说明 |
|---------|------|------|
| 环境配置 | [setup-guide.md](./setup-guide.md) | 首次使用引导,配置 Gitea URL 和 Token |
| SSH 密钥管理 | [ssh-key-management.md](./ssh-key-management.md) | 创建、部署 SSH 密钥,配置本地客户端 |
| Runner 管理 | [runner-management.md](./runner-management.md) | 创建、注册、管理 Gitea Act Runner |
| 自动创建脚本 | [create-runner.md](./create-runner.md) | 包含完整的 Runner 创建 Bash 脚本 |
| Workflow 生成 | [workflow-generator.md](./workflow-generator.md) | 根据项目类型生成 CI/CD workflow |
@@ -72,6 +103,7 @@ C:\Users\YourUsername\.config\gitea\
- "生成 workflow"、"CI/CD"
- "创建仓库"、"gitea 仓库"
- "gitea 配置"、"gitea token"
- "ssh key"、"ssh密钥"、"gitea ssh"、"部署密钥"
## 首次使用
@@ -89,7 +121,13 @@ C:\Users\YourUsername\.config\gitea\
- 可在所有个人仓库的 Actions workflow 中使用
- 使用方式:`${{ vars.USERNAME }}``${{ vars.WEBHOOK_URL }}`
**重要**Gitea 不允许 variable 名称以 `GITEA_``GITHUB_` 开头
**重要:变量命名规范**
- Gitea Actions 不允许变量和密钥名称以 `GITEA_``GITHUB_` 开头
- 推荐命名:
- `REGISTRY_TOKEN`(替代 `GITEA_TOKEN`
- `REGISTRY_URL` → 使用内置 `${{ env.registry }}`
- `USERNAME` → 使用内置 `${{ github.actor }}`
- `WEBHOOK_URL`(已自动注册为 variable
详见:[环境配置指南](./setup-guide.md)
@@ -100,6 +138,7 @@ C:\Users\YourUsername\.config\gitea\
| `/gitea-config` | 查看当前 Gitea 配置和 Runner 状态 |
| `/gitea-reset` | 重置 Gitea 配置(交互式向导) |
| `/gitea-switch-org` | 切换默认组织 |
| `/gitea-setup-ssh` | 创建 SSH 密钥并部署到 Gitea 服务器 |
| `/gitea-create-runner` | 创建并启动新 Runner默认 host 模式) |
| `/gitea-list-runners` | 列出所有 Runner 及其状态 |
| `/gitea-delete-runner` | 删除指定 Runner |
@@ -183,12 +222,30 @@ AI: 检测到 Go 项目,服务目录: ./backend
[自动生成 .gitea/workflows/backend.yml]
```
### 4. 创建仓库
### 4. 创建仓库(智能解析,默认私有)
**智能解析特性**
- 自动识别 `组织/仓库` 格式,优先使用指定组织
- 默认创建私有仓库(除非明确指定公开)
- 支持自动初始化 Git 仓库并推送代码
**示例**
```
用户: /gitea-create-repo my-project
AI: [使用配置的 Gitea URL 和默认组织创建仓库]
✓ 仓库创建成功: ai/my-project
用户: /gitea-create-repo shigongcao/shigongcao
AI: [智能解析为 shigongcao 组织下的 shigongcao 仓库]
✓ 仓库创建成功: shigongcao/shigongcao (私有)
✓ 组织验证通过
✓ 可选的 Git 初始化流程...
用户: /gitea-create-repo my-project public
AI: [使用默认组织创建公开仓库]
✓ 仓库创建成功: ai/my-project (公开)
用户: /gitea-create-repo org/project --init
AI: [创建仓库并自动初始化当前目录的 Git 仓库]
✓ 仓库创建成功: org/project
✓ Git 初始化完成
✓ 代码已推送到远程仓库
```
## 配置管理
@@ -336,6 +393,7 @@ Runner 信息
| 类型 | 模板文档 | 适用场景 |
|------|---------|---------|
| **Rust 后端** | [rust-backend.md](./workflow-templates/rust-backend.md) | **Rust API 服务、微服务、CLI 工具** |
| Go 后端 | [go-backend.md](./workflow-templates/go-backend.md) | API 服务、微服务、CLI 工具 |
| Node.js 前端 | [nodejs-frontend.md](./workflow-templates/nodejs-frontend.md) | React/Vue/Vite/Next.js |
| Android 应用 | [android-app.md](./workflow-templates/android-app.md) | Kotlin/Java/Jetpack Compose |
@@ -357,6 +415,38 @@ AI 会自动:
详见:[API 参考](./api-reference.md)
## 变量命名规范
**⚠️ 重要限制**Gitea Actions 不允许变量和密钥名称以 `GITEA_` 或 `GITHUB_` 开头
### 推荐命名约定
| 原名称 | 推荐替代 | 说明 |
|--------|----------|------|
| `GITEA_TOKEN` | `REGISTRY_TOKEN` | Container registry 访问令牌 |
| `GITEA_URL` | `${{ env.registry }}` | 使用内置变量自动获取当前实例地址 |
| `GITEA_USERNAME` | `${{ github.actor }}` | 使用内置变量获取当前用户 |
| `GITHUB_TOKEN` | `RELEASE_TOKEN` | 用于创建 Release 的 API 令牌 |
### 内置变量利用
Gitea Actions 提供的内置变量(推荐优先使用):
- `${{ env.registry }}`: 当前 Gitea 实例的 registry 地址
- `${{ github.actor }}`: 触发 workflow 的用户名
- `${{ github.repository }}`: 当前仓库的 `owner/repo` 格式
- `${{ github.server_url }}`: Gitea 实例的基础 URL
### Variables vs Secrets
**Variables**(明文存储,适用于非敏感配置):
- `USERNAME`: 用户名(自动从配置同步)
- `WEBHOOK_URL`: 通知 webhook 地址
- `DEFAULT_ORG`: 默认组织名
**Secrets**(加密存储,适用于敏感信息):
- `REGISTRY_TOKEN`: Container registry 密码
- `RELEASE_TOKEN`: Release 创建 API 令牌
## 安全性
- 配置文件权限:`600`(仅所有者可读写)
@@ -376,10 +466,17 @@ AI 会自动:
## 版本
- **Skill Version**: 1.0
- **Last Updated**: 2026-01-12
- **整合内容**: gitea-runner + gitea-workflow
- **新增功能**: 统一配置管理、Runner CRUD、智能 labels 检测
- **Skill Version**: 1.3
- **Last Updated**: 2026-01-29
- **整合内容**: gitea-runner + gitea-workflow + 增强仓库管理 + SSH 密钥管理 + Rust 模板
- **主要改进**:
- 仓库创建智能解析(优先使用指定组织)
- **简化验证**默认假设组织存在API失败时提示创建组织
- 默认私有仓库策略(除非明确指定公开)
- **完整 Git 集成**自动检查Git状态提供一键初始化、提交、推送
- **简洁高效**减少预先验证API失败时给出具体解决方案
- 工作目录概念澄清(配置 vs 仓库操作)
- **SSH 密钥管理**:完整的密钥创建、部署和跨设备使用指南
## 相关资源

View File

@@ -119,7 +119,9 @@ host:
workdir_parent: /data/workspace
YAML
local labels="ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,ubuntu-20.04:docker://node:16-buster,linux:docker://node:16-bullseye"
# 使用 catthehacker/ubuntu:act-* 镜像,内置 Docker CLI、Buildx 等 CI/CD 工具
# 注意:不要使用 node:16-bullseye 等纯运行时镜像,它们不包含 docker 命令
local labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest"
docker run -d \
--name "$name" \
@@ -224,6 +226,45 @@ EOF
# gitea-create-runner all "wibnIxvgeyYj3D7d53VTQvNxv0UVqArBwAtPFBWD"
```
## 🔄 恢复离线 Runner
**场景**Runner 在 Gitea 服务器上被删除,但本地配置文件仍在。需要重新注册并上线。
**快速恢复命令**(针对 runner-Mac-mini4-host
```bash
cd ~/.config/gitea/runners/runner-Mac-mini4-host
# 停止旧进程
if [ -f pid ]; then kill $(cat pid) 2>/dev/null || true; fi
# 加载配置并重新注册
source ~/.config/gitea/config.env
# 获取令牌(需要 jq
token=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/runners/registration-token" | jq -r '.token')
# 生成标签
OS=$(uname -s); ARCH=$(uname -m)
case "$OS" in Darwin) os="macOS";; Linux) os="ubuntu";; *) os="unknown";; esac
case "$ARCH" in arm64|aarch64) arch="ARM64";; x86_64) arch="x64";; *) arch="unknown";; esac
labels="self-hosted:host,${os}:host,${arch}:host,$(echo "${OS}-${ARCH}" | tr '[:upper:]' '[:lower:]'):host"
# 重新注册并启动
act_runner register --config config.yaml --instance "$GITEA_URL" \
--token "$token" --name "runner-Mac-mini4-host" --labels "$labels" --no-interactive
nohup act_runner daemon --config config.yaml > runner.log 2>&1 &
echo $! > pid
echo "✅ Runner 恢复完成 (PID: $(cat pid))"
```
**说明**
- 确保 `config.yaml` 中的 `labels:` 配置为空(`labels: []`),注册时会使用命令行参数
- 如果全局令牌权限不足,需要获取组织令牌(参考 runner-management.md
- 恢复后 runner 会获得新的 ID但名称和 labels 保持不变
### 方法三:完整脚本
如果你需要更多自定义选项,可以使用下面的完整脚本:
@@ -588,7 +629,13 @@ create_runner() {
else
# Docker mode uses standard labels mapping to images
# Format: label:docker://image
labels="ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,ubuntu-20.04:docker://node:16-buster,linux:docker://node:16-bullseye"
# 重要:必须使用包含 Docker CLI 的镜像,否则 docker/login-action 等 actions 会失败
# catthehacker/ubuntu:act-* 是专为 GitHub/Gitea Actions 设计的镜像,预装:
# - Docker CLI (docker 命令)
# - Docker Buildx
# - git, curl, jq 等常用工具
# 不要使用 node:16-bullseye 等纯运行时镜像!
labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest"
fi
echo "✓ Labels ($mode):"
@@ -849,7 +896,13 @@ if [ "$RUNNER_MODE" = "host" ]; then
else
# Docker mode uses standard labels mapping to images
# Format: label:docker://image
labels="ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,ubuntu-20.04:docker://node:16-buster,linux:docker://node:16-bullseye"
# 重要:必须使用包含 Docker CLI 的镜像,否则 docker/login-action 等 actions 会失败
# catthehacker/ubuntu:act-* 是专为 GitHub/Gitea Actions 设计的镜像,预装:
# - Docker CLI (docker 命令)
# - Docker Buildx
# - git, curl, jq 等常用工具
# 不要使用 node:16-bullseye 等纯运行时镜像!
labels="ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04,linux:docker://catthehacker/ubuntu:act-latest"
fi
echo "✓ Labels ($RUNNER_MODE):"

View File

@@ -69,10 +69,10 @@ LOCAL_MAP=$(mktemp)
FINAL_LIST=$(mktemp)
# 2.1 Fetch Remote Runners (Try Admin first, then Org)
# Note: Admin endpoint /api/v1/admin/runners lists all runners
# Note: Admin endpoint /api/v1/admin/actions/runners lists all runners
HTTP_CODE=$(curl -s -w "%{http_code}" -o "$REMOTE_LIST" \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/runners?page=1&limit=100")
"${GITEA_URL}/api/v1/admin/actions/runners?page=1&limit=100")
if [ "$HTTP_CODE" != "200" ]; then
# Fallback to Org level if defined

View File

@@ -11,6 +11,34 @@
- 管理 Secrets 和 Variables
- 设置 Webhooks
## 快速使用
### 核心原则(简洁高效)
1. **智能解析**:自动识别 `组织/仓库` 格式,优先使用指定组织而非默认组织
2. **默认私有**:除非明确指定,所有仓库默认创建为私有
3. **简化验证**默认假设组织存在API创建失败时清晰提示解决方案
4. **Git集成**自动检查Git仓库状态提供一键初始化、提交、推送完整流程
5. **错误处理**API失败时给出具体操作建议而非预先复杂验证
### 常用命令(简洁高效)
```bash
# 智能解析组织/仓库格式(默认私有)
/gitea-create-repo shigongcao/shigongcao
# 使用默认组织创建公开仓库
/gitea-create-repo my-project public
# 自动检查Git状态提供完整初始化流程
/gitea-create-repo org/project
# 特性说明:
# 1. 自动识别组织/仓库格式
# 2. 默认创建私有仓库除非指定public
# 3. 自动检查当前目录Git状态
# 4. 提供一键初始化、提交、推送选项
# 5. API失败时给出清晰解决方案如组织不存在
```
## 创建仓库
### 使用命令创建
@@ -65,24 +93,28 @@ fi
source "$config_file"
```
#### 步骤 2: 解析输入
#### 步骤 2: 智能解析输入(简洁高效版)
```bash
input="$1"
# 解析 owner/repo
# 智能解析:优先使用用户指定的组织
if [[ "$input" =~ / ]]; then
owner=$(echo "$input" | cut -d'/' -f1)
repo=$(echo "$input" | cut -d'/' -f2)
echo "使用指定组织: $owner"
echo "提示假设组织存在如不存在会在API创建时提示"
else
# 使用默认组织或当前用户
# 未指定组织,使用默认组织或当前用户
if [ -z "$GITEA_DEFAULT_ORG" ]; then
# 获取当前用户
echo "未指定组织,获取当前用户..."
owner=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/user" | jq -r '.login')
if [ -z "$owner" ] || [ "$owner" = "null" ]; then
echo "❌ 无法获取当前用户信息,请使用 owner/repo 格式"
echo "❌ 无法获取当前用户信息,请使用 组织/仓库 格式"
echo "例如:/gitea-create-repo shigongcao/shigongcao"
exit 1
fi
@@ -94,9 +126,21 @@ else
repo="$input"
fi
# 解析可见性
# 解析可见性:默认私有仓库(除非明确指定公开)
visibility="${2:-private}"
if [[ "$visibility" != "private" && "$visibility" != "public" ]]; then
echo "⚠️ 可见性参数无效,使用默认值: private"
visibility="private"
fi
private_bool=$([ "$visibility" = "private" ] && echo "true" || echo "false")
echo "仓库可见性: $visibility"
# 提前检查当前目录是否是 Git 仓库(为后续步骤做准备)
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "⚠️ 当前目录不是 Git 仓库"
echo "提示:创建远程仓库后可以初始化本地 Git 仓库并推送代码"
fi
```
#### 步骤 3: 验证仓库名
@@ -109,12 +153,29 @@ if ! [[ "$repo" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
fi
```
#### 步骤 4: 调用 API 创建
#### 步骤 4: 调用 API 创建(带详细错误处理)
```bash
echo "正在创建仓库: $owner/$repo ($visibility)"
# 尝试组织仓库
# 检查 Token 权限
echo "检查 Token 权限..."
user_info=$(curl -s -H "Authorization: token $GITEA_TOKEN" "${GITEA_URL}/api/v1/user")
username=$(echo "$user_info" | jq -r '.login // empty')
if [ -z "$username" ] || [ "$username" = "null" ]; then
echo "❌ Token 无效或权限不足"
echo "请检查:"
echo "1. Token 是否有效"
echo "2. Token 是否有 'repo' 权限"
echo "3. GITEA_URL 是否正确"
exit 1
fi
echo "✓ Token 有效,当前用户: $username"
# 尝试创建组织仓库
echo "调用 Gitea API 创建仓库..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
@@ -122,42 +183,87 @@ response=$(curl -s -w "\n%{http_code}" -X POST \
\"name\": \"${repo}\",
\"private\": ${private_bool},
\"auto_init\": false,
\"default_branch\": \"main\"
\"default_branch\": \"main\",
\"description\": \"\"
}" \
"${GITEA_URL}/api/v1/orgs/${owner}/repos")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
# 如果 404可能是用户而非组织
if [ "$http_code" = "404" ]; then
echo "⚠️ 组织不存在,尝试创建用户仓库..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${repo}\",
\"private\": ${private_bool}
}" \
"${GITEA_URL}/api/v1/user/repos")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
fi
# 处理响应
case "$http_code" in
201)
echo "✓ 仓库创建成功"
;;
400)
error_msg=$(echo "$body" | jq -r '.message // "未知错误"')
echo "❌ 请求参数错误: $error_msg"
echo "请检查:"
echo "1. 仓库名格式是否正确"
echo "2. 是否缺少必要参数"
exit 1
;;
403)
echo "❌ 权限不足"
echo "请检查:"
echo "1. 是否有在组织 '$owner' 下创建仓库的权限"
echo "2. 是否是组织成员"
echo "3. Token 权限是否足够"
exit 1
;;
404)
# 组织不存在API 返回 404
echo "❌ API 创建失败:组织 '$owner' 不存在"
# 检查是否为当前用户(可能用户输入的是自己的用户名)
if [ "$owner" = "$username" ]; then
echo "检测到 '$owner' 是当前用户,创建个人仓库..."
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${repo}\",
\"private\": ${private_bool},
\"auto_init\": false,
\"default_branch\": \"main\"
}" \
"${GITEA_URL}/api/v1/user/repos")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" = "201" ]; then
echo "✓ 个人仓库创建成功"
else
error_msg=$(echo "$body" | jq -r '.message // "未知错误"')
echo "❌ 个人仓库创建失败 (HTTP $http_code): $error_msg"
exit 1
fi
else
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "组织不存在,请先创建组织"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "您可以通过以下方式创建组织 '$owner'"
echo "1. 访问 ${GITEA_URL}/org/create"
echo "2. 使用 Gitea Web 界面创建组织"
echo "3. 或者使用个人仓库格式: $username/$repo"
echo ""
echo "创建组织后,重新运行此命令创建仓库。"
exit 1
fi
;;
409)
echo "❌ 仓库已存在"
echo "❌ 仓库已存在: $owner/$repo"
echo "请使用不同的仓库名或删除现有仓库"
exit 1
;;
*)
echo "❌ 创建失败 (HTTP $http_code)"
echo "$body" | jq -r '.message // empty'
error_msg=$(echo "$body" | jq -r '.message // "未知错误"')
echo "❌ 创建失败 (HTTP $http_code): $error_msg"
exit 1
;;
esac
@@ -183,45 +289,104 @@ echo " SSH URL: $ssh_url"
echo ""
```
#### 步骤 6: 添加 Git Remote
#### 步骤 6: Git 仓库集成(简洁高效版)
```bash
read -p "是否将此仓库添加为 Git remote? [Y/n] " add_remote
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Git 仓库集成"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [[ ! "$add_remote" =~ ^[Nn]$ ]]; then
# 检查是否是 Git 仓库
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "当前目录不是 Git 仓库"
read -p "是否初始化 Git 仓库? [Y/n] " init_git
# 检查是否是 Git 仓库
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "当前目录不是 Git 仓库"
read -p "是否初始化 Git 仓库并添加 remote? [Y/n] " init_git
if [[ ! "$init_git" =~ ^[Nn]$ ]]; then
git init
echo "✓ Git 仓库已初始化"
else
exit 0
fi
if [[ "$init_git" =~ ^[Nn]$ ]]; then
echo "跳过 Git 初始化,仅创建远程仓库。"
echo "您可以在需要时手动执行:"
echo " git init"
echo " git remote add origin \"$clone_url\""
exit 0
fi
# 初始化 Git 仓库
echo "正在初始化 Git 仓库..."
git init
echo "✓ Git 仓库已初始化"
# 检查 origin 是否已存在
if git remote get-url origin >/dev/null 2>&1; then
existing_url=$(git remote get-url origin)
echo "⚠️ origin remote 已存在: $existing_url"
read -p "是否覆盖? [y/N] " overwrite
read -p "是否覆盖为新的仓库? [y/N] " overwrite
if [[ "$overwrite" =~ ^[Yy]$ ]]; then
git remote set-url origin "$clone_url"
echo "✓ origin remote 已更新"
echo "✓ origin remote 已更新为: $clone_url"
else
echo "保持现有的 origin remote"
fi
else
git remote add origin "$clone_url"
echo "✓ origin remote 已添加"
echo "✓ origin remote 已添加: $clone_url"
fi
# 显示 remote 信息
# 可选:添加文件、提交并推送
echo ""
echo "当前 remote:"
git remote -v
read -p "是否添加当前文件、提交并推送到远程仓库? [Y/n] " push_code
if [[ ! "$push_code" =~ ^[Nn]$ ]]; then
echo "添加所有文件到暂存区..."
git add .
echo "创建初始提交..."
git commit -m "Initial commit" || {
echo "⚠️ 提交失败(可能没有文件可提交)"
echo "请手动添加文件后提交"
}
echo "推送到远程仓库 (main 分支)..."
git branch -M main 2>/dev/null
git push -u origin main
echo "✓ 代码已推送到远程仓库"
else
echo "跳过推送,您可以在需要时手动推送代码。"
fi
else
# 已经是 Git 仓库,询问是否添加/更新 remote
echo "当前目录已是 Git 仓库"
read -p "是否添加/更新 origin remote 为此仓库? [Y/n] " add_remote
if [[ ! "$add_remote" =~ ^[Nn]$ ]]; then
# 检查 origin 是否已存在
if git remote get-url origin >/dev/null 2>&1; then
existing_url=$(git remote get-url origin)
echo "⚠️ origin remote 已存在: $existing_url"
read -p "是否覆盖? [y/N] " overwrite
if [[ "$overwrite" =~ ^[Yy]$ ]]; then
git remote set-url origin "$clone_url"
echo "✓ origin remote 已更新"
else
echo "保持现有的 origin remote"
fi
else
git remote add origin "$clone_url"
echo "✓ origin remote 已添加"
fi
fi
fi
# 显示当前状态
echo ""
echo "当前 Git 状态:"
git status --short 2>/dev/null || echo "(非 Git 仓库)"
echo ""
echo "Remote 配置:"
git remote -v 2>/dev/null || echo "(无 remote 配置)"
```
## 仓库初始化
@@ -689,5 +854,11 @@ curl -s -H "Authorization: token $GITEA_TOKEN" \
## 版本
- **文档版本**: 1.0
- **最后更新**: 2026-01-12
- **文档版本**: 1.1
- **最后更新**: 2026-01-23
- **主要改进**:
- 智能解析输入:优先使用指定组织而非默认组织
- 组织存在性验证和清晰错误提示
- 默认私有仓库策略(除非明确指定公开)
- 增强的错误处理Token验证、权限检查、详细错误消息
- 完整的Git集成流程可选自动初始化和推送

View File

@@ -29,6 +29,7 @@ Gitea Act Runner 是 Gitea Actions 的 CI/CD 执行器,兼容 GitHub Actions w
- **macOS ARM64**: 使用 **Host Mode** 以支持 Android SDK
- **Windows**: 使用 **Host Mode**(需在 workflow 中指定 `shell: powershell`
- **Linux**: 两种模式均可Docker Mode 隔离性更好
- **Docker Mode**: 必须使用 `catthehacker/ubuntu:act-*` 镜像(内置 Docker CLI
**Windows Host Mode 注意事项**
- Bash 默认不可用,需在 workflow 中指定 shell
@@ -577,9 +578,51 @@ labels:
```yaml
labels:
- "ubuntu-latest:docker://catthehacker/ubuntu:act-latest"
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-latest"
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
- "ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04"
- "linux:docker://catthehacker/ubuntu:act-latest"
```
### Docker Mode 镜像选择(重要)
**必须使用包含 Docker CLI 的镜像**,否则 `docker/login-action``docker/build-push-action` 等 actions 会失败。
| 镜像 | Docker CLI | 适用场景 |
|------|------------|---------|
| `catthehacker/ubuntu:act-latest` | ✅ 有 | **推荐**CI/CD 专用镜像 |
| `catthehacker/ubuntu:act-22.04` | ✅ 有 | Ubuntu 22.04 兼容 |
| `node:16-bullseye` | ❌ 无 | **不推荐**,仅适合纯 Node.js 项目 |
| `golang:1.21` | ❌ 无 | **不推荐**,仅适合纯 Go 项目 |
**catthehacker/ubuntu:act-* 预装工具**
- Docker CLI (`/usr/bin/docker`)
- Docker Buildx
- git, curl, wget, jq
- Node.js, Python
- 常用编译工具
**工作原理**
```
workflow: runs-on: ubuntu-latest
runner 查找 label: ubuntu-latest:docker://镜像名
启动 job 容器(使用该镜像)
在 job 容器内执行 steps
docker/login-action 调用 `docker login` 命令
需要 job 容器内有 docker CLI ← 关键!
```
**常见错误**
```
Unable to locate executable file: docker. Please verify either the file path exists
or the file can be found within a directory specified by the PATH environment variable.
```
此错误表示 job 容器内缺少 docker 命令,需要更换为包含 Docker CLI 的镜像。
### Workflow 匹配
**方法 1: 组合 label推荐最精确**
@@ -993,6 +1036,106 @@ nohup act_runner daemon --config ~/.config/gitea/runners/runner-macbook-pro/conf
tail -f ~/.config/gitea/runners/runner-macbook-pro/runner.log
```
### 8. 恢复被删除的 Runner从服务器删除但本地文件仍在
**场景**Runner 在 Gitea 服务器上被删除,但本地目录和配置文件仍在。需要重新注册并上线。
**恢复步骤**
```bash
# 1. 进入 runner 目录
cd ~/.config/gitea/runners/runner-Mac-mini4-host
# 2. 停止旧进程(如果仍在运行)
if [ -f pid ]; then
kill $(cat pid) 2>/dev/null || true
rm -f pid
fi
# 3. 加载 Gitea 配置
source ~/.config/gitea/config.env
# 4. 获取注册令牌(优先全局,失败则降级到组织)
echo "获取注册令牌..."
response=$(curl -s -w "\n%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/admin/runners/registration-token")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" != "200" ]; then
echo "全局令牌权限不足,尝试组织令牌..."
if [ -n "$GITEA_DEFAULT_ORG" ]; then
org_name="$GITEA_DEFAULT_ORG"
else
read -p "请输入组织名称: " org_input
org_name="$org_input"
fi
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token $GITEA_TOKEN" \
"${GITEA_URL}/api/v1/orgs/$org_name/actions/runners/registration-token")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
fi
if [ "$http_code" != "200" ]; then
echo "❌ 获取注册令牌失败"
exit 1
fi
registration_token=$(echo "$body" | jq -r '.token')
# 5. 生成标签(基于系统环境)
OS=$(uname -s)
case "$OS" in
Darwin) os_label="macOS" ;;
Linux) os_label="ubuntu" ;;
*) os_label="unknown" ;;
esac
ARCH=$(uname -m)
case "$ARCH" in
arm64|aarch64) arch_label="ARM64" ;;
x86_64) arch_label="x64" ;;
*) arch_label="unknown" ;;
esac
combined=$(echo "${OS}-${ARCH}" | tr '[:upper:]' '[:lower:]')
labels="self-hosted:host,${os_label}:host,${arch_label}:host,${combined}:host"
# 6. 重新注册 runner
act_runner register \
--config config.yaml \
--instance "$GITEA_URL" \
--token "$registration_token" \
--name "runner-Mac-mini4-host" \
--labels "$labels" \
--no-interactive
if [ $? -ne 0 ]; then
echo "❌ 注册失败"
exit 1
fi
# 7. 启动 runner daemon
nohup act_runner daemon --config config.yaml > runner.log 2>&1 &
echo $! > pid
sleep 2
if ps -p $(cat pid) > /dev/null 2>&1; then
echo "✅ Runner 恢复成功 (PID: $(cat pid))"
echo "日志: tail -f runner.log"
else
echo "❌ Runner 启动失败"
exit 1
fi
```
**说明**
- 恢复后 runner 会获得新的 ID 和 token但名称和 labels 保持不变
- 旧的 `.runner` 文件会被新的覆盖
- 确保 `config.yaml` 中的 labels 配置为空(`labels: []`),注册时会使用命令行参数
## Quick Reference
| 任务 | 命令 |
@@ -1001,6 +1144,7 @@ tail -f ~/.config/gitea/runners/runner-macbook-pro/runner.log
| 创建 runner | `/gitea-create-runner`(自动安装、配置、启动)|
| 列出 runners | `/gitea-list-runners` |
| 删除 runner | `/gitea-delete-runner` |
| 恢复 runner | 参考「恢复被删除的 Runner」章节 |
| 手动启动 | `nohup act_runner daemon --config <config.yaml> > <log> 2>&1 &` |
| 停止 runner | `pkill -f "act_runner daemon --config.*<name>"` |
| 查看状态 | `ps aux \| grep act_runner` |
@@ -1016,6 +1160,9 @@ tail -f ~/.config/gitea/runners/runner-macbook-pro/runner.log
## 版本
- **文档版本**: 1.0
- **最后更新**: 2026-01-12
- **文档版本**: 1.2
- **最后更新**: 2026-01-24
- **更新内容**:
- Docker Mode 镜像选择说明(必须使用包含 Docker CLI 的镜像)
- Runner 恢复流程
- **兼容性**: act_runner 0.2.13+, macOS ARM64, Linux

View File

@@ -0,0 +1,254 @@
# SSH 密钥管理
完整的 SSH 密钥创建、部署和配置流程,支持跨设备使用。
## 命令
### `/gitea-setup-ssh`
创建 SSH 密钥对,部署公钥到 Gitea 服务器,配置本地 SSH 客户端。
## 功能概述
1. **生成 SSH 密钥对** (RSA 4096 位)
2. **部署公钥到 Gitea 服务器** (通过 API)
3. **配置本地 SSH 客户端**
4. **验证 SSH 连接**
## 详细流程
### 1. 生成 SSH 密钥对
```bash
ssh-keygen -t rsa -b 4096 -f ~/.ssh/gitea_server_key -N ""
```
**生成的密钥文件:**
- 私钥:`~/.ssh/gitea_server_key`
- 公钥:`~/.ssh/gitea_server_key.pub`
### 2. 部署公钥到 Gitea
通过 Gitea API 将公钥添加到用户账户:
```bash
curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\":\"server-key\",\"key\":\"$(cat ~/.ssh/gitea_server_key.pub)\"}" \
"$GITEA_URL/api/v1/user/keys"
```
### 3. 配置本地 SSH 客户端
`~/.ssh/config` 中添加配置:
```bash
cat >> ~/.ssh/config << 'EOF'
Host git.refining.app
HostName git.refining.app
User git
IdentityFile ~/.ssh/gitea_server_key
IdentitiesOnly yes
EOF
```
### 4. 添加服务器主机密钥
```bash
ssh-keyscan git.refining.app >> ~/.ssh/known_hosts
```
### 5. 验证连接
```bash
ssh -T git@git.refining.app
```
成功输出:
```
Hi there, username! You've successfully authenticated with the key named server-key...
```
## 跨设备使用
### 复制密钥到新设备
1. **复制私钥文件**到新设备的 `~/.ssh/` 目录
```bash
# 在源设备上查看私钥内容
cat ~/.ssh/gitea_server_key
# 在新设备上创建文件
echo "-----BEGIN OPENSSH PRIVATE KEY-----" > ~/.ssh/gitea_server_key
echo "MIIEog..." >> ~/.ssh/gitea_server_key
echo "...cQ=" >> ~/.ssh/gitea_server_key
echo "-----END OPENSSH PRIVATE KEY-----" >> ~/.ssh/gitea_server_key
```
2. **设置正确的权限**
```bash
chmod 600 ~/.ssh/gitea_server_key
```
3. **配置 SSH 客户端**
```bash
# 在新设备上编辑 ~/.ssh/config
cat >> ~/.ssh/config << 'EOF'
Host git.refining.app
HostName git.refining.app
User git
IdentityFile ~/.ssh/gitea_server_key
IdentitiesOnly yes
EOF
```
4. **添加服务器主机密钥**
```bash
ssh-keyscan git.refining.app >> ~/.ssh/known_hosts
```
5. **测试连接**
```bash
ssh -T git@git.refining.app
```
## 密钥位置说明
### 源设备(当前已配置)
```
~/.ssh/
├── gitea_server_key # 私钥 (权限 600)
├── gitea_server_key.pub # 公钥 (已部署到 Gitea)
├── config # SSH 配置
└── known_hosts # 服务器主机密钥
```
### 新设备(需要配置)
需要从源设备复制:
1. `gitea_server_key` - **私钥**(核心文件)
2. `~/.ssh/config` - SSH 配置(相关部分)
3. `known_hosts` - 服务器主机密钥(可选,会自动获取)
## 安全注意事项
### ✅ 推荐做法
1. **私钥权限设置为 600**
```bash
chmod 600 ~/.ssh/gitea_server_key
```
2. **不分享私钥**
- 私钥应保持在个人设备上
- 仅在有信任关系的设备间复制
3. **使用强密码保护**
- 生成密钥时可添加密码:`ssh-keygen -t rsa -b 4096 -f ~/.ssh/gitea_server_key`
- 每次使用时需要输入密码
### ❌ 禁止行为
1. 不要将私钥提交到 Git 仓库
2. 不要通过不安全渠道传输私钥
3. 不要设置过于宽松的权限(如 644、777
## 故障排除
### 1. 权限错误
```
Permissions 0644 for '/Users/username/.ssh/gitea_server_key' are too open.
```
**解决方案**
```bash
chmod 600 ~/.ssh/gitea_server_key
```
### 2. 主机密钥验证失败
```
Host key verification failed.
```
**解决方案**
```bash
ssh-keyscan git.refining.app >> ~/.ssh/known_hosts
```
### 3. 认证失败
```
git@git.refining.app: Permission denied (publickey).
```
**检查步骤**
1. 确认私钥文件存在且权限正确
2. 确认 SSH 配置正确
3. 确认公钥已添加到 Gitea
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/user/keys" | jq -r '.[].title'
```
## Git 使用示例
### 克隆仓库
```bash
git clone git@git.refining.app:username/repository.git
```
### 更新现有仓库的 remote URL
```bash
git remote set-url origin git@git.refining.app:username/repository.git
```
### 查看当前 remote URL
```bash
git remote -v
```
## 撤销密钥访问
如果密钥泄露或设备丢失:
1. **登录 Gitea Web 界面**
2. 进入 **Settings → SSH/GPG Keys**
3. 找到对应的密钥并删除
2. **通过 API 删除**
```bash
# 首先获取密钥 ID
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/user/keys" | jq '.[] | select(.title=="server-key").id'
# 删除密钥(将 {id} 替换为实际 ID
curl -s -X DELETE -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/user/keys/{id}"
```
3. **在受影响的设备上删除私钥**
```bash
rm ~/.ssh/gitea_server_key
rm ~/.ssh/gitea_server_key.pub
```
## 相关命令
### 查看 Gitea 上的所有 SSH 密钥
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/user/keys" | jq -r '.[] | "\(.id): \(.title) (\(.fingerprint))"'
```
### 测试 SSH 连接(详细模式)
```bash
ssh -T -v git@git.refining.app
```
### 检查 SSH 配置语法
```bash
ssh -G git.refining.app
```
---
*文档版本1.1*
*最后更新2026-01-28*
*集成到 Gitea Skill v1.2*

View File

@@ -16,6 +16,7 @@ Gitea Actions 使用 GitHub Actions 兼容的 workflow 语法,定义在 `.gite
| 类型 | 模板文档 | 适用场景 |
|------|---------|---------|
| **Rust 后端** | [rust-backend.md](./workflow-templates/rust-backend.md) | **Rust API 服务、微服务、CLI 工具** |
| Go 后端 | [go-backend.md](./workflow-templates/go-backend.md) | API 服务、微服务、CLI 工具 |
| Node.js 前端 | [nodejs-frontend.md](./workflow-templates/nodejs-frontend.md) | React/Vue/Vite/Next.js |
| Android 应用 | [android-app.md](./workflow-templates/android-app.md) | Kotlin/Java/Jetpack Compose |
@@ -28,6 +29,11 @@ Gitea Actions 使用 GitHub Actions 兼容的 workflow 语法,定义在 `.gite
当用户说"为我的项目生成 workflow"时AI 会自动检测项目类型:
```bash
# Rust 项目特征
if [ -f "Cargo.toml" ] || [ -f "src/main.rs" ]; then
project_type="rust"
fi
# Go 项目特征
if [ -f "go.mod" ] || [ -f "main.go" ]; then
project_type="go"

View File

@@ -211,7 +211,7 @@ jobs:
- name: Create Release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
VERSION_NAME: ${{ needs.build.outputs.version_name }}
APK_NAME: ${{ needs.build.outputs.apk_name }}
run: |
@@ -221,20 +221,20 @@ jobs:
# 创建 Release
release_id=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag} (v${VERSION_NAME})\"}" \
"${api_url}/repos/${repo}/releases" | jq -r '.id')
# 上传 APK
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@dist/${APK_NAME}.apk" \
"${api_url}/repos/${repo}/releases/${release_id}/assets"
# 上传校验和
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@dist/${APK_NAME}.apk.sha256" \
"${api_url}/repos/${repo}/releases/${release_id}/assets"
```

View File

@@ -134,8 +134,8 @@ jobs:
uses: docker/login-action@v3
with:
registry: ${{ env.registry }}
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Docker - Setup Buildx
uses: docker/setup-buildx-action@v3
@@ -183,9 +183,9 @@ jobs:
name: ${{ env.SERVICE_PREFIX }}-binary-linux-amd64
path: dist
- name: Create Release
- name: Create Release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
BINARY_NAME: ${{ needs.build-and-publish.outputs.binary_name }}
run: |
git_tag=$(git describe --tags --abbrev=0)
@@ -199,20 +199,20 @@ jobs:
# 创建 Release
release_id=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag}\"}" \
"${api_url}/repos/${repo}/releases" | jq -r '.id')
# 上传二进制文件
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@dist/${BINARY_NAME}" \
"${api_url}/repos/${repo}/releases/${release_id}/assets"
# 上传校验和
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@dist/${BINARY_NAME}.sha256" \
"${api_url}/repos/${repo}/releases/${release_id}/assets"
```
@@ -284,7 +284,7 @@ GOARCH=amd64 # 目标架构
| Secret | 用途 |
|--------|------|
| `REGISTRY_PASSWORD` | Docker Registry 密码 |
| `REGISTRY_TOKEN` | Docker Registry 密码 |
| `RELEASE_TOKEN` | Gitea API 令牌(创建 Release |
---

View File

@@ -132,8 +132,8 @@ jobs:
uses: docker/login-action@v3
with:
registry: ${{ env.registry }}
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Docker - Setup Buildx
uses: docker/setup-buildx-action@v3
@@ -183,7 +183,7 @@ jobs:
- name: Create Release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
git_tag=$(git describe --tags --abbrev=0)
api_url="${{ github.server_url }}/api/v1"
@@ -197,14 +197,14 @@ jobs:
# 创建 Release
release_id=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag}\"}" \
"${api_url}/repos/${repo}/releases" | jq -r '.id')
# 上传附件
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@dist/${{ env.SERVICE_PREFIX }}-dist.zip" \
"${api_url}/repos/${repo}/releases/${release_id}/assets"
```
@@ -357,7 +357,7 @@ env:
| Secret | 用途 |
|--------|------|
| `REGISTRY_PASSWORD` | Docker Registry 密码 |
| `REGISTRY_TOKEN` | Docker Registry 密码 |
| `RELEASE_TOKEN` | Gitea API 令牌(创建 Release |
---

View File

@@ -0,0 +1,541 @@
# Rust 后端服务 Workflow 模板
适用于 Rust 后端 API 服务、微服务、CLI 工具的 CI/CD workflow。
## 适用场景
- Rust HTTP API 服务Axum、Actix-Web、Warp
- gRPC 微服务
- CLI 工具
- 需要构建 Docker 镜像的 Rust 项目
- 跨平台编译需求
## 环境要求
| 依赖 | Runner 要求 |
|------|------------|
| Rust 1.75+ | Runner 主机已安装 |
| Docker | Runner 主机已安装 |
| Cross 编译工具 | 可选,用于多平台构建 |
## Workflow 骨架模板
```yaml
name: API Hub CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
SERVICE_NAME: api-hub
jobs:
test:
name: 测试
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
- name: 缓存 Cargo 依赖
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: 检查代码格式
run: cargo fmt -- --check
- name: 运行 Clippy 检查
run: cargo clippy -- -D warnings
- name: 运行测试
run: cargo test --verbose
- name: 构建
run: cargo build --release --verbose
build-docker:
name: 构建 Docker 镜像
needs: test
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录容器仓库
uses: docker/login-action@v3
with:
registry: ${{ env.registry }}
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: 提取元数据
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.registry }}/${{ github.repository }}/${{ env.SERVICE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=,suffix=,format=short
- name: 构建并推送镜像
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
health-check:
name: 健康检查
needs: build-docker
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 运行健康检查测试
run: |
# 启动服务
docker run -d --name api-hub-test -p 8080:8080 ${{ env.registry }}/${{ github.repository }}/${{ env.SERVICE_NAME }}:main
# 等待服务启动
sleep 10
# 健康检查
echo "检查健康端点..."
curl -f http://localhost:8080/health || exit 1
# 就绪检查
echo "检查就绪端点..."
curl -f http://localhost:8080/ready || exit 1
# 存活检查
echo "检查存活端点..."
curl -f http://localhost:8080/live || exit 1
echo "所有健康检查通过!"
# 清理
docker stop api-hub-test
docker rm api-hub-test
release:
name: 创建发布
needs: test
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
targets: x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
- name: 构建 Release 二进制文件
run: |
# Linux GNU 版本
cargo build --release --target x86_64-unknown-linux-gnu
strip target/x86_64-unknown-linux-gnu/release/${{ env.SERVICE_NAME }}
tar czf ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz \
-C target/x86_64-unknown-linux-gnu/release ${{ env.SERVICE_NAME }}
# Linux MUSL 版本 (静态链接)
cargo build --release --target x86_64-unknown-linux-musl
strip target/x86_64-unknown-linux-musl/release/${{ env.SERVICE_NAME }}
tar czf ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz \
-C target/x86_64-unknown-linux-musl/release ${{ env.SERVICE_NAME }}
- name: 生成 Changelog
id: changelog
run: |
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
if [ -f CHANGELOG.md ]; then
# 提取当前版本的变更日志
awk '/^## \[${{ github.ref_name }}\]/{flag=1;next} /^## \[/{flag=0} flag' CHANGELOG.md || echo "查看完整变更日志CHANGELOG.md"
else
echo "## ${{ github.ref_name }}"
echo ""
echo "### 变更内容"
echo "- 发布版本 ${{ github.ref_name }}"
echo ""
echo "### 下载"
echo "- \`${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz\`: 标准 Linux 版本"
echo "- \`${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz\`: 静态链接版本(推荐用于容器)"
echo ""
echo "### 部署"
echo "\`\`\`bash"
echo "# 下载并解压"
echo "wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz"
echo "tar xzf ${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz"
echo ""
echo "# 运行服务"
echo "./${{ env.SERVICE_NAME }}"
echo "\`\`\`"
fi
EOF
- name: 创建 Release
uses: softprops/action-gh-release@v1
with:
name: ${{ env.SERVICE_NAME }} ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.CHANGELOG }}
files: |
${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz
${{ env.SERVICE_NAME }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
notify:
name: 发送通知
needs: [test, build-docker, release]
if: always()
runs-on: ubuntu-latest
steps:
- name: 发送构建通知
run: |
TEST_STATUS="${{ needs.test.result }}"
BUILD_STATUS="${{ needs.build-docker.result }}"
RELEASE_STATUS="${{ needs.release.result }}"
if [[ "$TEST_STATUS" == "success" && ("$BUILD_STATUS" == "success" || "$BUILD_STATUS" == "skipped") && ("$RELEASE_STATUS" == "success" || "$RELEASE_STATUS" == "skipped") ]]; then
STATUS="成功"
else
STATUS="失败"
fi
curl -X POST ${{ vars.WEBHOOK_URL }} \
-H "Content-Type: application/json" \
-d '{
"repository": "${{ github.repository }}",
"ref": "${{ github.ref }}",
"commit": "${{ github.sha }}",
"status": "'"$STATUS"'",
"workflow": "${{ github.workflow }}",
"actor": "${{ github.actor }}",
"test_result": "'"$TEST_STATUS"'",
"build_result": "'"$BUILD_STATUS"'",
"release_result": "'"$RELEASE_STATUS"'"
}' || true
```
---
## Dockerfile 模板
### 多阶段构建(推荐)
```dockerfile
# 构建阶段
FROM rust:1.75-slim as builder
# 安装构建依赖
RUN apt-get update && apt-get install -y \
pkg-config \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 复制依赖文件,利用 Docker 缓存
COPY Cargo.toml Cargo.lock ./
# 创建虚拟源文件用于编译依赖
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release && rm -rf src
# 复制实际源码并重新编译
COPY src ./src
RUN touch src/main.rs && cargo build --release
# 运行阶段
FROM debian:bookworm-slim
# 安装运行时依赖
RUN apt-get update && apt-get install -y \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 复制二进制文件
COPY --from=builder /app/target/release/api-hub /app/api-hub
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl --fail http://localhost:8080/health || exit 1
EXPOSE 8080
ENTRYPOINT ["/app/api-hub"]
```
### 简单构建
```dockerfile
FROM alpine:latest
# 安装运行时依赖
RUN apk add --no-cache ca-certificates curl tzdata
# 设置时区
ENV TZ=Asia/Shanghai
WORKDIR /app
# 复制预编译的二进制文件
COPY api-hub /app/api-hub
RUN chmod +x /app/api-hub
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl --fail http://localhost:8080/health || exit 1
EXPOSE 8080
ENTRYPOINT ["/app/api-hub"]
```
---
## 构建参数说明
### 编译优化
```bash
# 交叉编译目标
x86_64-unknown-linux-gnu # 标准 Linux (glibc)
x86_64-unknown-linux-musl # 静态链接 Linux
# 编译标志
--release # 发布模式优化
--target <target> # 指定目标平台
# 二进制优化
strip <binary> # 去除符号表,减小体积
```
### 缓存策略
```yaml
# Cargo 缓存路径
~/.cargo/bin/ # 已安装的 cargo 工具
~/.cargo/registry/index/ # 注册表索引
~/.cargo/registry/cache/ # 下载的 crate 缓存
~/.cargo/git/db/ # Git 依赖缓存
target/ # 编译输出缓存
# 缓存 Key 计算
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
```
---
## Variables 和 Secrets 配置
### Required Variables
| Variable | 说明 | 示例 |
|----------|------|------|
| `WEBHOOK_URL` | 构建通知 Webhook | `https://api.example.com/webhook` |
### Required Secrets
| Secret | 说明 |
|--------|------|
| `REGISTRY_TOKEN` | Container Registry 访问令牌 |
### Optional Secrets
| Secret | 说明 |
|--------|------|
| 无 | 基础模板无额外 secrets |
**注意**Gitea Actions 不允许变量和密钥名称以 `GITEA_``GITHUB_` 开头
---
## 触发条件
### 标准触发
```yaml
on:
push:
branches: [main] # 仅 main 分支
tags: ['v*'] # 版本标签
pull_request:
branches: [main] # PR 到 main
```
### 路径过滤(单体仓库)
```yaml
on:
push:
branches: [main]
paths:
- 'services/api-hub/**' # 仅特定服务目录变更
- 'shared/**' # 共享代码变更
- 'Cargo.toml' # 根依赖变更
```
---
## 发布流程
### Release 创建
- **触发**:推送 `v*` 格式的 tag`v1.0.0`
- **构建物**
- `{SERVICE_NAME}-{version}-x86_64-linux-gnu.tar.gz`
- `{SERVICE_NAME}-{version}-x86_64-linux-musl.tar.gz`
- **Changelog**: 自动从 `CHANGELOG.md` 提取或生成默认内容
### 版本发布操作
```bash
# 创建并推送版本标签
git tag v1.0.0
git push origin v1.0.0
# 预发布版本(标记为 prerelease
git tag v1.0.0-beta.1
git push origin v1.0.0-beta.1
```
---
## 常见构建工具集成
根据项目使用的工具,在构建步骤中添加相应命令:
| 框架/工具 | 命令 |
|----------|------|
| SeaORM | `sea-orm-cli generate entity -o src/entity` |
| Diesel | `diesel migration run && diesel print-schema > src/schema.rs` |
| tonic (gRPC) | `cargo build` (build.rs 自动处理) |
| utoipa (OpenAPI) | 代码生成,无需额外命令 |
| cargo-make | `cargo make ci` |
---
## Dockerfile.ci 模板(可选)
如果需要在 CI 中使用自定义 Dockerfile
```dockerfile
# 用于 CI 的轻量构建
FROM rust:1.75-alpine as builder
RUN apk add --no-cache musl-dev openssl-dev
WORKDIR /app
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl
# 运行时镜像
FROM alpine:latest
RUN apk add --no-cache ca-certificates curl
WORKDIR /app
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/api-hub .
EXPOSE 8080
HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1
ENTRYPOINT ["./api-hub"]
```
---
## 使用步骤
1. **复制模板**到 `.gitea/workflows/{service}.yml`
2. **修改变量**
- `SERVICE_NAME`: 服务名称
- 触发条件中的分支和标签模式
3. **配置 Secrets**
- `REGISTRY_TOKEN`: Container registry 访问令牌
4. **可选配置**
- 健康检查端点和端口
- 构建目标平台
- Webhook 通知 URL
5. **推送代码**触发 workflow
---
## 高级功能
### 多平台构建
```yaml
- name: 安装交叉编译工具
run: |
rustup target add x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-musl
# cargo install cross
- name: 构建多平台二进制
run: |
# x86_64 GNU
cargo build --release --target x86_64-unknown-linux-gnu
# x86_64 MUSL
cargo build --release --target x86_64-unknown-linux-musl
# ARM64 MUSL (使用 cross)
# cross build --release --target aarch64-unknown-linux-musl
```
### 集成测试
```yaml
- name: 集成测试
run: |
# 启动依赖服务数据库、Redis 等)
docker compose up -d postgres redis
# 等待服务就绪
sleep 5
# 运行集成测试
cargo test --test integration_tests
# 清理
docker compose down
```
### 安全扫描
```yaml
- name: 安全审计
run: |
cargo install cargo-audit
cargo audit
```
---
## 版本
- **模板版本**: 1.0
- **最后更新**: 2026-01-29
- **基于项目**: API Hub
- **优化重点**: 变量命名规范、简化配置、增强 Release 功能

View File

@@ -243,7 +243,7 @@ jobs:
- name: Create Release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
VERSION: ${{ needs.build-and-upload.outputs.version }}
run: |
git_tag=$(git describe --tags --abbrev=0)
@@ -252,14 +252,14 @@ jobs:
# 创建 Release
release_id=$(curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag} (v${VERSION})\"}" \
"${api_url}/repos/${repo}/releases" | jq -r '.id')
# 上传源码包
curl -s -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "attachment=@dist/${{ env.SERVICE_PREFIX }}-${VERSION}-source.tar.gz" \
"${api_url}/repos/${repo}/releases/${release_id}/assets"
```