Initial commit: Add interactive Gitea download script
This commit is contained in:
3
.cursorindexingignore
Normal file
3
.cursorindexingignore
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
|
||||
.specstory/**
|
||||
2
.specstory/.gitignore
vendored
Normal file
2
.specstory/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# SpecStory explanation file
|
||||
/.what-is-this.md
|
||||
1253
.specstory/history/2025-07-27_09-31Z-自动化下载gitea私有仓库文件.md
Normal file
1253
.specstory/history/2025-07-27_09-31Z-自动化下载gitea私有仓库文件.md
Normal file
File diff suppressed because it is too large
Load Diff
321
gitea.sh
Normal file
321
gitea.sh
Normal file
@ -0,0 +1,321 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# gitea.sh - Gitea 交互式下载器
|
||||
#
|
||||
# 一个用于通过交互式菜单浏览和下载 Gitea 私有仓库中文件的脚本。
|
||||
#
|
||||
# 依赖: curl, jq, fzf
|
||||
|
||||
# --- 配置 ---
|
||||
CONFIG_FILE="${HOME}/.gitea_cli_config"
|
||||
|
||||
# --- 辅助函数 ---
|
||||
|
||||
# 带颜色的输出函数
|
||||
_print() {
|
||||
local color_code="$1"
|
||||
shift
|
||||
if [ -t 1 ]; then # 检查标准输出是否为终端
|
||||
printf "\e[${color_code}m%s\e[0m\n" "$@"
|
||||
else
|
||||
printf "%s\n" "$@"
|
||||
fi
|
||||
}
|
||||
info() { _print "34" "$@"; } # 蓝色
|
||||
success() { _print "32" "$@"; } # 绿色
|
||||
error() { >&2 _print "31" "$@"; } # 红色
|
||||
warning() { _print "33" "$@"; } # 黄色
|
||||
|
||||
# 脚本退出时调用的清理函数
|
||||
cleanup() {
|
||||
# 恢复光标和终端设置
|
||||
tput cnorm 2>/dev/null
|
||||
stty echo 2>/dev/null
|
||||
return
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# 检查必要的依赖工具
|
||||
check_dependencies() {
|
||||
local missing_deps=()
|
||||
for dep in curl jq fzf; do
|
||||
if ! command -v "$dep" &> /dev/null; then
|
||||
missing_deps+=("$dep")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_deps[@]} -gt 0 ]; then
|
||||
error "错误: 必要的依赖工具未安装: ${missing_deps[*]}"
|
||||
|
||||
# 检查是否为 Debian/Ubuntu 环境以便提供自动安装
|
||||
if ! command -v apt-get &> /dev/null; then
|
||||
error "未找到 'apt-get' 包管理器。自动安装功能仅支持 Debian/Ubuntu 系统。"
|
||||
warning "请为您的系统手动安装以下依赖: ${missing_deps[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 sudo 是否可用
|
||||
if [[ $EUID -ne 0 ]] && ! command -v sudo &> /dev/null; then
|
||||
error "检测到您不是 root 用户, 且 'sudo' 命令不可用。无法进行自动安装。"
|
||||
error "请使用 root 用户运行,或安装 'sudo' 后重试。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read -r -p "是否尝试使用 'sudo apt-get' 自动安装? [Y/n] " choice
|
||||
case "$choice" in
|
||||
n|N)
|
||||
error "用户取消安装。请手动安装依赖后重试。"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
info "\n即将开始自动安装依赖..."
|
||||
local SUDO_CMD=""
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
SUDO_CMD="sudo"
|
||||
fi
|
||||
|
||||
info "步骤 1/2: 更新软件包列表 (apt-get update)..."
|
||||
$SUDO_CMD apt-get update || {
|
||||
error "\n执行 'apt-get update' 失败。请检查您的软件源配置或网络连接。"
|
||||
exit 1
|
||||
}
|
||||
|
||||
info "步骤 2/2: 安装缺失的软件包 (${missing_deps[*]})..."
|
||||
$SUDO_CMD apt-get install -y "${missing_deps[@]}" || {
|
||||
error "\n依赖包自动安装失败。"
|
||||
error "请尝试手动执行: '$SUDO_CMD apt-get install -y ${missing_deps[*]}'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
success "\n依赖已成功安装!"
|
||||
info "重新验证依赖..."
|
||||
# 重新验证确保所有依赖都已就位
|
||||
for dep in "${missing_deps[@]}"; do
|
||||
if ! command -v "$dep" &> /dev/null; then
|
||||
error "严重错误: 依赖 '$dep' 在安装后仍无法找到。"
|
||||
error "请手动检查安装过程。"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
success "所有依赖均已准备就绪!"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# --- 配置管理 ---
|
||||
|
||||
# 从文件加载配置
|
||||
load_config() {
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$CONFIG_FILE"
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 提示用户输入配置并保存
|
||||
prompt_for_config() {
|
||||
info "--- Gitea 交互式下载器配置 ---"
|
||||
warning "您的 Gitea 实例信息将被保存在: $CONFIG_FILE"
|
||||
|
||||
while true; do
|
||||
read -r -p "请输入您的 Gitea 实例 URL (例如 https://git.example.com): " GITEA_URL
|
||||
if [[ -n "$GITEA_URL" ]]; then break; else error "URL 不能为空。"; fi
|
||||
done
|
||||
|
||||
GITEA_URL=${GITEA_URL%/} # 移除末尾的斜杠
|
||||
|
||||
while true; do
|
||||
read -r -s -p "请输入您的 Gitea Access Token: " GITEA_TOKEN
|
||||
echo
|
||||
if [[ -n "$GITEA_TOKEN" ]]; then break; else error "Access Token 不能为空。"; fi
|
||||
done
|
||||
|
||||
# 保存到配置文件
|
||||
cat > "$CONFIG_FILE" << EOF
|
||||
# Gitea CLI Configuration
|
||||
GITEA_URL="${GITEA_URL}"
|
||||
ACCESS_TOKEN="${GITEA_TOKEN}"
|
||||
EOF
|
||||
chmod 600 "$CONFIG_FILE" # 设置文件权限保护 Token
|
||||
success "\n配置已成功保存至 $CONFIG_FILE"
|
||||
}
|
||||
|
||||
# --- Gitea API 函数 ---
|
||||
|
||||
# 通用 API 请求函数
|
||||
gitea_api_get() {
|
||||
local endpoint="$1"
|
||||
local full_url="${GITEA_URL}${endpoint}"
|
||||
|
||||
# 使用 --fail 使 curl 在遇到 HTTP 错误时返回非零退出码
|
||||
curl --fail -s -H "Authorization: token ${ACCESS_TOKEN}" -H "Accept: application/json" "${full_url}"
|
||||
}
|
||||
|
||||
# 获取与 Token 关联的用户名
|
||||
get_gitea_user() {
|
||||
gitea_api_get "/api/v1/user" | jq -r '.login'
|
||||
}
|
||||
|
||||
# 获取认证用户的所有仓库
|
||||
fetch_repos() {
|
||||
# 限制为 200 个仓库,如果需要可以后续增加分页功能
|
||||
gitea_api_get "/api/v1/user/repos?limit=200" | jq -r '.[].full_name'
|
||||
}
|
||||
|
||||
# 获取指定仓库的文件列表
|
||||
fetch_file_list() {
|
||||
local full_repo_name="$1"
|
||||
|
||||
info "正在获取 '${full_repo_name}' 的默认分支信息..."
|
||||
local default_branch
|
||||
default_branch=$(gitea_api_get "/api/v1/repos/${full_repo_name}" | jq -r '.default_branch')
|
||||
if [ -z "$default_branch" ] || [ "$default_branch" == "null" ]; then
|
||||
error "无法确定 '${full_repo_name}' 的默认分支。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local commit_sha
|
||||
commit_sha=$(gitea_api_get "/api/v1/repos/${full_repo_name}/branches/${default_branch}" | jq -r '.commit.id')
|
||||
if [ -z "$commit_sha" ] || [ "$commit_sha" == "null" ]; then
|
||||
error "无法获取分支 '${default_branch}' 的 commit SHA。"
|
||||
return 1
|
||||
fi
|
||||
|
||||
info "正在获取文件树..."
|
||||
gitea_api_get "/api/v1/repos/${full_repo_name}/git/trees/${commit_sha}?recursive=1" | jq -r '.tree[] | select(.type == "blob") | .path'
|
||||
}
|
||||
|
||||
# 下载单个文件
|
||||
download_file() {
|
||||
local full_repo_name="$1"
|
||||
local file_path="$2"
|
||||
local owner_repo=(${full_repo_name//// })
|
||||
local owner=${owner_repo[0]}
|
||||
local repo_name=${owner_repo[1]}
|
||||
|
||||
local output_file
|
||||
output_file=$(basename "${file_path}")
|
||||
# 兼容 Windows,替换路径中的 / 为 \ (虽然 curl 可能不需要)
|
||||
mkdir -p "$(dirname "$file_path")"
|
||||
|
||||
local download_url="${GITEA_URL}/api/v1/repos/${owner}/${repo_name}/raw/${file_path}"
|
||||
|
||||
info "正在下载 '${file_path}'..."
|
||||
curl -L --progress-bar --fail \
|
||||
-H "Authorization: token ${ACCESS_TOKEN}" \
|
||||
-o "${file_path}" \
|
||||
"${download_url}" \
|
||||
|| { error "\n下载 '${file_path}' 失败。"; return 1; }
|
||||
|
||||
success "成功下载至 '${file_path}'"
|
||||
}
|
||||
|
||||
# --- 主逻辑 ---
|
||||
|
||||
main() {
|
||||
check_dependencies
|
||||
|
||||
if ! load_config; then
|
||||
prompt_for_config
|
||||
load_config
|
||||
else
|
||||
local username
|
||||
username=$(get_gitea_user)
|
||||
info "已从 $CONFIG_FILE 加载配置"
|
||||
warning "URL: $GITEA_URL"
|
||||
warning "用户: $username"
|
||||
read -r -p "是否使用此配置? [Y/n/reset] " choice
|
||||
case "$choice" in
|
||||
n|N)
|
||||
info "操作取消。"
|
||||
exit 0
|
||||
;;
|
||||
reset|RESET)
|
||||
prompt_for_config
|
||||
load_config
|
||||
;;
|
||||
*)
|
||||
# 使用当前配置继续
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
error "无法加载 Access Token,请检查您的配置。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 步骤 1: 选择仓库
|
||||
info "\n正在获取您的仓库列表..."
|
||||
local repos
|
||||
repos=$(fetch_repos)
|
||||
if [ -z "$repos" ]; then
|
||||
error "无法获取仓库列表。请检查您的 Token、URL 以及 Token 是否拥有 'read:repository' 权限。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local selected_repo
|
||||
selected_repo=$(echo "$repos" | fzf --height 40% --reverse --prompt="请选择一个仓库 > " --header="使用方向键导航, Enter 键选择。")
|
||||
|
||||
if [ -z "$selected_repo" ]; then
|
||||
info "未选择仓库,操作取消。"
|
||||
exit 0
|
||||
fi
|
||||
success "已选择仓库: $selected_repo"
|
||||
|
||||
# 步骤 2: 选择文件
|
||||
info "\n正在获取 '${selected_repo}' 的文件列表..."
|
||||
local files
|
||||
files=$(fetch_file_list "$selected_repo")
|
||||
|
||||
if [ -z "$files" ]; then
|
||||
warning "仓库 '${selected_repo}' 为空或发生错误。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local selected_files
|
||||
selected_files=$(echo "$files" | fzf --multi --height 60% --reverse --prompt="请选择要下载的文件 > " --header="使用 Tab 键选中/取消多个文件, Enter 键确认。")
|
||||
|
||||
if [ -z "$selected_files" ]; then
|
||||
info "未选择文件,操作取消。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 步骤 3: 下载选中的文件
|
||||
info "\n准备下载选中的文件..."
|
||||
local file_count=0
|
||||
local success_count=0
|
||||
local failed_count=0
|
||||
|
||||
local OLD_IFS="$IFS"
|
||||
IFS=$'\n'
|
||||
for file in $selected_files; do
|
||||
((file_count++))
|
||||
if download_file "$selected_repo" "$file"; then
|
||||
((success_count++))
|
||||
else
|
||||
((failed_count++))
|
||||
fi
|
||||
done
|
||||
IFS="$OLD_IFS"
|
||||
|
||||
# 最终总结
|
||||
info "\n--- 下载完成 ---"
|
||||
success "成功下载: ${success_count} 个文件。"
|
||||
if [ "$failed_count" -gt 0 ]; then
|
||||
error "下载失败: ${failed_count} 个文件。"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- 脚本入口 ---
|
||||
|
||||
if [[ "$1" != "down" ]]; then
|
||||
warning "用法: $(basename "$0") down"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
main
|
||||
Reference in New Issue
Block a user