feat: add reusable deb upload script
This commit is contained in:
@@ -18,7 +18,8 @@
|
||||
请准备或确认:
|
||||
|
||||
- 要构建或发布的版本号。
|
||||
- 项目已经配置好构建脚本、Make 目标或 `debian/` 目录。
|
||||
- 项目已经配置好构建脚本、Make 目标或 `debian/` 目录;上传脚本由 skill 自带,
|
||||
不需要放进项目。
|
||||
- 发布所需的令牌已经放入项目约定的环境变量或密钥系统。
|
||||
- 如果要真实发布,明确告诉 Agent 目标仓库以及是否允许覆盖同版本。
|
||||
|
||||
@@ -57,7 +58,7 @@ Agent 会优先发现和复用项目已有入口,然后:
|
||||
1. 确认版本、架构、产物路径、目标仓库和授权范围。
|
||||
2. 构建 DEB,或定位你指定的已有产物。
|
||||
3. 检查包的元数据、内容和 SHA-256。
|
||||
4. 在你明确要求发布时,通过项目已有脚本上传。
|
||||
4. 在你明确要求发布时,通过 skill 自带的 `scripts/upload_deb.sh` 上传。
|
||||
5. 检查服务端响应,并在仓库支持时确认该版本已经可见。
|
||||
|
||||
## 如何判断完成
|
||||
@@ -65,3 +66,15 @@ Agent 会优先发现和复用项目已有入口,然后:
|
||||
结果中应包含包名、版本、架构、产物路径、SHA-256,以及构建、上传和仓库可见性的
|
||||
独立状态。异步索引尚未完成时,Agent 应明确说明“上传已接受,索引待更新”。
|
||||
|
||||
## 上传协议
|
||||
|
||||
自带脚本适用于接收 multipart 字段 `package`、`token` 和 `repository_name` 的
|
||||
HTTP DEB 仓库,默认路径是 `/api/v2/upload/package`。使用以下环境变量:
|
||||
|
||||
- `DEB_SERVER_URL`:仓库服务地址。
|
||||
- `DEB_TOKEN`:认证令牌。
|
||||
- `DEB_REPOSITORY`:目标仓库名。
|
||||
- `DEB_UPLOAD_PATH`:可选的上传路径覆盖。
|
||||
|
||||
令牌只从环境变量读取,不作为命令行参数传递。若你的仓库使用其他 API 协议,应先告诉
|
||||
Agent,不要直接套用该脚本。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: deb-publisher
|
||||
description: >-
|
||||
构建并发布 Debian DEB 包:发现项目已有的 Makefile、打包脚本和仓库上传入口,
|
||||
校验包元数据与内容,使用环境变量中的凭据上传,并验证发布结果。触发词:构建 deb、
|
||||
构建并发布 Debian DEB 包:发现项目已有的 Makefile 和打包入口,使用 skill 自带的
|
||||
通用上传脚本提交包,校验包元数据与内容,并验证发布结果。触发词:构建 deb、
|
||||
发布 deb、上传 deb、提交 deb、推送 apt 仓库、打 Debian 包。仅分析打包逻辑时也可使用,
|
||||
但不会在未获授权时执行上传。
|
||||
---
|
||||
@@ -36,12 +36,13 @@ rg -n -i --hidden --glob '!.git' \
|
||||
|
||||
重点检查:
|
||||
|
||||
- `Makefile`、CI 配置、`debian/`、`scripts/` 和发布文档。
|
||||
- `Makefile`、CI 配置、`debian/`、构建脚本和发布文档。
|
||||
- 版本号、包名、架构、产物目录和仓库名如何传入。
|
||||
- 发布端点、认证方式以及发布是否由构建目标自动触发。
|
||||
- 当前工作树和目标版本是否匹配。
|
||||
|
||||
优先复用已有入口。除非用户要求改造,否则不要另建一套并行发布脚本。
|
||||
优先复用已有构建入口。上传默认使用本 skill 的 `scripts/upload_deb.sh`,不要把它
|
||||
复制到项目中;仅当目标仓库协议不兼容时才复用或修改项目专属上传逻辑。
|
||||
|
||||
### 2. 确认发布边界
|
||||
|
||||
@@ -95,22 +96,26 @@ sha256sum <package.deb>
|
||||
|
||||
### 5. 发布
|
||||
|
||||
优先调用项目已有上传目标或脚本,并通过环境变量注入凭据:
|
||||
解析当前 `SKILL.md` 所在目录,以绝对路径调用随 skill 分发的脚本:
|
||||
|
||||
```bash
|
||||
make upload-deb VERSION="$RELEASE_VERSION"
|
||||
DEB_SERVER_URL="$DEB_SERVER_URL" \
|
||||
DEB_TOKEN="$DEB_TOKEN" \
|
||||
DEB_REPOSITORY="$DEB_REPOSITORY" \
|
||||
<skill-dir>/scripts/upload_deb.sh <exact-package-path.deb>
|
||||
```
|
||||
|
||||
若项目上传脚本接受文件参数,传入刚刚校验过的确切路径。不要使用宽泛 glob。
|
||||
不要把脚本复制进当前项目,也不要将 token 作为命令行参数。脚本默认请求
|
||||
`/api/v2/upload/package`,以 multipart 字段 `package`、`token`、
|
||||
`repository_name` 上传,接受 `200` 和 `201` 为成功。端点路径不同时可设置
|
||||
`DEB_UPLOAD_PATH`。
|
||||
|
||||
若必须维护 Bash 上传脚本:
|
||||
调用前确认目标服务使用上述协议;不兼容时不要强行调用。传入刚刚校验过的确切路径,
|
||||
不要使用宽泛 glob。项目已有 `make upload-deb` 时,检查它是否只是包装了同一协议:
|
||||
如果是,直接使用 skill 脚本;若 CI 或其他人仍依赖 Make 目标,可将目标改为调用已安装
|
||||
skill 的脚本,但不要提交脚本副本。
|
||||
|
||||
- 使用 `curl --fail-with-body` 或显式检查 HTTP 状态码。
|
||||
- 只把 `200`/`201` 等服务端约定的状态判为成功。
|
||||
- 用 trap 清理 `mktemp` 创建的响应文件。
|
||||
- 在 `set -e` 下避免 `((count++))` 首次返回状态 1;使用
|
||||
`count=$((count + 1))` 或 `((++count))`。
|
||||
- 多文件上传应汇总每个文件的结果,并在任一失败时返回非零。
|
||||
脚本支持多个确切文件路径,会汇总每个文件的结果,并在任一失败时返回非零。
|
||||
|
||||
### 6. 验证与汇报
|
||||
|
||||
@@ -134,6 +139,8 @@ make upload-deb VERSION="$RELEASE_VERSION"
|
||||
- 保持项目现有变量名和调用入口,避免无关重构。
|
||||
- 修复行为缺陷时增加最小静态检查或可离线运行的测试。
|
||||
- 可用 `bash -n` 检查脚本语法;项目有 ShellCheck 时一并运行。
|
||||
- skill 自带上传脚本是 SSOT;通用上传行为的修改应落在
|
||||
`skills/deb-publisher/scripts/upload_deb.sh`,不要同步复制到业务项目。
|
||||
- 不通过真实生产上传来测试脚本,除非用户明确授权并给出测试版本或测试仓库。
|
||||
|
||||
## 完成标准
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
DEB_SERVER_URL=https://deb.example.com \
|
||||
DEB_TOKEN=secret \
|
||||
DEB_REPOSITORY=main \
|
||||
upload_deb.sh FILE.deb [FILE.deb ...]
|
||||
|
||||
Options:
|
||||
-s SERVER_URL Override DEB_SERVER_URL
|
||||
-n REPOSITORY Override DEB_REPOSITORY
|
||||
-p UPLOAD_PATH Override DEB_UPLOAD_PATH (default: /api/v2/upload/package)
|
||||
-h Show help
|
||||
|
||||
The endpoint must accept multipart fields named package, token, and
|
||||
repository_name. Authentication is read only from DEB_TOKEN so it is not
|
||||
exposed in the process command line.
|
||||
EOF
|
||||
}
|
||||
|
||||
server_url=${DEB_SERVER_URL:-}
|
||||
repository=${DEB_REPOSITORY:-}
|
||||
upload_path=${DEB_UPLOAD_PATH:-/api/v2/upload/package}
|
||||
token=${DEB_TOKEN:-}
|
||||
|
||||
while getopts ":s:n:p:h" option; do
|
||||
case "$option" in
|
||||
s) server_url=$OPTARG ;;
|
||||
n) repository=$OPTARG ;;
|
||||
p) upload_path=$OPTARG ;;
|
||||
h) usage; exit 0 ;;
|
||||
:) echo "Error: -$OPTARG requires a value" >&2; usage >&2; exit 2 ;;
|
||||
\?) echo "Error: unknown option -$OPTARG" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ -z "$server_url" || -z "$repository" || -z "$token" || $# -eq 0 ]]; then
|
||||
echo "Error: DEB_SERVER_URL, DEB_TOKEN, DEB_REPOSITORY, and at least one file are required." >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "$upload_path" != /* ]]; then
|
||||
echo "Error: upload path must start with /" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "Error: curl is required." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
server_url=${server_url%/}
|
||||
success_count=0
|
||||
fail_count=0
|
||||
response_file=
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$response_file" && -f "$response_file" ]]; then
|
||||
rm -f -- "$response_file"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
print_response() {
|
||||
local file=$1
|
||||
if command -v jq >/dev/null 2>&1 && jq -e . "$file" >/dev/null 2>&1; then
|
||||
jq . "$file"
|
||||
else
|
||||
cat -- "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
for package_file in "$@"; do
|
||||
if [[ ! -f "$package_file" ]]; then
|
||||
echo "Skip: file not found: $package_file" >&2
|
||||
fail_count=$((fail_count + 1))
|
||||
continue
|
||||
fi
|
||||
if [[ "$package_file" != *.deb ]]; then
|
||||
echo "Skip: not a .deb file: $package_file" >&2
|
||||
fail_count=$((fail_count + 1))
|
||||
continue
|
||||
fi
|
||||
if [[ ! -s "$package_file" ]]; then
|
||||
echo "Skip: empty file: $package_file" >&2
|
||||
fail_count=$((fail_count + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
response_file=$(mktemp)
|
||||
echo "Uploading $(basename -- "$package_file") to $server_url (repository $repository)..."
|
||||
|
||||
http_code=000
|
||||
if http_code=$(curl --silent --show-error \
|
||||
--output "$response_file" \
|
||||
--write-out "%{http_code}" \
|
||||
--request POST \
|
||||
"$server_url$upload_path" \
|
||||
--form "package=@${package_file};type=application/vnd.debian.binary-package" \
|
||||
--form "token=${token}" \
|
||||
--form "repository_name=${repository}"); then
|
||||
:
|
||||
else
|
||||
echo "Failed (transport error): $(basename -- "$package_file")" >&2
|
||||
print_response "$response_file" >&2
|
||||
fail_count=$((fail_count + 1))
|
||||
cleanup
|
||||
response_file=
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$http_code" == 200 || "$http_code" == 201 ]]; then
|
||||
echo "Success ($http_code): $(basename -- "$package_file")"
|
||||
print_response "$response_file"
|
||||
success_count=$((success_count + 1))
|
||||
else
|
||||
echo "Failed ($http_code): $(basename -- "$package_file")" >&2
|
||||
print_response "$response_file" >&2
|
||||
fail_count=$((fail_count + 1))
|
||||
fi
|
||||
|
||||
cleanup
|
||||
response_file=
|
||||
done
|
||||
|
||||
echo "Done. Success: $success_count, Failed: $fail_count"
|
||||
if ((fail_count > 0)); then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user