Files
gitea-cli/scripts/lib/common.sh
timmy f28f20ba56 feat(config): support multiple Gitea profiles via GITEA_PROFILE env var
未設時讀 config.env(行為不變);設了讀 config.<profile>.env。
.gitignore 新增 config.*.env 但保留 config.example.env。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:18:16 +08:00

203 lines
6.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# gitea-cli 共用 lib。
# 使用方式(呼叫端):
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# source "${SCRIPT_DIR}/lib/common.sh"
# load_config
#
# 重要:所有 API 呼叫走 gitea_api所有 push/clone 走 git_push_with_token / git_clone_with_token。
# token 絕不允許出現在 .git/config、remote URL、log 中。
set -euo pipefail
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_SKILL_DIR="$(cd "${_LIB_DIR}/../.." && pwd)"
# GITEA_PROFILE 未設 → 讀 config.env設了 → 讀 config.<profile>.env
if [[ -n "${GITEA_PROFILE:-}" ]]; then
CONFIG_FILE="${_SKILL_DIR}/config.${GITEA_PROFILE}.env"
else
CONFIG_FILE="${_SKILL_DIR}/config.env"
fi
GITEA_API_RESP_FILE=""
_cleanup_common() {
if [[ -n "${GITEA_API_RESP_FILE:-}" && -f "${GITEA_API_RESP_FILE}" ]]; then
rm -f "${GITEA_API_RESP_FILE}"
fi
}
trap _cleanup_common EXIT
# === logging全部寫 stderrstdout 留給結構化輸出)===
log_info() { printf '▶ %s\n' "$*" >&2; }
log_ok() { printf '✓ %s\n' "$*" >&2; }
log_warn() { printf '⚠ %s\n' "$*" >&2; }
log_err() { printf '❌ %s\n' "$*" >&2; }
log_done() { printf '✅ %s\n' "$*" >&2; }
# === 必要指令檢查 ===
require_cmd() {
local missing=()
for c in "$@"; do
command -v "$c" >/dev/null 2>&1 || missing+=("$c")
done
if [[ ${#missing[@]} -gt 0 ]]; then
log_err "缺少指令:${missing[*]}"
exit 1
fi
}
# === config 載入 ===
load_config() {
require_cmd curl git
if [[ ! -f "$CONFIG_FILE" ]]; then
log_err "找不到設定檔:${CONFIG_FILE}"
{
echo ""
echo "請先建立:"
echo " cp ${_SKILL_DIR}/config.example.env ${CONFIG_FILE}"
echo ""
echo "然後編輯 ${CONFIG_FILE} 填入 GITEA_URL / GITEA_USER / GITEA_TOKEN"
if [[ -n "${GITEA_PROFILE:-}" ]]; then
echo ""
echo "(目前使用 profile${GITEA_PROFILE}"
fi
} >&2
exit 1
fi
# shellcheck disable=SC1090
source "$CONFIG_FILE"
: "${GITEA_URL:?GITEA_URL 未設定(請編輯 ${CONFIG_FILE}}"
: "${GITEA_USER:?GITEA_USER 未設定(請編輯 ${CONFIG_FILE}}"
: "${GITEA_TOKEN:?GITEA_TOKEN 未設定(請編輯 ${CONFIG_FILE}}"
if [[ "$GITEA_TOKEN" == "請貼上你的_access_token" ]]; then
log_err "GITEA_TOKEN 仍是預設值,請編輯 ${CONFIG_FILE}"
exit 1
fi
if [[ "$GITEA_URL" == "http://your-gitea.example.com:3000" ]]; then
log_err "GITEA_URL 仍是預設值,請編輯 ${CONFIG_FILE}"
exit 1
fi
if [[ "$GITEA_USER" == "your-username" ]]; then
log_err "GITEA_USER 仍是預設值,請編輯 ${CONFIG_FILE}"
exit 1
fi
GITEA_URL="${GITEA_URL%/}"
export GITEA_URL GITEA_USER GITEA_TOKEN
}
# === Gitea API 呼叫 ===
# gitea_api <METHOD> <path-from-/api/v1> [json-body]
# 回傳後讀:$HTTP_CODE狀態碼字串、$GITEA_API_RESP_FILEbody 檔路徑)
gitea_api() {
# 清掉上一次的 tmpfile防止多次呼叫時洩漏
if [[ -n "${GITEA_API_RESP_FILE:-}" && -f "${GITEA_API_RESP_FILE}" ]]; then
rm -f "${GITEA_API_RESP_FILE}"
fi
local method="$1"
local path="$2"
local body="${3:-}"
local url="${GITEA_URL}/api/v1${path}"
GITEA_API_RESP_FILE="$(mktemp)"
local -a curl_args=(
-sS
-o "$GITEA_API_RESP_FILE"
-w '%{http_code}'
-X "$method"
-H "Authorization: token ${GITEA_TOKEN}"
)
if [[ -n "$body" ]]; then
curl_args+=(-H "Content-Type: application/json" -d "$body")
fi
HTTP_CODE="$(curl "${curl_args[@]}" "$url")"
export HTTP_CODE GITEA_API_RESP_FILE
}
# 一次性帶 token 的 git push / clone
# 注意token 透過 -c 參數傳入,在 git 子程序存活期間短暫出現於 process 列表
# /proc/PID/cmdline。這是此模式固有取捨不會持久化到 .git/config / remote URL / log。
_git_with_token() {
git \
-c 'credential.helper=' \
-c "credential.helper=!f() { echo username=${GITEA_USER}; echo password=${GITEA_TOKEN}; }; f" \
"$@"
}
git_push_with_token() {
local branch="$1"
_git_with_token push -u origin "$branch"
}
git_push_tag_with_token() {
local tag="$1"
_git_with_token push origin "$tag"
}
git_clone_with_token() {
local clean_url="$1"
local target_dir="$2"
_git_with_token clone "$clean_url" "$target_dir"
# 再確保 origin 是乾淨 URLclone 本來就是用 clean_url這裡是防呆
git -C "$target_dir" remote set-url origin "$clean_url"
}
# 從 cwd origin 推斷 repo 名稱
# 成功時 echo "owner/repo"timmy/my-project並回傳 0失敗回傳非 0
# 呼叫端若只需要 repo 名稱repo_name="${result##*/}"
# 只支援 http(s):// 形式 URLssh (git@) 不支援(刻意 YAGNI
infer_repo_from_cwd() {
local url
if ! url="$(git remote get-url origin 2>/dev/null)"; then
return 1
fi
# 支援 http(s)://host[:port]/owner/repo[.git]
local path="${url#*://}"
path="${path#*/}"
path="${path%.git}"
[[ -z "$path" ]] && return 1
echo "$path"
}
# === 確認提示 ===
# confirm_destructive <prompt>
# 若 GITEA_YES=1 則直接通過;否則 y/N 互動
confirm_destructive() {
local prompt="$1"
if [[ "${GITEA_YES:-0}" == "1" ]]; then
return 0
fi
if ! { true < /dev/tty; } 2>/dev/null; then
log_err "需要確認但無 TTY請加 -y / --yes 或設定 GITEA_YES=1"
exit 1
fi
local ans
printf '%s [y/N] ' "$prompt" >&2
read -r ans < /dev/tty
case "$ans" in
y|Y|yes|YES) return 0 ;;
*) log_err "取消"; exit 1 ;;
esac
}
# confirm_exact_match <expected>
# 要求使用者重新輸入完整字串才通過GITEA_YES=1 可略過
confirm_exact_match() {
local expected="$1"
if [[ "${GITEA_YES:-0}" == "1" ]]; then
return 0
fi
if ! { true < /dev/tty; } 2>/dev/null; then
log_err "需要確認但無 TTY請加 -y / --yes 或設定 GITEA_YES=1"
exit 1
fi
local ans
printf '請完整輸入 "%s" 以確認:' "$expected" >&2
read -r ans < /dev/tty
if [[ "$ans" == "$expected" ]]; then
return 0
fi
log_err "輸入不符合,取消"
exit 1
}