commit b3da9bfa9a1748e91e295d03b575dff0e5c7eac1 Author: zhangyang Date: Thu May 14 21:00:50 2026 +0000 添加 install.sh diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..75293f3 --- /dev/null +++ b/install.sh @@ -0,0 +1,2758 @@ +#!/bin/bash +# ================================================================ +# “ VPS 包工头面板 Cloudflare Tunnel版 ” v7.0.9 (最终版) +# 1. 架构重构,移除Nginx Proxy Manager,全面转向Cloudflare Tunnel统一管理域名。 +# 2. 精简菜单选项,移除Alist, Fail2ban, Mail Reporter等低频功能。 +# 3. 优化服务部署流程,安装应用后自动更新Tunnel配置并重启,实现即装即用。 +# 4. 更新“科学上网”工具箱,专注于基于Tunnel的Vless节点部署。 +# 5. 修复了Cloudflared在某些系统上因源地址错误而安装失败的Bug。 +# 6. 全面改用 `docker compose` v2命令,并增加部署后隧道配置的可视化反馈。 +# 7. 新增VPS信息查看功能,方便快速了解服务器状态。 +# 8. 修正了因Markdown链接格式錯誤導致的語法問題。 +# 9. 修正了所有docker-compose.yml文件的YAML语法,以解决“mapping values”错误。 +# 10. 修复了因localhost解析问题导致的ERR_CONNECTION_CLOSED和502错误。 +# 11. 优化了凭证显示,为Nextcloud提供更清晰的首次安装指南。 +# 12. 修复了VLESS节点部署时Docker未检查、端口未映射的严重Bug。 +# 13. (安全加固) 将所有Docker服务的端口绑定到127.0.0.1,避免公网暴露。 +# 14. (安全加固) 对XRDP远程桌面服务进行本地化绑定,防止公网扫描。 +# 15. (功能增强 by 张先生) 自动保存并显示VLESS节点凭证。 +# 16. (最终优化) 实现了远程桌面(RDP)的Tunnel自动化配置。 +# 作者 : 張財多 zhangcaiduo.com +# 全局帮助 : Gemini 地球之神 +# ================================================================ + +# --- 全局函数与配置 --- + +STATE_FILE="/root/.vps_setup_credentials" # 用于存储密码和配置的凭证文件 +TUNNEL_CONFIG_FILE="/root/.cloudflared/config.yml" +RCLONE_CONFIG_FILE="/root/.config/rclone/rclone.conf" +RCLONE_LOG_FILE="/var/log/rclone.log" + +# --- 颜色定义 --- +setup_colors() { + if [[ -t 1 ]]; + then + GREEN='\033[0;32m' + BLUE='\033[0;34m' + RED='\033[0;31m' + YELLOW='\033[0;33m' + CYAN='\033[0;36m' + NC='\033[0m' + else + GREEN='' + BLUE='' + RED='' + YELLOW='' + CYAN='' + NC='' + fi +} + +# --- 美化对齐输出函数 (解决中文宽度问题 - 终极兼容版) --- +print_menu_item() { + local text="$1" + local status="$2" + + local visual_width=$(echo -n "$text" | perl -CS -Mutf8 -nlE ' + use Encode; + my $width = 0; + for my $char (split //, $_) { + $width += (length(Encode::encode("utf8", $char)) > 1 ? 2 : 1); + } + print $width; + ') + + local target_column=72 + local padding_needed=$((target_column - visual_width)) + if [ $padding_needed -lt 1 ]; + then + padding_needed=1 + fi + echo -e -n " ${text}" + printf "%*s" $padding_needed "" + echo -e "${status}" +} + +# --- 首次运行自安装快捷命令 --- +setup_shortcut() { + if [[ "$0" != "bash" && "$0" != "sh" ]]; then + local script_path + script_path=$(realpath "$0") + local link_path="/usr/local/bin/zhangcaiduo" + if [[ -n "$script_path" ]] && { [ ! -L "${link_path}" ] || [ "$(readlink -f "${link_path}")" != "${script_path}" ]; }; then + echo -e "${GREEN}为方便您使用,正在创建快捷命令 'zhangcaiduo'...${NC}" + chmod +x "${script_path}" + sudo ln -sf "${script_path}" "${link_path}" + if [ $? -eq 0 ]; then + echo -e "${GREEN}快捷命令创建成功!请重新登录 SSH 后,或在新的终端会话中,输入 'zhangcaiduo' 即可启动此面板。${NC}" + else + echo -e "${RED}快捷命令创建失败。您仍需使用 'bash ${script_path}' 来运行。${NC}" + fi + sleep 4 + fi + fi +} + +# --- 核心依赖检查函数 --- +check_core_dependencies() { + if ! perl -e 'use Encode;' 2>/dev/null; then + echo -e "${YELLOW}--- 检测到核心组件 Perl::Encode 缺失,正在自动安装 ---${NC}" + echo -e "${YELLOW}这是确保菜单正确对齐所必需的。${NC}" + sleep 2 + sudo apt-get update >/dev/null 2>&1 + sudo apt-get install -y libencode-perl + if ! perl -e 'use Encode;' 2>/dev/null; then + echo -e "${RED}错误:核心组件自动安装失败。${NC}" + echo -e "${RED}请手动执行 'sudo apt-get install libencode-perl' 后重试。${NC}" + exit 1 + fi + echo -e "${GREEN}✅ 核心组件已成功安装!脚本将继续运行。${NC}" + sleep 2 + fi +} + +# --- 核心环境检查函数 --- +ensure_docker_installed() { + if ! command -v docker &> /dev/null || ! docker compose version &> /dev/null; then + echo -e "${YELLOW}--- 检查到 Docker 或 Docker Compose 插件未安装,现在开始自动安装 ---${NC}" + sleep 2 + + if ! command -v docker &> /dev/null; then + sudo apt-get update + sudo apt-get install -y ca-certificates curl gnupg + echo -e "${YELLOW}正在安装 Docker Engine (包含 Compose 插件)...${NC}" + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh && rm get-docker.sh + sudo systemctl restart docker + sudo systemctl enable docker + fi + + if ! docker compose version &> /dev/null; then + echo -e "${YELLOW}Docker Compose 插件未找到, 正在尝试补充安装...${NC}" + sudo apt-get update + sudo apt-get install -y docker-compose-plugin + fi + + if ! command -v docker &> /dev/null || ! docker compose version &> /dev/null; then + echo -e "${RED}错误:Docker 环境自动安装失败,请检查网络或手动安装后重试。${NC}" + sleep 5 + return 1 + else + echo -e "${GREEN}✅ Docker 环境已成功安装并准备就绪!${NC}" + sleep 2 + fi + fi + + if ! sudo docker info >/dev/null 2>&1; then + echo -e "${YELLOW}检测到 Docker 服务未运行,正在尝试启动...${NC}" + sudo systemctl start docker + sleep 3 + if ! sudo docker info >/dev/null 2>&1; then + echo -e "${RED}错误:无法启动 Docker 服务!请手动检查 'sudo systemctl status docker'。${NC}" + return 1 + fi + echo -e "${GREEN}✅ Docker 服务已成功启动!${NC}" + fi + return 0 +} + +# --- 系统基础功能函数 --- +update_system() { + clear + echo -e "${BLUE}--- 更新系统与软件 (apt update && upgrade) ---${NC}" + echo -e "${YELLOW}即将开始更新系统软件包列表并升级所有已安装的软件 ...${NC}" + sudo apt-get update && sudo apt-get upgrade -y + echo -e "\n${GREEN}✅ 系统更新完成!${NC}" + echo -e "\n${GREEN}按任意键返回主菜单 ...${NC}"; read -n 1 -s +} + +run_unminimize() { + clear + echo -e "${BLUE}--- 恢复至标准系统 (unminimize) ---${NC}" + if grep -q -i "ubuntu" /etc/os-release; then + echo -e "${YELLOW}此操作将为您的最小化 Ubuntu 系统安装完整的标准系统包。${NC}" + echo -e "${YELLOW}它会增加一些磁盘占用,但可以解决某些软件的兼容性问题。${NC}" + read -p "您确定要继续吗? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "${GREEN}正在执行 unminimize ,请稍候 ...${NC}" + sudo unminimize + echo -e "\n${GREEN}✅ 操作完成!${NC}" + else + echo -e "${GREEN}操作已取消。${NC}" + fi + else + echo -e "${RED}此功能专为 Ubuntu 系统设计,您当前的系统似乎不是 Ubuntu 。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单 ...${NC}"; read -n 1 -s +} + +manage_swap() { + clear + echo -e "${BLUE}--- 配置虚拟内存 (Swap) ---${NC}" + if swapon --show | grep -q '/swapfile'; then + echo -e "${YELLOW}检测到已存在 /swapfile 虚拟内存。${NC}" + read -p "您想移除现有的虚拟内存吗? (y/n): " confirm_remove + if [[ "$confirm_remove" == "y" || "$confirm_remove" == "Y" ]]; then + echo -e "${YELLOW}正在停止并移除虚拟内存...${NC}" + sudo swapoff /swapfile + sudo sed -i '/\/swapfile/d' /etc/fstab + sudo rm -f /swapfile + echo -e "${GREEN}✅ 虚拟内存已成功移除!${NC}" + free -h + else + echo -e "${GREEN}操作已取消。${NC}" + fi + else + echo -e "${YELLOW}未检测到虚拟内存。现在为您创建。${NC}" + read -p "请输入您期望的 Swap 大小 (例如: 4G, 8G, 10G) [建议为内存的1-2倍]: " swap_size + if [ -z "$swap_size" ]; then + echo -e "${RED}输入为空,操作取消。${NC}"; sleep 2; return + fi + sudo fallocate -l ${swap_size} /swapfile + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + if ! grep -q "/swapfile" /etc/fstab; then + echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab + fi + echo -e "\n${GREEN}✅ 虚拟内存创建并启用成功!${NC}" + free -h + fi + echo -e "\n${GREEN}按任意键返回主菜单 ...${NC}"; read -n 1 -s +} + +# --- 检查与菜单显示函数 --- +check_and_display() { + local option_num="$1" + local text="$2" + local check_type="$3" + local check_path="$4" + local display_text="${option_num}) ${text}" + local status_string="[ ❌ 未安装 ]" + + local is_installed=false + case "$check_type" in + "dir") + [ -d "$check_path" ] && is_installed=true + ;; + "file") + [ -f "$check_path" ] && is_installed=true + ;; + "service") + systemctl is-active --quiet "$check_path" 2>/dev/null && is_installed=true + ;; + esac + + if $is_installed; then + status_string="[ ${GREEN}✅ 已安装${NC} ]" + fi + + if [[ "$text" == *"Rclone"* && -f "$check_path" ]]; + then + if grep -q "RCLONE_REMOTE" "${STATE_FILE}"; + then + local remote_name + remote_name=$(grep "RCLONE_REMOTE" "${STATE_FILE}" | cut -d'=' -f2) + status_string="[ ${GREEN}✅ 已配置: ${remote_name}${NC} ]" + else + status_string="[ ${GREEN}✅ 已安装, 未配置${NC} ]" + fi + fi + + print_menu_item "${display_text}" "${status_string}" +} + +# --- 动态 LOGO 显示函数 --- +display_animated_logo() { + local logo_lines=( + " ███████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██████╗ █████╗ ██╗██████╗ ██╗ ██╗ ██████╗ " + " ╚══███╔╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝██╔══██╗██║██╔══██╗██║ ██║██╔═══██╗" + " ███╔╝ ███████║███████║██╔██╗ ██║██║ ███╗██║ ███████║██║██║ ██║██║ ██║██║ ██║" + " ███╔╝ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██║ ██╔══██║██║██║ ██║██║ ██║██║ ██║" + " ███████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝╚██████╗██║ ██║██║██████╔╝╚██████╔╝╚██████╔╝" + " ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═════╝ " + ) + local site_line=" zhangcaiduo.com " + local colors=("\033[1;96m" "\033[1;97m" "\033[1;90m" "\033[0;36m") + echo -e "\033[s" + for line in "${logo_lines[@]}"; do echo -e "${CYAN}${line}${NC}"; done + echo -e "${CYAN}${site_line}${NC}" + for _ in {1..30}; do + local random_line=$((RANDOM % ${#logo_lines[@]})) + local random_col=$((RANDOM % ${#logo_lines[random_line]})) + local random_color_index=$((RANDOM % ${#colors[@]})) + local char="${logo_lines[random_line]:$random_col:1}" + if [[ "$char" != " " ]]; then + echo -e "\033[u\033[${random_line}A\033[${random_col}C" + echo -e -n "${colors[random_color_index]}${char}${NC}" + fi + sleep 0.04 + done + echo -e "\033[u" + for line in "${logo_lines[@]}"; do echo -e "${CYAN}${line}${NC}"; done + echo -e "${CYAN}${site_line}${NC}" +} + +# --- 菜单显示 --- +show_main_menu() { + clear + display_animated_logo + + echo -e "${GREEN}============ VPS 包工头面板 Cloudflare Tunnel版 v7.0.9 ===================================${NC}" + echo -e "${BLUE}本脚本适用于 Ubuntu 和 Debian 系统的 VPS 常用项目部署 ${NC}" + echo -e "${BLUE}如果您退出了装修面板,输入 zhangcaiduo 可再次调出 ${NC}" + echo -e "${BLUE}本脚本是小白学习的总结,不做任何商业用途和盈利,感谢 Gemini 地球之神的全局帮助。${NC}" + echo -e "${GREEN}===================================================================================================${NC}" + echo -e " --- 地基与系统 (基础) ---" + print_menu_item "u) 更新系统与软件" "[ apt update && upgrade ]" + print_menu_item "m) 恢复至标准系统" "[ unminimize, 仅限 Ubuntu 系统 ]" + print_menu_item "s) 配置虚拟内存 (Swap)" "[ 增强低配VPS性能 ]" + echo -e "${GREEN}===================================================================================================${NC}" + echo -e " --- 主体装修选项 (应用部署) ---" + check_and_display "1" "部署Cloudflare Tunnel" "service" "cloudflared" + echo "" + check_and_display "2" "配置 Rclone 数据同步桥 (全盘跃遷)" "file" "${RCLONE_CONFIG_FILE}" + echo " -------------------------------------------------------------------------------------------------" + print_menu_item " 3) 部署家庭数据中心" "" + check_and_display " 3.1) └─ 部署 Nextcloud" "dir" "/root/nextcloud_data" + check_and_display " 3.2) └─ 部署 OnlyOffice" "dir" "/root/onlyoffice_data" + check_and_display " 3.3) └─ 部署 Home Assistant" "dir" "/root/home_assistant_data" + check_and_display "4" "部署 WordPress 个人博客" "dir" "/root/wordpress_data" + echo " -------------------------------------------------------------------------------------------------" + check_and_display "5" "部署 AI 大脑 (Ollama+WebUI)" "dir" "/root/ai_stack" + print_menu_item " 17) └─ 为 AI 大脑安装知识库 (安装模型)" "" + echo " -------------------------------------------------------------------------------------------------" + check_and_display "6" "部署 Jellyfin 家庭影院" "dir" "/root/jellyfin_data" + check_and_display "7" "部署 Navidrome 音乐服务器" "dir" "/root/navidrome_data" + check_and_display "8" "部署 Miniflux RSS 阅读器" "dir" "/root/miniflux_data" + check_and_display "9" "部署 Gitea 代码仓库" "dir" "/root/gitea_data" + check_and_display "10" "部署 Memos 轻量笔记" "dir" "/root/memos_data" + echo " -------------------------------------------------------------------------------------------------" + check_and_display "11" "部署 qBittorrent 下载器" "dir" "/root/qbittorrent_data" + check_and_display "12" "部署 JDownloader 下载器" "dir" "/root/jdownloader_data" + check_and_display "13" "部署 yt-dlp 视频下载器" "dir" "/root/ytdlp_data" + check_and_display "14" "部署 Draw.io 绘图工具" "dir" "/root/drawio_data" + echo -e " ${CYAN}注意: 关联Rclone后, 请确保在下载器WEB界面中, 保存路径为 /downloads 或 /output ${NC}" + echo -e "${GREEN}===================================================================================================${NC}" + echo -e " --- 安防与工具 ---" + check_and_display "15.1" "部署远程工作台 (Xfce)" "file" "/etc/xrdp/xrdp.ini" + print_menu_item " 15.2) └─ 为远程工作台安装中文字体" "[ 解决中文乱码问题 ]" + print_menu_item " 15.3) └─ 为远程桌面安装 Chromium 浏览器" "[ 解决无浏览器问题 ]" + check_and_display "18" "部署 Fail2ban (防暴力破解)" "service" "fail2ban" + check_and_display "19" "部署 Uptime Kuma (服务监控)" "dir" "/root/uptime_kuma_data" + check_and_display "19.1" "部署 Glances (实时资源监控)" "file" "/var/lib/docker/containers/$(docker ps -aqf name=glances_app)" + check_and_display "19.2" "部署 Syncthing (两机文件同步)" "service" "syncthing@root" + + # --- 新增 SSH 密钥管理菜单 --- + print_menu_item " 20) 添加 SSH 密钥" "[ 公钥登录 ]" + local ssh_pass_status + if grep -qE "^\s*PasswordAuthentication\s+no" /etc/ssh/sshd_config 2>/dev/null; then + ssh_pass_status="[ ${RED}❌ 已禁用${NC} ]" + else + ssh_pass_status="[ ${GREEN}✅ 已启用${NC} ]" + fi + print_menu_item " 21) 切换 SSH 密码登录" "${ssh_pass_status}" + # --- 新增结束 --- + echo -e "${GREEN}===================================================================================================${NC}" + echo -e " --- 高級功能與維護 ---" + print_menu_item " 22) 執行 Nextcloud 最終性能優化" "" + print_menu_item " 22.1) 部署 Nextcloud 高速风景版" "[ 视听封面+BBR+大文件分块 ]" # <-- 加上这一行 + print_menu_item " 23) 進入服務控制中心" "[ 啟停/重啟/關聯Rclone ]" + print_menu_item " 24) 查看密碼與數據路徑" "[ 重要憑證 ]" + print_menu_item " 25) 查看VPS資訊" "[ 快速了解伺服器狀態 ]" + print_menu_item " 30) 運行收尾工作與優化腳本" "[ 增強與修正 ]" + print_menu_item " 31) 部署甲骨文開機助手 (oci-helper)" "[ 適用於 Oracle Cloud ]" + print_menu_item " 32) 部署伺服器每日管家 (郵件報告)" "[ 每日自動匯報 ]" + print_menu_item " 33) 進入应用升级中心" "[ 🚀 一键升级防催更 ]" # <--- 加在这里 + print_menu_item " 40) 進入應用卸載中心" "[ ${RED}按需刪除${NC} ]" + echo -e "${GREEN}===================================================================================================${NC}" + print_menu_item " 26) 打開“科學上網”工具箱" "[ 部署基於Cloudflare Tunnel的 Vless節點 ]" + print_menu_item " 28) 部署 Hysteria 2 (暴力加速)" "[ 🚀 速度最快的协议, 需放行端口 ]" + echo -e "${GREEN}===================================================================================================${NC}" + echo "" + print_menu_item " X) 一键深度清理 (清理垃圾与缓存)" "[ ${CYAN}让小鸡更丝滑${NC} ]" + print_menu_item " 99) ${RED}一键辞退包工头${NC}" "[ ${RED}注:此选项将会拆卸本脚本!!!${NC} ]" + print_menu_item " q) 退出面板" "" + echo "" + echo -e "${GREEN}===================================================================================================${NC}" +} + +# --- Cloudflare Tunnel 核心管理函数 --- +update_tunnel_config() { + local hostname="$1" + local service_url="$2" + local comment="$3" + local extra_param="$4" + + if [ ! -f "$TUNNEL_CONFIG_FILE" ]; then + echo -e "${RED}错误: Tunnel 配置文件不存在,请先部署 Tunnel。${NC}"; return 1 + fi + + sudo sed -i '/- service: http_status:404/d' "$TUNNEL_CONFIG_FILE" + + if grep -q "hostname: $hostname" "$TUNNEL_CONFIG_FILE"; then + echo -e "${YELLOW}域名 ${hostname} 的配置已存在,跳过添加。${NC}" + else + echo -e "\n # ${comment}" | sudo tee -a "$TUNNEL_CONFIG_FILE" > /dev/null + echo -e " - hostname: $hostname\n service: $service_url" | sudo tee -a "$TUNNEL_CONFIG_FILE" > /dev/null + fi +# 如果传入了 "access" 参数,就添加 Cloudflare Access 规则 +if [[ "$extra_param" == "access" ]]; then + echo -e " # Cloudflare Access 保护" | sudo tee -a "$TUNNEL_CONFIG_FILE" > /dev/null + echo -e " access:\n required: true\n teamsName: \"$(echo $hostname | cut -d. -f2-).cloudflareaccess.com\"" | sudo tee -a "$TUNNEL_CONFIG_FILE" > /dev/null +fi + echo -e " - service: http_status:404" | sudo tee -a "$TUNNEL_CONFIG_FILE" > /dev/null + + echo -e "\n${CYAN}--- Tunnel 配置预览 ---${NC}" + echo -e "${YELLOW}已将以下规则添加/更新至 ${TUNNEL_CONFIG_FILE}:${NC}" + echo "--------------------------------------------------" + grep -A 2 "hostname: $hostname" "$TUNNEL_CONFIG_FILE" | sed 's/^/ /' + echo "--------------------------------------------------" + + echo -e "${YELLOW}正在应用新的网络配置...${NC}" + sudo systemctl restart cloudflared + sleep 2 + if systemctl is-active --quiet cloudflared; then + echo -e "${GREEN}✅ Cloudflare Tunnel 已为 ${hostname} 更新并重启成功!${NC}" + echo -e "${GREEN} 您现在应该可以通过 https://${hostname} 访问您的服务了。${NC}" + else + echo -e "${RED}❌ Cloudflare Tunnel 重启失败!请执行 'sudo journalctl -u cloudflared -f' 查看日志。${NC}" + fi +} + +# 1. 部署Cloudflare Tunnel +install_cloudflare_tunnel() { + clear + echo -e "${BLUE}--- “Cloudflare Tunnel 总线”开始施工! ---${NC}" + + if ! command -v cloudflared &> /dev/null; then + echo -e "${YELLOW}正在安装 Cloudflare Tunnel (cloudflared)...${NC}" + sudo mkdir -p --mode=0755 /usr/share/keyrings + curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null + echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list + sudo apt-get update + sudo apt-get install -y cloudflared + + if ! command -v cloudflared &> /dev/null; then + echo -e "${RED}❌ cloudflared 安装失败!无法找到命令。请检查上面的 apt 日志。${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s; return 1 + fi + echo -e "${GREEN}✅ cloudflared 安装完毕!${NC}" + fi + + echo -e "\n${CYAN}接下来,您需要提供 Cloudflare Tunnel 的令牌 (Token)。${NC}" + echo -e "${CYAN}请登录 Cloudflare Zero Trust Dashboard, 找到 Access -> Tunnels。${NC}" + echo -e "${CYAN}创建一个新的 Tunnel,选择 'Connector' 类型,复制页面上生成的 Token。${NC}" + read -p "请在此处粘贴您的 Tunnel Token: " tunnel_token + if [ -z "$tunnel_token" ]; then + echo -e "${RED}Token 不能为空,安装中止。${NC}"; sleep 3; return + fi + + if sudo cloudflared service install "$tunnel_token"; then + echo "CLOUDFLARE_TOKEN=${tunnel_token}" > ${STATE_FILE} + sudo mkdir -p "$(dirname "$TUNNEL_CONFIG_FILE")" + sudo tee "$TUNNEL_CONFIG_FILE" > /dev/null </dev/null; then + echo -e "${RED}错误:此功能依赖“Cloudflare Tunnel”,请先执行选项 1 进行安装和配置!${NC}" + sleep 3; return 1 + fi + return 0 +} + +# 2. Rclone 数据同步桥 +configure_rclone_engine() { + clear + echo -e "${BLUE}--- “Rclone 数据同步桥”配置向导 (全盘跃遷模式) ---${NC}" + if ! command -v rclone &> /dev/null; then + echo -e "\n${YELLOW}🚀 正在为您安装 Rclone 主程序...${NC}" + curl https://rclone.org/install.sh | sudo bash; sudo apt-get install -y fuse3 + echo -e "${GREEN}✅ Rclone 已安装完毕!${NC}"; sleep 2 + fi + if [ ! -f "${RCLONE_CONFIG_FILE}" ]; then + echo -e "\n${YELLOW}未检测到 Rclone 配置文件。${NC}\n${CYAN}即将启动 Rclone 官方交互式配置工具...${NC}" + read -p "准备好后,请按任意键继续..." -n 1 -s; echo -e "\n"; rclone config + if [ ! -f "${RCLONE_CONFIG_FILE}" ]; then + echo -e "\n${RED}错误:配置似乎未成功保存。请重新尝试。${NC}"; sleep 3; return + fi + echo -e "\n${GREEN}✅ 检测到 Rclone 配置文件已成功创建!${NC}"; sleep 2 + fi + echo -e "\n${CYAN}--- 设置 Rclone 全盘自动挂载 ---${NC}" + read -p "请输入您在上面配置中设置的 remote 名称 (例如 onedrive): " rclone_remote_name + if [ -z "$rclone_remote_name" ]; then echo -e "${RED}remote 名称不能为空,配置中止。${NC}"; sleep 3; return; fi + local rclone_mount_path="/mnt/onedrive" + sed -i.bak '/^RCLONE_REMOTE/d' ${STATE_FILE} 2>/dev/null; sed -i.bak '/^RCLONE_MOUNT_PATH/d' ${STATE_FILE} 2>/dev/null; rm -f "${STATE_FILE}.bak" + echo "RCLONE_REMOTE=${rclone_remote_name}" >> ${STATE_FILE}; echo "RCLONE_MOUNT_PATH=${rclone_mount_path}" >> ${STATE_FILE} + echo -e "\n${YELLOW}正在为 ${rclone_remote_name} 创建全盘挂载通道...${NC}"; sudo mkdir -p "${rclone_mount_path}" + sudo tee "/etc/systemd/system/rclone-vps-mount.service" > /dev/null < /root/nextcloud_data/docker-compose.yml < /root/nextcloud_data/php-opcache.ini + (cd /root/nextcloud_data && sudo docker compose up -d) + + echo -e "${GREEN}✅ Nextcloud 已启动,等待服务稳定...${NC}"; sleep 5 + echo -e "\n## Nextcloud 凭证 (部署于: $(date))" >> ${STATE_FILE} + echo "NEXTCLOUD_DOMAIN=${NEXTCLOUD_DOMAIN}" >> ${STATE_FILE} + echo "DB_USER=nextclouduser" >> ${STATE_FILE} + echo "DB_NAME=nextclouddb" >> ${STATE_FILE} + echo "DB_HOST=db" >> ${STATE_FILE} + echo "DB_PASSWORD=${DB_PASSWORD}" >> ${STATE_FILE} + + echo -e "${GREEN}正在为您配置网络...${NC}" + update_tunnel_config "${NEXTCLOUD_DOMAIN}" "http://127.0.0.1:8888" "Nextcloud" + + echo -e "\n${YELLOW}提示:Nextcloud 核心已部署。请在主菜单选择 3.2) 部署 OnlyOffice 来完成办公套件的安装。${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 3.2. 部署 OnlyOffice +install_onlyoffice() { + ensure_docker_installed || return; check_tunnel_installed || return + read -p "请输入您的主域名 (例如 zhangcaiduo.com): " MAIN_DOMAIN + if [ -z "$MAIN_DOMAIN" ]; then echo -e "${RED}错误:主域名不能为空!${NC}"; sleep 2; return; fi + local ONLYOFFICE_DOMAIN="onlyoffice.${MAIN_DOMAIN}" + local ONLYOFFICE_JWT_SECRET="JwtS3cr3t-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)" + clear; echo -e "${BLUE}--- “OnlyOffice 办公套件”部署计划启动! ---${NC}" + + mkdir -p /root/onlyoffice_data; cat > /root/onlyoffice_data/docker-compose.yml <> ${STATE_FILE} + echo "ONLYOFFICE_DOMAIN=${ONLYOFFICE_DOMAIN}" >> ${STATE_FILE} + echo "ONLYOFFICE_JWT_SECRET=${ONLYOFFICE_JWT_SECRET}" >> ${STATE_FILE} + + echo -e "${GREEN}正在为您配置网络...${NC}" + update_tunnel_config "${ONLYOFFICE_DOMAIN}" "http://127.0.0.1:8889" "OnlyOffice" + + echo -e "\n${YELLOW}提示:OnlyOffice 已部署。请登录您的 Nextcloud,在设置中找到 'ONLYOFFICE',${NC}" + echo -e "${YELLOW}填入此域名 (https://${ONLYOFFICE_DOMAIN}) 和 JWT 密钥 (见选项24) 以完成集成。${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 3.3. 部署 Home Assistant +install_home_assistant() { + ensure_docker_installed || return; check_tunnel_installed || return + read -p "请输入您为 Home Assistant 规划的子域名 (例如 ha.zhangcaiduo.com): " HA_DOMAIN + if [ -z "$HA_DOMAIN" ]; then echo -e "${RED}错误:域名不能为空!${NC}"; sleep 2; return; fi + clear; echo -e "${BLUE}--- “Home Assistant 智能家居中枢”部署计划启动! ---${NC}" + + # 【修复】创建 config 目录,为预配置做准备 + mkdir -p /root/home_assistant_data/config + + cat > /root/home_assistant_data/docker-compose.yml < /root/home_assistant_data/config/configuration.yaml <> ${STATE_FILE} + echo "HA_DOMAIN=${HA_DOMAIN}" >> ${STATE_FILE} + + echo -e "${GREEN}正在为您配置网络...${NC}" + update_tunnel_config "${HA_DOMAIN}" "http://127.0.0.1:8123" "Home Assistant" + + echo -e "\n${GREEN}✅ Home Assistant 部署成功!${NC}" + echo -e "${YELLOW}请访问 https://${HA_DOMAIN} 来创建您的管理员账户并开始配置。${NC}" + else + echo -e "${RED}❌ Home Assistant 部署失败!请检查 Docker 是否正常运行。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 4. WordPress +install_wordpress() { + ensure_docker_installed || return; check_tunnel_installed || return + read -p "请输入您的 WordPress 域名 (例如 blog.zhangcaiduo.com): " WP_DOMAIN + if [ -z "$WP_DOMAIN" ]; then echo -e "${RED}错误:域名不能为空!${NC}"; sleep 2; return; fi + local WP_DB_PASS="WpDb-pW_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12)"; local WP_DB_ROOT_PASS="WpRoot-pW_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12)" + clear; echo -e "${BLUE}--- “WordPress 个人博客”建造计划启动! ---${NC}" + mkdir -p /root/wordpress_data; cat > /root/wordpress_data/docker-compose.yml <> ${STATE_FILE}; echo "WORDPRESS_DOMAIN=${WP_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${WP_DOMAIN}" "http://127.0.0.1:8890" "WordPress" + else echo -e "${RED}❌ WordPress 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 5. AI 核心 +install_ai_suite() { + ensure_docker_installed || return; check_tunnel_installed || return + read -p "请输入您为 AI 规划的子域名 (例如 ai.zhangcaiduo.com): " AI_DOMAIN + if [ -z "$AI_DOMAIN" ]; then echo -e "${RED}错误:AI 域名不能为空!${NC}"; sleep 2; return; fi + clear; echo -e "${BLUE}--- “AI 大脑”激活计划启动! ---${NC}" + mkdir -p /root/ai_stack; cat > /root/ai_stack/docker-compose.yml <> ${STATE_FILE}; echo "AI_DOMAIN=${AI_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${AI_DOMAIN}" "http://127.0.0.1:3001" "AI WebUI" + echo -e "${YELLOW}强烈建议立即执行选项 17 安装一个知识库 (模型)!${NC}" + else echo -e "${RED}❌ AI 核心部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 6. Jellyfin +install_jellyfin() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Jellyfin 规划的子域名 (例如 jellyfin.zhangcaiduo.com): " JELLYFIN_DOMAIN + if [ -z "$JELLYFIN_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + echo -e "${BLUE}--- “Jellyfin 家庭影院”建造计划启动! ---${NC}" + mkdir -p /root/jellyfin_data/config /mnt/Movies /mnt/TVShows /mnt/Music + cat > /root/jellyfin_data/docker-compose.yml <> ${STATE_FILE}; echo "JELLYFIN_DOMAIN=${JELLYFIN_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${JELLYFIN_DOMAIN}" "http://127.0.0.1:8096" "Jellyfin" + else echo -e "${RED}❌ Jellyfin 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 7. Navidrome +install_navidrome() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Navidrome 规划的子域名 (例如 music.zhangcaiduo.com): " NAVIDROME_DOMAIN + if [ -z "$NAVIDROME_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + echo -e "${BLUE}--- “Navidrome 音乐服务器”部署计划启动! ---${NC}"; mkdir -p /root/navidrome_data /mnt/Music + cat > /root/navidrome_data/docker-compose.yml <> ${STATE_FILE}; echo "NAVIDROME_DOMAIN=${NAVIDROME_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${NAVIDROME_DOMAIN}" "http://127.0.0.1:4533" "Navidrome" + else echo -e "${RED}❌ Navidrome 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 8. Miniflux RSS 阅读器 +install_miniflux() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Miniflux 规划的子域名 (例如 rss.zhangcaiduo.com): " MINIFLUX_DOMAIN + if [ -z "$MINIFLUX_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + local DB_PASSWORD="miniflux_secret_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12)" + local ADMIN_PASSWORD_INITIAL="admin123456" + + echo -e "${BLUE}--- “Miniflux RSS 阅读器”部署计划启动! ---${NC}"; + mkdir -p /root/miniflux_data + cat > /root/miniflux_data/docker-compose.yml <> ${STATE_FILE} + echo "MINIFLUX_DOMAIN=${MINIFLUX_DOMAIN}" >> ${STATE_FILE} + echo "MINIFLUX_ADMIN_USER=admin" >> ${STATE_FILE} + echo "MINIFLUX_ADMIN_PASSWORD_INITIAL=${ADMIN_PASSWORD_INITIAL}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; + update_tunnel_config "${MINIFLUX_DOMAIN}" "http://127.0.0.1:8091" "Miniflux" + echo -e "\n${YELLOW}首次登录 https://${MINIFLUX_DOMAIN} 后,请立即修改密码!${NC}" + else + echo -e "${RED}❌ Miniflux 部署失败!${NC}"; + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 9. Gitea +install_gitea() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Gitea 规划的子域名 (例如 git.zhangcaiduo.com): " GITEA_DOMAIN + if [ -z "$GITEA_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + echo -e "${BLUE}--- “Gitea 代码仓库”部署计划启动! ---${NC}"; mkdir -p /root/gitea_data + cat >/root/gitea_data/docker-compose.yml <> ${STATE_FILE}; echo "GITEA_DOMAIN=${GITEA_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${GITEA_DOMAIN}" "http://127.0.0.1:3000" "Gitea" + else echo -e "${RED}❌ Gitea 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 10. Memos (已集成 Mortis 修复 App API) +install_memos() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Memos 规划的子域名 (例如 memos.zhangcaiduo.com): " MEMOS_DOMAIN + if [ -z "$MEMOS_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + echo -e "${BLUE}--- “Memos 轻量笔记”部署计划启动! ---${NC}"; + echo -e "${CYAN}--- 正在为您集成 Mortis (gRPC 翻译器) 以兼容 iOS/Android App ---${NC}" + mkdir -p /root/memos_data + +# --- YAML 语法修正区 --- +# 注意:下面的 cat 命令使用了 <<-EOF,并且所有缩进都是空格,不是 TAB! +cat > /root/memos_data/docker-compose.yml <<-EOF +services: + memos: + image: neosmemo/memos:latest + container_name: memos_app + restart: always + ports: + - "127.0.0.1:5230:5230" + volumes: + - './data:/var/opt/memos' + + mortis: + image: ghcr.io/mudkipme/mortis:latest + container_name: memos_mortis_translator + restart: always + ports: + - "127.0.0.1:5231:5231" + # mortis 会自动连接到名为 'memos' (本 compose 文件中的服务名) 的 5230 端口 + entrypoint: ["/app/mortis"] + command: ["-grpc-addr=memos:5230"] + depends_on: + - memos +EOF + + if (cd /root/memos_data && sudo docker compose up -d); then + echo -e "${GREEN}✅ Memos 和 Mortis 翻译器已启动,等待服务稳定...${NC}"; sleep 5 + echo -e "\n## Memos 凭证 (部署于: $(date))" >> ${STATE_FILE}; echo "MEMOS_DOMAIN=${MEMOS_DOMAIN}" >> ${STATE_FILE} + + echo -e "${GREEN}正在为您配置网络 (仅 Web 界面)...${NC}"; + # 先为 Web 界面配置隧道 + update_tunnel_config "${MEMOS_DOMAIN}" "http://127.0.0.1:5230" "Memos (WebUI)" + + echo -e "\n\n${RED}=======================【!重要!】=======================${NC}" + echo -e "${YELLOW}Memos 网页版已部署成功!但要让手机 App 工作,${NC}" + echo -e "${YELLOW}您必须执行【第 2 步:手动修改 Tunnel 配置】。${NC}" + echo -e "${RED}===========================================================${NC}" + else + echo -e "${RED}❌ Memos 部署失败!${NC}"; + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 11. qBittorrent +install_qbittorrent() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 qBittorrent 规划的子域名 (例如 qb.zhangcaiduo.com): " QB_DOMAIN + if [ -z "$QB_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + echo -e "${BLUE}--- “qBittorrent 下载器”部署计划启动! ---${NC}"; mkdir -p /root/qbittorrent_data /mnt/Downloads + cat > /root/qbittorrent_data/docker-compose.yml <> ${STATE_FILE}; echo "QB_DOMAIN=${QB_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${QB_DOMAIN}" "http://127.0.0.1:8080" "qBittorrent" + else echo -e "${RED}❌ qBittorrent 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 12. JDownloader +install_jdownloader() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 JDownloader 规划的子域名 (例如 jd.zhangcaiduo.com): " JD_DOMAIN + if [ -z "$JD_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + local JDOWNLOADER_PASS="VNC-Pass-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)" + echo -e "${BLUE}--- “JDownloader 下载器”部署计划启动! ---${NC}"; mkdir -p /root/jdownloader_data /mnt/Downloads + cat > /root/jdownloader_data/docker-compose.yml <> ${STATE_FILE}; echo "JD_DOMAIN=${JD_DOMAIN}" >> ${STATE_FILE} + echo "JDOWNLOADER_VNC_PASSWORD=${JDOWNLOADER_PASS}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${JD_DOMAIN}" "http://127.0.0.1:5800" "JDownloader" + else echo -e "${RED}❌ JDownloader 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 13. yt-dlp +install_ytdlp() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 yt-dlp 规划的子域名 (例如 ytdl.zhangcaiduo.com): " YTDL_DOMAIN + if [ -z "$YTDL_DOMAIN" ]; then echo -e "${RED}yt-dlp 域名不能为空,安装取消。${NC}"; sleep 2; return; fi + echo -e "${BLUE}--- “yt-dlp 视频下载器”部署计划启动! ---${NC}"; mkdir -p /root/ytdlp_data /mnt/Downloads + + # --- 【包工头修正 2025-10-26】 --- + # 原 Youtubedl-Material 镜像 (tzahi12345/youtubedl-material) 已失效且无人维护 + # 现已替换为积极维护的 MeTube (alexta69/metube) + cat > /root/ytdlp_data/docker-compose.yml <> ${STATE_FILE}; echo "YTDL_DOMAIN=${YTDL_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${YTDL_DOMAIN}" "http://127.0.0.1:8999" "yt-dlp" + else echo -e "${RED}❌ yt-dlp 部署失败!${NC}"; fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 14. Draw.io 绘图工具 +install_drawio() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Draw.io 规划的子域名 (例如 draw.zhangcaiduo.com): " DRAWIO_DOMAIN + if [ -z "$DRAWIO_DOMAIN" ]; then echo -e "${RED}错误:域名不能为空!安装已中止。${NC}"; return 1; fi + + # 【修复】移除了不再需要的用户名和密码输入 + + echo -e "${GREEN}正在创建 Draw.io 的配置文件...${NC}" + mkdir -p /root/drawio_data + +# --- YAML 语法修正区 --- +# 注意:下面的 cat 命令使用了 <<-EOF,并且所有缩进都是空格,不是 TAB! +cat > /root/drawio_data/docker-compose.yml <<-EOF +services: + drawio: + # 【修复】使用了官方的 jgraph/drawio 镜像替换了已失效的 fuerst/draw.io-basic-auth + image: jgraph/drawio + container_name: drawio_app + restart: unless-stopped + ports: + - "127.0.0.1:8082:8080" + # 【修复】移除了不再需要的 BASIC_AUTH 环境变量 +EOF + + echo -e "${YELLOW}正在启动 Draw.io 服务...${NC}" + if (cd /root/drawio_data && sudo docker compose up -d); then + echo -e "${GREEN}✅ Draw.io 已启动,等待服务稳定...${NC}"; sleep 5 + echo -e "\n## Draw.io 凭证 (部署于: $(date))" >> ${STATE_FILE}; echo "DRAWIO_DOMAIN=${DRAWIO_DOMAIN}" >> ${STATE_FILE} + # 【修复】移除了不再需要的用户名和密码保存 + echo -e "${GREEN}正在为您配置网络...${NC}"; update_tunnel_config "${DRAWIO_DOMAIN}" "http://127.0.0.1:8082" "Draw.io" "access" + + # 【修复】更新了提示信息 + echo -e "\n${YELLOW}=======================【!重要!】=======================${NC}" + echo -e "${YELLOW}Draw.io 已部署成功!${NC}" + echo -e "${YELLOW}此应用已通过 Cloudflare Tunnel 的 ${CYAN}Access${YELLOW} 功能进行保护。${NC}" + echo -e "${YELLOW}您需要登录 Cloudflare Zero Trust 仪表盘, ${NC}" + echo -e "${YELLOW}在 Access -> Applications 中为 ${GREEN}${DRAWIO_DOMAIN}${YELLOW} 添加访问策略 (例如: 允许您的邮箱登录)。${NC}" + echo -e "${RED}===========================================================${NC}" + else + echo -e "${RED}❌ Draw.io 部署失败!请检查 Docker 是否正常运行。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 15. 远程工作台 +install_desktop_env() { + ensure_docker_installed || return; check_tunnel_installed || return + clear + echo -e "${BLUE}--- “远程工作台”建造计划启动! ---${NC}"; + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update + sudo apt-get install -y xfce4 xfce4-goodies xrdp + if [ -f /etc/xrdp/sesman.ini ]; then + sudo sed -i 's/AllowRootLogin=true/AllowRootLogin=false/g' /etc/xrdp/sesman.ini + fi + sudo systemctl enable --now xrdp + echo xfce4-session > ~/.xsession + sudo adduser xrdp ssl-cert + sudo systemctl restart xrdp + + read -p "请输入您想创建的远程桌面用户名 (例如 zhangcaiduo): " NEW_USER + if [ -z "$NEW_USER" ]; then echo -e "${RED}用户名不能为空,操作取消。${NC}"; sleep 2; return; fi + sudo adduser --gecos "" "$NEW_USER" + echo -e "${YELLOW}请为新账户 '$NEW_USER' 设置登录密码...${NC}" + sudo passwd "$NEW_USER" + + echo -e "\n## 远程工作台 (Xfce) 凭证 (部署于: $(date))" >> ${STATE_FILE} + echo "RDP_USER=${NEW_USER}" >> ${STATE_FILE} + + echo -e "\n${GREEN}✅ 远程工作台建造及隧道配置完毕!${NC}" + echo -e "\n${YELLOW}--- 如何连接 ---${NC}" + echo -e "\n${YELLOW}--- 如何连接 ---${NC}" + echo -e " 1. 打开您本地的远程桌面客户端 (Windows/Mac/手机)。" + echo -e " 2. 在计算机/服务器地址栏输入: ${CYAN}\$(curl -s4 https://ifconfig.me/ip)${NC}" + echo -e " 3. (如果提示端口,请输入: ${CYAN}3389${NC})" + echo -e " 4. 使用您刚才创建的用户名 ${GREEN}${NEW_USER}${NC} 和密码登录。" + echo -e " 5. ${RED}重要: 您的 3389 端口已公网暴露,请确保已部署 Fail2ban (选项 18)!${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 16. 为远程桌面安装中文字体 +install_chinese_fonts() { + clear + echo -e "${BLUE}--- 为远程桌面安装中文字体 ---${NC}" + if [ ! -f "/etc/xrdp/xrdp.ini" ]; then + echo -e "${RED}错误:此功能依赖“远程工作台”,请先执行选项 15 进行安装。${NC}"; sleep 4; return 1 + fi + echo -e "${YELLOW}此操作将为您的远程桌面环境安装文泉驿等常用中文字体。${NC}" + read -p "您确定要安装吗? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "\n${CYAN}[1/3] 正在更新软件包列表...${NC}"; sudo apt-get update > /dev/null 2>&1 + echo -e "\n${CYAN}[2/3] 正在安装中文字体包...${NC}"; sudo apt-get install -y fonts-wqy-microhei fonts-wqy-zenhei fonts-arphic-ukai fonts-arphic-uming + echo -e "\n${CYAN}[3/3] 正在刷新系统字体缓存...${NC}"; sudo fc-cache -fv + echo -e "\n${GREEN}================================================" + echo -e "✅ 中文字体安装完成!" + echo -e "================================================${NC}" + echo -e "${YELLOW}为了使字体完全生效,请注销后重新登录您的远程桌面会话。${NC}" + else + echo -e "${GREEN}操作已取消。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + + _install_desktop_browser() { + echo -e "\n${CYAN}--- 步驟 2: 為遠程桌面安裝瀏覽器 (智能兼容最終修正版) ---${NC}" + if [ ! -f "/etc/xrdp/xrdp.ini" ]; then echo -e "${RED}錯誤:遠程工作台未安裝,無法安裝瀏覽器。${NC}"; sleep 3; return; fi + local ARCH; ARCH=$(dpkg --print-architecture) + echo -e "${GREEN}檢測到您當前的 CPU 架構為: ${ARCH}${NC}" + local browser_package="" + case "$ARCH" in + "amd64") + echo -e "${YELLOW}在此主流架構上,我們推薦安裝 Firefox-ESR 或 Chromium。${NC}" + read -p "請選擇要安裝的瀏覽器 (1=Firefox-ESR, 2=Chromium, 其他=取消): " choice + case "$choice" in + 1) browser_package="firefox-esr";; 2) browser_package="chromium";; + *) echo -e "${GREEN}操作已取消。${NC}"; read -p "按 Enter 鍵繼續..."; return;; + esac + ;; + "arm64") echo -e "${YELLOW}檢測到 ARM 架構,將為您安裝兼容的 chromium-browser。${NC}"; browser_package="chromium-browser" ;; + *) echo -e "${RED}錯誤:未知的 CPU 架構 ($ARCH),無法確定要安裝的瀏覽器。${NC}"; read -p "按 Enter 鍵繼續..."; return ;; + esac + echo -e "\n${CYAN}正在安裝 ${browser_package}...${NC}"; sudo apt-get update > /dev/null 2>&1 + if sudo apt-get install -y "$browser_package"; then + echo -e "\n${GREEN}✔ ${browser_package} 瀏覽器已成功安裝!${NC}" + echo -e "${YELLOW}請登錄遠程桌面後,在終端運行 'exo-preferred-applications' 將其設為默認。${NC}" + else + echo -e "\n${RED}❌ 瀏覽器安裝失敗!請檢查上面的錯誤信息。${NC}" + echo -e "${YELLOW}提示:如果安裝失敗,可以嘗試主菜單的 'm) 恢复至标准系统' 功能後重試。${NC}" + fi + read -p "按 Enter 鍵繼續..." + } + +# 17. 安装 AI 知识库 +install_ai_model() { + ensure_docker_installed || return + if [ ! -d "/root/ai_stack" ]; then echo -e "${RED}错误:AI 大脑未安装!${NC}"; sleep 3; return; fi + clear + echo -e "${BLUE}--- 为 AI 大脑安装知识库 (安装大语言模型) ---${NC}" + echo " 1) qwen:1.8b (阿里通义千问), 2) gemma:2b (Google), 3) tinyllama (极限轻量)" + echo " 4) llama3:8b (Meta, 推荐), 5) qwen:4b (更强中文), 6) phi3 (微软)" + echo " 7) qwen:14b (准专业级), 8) llama3:70b (性能怪兽)" + read -p "请输入您的选择: " model_choice + local model_name="" + case $model_choice in + 1) model_name="qwen:1.8b";; 2) model_name="gemma:2b";; 3) model_name="tinyllama";; + 4) model_name="llama3:8b";; 5) model_name="qwen:4b";; 6) model_name="phi3";; + 7) model_name="qwen:14b";; 8) model_name="llama3:70b";; + *) echo -e "${RED}无效选择!${NC}"; sleep 2; return;; + esac + echo -e "\n${YELLOW}即将开始下载模型: ${model_name},这可能需要一些时间,请耐心等待...${NC}" + sudo docker exec -it ollama_app ollama pull ${model_name} + echo -e "\n${GREEN}✅ 知识库 ${model_name} 安装完成!${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 18. 部署 Fail2ban (防暴力破解) +install_fail2ban() { + clear + echo -e "${BLUE}--- “Fail2ban 安防系统”部署计划启动! ---${NC}" + echo -e "${YELLOW}即将安装 Fail2ban 并自动配置 SSH 与 RDP 防护...${NC}" + + sudo apt-get update >/dev/null 2>&1 + sudo apt-get install -y fail2ban + sudo systemctl enable --now fail2ban + + echo -e "${GREEN}✅ Fail2ban 已安装并启动。${NC}" + echo -e "${CYAN}正在配置 jail.local (设置 SSH 和 RDP 规则)...${NC}" + + # --- 创建 jail.local 配置文件 --- + sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF' +[DEFAULT] +bantime = 3600 +findtime = 600 +maxretry = 3 + +[sshd] +enabled = true + +[xrdp] +enabled = true +port = 3389 +logpath = /var/log/xrdp.log +EOF + + echo -e "${CYAN}正在配置 xrdp.conf (设置过滤器)...${NC}" + + # --- 创建 xrdp 过滤器文件 --- + sudo tee /etc/fail2ban/filter.d/xrdp.conf > /dev/null <<'EOF' +[Definition] +failregex = ^.*\[WARN \].*A connection problem has occurred, login is denied.*$ +EOF + + echo -e "${CYAN}正在创建 RDP 日志文件...${NC}" + sudo touch /var/log/xrdp.log + + echo -e "${YELLOW}正在重启 Fail2ban 以应用所有配置...${NC}" + sudo systemctl restart fail2ban + sleep 2 + + echo -e "\n${GREEN}--- 部署完成!正在检查 [xrdp] 规则状态 ---${NC}" + sudo fail2ban-client status xrdp + + echo -e "\n${GREEN}✅ Fail2ban 已成功加载!您的 SSH 和 RDP 已受到保护。${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 19. 部署 Uptime Kuma 监控面板 +install_uptime_kuma() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Uptime Kuma 规划的子域名 (例如 status.zhangcaiduo.com): " KUMA_DOMAIN + if [ -z "$KUMA_DOMAIN" ]; then echo -e "${RED}错误:域名不能为空!${NC}"; sleep 2; return; fi + + echo -e "${BLUE}--- “Uptime Kuma 监控面板”部署计划启动! ---${NC}"; + mkdir -p /root/uptime_kuma_data + + # --- YAML 语法修正区 --- + # 注意:下面的 cat 命令使用了 <<-EOF,并且所有缩进都是空格,不是 TAB! + # 端口 3001 已被 AI (Option 5) 占用,此处使用 3002 + cat > /root/uptime_kuma_data/docker-compose.yml <<-EOF +services: + uptime-kuma: + image: louislam/uptime-kuma:1 + container_name: uptime_kuma_app + restart: always + ports: + - "127.0.0.1:3002:3001" + volumes: + - './data:/app/data' + environment: + - 'TZ=Asia/Shanghai' +EOF + + echo -e "${YELLOW}正在启动 Uptime Kuma 服务...${NC}" + if (cd /root/uptime_kuma_data && sudo docker compose up -d); then + echo -e "${GREEN}✅ Uptime Kuma 已启动,等待服务稳定...${NC}"; sleep 5 + echo -e "\n## Uptime Kuma 凭证 (部署于: $(date))" >> ${STATE_FILE}; + echo "KUMA_DOMAIN=${KUMA_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络...${NC}"; + # 注意:这里 service_url 必须指向本地端口 3002 + update_tunnel_config "${KUMA_DOMAIN}" "http://127.0.0.1:3002" "Uptime Kuma" + else + echo -e "${RED}❌ Uptime Kuma 部署失败!请检查 Docker 是否正常运行。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 19.1 部署 Glances 实时资源监控 +install_glances() { + ensure_docker_installed || return; check_tunnel_installed || return; clear + read -p "请输入您为 Glances 规划的子域名 (例如 glances.zhangcaiduo.com): " GLANCES_DOMAIN + if [ -z "$GLANCES_DOMAIN" ]; then echo -e "${RED}错误:域名不能为空!${NC}"; sleep 2; return; fi + + echo -e "${BLUE}--- “Glances 实时资源监控”部署计划启动! ---${NC}" + echo -e "${YELLOW}正在拉取镜像并启动 Web 模式...${NC}" + + # 使用 Docker 直接运行,开启 -w 网页模式,并绑定本地 61208 端口 + docker run -d \ + --name glances_app \ + --restart always \ + -p 127.0.0.1:61208:61208 \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + -v /:/host:ro \ + --pid host \ + nicolargo/glances:latest-full \ + /usr/local/bin/glances -w + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✅ Glances 已启动!${NC}" + echo -e "\n## Glances 监控凭证 (部署于: $(date))" >> ${STATE_FILE} + echo "GLANCES_DOMAIN=${GLANCES_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在为您配置网络隧道...${NC}" + # 关联 Cloudflare Tunnel + update_tunnel_config "${GLANCES_DOMAIN}" "http://127.0.0.1:61208" "Glances Monitor" + else + echo -e "${RED}❌ Glances 部署失败!${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 20. 添加 SSH 密钥 +add_ssh_public_key() { + clear + echo -e "${BLUE}--- 添加 SSH 公钥 (密钥登录) ---${NC}" + echo -e "${YELLOW}此功能将允许您通过粘贴“公钥”来实现密钥登录。${NC}" + echo -e "${RED}警告:请确保您粘贴的是“公钥” (例如 id_rsa.pub 的内容),而不是“私钥”!${NC}" + + read -p "请粘贴您的 SSH 公钥内容 (通常以 ssh-rsa, ssh-ed25519 等开头): " public_key + + if [ -z "$public_key" ]; then + echo -e "${RED}输入为空,操作已取消。${NC}"; sleep 3; return + fi + + if [[ ! "$public_key" == "ssh-"* ]]; then + echo -e "${RED}错误:公钥格式不正确。它应该以 'ssh-' 开头。${NC}"; sleep 3; return + fi + + read -p "您想为哪个用户配置此密钥? [默认为 root]: " target_user + target_user=${target_user:-root} + + if ! id "$target_user" &>/dev/null; then + echo -e "${RED}错误:用户 ${target_user} 不存在于系统中。${NC}"; sleep 3; return + fi + + local target_home + target_home=$(getent passwd "$target_user" | cut -d: -f6) + if [ -z "$target_home" ]; then + echo -e "${RED}错误:无法找到用户 ${target_user} 的主目录。${NC}"; sleep 3; return + fi + + local ssh_dir="${target_home}/.ssh" + local auth_keys_file="${ssh_dir}/authorized_keys" + + echo -e "${YELLOW}正在为用户 ${target_user} 配置密钥 (路径: ${auth_keys_file})...${NC}" + + # 创建目录和文件 + mkdir -p "$ssh_dir" + + # 检查密钥是否已存在 + if grep -qF "$public_key" "$auth_keys_file" 2>/dev/null; then + echo -e "${YELLOW}此密钥已存在于 ${auth_keys_file} 中,无需重复添加。${NC}" + else + # 追加密钥 + echo "$public_key" >> "$auth_keys_file" + echo -e "${GREEN}✅ 密钥已成功追加。${NC}" + fi + + # 设置严格的权限 + echo -e "${YELLOW}正在设置安全权限...${NC}" + chmod 700 "$ssh_dir" + chmod 600 "$auth_keys_file" + + # 设置正确的所有者 + if [ "$(stat -c '%U' "$ssh_dir")" != "$target_user" ]; then + chown -R "${target_user}:${target_user}" "$ssh_dir" + echo -e "${GREEN}✅ 目录所有者已修正为 ${target_user}。${NC}" + fi + + echo -e "\n${GREEN}================================================================${NC}" + echo -e "✅ SSH 密钥添加成功!" + echo -e "${YELLOW}请尝试在新终端中使用您的“私钥”登录。" + echo -e "${YELLOW}测试成功后,强烈建议您执行主菜单中的“切换 SSH 密码登录”选项。${NC}" + echo -e "${GREEN}================================================================${NC}" + + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 21. 切换 SSH 密码登录 +toggle_ssh_password_login() { + clear + echo -e "${BLUE}--- 切换 SSH 密码登录状态 ---${NC}" + + local config_file="/etc/ssh/sshd_config" + local current_status + current_status=$(grep -E "^\s*#*\s*PasswordAuthentication" "$config_file" | tail -n 1 | awk '{print $2}') + + if [[ "$current_status" == "yes" || -z "$current_status" ]]; then + echo -e "${YELLOW}当前状态:允许密码登录 (PasswordAuthentication is 'yes' or commented out)。${NC}" + read -p "您确定要【禁用】密码登录吗? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "${YELLOW}正在禁用密码登录 (设置 PasswordAuthentication no)...${NC}" + sudo sed -i 's/^[ \t]*#*[ \t]*PasswordAuthentication .*/PasswordAuthentication no/g' "$config_file" + sudo sed -i 's/^[ \t]*#*[ \t]*ChallengeResponseAuthentication .*/ChallengeResponseAuthentication no/g' "$config_file" + echo -e "${YELLOW}正在重启 SSH 服务...${NC}" + sudo systemctl restart sshd + echo -e "\n${RED}================================================================${NC}" + echo -e "✅ 密码登录已禁用!请确保您的密钥可以正常登录!" + echo -e "${RED}如果密钥失效,您可能无法再次登录此服务器!${NC}" + echo -e "${RED}================================================================${NC}" + else + echo -e "${GREEN}操作已取消。${NC}" + fi + else + echo -e "${GREEN}当前状态:密码登录已被禁用 (PasswordAuthentication is 'no')。${NC}" + read -p "您确定要【启用】密码登录吗? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "${YELLOW}正在启用密码登录 (设置 PasswordAuthentication yes)...${NC}" + sudo sed -i 's/^[ \t]*#*[ \t]*PasswordAuthentication .*/PasswordAuthentication yes/g' "$config_file" + echo -e "${YELLOW}正在重启 SSH 服务...${NC}" + sudo systemctl restart sshd + echo -e "\n${GREEN}✅ 密码登录已重新启用。${NC}" + else + echo -e "${GREEN}操作已取消。${NC}" + fi + fi + + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 22. Nextcloud 优化 +run_nextcloud_optimization() { + ensure_docker_installed || return + if [ ! -d "/root/nextcloud_data" ]; then echo -e "${RED}错误:Nextcloud 套件未安装!${NC}"; sleep 3; return; fi + clear + echo -e "${BLUE}--- “Nextcloud 精装修”计划启动! ---${NC}"; + local nc_domain=$(grep 'NEXTCLOUD_DOMAIN' ${STATE_FILE} | cut -d'=' -f2) + if [ -z "$nc_domain" ]; then echo -e "${RED}错误: 无法从凭证文件找到 Nextcloud 域名!${NC}"; sleep 3; return; fi + sudo docker exec --user www-data nextcloud_app php occ config:system:set trusted_proxies 0 --value='172.17.0.0/16' + sudo docker exec --user www-data nextcloud_app php occ config:system:set overwrite.cli.url --value="https://${nc_domain}" + sudo docker exec --user www-data nextcloud_app php occ config:system:set overwriteprotocol --value='https' + sudo docker exec --user www-data nextcloud_app php occ config:system:set memcache.local --value '\\OC\\Memcache\\Redis' + sudo docker exec --user www-data nextcloud_app php occ config:system:set memcache.locking --value '\\OC\\Memcache\\Redis' + sudo docker exec --user www-data nextcloud_app php occ config:system:set redis host --value 'nextcloud_redis' + sudo docker exec --user www-data nextcloud_app php occ db:add-missing-indices + sudo docker exec --user www-data nextcloud_app php occ maintenance:repair --include-expensive + sudo docker exec --user www-data nextcloud_app php occ config:system:set maintenance_window_start --type=integer --value=1 + sudo docker exec --user www-data nextcloud_app php occ config:system:set default_phone_region --value="CN" + echo -e "\n${GREEN}✅ Nextcloud 精装修完成!${NC}" + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} +# 22.1 Nextcloud 高速风景版 (视听封面 + BBR + 大文件分块) +run_nextcloud_super_boost() { + ensure_docker_installed || return + if [ ! -d "/root/nextcloud_data" ]; then + echo -e "${RED}错误:Nextcloud 套件未安装! 请先执行 3.1 部署。${NC}" + sleep 3 + return + fi + clear + echo -e "${BLUE}--- “Nextcloud 高速风景版” 深度优化计划启动! ---${NC}" + + echo -e "\n${CYAN}[1/5] 正在强制 IPv4 优先 (解决甲骨文等机器的 IPv6 绕路/断连问题)...${NC}" + sudo sed -i 's/#precedence ::ffff:0:0\/96 100/precedence ::ffff:0:0\/96 100/' /etc/gai.conf + grep -q "precedence ::ffff:0:0/96 100" /etc/gai.conf || echo "precedence ::ffff:0:0/96 100" | sudo tee -a /etc/gai.conf >/dev/null + + echo -e "\n${CYAN}[2/5] 正在进行内核网络调优 (开启 BBR 加速 + 扩展缓冲区)...${NC}" + sudo cat > /tmp/sysctl_nc_boost.conf << EOB +net.ipv4.ip_forward = 1 +net.core.default_qdisc=fq +net.ipv4.tcp_congestion_control=bbr +net.core.rmem_max = 67108864 +net.core.wmem_max = 67108864 +net.ipv4.tcp_rmem = 4096 87380 67108864 +net.ipv4.tcp_wmem = 4096 65536 67108864 +net.core.netdev_max_backlog = 250000 +net.ipv4.tcp_max_syn_backlog = 30000 +net.ipv4.tcp_max_tw_buckets = 2000000 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.tcp_fin_timeout = 10 +vm.swappiness = 10 +EOB + sudo cat /tmp/sysctl_nc_boost.conf | sudo tee -a /etc/sysctl.conf >/dev/null + sudo sysctl -p >/dev/null 2>&1 + rm -f /tmp/sysctl_nc_boost.conf + + echo -e "\n${CYAN}[3/5] 正在安装多核调度器 (irqbalance)...${NC}" + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update >/dev/null 2>&1 + sudo apt-get install irqbalance -y >/dev/null 2>&1 + sudo systemctl enable --now irqbalance >/dev/null 2>&1 + unset DEBIAN_FRONTEND + + echo -e "\n${CYAN}[4/5] 正在为 Nextcloud 容器内部安装视频解码器 (ffmpeg)...${NC}" + sudo docker exec -u root nextcloud_app apt-get update >/dev/null 2>&1 + sudo docker exec -u root nextcloud_app apt-get install -y ffmpeg >/dev/null 2>&1 + + echo -e "\n${CYAN}[5/5] 正在注入视听封面引擎与大文件切片配置...${NC}" + sudo docker exec --user www-data nextcloud_app php occ config:system:set enable_previews --value=true --type=boolean + sudo docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 0 --value="OC\Preview\Image" + sudo docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 1 --value="OC\Preview\Movie" + sudo docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 2 --value="OC\Preview\MP3" + sudo docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 3 --value="OC\Preview\TXT" + sudo docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 4 --value="OC\Preview\MarkDown" + sudo docker exec --user www-data nextcloud_app php occ config:app:set files max_chunk_size --value 524288000 + + echo -e "\n${GREEN}✅ 基础配置全部注入完成!新上传的视听文件将自动生成封面。${NC}" + + # --- 历史文件缩略图生成模块 --- + echo -e "\n${YELLOW}================================================================${NC}" + echo -e "${YELLOW}检测到您可能需要为以前上传的“老文件”补齐缩略图。${NC}" + echo -e "${YELLOW}此操作需要安装官方的 Preview Generator 插件,并扫描整个网盘。${NC}" + echo -e "${RED}注意:如果您的网盘文件非常多,这一步可能会花费十几分钟甚至更久!${NC}" + echo -e "${YELLOW}================================================================${NC}" + read -p "您想现在立刻为所有历史文件生成封面吗?(y/n): " gen_choice + + if [[ "$gen_choice" == "y" || "$gen_choice" == "Y" ]]; then + echo -e "\n${CYAN}正在下载并安装 Preview Generator 插件...${NC}" + sudo docker exec --user www-data nextcloud_app php occ app:install previewgenerator + echo -e "${CYAN}正在全盘扫描并生成历史缩略图,请耐心等待 (切勿关闭终端窗口)...${NC}" + sudo docker exec --user www-data nextcloud_app php occ preview:generate-all + echo -e "${GREEN}✅ 历史文件缩略图全盘生成完毕!${NC}" + else + echo -e "\n${GREEN}已跳过历史文件生成。您以后可以随时重新运行此选项来补齐封面。${NC}" + fi + + echo -e "\n${GREEN}按任意键返回主菜单...${NC}" + read -n 1 -s +} + +# 22.2 Nextcloud PDF及画册封面引擎补丁 +run_nextcloud_pdf_patch() { + if ! docker ps | grep -q nextcloud_app; then + echo -e "\n${RED}错误:未检测到运行中的 nextcloud_app 容器!请确认 Nextcloud 正常运行。${NC}" + sleep 3 + return + fi + clear + echo -e "${BLUE}--- Nextcloud PDF/书籍封面引擎补丁启动 ---${NC}" + + echo -e "\n${CYAN}[1/3] 正在为容器安装 PDF 渲染核心组件 (Ghostscript)...${NC}" + docker exec -u root nextcloud_app apt-get update >/dev/null 2>&1 + docker exec -u root nextcloud_app apt-get install -y ghostscript >/dev/null 2>&1 + + echo -e "\n${CYAN}[2/3] 正在解除 ImageMagick 的 PDF 安全限制...${NC}" + # 将默认的 rights="none" 修改为 rights="read|write",释放 PDF 渲染权限 + docker exec -u root nextcloud_app sed -i 's/rights="none" pattern="PDF"/rights="read|write" pattern="PDF"/g' /etc/ImageMagick-6/policy.xml + + echo -e "\n${CYAN}[3/3] 正在向 Nextcloud 注入 PDF 预览提供程序...${NC}" + docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 5 --value="OC\Preview\PDF" + # 顺手把矢量图 SVG 的预览也加上 + docker exec --user www-data nextcloud_app php occ config:system:set enabledPreviewProviders 6 --value="OC\Preview\SVG" + + echo -e "\n${GREEN}✅ PDF 封面引擎物理配置完成!新上传的 PDF 将自动生成缩略图。${NC}" + + # --- 历史文件缩略图生成模块 --- + echo -e "\n${YELLOW}================================================================${NC}" + read -p "您想现在立刻为网盘里【已经存在】的历史 PDF 文件生成封面吗?(y/n): " gen_choice + + if [[ "$gen_choice" == "y" || "$gen_choice" == "Y" ]]; then + echo -e "\n${CYAN}正在全盘扫描并生成历史缩略图,请耐心等待 (切勿关闭终端窗口)...${NC}" + docker exec --user www-data nextcloud_app php occ preview:generate-all + echo -e "${GREEN}✅ 历史 PDF 及其他文件缩略图全盘生成完毕!${NC}" + else + echo -e "\n${GREEN}已跳过历史文件生成。${NC}" + fi + + echo -e "\n${GREEN}按任意键返回主菜单...${NC}" + read -n 1 -s +} + +# 23. 服务控制中心 +show_service_control_panel() { + ensure_docker_installed || return + while true; do + clear; echo -e "${BLUE}--- 服务控制中心 ---${NC}" + declare -a services=("Nextcloud 数据中心:/root/nextcloud_data" "OnlyOffice 办公室:/root/onlyoffice_data" "WordPress 博客:/root/wordpress_data" "AI 大脑:/root/ai_stack" "Jellyfin 影院:/root/jellyfin_data" "Navidrome 音乐:/root/navidrome_data" "Miniflux RSS 阅读器:/root/miniflux_data" "Gitea 仓库:/root/gitea_data" "Memos 笔记:/root/memos_data" "qBittorrent:/root/qbittorrent_data" "JDownloader:/root/jdownloader_data" "yt-dlp 下载:/root/ytdlp_data" "Uptime Kuma 监控:/root/uptime_kuma_data" "Home Assistant:/root/home_assistant_data") + + local i=1; declare -a active_services=() + + for service_entry in "${services[@]}"; do + local name=$(echo $service_entry | cut -d':' -f1); local path=$(echo $service_entry | cut -d':' -f2) + if [ -f "${path}/docker-compose.yml" ]; then + local status + if sudo docker compose -f ${path}/docker-compose.yml ps -q 2>/dev/null | grep -q .; then status="${GREEN}[ 运行中 ]${NC}"; else status="${RED}[ 已停止 ]${NC}"; fi + printf " %2d) %-25s %s\n" "$i" "$name" "$status"; active_services+=("$name:$path"); i=$((i+1)) + fi + done + echo "------------------------------------"; echo " b) 返回主菜单" + read -p "请输入数字选择服务 , 或 'b' 返回 : " service_choice + if [[ "$service_choice" == "b" || "$service_choice" == "B" ]]; then break; fi + local index=$((service_choice-1)); if ! [[ $index -ge 0 && $index -lt ${#active_services[@]} ]]; then echo -e "${RED}无效选择!${NC}"; sleep 2; continue; fi + + local selected_service=${active_services[$index]}; local s_name=$(echo $selected_service | cut -d':' -f1); local s_path=$(echo $selected_service | cut -d':' -f2); local compose_file="${s_path}/docker-compose.yml" + + local is_linkable=false; local container_paths=(); local path_labels=(); local default_local_paths=() + case "$s_name" in "Jellyfin 影院") is_linkable=true; container_paths=("/media/music" "/media/movies" "/media/tvshows"); path_labels=("音乐库" "电影库" "电视剧库"); default_local_paths=("/mnt/Music" "/mnt/Movies" "/mnt/TVShows");; "Navidrome 音乐") is_linkable=true; container_paths=("/music"); path_labels=("音乐库"); default_local_paths=("/mnt/Music");; "qBittorrent") is_linkable=true; container_paths=("/downloads"); path_labels=("下载目录"); default_local_paths=("/mnt/Downloads");; "JDownloader") is_linkable=true; container_paths=("/output"); path_labels=("下载目录"); default_local_paths=("/mnt/Downloads");; "yt-dlp 下载") is_linkable=true; container_paths=("/downloads"); path_labels=("下载目录"); default_local_paths=("/mnt/Downloads");; esac + clear; echo "正在操作服务: ${CYAN}${s_name}${NC}" + + # --- 【张先生 修正】 --- + # 为 Nextcloud 和其他“可关联”应用定义不同的菜单 + local menu_options + if [[ "$s_name" == "Nextcloud 数据中心" ]]; then + # 【二次修正】添加 Cron 定时任务选项 + menu_options=("启动" "停止" "重启" "查看日志" "添加Rclone跃遷盘(外部存储)" "配置Cron定时任务(文件盘点)" "返回") + elif $is_linkable; then + # 将"关联Rclone网盘"重命名为"关联Rclone媒体库"以示区别 + menu_options=("启动" "停止" "重启" "查看日志" "关联Rclone媒体库" "查看数据路径" "返回") + else + menu_options=("启动" "停止" "重启" "查看日志" "返回") + fi + + select opt in "${menu_options[@]}"; do + case $opt in + "启动") (cd $s_path && sudo docker compose up -d); break;; + "停止") (cd $s_path && sudo docker compose stop); break;; + "重启") (cd $s_path && sudo docker compose restart); break;; + "查看日志") sudo docker compose -f ${compose_file} logs -f --tail 50; break;; + + # --- 【张先生 修正】 新增 Nextcloud 专属选项 --- + "添加Rclone跃遷盘(外部存储)") + if ! grep -q "RCLONE_MOUNT_PATH" "${STATE_FILE}"; then echo -e "${RED}错误:Rclone未配置${NC}"; sleep 3; break; fi + local rclone_mount_path=$(grep "RCLONE_MOUNT_PATH" "${STATE_FILE}" | cut -d'=' -f2) + if ! mount | grep -q "${rclone_mount_path}"; then echo -e "${RED}错误:Rclone挂载点未生效${NC}"; sleep 3; break; fi + + # 检查是否已添加 + if grep -q "${rclone_mount_path}:${rclone_mount_path}" "$compose_file"; then + echo -e "${YELLOW}检测到跃遷盘已添加,无需重复操作。${NC}"; sleep 3; break + fi + + echo -e "${GREEN}正在为 Nextcloud 添加 ${rclone_mount_path} 映射...${NC}" + # 找到最后一行 volume 映射 (php-opcache.ini) + local last_volume_line_num=$(grep -n 'php-opcache.ini' "$compose_file" | cut -d: -f1) + + if [ -n "$last_volume_line_num" ]; then + # 在该行后追加新映射 + local indentation=$(grep 'php-opcache.ini' "$compose_file" | awk '{gsub(/[^ ].*/, ""); print}') + local new_line="${indentation}- '${rclone_mount_path}:${rclone_mount_path}' # 由控制中心添加" + sudo sed -i "${last_volume_line_num}a ${new_line}" "$compose_file" + + echo -e "${GREEN}✅ 映射添加成功!正在重启 Nextcloud...${NC}" + (cd $s_path && sudo docker compose up -d --force-recreate) # + echo -e "${GREEN}✅ Nextcloud 已重启!${NC}" + echo -e "${YELLOW}请登录 Nextcloud, 进入“设置”->“外部存储”添加 ${rclone_mount_path} 路径。${NC}" + else + echo -e "${RED}错误:无法找到 'php-opcache.ini' 锚点,自动添加失败。${NC}" + echo -e "${RED}请检查 /root/nextcloud_data/docker-compose.yml 文件是否完整。${NC}" + fi + sleep 5 + break + ;; + + # --- 【张先生 修正】 新增 Cron 定时任务 --- + "配置Cron定时任务(文件盘点)") + echo -e "${CYAN}正在为 Nextcloud 配置两条核心定时任务...${NC}" + + # 定义两条命令 + local cron_job_scan="*/15 * * * * /usr/bin/docker exec --user www-data nextcloud_app php occ files:scan --all > /dev/null 2>&1" + local cron_job_php="*/5 * * * * /usr/bin/docker exec --user www-data nextcloud_app php -f /var/www/html/cron.php > /dev/null 2>&1" + + # 获取当前 crontab 内容 + local current_cron=$(crontab -l 2>/dev/null) + + local added_scan=false + local added_php=false + + # 检查并添加文件扫描任务 + if ! echo "$current_cron" | grep -q "occ files:scan --all"; then + (echo "$current_cron"; echo "$cron_job_scan") | crontab - + added_scan=true + fi + + # 再次获取 crontab (因为上一步可能修改了它) + current_cron=$(crontab -l 2>/dev/null) + + # 检查并添加 cron.php 任务 + if ! echo "$current_cron" | grep -q "/var/www/html/cron.php"; then + (echo "$current_cron"; echo "$cron_job_php") | crontab - + added_php=true + fi + + # 提供反馈 + if $added_scan && $added_php; then + echo -e "${GREEN}✅ 成功添加了“文件盘点”和“后台任务”两条定时任务!${NC}" + elif $added_scan; then + echo -e "${GREEN}✅ 成功添加了“文件盘点”任务。${NC}" + echo -e "${YELLOW}ℹ️ “后台任务” (cron.php) 似乎已存在,跳过添加。${NC}" + elif $added_php; then + echo -e "${GREEN}✅ 成功添加了“后台任务”。${NC}" + echo -e "${YELLOW}ℹ️ “文件盘点” (files:scan) 似乎已存在,跳过添加。${NC}" + else + echo -e "${YELLOW}ℹ️ 两条 Nextcloud 定时任务似乎都已存在,无需操作。${NC}" + fi + + echo -e "\n${CYAN}当前已为 Nextcloud 配置的任务列表:${NC}" + crontab -l | grep 'nextcloud_app' + sleep 5 + break + ;; + + # --- 【张先生 修正】 修改旧的"关联Rclone网盘"逻辑,使其更健壮 --- + "关联Rclone媒体库") # 已重命名 + if ! grep -q "RCLONE_MOUNT_PATH" "${STATE_FILE}"; then echo -e "${RED}错误:Rclone未配置${NC}"; sleep 3; break; fi + local rclone_mount_path=$(grep "RCLONE_MOUNT_PATH" "${STATE_FILE}" | cut -d'=' -f2); if ! mount | grep -q "${rclone_mount_path}"; then echo -e "${RED}错误:Rclone挂载点未生效${NC}"; sleep 3; break; fi + + for i in ${!container_paths[@]}; do + local c_path=${container_paths[i]}; local label=${path_labels[i]}; local default_local_path=${default_local_paths[i]} + + # 使用 awk 替换 grep -E,更精确地查找包含 ${c_path} 的行 + local line_to_replace=$(awk -v path=":${c_path}" '$0 ~ path {print}' "$compose_file" | head -n 1) + + if [ -z "$line_to_replace" ]; then echo -e "${RED}未找到 ${c_path} 的原始映射,跳过...${NC}"; continue; fi + + echo -e "${YELLOW}(留空则恢复默认VPS的 ${default_local_path} 文件夹)${NC}" + read -p "请输入用于[${label}]的网盘文件夹名 (例如 Music): " rclone_subfolder + local new_host_path; if [ -z "$rclone_subfolder" ]; then new_host_path=$default_local_path; else new_host_path="${rclone_mount_path}/${rclone_subfolder}"; fi + sudo mkdir -p "${new_host_path}" + + local indentation=$(echo "$line_to_replace" | awk '{gsub(/[^ ].*/, ""); print}') + local new_line="${indentation}- '${new_host_path}:${c_path}'" + + # 使用 awk 替换 sed,避免 $line_to_replace 中包含特殊字符导致替换失败 + sudo awk -v old="$line_to_replace" -v new="$new_line" '{if ($0 == old) {print new} else {print $0}}' "$compose_file" > "${compose_file}.tmp" && sudo mv "${compose_file}.tmp" "$compose_file" + done + + (cd $s_path && sudo docker compose up -d --force-recreate); echo -e "${GREEN}✅ 服务已重启并关联Rclone!${NC}" + break;; + "查看数据路径") + for i in ${!container_paths[@]}; do + local c_path=${container_paths[i]}; local label=${path_labels[i]} + # 同样使用 awk 优化查找 + local line=$(awk -v path=":${c_path}" '$0 ~ path {print}' "$compose_file" | head -n 1) + if [ -n "$line" ]; then local host_path=$(echo "$line"|awk -F: '{print $1}'|sed -e 's/^[ \t-]*//' -e "s/['\"]//g"); echo "- ${label}: ${GREEN}${host_path}${NC}"; fi + done; read -n 1 -s -r -p "按任意键返回..."; break;; + "返回") break 2;; + *) echo "无效选项";; + esac + done; sleep 1 + done +} + +# 24. 查看密码与数据路径 +show_credentials() { + if [ ! -f "${STATE_FILE}" ]; then echo -e "\n${YELLOW}尚未开始装修,没有凭证信息。${NC}"; sleep 2; return; fi + clear + echo -e "${RED}==================== 🔑 【重要凭证保险箱】 🔑 ====================${NC}" + + # --- 特殊处理 Nextcloud --- + if grep -q "## Nextcloud 套件凭证" "${STATE_FILE}"; then + local nc_db_pass=$(grep 'DB_PASSWORD' ${STATE_FILE} | head -n 1 | cut -d'=' -f2) + local nc_domain=$(grep 'NEXTCLOUD_DOMAIN' ${STATE_FILE} | cut -d'=' -f2) + local oo_domain=$(grep 'ONLYOFFICE_DOMAIN' ${STATE_FILE} | cut -d'=' -f2) + local oo_jwt=$(grep 'ONLYOFFICE_JWT_SECRET' ${STATE_FILE} | cut -d'=' -f2) + + echo -e "\n${CYAN}--- Nextcloud 首次安装配置信息 ---${NC}" + echo -e " 数据库用户: ${GREEN}nextclouduser${NC}" + echo -e " 数据库名称: ${GREEN}nextclouddb${NC}" + echo -e " 数据库主机: ${GREEN}db${NC}" + echo -e " 数据库密码: ${GREEN}${nc_db_pass}${NC}" + echo "" + echo -e "${CYAN}--- Nextcloud & OnlyOffice 访问与集成信息 ---${NC}" + echo -e " Nextcloud 访问域名: ${GREEN}https://${nc_domain}${NC}" + echo -e " OnlyOffice 访问域名: ${GREEN}https://${oo_domain}${NC}" + echo -e " OnlyOffice JWT Secret: ${GREEN}${oo_jwt}${NC}" + fi + + # --- VLESS 节点信息 --- + if grep -q "VLESS_DOMAIN" "${STATE_FILE}"; then + local vless_domain=$(grep 'VLESS_DOMAIN' ${STATE_FILE} | head -n 1 | cut -d'=' -f2) + local vless_uuid=$(grep 'VLESS_UUID' ${STATE_FILE} | head -n 1 | cut -d'=' -f2) + + if [ -n "$vless_domain" ] && [ -n "$vless_uuid" ]; then + local vless_link="vless://${vless_uuid}@${vless_domain}:443?encryption=none&security=tls&type=ws&host=${vless_domain}&path=%2F#VPS-Tunnel-VLESS" + echo -e "\n${CYAN}--- “科学上网” VLESS 节点信息 ---${NC}" + echo -e " 节点域名: ${GREEN}${vless_domain}${NC}" + echo -e " 节点 UUID: ${GREEN}${vless_uuid}${NC}" + echo -e " VLESS 链接: ${CYAN}${vless_link}${NC}" + fi + fi + + # --- 循环显示其他凭证 --- + local current_section="" + while IFS= read -r line; do + if [[ "$line" == "## Nextcloud 套件凭证"* || "$line" == "## “科学上网” VLESS 节点"* ]]; then + continue + elif [[ "$line" == "## "* ]]; then + current_section=$(echo "$line" | sed -e 's/## //' -e 's/ (.*//') + echo -e "\n${CYAN}--- ${current_section} ---${NC}" + elif [[ -n "$line" && ! "$line" =~ ^# ]]; then + local key=$(echo "$line" | cut -d'=' -f1) + local value=$(echo "$line" | cut -d'=' -f2-) + if [[ "$key" == *"DOMAIN"* ]]; then + echo " 访问域名: ${GREEN}https://${value}${NC}" + elif [[ "$key" == "CLOUDFLARE_TOKEN" || "$key" == "VLESS_UUID" ]]; then + continue + else + echo " ${key}: ${value}" + fi + fi + done < "${STATE_FILE}" + + # --- qBittorrent 初始密码 --- + if [ -d "/root/qbittorrent_data" ]; then + echo -e "\n${CYAN}--- qBittorrent 初始密码 ---${NC}" + echo " qBittorrent 的初始密码在首次启动的日志中。请退出本面板后执行:" + echo -e " ${YELLOW}sudo docker logs qbittorrent_app${NC}" + echo " (在日志中找到 The Web UI administrator password is: xxxxxxxx 这一行)" + fi + + # --- 应用数据目录 --- + echo -e "\n${CYAN}--- 应用数据目录 ---${NC}" + [ -d "/mnt/Music" ] && echo " 🎵 音乐库 (Navidrome/Jellyfin): /mnt/Music" + [ -d "/mnt/Movies" ] && echo " 🎬 电影库 (Jellyfin): /mnt/Movies" + [ -d "/mnt/TVShows" ] && echo " 📺 电视剧库 (Jellyfin): /mnt/TVShows" + [ -d "/mnt/Downloads" ] && echo " 🔽 默认下载目录: /mnt/Downloads" + if grep -q "RCLONE_MOUNT_PATH" "${STATE_FILE}"; then + echo " ☁️ Rclone 网盘挂载点: $(grep 'RCLONE_MOUNT_PATH' ${STATE_FILE} | cut -d'=' -f2)" + fi + + echo -e "\n${RED}========================================================================${NC}" + read -n 1 -s -r -p "按任意键返回主菜单..." +} + +# --- VPS信息查看函数 --- +show_vps_info() { + clear + echo -e "${BLUE}--- VPS 服务器信息 ---${NC}" + local ipv4=$(curl -s4 https://ifconfig.me/ip) + local os_info=$(source /etc/os-release && echo "$PRETTY_NAME") + local cpu_info=$(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2 | sed 's/^[ \t]*//') + echo -e "${CYAN}公网 IP 地址:${NC} ${GREEN}${ipv4}${NC}" + echo -e "${CYAN}操作系统:${NC} ${GREEN}${os_info}${NC}" + echo -e "${CYAN}CPU 型号:${NC} ${GREEN}${cpu_info}${NC}" + echo -e "${CYAN}内存使用情况:${NC}" + free -h | sed 's/^/ /' + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 26. 科学上网工具箱 +install_vless_node() { + ensure_docker_installed || return; check_tunnel_installed || return + clear; echo -e "${BLUE}--- “科学上网”工具箱 ---${NC}" + read -p "请输入您为VLESS节点准备的域名 (例如 v.zhangcaiduo.com): " VLESS_DOMAIN + if [ -z "$VLESS_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; sleep 2; return; fi + + local UUID; UUID=$(cat /proc/sys/kernel/random/uuid) + + mkdir -p /root/vless_data; cat > /root/vless_data/docker-compose.yml < /root/vless_data/config.json <> ${STATE_FILE} + echo "VLESS_DOMAIN=${VLESS_DOMAIN}" >> ${STATE_FILE} + echo "VLESS_UUID=${UUID}" >> ${STATE_FILE} + echo -e "\n${GREEN}✅ VLESS 节点部署成功!${NC}" + echo -e "${YELLOW}请使用以下链接导入客户端:${NC}" + echo -e "${CYAN}${VLESS_LINK}${NC}" + else + echo -e "${RED}❌ VLESS 节点部署失败!请检查 Docker 是否正常运行。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 27. 部署 Socks5 代理 +install_socks5_proxy() { + ensure_docker_installed || return; check_tunnel_installed || return + clear + echo -e "${BLUE}--- “Socks5 代理”部署計劃啟動! ---${NC}" + read -p "請輸入您為 Socks5 代理規劃的子域名 (例如 proxy.zhangcaiduo.com): " SOCKS5_DOMAIN + if [ -z "$SOCKS5_DOMAIN" ]; then echo -e "${RED}錯誤:域名不能為空!${NC}"; sleep 2; return; fi + + mkdir -p /root/socks5_proxy_data + cat > /root/socks5_proxy_data/docker-compose.yml <> ${STATE_FILE} + echo "SOCKS5_DOMAIN=${SOCKS5_DOMAIN}" >> ${STATE_FILE} + echo -e "${GREEN}正在為您配置網絡...${NC}" + update_tunnel_config "${SOCKS5_DOMAIN}" "socks://127.0.0.1:1080" "Socks5 Proxy" + + echo -e "\n${GREEN}✅ Socks5 代理部署成功!${NC}" + echo -e "${YELLOW}請在您的客戶端中使用以下資訊配置代理:${NC}" + echo -e " 代理類型: ${CYAN}SOCKS5${NC}" + echo -e " 伺服器地址: ${CYAN}${SOCKS5_DOMAIN}${NC}" + echo -e " 端口: ${CYAN}443${NC}" + echo -e " ${YELLOW}注意:需要客戶端支援 Socks5 over TLS/SSL (例如 v2rayN, Clash 等)。${NC}" + else + echo -e "${RED}❌ Socks5 代理部署失敗!請檢查 Docker 是否正常運行。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主選單...${NC}"; read -n 1 -s +} +# 28. Hysteria 2 暴力加速 (集成版 - IPv6 优先/WARP 修正版) +install_hysteria_node() { + clear + echo -e "${BLUE}--- “Hysteria 2 (歇斯底里)” 暴力加速部署启动! ---${NC}" + echo -e "${YELLOW}注意:此协议需要占用一个公网端口 (UDP),且不走 Cloudflare Tunnel。${NC}" + echo -e "${YELLOW}请确保您的 VPS 防火墙 (安全组) 已放行您设置的端口!${NC}" + + # 检查是否已安装 + if command -v hysteria &> /dev/null; then + echo -e "${RED}检测到 Hysteria 2 已安装!如需重装请先卸载。${NC}"; sleep 3; return + fi + + # 1. 安装基础环境 + echo -e "${CYAN}[1/4] 正在安装 Hysteria 2 核心组件...${NC}" + sudo apt-get update >/dev/null 2>&1 + sudo apt-get install -y curl wget openssl >/dev/null 2>&1 + bash <(curl -fsSL https://get.hy2.sh/) + + # 2. 配置交互 + echo -e "${CYAN}[2/4] 进行参数配置...${NC}" + read -p "请设置连接密码 (留空自动生成): " USER_PASS + [[ -z "$USER_PASS" ]] && USER_PASS=$(openssl rand -base64 16) + + read -p "请设置端口号 (默认 54321): " USER_PORT + [[ -z "$USER_PORT" ]] && USER_PORT=54321 + + # 为防止与 Cloudflare Tunnel 的域名冲突,强制使用自签证书模式 + echo -e "${YELLOW}为避免与您的 Tunnel 域名冲突,脚本自动采用“自签证书”模式。${NC}" + mkdir -p /etc/hysteria + local CERT_FILE="/etc/hysteria/server.crt" + local KEY_FILE="/etc/hysteria/server.key" + openssl req -x509 -nodes -newkey rsa:2048 -keyout $KEY_FILE -out $CERT_FILE -subj "/CN=bing.com" -days 3650 >/dev/null 2>&1 + chmod 644 $CERT_FILE + chmod 600 $KEY_FILE + + # 3. 写入配置文件 + cat < /etc/hysteria/config.yaml +listen: :$USER_PORT +tls: + cert: $CERT_FILE + key: $KEY_FILE +auth: + type: password + password: $USER_PASS +quic: + initStreamReceiveWindow: 8388608 + maxStreamReceiveWindow: 8388608 + initConnReceiveWindow: 20971520 + maxConnReceiveWindow: 20971520 +EOF + chown -R hysteria:hysteria /etc/hysteria + + # 4. 开启 BBR (继承自原脚本) + echo -e "${CYAN}[3/4] 优化系统网络参数 (BBR)...${NC}" + if ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then + echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf + echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf + sysctl -p >/dev/null 2>&1 + fi + + # 启动服务 + systemctl enable --now hysteria-server.service + + # --- 智能 IP 识别 (修正 WARP 干扰) --- + echo -e "${CYAN}[4/4] 正在智能检测真实公网 IP...${NC}" + + # 尝试获取 IPv6 (设置5秒超时) + local IP_V6=$(curl -s6 -m 5 https://ifconfig.me/ip) + # 尝试获取 IPv4 (设置5秒超时) + local IP_V4=$(curl -s4 -m 5 https://ifconfig.me/ip) + + local FINAL_IP="" + local SHARE_LINK="" + + # 逻辑:只要有 IPv6,就强制优先使用 IPv6 + # 原因:对于这就好像 EUserv/Hax 这种机器,IPv4 往往是 WARP 的假 IP,无法入站连接 + if [[ -n "$IP_V6" ]]; then + echo -e "${GREEN}✔ 检测到 IPv6 地址,将优先使用以避开 WARP 干扰。${NC}" + FINAL_IP="$IP_V6" + # IPv6 在链接中必须加上 [] + SHARE_LINK="hysteria2://$USER_PASS@[$FINAL_IP]:$USER_PORT/?sni=bing.com&insecure=1#Hysteria2-IPv6" + # 为了显示好看,把保存到凭证里的 IP 也加上括号 + DISPLAY_IP="[$FINAL_IP]" + elif [[ -n "$IP_V4" ]]; then + echo -e "${YELLOW}未检测到 IPv6,将使用 IPv4 (请注意:如果是 WARP IP 将无法连接)。${NC}" + FINAL_IP="$IP_V4" + SHARE_LINK="hysteria2://$USER_PASS@$FINAL_IP:$USER_PORT/?sni=bing.com&insecure=1#Hysteria2-IPv4" + DISPLAY_IP="$FINAL_IP" + else + echo -e "${RED}错误:无法检测到任何公网 IP!${NC}" + FINAL_IP="<未知IP>" + DISPLAY_IP="<未知IP>" + fi + + # 保存凭证 + echo -e "\n## Hysteria 2 凭证 (部署于: $(date))" >> ${STATE_FILE} + echo "HY2_IP=${DISPLAY_IP}" >> ${STATE_FILE} + echo "HY2_PORT=${USER_PORT}" >> ${STATE_FILE} + echo "HY2_PASSWORD=${USER_PASS}" >> ${STATE_FILE} + echo "HY2_LINK=${SHARE_LINK}" >> ${STATE_FILE} + + echo -e "\n${GREEN}✅ Hysteria 2 部署成功!${NC}" + echo -e "${YELLOW}分享链接已保存到凭证箱 (选项 24)。${NC}" + + if [[ -n "$IP_V6" ]]; then + echo -e "${CYAN}提示:已为您自动适配 IPv6 格式 [$FINAL_IP]${NC}" + echo -e "${CYAN}小白请注意:您的本地网络(手机/电脑)必须支持 IPv6 才能连接此节点!${NC}" + fi + + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; + read -n 1 -s +} + +# 30. 收尾工作与优化脚本 +run_wrap_up_tasks() { + _patch_vps_installer() { + local installer_path="/root/vps_installer.sh" + echo -e "\n${CYAN}--- 步骤 1: 修正 vps_installer.sh 的凭证覆盖 Bug ---${NC}" + if [ -f "$installer_path" ]; then + if grep -q '> ${STATE_FILE}' "$installer_path"; then + sed -i "s/> \${STATE_FILE}/>> \${STATE_FILE}/g" "$installer_path" + sed -i "s/echo \"## Nextcloud 套件憑證.*>>/>> \${STATE_FILE}/echo \"## Nextcloud 套件憑證 ( 部署于 : \$(date))\" > \${STATE_FILE}/" "$installer_path" + echo -e "${GREEN}✔ 已自動修補 $installer_path,解決了憑證文件被覆蓋的問題。${NC}" + else + echo -e "${YELLOW}ℹ️ 檢測到 vps_installer.sh 似乎已經被修正過,跳過此步驟。${NC}" + fi + else + echo -e "${YELLOW}⚠️ 未在 $installer_path 找到安裝腳本,跳過修補步驟。${NC}" + fi + read -p "按 Enter 鍵繼續..." + } + + _create_ai_script() { + local script_path="/usr/local/bin/ask_ai_a_question.sh"; local cron_job="0 * * * * /bin/bash -c '/bin/sleep \$((RANDOM % 3600)) && $script_path'" + echo -e "\n${CYAN}--- 步驟 3: 創建並部署“AI 智能提問官”腳本 (日誌增強版) ---${NC}" + echo -e "${YELLOW}這將創建一個腳本並設置定時任務,每小時隨機向您的 AI 提問,並將對話記錄到日誌。${NC}" + read -p "您確定要創建嗎? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + cat <<'EOF' > "$script_path" +#!/bin/bash +QUESTIONS=( "请写一首关于悉尼歌剧院的诗。" "用一个简单的比喻解释一下什么是“量子计算”。" "给我讲一个关于程序员的简短冷笑话。" "如果AI统治了世界,人类最好的结局会是什么?" "推荐一道适合在办公室当午餐的、简单的食谱。" "澳大利亚历史上有什么不出名但却非常有趣的事件吗?" ) +MODEL_NAME="llama3:8b"; LOG_FILE="/var/log/ask_ai.log" +RANDOM_QUESTION=${QUESTIONS[$((RANDOM % ${#QUESTIONS[@]}))]} +echo "==================================================" >> "$LOG_FILE" +echo "時間: $(date)" >> "$LOG_FILE"; echo "提問: $RANDOM_QUESTION" >> "$LOG_FILE"; echo "回答:" >> "$LOG_FILE" +echo "$RANDOM_QUESTION" | /usr/bin/docker exec -i ollama_app ollama run "$MODEL_NAME" >> "$LOG_FILE" 2>&1 +echo "" >> "$LOG_FILE" +EOF + chmod +x "$script_path"; echo -e "${GREEN}✔ 已成功創建腳本: $script_path${NC}" + (crontab -l 2>/dev/null | grep -v "$script_path"; echo "$cron_job") | crontab - + echo -e "${GREEN}✔ 已成功設置定時任務 (crontab)。${NC}"; echo -e "${YELLOW}您可以通過 'sudo $script_path' 來手動測試它。${NC}"; echo -e "${YELLOW}測試後,使用 'cat /var/log/ask_ai.log' 來查看 AI 的回答。${NC}" + else echo -e "${YELLOW}已跳過創建 AI 腳本。${NC}"; fi + read -p "按 Enter 鍵繼續..." + } + _create_backup_script() { + local script_path="/usr/local/bin/vps_backup.sh" + echo -e "\n${CYAN}--- 步骤 4: 创建终极安全的“冷备份”脚本 ---${NC}" + echo -e "${YELLOW}这会创建一个带“安全停机机制”的备份脚本,彻底杜绝数据库损坏。${NC}" + read -p "您确定要创建吗? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + local default_backup_path="/mnt/onedrive/VPS-Backups" + echo -e "${CYAN}请输入您希望在 Rclone 网盘中存放备份的目标文件夹路径。${NC}" + read -p "留空将使用默认值 [${default_backup_path}]: " custom_backup_path + local BACKUP_DEST_DIR="${custom_backup_path:-$default_backup_path}" + echo -e "${GREEN}备份目标文件夹已成功设置为: ${BACKUP_DEST_DIR}${NC}"; sleep 2 + + cat < "$script_path" +#!/bin/bash +BACKUP_DEST_DIR="${BACKUP_DEST_DIR}" +LOG_FILE="/var/log/vps_backup.log"; TIMESTAMP=\$(date +"%Y-%m-%d_%H-%M-%S") +BACKUP_FILENAME="vps_data_backup_\${TIMESTAMP}.tar.gz"; +LOCAL_TEMP_PATH="/tmp/\${BACKUP_FILENAME}" + +BACKUP_PATHS="/etc /home /usr/local/bin /root/.vps_setup_credentials /root/.cloudflared /root/.config/rclone" +DATA_DIRS=\$(find /root -maxdepth 1 -type d -name "*_data" 2>/dev/null) +COMPOSE_FILES=\$(find /root -maxdepth 2 -name "docker-compose.yml" 2>/dev/null) + +log_message() { echo "\$(date +"%Y-%m-%d %H:%M:%S") - \$1" | sudo tee -a \$LOG_FILE; } + +log_message "================== 应用数据冷备份任务开始 ==================" +mkdir -p "\$BACKUP_DEST_DIR" +log_message "正在清理旧备份..."; +find "\$BACKUP_DEST_DIR" -name "vps_data_backup_*.tar.gz" -type f -delete + +# [核心优化] 备份前安全暂停所有 Docker 容器,防止数据写入产生脏数据 +log_message "【安全机制】正在暂停所有容器运行..." +docker stop \$(docker ps -q) > /dev/null 2>&1 + +log_message "正在打包核心配置与应用数据..." +tar -czf "\$LOCAL_TEMP_PATH" \ + --exclude="*/.cache/*" \ + --exclude="*/.cache" \ + --ignore-failed-read \ + \$BACKUP_PATHS \$COMPOSE_FILES \$DATA_DIRS + +# [核心优化] 打包完成后,立即唤醒所有容器 +log_message "【安全机制】打包完成,正在唤醒所有容器恢复服务..." +docker start \$(docker ps -a -q) > /dev/null 2>&1 + +if [ \$? -eq 0 ]; then + log_message "正在上传到云盘..." + mv "\$LOCAL_TEMP_PATH" "\$BACKUP_DEST_DIR/"; + if [ \$? -eq 0 ]; then + log_message "✅ 上传成功!文件位于: \${BACKUP_DEST_DIR}/\${BACKUP_FILENAME}"; + else + log_message "❌ 错误:上传失败!请检查挂载点。"; rm -f "\$LOCAL_TEMP_PATH"; exit 1; + fi +else + log_message "❌ 错误:打包失败!"; rm -f "\$LOCAL_TEMP_PATH"; exit 1; +fi +log_message "================== 备份任务结束 ==================" +EOF + chmod +x "$script_path"; + echo -e "${GREEN}✔ 已成功创建安全的冷备份脚本: $script_path${NC}" + + echo -e "${CYAN}--- 接下来, 设置自动备份定时任务 ---${NC}" + read -p "请选择备份频率 (1=每小时, 2=每天) [默认: 2]: " freq_choice + read -p "是否随机化执行时间 (y/n) [默认: y]: " rand_choice + + [[ -z "$freq_choice" ]] && freq_choice="2" + [[ -z "$rand_choice" ]] && rand_choice="y" + + local cron_job="" + local message="" + local full_command="/bin/bash $script_path" + + if [[ "$freq_choice" == "1" ]]; then + if [[ "$rand_choice" == "y" || "$rand_choice" == "Y" ]]; then + cron_job="0 * * * * /bin/bash -c '/bin/sleep \$((RANDOM \\% 3540)) && $full_command'" + message="✔ 已设置定时备份 (每小时随机一次)。" + else + cron_job="5 * * * * $full_command" + message="✔ 已设置定时备份 (每小时的第5分钟)。" + fi + else + if [[ "$rand_choice" == "y" || "$rand_choice" == "Y" ]]; then + cron_job="0 0 * * * /bin/bash -c '/bin/sleep \$((RANDOM \\% 86000)) && $full_command'" + message="✔ 已设置定时备份 (每天随机时间)。" + else + cron_job="5 4 * * * $full_command" + message="✔ 已设置定时备份 (每天 4:05 AM)。" + fi + fi + + (crontab -l 2>/dev/null | grep -v "$script_path"; echo "$cron_job") | crontab - + echo -e "${GREEN}${message}${NC}" + echo -e "${YELLOW}您可以随时输入 'sudo $script_path' 手动触发安全备份测试。${NC}" + else + echo -e "${YELLOW}已跳过创建备份脚本。${NC}"; + fi + read -p "按 Enter 键继续..." + } + + while true; do + clear; echo -e "${BLUE}--- 收尾工作与优化脚本 ---${NC}"; echo -e "${YELLOW}请选择要执行的任务:${NC}" + echo " 1) 修正 vps_installer.sh 凭证覆盖 Bug" + local ai_status="[ 依赖: AI 大脑 (选项 5) ]"; if [ -d "/root/ai_stack" ]; then ai_status="${GREEN}[ ✅ 依赖已满足 ]${NC}"; fi + printf " %-50s %s\n" "3) 部署“AI 智能提问官”脚本" "${ai_status}" + echo " 4) 创建修正后的“整机备份”脚本"; echo "------------------------------------------------------------------"; echo " b) 返回主菜单" + read -p "请输入您的选择: " task_choice + case $task_choice in + 1) _patch_vps_installer ;; + 3) if [ ! -d "/root/ai_stack" ]; then echo -e "${RED}错误:AI 大脑未安装。${NC}"; sleep 3; else _create_ai_script; fi ;; + 4) _create_backup_script ;; + b|B) break ;; *) echo -e "${RED}无效的选项。${NC}"; sleep 2 ;; + esac + done +} + +# 31. 甲骨文开机助手 (oci-helper) +install_oci_helper() { + clear + echo -e "${BLUE}--- “甲骨文开机助手 (oci-helper)” 部署向导 ---${NC}" + echo -e "${YELLOW}此功能将从 GitHub 下载并执行 oci-helper 的官方一键安装脚本。${NC}" + echo -e "${YELLOW}请确保您的 VPS 是甲骨文云 (Oracle Cloud Infrastructure) 服务器。${NC}" + read -p "您确定要继续吗? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "${GREEN}正在运行 oci-helper 安装脚本...${NC}" + bash <(wget -qO- https://github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh) + echo -e "\n${GREEN}✅ oci-helper 脚本执行完毕。${NC}" + echo -e "${CYAN}请根据其自身的提示进行后续操作。${NC}" + else + echo -e "${GREEN}操作已取消。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# 32. 服务器每日管家报告系统 +install_mail_report() { + clear + echo -e "${BLUE}--- “服务器每日管家”报告系统安装程序 ---${NC}" + echo -e "${CYAN}正在检查并安装专业邮件工具 (msmtp, vnstat, cron)...${NC}" + + # 声明非交互模式,防止 mailutils/postfix 弹出配置界面导致静默安装卡死 + export DEBIAN_FRONTEND=noninteractive + apt-get update >/dev/null 2>&1 + apt-get install -y msmtp msmtp-mta vnstat mailutils cron >/dev/null 2>&1 + unset DEBIAN_FRONTEND + + echo -e "${GREEN}✅ 所需工具已安装完毕。${NC}"; echo "-------------------------------------------------" + echo -e "${CYAN}请输入您的发件邮箱配置 (用于发送报告):${NC}" + echo -e "${YELLOW}重要提示: 请务必使用您邮箱的“应用密码”或“授权码”!${NC}" + read -p "请输入您的邮箱地址 (例如: yourname@gmail.com): " mail_user + read -sp "请输入上面邮箱的“应用密码”或“授权码”: " mail_pass; echo + read -p "请输入邮箱的 SMTP 服务器地址 (例如: smtp.gmail.com): " mail_server + read -p "请输入 SMTP 端口号 (AOL/Gmail/QQ等通常为 587,默认为 587): " mail_port; mail_port=${mail_port:-587} + read -p "请输入接收报告的邮箱地址 (可以和上面相同): " to_email + echo -e "${CYAN}正在配置专业邮件发送服务 (msmtp)...${NC}" + + # [修复] 恢复标准的换行格式,msmtprc 不支持用分号将多条配置写在同一行 + cat > /etc/msmtprc <> "/etc/mail.rc"; fi + echo -e "${GREEN}✅ 系统 mail 命令现在将由 msmtp 负责!${NC}"; echo "-------------------------------------------------" + + echo -e "${CYAN}正在创建每日报告生成脚本...${NC}" + local report_script_path="/usr/local/bin/daily_server_report.sh" + cat > $report_script_path <<'EOF' +#!/bin/bash +HOSTNAME=$(hostname); CURRENT_TIME=$(date "+%Y-%m-%d %H:%M:%S"); UPTIME=$(uptime -p); LAST_REBOOT=$(who -b | awk '{print $3, $4}') +if [ -f "/var/log/auth.log" ]; then FAILED_LOGINS=$(grep -c "Failed password" /var/log/auth.log || echo "0"); else FAILED_LOGINS="N/A"; fi +[ -z "$FAILED_LOGINS" ] && FAILED_LOGINS=0 +if ! systemctl is-active --quiet vnstat; then systemctl enable vnstat > /dev/null 2>&1 && systemctl start vnstat && sleep 5; fi +INTERFACE=$(ip -o -4 route show to default | awk '{print $5}'); ! vnstat --iflist | grep -q "$INTERFACE" && vnstat -u -i $INTERFACE +TRAFFIC_INFO=$(vnstat -d 1); SUBJECT="【服务器管家报告】来自 $HOSTNAME - $(date "+%Y-%m-%d")" +HTML_BODY="

