Files
nas-media-player/install.sh
2026-04-19 05:07:07 +08:00

513 lines
19 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
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}" <<EOF
[Unit]
Description=Lightweight NAS Media Player Service
Documentation=https://github.com/teasiu/nas-media-player
After=network.target network-online.target local-fs.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=${APP_DIR}
ExecStart=${TARGET_BIN_FILE}
ExecReload=/bin/kill -HUP \$MAINPID
Restart=always
RestartSec=5
TimeoutStartSec=30
TimeoutStopSec=10
LimitNOFILE=65535
StandardOutput=append:${LOG_FILE}
StandardError=append:${LOG_FILE}
[Install]
WantedBy=multi-user.target
EOF
# 验证并启用服务
if [ -f "${SYSTEMD_SERVICE}" ]; then
systemctl daemon-reload
systemctl enable "${SERVICE_NAME}" >/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 "$@"