一键脚本
bash <(curl -sL gitee.com/meimolihan/cmdbox/raw/master/sh/fan-nginx_menu.sh)
bash <(curl -sL ../sh/fan-nginx_menu.sh)
补充说明
该脚本为 fan-nginx 交互式管理菜单,集成服务启停、状态查看、开机自启管理、日志查看、管理密码重置、安装/卸载、数据备份/恢复等功能,操作后自动等待按键再返回菜单。
功能特点
- 交互式菜单:可视化操作界面,输入序号即可执行对应操作
- 服务管理:停止、启动、重启 fan-nginx 服务
- 状态查看:查看服务运行状态、开机自启状态(含启用/禁用切换)
- 日志查看:查看最近100行日志,或实时跟踪日志
- 密码管理:通过 CLI
fan-nginx credentials一次重置并打印全部管理员账号密码(同时关闭两步验证) - 数据管理:一键备份数据、从最新备份一键恢复
- 一键安装/卸载:菜单内直接调用 fan-nginx 仓库的 install.sh / uninstall.sh
- 退场动画:退出时显示 spinner 动画
- 彩色终端输出,操作反馈清晰直观
菜单选项说明
| 选项 | 功能 | 说明 |
|---|---|---|
1 |
停止 fan-nginx | systemctl stop fan-nginx |
2 |
启动 fan-nginx | systemctl start fan-nginx |
3 |
重启 fan-nginx | systemctl restart fan-nginx |
4 |
查看服务状态 | systemctl status fan-nginx |
5 |
查看开机自启状态 | systemctl is-enabled fan-nginx |
6 |
开启开机自启 | systemctl enable fan-nginx |
7 |
禁用开机自启 | systemctl disable fan-nginx |
8 |
查看日志(最近100行) | journalctl -u fan-nginx -n 100 |
9 |
实时跟踪日志 | journalctl -u fan-nginx -f |
10 |
查看/重置管理密码 | fan-nginx credentials(打印全部管理员账号密码、关闭两步验证) |
66 |
安装/升级 fan-nginx | 调用 fan-nginx 仓库 install.sh |
77 |
备份数据 | 调用 fan-nginx 仓库 fan-nginx_backup.sh(默认目录保留6份) |
88 |
恢复数据 | 调用 fan-nginx 仓库 fan-nginx_recover.sh(最近备份) |
99 |
卸载 fan-nginx | 调用 fan-nginx 仓库 uninstall.sh |
0 |
返回上一级菜单 | 回到主菜单 |
00 |
退出脚本 | 带动画退出 |
注意事项
- 需要 root 权限运行(sudo 或 root 用户)
- 所有 systemd 操作需系统已安装
systemctl - 实时跟踪日志按
Ctrl+C退出,会自动返回菜单 - 安装/卸载/备份/恢复操作依赖网络,需能访问 GitHub raw 源(国内网络可设置代理或 Gitee 中转)
- 备份/恢复默认使用
/vol2/1000/file/backup/fan-nginx-backup(保留6份),如需自定义目录请直接运行fan-nginx_backup.sh <目录> <份数> - 恢复数据会覆盖现有数据,执行前请确认备份文件可用
- 选项
10会关闭两步验证并重置管理密码,请在生产环境慎重使用 - 脚本以 bash 编写,确保运行环境支持 bash
脚本源码
#!/bin/bash
set -uo pipefail
# ================== terminal colors ==================
list_color_init() {
export gl_hui=$'\033[38;5;59m'
export gl_hong=$'\033[38;5;9m'
export gl_lv=$'\033[38;5;10m'
export gl_huang=$'\033[38;5;11m'
export gl_lan=$'\033[38;5;32m'
export gl_bai=$'\033[38;5;15m'
export gl_zi=$'\033[38;5;13m'
export gl_bufan=$'\033[38;5;14m'
export reset=$'\033[0m'
}
list_color_init
SERVICE="fan-nginx"
INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/meimolihan/fan-nginx/main/scripts/install.sh"
UNINSTALL_SCRIPT_URL="https://raw.githubusercontent.com/meimolihan/fan-nginx/main/scripts/uninstall.sh"
BACKUP_SCRIPT_URL="https://raw.githubusercontent.com/meimolihan/fan-nginx/main/scripts/fan-nginx_backup.sh"
RECOVER_SCRIPT_URL="https://raw.githubusercontent.com/meimolihan/fan-nginx/main/scripts/fan-nginx_recover.sh"
# 默认备份目录(保留 6 份;可在 77 处直接传参覆盖)
BACKUP_DIR="/vol2/1000/file/backup/fan-nginx-backup"
log_info() { echo -e "${gl_lan}[信息]${gl_bai} $*"; }
log_ok() { echo -e "${gl_lv}[成功]${gl_bai} $*"; }
log_warn() { echo -e "${gl_huang}[警告]${gl_bai} $*"; }
log_error() { echo -e "${gl_hong}[错误]${gl_bai} $*" >&2; }
break_end() {
echo -e "${gl_lv}操作完成${gl_bai}"
echo -e "${gl_bai}按任意键继续 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
read -r -n 1 -s -p ""
echo ""
clear
}
sleep_fractional() {
local seconds=$1
if sleep "$seconds" 2>/dev/null; then return 0; fi
if command -v perl >/dev/null 2>&1; then perl -e "select(undef, undef, undef, $seconds)"; return 0; fi
if command -v python3 >/dev/null 2>&1; then python3 -c "import time; time.sleep($seconds)"; return 0; fi
if command -v python >/dev/null 2>&1; then python -c "import time; time.sleep($seconds)"; return 0; fi
local int_seconds=$(echo "$seconds" | awk '{print int($1+0.999)}')
sleep "$int_seconds"
}
cancel_return() {
local menu_name="${1:-退出脚本}"
echo -ne "${gl_lv}即将返回 ${gl_huang}${menu_name} ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.5
echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.6
echo ""
clear
}
exit_script() {
local frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
local dots=(
"${gl_hong}."
"${gl_huang}."
"${gl_lv}."
"${gl_bufan}."
"${gl_zi}."
)
local dot_buffer=""
local frame_len=${#frames[@]}
local dot_idx=0
local total_dots=${#dots[@]}
for ((i=0; i<20; i++)); do
if (( i > 0 && i % 3 == 0 && dot_idx < total_dots )); then
dot_buffer+=${dots[$dot_idx]}
((dot_idx++))
fi
echo -ne "\r\033[K${gl_bufan}${frames[i % frame_len]}${gl_bai} 正在退出 ${dot_buffer}"
sleep_fractional 0.06
done
echo -e "\r\033[K${gl_lv}✓${gl_bai} 成功退出\n"
clear
exit 0
}
handle_y_n() {
echo -ne "\r${gl_hong}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}或${gl_hong}N${gl_bai}) ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.3
echo -ne "\r${gl_huang}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}或${gl_hong}N${gl_bai}) ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.3
echo -ne "\r${gl_lv}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}或${gl_hong}N${gl_bai}) ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.6
echo ""
return 2
}
exit_animation() {
echo -ne "\r${gl_lv}即将退出 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.5
echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.6
echo ""
clear
}
cancel_empty() {
local menu_name="${1:-上一级选单}"
echo -e "${gl_hong}空输入,返回 ${gl_huang}${menu_name} ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.5
echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
sleep_fractional 0.6
echo ""
clear
}
handle_invalid_input() {
echo -ne "\r\033[K${gl_huang}无效的输入,请重新输入! ${gl_zi} 1 ${gl_huang} 秒后返回"
sleep_fractional 1
echo -ne "\r\033[K${gl_lv}无效的输入,请重新输入! ${gl_zi}0${gl_lv} 秒后返回"
sleep_fractional 0.5
echo -ne "\r\033[K"
return 2
}
show_service_url() {
local service="${1:-fan-nginx}"
local url=""
local port=""
local ip
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
[ -z "$ip" ] && ip=$(ip route get 1 2>/dev/null | awk '{print $7}' | head -1)
[ -z "$ip" ] && ip=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1)
[ -z "$ip" ] && ip="127.0.0.1"
# 优先从 systemd ExecStart 解析 --server.port
if [ -z "$port" ];then
local exec_cmd
exec_cmd=$(systemctl show -p ExecStart "$service" 2>/dev/null | cut -d= -f2-)
port=$(echo "$exec_cmd" | grep -oE '\-\-server\.port=[0-9]+' | grep -oE '[0-9]+' | head -1)
fi
# 回退:从启动日志(log file / journalctl)解析 Undertow 监听端口
if [ -z "$port" ] && [ -f "/var/lib/fan-nginx/log/fan-nginx.log" ]; then
port=$(grep -oE 'Undertow started on port\(s\) [0-9]+' /var/lib/fan-nginx/log/fan-nginx.log | grep -oE '[0-9]+$' | head -1)
fi
if [ -z "$port" ] && command -v journalctl >/dev/null 2>&1; then
port=$(journalctl -u "$service" --no-pager -n 200 -o cat 2>/dev/null \
| grep -oE 'Undertow started on port\(s\) [0-9]+' | grep -oE '[0-9]+$' | head -1)
fi
if [ -z "$port" ] && command -v ss >/dev/null 2>&1;then
local pid
pid=$(systemctl show -p MainPID "$service" 2>/dev/null | cut -d= -f2-)
if [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 0 ]];then
port=$(ss -tlnp 2>/dev/null | grep ",pid=$pid," | grep -oE ':[0-9]+' | sed 's/^://' | head -1)
fi
fi
if [ -n "$port" ]; then
url="http://${ip}:${port}"
fi
if [ -n "$url" ]; then
echo -e "访问地址:${gl_lv}${url}${gl_bai}"
else
echo -e "访问地址:${gl_hong}无法获取访问地址${gl_bai}"
return 1
fi
}
show_service_status() {
local service="${1:-fan-nginx}"
local version=""
local ver_regex='\b(v[0-9]+\.[0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+)\b'
if command -v "$service" &>/dev/null; then
version=$("$service" --version 2>/dev/null | grep -vE '[_#]{3,}' | grep -oE "$ver_regex" | head -1)
[ -z "$version" ] && version=$("$service" version 2>/dev/null | grep -vE '[_#]{3,}' | grep -oE "$ver_regex" | head -1)
fi
if [ -z "$version" ]; then
local exec_path
exec_path=$(systemctl show -p ExecStart "$service" 2>/dev/null | cut -d= -f2 | awk '{print $1}')
if [ -n "$exec_path" ] && [ -x "$exec_path" ]; then
version=$("$exec_path" --version 2>/dev/null | grep -vE '[_#]{3,}' | grep -oE "$ver_regex" | head -1)
[ -z "$version" ] && version=$("$exec_path" version 2>/dev/null | grep -vE '[_#]{3,}' | grep -oE "$ver_regex" | head -1)
fi
fi
if [ -z "$version" ] && command -v journalctl &>/dev/null; then
version=$(journalctl -u "$service" --no-pager -n 50 -o cat 2>/dev/null | grep -oE "$ver_regex" | head -1)
fi
if [[ ! "$version" =~ $ver_regex ]]; then
version=""
fi
if systemctl is-active --quiet "$service"; then
echo -e "运行状态:${gl_lv}$service 正在运行${gl_bai}"
else
echo -e "运行状态:${gl_hong}$service 未运行${gl_bai}"
fi
if [ -n "$version" ]; then
echo -e "版本信息:${gl_huang}$version${gl_bai}"
else
echo -e "版本信息:${gl_huang}无法获取${gl_bai}"
fi
}
manage_fan_nginx() {
while true; do
clear
echo -e ""
echo -e "${gl_zi}>>> fan-nginx 管理工具${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
show_service_status fan-nginx
show_service_url fan-nginx
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_bufan}1. ${gl_bai}停止 fan-nginx ${gl_bufan}2. ${gl_bai}启动 fan-nginx"
echo -e "${gl_bufan}3. ${gl_bai}重启 fan-nginx ${gl_bufan}4. ${gl_bai}查看服务状态"
echo -e "${gl_bufan}5. ${gl_bai}查看开机自启状态 ${gl_bufan}6. ${gl_bai}开启开机自启"
echo -e "${gl_bufan}7. ${gl_bai}禁用开机自启 ${gl_bufan}8. ${gl_bai}查看日志(100行)"
echo -e "${gl_bufan}9. ${gl_bai}实时跟踪日志 ${gl_lv}10. ${gl_bai}查看/重置管理密码"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_lv}66. ${gl_bai}安装/升级 fan-nginx ${gl_huang}77. ${gl_bai}备份数据"
echo -e "${gl_lv}88. ${gl_bai}恢复数据 ${gl_hong}99. ${gl_bai}卸载 fan-nginx"
echo -e "${gl_huang}0. ${gl_bai}返回上一级选单 ${gl_hong}00. ${gl_bai}退出脚本"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入你的选择: ")" action
case "$action" in
1)
echo -e ""
echo -e "${gl_zi}>>> 正在停止 fan-nginx 服务 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo systemctl stop ${SERVICE}
log_ok "fan-nginx 服务已停止"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
2)
echo -e ""
echo -e "${gl_zi}>>> 正在启动 fan-nginx 服务 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo systemctl start ${SERVICE}
log_ok "fan-nginx 服务已启动"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
3)
echo -e ""
echo -e "${gl_zi}>>> 正在重启 fan-nginx 服务 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo systemctl restart ${SERVICE}
log_ok "fan-nginx 服务已重启"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
4)
echo -e ""
echo -e "${gl_zi}>>> fan-nginx 服务状态 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo systemctl status ${SERVICE}
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
5)
echo -e ""
echo -e "${gl_zi}>>> fan-nginx 开机自启状态 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
local status=$(sudo systemctl is-enabled ${SERVICE} 2>/dev/null)
case "$status" in
enabled) echo -e "${gl_lv}已启用${gl_bai}" ;;
disabled) echo -e "${gl_hong}已禁用${gl_bai}" ;;
static) echo "静态(非服务单元)" ;;
indirect) echo "间接(依赖其他单元)" ;;
*) echo "$status" ;;
esac
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
6)
echo -e ""
echo -e "${gl_zi}>>> 正在开启 fan-nginx 开机自启 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo systemctl enable ${SERVICE}
log_ok "已开启 fan-nginx 开机自启"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
7)
echo -e ""
echo -e "${gl_zi}>>> 正在禁用 fan-nginx 开机自启 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo systemctl disable ${SERVICE}
log_ok "已禁用 fan-nginx 开机自启"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
8)
echo -e ""
echo -e "${gl_zi}>>> fan-nginx 日志(最近100行)${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo journalctl -u ${SERVICE} -n 100
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
9)
echo -e ""
echo -e "${gl_zi}>>> 实时跟踪 fan-nginx 日志(按 Ctrl+C 退出)${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
sudo journalctl -u ${SERVICE} -f
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
10)
echo -e ""
echo -e "${gl_zi}>>> fan-nginx CLI:查看/重置管理密码 (credentials)${gl_huang}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_huang}注意:该操作会重置并打印全部管理员账号密码,并关闭两步验证。${gl_bai}"
if command -v fan-nginx >/dev/null 2>&1; then
sudo fan-nginx credentials
else
log_error "未找到 fan-nginx 命令,请先通过 66 安装/升级 fan-nginx"
fi
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
;;
66)
bash -c "$(curl -sSL ${INSTALL_SCRIPT_URL})"
break_end
continue
;;
77)
bash <(curl -sL ${BACKUP_SCRIPT_URL}) "${BACKUP_DIR}" 6
break_end
continue
;;
88)
bash <(curl -sL ${RECOVER_SCRIPT_URL}) "${BACKUP_DIR}"
break_end
continue
;;
99)
bash <(curl -sSL ${UNINSTALL_SCRIPT_URL})
break_end
continue
;;
0)
cancel_return "已是主菜单"
continue
;;
00 | 000 | 0000)
exit_script
;;
*)
handle_invalid_input
;;
esac
done
}
manage_fan_nginx
创建本地脚本
new_script="new_test.sh"
cat > "$new_script" << 'EOF'
#!/bin/bash
# 粘贴脚本源码
EOF
# 保留本地脚本,去掉 rm -f "$new_script"
chmod +x "$new_script" && ./"$new_script" && rm -f "$new_script"