diff --git a/setup/foxel.sh b/setup/foxel.sh index e07fb9a..89c44f3 100644 --- a/setup/foxel.sh +++ b/setup/foxel.sh @@ -1,31 +1,30 @@ #!/bin/bash -#================================================================================ -# Foxel 一键部署与更新脚本 # -# 作者: maxage -# 版本: 1.7 (增加下载镜像, 解决网络问题) -# 描述: 此脚本用于自动化安装、配置和管理 Foxel 项目 (使用 Docker Compose)。 -# - 智能检测现有安装,提供安装向导和管理菜单两种模式。 -# - 自动检测并安装依赖。 -# - 为国内用户提供镜像源切换选项。 -# -# 一键运行命令: +# Foxel 一键安装与管理脚本(Docker Compose) +# 一键运行: # bash <(curl -sL "https://raw.githubusercontent.com/DrizzleTime/Foxel/main/setup/foxel.sh?_=$(date +%s)") -#================================================================================ +# -# --- 消息打印函数 --- -info() { - echo "[信息] $1" -} +# --- 输出(可关闭颜色:NO_COLOR=1) --- +if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then + C_RESET='\033[0m' + C_RED='\033[31m' + C_GREEN='\033[32m' + C_YELLOW='\033[33m' + C_BLUE='\033[34m' +else + C_RESET='' + C_RED='' + C_GREEN='' + C_YELLOW='' + C_BLUE='' +fi -warn() { - echo "[警告] $1" -} - -error() { - echo "[错误] $1" -} +info() { printf "%b[信息]%b %s\n" "$C_BLUE" "$C_RESET" "$*"; } +success() { printf "%b[成功]%b %s\n" "$C_GREEN" "$C_RESET" "$*"; } +warn() { printf "%b[警告]%b %s\n" "$C_YELLOW" "$C_RESET" "$*"; } +error() { printf "%b[错误]%b %s\n" "$C_RED" "$C_RESET" "$*"; } # --- 基础函数 --- command_exists() { @@ -34,16 +33,33 @@ command_exists() { confirm_action() { local prompt_message="$1" - printf "%s" "${prompt_message} (y/n): " - read confirmation - if [[ "$confirmation" =~ ^[Yy]$ ]]; then - return 0 # Yes + local default="${2:-N}" + local hint='[y/N]' + local confirmation + + if [[ "$default" =~ ^[Yy]$ ]]; then + default="Y" + hint='[Y/n]' else - return 1 # No + default="N" + hint='[y/N]' fi + + while true; do + read -r -p "${prompt_message} ${hint}: " confirmation + if [[ -z "$confirmation" ]]; then + [[ "$default" == "Y" ]] && return 0 || return 1 + fi + + case "$confirmation" in + [Yy]|[Yy][Ee][Ss]) return 0 ;; + [Nn]|[Nn][Oo]) return 1 ;; + *) warn "请输入 y 或 n。" ;; + esac + done } -# --- IP地址检测函数 (只输出IP) --- +# --- IP地址检测函数(只输出IP) --- get_public_ipv4() { curl -4 -s --max-time 2 https://api.ipify.org || \ curl -4 -s --max-time 2 https://ifconfig.me/ip || \ @@ -65,7 +81,7 @@ get_private_ip() { # --- 依赖与环境检查 --- check_and_install_dependencies() { - info "正在检查所需依赖..." + info "检查依赖..." declare -A deps=( [curl]="curl" [openssl]="openssl" [ss]="iproute2" ) local missing_deps=() for cmd in "${!deps[@]}"; do @@ -75,8 +91,8 @@ check_and_install_dependencies() { done if [ ${#missing_deps[@]} -gt 0 ]; then - warn "检测到以下依赖项缺失: ${missing_deps[*]}" - if confirm_action "是否尝试自动安装它们?"; then + warn "缺少依赖: ${missing_deps[*]}" + if confirm_action "是否尝试自动安装?" "Y"; then local pm_cmd="" if command_exists apt-get; then pm_cmd="sudo apt-get update && sudo apt-get install -y"; elif command_exists yum; then pm_cmd="sudo yum install -y"; @@ -87,12 +103,12 @@ check_and_install_dependencies() { for cmd in "${!deps[@]}"; do if ! command_exists "$cmd"; then error "依赖 '${deps[$cmd]}' 自动安装失败。"; exit 1; fi done - info "依赖已成功安装。" + success "依赖安装完成。" else error "用户取消了安装。请先手动安装依赖: ${missing_deps[*]}"; exit 1 fi else - info "所有基础依赖均已满足。" + success "依赖已满足。" fi } @@ -101,64 +117,107 @@ initialize_environment() { if ! command_exists docker; then error "未找到 Docker。请参照官方文档安装: https://docs.docker.com/engine/install/"; exit 1; fi - if ! docker info &> /dev/null; then error "Docker deamon 未在运行。请先启动 Docker。"; exit 1; fi - info "Docker 环境检测通过。" + if ! docker info &> /dev/null; then error "Docker daemon 未在运行。请先启动 Docker。"; exit 1; fi + success "Docker 环境正常。" if command_exists docker-compose; then COMPOSE_CMD="docker-compose"; elif docker compose version &> /dev/null; then COMPOSE_CMD="docker compose"; else error "未找到 Docker Compose。请安装 Docker Compose v1 或 v2。"; exit 1; fi - info "检测到 Docker Compose 命令: $COMPOSE_CMD" + info "Docker Compose: $COMPOSE_CMD" +} + +set_image_source_official() { + sed -i -E 's|^([[:space:]]*)#?image:[[:space:]]*ghcr\.io/drizzletime/foxel:latest|\1image: ghcr.io/drizzletime/foxel:latest|' compose.yaml + sed -i -E 's|^([[:space:]]*)#?image:[[:space:]]*ghcr\.nju\.edu\.cn/drizzletime/foxel:latest|\1#image: ghcr.nju.edu.cn/drizzletime/foxel:latest|' compose.yaml +} + +set_image_source_mirror() { + sed -i -E 's|^([[:space:]]*)#?image:[[:space:]]*ghcr\.io/drizzletime/foxel:latest|\1#image: ghcr.io/drizzletime/foxel:latest|' compose.yaml + sed -i -E 's|^([[:space:]]*)#?image:[[:space:]]*ghcr\.nju\.edu\.cn/drizzletime/foxel:latest|\1image: ghcr.nju.edu.cn/drizzletime/foxel:latest|' compose.yaml +} + +choose_image_source() { + echo + info "请选择镜像源:" + echo "1) ghcr.io (默认)" + echo "2) ghcr.nju.edu.cn (国内)" + local image_choice + read -r -p "请选择 [1-2] (默认 1): " image_choice + image_choice="${image_choice:-1}" + + case "$image_choice" in + 1) + set_image_source_official + info "已选择: ghcr.io" + ;; + 2) + set_image_source_mirror + info "已选择: ghcr.nju.edu.cn" + ;; + *) + warn "无效选择,使用默认 ghcr.io" + set_image_source_official + ;; + esac } # --- 新安装流程 --- install_new_foxel() { - info "--- 开始 Foxel 全新安装 ---" - local install_path + info "开始全新安装..." + local foxel_dir + local default_dir="/opt/foxel" + while true; do - read -p "请输入您想在哪里创建 Foxel 的数据目录 (例如: /opt/docker): " install_path - if [[ -z "$install_path" ]]; then warn "输入不能为空,请重新输入。"; continue; fi - if [ ! -d "$install_path" ]; then - if confirm_action "目录 '$install_path' 不存在。您想现在创建它吗?"; then - mkdir -p "$install_path" - if [ $? -eq 0 ]; then info "目录 '$install_path' 创建成功。"; break; - else error "创建目录 '$install_path' 失败。"; fi - else info "操作已取消。"; fi - else info "将使用已存在的目录 '$install_path'。"; break; fi + read -r -p "请输入 Foxel 安装目录 (默认: ${default_dir}): " foxel_dir + foxel_dir="${foxel_dir:-$default_dir}" + + if [[ -f "$foxel_dir/compose.yaml" ]]; then + warn "检测到已存在: $foxel_dir/compose.yaml" + if confirm_action "是否覆盖它?" "N"; then + mv "$foxel_dir/compose.yaml" "$foxel_dir/compose.yaml.bak.$(date +%s)" + info "已备份为: $foxel_dir/compose.yaml.bak.*" + else + continue + fi + fi + + if [[ -d "$foxel_dir" ]]; then + break + fi + + if confirm_action "目录不存在,是否创建?" "Y"; then + if mkdir -p "$foxel_dir"; then + break + fi + error "创建目录失败: $foxel_dir" + fi done echo - local foxel_dir="$install_path/Foxel" - info "将在 '$foxel_dir' 目录中创建所需文件..." + info "准备目录: $foxel_dir" mkdir -p "$foxel_dir/data/"{db,mount} && chmod 777 "$foxel_dir/data/"{db,mount} if [ $? -ne 0 ]; then error "创建或设置子目录权限失败。"; exit 1; fi cd "$foxel_dir" || exit - info "正在下载 'compose.yaml'..." + info "下载 compose.yaml..." local COMPOSE_MIRROR_URL="https://ghproxy.com/https://raw.githubusercontent.com/DrizzleTime/Foxel/main/compose.yaml" local COMPOSE_OFFICIAL_URL="https://raw.githubusercontent.com/DrizzleTime/Foxel/main/compose.yaml" - if ! curl -L -o compose.yaml "$COMPOSE_MIRROR_URL"; then - warn "镜像源下载失败,正在尝试从官方源下载..." - if ! curl -L -o compose.yaml "$COMPOSE_OFFICIAL_URL"; then + if ! curl -fsSL -o compose.yaml "$COMPOSE_MIRROR_URL"; then + warn "镜像下载失败,尝试官方源..." + if ! curl -fsSL -o compose.yaml "$COMPOSE_OFFICIAL_URL"; then error "下载 'compose.yaml' 失败。请检查您的网络连接。"; exit 1; fi fi - info "'compose.yaml' 下载成功。" + success "compose.yaml 下载成功。" echo - if confirm_action "您的服务器是否位于中国大陆(以便为您选择更快的镜像源)?"; then - info "正在切换到国内镜像源..." - sed -i 's|^\( *\)image: ghcr.io/drizzletime/foxel:latest|\1#image: ghcr.io/drizzletime/foxel:latest|' compose.yaml - sed -i 's|^\( *\)#image: ghcr.nju.edu.cn/drizzletime/foxel:latest|\1image: ghcr.nju.edu.cn/drizzletime/foxel:latest|' compose.yaml - info "已成功切换到 ghcr.nju.edu.cn 镜像源。" - else - info "将使用默认的 ghcr.io 官方镜像源。" - fi + choose_image_source echo local new_port while true; do - read -p "请输入新的对外端口 (或直接按回车使用默认的 8088): " new_port + read -r -p "请输入对外端口 (默认 8088): " new_port if [[ -z "$new_port" ]]; then new_port="8088" info "将使用默认端口 8088。" @@ -173,30 +232,29 @@ install_new_foxel() { if ss -tuln | grep -q ":${new_port}\b"; then warn "端口 $new_port 已被占用,请换一个。" else - sed -i "s/\"8088:80\"/\"$new_port:80\"/" compose.yaml + sed -i -E "s|\"[0-9]{1,5}:80\"|\"$new_port:80\"|" compose.yaml info "端口已成功修改为 $new_port。" break fi done echo - if ! confirm_action "是否需要生成新的随机密钥 (推荐)?(选择 'n' 将使用默认值)"; then + if ! confirm_action "是否生成新的随机密钥(推荐)?" "Y"; then info "将使用 'compose.yaml' 文件中的默认密钥。" else info "正在生成新的随机密钥..." sed -i "s|SECRET_KEY=.*|SECRET_KEY=$(openssl rand -base64 32)|" compose.yaml sed -i "s|TEMP_LINK_SECRET_KEY=.*|TEMP_LINK_SECRET_KEY=$(openssl rand -base64 32)|" compose.yaml - info "新的密钥已成功生成并替换。" + success "新的密钥已写入 compose.yaml。" fi echo - if confirm_action "所有配置已准备就绪!您想现在启动 Foxel 项目吗?"; then - info "正在启动 Foxel 服务... 这可能需要一些时间来拉取镜像。" + if confirm_action "配置完成,是否现在启动 Foxel?" "Y"; then + info "启动中(首次会拉取镜像,可能需要几分钟)..." $COMPOSE_CMD pull && $COMPOSE_CMD up -d if [ $? -eq 0 ]; then - info "Foxel 部署成功!" - info "-------------------------------------------------" - info "正在检测服务器IP地址,请稍候..." + success "Foxel 已启动。" + info "正在检测访问地址..." # 先捕获所有IP地址 local public_ipv4=$(get_public_ipv4 2>/dev/null) @@ -206,7 +264,7 @@ install_new_foxel() { local ip_found=false echo - info "部署完成!您可以通过以下地址访问 Foxel:" + info "访问地址:" if [[ -n "$private_ip" ]]; then echo " - 局域网地址: http://${private_ip}:${final_port}" @@ -226,12 +284,16 @@ install_new_foxel() { warn "未能自动检测到服务器IP地址。" echo " 请手动使用 http://[您的服务器IP]:${final_port} 访问它。" fi - echo "-------------------------------------------------" + echo + info "常用命令:" + echo " - 启动/更新: cd $foxel_dir && $COMPOSE_CMD up -d" + echo " - 停止: cd $foxel_dir && $COMPOSE_CMD stop" + echo " - 日志: cd $foxel_dir && $COMPOSE_CMD logs -f" else error "启动 Foxel 失败。请运行 'cd $foxel_dir && $COMPOSE_CMD logs' 查看日志。" fi else - info "操作已取消。您可以稍后进入 '$foxel_dir' 并手动运行 '$COMPOSE_CMD up -d'。" + info "已跳过启动。稍后可运行:cd $foxel_dir && $COMPOSE_CMD up -d" fi } @@ -291,7 +353,7 @@ manage_existing_installation() { case $choice in 1) # 更新 warn "更新前,强烈建议您备份 '$foxel_dir/data' 目录!" - if confirm_action "您确定要继续更新吗?"; then + if confirm_action "确认继续更新?" "Y"; then info "正在拉取最新镜像..." $COMPOSE_CMD pull info "正在使用新镜像重新部署..." @@ -302,14 +364,14 @@ manage_existing_installation() { 2) # 卸载 warn "这将停止并删除 Foxel 容器及相关网络!" warn "强烈建议您先备份 '$foxel_dir/data' 目录!" - if confirm_action "您确定要继续卸载吗?"; then + if confirm_action "确认继续卸载?" "N"; then info "正在停止并移除容器..." $COMPOSE_CMD down - if confirm_action "是否要删除所有数据卷(这将删除数据库等所有数据)?"; then + if confirm_action "是否删除所有数据卷(会删除数据库等数据)?" "N"; then $COMPOSE_CMD down -v info "数据卷已删除。" fi - if confirm_action "是否要删除整个 Foxel 安装目录 '$foxel_dir'?"; then + if confirm_action "是否删除 Foxel 安装目录 '$foxel_dir'?" "N"; then rm -rf "$foxel_dir" info "安装目录已删除。" fi @@ -320,7 +382,7 @@ manage_existing_installation() { 3) # 重新安装 warn "重新安装将完全删除当前的 Foxel 实例(包括数据),然后进入全新安装流程。" warn "在继续之前,请务必备份好您的重要数据!" - if confirm_action "您确定要重新安装吗?"; then + if confirm_action "确认继续重新安装?" "N"; then info "正在执行卸载..." $COMPOSE_CMD down -v && rm -rf "$foxel_dir" info "旧实例已彻底移除。" @@ -344,9 +406,8 @@ manage_existing_installation() { # --- 主函数 --- main() { clear - local SCRIPT_VERSION="1.7" echo "=================================================" - info "欢迎使用 Foxel 一键安装与管理脚本 (版本: ${SCRIPT_VERSION})" + info "欢迎使用 Foxel 一键安装与管理脚本" echo "=================================================" echo