commit 25539ea8926df713048a4a5ab15912e22c8ef013 Author: figmar Date: Sun Aug 9 08:11:19 2026 +0800 Publish gitea-publish via gitea-publish skill diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..ab13ec5 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,63 @@ +--- +name: gitea-publish +description: 将本地目录发布到自托管 Gitea 实例,自动建仓并推送。 +--- + +# Gitea 发布技能 + +把任意本地目录发布为 Gitea 仓库:自动建仓(若不存在)+ git 提交并推送。 +典型用途:开发一个 WorkBuddy 技能后,直接把它发布到你自己的 Gitea。 + +## 何时使用 + +- "嘎查" / "嘎查一下"(暗号,谐音 Gitea) +- "把这个技能发布到我的 Gitea" +- "推送到 gitea.apescale.com" +- "在 gitea 上建一个仓库并把内容传上去" +- "把生成内容提交到个人 gitea" + +## 配置(凭据,务必通过变量/参数传入,切勿硬编码) + +通过环境变量或命令行参数传入,避免把令牌写进任何文件或在聊天里回显: + +- `GITEA_URL` : Gitea 基地址,例如 `https://gitea.apescale.com` +- `GITEA_USER` : 用户名 +- `GITEA_TOKEN` : 个人访问令牌(PAT,推荐)或密码 + +生成 PAT:Gitea 网页 → 右上角头像 → 设置 → 应用 → 生成令牌。 + +## 用法 + +运行本技能附带的脚本(路径相对于本 SKILL.md 所在目录): + +```bash +bash /publish.sh \ + --repo <仓库名> \ + --path <本地目录> \ + --url "$GITEA_URL" \ + --user "$GITEA_USER" \ + --token "$GITEA_TOKEN" \ + [--display-url "$GITEA_DISPLAY_URL"] \ # 可选:对外展示的公开域名,默认 https://gitea.apescale.com + [--private] # 可选:创建私有仓库 + [--branch <分支>] # 可选:指定默认分支,默认 main +``` + +也可以用同名环境变量替代 `--url/--user/--token`。 + +## 脚本流程 + +1. 调用 Gitea API `GET /api/v1/repos/{user}/{repo}` 检查仓库是否存在; + 不存在则用 `POST /api/v1/user/repos` 创建(支持 `--private`)。 +2. 在 `--path` 内执行 `git init`(若尚未初始化),提交全部文件。 +3. 设置 remote 为 `://:@//.git` 并推送默认分支。协议(http/https)从 `--url` 参数自动提取。 +4. 若远程已有内容,先 `pull --rebase` 再推送,降低冲突风险。 + +## 注意事项 + +- 凭据仅用于本次建仓/推送,不要写入仓库或回显到聊天记录。 +- 默认分支优先使用 `main`,回退到 `master`。 +- 该技能可"自举":开发完成后,用它把自身(即本目录)发布到 Gitea。 +- **协议自适应**:脚本从 `--url` 中提取协议(`http://` 或 `https://`),不再硬编码 `https://`。HTTP-only 的 Gitea 实例也可正常推送。 +- **代理环境**:若运行环境有全局代理(如沙箱),需设置 `no_proxy='*'` 或在命令前添加 `http_proxy='' https_proxy=''`,否则 curl/git 可能走代理导致连接失败。 +- **凭据自动发现**:若用户未显式提供凭据,可检查 macOS 钥匙串(`git credential-osxkeychain get`)和 `~/.ssh/config`(Host 条目)自动发现 Gitea 地址和凭据。 +- **对外地址始终为公开域名**:脚本把"实际推送/API 端点"与"对外展示的 Web 地址"解耦。无论 `--url`(或 `GITEA_URL`)是内网 IP、端口还是其他主机名,最终打印给用户的仓库地址都落在 `--display-url` / `GITEA_DISPLAY_URL` 指定的公开域名下(默认 `https://gitea.apescale.com`),绝不把内网主机名或带凭据的 URL 透出。 diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 0000000..4a9eadb --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# gitea-publish helper: 在自托管 Gitea 上建仓(若不存在)并推送一个本地目录。 +# 凭据通过参数或环境变量传入,不写入任何文件;推送时用一次性鉴权 URL, +# 本地 .git/config 只保存不含口令的干净 remote,避免口令落盘。 +set -euo pipefail + +REPO="" +PATH_="" +URL="${GITEA_URL:-}" +USER="${GITEA_USER:-}" +TOKEN="${GITEA_TOKEN:-}" +DISPLAY_URL="" +PRIVATE="false" +BRANCH="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) REPO="$2"; shift 2;; + --path) PATH_="$2"; shift 2;; + --url) URL="$2"; shift 2;; + --display-url) DISPLAY_URL="$2"; shift 2;; + --user) USER="$2"; shift 2;; + --token) TOKEN="$2"; shift 2;; + --private) PRIVATE="true"; shift;; + --branch) BRANCH="$2"; shift 2;; + *) echo "未知参数: $1" >&2; exit 1;; + esac +done + +# ---- 校验必填项 ---- +[[ -z "$REPO" ]] && { echo "缺少 --repo" >&2; exit 1; } +[[ -z "$PATH_" ]] && { echo "缺少 --path" >&2; exit 1; } +[[ -d "$PATH_" ]] || { echo "目录不存在: $PATH_" >&2; exit 1; } +[[ -z "$URL" ]] && { echo "缺少 --url 或环境变量 GITEA_URL" >&2; exit 1; } +[[ -z "$USER" ]] && { echo "缺少 --user 或环境变量 GITEA_USER" >&2; exit 1; } +[[ -z "$TOKEN" ]] && { echo "缺少 --token 或环境变量 GITEA_TOKEN" >&2; exit 1; } + +# 规范化:去掉结尾斜杠 +URL="${URL%/}" + +# 对外透出的 Web 地址:默认使用公开域名 gitea.apescale.com。 +# 即使 git 推送 / API 调用走的是内网地址或 IP,最终展示给用户的链接 +# 也始终落在公开域名下,避免把内网主机名 / IP 直接暴露出来。 +DISPLAY_URL="${DISPLAY_URL:-${GITEA_DISPLAY_URL:-https://gitea.apescale.com}}" +DISPLAY_URL="${DISPLAY_URL%/}" +WEB_URL="$DISPLAY_URL/$USER/$REPO" + +# 对凭据做 URL 百分号编码(口令可能含 # $ @ 等特殊字符) +urlencode() { python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$1"; } +U_ENC="$(urlencode "$USER")" +P_ENC="$(urlencode "$TOKEN")" +AUTH_ENC="$U_ENC:$P_ENC" # 仅用于嵌入 git remote URL +AUTH_RAW="$USER:$TOKEN" # 仅用于 curl -u(走 Basic Auth 头,base64 安全) + +# 推断默认分支 +if [[ -z "$BRANCH" ]]; then + if [[ -d "$PATH_/.git" ]]; then + BRANCH="$(git -C "$PATH_" symbolic-ref --short HEAD 2>/dev/null || true)" + fi +fi +[[ -z "$BRANCH" ]] && BRANCH="main" + +API="$URL/api/v1" +PROTO="${URL%%://*}" # 从 URL 提取协议 (http 或 https) +HOST="${URL#*//}" # 提取主机+端口 +REMOTE_CLEAN="$PROTO://$HOST/$USER/$REPO.git" +REMOTE_AUTH="$PROTO://$AUTH_ENC@$HOST/$USER/$REPO.git" + +echo "==> 检查仓库 $USER/$REPO 是否存在 ..." +HTTP="$(curl -sS -o /dev/null -w '%{http_code}' -u "$AUTH_RAW" "$API/repos/$USER/$REPO")" +if [[ "$HTTP" == "200" ]]; then + echo " 仓库已存在,跳过创建。" +elif [[ "$HTTP" == "404" ]]; then + echo " 仓库不存在,创建中 ..." + CREATE_JSON="$(printf '{"name":"%s","private":%s,"auto_init":false,"default_branch":"%s"}' "$REPO" "$PRIVATE" "$BRANCH")" + CREATE_HTTP="$(curl -sS -o /tmp/gitea_create.json -w '%{http_code}' -u "$AUTH_RAW" \ + -H 'Content-Type: application/json' -X POST "$API/user/repos" -d "$CREATE_JSON")" + if [[ "$CREATE_HTTP" != "201" && "$CREATE_HTTP" != "200" ]]; then + echo "创建仓库失败 (HTTP $CREATE_HTTP):" >&2 + cat /tmp/gitea_create.json >&2 + exit 1 + fi + echo " 仓库已创建。" +else + echo "检查仓库时返回意外状态: $HTTP" >&2 + exit 1 +fi + +# ---- git 操作 ---- +cd "$PATH_" +[[ ! -d .git ]] && git init -q -b "$BRANCH" + +git config user.email "${GITEA_USER:-gitea-publish}@local" 2>/dev/null || true +git config user.name "${GITEA_USER:-gitea-publish}" 2>/dev/null || true + +# remote 只保存不含口令的干净地址,方便后续查看/手动更新 +if git remote get-url origin >/dev/null 2>&1; then + git remote set-url origin "$REMOTE_CLEAN" +else + git remote add origin "$REMOTE_CLEAN" +fi + +git add -A +if git diff --cached --quiet; then + echo " 没有需要提交的变更。" +else + git commit -q -m "Publish $REPO via gitea-publish skill" + echo " 已提交。" +fi + +# 若远程已有该分支,先用带口令的 URL rebase,避免冲突(不落盘口令) +if git ls-remote --exit-code "$REMOTE_AUTH" "$BRANCH" >/dev/null 2>&1; then + git pull -q --rebase "$REMOTE_AUTH" "$BRANCH" || true +fi + +# 用一次性带口令 URL 推送,不设置含口令的 upstream +git push "$REMOTE_AUTH" "$BRANCH" +git branch --set-upstream-to="origin/$BRANCH" 2>/dev/null || true +echo "==> 完成。仓库地址: $WEB_URL"