feat: 端口改为 1219,install.sh 支持 systemd 后台部署,增加目录非 git 仓库时自动重建
This commit is contained in:
10
README.md
10
README.md
@@ -11,7 +11,7 @@ QZMusic 网页版,基于 Vue 3 + TypeScript + Vite 构建的音乐播放器,
|
|||||||
- 📊 音频可视化(基于 Web Audio API)
|
- 📊 音频可视化(基于 Web Audio API)
|
||||||
- 🔍 搜索功能
|
- 🔍 搜索功能
|
||||||
- 🔌 **插件系统** - 支持通过插件获取音乐资源
|
- 🔌 **插件系统** - 支持通过插件获取音乐资源
|
||||||
- 🌐 默认端口:10096
|
- 🌐 默认端口:1219
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ bash uninstall.sh
|
|||||||
|
|
||||||
卸载将删除:
|
卸载将删除:
|
||||||
- 安装目录(`/opt/QZMusic-Web`)
|
- 安装目录(`/opt/QZMusic-Web`)
|
||||||
- 端口 10096 上的所有进程
|
- 端口 1219 上的所有进程
|
||||||
- Systemd 服务(如有)
|
- Systemd 服务(如有)
|
||||||
- npm 全局包(如有)
|
- npm 全局包(如有)
|
||||||
- 相关缓存文件
|
- 相关缓存文件
|
||||||
@@ -81,7 +81,7 @@ npm install
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
服务器将在 http://localhost:10096 启动
|
服务器将在 http://localhost:1219 启动
|
||||||
|
|
||||||
### 构建生产版本
|
### 构建生产版本
|
||||||
|
|
||||||
@@ -101,9 +101,9 @@ npm run preview
|
|||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `npm run dev` | 启动开发服务器(端口 10096) |
|
| `npm run dev` | 启动开发服务器(端口 1219) |
|
||||||
| `npm run build` | 构建生产版本 |
|
| `npm run build` | 构建生产版本 |
|
||||||
| `npm run preview` | 预览生产构建(端口 10096) |
|
| `npm run preview` | 预览生产构建(端口 1219) |
|
||||||
| `npm run deploy` | 一键部署(安装依赖 + 构建) |
|
| `npm run deploy` | 一键部署(安装依赖 + 构建) |
|
||||||
| `npm run start` | 一键启动(自动安装依赖 + 启动) |
|
| `npm run start` | 一键启动(自动安装依赖 + 启动) |
|
||||||
| `npm run install-app` | 一键安装(从仓库安装到 /opt) |
|
| `npm run install-app` | 一键安装(从仓库安装到 /opt) |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# QZMusic-Web 一键部署脚本
|
# QZMusic-Web 一键部署脚本
|
||||||
# 端口:10096
|
# 端口:1219
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " QZMusic-Web 一键部署"
|
echo " QZMusic-Web 一键部署"
|
||||||
@@ -59,5 +59,5 @@ echo "🚀 启动方式:"
|
|||||||
echo " 开发模式: npm run dev"
|
echo " 开发模式: npm run dev"
|
||||||
echo " 预览模式: npm run preview"
|
echo " 预览模式: npm run preview"
|
||||||
echo ""
|
echo ""
|
||||||
echo "🌐 访问地址: http://localhost:10096"
|
echo "🌐 访问地址: http://localhost:1219"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
218
install.sh
218
install.sh
@@ -1,138 +1,206 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# QZMusic-Web 一键安装脚本
|
# QZMusic-Web 一键安装脚本
|
||||||
# 从Gitea仓库获取并自动部署
|
# 从 Gitea 仓库获取并自动部署,安装后以 systemd 服务对外暴露 1219 端口
|
||||||
# 使用方法: bash <(curl -sL http://171.80.3.149:4321/miao-moe/QZMusic-Web/raw/branch/master/install.sh)
|
# 使用方法:
|
||||||
# 或者直接运行此脚本
|
# bash <(curl -sL http://171.80.3.149:4321/miao-moe/QZMusic-Web/raw/branch/master/install.sh)
|
||||||
|
# 或直接在项目目录运行: ./install.sh
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " QZMusic-Web 一键安装"
|
echo " QZMusic-Web 一键安装 (systemd)"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 配置
|
# ========= 配置 =========
|
||||||
REPO_URL="http://171.80.3.149:4321/miao-moe/QZMusic-Web.git"
|
REPO_URL="http://171.80.3.149:4321/miao-moe/QZMusic-Web.git"
|
||||||
INSTALL_DIR="/opt/QZMusic-Web"
|
INSTALL_DIR="/opt/QZMusic-Web"
|
||||||
PORT=10096
|
PORT=1219
|
||||||
|
SERVICE_NAME="qzmusic-web"
|
||||||
|
BRANCH="master"
|
||||||
|
|
||||||
# 颜色定义
|
# ========= 颜色 =========
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m'
|
||||||
|
|
||||||
log_info() {
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||||
echo -e "${GREEN}[INFO]${NC} $1"
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
}
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
log_warn() {
|
# ========= 权限 =========
|
||||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
SUDO=""
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查是否以root运行
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
log_warn "建议使用 root 权限运行以获得最佳体验"
|
SUDO="sudo"
|
||||||
|
log_warn "当前非 root,将使用 sudo 执行系统级操作。"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查Node.js
|
# ========= Node.js 检查 =========
|
||||||
if ! command -v node &> /dev/null; then
|
if ! command -v node &> /dev/null; then
|
||||||
log_error "Node.js 未安装!请先安装 Node.js"
|
log_error "Node.js 未安装!请先安装 Node.js (推荐 v18+)"
|
||||||
echo "安装 Node.js:"
|
echo " Ubuntu/Debian: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs"
|
||||||
echo " Ubuntu/Debian: sudo apt-get install nodejs npm"
|
echo " CentOS/RHEL: curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - && sudo yum install -y nodejs"
|
||||||
echo " CentOS/RHEL: sudo yum install nodejs npm"
|
|
||||||
echo " 或访问: https://nodejs.org/"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Node.js 版本: $(node -v)"
|
log_info "Node.js 版本: $(node -v)"
|
||||||
log_info "npm 版本: $(npm -v)"
|
log_info "npm 版本: $(npm -v)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 检查是否已存在安装目录
|
# ========= 克隆/拉取代码 =========
|
||||||
if [ -d "$INSTALL_DIR" ]; then
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
log_warn "检测到已存在的安装目录: $INSTALL_DIR"
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||||
read -p "是否更新到最新版本?(y/n): " update_choice
|
log_info "检测到已有 git 仓库,正在更新到最新版本..."
|
||||||
if [ "$update_choice" != "y" ] && [ "$update_choice" != "Y" ]; then
|
cd "$INSTALL_DIR"
|
||||||
log_info "取消安装"
|
git fetch origin "$BRANCH"
|
||||||
exit 0
|
git reset --hard "origin/$BRANCH"
|
||||||
fi
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "更新失败!"
|
||||||
log_info "正在更新到最新版本..."
|
exit 1
|
||||||
cd "$INSTALL_DIR"
|
fi
|
||||||
git pull origin master
|
else
|
||||||
if [ $? -ne 0 ]; then
|
log_warn "目录 $INSTALL_DIR 已存在但不是 git 仓库,将清理后重新克隆..."
|
||||||
log_error "更新失败!"
|
$SUDO rm -rf "$INSTALL_DIR"
|
||||||
exit 1
|
log_info "正在从仓库获取代码 ($REPO_URL)..."
|
||||||
|
$SUDO git clone -b "$BRANCH" "$REPO_URL" "$INSTALL_DIR"
|
||||||
|
$SUDO chown -R "$(whoami)" "$INSTALL_DIR" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# 创建安装目录
|
|
||||||
log_info "正在创建安装目录: $INSTALL_DIR"
|
log_info "正在创建安装目录: $INSTALL_DIR"
|
||||||
sudo mkdir -p "$INSTALL_DIR"
|
$SUDO mkdir -p "$(dirname "$INSTALL_DIR")"
|
||||||
|
log_info "正在从仓库获取代码 ($REPO_URL)..."
|
||||||
# 克隆仓库
|
$SUDO git clone -b "$BRANCH" "$REPO_URL" "$INSTALL_DIR"
|
||||||
log_info "正在从仓库获取代码..."
|
$SUDO chown -R "$(whoami)" "$INSTALL_DIR" 2>/dev/null || true
|
||||||
if ! git clone "$REPO_URL" "$INSTALL_DIR"; then
|
|
||||||
log_error "克隆仓库失败!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 进入安装目录
|
# ========= 安装依赖 =========
|
||||||
cd "$INSTALL_DIR"
|
log_info "正在安装依赖 (使用淘宝镜像加速)..."
|
||||||
|
npm install --registry=https://registry.npmmirror.com --loglevel=error
|
||||||
# 安装依赖
|
|
||||||
log_info "正在安装依赖..."
|
|
||||||
npm install --registry=https://registry.npmmirror.com
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
log_error "依赖安装失败!"
|
log_error "依赖安装失败!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "依赖安装成功!"
|
log_info "依赖安装成功!"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 构建项目
|
# ========= 构建 =========
|
||||||
log_info "正在构建项目..."
|
log_info "正在构建项目..."
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
log_error "构建失败!"
|
log_error "构建失败!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "项目构建成功!"
|
log_info "项目构建成功!"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 检查端口占用
|
# ========= 部署 systemd 服务 =========
|
||||||
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
|
log_info "正在部署 systemd 服务: $SERVICE_NAME"
|
||||||
log_warn "端口 $PORT 已被占用,尝试停止现有进程..."
|
|
||||||
PID=$(lsof -ti:$PORT)
|
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"
|
||||||
kill $PID 2>/dev/null || true
|
|
||||||
sleep 2
|
# 停止旧的监听(以防之前有进程占端口)
|
||||||
|
if command -v lsof &> /dev/null; then
|
||||||
|
OLD_PID=$(lsof -ti:$PORT 2>/dev/null || true)
|
||||||
|
if [ -n "$OLD_PID" ]; then
|
||||||
|
log_warn "端口 $PORT 已被占用 (PID: $OLD_PID),正在清理..."
|
||||||
|
$SUDO kill $OLD_PID 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 如果之前有同名服务,先停掉
|
||||||
|
if systemctl list-unit-files 2>/dev/null | grep -q "$SERVICE_NAME.service"; then
|
||||||
|
$SUDO systemctl stop "$SERVICE_NAME" 2>/dev/null || true
|
||||||
|
$SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 写入服务文件
|
||||||
|
$SUDO tee "$SERVICE_FILE" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=QZMusic Web Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=$INSTALL_DIR
|
||||||
|
ExecStart=$(command -v node) $INSTALL_DIR/server.cjs
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=QZMUSIC_PORT=$PORT
|
||||||
|
Environment=QZMUSIC_HOST=0.0.0.0
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 重载 & 启动
|
||||||
|
$SUDO systemctl daemon-reload
|
||||||
|
$SUDO systemctl enable "$SERVICE_NAME"
|
||||||
|
$SUDO systemctl restart "$SERVICE_NAME"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# ========= 防火墙开放 =========
|
||||||
|
log_info "正在开放防火墙端口 $PORT..."
|
||||||
|
if command -v firewall-cmd &> /dev/null && systemctl is-active --quiet firewalld 2>/dev/null; then
|
||||||
|
$SUDO firewall-cmd --permanent --add-port=${PORT}/tcp >/dev/null
|
||||||
|
$SUDO firewall-cmd --reload >/dev/null
|
||||||
|
log_info "firewalld: 已放行 $PORT/tcp"
|
||||||
|
elif command -v ufw &> /dev/null && systemctl is-active --quiet ufw 2>/dev/null; then
|
||||||
|
$SUDO ufw allow ${PORT}/tcp >/dev/null
|
||||||
|
log_info "ufw: 已放行 $PORT/tcp"
|
||||||
|
else
|
||||||
|
log_warn "未检测到活动的 firewalld/ufw,跳过防火墙配置。如有其他防火墙请手动放行 $PORT/tcp。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========= 云服务器安全组提醒 =========
|
||||||
|
log_info "已处理系统防火墙。如果你的机器是云服务器,请记得在云厂商控制台的『安全组』也放行 $PORT/tcp。"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ========= 验证 =========
|
||||||
|
log_info "等待服务启动..."
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$PORT/" 2>/dev/null | grep -q "200"; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$PORT/" 2>/dev/null || echo "000")
|
||||||
|
STATUS=$($SUDO systemctl is-active "$SERVICE_NAME" 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
# 获取本机公网/局域网 IP
|
||||||
|
LAN_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
WAN_IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null || curl -s --max-time 5 https://api.ipify.org 2>/dev/null || echo "")
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " 🎉 安装完成!"
|
echo " 🎉 安装完成!"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "📂 安装目录: $INSTALL_DIR"
|
echo "📂 安装目录: $INSTALL_DIR"
|
||||||
echo "🌐 访问地址: http://localhost:$PORT"
|
echo "🖥 服务状态: $STATUS"
|
||||||
echo ""
|
echo "🌐 本地访问: http://127.0.0.1:$PORT (HTTP $HTTP_CODE)"
|
||||||
echo "启动服务:"
|
if [ -n "$LAN_IP" ]; then
|
||||||
echo " 开发模式: cd $INSTALL_DIR && npm run dev"
|
echo "🏠 局域网: http://$LAN_IP:$PORT"
|
||||||
echo " 生产模式: cd $INSTALL_DIR && npm run preview"
|
fi
|
||||||
|
if [ -n "$WAN_IP" ]; then
|
||||||
|
echo "🌍 公网: http://$WAN_IP:$PORT"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo "管理命令:"
|
echo "管理命令:"
|
||||||
echo " 更新: cd $INSTALL_DIR && git pull"
|
echo " 查看日志: sudo journalctl -u $SERVICE_NAME -f --since \"5 min ago\""
|
||||||
echo " 卸载: cd $INSTALL_DIR && npm run uninstall"
|
echo " 启动服务: sudo systemctl start $SERVICE_NAME"
|
||||||
|
echo " 停止服务: sudo systemctl stop $SERVICE_NAME"
|
||||||
|
echo " 重启服务: sudo systemctl restart $SERVICE_NAME"
|
||||||
|
echo " 开机自启: sudo systemctl enable $SERVICE_NAME"
|
||||||
|
echo " 更新代码: cd $INSTALL_DIR && git pull && npm run build && sudo systemctl restart $SERVICE_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "卸载/清除所有部署: bash $INSTALL_DIR/uninstall.sh"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc && vite build",
|
"build": "vue-tsc && vite build",
|
||||||
"preview": "vite preview --port 10096 --host",
|
"preview": "vite preview --port 1219 --host",
|
||||||
"serve": "node server.cjs",
|
"serve": "node server.cjs",
|
||||||
"deploy": "./deploy.sh",
|
"deploy": "./deploy.sh",
|
||||||
"start": "./start.sh",
|
"start": "./start.sh",
|
||||||
|
|||||||
18
qzmusic-web.service
Normal file
18
qzmusic-web.service
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=QZMusic Web Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/QZMusic-Web
|
||||||
|
ExecStart=/usr/bin/env node server.cjs
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=QZMUSIC_PORT=1219
|
||||||
|
Environment=QZMUSIC_HOST=0.0.0.0
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
138
server.cjs
138
server.cjs
@@ -1,69 +1,119 @@
|
|||||||
/**
|
/**
|
||||||
* QZMusic Web 静态文件服务器
|
* QZMusic Web 静态文件服务器(生产用)
|
||||||
* 外网访问支持
|
* 监听 0.0.0.0:1219,支持公网访问
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
const PORT = 10096;
|
const PORT = parseInt(process.env.QZMUSIC_PORT || '1219', 10);
|
||||||
|
const HOST = process.env.QZMUSIC_HOST || '0.0.0.0';
|
||||||
const ROOT = path.join(__dirname, 'dist');
|
const ROOT = path.join(__dirname, 'dist');
|
||||||
|
|
||||||
const mimeTypes = {
|
const mimeTypes = {
|
||||||
'.html': 'text/html',
|
'.html': 'text/html; charset=utf-8',
|
||||||
'.js': 'text/javascript',
|
'.htm': 'text/html; charset=utf-8',
|
||||||
'.css': 'text/css',
|
'.js': 'application/javascript; charset=utf-8',
|
||||||
'.json': 'application/json',
|
'.mjs': 'application/javascript; charset=utf-8',
|
||||||
'.png': 'image/png',
|
'.css': 'text/css; charset=utf-8',
|
||||||
'.jpg': 'image/jpg',
|
'.json': 'application/json; charset=utf-8',
|
||||||
'.gif': 'image/gif',
|
'.png': 'image/png',
|
||||||
'.svg': 'image/svg+xml',
|
'.jpg': 'image/jpeg',
|
||||||
'.ico': 'image/x-icon',
|
'.jpeg': 'image/jpeg',
|
||||||
'.ttf': 'font/ttf',
|
'.gif': 'image/gif',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.ttf': 'font/ttf',
|
||||||
'.woff': 'font/woff',
|
'.woff': 'font/woff',
|
||||||
'.woff2': 'font/woff2'
|
'.woff2':'font/woff2',
|
||||||
|
'.map': 'application/json; charset=utf-8',
|
||||||
|
'.txt': 'text/plain; charset=utf-8'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function log(...args) {
|
||||||
|
const ts = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||||
|
console.log(`[${ts}]`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
let filePath = path.join(ROOT, req.url === '/' ? 'index.html' : req.url);
|
const parsed = url.parse(req.url);
|
||||||
|
let pathname = decodeURIComponent(parsed.pathname || '/');
|
||||||
|
if (pathname === '/') pathname = '/index.html';
|
||||||
|
|
||||||
|
let filePath = path.join(ROOT, pathname);
|
||||||
|
|
||||||
|
// 防止路径穿越
|
||||||
|
if (!filePath.startsWith(ROOT)) {
|
||||||
|
res.writeHead(403);
|
||||||
|
res.end('Forbidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const extname = String(path.extname(filePath)).toLowerCase();
|
const extname = String(path.extname(filePath)).toLowerCase();
|
||||||
const contentType = mimeTypes[extname] || 'application/octet-stream';
|
const contentType = mimeTypes[extname] || 'application/octet-stream';
|
||||||
|
|
||||||
if (!extname && !fs.existsSync(filePath)) {
|
// SPA 路由:如果不是带扩展名的文件且文件不存在,则回退到 index.html
|
||||||
filePath = path.join(ROOT, 'index.html');
|
const tryStatic = (cb) => {
|
||||||
}
|
fs.stat(filePath, (err, stat) => {
|
||||||
|
if (!err && stat.isDirectory()) {
|
||||||
fs.readFile(filePath, (error, content) => {
|
filePath = path.join(filePath, 'index.html');
|
||||||
if (error) {
|
return fs.stat(filePath, (e2, s2) => {
|
||||||
if (error.code === 'ENOENT') {
|
if (!e2 && s2.isFile()) return cb(true);
|
||||||
fs.readFile(path.join(ROOT, 'index.html'), (err, html) => {
|
return cb(false);
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
||||||
res.end(html, 'utf-8');
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
res.writeHead(500);
|
|
||||||
res.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n');
|
|
||||||
}
|
}
|
||||||
} else {
|
if (!err && stat.isFile()) return cb(true);
|
||||||
res.writeHead(200, { 'Content-Type': contentType });
|
return cb(false);
|
||||||
res.end(content, 'utf-8');
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
tryStatic((found) => {
|
||||||
|
if (!found) {
|
||||||
|
filePath = path.join(ROOT, 'index.html');
|
||||||
}
|
}
|
||||||
|
fs.readFile(filePath, (error, content) => {
|
||||||
|
if (error) {
|
||||||
|
log(`${req.method} ${req.url} -> 500 (${error.code})`);
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('Server Error: ' + error.code);
|
||||||
|
} else {
|
||||||
|
const finalType = found ? contentType : 'text/html; charset=utf-8';
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': finalType,
|
||||||
|
'Cache-Control': extname === '.html' || extname === '.htm'
|
||||||
|
? 'no-cache, no-store, must-revalidate'
|
||||||
|
: 'public, max-age=3600'
|
||||||
|
});
|
||||||
|
res.end(content);
|
||||||
|
log(`${req.method} ${req.url} -> 200 (${pathname})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(PORT, '0.0.0.0', () => {
|
server.listen(PORT, HOST, () => {
|
||||||
console.log(`
|
const banner = `
|
||||||
╔═══════════════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ ║
|
║ ║
|
||||||
║ QZMusic Web Server 启动成功! ║
|
║ QZMusic Web Server 启动成功! ║
|
||||||
║ ║
|
║ ║
|
||||||
║ 本地访问:http://localhost:${PORT} ║
|
║ 监听地址: http://${HOST}:${PORT} ║
|
||||||
║ 局域网访问:http://[你的IP]:${PORT} ║
|
║ 本地访问: http://localhost:${PORT} ║
|
||||||
║ ║
|
║ 公网访问: http://[你的公网IP]:${PORT} ║
|
||||||
║ 外网访问:请配置端口转发/公网IP访问 ║
|
║ ║
|
||||||
║ ║
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
╚═══════════════════════════════════════════════════════════════════════╝
|
`;
|
||||||
`);
|
console.log(banner);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
log('收到 SIGINT,正在关闭服务器...');
|
||||||
|
server.close(() => process.exit(0));
|
||||||
|
});
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
log('收到 SIGTERM,正在关闭服务器...');
|
||||||
|
server.close(() => process.exit(0));
|
||||||
});
|
});
|
||||||
|
|||||||
6
start.sh
6
start.sh
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# QZMusic-Web 启动脚本
|
# QZMusic-Web 启动脚本
|
||||||
# 端口:10096
|
# 端口:1219
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " 启动 QZMusic-Web"
|
echo " 启动 QZMusic-Web"
|
||||||
@@ -28,7 +28,7 @@ case ${choice:-2} in
|
|||||||
1)
|
1)
|
||||||
echo ""
|
echo ""
|
||||||
echo "🚀 正在启动开发服务器..."
|
echo "🚀 正在启动开发服务器..."
|
||||||
echo "🌐 访问地址: http://localhost:10096"
|
echo "🌐 访问地址: http://localhost:1219"
|
||||||
echo ""
|
echo ""
|
||||||
echo "按 Ctrl+C 停止服务器"
|
echo "按 Ctrl+C 停止服务器"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -46,7 +46,7 @@ case ${choice:-2} in
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo "🚀 正在启动生产服务器..."
|
echo "🚀 正在启动生产服务器..."
|
||||||
echo "🌐 访问地址: http://[你的IP]:10096"
|
echo "🌐 访问地址: http://[你的IP]:1219"
|
||||||
echo ""
|
echo ""
|
||||||
echo "按 Ctrl+C 停止服务器"
|
echo "按 Ctrl+C 停止服务器"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
125
uninstall.sh
125
uninstall.sh
@@ -1,109 +1,114 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# QZMusic-Web 卸载脚本
|
# QZMusic-Web 卸载脚本 - 完全停止并删除所有 QZMusic-Web 部署
|
||||||
# 完全删除所有QZMusic部署文件和配置
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " QZMusic-Web 卸载工具"
|
echo " QZMusic-Web 完全卸载"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 配置
|
# ========= 配置 =========
|
||||||
INSTALL_DIR="/opt/QZMusic-Web"
|
INSTALL_DIR="/opt/QZMusic-Web"
|
||||||
PORT=10096
|
PORT=1219
|
||||||
SERVICE_NAME="qzmusic-web"
|
SERVICE_NAME="qzmusic-web"
|
||||||
|
|
||||||
# 颜色定义
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
|
|
||||||
log_info() {
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||||
echo -e "${GREEN}[INFO]${NC} $1"
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
}
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
log_warn() {
|
SUDO=""
|
||||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
if [ "$EUID" -ne 0 ]; then
|
||||||
}
|
SUDO="sudo"
|
||||||
|
fi
|
||||||
|
|
||||||
log_error() {
|
# ========= 确认 =========
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 确认卸载
|
|
||||||
echo "⚠️ 即将卸载 QZMusic-Web"
|
echo "⚠️ 即将卸载 QZMusic-Web"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "确定要删除所有相关文件吗?(yes/no): " confirm
|
echo "将执行以下操作:"
|
||||||
|
echo " - 停止并禁用 systemd 服务 $SERVICE_NAME"
|
||||||
|
echo " - 删除服务文件 /etc/systemd/system/$SERVICE_NAME.service"
|
||||||
|
echo " - 杀掉占用 $PORT 端口的进程"
|
||||||
|
echo " - 删除目录 $INSTALL_DIR"
|
||||||
|
echo " - 关闭防火墙 $PORT/tcp 放行"
|
||||||
|
echo ""
|
||||||
|
read -p "确认删除?输入 yes 继续: " confirm
|
||||||
if [ "$confirm" != "yes" ]; then
|
if [ "$confirm" != "yes" ]; then
|
||||||
log_info "取消卸载"
|
log_info "取消卸载"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 停止服务
|
# ========= 停止服务 =========
|
||||||
log_info "正在停止服务..."
|
log_info "停止 systemd 服务..."
|
||||||
|
if systemctl list-unit-files 2>/dev/null | grep -q "$SERVICE_NAME.service"; then
|
||||||
# 停止开发服务器
|
$SUDO systemctl stop "$SERVICE_NAME" 2>/dev/null || true
|
||||||
PID=$(lsof -ti:$PORT 2>/dev/null || true)
|
$SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true
|
||||||
if [ -n "$PID" ]; then
|
log_info "服务已停止 & 禁用"
|
||||||
log_info "停止进程 (PID: $PID)..."
|
else
|
||||||
kill $PID 2>/dev/null || true
|
log_warn "未检测到 systemd 服务 $SERVICE_NAME,跳过"
|
||||||
sleep 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 停止Systemd服务(如果存在)
|
# ========= 删除服务文件 =========
|
||||||
if systemctl is-active --quiet $SERVICE_NAME 2>/dev/null; then
|
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"
|
||||||
log_info "停止 Systemd 服务..."
|
if [ -f "$SERVICE_FILE" ]; then
|
||||||
sudo systemctl stop $SERVICE_NAME
|
$SUDO rm -f "$SERVICE_FILE"
|
||||||
sudo systemctl disable $SERVICE_NAME
|
$SUDO systemctl daemon-reload
|
||||||
|
log_info "已删除服务文件: $SERVICE_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 删除Systemd服务文件
|
# ========= 杀端口 =========
|
||||||
if [ -f "/etc/systemd/system/$SERVICE_NAME.service" ]; then
|
if command -v lsof &> /dev/null; then
|
||||||
log_info "删除 Systemd 服务文件..."
|
OLD_PID=$(lsof -ti:$PORT 2>/dev/null || true)
|
||||||
sudo rm -f /etc/systemd/system/$SERVICE_NAME.service
|
if [ -n "$OLD_PID" ]; then
|
||||||
sudo systemctl daemon-reload
|
log_info "杀掉占用 $PORT 的进程: $OLD_PID"
|
||||||
|
$SUDO kill $OLD_PID 2>/dev/null || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
# ========= 删除目录 =========
|
||||||
|
|
||||||
# 删除安装目录
|
|
||||||
if [ -d "$INSTALL_DIR" ]; then
|
if [ -d "$INSTALL_DIR" ]; then
|
||||||
log_info "删除安装目录: $INSTALL_DIR"
|
log_info "删除目录: $INSTALL_DIR"
|
||||||
sudo rm -rf "$INSTALL_DIR"
|
$SUDO rm -rf "$INSTALL_DIR"
|
||||||
|
else
|
||||||
|
log_warn "目录不存在: $INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 删除npm全局链接(如果有)
|
# ========= 关闭防火墙端口 =========
|
||||||
if npm list -g --depth=0 2>/dev/null | grep -q "qzmusic-web"; then
|
log_info "关闭防火墙 $PORT/tcp 放行..."
|
||||||
log_info "删除 npm 全局包..."
|
if command -v firewall-cmd &> /dev/null && systemctl is-active --quiet firewalld 2>/dev/null; then
|
||||||
sudo npm uninstall -g qzmusic-web 2>/dev/null || true
|
$SUDO firewall-cmd --permanent --remove-port=${PORT}/tcp >/dev/null 2>&1 || true
|
||||||
|
$SUDO firewall-cmd --reload >/dev/null
|
||||||
|
log_info "firewalld: 已关闭 $PORT/tcp"
|
||||||
|
elif command -v ufw &> /dev/null && systemctl is-active --quiet ufw 2>/dev/null; then
|
||||||
|
$SUDO ufw delete allow ${PORT}/tcp >/dev/null 2>&1 || true
|
||||||
|
log_info "ufw: 已关闭 $PORT/tcp"
|
||||||
|
else
|
||||||
|
log_warn "未检测到活动的 firewalld/ufw,跳过防火墙配置。"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 删除相关缓存
|
# ========= 清理缓存 =========
|
||||||
log_info "清理缓存..."
|
log_info "清理 npm 缓存..."
|
||||||
|
|
||||||
# 删除npm缓存
|
|
||||||
if [ -d "$HOME/.npm/_cacache" ]; then
|
if [ -d "$HOME/.npm/_cacache" ]; then
|
||||||
npm cache clean --force 2>/dev/null || true
|
npm cache clean --force >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
$SUDO rm -rf /tmp/QZMusic* /var/tmp/QZMusic* 2>/dev/null || true
|
||||||
# 删除可能的临时文件
|
|
||||||
sudo rm -rf /tmp/QZMusic* 2>/dev/null || true
|
|
||||||
sudo rm -rf /var/tmp/QZMusic* 2>/dev/null || true
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " ✅ 卸载完成!"
|
echo " ✅ 卸载完成!"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "已删除:"
|
echo "已清理:"
|
||||||
echo " - 安装目录: $INSTALL_DIR"
|
echo " - 目录: $INSTALL_DIR"
|
||||||
echo " - 服务端口: $PORT"
|
echo " - systemd 服务: $SERVICE_NAME"
|
||||||
|
echo " - 端口: $PORT"
|
||||||
echo " - 相关缓存"
|
echo " - 相关缓存"
|
||||||
echo ""
|
echo ""
|
||||||
echo "如需重新安装,请运行:"
|
echo "如需重新安装,请运行:"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 10096,
|
port: 1219,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
open: false
|
open: false
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user