服务器每日管家报告

主机名: $HOSTNAME

报告时间: $CURRENT_TIME


核心状态摘要:

  • 已持续运行: $UPTIME
  • 最近启动于: $LAST_REBOOT
  • SSH 登录失败次数 (今日): $FAILED_LOGINS 次

今日网络流量报告 (由 vnstat 提供):

$TRAFFIC_INFO

提示: 如果 vnstat 报告显示 'Not enough data available yet',这是正常的,请等待24小时后它才能收集到完整数据。

" +echo "$HTML_BODY" | mail -s "$SUBJECT" -a "Content-Type: text/html" "$1" +EOF + chmod +x $report_script_path; echo -e "${GREEN}✅ 报告生成脚本已创建。${NC}"; echo "-------------------------------------------------" + + echo -e "${CYAN}正在设置定时任务,让脚本在每天 23:30 自动运行...${NC}" + (crontab -l 2>/dev/null | grep -v "$report_script_path" ; echo "30 23 * * * $report_script_path $to_email") | crontab - + echo -e "${GREEN}✅ 定时任务已设置成功!${NC}"; echo "-------------------------------------------------" + + echo -e "${CYAN}正在发送最终测试邮件到 $to_email...${NC}" + echo "这是一封来自【服务器每日管家】最终版脚本的安装成功测试邮件!" | mail -s "【服务器管家】安装成功测试" $to_email + if [ $? -eq 0 ]; then + echo -e "${GREEN}★★★★★ 系统安装完毕!一切完美!★★★★★${NC}"; echo -e "${CYAN}测试邮件已成功发送!请检查您的邮箱。${NC}"; echo -e "${CYAN}第一份正式报告将在今晚 23:30 发送给您。${NC}" + else + echo -e "${RED}最终测试邮件发送失败!请检查您的邮箱配置是否正确。${NC}" + echo -e "${YELLOW}如遇报错,请退出脚本后输入 'cat ~/.msmtp.log' 查看具体错误原因。${NC}" + fi + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# X. 一键深度清理 +system_cleanup() { + clear; echo -e "${BLUE}--- 深度清理与系统优化 ---${NC}\n${YELLOW}即将开始一套大扫除...${NC}"; sleep 2 + echo -e "\n${CYAN}🧹 [1/4] 正在清扫系统更新缓存...${NC}"; sudo apt-get clean; sudo apt-get autoremove -y > /dev/null 2>&1; echo -e "${GREEN}✅ 清理完毕!${NC}"; sleep 1 + echo -e "\n${CYAN}📦 [2/4] 正在清理 Docker 环境...${NC}"; docker system prune -f > /dev/null 2>&1; echo -e "${GREEN}✅ 清理完毕!${NC}"; sleep 1 + echo -e "\n${CYAN}📜 [3/4] 正在压缩系统日志...${NC}"; sudo journalctl --vacuum-size=50M > /dev/null 2>&1; echo -e "${GREEN}✅ 优化完毕!${NC}"; sleep 1 + echo -e "\n${CYAN}💧 [4/4] 正在释放内存缓存...${NC}"; sudo sync && sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'; echo -e "${GREEN}✅ 释放完毕!${NC}"; sleep 1 + echo -e "\n${GREEN}✨ 系统深度清理完成!${NC}"; echo -e "${YELLOW}当前内存状态:${NC}"; free -h + echo -e "\n${GREEN}按任意键返回主菜单 ...${NC}"; read -n 1 -s +} + +# 99. 一键辞退包工头 +uninstall_everything() { + clear + read -p "为确认执行此终极毁灭操作,请输入【yEs-i-aM-sUrE】: " confirmation + if [[ "$confirmation" != "yEs-i-aM-sUrE" ]]; then echo -e "${GREEN}操作已取消。${NC}"; sleep 3; return; fi + + echo -e "${RED}正在拆除所有由本脚本安装的服务...${NC}" + if command -v docker &> /dev/null; then sudo docker system prune -a --volumes -f; fi + if systemctl is-active --quiet "rclone-vps-mount.service"; then sudo systemctl stop rclone-vps-mount.service; sudo systemctl disable rclone-vps-mount.service; sudo rm -f /etc/systemd/system/rclone-vps-mount.service; fi + if systemctl is-active --quiet "cloudflared"; then sudo systemctl stop cloudflared; sudo systemctl disable cloudflared; fi + + echo -e "${RED}正在清理所有数据和配置文件...${NC}" + sudo rm -rf /root/*_data /root/.config/rclone /root/.cloudflared /mnt/* + if [ -f "/etc/xrdp/xrdp.ini" ]; then + local desktop_user=$(grep 'RDP_USER' ${STATE_FILE} 2>/dev/null | cut -d'=' -f2) + if [ -n "$desktop_user" ] && id "$desktop_user" &>/dev/null; then sudo deluser --remove-home "$desktop_user" &>/dev/null; fi + sudo apt-get purge -y xrdp xfce4* &>/dev/null; sudo rm -f /root/.xsession + fi + sudo apt-get autoremove -y &>/dev/null + + echo -e "${RED}正在清理脚本自身...${NC}" + sudo rm -f ${STATE_FILE} ${RCLONE_LOG_FILE} /usr/local/bin/zhangcaiduo + + echo -e "\n${GREEN}✅ 终极还原完成。建议重启服务器。${NC}" + rm -- "$0" + exit 0 +} + + +# --- 新增功能:部署 Syncthing 同步引擎 --- +install_syncthing() { + clear + echo -e "${BLUE}--- “Syncthing 跨服同步引擎”部署计划启动! ---${NC}" + + # 1. 安装软件 + if ! command -v syncthing &> /dev/null; then + echo -e "${YELLOW}正在安装 Syncthing 主程序...${NC}" + sudo apt-get update && sudo apt-get install -y syncthing + fi + + # 2. 预初始化配置 + echo -e "${YELLOW}正在进行静默初始化...${NC}" + pkill syncthing + syncthing --no-browser > /dev/null 2>&1 & + sleep 3 + pkill syncthing + + # 3. 核心配置修改:允许通过域名访问、跳过主机检查、放行 127.0.0.1 + sed -i 's/127.0.0.1:8384/127.0.0.1:8384/g' ~/.config/syncthing/config.xml + if ! grep -q "insecureSkipHostcheck" ~/.config/syncthing/config.xml; then + sed -i '/true' ~/.config/syncthing/config.xml + fi + + # 4. 自动配置网络隧道 + check_tunnel_installed || return + read -p "请输入您为 Syncthing 面板规划的域名 (例如 sync.zhangyang.cloudns.be): " SYNC_DOMAIN + if [ -z "$SYNC_DOMAIN" ]; then echo -e "${RED}域名不能为空!${NC}"; return; fi + + update_tunnel_config "${SYNC_DOMAIN}" "http://127.0.0.1:8384" "Syncthing GUI" + + # 5. 放行传输端口 (22000) + echo -e "${YELLOW}正在放行 Syncthing 传输端口 (22000)...${NC}" + sudo ufw allow 22000/tcp > /dev/null 2>&1 + sudo ufw allow 22000/udp > /dev/null 2>&1 + + # 6. 设置后台开机自启 (使用 Systemd) + echo -e "${YELLOW}正在配置 Systemd 服务以确保稳定运行...${NC}" + sudo tee /etc/systemd/system/syncthing@root.service > /dev/null <设置->图形用户界面] 设置用户名和密码!${NC}" + + echo -e "\n## Syncthing 凭证 (部署于: $(date))" >> ${STATE_FILE} + echo "SYNCTHING_DOMAIN=${SYNC_DOMAIN}" >> ${STATE_FILE} + + echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s +} + +# ================================================================ +# --- ⬇️ ⬇️ ⬇️ 請將以下所有代碼添加到 main() 函數之前 ⬇️ ⬇️ ⬇️ --- +# ================================================================ + +# --- 應用按需卸載助手 (Docker 應用) --- +_uninstall_docker_app() { + local s_name="$1" + local s_path="$2" + local s_credential_header="$3" + local s_tunnel_comment="$4" + + if [ ! -d "$s_path" ]; then + echo -e "\n${YELLOW}應用 【${s_name}】 未安裝,無需卸載。${NC}"; sleep 2; return + fi + + echo -e "\n${RED}--- 警告 ---${NC}" + echo -e "${YELLOW}您確定要徹底刪除 【${s_name}】 嗎?${NC}" + echo -e "${YELLOW}這將會停止並刪除其 Docker 容器,並永久刪除其所有數據 (位於 ${s_path})。${NC}" + read -p "請確認 (y/n): " confirm + + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + echo -e "${GREEN}操作已取消。${NC}"; sleep 2; return + fi + + echo -e "\n${CYAN}1/4: 正在停止並刪除 ${s_name} 的 Docker 容器...${NC}" + (cd ${s_path} && sudo docker compose down -v) > /dev/null 2>&1 + + echo -e "${CYAN}2/4: 正在刪除 ${s_name} 的數據文件夾: ${s_path}${NC}" + sudo rm -rf ${s_path} + + if [ -n "$s_credential_header" ] && [ -f "${STATE_FILE}" ]; then + echo -e "${CYAN}3/4: 正在清理 ${s_name} 的憑證...${NC}" + # 使用 awk 按標題塊來清理憑證文件 + sudo awk -v header="^${s_credential_header}" 'BEGIN{p=1} $0 ~ header{p=0} /^## /{p=1} p' ${STATE_FILE} > ${STATE_FILE}.tmp && sudo mv ${STATE_FILE}.tmp ${STATE_FILE} + # 清理可能殘留的空行 + sudo awk 'NF' ${STATE_FILE} > ${STATE_FILE}.tmp && sudo mv ${STATE_FILE}.tmp ${STATE_FILE} + else + echo -e "${YELLOW}3/4: 未找到憑證標頭,跳過憑證清理。${NC}" + fi + + if [ -n "$s_tunnel_comment" ] && [ -f "$TUNNEL_CONFIG_FILE" ]; then + echo -e "${CYAN}4/4: 正在清理 ${s_name} 的 Tunnel 域名配置...${NC}" + # 刪除從註釋行到服務行的整個塊 + sudo sed -i -e "/# ${s_tunnel_comment}/,/service: .*/{/./d;}" "$TUNNEL_CONFIG_FILE" + else + echo -e "${YELLOW}4/4: 未找到 Tunnel 註釋,跳過 Tunnel 清理。${NC}" + fi + + echo -e "\n${GREEN}✅ 【${s_name}】 已徹底卸載。${NC}" + echo -e "${YELLOW}正在重啟 Cloudflare Tunnel 以使配置生效...${NC}" + sudo systemctl restart cloudflared + sleep 3 +} + +# --- 應用按需卸載助手 (系統服務) --- +_uninstall_system_service() { + local s_name="$1" + local s_service="$2" + local s_package="$3" + local s_config_files=($4) # 接收一個文件路徑數組 + + if ! systemctl is-active --quiet "$s_service" 2>/dev/null; then + echo -e "\n${YELLOW}服務 【${s_name}】 未安裝或未運行,無需卸載。${NC}"; sleep 2; return + fi + + read -p "您確定要卸載 ${s_name} 嗎? (y/n): " confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + echo -e "${GREEN}操作已取消。${NC}"; sleep 2; return + fi + + echo -e "${YELLOW}正在停止並卸載 ${s_name} (包: ${s_package})...${NC}" + sudo systemctl stop "$s_service" + sudo systemctl disable "$s_service" + sudo apt-get purge -y "$s_package" + + if [ ${#s_config_files[@]} -gt 0 ]; then + echo -e "${YELLOW}正在清理 ${s_name} 的殘留配置文件...${NC}" + for file in "${s_config_files[@]}"; do + sudo rm -f "$file" + done + fi + + echo -e "\n${GREEN}✅ 【${s_name}】 已卸載。${NC}" + sleep 3 +} + +# --- 應用按需卸載助手 (Rclone) --- +_uninstall_rclone() { + if [ ! -f "${RCLONE_CONFIG_FILE}" ]; then + echo -e "\n${YELLOW}Rclone 未配置,無需卸載。${NC}"; sleep 2; return + fi + read -p "您確定要卸載 Rclone 嗎? (這將刪除您的配置和掛載服務) (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "${YELLOW}正在停止 Rclone 掛載服務...${NC}" + sudo systemctl stop rclone-vps-mount.service &>/dev/null + sudo systemctl disable rclone-vps-mount.service &>/dev/null + sudo rm -f /etc/systemd/system/rclone-vps-mount.service + echo -e "${YELLOW}正在刪除 Rclone 配置文件...${NC}" + sudo rm -rf "$(dirname "$RCLONE_CONFIG_FILE")" + sudo sed -i '/^RCLONE_REMOTE/d' ${STATE_FILE} + sudo sed -i '/^RCLONE_MOUNT_PATH/d' ${STATE_FILE} + echo -e "${GREEN}✅ Rclone 已卸載。${NC}" + sleep 3 + else + echo -e "${GREEN}操作已取消。${NC}"; sleep 2 + fi +} + +# --- 應用按需卸載助手 (XFCE 遠程桌面) --- +_uninstall_xrdp() { + if [ ! -f "/etc/xrdp/xrdp.ini" ]; then + echo -e "\n${YELLOW}遠程工作台未安裝,無需卸載。${NC}"; sleep 2; return + fi + read -p "您確定要卸載 XFCE 遠程工作台嗎? (y/n): " confirm + if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then + echo -e "${YELLOW}正在停止並卸載 XFCE 和 XRDP...${NC}" + sudo systemctl stop xrdp + sudo apt-get purge -y xfce4* xrdp + local desktop_user=$(grep 'RDP_USER' ${STATE_FILE} 2>/dev/null | cut -d'=' -f2) + if [ -n "$desktop_user" ] && id "$desktop_user" &>/dev/null; then + echo -e "${YELLOW}正在刪除您之前創建的桌面用戶: ${desktop_user}...${NC}" + sudo deluser --remove-home "$desktop_user" &>/dev/null + fi + sudo rm -f /root/.xsession + sudo sed -i '/^## 遠程工作台/,+1d' ${STATE_FILE} # 刪除憑證 + echo -e "${GREEN}✅ 遠程工作台已卸載。${NC}" + sleep 3 + else + echo -e "${GREEN}操作已取消。${NC}"; sleep 2 + fi +} + + +# --- “应用卸载中心”主菜单 --- +show_uninstall_menu() { + while true; do + clear + echo -e "${RED}==================== 💣 应用卸载中心 💣 ====================${NC}" + echo -e "${YELLOW}请选择您要卸载的应用。此操作将删除其所有数据且不可逆!${NC}" + echo -e "${GREEN}===================================================================================================${NC}" + + # --- 显示部分 (只负责打印) --- + check_and_display "1" "Cloudflare Tunnel (核心)" "service" "cloudflared" + check_and_display "2" "Rclone 数据同步桥" "file" "${RCLONE_CONFIG_FILE}" + echo " ---" + check_and_display "3.1" "Nextcloud" "dir" "/root/nextcloud_data" + check_and_display "3.2" "OnlyOffice" "dir" "/root/onlyoffice_data" + check_and_display "3.3" "Home Assistant" "dir" "/root/home_assistant_data" + check_and_display "4" "WordPress 个人博客" "dir" "/root/wordpress_data" + check_and_display "5" "AI 大脑 (Ollama+WebUI)" "dir" "/root/ai_stack" + check_and_display "6" "Jellyfin 家庭影院" "dir" "/root/jellyfin_data" + check_and_display "7" "Navidrome 音乐服务器" "dir" "/root/navidrome_data" + check_and_display "8" "Miniflux RSS 阅读器" "dir" "/root/miniflux_data" + check_and_display "9" "Gitea 代码仓库" "dir" "/root/gitea_data" + check_and_display "10" "Memos 轻量笔记" "dir" "/root/memos_data" + check_and_display "11" "qBittorrent 下载器" "dir" "/root/qbittorrent_data" + check_and_display "12" "JDownloader 下载器" "dir" "/root/jdownloader_data" + check_and_display "13" "yt-dlp 视频下载器" "dir" "/root/ytdlp_data" + check_and_display "14" "Draw.io 绘图工具" "dir" "/root/drawio_data" + echo " ---" + check_and_display "15" "远程工作台 (Xfce)" "file" "/etc/xrdp/xrdp.ini" + check_and_display "18" "Fail2ban (防暴力破解)" "service" "fail2ban" + check_and_display "19" "Uptime Kuma (服务监控)" "dir" "/root/uptime_kuma_data" + # 修正:Glances 检查建议检查是否存在容器,但脚本暂不支持,这里先打印出来 + print_menu_item " 19.1) Glances (实时资源监控)" "" + check_and_display "19.2" "Syncthing (跨服同步引擎)" "service" "syncthing@root" + echo " ---" + check_and_display "26" "“科学上网” Vless 节点" "dir" "/root/vless_data" + check_and_display "28" "Hysteria 2 (暴力加速)" "service" "hysteria-server" + + echo -e "${GREEN}===================================================================================================${NC}" + echo -e " b) 返回主菜单" + echo "" + read -p "请输入您要卸载的选项编号 (例如 3.1) 或 'b' 返回: " choice + + # --- 逻辑处理部分 (处理你的按键) --- + case $choice in + 3.1) _uninstall_docker_app "Nextcloud" "/root/nextcloud_data" "## Nextcloud 凭证" "Nextcloud" ;; + 3.2) _uninstall_docker_app "OnlyOffice" "/root/onlyoffice_data" "## OnlyOffice 凭证" "OnlyOffice" ;; + 3.3) _uninstall_docker_app "Home Assistant" "/root/home_assistant_data" "## Home Assistant 凭证" "Home Assistant" ;; + 4) _uninstall_docker_app "WordPress" "/root/wordpress_data" "## WordPress 凭证" "WordPress" ;; + 5) _uninstall_docker_app "AI 大脑" "/root/ai_stack" "## AI 核心凭证" "AI WebUI" ;; + 6) _uninstall_docker_app "Jellyfin" "/root/jellyfin_data" "## Jellyfin 凭证" "Jellyfin" ;; + 7) _uninstall_docker_app "Navidrome" "/root/navidrome_data" "## Navidrome 凭证" "Navidrome" ;; + 8) _uninstall_docker_app "Miniflux" "/root/miniflux_data" "## Miniflux 凭证" "Miniflux" ;; + 9) _uninstall_docker_app "Gitea" "/root/gitea_data" "## Gitea 凭证" "Gitea" ;; + 10) _uninstall_docker_app "Memos" "/root/memos_data" "## Memos 凭证" "Memos (WebUI)" ;; + 11) _uninstall_docker_app "qBittorrent" "/root/qbittorrent_data" "## qBittorrent 凭证" "qBittorrent" ;; + 12) _uninstall_docker_app "JDownloader" "/root/jdownloader_data" "## JDownloader 凭证" "JDownloader" ;; + 13) _uninstall_docker_app "yt-dlp" "/root/ytdlp_data" "## yt-dlp 凭证" "yt-dlp" ;; + 14) _uninstall_docker_app "Draw.io" "/root/drawio_data" "## Draw.io 凭证" "Draw.io" ;; + 19) _uninstall_docker_app "Uptime Kuma" "/root/uptime_kuma_data" "## Uptime Kuma 凭证" "Uptime Kuma" ;; + # 19.1 的逻辑放在这里才是正确的 + 19.1) docker rm -f glances_app && sudo sed -i -e "/# Glances Monitor/,/service: .*/{/./d;}" "$TUNNEL_CONFIG_FILE" && sudo systemctl restart cloudflared ;; + 19.2) _uninstall_system_service "Syncthing" "syncthing@root" "syncthing" "/etc/systemd/system/syncthing@root.service /root/.config/syncthing" ;; + 26) _uninstall_docker_app "VLESS 节点" "/root/vless_data" "## “科学上网” VLESS 节点" "VLESS Node" ;; + 28) _uninstall_system_service "Hysteria 2" "hysteria-server" "hysteria-2" "/etc/hysteria /usr/bin/hysteria" ;; + 1) _uninstall_system_service "Cloudflare Tunnel" "cloudflared" "cloudflared" "/root/.cloudflared /etc/apt/sources.list.d/cloudflared.list ${STATE_FILE}" ;; + 2) _uninstall_rclone ;; + 15) _uninstall_xrdp ;; + 18) _uninstall_system_service "Fail2ban" "fail2ban" "fail2ban" "/etc/fail2ban/jail.local /etc/fail2ban/filter.d/xrdp.conf" ;; + b|B) break ;; + *) echo -e "${RED}无效的选项。${NC}"; sleep 2 ;; + esac + done +} +# ================================================================ +# 33. 应用升级与维护中心 +# ================================================================ +show_upgrade_menu() { + while true; do + clear + echo -e "${BLUE}==================== 🚀 应用升级中心 🚀 ====================${NC}" + echo -e "${YELLOW}一键升级你的应用,专治各种电报机器人“催更”!${NC}" + echo -e "${GREEN}======================================================================${NC}" + echo -e " 1) 升级 甲骨文开机助手 (oci-helper)" + echo -e " 2) 升级 AI 大脑 (Open WebUI 保数据升级)" + echo -e " b) 返回主菜单" + echo -e "${GREEN}======================================================================${NC}" + read -p "请输入你要升级的选项: " up_choice + + case $up_choice in + 1) + echo -e "${CYAN}正在调用 oci-helper 官方脚本进行升级...${NC}" + echo -e "${YELLOW}提示:如果弹出官方菜单,请选择对应的【更新/Upgrade】选项。${NC}" + # 重新调用官方脚本,官方脚本自带更新逻辑 + bash <(wget -qO- https://github.com/Yohann0617/oci-helper/releases/latest/download/sh_oci-helper_install.sh) + echo -e "\n${GREEN}按任意键继续...${NC}"; read -n 1 -s + ;; + 2) + if [ ! -d "/root/ai_stack" ]; then + echo -e "${RED}未检测到 AI 大脑安装目录!${NC}"; sleep 2; continue + fi + echo -e "${CYAN}正在安全升级 AI 大脑 (基于我们刚修复的保数据方案)...${NC}" + cd /root/ai_stack + sudo docker compose pull open-webui + sudo docker compose up -d + sudo docker image prune -f >/dev/null 2>&1 + echo -e "${GREEN}✅ AI 大脑升级完成,旧镜像已清理!${NC}" + echo -e "\n${GREEN}按任意键继续...${NC}"; read -n 1 -s + ;; + b|B) break ;; + *) echo -e "${RED}无效选项,请重新输入!${NC}"; sleep 1 ;; + esac + done +} +# ================================================================ +# --- ⬆️ ⬆️ ⬆️ 請將以上所有代碼添加到 main() 函數之前 ⬆️ ⬆️ ⬆️ --- +# ================================================================ + +# --- 主循環 --- +main() { + check_core_dependencies + setup_colors + setup_shortcut + + while true; do + show_main_menu + read -p "請輸入您的選擇 (u, m, s, 1-32, X, 99, q): " choice + case $choice in + u|U) update_system ;; + m|M) run_unminimize ;; + s|S) manage_swap ;; + 1) [ -f "$TUNNEL_CONFIG_FILE" ] && { echo -e "\n${YELLOW}Cloudflare Tunnel 已安裝。${NC}"; sleep 2; } || install_cloudflare_tunnel ;; + 2) configure_rclone_engine ;; + 3.1) [ -d "/root/nextcloud_data" ] && { echo -e "\n${YELLOW}Nextcloud 已安裝。${NC}"; sleep 2; } || install_nextcloud ;; + 3.2) [ -d "/root/onlyoffice_data" ] && { echo -e "\n${YELLOW}OnlyOffice 已安裝。${NC}"; sleep 2; } || install_onlyoffice ;; + 3.3) [ -d "/root/home_assistant_data" ] && { echo -e "\n${YELLOW}Home Assistant 已安裝。${NC}"; sleep 2; } || install_home_assistant ;; + 4) [ -d "/root/wordpress_data" ] && { echo -e "\n${YELLOW}WordPress 已安裝。${NC}"; sleep 2; } || install_wordpress ;; + 5) [ -d "/root/ai_stack" ] && { echo -e "\n${YELLOW}AI 大腦已安裝。${NC}"; sleep 2; } || install_ai_suite ;; + 6) [ -d "/root/jellyfin_data" ] && { echo -e "\n${YELLOW}Jellyfin 已安裝。${NC}"; sleep 2; } || install_jellyfin ;; + 7) [ -d "/root/navidrome_data" ] && { echo -e "\n${YELLOW}Navidrome 已安裝。${NC}"; sleep 2; } || install_navidrome ;; + 8) [ -d "/root/miniflux_data" ] && { echo -e "\n${YELLOW}Miniflux 已安裝。${NC}"; sleep 2; } || install_miniflux ;; + 9) [ -d "/root/gitea_data" ] && { echo -e "\n${YELLOW}Gitea 已安裝。${NC}"; sleep 2; } || install_gitea ;; + 10) [ -d "/root/memos_data" ] && { echo -e "\n${YELLOW}Memos 已安裝。${NC}"; sleep 2; } || install_memos ;; + 11) [ -d "/root/qbittorrent_data" ] && { echo -e "\n${YELLOW}qBittorrent 已安裝。${NC}"; sleep 2; } || install_qbittorrent ;; + 12) [ -d "/root/jdownloader_data" ] && { echo -e "\n${YELLOW}JDownloader 已安裝。${NC}"; sleep 2; } || install_jdownloader ;; + 13) [ -d "/root/ytdlp_data" ] && { echo -e "\n${YELLOW}yt-dlp 已安裝。${NC}"; sleep 2; } || install_ytdlp ;; + 14) [ -d "/root/drawio_data" ] && { echo -e "\n${YELLOW}Draw.io 已安裝。${NC}"; sleep 2; } || install_drawio ;; +15.1) [ -f "/etc/xrdp/xrdp.ini" ] && { echo -e "\n${YELLOW}遠程工作台已安裝。${NC}"; sleep 2; } || install_desktop_env ;; + 15.2) install_chinese_fonts ;; + 15.3) + if [ ! -f "/etc/xrdp/xrdp.ini" ]; then + echo -e "${RED}错误:远程工作台未安装。${NC}"; sleep 3 + else + _install_desktop_browser + fi + ;; + 16) install_chinese_fonts ;; + 17) install_ai_model ;; + 18) [ -f "/etc/fail2ban/jail.local" ] && { echo -e "\n${YELLOW}Fail2ban 已安裝。${NC}"; sleep 2; } || install_fail2ban ;; +19) [ -d "/root/uptime_kuma_data" ] && { echo -e "\n${YELLOW}Uptime Kuma 已安裝。${NC}"; sleep 2; } || install_uptime_kuma ;; +19.1) install_glances ;; +19.2) install_syncthing ;; +# --- 新增 SSH 选项 --- + 20) add_ssh_public_key ;; + 21) toggle_ssh_password_login ;; + # --- 新增结束 --- + 22) run_nextcloud_optimization ;; + 22.1) run_nextcloud_super_boost ;; # <-- 加上这一行 + 23) show_service_control_panel ;; + 24) show_credentials ;; + 25) show_vps_info ;; + 26) install_vless_node ;; + 28) install_hysteria_node ;; + 30) run_wrap_up_tasks ;; + 31) install_oci_helper ;; + 32) install_mail_report ;; + 33) show_upgrade_menu ;; # <--- 加在这里 + 40) show_uninstall_menu ;; + x|X) system_cleanup ;; + 99) uninstall_everything ;; + q|Q) echo -e "${BLUE}裝修愉快,房主再見!${NC}"; exit 0 ;; + *) echo -e "${RED}無效的選項,請重新輸入。${NC}"; sleep 2 ;; + esac + done +} + +main "$@" \ No newline at end of file