#!/bin/bash set -euo pipefail # ============================================================================== # 配置中心 # ============================================================================== readonly APP_DIR="/opt/nas-media-player" readonly SERVICE_NAME="nas-media-player" readonly WAIT_TIMEOUT=10 readonly SYSTEMD_SERVICE="/etc/systemd/system/${SERVICE_NAME}.service" readonly PORT=8800 # 服务监听端口 readonly VIDEO_DIR="/mnt" # 媒体文件根目录(和程序逻辑对齐) readonly LOG_FILE="${APP_DIR}/${SERVICE_NAME}.log" # 日志文件固定在运行目录 readonly PIP_MIRROR="https://mirrors.tencent.com/pypi/simple" # 网络检查镜像 readonly BIN_NAME="${SERVICE_NAME}" # 运行目录下的二进制文件名(统一命名) # 必需文件列表(包含所有架构二进制,但部署时仅复制匹配的) readonly REQUIRED_FILES=( "nas-media-player.py" "index.html" "zhinan.html" "releases/nas-media-player-armhf" "releases/nas-media-player-arm64" "releases/nas-media-player-x86_64" ) # 架构映射表(uname -m → 二进制后缀) declare -A ARCH_MAP=( ["armv7l"]="armhf" ["aarch64"]="arm64" ["x86_64"]="x86_64" ) # ============================================================================== # 颜色与样式定义(提升用户体验) # ============================================================================== readonly COLOR_RESET="\033[0m" readonly COLOR_RED="\033[31m" readonly COLOR_GREEN="\033[32m" readonly COLOR_YELLOW="\033[33m" readonly COLOR_BLUE="\033[34m" readonly COLOR_BOLD="\033[1m" # ============================================================================== # 全局变量(检测后赋值) # ============================================================================== OS_NAME="" # 系统发行版 DETECTED_ARCH="" # 检测到的系统架构 SOURCE_BIN_FILE="" # 源二进制文件(releases下的匹配文件) TARGET_BIN_FILE="" # 目标二进制文件(运行目录下) HAS_SYSTEMD="false" # 是否支持systemd # ============================================================================== # 日志与输出函数(统一输出格式) # ============================================================================== log_info() { echo -e "${COLOR_BLUE}[INFO]${COLOR_RESET} $*" } log_success() { echo -e "${COLOR_GREEN}[SUCCESS]${COLOR_RESET} $*" } log_warn() { echo -e "${COLOR_YELLOW}[WARN]${COLOR_RESET} $*" } log_error() { echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $*" >&2 } log_step() { echo -e "\n${COLOR_BOLD}[$1/$2] $3${COLOR_RESET}" } # ============================================================================== # 系统检测函数(核心:架构/发行版/systemd检测) # ============================================================================== detect_os_info() { log_step 1 7 "检测系统信息" # 1. 检测系统发行版 if [ -f /etc/os-release ]; then source /etc/os-release OS_NAME="${NAME} ${VERSION_ID}" elif [ -f /etc/lsb-release ]; then source /etc/lsb-release OS_NAME="${DISTRIB_ID} ${DISTRIB_RELEASE}" else OS_NAME="Unknown Linux" fi log_info "系统发行版:${OS_NAME}" # 2. 检测系统架构并匹配二进制文件 local raw_arch=$(uname -m) if [[ -v ARCH_MAP["${raw_arch}"] ]]; then DETECTED_ARCH="${ARCH_MAP["${raw_arch}"]}" SOURCE_BIN_FILE="releases/${SERVICE_NAME}-${DETECTED_ARCH}" # 源文件(脚本同目录) TARGET_BIN_FILE="${APP_DIR}/${BIN_NAME}" # 目标文件(运行目录) log_info "检测到架构:${raw_arch} → 匹配二进制:${SOURCE_BIN_FILE} → 部署到:${TARGET_BIN_FILE}" else log_error "不支持的架构:${raw_arch}(仅支持armhf/arm64/x86_64)" exit 1 fi # 3. 检测是否支持systemd if command -v systemctl >/dev/null 2>&1 && systemctl >/dev/null 2>&1; then HAS_SYSTEMD="true" log_info "系统支持systemd服务管理" else log_warn "系统不支持systemd,无法配置开机自启" fi } # ============================================================================== # 前置检查函数 # ============================================================================== check_root() { if [ "$(id -u)" -ne 0 ]; then log_error "请使用 root 用户执行(sudo -i 后运行)" exit 1 fi } check_network() { log_step 2 7 "检查网络连通性" if ! curl -s --connect-timeout 5 "${PIP_MIRROR}" >/dev/null; then log_warn "网络连接可能不稳定(不影响本地部署)" else log_success "网络连通性检查通过" fi } check_required_files() { log_step 3 7 "检查必需文件" local missing_files=() for file in "${REQUIRED_FILES[@]}"; do if [ ! -f "${file}" ]; then missing_files+=("${file}") fi done if [ ${#missing_files[@]} -gt 0 ]; then log_error "缺失必需文件:${missing_files[*]}" log_error "请确保所有必需文件与脚本同目录" exit 1 fi log_success "所有必需文件检查通过" } check_system_deps() { log_step 4 7 "检查系统依赖" # 检查必要命令 local required_cmds=("curl" "awk" "grep" "pkill" "pgrep") for cmd in "${required_cmds[@]}"; do if ! command -v "${cmd}" >/dev/null 2>&1; then log_error "缺失必需命令:${cmd}(请先安装)" exit 1 fi done log_success "系统依赖检查通过" } # ============================================================================== # 核心功能函数 # ============================================================================== create_app_dirs() { log_step 5 7 "创建应用目录" # 仅创建必要目录 mkdir -p "${APP_DIR}" \ "${APP_DIR}/static" \ "${VIDEO_DIR}" # 创建日志文件并设置权限(确保运行目录可写) touch "${LOG_FILE}" chmod 644 "${LOG_FILE}" chmod 755 "${APP_DIR}" # 确保运行目录有执行权限 log_success "应用目录创建完成:${APP_DIR}(日志文件:${LOG_FILE})" } deploy_app_files() { log_step 6 7 "部署程序文件" # 复制主程序文件 cp -f "nas-media-player.py" "${APP_DIR}/" || { log_error "复制主程序文件失败"; exit 1; } # 复制静态文件 cp -f "index.html" "zhinan.html" "${APP_DIR}/static/" || { log_error "复制静态文件失败"; exit 1; } # 仅复制匹配架构的二进制文件到运行目录(核心改动) cp -f "${SOURCE_BIN_FILE}" "${TARGET_BIN_FILE}" || { log_error "复制二进制文件 ${SOURCE_BIN_FILE} 失败"; exit 1; } # 给二进制文件加可执行权限(关键) chmod +x "${TARGET_BIN_FILE}" || { log_error "设置二进制文件可执行权限失败"; exit 1; } # 验证部署 if [ -f "${APP_DIR}/static/index.html" ] && [ -x "${TARGET_BIN_FILE}" ]; then log_success "程序文件部署完成:" log_success " - 主程序:${APP_DIR}/nas-media-player.py" log_success " - 静态文件:${APP_DIR}/static/" log_success " - 二进制文件:${TARGET_BIN_FILE}(${DETECTED_ARCH}架构)" log_success " - 日志文件:${LOG_FILE}" else log_error "文件部署失败!请检查 ${APP_DIR} 目录权限" exit 1 fi } create_systemd_service() { log_step 7 7 "配置系统服务(开机启动)" if [ "${HAS_SYSTEMD}" != "true" ]; then log_warn "跳过systemd服务配置(系统不支持)" return 0 fi # 写入服务文件(日志固定输出到运行目录,二进制路径为运行目录) cat > "${SYSTEMD_SERVICE}" </dev/null 2>&1 log_success "systemd服务配置完成(已启用开机启动)" else log_error "systemd服务文件创建失败" exit 1 fi } # 检查端口监听状态 check_port_listen() { local port=$1 if command -v ss >/dev/null 2>&1; then ss -tulpn 2>/dev/null | grep -q ":${port}.*${BIN_NAME}" elif command -v netstat >/dev/null 2>&1; then netstat -tulpn 2>/dev/null | grep -q ":${port}.*${BIN_NAME}" else return 1 fi } # 启动服务 start_service() { log_info "\n========== 启动服务 ==========" # 停止旧进程 log_info "清理旧进程..." pkill -f "${TARGET_BIN_FILE}" >/dev/null 2>&1 || true sleep 2 if [ "${HAS_SYSTEMD}" = "true" ]; then # systemd启动 systemctl start "${SERVICE_NAME}" # 等待服务启动 log_info "等待服务启动(最长 ${WAIT_TIMEOUT} 秒)..." local counter=0 while [ ${counter} -lt ${WAIT_TIMEOUT} ]; do if systemctl is-active --quiet "${SERVICE_NAME}"; then if check_port_listen "${PORT}"; then log_success "服务启动成功(端口${PORT}已监听)" return 0 else log_warn "服务已启动,但端口${PORT}未监听(嵌入式设备可能延迟,建议等待1分钟后重试)" return 0 fi fi counter=$((counter + 1)) sleep 1 done # 启动失败处理 log_error "服务启动失败!" log_info "错误日志(最后20行):" tail -n 20 "${LOG_FILE}" || log_warn "无法读取日志文件:${LOG_FILE}" log_info "请检查服务状态:systemctl status ${SERVICE_NAME}" exit 1 else # 非systemd启动(前台运行) log_warn "非systemd系统,将以前台方式启动服务(关闭终端则停止)" nohup "${TARGET_BIN_FILE}" > "${LOG_FILE}" 2>&1 & sleep 3 if check_port_listen "${PORT}"; then log_success "服务前台启动成功(端口${PORT}已监听)" log_info "日志文件:${LOG_FILE}" else log_error "服务启动失败!请查看日志:${LOG_FILE}" exit 1 fi fi } stop_service() { log_info "\n========== 停止服务 ==========" # systemd停止 if [ "${HAS_SYSTEMD}" = "true" ] && systemctl is-active --quiet "${SERVICE_NAME}"; then systemctl stop "${SERVICE_NAME}" sleep 2 fi # 强制清理残留进程 pkill -9 -f "${TARGET_BIN_FILE}" >/dev/null 2>&1 || true sleep 1 if ! pgrep -f "${TARGET_BIN_FILE}" >/dev/null; then log_success "服务已停止" else log_error "服务停止失败,请手动执行:pkill -9 -f '${TARGET_BIN_FILE}'" exit 1 fi } # 获取本地IP(优先非回环IPv4) get_local_ip() { local ip ip=$(hostname -I | awk '{print $1}' | grep -v '^127.' | grep -v '^::') || \ ip=$(ip addr show | grep 'inet ' | grep -v '127.0.0.1' | grep -v '::1' | head -n1 | awk '{print $2}' | cut -d'/' -f1) || \ ip="127.0.0.1" echo "${ip}" } # 安装完成总结 show_install_summary() { local ip=$(get_local_ip) echo -e "\n${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "${COLOR_GREEN}🎉 ${SERVICE_NAME} 安装成功!${COLOR_RESET}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "📍 访问地址:${COLOR_BLUE}http://${ip}:${PORT}${COLOR_RESET}" echo -e "📁 运行目录:${APP_DIR}" echo -e "🎬 媒体目录:${VIDEO_DIR}" echo -e "📜 日志文件:${LOG_FILE}(固定在运行目录)" echo -e "⚙️ 运行二进制:${TARGET_BIN_FILE}(${DETECTED_ARCH}架构)" echo -e "🔧 系统服务:${SERVICE_NAME}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "✨ 功能特性:" echo -e " ✅ 自动匹配系统架构部署二进制文件" echo -e " ✅ 支持子目录浏览和播放" echo -e " ✅ 支持视频文件上传(大小不限)" echo -e " ✅ 支持创建新目录/私密目录" echo -e " ✅ 丝滑的上传进度条显示" echo -e " ✅ 支持MP4/AVI/MKV/WEBM等主流格式" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "📋 常用命令:" echo -e " 启动服务:${0} start 或 systemctl start ${SERVICE_NAME}" echo -e " 停止服务:${0} stop 或 systemctl stop ${SERVICE_NAME}" echo -e " 重启服务:${0} restart 或 systemctl restart ${SERVICE_NAME}" echo -e " 查看状态:${0} status 或 systemctl status ${SERVICE_NAME}" echo -e " 查看日志:tail -f ${LOG_FILE}(推荐)或 journalctl -u ${SERVICE_NAME} -f" echo -e " 卸载服务:${0} uninstall" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" } # 卸载服务 uninstall_service() { echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "${COLOR_YELLOW}开始卸载 ${SERVICE_NAME} 服务${COLOR_RESET}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" # 停止并清理systemd服务 if [ "${HAS_SYSTEMD}" = "true" ] && [ -f "${SYSTEMD_SERVICE}" ]; then systemctl stop "${SERVICE_NAME}" >/dev/null 2>&1 || true systemctl disable "${SERVICE_NAME}" >/dev/null 2>&1 || true rm -f "${SYSTEMD_SERVICE}" systemctl daemon-reload log_success "systemd服务已清理" fi # 停止残留进程 stop_service >/dev/null 2>&1 || true # 删除程序目录(包含日志文件) log_info "删除运行目录(含日志文件)..." rm -rf "${APP_DIR}" && log_success "运行目录 ${APP_DIR} 已删除" # 保留媒体目录 log_warn "媒体目录 ${VIDEO_DIR} 已保留(包含您的媒体文件)" echo -e "\n${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "${COLOR_GREEN}✅ ${SERVICE_NAME} 服务卸载完成${COLOR_RESET}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" exit 0 } # 显示帮助信息 show_help() { echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "${COLOR_BLUE}${SERVICE_NAME} 安装管理脚本${COLOR_RESET}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "使用方法:${0} [命令]" echo -e "\n可用命令:" echo -e " install - 安装并启动服务(核心命令)" echo -e " start - 启动服务" echo -e " stop - 停止服务" echo -e " restart - 重启服务" echo -e " status - 查看服务状态" echo -e " uninstall - 卸载服务(保留媒体文件)" echo -e " help - 显示此帮助信息" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" exit 0 } # 显示服务状态 show_status() { echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "${COLOR_BLUE}${SERVICE_NAME} 服务状态${COLOR_RESET}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" echo -e "服务名称:${SERVICE_NAME}" echo -e "运行架构:${DETECTED_ARCH:-未检测}" echo -e "运行二进制:${TARGET_BIN_FILE:-未知}" if [ "${HAS_SYSTEMD}" = "true" ]; then echo -e "运行状态:$(systemctl is-active --quiet "${SERVICE_NAME}" && echo -e "${COLOR_GREEN}运行中${COLOR_RESET}" || echo -e "${COLOR_RED}已停止${COLOR_RESET}")" else echo -e "运行状态:$(pgrep -f "${TARGET_BIN_FILE}" >/dev/null && echo -e "${COLOR_GREEN}运行中${COLOR_RESET}" || echo -e "${COLOR_RED}已停止${COLOR_RESET}")" fi echo -e "监听端口:$(check_port_listen "${PORT}" && echo -e "${COLOR_GREEN}${PORT}(已监听)${COLOR_RESET}" || echo -e "${COLOR_RED}${PORT}(未监听)${COLOR_RESET}")" echo -e "运行目录:${APP_DIR} ($([ -d "${APP_DIR}" ] && echo -e "${COLOR_GREEN}存在${COLOR_RESET}" || echo -e "${COLOR_RED}不存在${COLOR_RESET}"))" echo -e "媒体目录:${VIDEO_DIR} ($([ -d "${VIDEO_DIR}" ] && echo -e "${COLOR_GREEN}存在${COLOR_RESET}" || echo -e "${COLOR_RED}不存在${COLOR_RESET}"))" echo -e "日志文件:${LOG_FILE} ($([ -f "${LOG_FILE}" ] && echo -e "${COLOR_GREEN}存在${COLOR_RESET}" || echo -e "${COLOR_RED}不存在${COLOR_RESET}"))" echo -e "系统发行版:${OS_NAME:-未知}" echo -e "${COLOR_BOLD}========================================${COLOR_RESET}" } # ============================================================================== # 主函数(处理命令参数) # ============================================================================== main() { # 无参数时显示帮助 if [ $# -eq 0 ]; then show_help fi local cmd="$1" case "${cmd}" in install) check_root detect_os_info check_network check_required_files check_system_deps create_app_dirs deploy_app_files if [ "${HAS_SYSTEMD}" = "true" ]; then create_systemd_service fi start_service show_install_summary ;; start) check_root detect_os_info start_service ;; stop) check_root detect_os_info stop_service ;; restart) check_root detect_os_info stop_service start_service ;; status) detect_os_info show_status ;; uninstall) check_root detect_os_info uninstall_service ;; help) show_help ;; *) log_error "无效命令:${cmd}(使用 ${0} help 查看帮助)" exit 1 ;; esac } # ============================================================================== # 执行入口 # ============================================================================== main "$@"