Files
2026-05-14 21:00:50 +00:00

2758 lines
141 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <<EOF
# 由VPS包工头脚本自动生成
# tunnel: UUID (这一行由cloudflared自动管理,请勿手动修改)
# credentials-file: /root/.cloudflared/UUID.json (这一行也由cloudflared自动管理)
ingress:
- service: http_status:404
EOF
echo -e "${GREEN}✅ Cloudflare Tunnel 服务安装成功!${NC}"
echo -e "${YELLOW}正在启动服务...${NC}"
sudo systemctl start cloudflared
sleep 2
if systemctl is-active --quiet cloudflared; then
echo -e "${GREEN}✅ Tunnel 服务已成功启动!现在您可以部署其他应用了。${NC}"
else
echo -e "${RED}❌ Tunnel 服务启动失败!请检查日志排查问题。${NC}"
echo -e "${RED} 您可以尝试执行 'sudo journalctl -u cloudflared -f' 来查看实时日志。${NC}"
fi
else
echo -e "${RED}❌ Tunnel 服务安装失败!请检查您的 Token 是否正确,或查看 cloudflared 的错误输出。${NC}"
fi
echo -e "\n${GREEN}按任意键返回主菜单...${NC}"; read -n 1 -s
}
# --- 前置检查 ---
check_tunnel_installed() {
if ! systemctl is-active --quiet cloudflared 2>/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 <<EOF
[Unit]
Description=Rclone Mount Service for ${rclone_remote_name}
Wants=network-online.target
After=network-online.target
[Service]
Type=simple; User=root; Group=root; RestartSec=10; Restart=on-failure
ExecStart=/usr/bin/rclone mount ${rclone_remote_name}: ${rclone_mount_path} --config ${RCLONE_CONFIG_FILE} --uid 1000 --gid 1000 --umask 022 --allow-other --allow-non-empty --vfs-cache-mode full --vfs-cache-max-size 5G --log-level INFO --log-file ${RCLONE_LOG_FILE}
ExecStop=/bin/fusermount -u ${rclone_mount_path}
[Install]
WantedBy=default.target
EOF
sudo systemctl daemon-reload; sudo systemctl enable --now "rclone-vps-mount.service"; sleep 2
if systemctl is-active --quiet "rclone-vps-mount.service"; then
echo -e "${GREEN}✅ Rclone 全盘跃遷通道已激活!写入性能已优化!${NC}"
else
echo -e "${RED}❌ 挂载通道启动失败!请检查日志。${NC}"
fi
echo -e "\n${GREEN}Rclone 数据同步桥配置完成!按任意键返回主菜单...${NC}"; read -n 1 -s
}
# 3.1. 部署 Nextcloud
install_nextcloud() {
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 NEXTCLOUD_DOMAIN="nextcloud.${MAIN_DOMAIN}"
local DB_PASSWORD="NcDb-pW_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 12)"
clear; echo -e "${BLUE}--- “Nextcloud 核心”部署计划启动! ---${NC}"
mkdir -p /root/nextcloud_data; cat > /root/nextcloud_data/docker-compose.yml <<EOF
services:
db:
image: mariadb:11.4
container_name: nextcloud_db
restart: unless-stopped
command: [--transaction-isolation=READ-COMMITTED, --binlog-format=ROW, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci]
volumes:
- './db:/var/lib/mysql'
environment:
MYSQL_DATABASE: nextclouddb
MYSQL_USER: nextclouduser
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}_root
redis:
image: redis:alpine
container_name: nextcloud_redis
restart: unless-stopped
app:
image: nextcloud:latest
container_name: nextcloud_app
restart: unless-stopped
ports:
- "127.0.0.1:8888:80"
volumes:
- './html:/var/www/html'
- './php-opcache.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini'
depends_on:
- db
- redis
EOF
echo -e "opcache.memory_consumption=512\nopcache.interned_strings_buffer=16" > /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 <<EOF
services:
onlyoffice:
image: onlyoffice/documentserver:latest
container_name: onlyoffice_app
restart: always
ports:
- "127.0.0.1:8889:80"
volumes:
- './data:/var/www/onlyoffice/Data'
- './logs:/var/log/onlyoffice'
environment:
JWT_ENABLED: 'true'
JWT_SECRET: ${ONLYOFFICE_JWT_SECRET}
EOF
(cd /root/onlyoffice_data && sudo docker compose up -d)
echo -e "${GREEN}✅ OnlyOffice 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## OnlyOffice 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: home_assistant_app
restart: always
ports:
- "127.0.0.1:8123:8123"
volumes:
- './config:/config'
- '/etc/localtime:/etc/localtime:ro'
environment:
- 'TZ=Asia/Shanghai'
EOF
# ==================== 【关键修复开始】 ====================
# 在容器启动前,预先创建配置文件,并添加信任代理的设置
echo -e "${YELLOW}正在为 Home Assistant 预配置受信任的代理...${NC}"
cat > /root/home_assistant_data/config/configuration.yaml <<EOF
# 由 VPS 包工头脚本自动添加,用于信任 Cloudflare Tunnel
default_config:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1 # 信任来自本机的请求
- 172.16.0.0/12 # 信任 Docker 的默认网段
- 10.0.0.0/8 # 信任其他常见的内部网段
- 192.168.0.0/16
EOF
# ==================== 【关键修复结束】 ====================
echo -e "${YELLOW}正在启动 Home Assistant 服务... (首次启动可能需要几分钟时间初始化,请稍候...)${NC}"
if (cd /root/home_assistant_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ Home Assistant 已启动,正在进行初始化...${NC}"; sleep 10
echo -e "\n## Home Assistant 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
db:
image: mariadb:11.4
container_name: wordpress_db
restart: unless-stopped
volumes:
- './db_data:/var/lib/mysql'
environment:
MYSQL_ROOT_PASSWORD: ${WP_DB_ROOT_PASS}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${WP_DB_PASS}
wordpress:
image: wordpress:latest
container_name: wordpress_app
restart: unless-stopped
ports:
- "127.0.0.1:8890:80"
volumes:
- './html:/var/www/html'
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${WP_DB_PASS}
WORDPRESS_DB_NAME: wordpress
depends_on:
- db
EOF
if (cd /root/wordpress_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ WordPress 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## WordPress 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
ollama:
image: ollama/ollama
container_name: ollama_app
restart: unless-stopped
volumes:
- './ollama_data:/root/.ollama'
open-webui:
image: 'ghcr.io/open-webui/open-webui:main'
container_name: open_webui_app
restart: unless-stopped
ports:
- "127.0.0.1:3001:8080"
environment:
- 'OLLAMA_BASE_URL=http://ollama:11434'
depends_on:
- ollama
EOF
if (cd /root/ai_stack && sudo docker compose up -d); then
echo -e "${GREEN}✅ AI 核心已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## AI 核心凭证 (部署于: $(date))" >> ${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 <<EOF
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin_app
restart: unless-stopped
ports:
- "127.0.0.1:8096:8096"
environment:
- 'PUID=1000'
- 'PGID=1000'
- 'TZ=Asia/Shanghai'
volumes:
- './config:/config'
- '/mnt/Movies:/media/movies'
- '/mnt/TVShows:/media/tvshows'
- '/mnt/Music:/media/music'
EOF
if (cd /root/jellyfin_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ Jellyfin 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## Jellyfin 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
navidrome:
image: deluan/navidrome:latest
container_name: navidrome_app
restart: unless-stopped
ports:
- "127.0.0.1:4533:4533"
volumes:
- '/mnt/Music:/music'
- './data:/data'
environment:
- 'PUID=1000'
- 'PGID=1000'
- 'TZ=Asia/Shanghai'
EOF
if (cd /root/navidrome_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ Navidrome 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## Navidrome 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
db:
image: postgres:15-alpine
container_name: miniflux_db
restart: unless-stopped
environment:
POSTGRES_USER: miniflux
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: miniflux
volumes:
- ./db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux"]
interval: 10s
start_period: 30s
app:
image: miniflux/miniflux:latest
container_name: miniflux_app
restart: unless-stopped
ports:
- "127.0.0.1:8091:8080"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://miniflux:${DB_PASSWORD}@db/miniflux?sslmode=disable
RUN_MIGRATIONS: "1"
CREATE_ADMIN: "1"
ADMIN_USERNAME: admin
ADMIN_PASSWORD: ${ADMIN_PASSWORD_INITIAL}
BASE_URL: https://${MINIFLUX_DOMAIN}
EOF
if (cd /root/miniflux_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ Miniflux 已启动,等待服务稳定...${NC}"; sleep 10
echo -e "\n## Miniflux 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
server:
image: gitea/gitea:latest
container_name: gitea_app
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
- 'USER_UID=1000'
- 'USER_GID=1000'
volumes:
- './gitea:/data'
- '/etc/timezone:/etc/timezone:ro'
- '/etc/localtime:/etc/localtime:ro'
EOF
if (cd /root/gitea_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ Gitea 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## Gitea 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent_app
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:6881:6881"
- "127.0.0.1:6881:6881/udp"
environment:
- 'PUID=1000'
- 'PGID=1000'
- 'TZ=Asia/Shanghai'
- 'WEBUI_PORT=8080'
volumes:
- './config:/config'
- '/mnt/Downloads:/downloads'
EOF
if (cd /root/qbittorrent_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ qBittorrent 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## qBittorrent 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
jdownloader-2:
image: jlesage/jdownloader-2
container_name: jdownloader_app
restart: unless-stopped
ports:
- "127.0.0.1:5800:5800"
volumes:
- './config:/config'
- '/mnt/Downloads:/output'
environment:
- 'USER_ID=1000'
- 'GROUP_ID=1000'
- 'TZ=Asia/Shanghai'
- 'VNC_PASSWORD=${JDOWNLOADER_PASS}'
EOF
if (cd /root/jdownloader_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ JDownloader 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## JDownloader 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
ytdlp-ui:
image: alexta69/metube:latest
container_name: ytdlp_app
restart: unless-stopped
ports:
# MeTube 的容器内部端口是 8081
- "127.0.0.1:8999:8081"
volumes:
# MeTube 的下载路径是 /downloads
- '/mnt/Downloads:/downloads'
environment:
- 'PUID=1000'
- 'PGID=1000'
- 'TZ=Asia/Shanghai'
EOF
# --- 修正结束 ---
if (cd /root/ytdlp_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ yt-dlp (MeTube) 已启动,等待服务稳定...${NC}"; sleep 5
echo -e "\n## yt-dlp 凭证 (部署于: $(date))" >> ${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 <<EOF
services:
vless:
image: teddysun/xray:latest
container_name: vless_app
restart: always
ports:
- "127.0.0.1:40001:40001"
command: xray -c /etc/xray/config.json
volumes:
- ./config.json:/etc/xray/config.json
EOF
cat > /root/vless_data/config.json <<EOF
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"listen": "0.0.0.0",
"port": 40001,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "${UUID}", "level": 0 }
],
"decryption": "none"
},
"streamSettings": { "network": "ws" }
}
],
"outbounds": [ { "protocol": "freedom" } ]
}
EOF
if (cd /root/vless_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ VLESS 节点已启动,等待服务稳定...${NC}"; sleep 5
update_tunnel_config "${VLESS_DOMAIN}" "http://127.0.0.1:40001" "VLESS Node"
local VLESS_LINK="vless://${UUID}@${VLESS_DOMAIN}:443?encryption=none&security=tls&type=ws&host=${VLESS_DOMAIN}&path=%2F#VPS-Tunnel-VLESS"
echo -e "\n## “科学上网” VLESS 节点 (部署于: $(date))" >> ${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 <<EOF
services:
socks5:
# 【建材升级】已从 serjs/go-socks5-proxy 替换为维护更积极的 dockage/socks5
image: dockage/socks5:latest
container_name: socks5_proxy_app
restart: unless-stopped
ports:
- "127.0.0.1:1080:1080"
EOF
if (cd /root/socks5_proxy_data && sudo docker compose up -d); then
echo -e "${GREEN}✅ Socks5 代理服務已啟動,等待服務穩定...${NC}"; sleep 5
echo -e "\n## Socks5 代理憑證 (部署于: $(date))" >> ${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 <<EOF > /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 <<EOF > "$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 <<EOF
defaults
auth on
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
account default
host $mail_server
port $mail_port
from $mail_user
user $mail_user
password $mail_pass
EOF
chmod 600 /etc/msmtprc; echo -e "${GREEN}✅ 邮件发送服务 (msmtp) 配置完毕。${NC}"; echo "-------------------------------------------------"
echo -e "${CYAN}正在任命新的邮件管家 (msmtp)...${NC}"
# [修复] 预先创建文件,消除 grep 找不到文件的报错警告
touch /etc/mail.rc
if ! grep -q "set mta=/usr/bin/msmtp" "/etc/mail.rc"; then echo "set mta=/usr/bin/msmtp" >> "/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="<html><body><h2 style='color:#2c3e50;'>服务器每日管家报告</h2><p><b>主机名:</b> $HOSTNAME</p><p><b>报告时间:</b> $CURRENT_TIME</p><hr><h3>核心状态摘要:</h3><ul><li><b>已持续运行:</b> $UPTIME</li><li><b>最近启动于:</b> $LAST_REBOOT</li><li><b>SSH 登录失败次数 (今日):</b> <strong style='color:red;'>$FAILED_LOGINS 次</strong></li></ul><hr><h3>今日网络流量报告 (由 vnstat 提供):</h3><pre style='background-color:#f5f5f5; padding:10px; border-radius:5px; font-family:monospace;'>$TRAFFIC_INFO</pre><p style='font-size:12px; color:#7f8c8d;'>提示: 如果 vnstat 报告显示 'Not enough data available yet',这是正常的,请等待24小时后它才能收集到完整数据。</p></body></html>"
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 '/<gui/a \ <insecureSkipHostcheck>true</insecureSkipHostcheck>' ~/.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 <<EOF
[Unit]
Description=Syncthing - Open Source Continuous File Synchronization for %i
Documentation=man:syncthing(1)
After=network.target
[Service]
User=%i
ExecStart=/usr/bin/syncthing --no-browser --no-restart --logflags=0
Restart=on-failure
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now syncthing@root.service
echo -e "\n${GREEN}✅ Syncthing 部署成功!${NC}"
echo -e "${CYAN}访问域名: https://${SYNC_DOMAIN}${NC}"
echo -e "${YELLOW}提示:请立即在网页中 [操作->设置->图形用户界面] 设置用户名和密码!${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 "$@"