From 870b21916cd0e8fe1159aa50a58e1280bbdb310e Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 24 Dec 2025 17:44:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(install):=20=E6=B7=BB=E5=8A=A0=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E6=8C=87=E5=AE=9A=E7=89=88=E6=9C=AC=E5=92=8C=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 rollback 命令支持回退到指定版本 - 新增 list-versions 命令列出可用版本 - 新增 -v/--version 参数指定安装版本 - upgrade 命令支持升级到指定版本 - 添加安装状态检查,未安装时给出明确提示 - 版本切换仅替换二进制文件,保留配置和数据 - 自动备份当前版本(带版本号或时间戳后缀) - 改进网络错误处理,添加超时和友好提示 - 修复 grep -oP 兼容性问题,改用 grep -oE --- deploy/install.sh | 379 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 347 insertions(+), 32 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 85d243e4..819cfc0c 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -101,6 +101,18 @@ declare -A MSG_ZH=( ["starting_service"]="正在启动服务..." ["upgrade_complete"]="升级完成!" + # Version install + ["installing_version"]="正在安装指定版本" + ["version_not_found"]="指定版本不存在" + ["same_version"]="已经是该版本,无需操作" + ["rollback_complete"]="版本回退完成!" + ["install_version_complete"]="指定版本安装完成!" + ["validating_version"]="正在验证版本..." + ["available_versions"]="可用版本列表" + ["fetching_versions"]="正在获取可用版本..." + ["not_installed"]="Sub2API 尚未安装,请先执行全新安装" + ["fresh_install_hint"]="用法" + # Uninstall ["uninstall_confirm"]="这将从系统中移除 Sub2API。" ["are_you_sure"]="确定要继续吗?(y/N)" @@ -118,6 +130,9 @@ declare -A MSG_ZH=( ["cmd_install"]="安装 Sub2API" ["cmd_upgrade"]="升级到最新版本" ["cmd_uninstall"]="卸载 Sub2API" + ["cmd_install_version"]="安装/回退到指定版本" + ["cmd_list_versions"]="列出可用版本" + ["opt_version"]="指定要安装的版本号 (例如: v1.0.0)" # Server configuration ["server_config_title"]="服务器配置" @@ -207,6 +222,18 @@ declare -A MSG_EN=( ["starting_service"]="Starting service..." ["upgrade_complete"]="Upgrade completed!" + # Version install + ["installing_version"]="Installing specified version" + ["version_not_found"]="Specified version not found" + ["same_version"]="Already at this version, no action needed" + ["rollback_complete"]="Version rollback completed!" + ["install_version_complete"]="Specified version installed!" + ["validating_version"]="Validating version..." + ["available_versions"]="Available versions" + ["fetching_versions"]="Fetching available versions..." + ["not_installed"]="Sub2API is not installed. Please run a fresh install first" + ["fresh_install_hint"]="Usage" + # Uninstall ["uninstall_confirm"]="This will remove Sub2API from your system." ["are_you_sure"]="Are you sure? (y/N)" @@ -224,6 +251,9 @@ declare -A MSG_EN=( ["cmd_install"]="Install Sub2API" ["cmd_upgrade"]="Upgrade to the latest version" ["cmd_uninstall"]="Remove Sub2API" + ["cmd_install_version"]="Install/rollback to a specific version" + ["cmd_list_versions"]="List available versions" + ["opt_version"]="Specify version to install (e.g., v1.0.0)" # Server configuration ["server_config_title"]="Server Configuration" @@ -428,16 +458,88 @@ check_dependencies() { # Get latest release version get_latest_version() { print_info "$(msg 'fetching_version')" - LATEST_VERSION=$(curl -s "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') + LATEST_VERSION=$(curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" 2>/dev/null | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') if [ -z "$LATEST_VERSION" ]; then print_error "$(msg 'failed_get_version')" + print_info "Please check your network connection or try again later." exit 1 fi print_info "$(msg 'latest_version'): $LATEST_VERSION" } +# List available versions +list_versions() { + print_info "$(msg 'fetching_versions')" + + local versions + versions=$(curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${GITHUB_REPO}/releases" 2>/dev/null | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/' | head -20) + + if [ -z "$versions" ]; then + print_error "$(msg 'failed_get_version')" + print_info "Please check your network connection or try again later." + exit 1 + fi + + echo "" + echo "$(msg 'available_versions'):" + echo "----------------------------------------" + echo "$versions" | while read -r version; do + echo " $version" + done + echo "----------------------------------------" + echo "" +} + +# Validate if a version exists +validate_version() { + local version="$1" + + # Check for empty version + if [ -z "$version" ]; then + print_error "$(msg 'opt_version')" >&2 + exit 1 + fi + + # Ensure version starts with 'v' + if [[ ! "$version" =~ ^v ]]; then + version="v$version" + fi + + print_info "$(msg 'validating_version') $version" >&2 + + # Check if the release exists + local http_code + http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${version}" 2>/dev/null) + + # Check for network errors (empty or non-numeric response) + if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then + print_error "Network error: Failed to connect to GitHub API" >&2 + exit 1 + fi + + if [ "$http_code" != "200" ]; then + print_error "$(msg 'version_not_found'): $version" >&2 + echo "" >&2 + list_versions >&2 + exit 1 + fi + + # Return the normalized version (to stdout) + echo "$version" +} + +# Get current installed version +get_current_version() { + if [ -f "$INSTALL_DIR/sub2api" ]; then + # Use grep -E for better compatibility (works on macOS and Linux) + "$INSTALL_DIR/sub2api" --version 2>/dev/null | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown" + else + echo "not_installed" + fi +} + # Download and extract download_and_extract() { local version_num=${LATEST_VERSION#v} @@ -678,13 +780,18 @@ print_completion() { # Upgrade function upgrade() { + # Check if Sub2API is installed + if [ ! -f "$INSTALL_DIR/sub2api" ]; then + print_error "$(msg 'not_installed')" + print_info "$(msg 'fresh_install_hint'): $0 install" + exit 1 + fi + print_info "$(msg 'upgrading')" # Get current version - if [ -f "$INSTALL_DIR/sub2api" ]; then - CURRENT_VERSION=$("$INSTALL_DIR/sub2api" --version 2>/dev/null | grep -oP 'v?\d+\.\d+\.\d+' || echo "unknown") - print_info "$(msg 'current_version'): $CURRENT_VERSION" - fi + CURRENT_VERSION=$("$INSTALL_DIR/sub2api" --version 2>/dev/null | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown") + print_info "$(msg 'current_version'): $CURRENT_VERSION" # Stop service if systemctl is-active --quiet sub2api; then @@ -693,10 +800,8 @@ upgrade() { fi # Backup current binary - if [ -f "$INSTALL_DIR/sub2api" ]; then - cp "$INSTALL_DIR/sub2api" "$INSTALL_DIR/sub2api.backup" - print_info "$(msg 'backup_created'): $INSTALL_DIR/sub2api.backup" - fi + cp "$INSTALL_DIR/sub2api" "$INSTALL_DIR/sub2api.backup" + print_info "$(msg 'backup_created'): $INSTALL_DIR/sub2api.backup" # Download and install new version get_latest_version @@ -712,6 +817,82 @@ upgrade() { print_success "$(msg 'upgrade_complete')" } +# Install specific version (for upgrade or rollback) +# Requires: Sub2API must already be installed +install_version() { + local target_version="$1" + + # Check if Sub2API is installed + if [ ! -f "$INSTALL_DIR/sub2api" ]; then + print_error "$(msg 'not_installed')" + print_info "$(msg 'fresh_install_hint'): $0 install -v $target_version" + exit 1 + fi + + # Validate and normalize version + target_version=$(validate_version "$target_version") + + print_info "$(msg 'installing_version'): $target_version" + + # Get current version + local current_version + current_version=$(get_current_version) + print_info "$(msg 'current_version'): $current_version" + + # Check if same version + if [ "$current_version" = "$target_version" ] || [ "$current_version" = "${target_version#v}" ]; then + print_warning "$(msg 'same_version')" + exit 0 + fi + + # Stop service if running + if systemctl is-active --quiet sub2api; then + print_info "$(msg 'stopping_service')" + systemctl stop sub2api + fi + + # Backup current binary (for potential recovery) + if [ -f "$INSTALL_DIR/sub2api" ]; then + local backup_name + if [ "$current_version" != "unknown" ] && [ "$current_version" != "not_installed" ]; then + backup_name="sub2api.backup.${current_version}" + else + backup_name="sub2api.backup.$(date +%Y%m%d%H%M%S)" + fi + cp "$INSTALL_DIR/sub2api" "$INSTALL_DIR/$backup_name" + print_info "$(msg 'backup_created'): $INSTALL_DIR/$backup_name" + fi + + # Set LATEST_VERSION to the target version for download_and_extract + LATEST_VERSION="$target_version" + + # Download and install + download_and_extract + + # Set permissions + chown "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR/sub2api" + + # Start service + print_info "$(msg 'starting_service')" + if systemctl start sub2api; then + print_success "$(msg 'service_started')" + else + print_error "$(msg 'service_start_failed')" + print_info "sudo journalctl -u sub2api -n 50" + fi + + # Print completion message + local new_version + new_version=$(get_current_version) + echo "" + echo "==============================================" + print_success "$(msg 'install_version_complete')" + echo "==============================================" + echo "" + echo " $(msg 'current_version'): $new_version" + echo "" +} + # Uninstall function uninstall() { print_warning "$(msg 'uninstall_confirm')" @@ -753,13 +934,43 @@ uninstall() { # Main main() { - # Parse -y flag first - for arg in "$@"; do - if [ "$arg" = "-y" ] || [ "$arg" = "--yes" ]; then - FORCE_YES="true" - fi + # Parse flags first + local target_version="" + local positional_args=() + + while [[ $# -gt 0 ]]; do + case "$1" in + -y|--yes) + FORCE_YES="true" + shift + ;; + -v|--version) + if [ -n "${2:-}" ] && [[ ! "$2" =~ ^- ]]; then + target_version="$2" + shift 2 + else + echo "Error: --version requires a version argument" + exit 1 + fi + ;; + --version=*) + target_version="${1#*=}" + if [ -z "$target_version" ]; then + echo "Error: --version requires a version argument" + exit 1 + fi + shift + ;; + *) + positional_args+=("$1") + shift + ;; + esac done + # Restore positional arguments + set -- "${positional_args[@]}" + # Select language first select_language @@ -769,12 +980,83 @@ main() { echo "==============================================" echo "" - # Parse arguments + # Parse commands case "${1:-}" in upgrade|update) check_root detect_platform - upgrade + check_dependencies + if [ -n "$target_version" ]; then + # Upgrade to specific version + install_version "$target_version" + else + # Upgrade to latest + upgrade + fi + exit 0 + ;; + install) + # Install with optional version + check_root + detect_platform + check_dependencies + if [ -n "$target_version" ]; then + # Install specific version (fresh install or rollback) + if [ -f "$INSTALL_DIR/sub2api" ]; then + # Already installed, treat as version change + install_version "$target_version" + else + # Fresh install with specific version + configure_server + LATEST_VERSION=$(validate_version "$target_version") + download_and_extract + create_user + setup_directories + install_service + prepare_for_setup + get_public_ip + start_service + enable_autostart + print_completion + fi + else + # Fresh install with latest version + configure_server + get_latest_version + download_and_extract + create_user + setup_directories + install_service + prepare_for_setup + get_public_ip + start_service + enable_autostart + print_completion + fi + exit 0 + ;; + rollback) + # Rollback to a specific version (alias for install with version) + if [ -z "$target_version" ] && [ -n "${2:-}" ]; then + target_version="$2" + fi + if [ -z "$target_version" ]; then + print_error "$(msg 'opt_version')" + echo "" + echo "Usage: $0 rollback -v " + echo " $0 rollback " + echo "" + list_versions + exit 1 + fi + check_root + detect_platform + check_dependencies + install_version "$target_version" + exit 0 + ;; + list-versions|versions) + list_versions exit 0 ;; uninstall|remove) @@ -786,32 +1068,65 @@ main() { echo "$(msg 'usage'): $0 [command] [options]" echo "" echo "Commands:" - echo " $(msg 'cmd_none') $(msg 'cmd_install')" - echo " upgrade $(msg 'cmd_upgrade')" - echo " uninstall $(msg 'cmd_uninstall')" + echo " $(msg 'cmd_none') $(msg 'cmd_install')" + echo " install $(msg 'cmd_install')" + echo " upgrade $(msg 'cmd_upgrade')" + echo " rollback $(msg 'cmd_install_version')" + echo " list-versions $(msg 'cmd_list_versions')" + echo " uninstall $(msg 'cmd_uninstall')" echo "" echo "Options:" - echo " -y, --yes Skip confirmation prompts (for uninstall)" + echo " -v, --version $(msg 'opt_version')" + echo " -y, --yes Skip confirmation prompts (for uninstall)" + echo "" + echo "Examples:" + echo " $0 # Install latest version" + echo " $0 install -v v0.1.0 # Install specific version" + echo " $0 upgrade # Upgrade to latest" + echo " $0 upgrade -v v0.2.0 # Upgrade to specific version" + echo " $0 rollback v0.1.0 # Rollback to v0.1.0" + echo " $0 list-versions # List available versions" echo "" exit 0 ;; esac - # Fresh install + # Default: Fresh install with latest version check_root detect_platform check_dependencies - configure_server - get_latest_version - download_and_extract - create_user - setup_directories - install_service - prepare_for_setup - get_public_ip - start_service - enable_autostart - print_completion + + if [ -n "$target_version" ]; then + # Install specific version + if [ -f "$INSTALL_DIR/sub2api" ]; then + install_version "$target_version" + else + configure_server + LATEST_VERSION=$(validate_version "$target_version") + download_and_extract + create_user + setup_directories + install_service + prepare_for_setup + get_public_ip + start_service + enable_autostart + print_completion + fi + else + # Install latest version + configure_server + get_latest_version + download_and_extract + create_user + setup_directories + install_service + prepare_for_setup + get_public_ip + start_service + enable_autostart + print_completion + fi } main "$@"