2758 lines
141 KiB
Bash
2758 lines
141 KiB
Bash
#!/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 "$@" |