Compare commits
11 Commits
fcec19117d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b0685c879b | |||
| 3f313283df | |||
| 0aa51cc932 | |||
| 0f183b61a4 | |||
| 900cc5fa09 | |||
| 89948f76b7 | |||
| a376a9e0f3 | |||
| 5d7eef29e8 | |||
| d0b6e37ae5 | |||
| b6191990da | |||
| cf106cab58 |
@@ -152,6 +152,9 @@ export default function Downloads() {
|
|||||||
{building ? '构建中...' : '构建选中平台'}
|
{building ? '构建中...' : '构建选中平台'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-gray-500 mb-4">
|
||||||
|
当前需在本地或 CI 中构建客户端,将产物(如 FunMC-版本号-windows-x64.exe)放入服务器 <code className="bg-gray-100 px-1 rounded">downloads</code> 目录(由环境变量 DOWNLOADS_DIR 指定,默认 /opt/funmc/downloads)后,下载页与下表才会提供下载。
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
{Object.entries(platformInfo).map(([key, info]) => (
|
{Object.entries(platformInfo).map(([key, info]) => (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware'
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
token: string | null
|
token: string | null
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export class ApiClient {
|
|||||||
|
|
||||||
constructor(baseUrl: string) {
|
constructor(baseUrl: string) {
|
||||||
this.http = axios.create({
|
this.http = axios.create({
|
||||||
baseURL: baseUrl.replace(/\/$/, '') + '/api',
|
baseURL: baseUrl.replace(/\/$/, '') + '/api/v1',
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
185
docs/BUILD-CLIENT.md
Normal file
185
docs/BUILD-CLIENT.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
# 各平台客户端构建并放入下载目录
|
||||||
|
|
||||||
|
下载页提供的文件名由服务端配置 `CLIENT_VERSION` 决定(默认 `0.1.0`),格式为:
|
||||||
|
|
||||||
|
- `FunMC-<版本>-windows-x64.exe`
|
||||||
|
- `FunMC-<版本>-macos-arm64.dmg` / `FunMC-<版本>-macos-x64.dmg`
|
||||||
|
- `FunMC-<版本>-linux-x64.AppImage`
|
||||||
|
- `FunMC-<版本>-android.apk`
|
||||||
|
|
||||||
|
构建产物需**复制到服务器的下载目录**并**按上述文件名命名**,下载页才会显示「下载」按钮。
|
||||||
|
服务器下载目录:`/opt/funmc/downloads`(或环境变量 `DOWNLOADS_DIR`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 版本号一致
|
||||||
|
|
||||||
|
构建前请确认与服务器一致:
|
||||||
|
|
||||||
|
- 查看服务器:`grep CLIENT_VERSION /etc/funmc/server.env`(例如 `0.1.0`)
|
||||||
|
- 下文中的 `VERSION` 请替换为该版本号。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Linux(可在服务器本机执行)
|
||||||
|
|
||||||
|
**在服务器或任意 Linux 机器上:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/funmc/src/client # 或你的项目 client 目录
|
||||||
|
npm install --registry https://registry.npmjs.org/
|
||||||
|
npm run dist:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
产物在 `client/release/` 下,例如:
|
||||||
|
|
||||||
|
- `FunConnect-1.1.0-Linux-x64.AppImage`
|
||||||
|
|
||||||
|
复制并重命名为下载页期望的文件名后放入下载目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VERSION=0.1.0 # 与 CLIENT_VERSION 一致
|
||||||
|
cp release/FunConnect-*-Linux-x64.AppImage /opt/funmc/downloads/FunMC-${VERSION}-linux-x64.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Windows
|
||||||
|
|
||||||
|
**在 Windows 本机:**
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
cd client
|
||||||
|
npm install
|
||||||
|
npm run dist:win
|
||||||
|
```
|
||||||
|
|
||||||
|
产物在 `client\release\`,例如:
|
||||||
|
|
||||||
|
- `FunConnect-1.1.0-Win-x64.exe`(或带 nsis 的安装包)
|
||||||
|
|
||||||
|
上传到服务器后重命名并放入下载目录(在服务器上执行,或本机重命名后上传):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在服务器上(假设已上传为 FunConnect-1.1.0-Win-x64.exe)
|
||||||
|
VERSION=0.1.0
|
||||||
|
mv /path/to/FunConnect-1.1.0-Win-x64.exe /opt/funmc/downloads/FunMC-${VERSION}-windows-x64.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
或用 SCP 从本机直接放到服务器并命名(PowerShell 示例):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scp client\release\FunConnect-1.1.0-Win-x64.exe root@你的服务器IP:/opt/funmc/downloads/FunMC-0.1.0-windows-x64.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. macOS
|
||||||
|
|
||||||
|
**在 Mac 本机:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd client
|
||||||
|
npm install
|
||||||
|
npm run dist:mac
|
||||||
|
```
|
||||||
|
|
||||||
|
产物在 `client/release/`,例如:
|
||||||
|
|
||||||
|
- Apple Silicon: `FunConnect-1.1.0-Mac-arm64.dmg`
|
||||||
|
- Intel: `FunConnect-1.1.0-Mac-x64.dmg`
|
||||||
|
|
||||||
|
上传到服务器并重命名(在服务器上):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VERSION=0.1.0
|
||||||
|
mv /path/to/FunConnect-1.1.0-Mac-arm64.dmg /opt/funmc/downloads/FunMC-${VERSION}-macos-arm64.dmg
|
||||||
|
mv /path/to/FunConnect-1.1.0-Mac-x64.dmg /opt/funmc/downloads/FunMC-${VERSION}-macos-x64.dmg
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Android(mobile/,Expo + React Native)
|
||||||
|
|
||||||
|
Android 客户端在 **`mobile/`** 目录,使用 **Expo** 构建。任选其一即可。
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- **方式一(EAS 云端)**:Expo 账号([expo.dev](https://expo.dev) 注册)
|
||||||
|
- **方式二(本地)**:Android Studio + Android SDK,并配置好 `ANDROID_HOME`
|
||||||
|
|
||||||
|
### 方式一:EAS Build(推荐,无需本机 Android 环境)
|
||||||
|
|
||||||
|
在项目根或 `mobile/` 下执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 安装 EAS CLI 并登录
|
||||||
|
npm install -g eas-cli
|
||||||
|
eas login
|
||||||
|
|
||||||
|
# 构建 APK(预览/内部分发,直接得到 .apk)
|
||||||
|
eas build --platform android --profile preview
|
||||||
|
```
|
||||||
|
|
||||||
|
构建完成后在 Expo 网页或邮件中下载 **APK**,上传到服务器后重命名并放入下载目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VERSION=0.1.0
|
||||||
|
cp /path/to/下载的.apk /opt/funmc/downloads/FunMC-${VERSION}-android.apk
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式二:本地构建(需 Android Studio + SDK)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 生成原生 android/ 目录
|
||||||
|
npx expo prebuild
|
||||||
|
|
||||||
|
# 构建 Release APK
|
||||||
|
cd android && ./gradlew assembleRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
APK 输出路径:
|
||||||
|
|
||||||
|
- `mobile/android/app/build/outputs/apk/release/app-release.apk`
|
||||||
|
|
||||||
|
复制到服务器下载目录并重命名:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VERSION=0.1.0
|
||||||
|
cp mobile/android/app/build/outputs/apk/release/app-release.apk /opt/funmc/downloads/FunMC-${VERSION}-android.apk
|
||||||
|
# 若在服务器上,可先 scp 上传再执行 cp/mv
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 一键复制脚本示例(在服务器上使用)
|
||||||
|
|
||||||
|
在服务器上,若已把各平台构建产物上传到某目录(或本机刚构建好 Linux 版),可统一复制并重命名:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 请先确认版本号与 /etc/funmc/server.env 中 CLIENT_VERSION 一致
|
||||||
|
VERSION=0.1.0
|
||||||
|
DOWNLOADS=/opt/funmc/downloads
|
||||||
|
|
||||||
|
# Linux(本机刚构建时)
|
||||||
|
cp -v /opt/funmc/src/client/release/FunConnect-*-Linux-x64.AppImage "$DOWNLOADS/FunMC-${VERSION}-linux-x64.AppImage" 2>/dev/null || true
|
||||||
|
|
||||||
|
# 若 Windows/macOS 已上传到 /opt/funmc/uploads/ 等目录,可类似:
|
||||||
|
# cp -v /opt/funmc/uploads/FunConnect-*-Win-x64.exe "$DOWNLOADS/FunMC-${VERSION}-windows-x64.exe" 2>/dev/null || true
|
||||||
|
# cp -v /opt/funmc/uploads/FunConnect-*-Mac-arm64.dmg "$DOWNLOADS/FunMC-${VERSION}-macos-arm64.dmg" 2>/dev/null || true
|
||||||
|
# cp -v /opt/funmc/uploads/FunConnect-*-Mac-x64.dmg "$DOWNLOADS/FunMC-${VERSION}-macos-x64.dmg" 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 验证
|
||||||
|
|
||||||
|
- 在服务器上:`ls -la /opt/funmc/downloads`
|
||||||
|
- 浏览器打开:`http://你的服务器:3000/download`,有文件的平台会显示「下载」按钮,没有的显示「暂无」。
|
||||||
101
install.sh
101
install.sh
@@ -1,7 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# FunMC 一键部署脚本
|
# FunMC 一键部署脚本
|
||||||
# 用法: curl -fsSL https://fc.funmc.cn/install.sh | bash
|
# 用法: bash install.sh [ -force ]
|
||||||
|
# - 无参数: 更新安装,保留数据库与现有配置(server.env / relay.env / credentials.txt)
|
||||||
|
# - -force: 强制覆盖安装,清空数据库并重写所有配置
|
||||||
#
|
#
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@@ -21,6 +23,15 @@ CONFIG_DIR="/etc/funmc"
|
|||||||
DATA_DIR="/var/lib/funmc"
|
DATA_DIR="/var/lib/funmc"
|
||||||
LOG_DIR="/var/log/funmc"
|
LOG_DIR="/var/log/funmc"
|
||||||
|
|
||||||
|
# 先解析参数(必须在显示运行模式前执行)
|
||||||
|
FORCE_INSTALL=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ "$arg" = "-force" ] || [ "$arg" = "--force" ]; then
|
||||||
|
FORCE_INSTALL=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
echo -e "${CYAN}"
|
echo -e "${CYAN}"
|
||||||
echo "╔═══════════════════════════════════════════════════════════╗"
|
echo "╔═══════════════════════════════════════════════════════════╗"
|
||||||
echo "║ ║"
|
echo "║ ║"
|
||||||
@@ -30,6 +41,12 @@ echo "║ 魔幻方开发 ║"
|
|||||||
echo "║ ║"
|
echo "║ ║"
|
||||||
echo "╚═══════════════════════════════════════════════════════════╝"
|
echo "╚═══════════════════════════════════════════════════════════╝"
|
||||||
echo -e "${NC}"
|
echo -e "${NC}"
|
||||||
|
if [ "$FORCE_INSTALL" -eq 1 ]; then
|
||||||
|
echo -e "${YELLOW}运行模式: 强制覆盖安装(将清空数据库并重写配置)${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}运行模式: 更新安装(保留数据库与现有配置)${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
# 检查 root 权限
|
# 检查 root 权限
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
@@ -99,39 +116,45 @@ install_nodejs() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 配置数据库
|
# 配置数据库(-force 时强制删除并重建,否则仅确保库存在且不覆盖数据)
|
||||||
setup_database() {
|
setup_database() {
|
||||||
echo -e "${YELLOW}[4/7] 配置数据库...${NC}"
|
echo -e "${YELLOW}[4/7] 配置数据库...${NC}"
|
||||||
|
|
||||||
systemctl enable postgresql
|
systemctl enable postgresql
|
||||||
systemctl start postgresql
|
systemctl start postgresql
|
||||||
|
|
||||||
# 生成随机密码
|
if [ "$FORCE_INSTALL" -eq 1 ]; then
|
||||||
DB_PASSWORD=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 24)
|
# 强制断开对 funmc 库的所有连接,再删除库和用户
|
||||||
|
sudo -u postgres psql -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'funmc' AND pid <> pg_backend_pid();" 2>/dev/null || true
|
||||||
|
sudo -u postgres psql -d postgres -c "DROP DATABASE IF EXISTS funmc;" 2>/dev/null || true
|
||||||
|
sudo -u postgres psql -d postgres -c "DROP USER IF EXISTS funmc;" 2>/dev/null || true
|
||||||
|
sudo -u postgres psql -d postgres -c "CREATE USER funmc WITH PASSWORD '12345678';"
|
||||||
|
sudo -u postgres psql -d postgres -c "CREATE DATABASE funmc OWNER funmc;"
|
||||||
|
sudo -u postgres psql -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE funmc TO funmc;"
|
||||||
|
echo -e "${GREEN}✓ 数据库已强制重建(密码 12345678)${NC}"
|
||||||
|
else
|
||||||
|
# 更新模式:不删除,仅确保用户和库存在(若已存在则跳过)
|
||||||
|
sudo -u postgres psql -d postgres -c "CREATE USER funmc WITH PASSWORD '12345678';" 2>/dev/null || true
|
||||||
|
sudo -u postgres psql -d postgres -c "CREATE DATABASE funmc OWNER funmc;" 2>/dev/null || true
|
||||||
|
sudo -u postgres psql -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE funmc TO funmc;" 2>/dev/null || true
|
||||||
|
echo -e "${GREEN}✓ 数据库检查完成(未覆盖现有数据)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
# 创建数据库和用户
|
# 配置 pg_hba.conf(仅追加缺失项)
|
||||||
sudo -u postgres psql -c "CREATE USER funmc WITH PASSWORD '$DB_PASSWORD';" 2>/dev/null || true
|
|
||||||
sudo -u postgres psql -c "CREATE DATABASE funmc OWNER funmc;" 2>/dev/null || true
|
|
||||||
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE funmc TO funmc;"
|
|
||||||
|
|
||||||
# 配置 pg_hba.conf
|
|
||||||
PG_HBA=$(sudo -u postgres psql -t -c "SHOW hba_file;" | xargs)
|
PG_HBA=$(sudo -u postgres psql -t -c "SHOW hba_file;" | xargs)
|
||||||
if ! grep -q "funmc" "$PG_HBA"; then
|
if ! grep -q "funmc" "$PG_HBA"; then
|
||||||
echo "local funmc funmc md5" >> "$PG_HBA"
|
echo "local funmc funmc md5" >> "$PG_HBA"
|
||||||
echo "host funmc funmc 127.0.0.1/32 md5" >> "$PG_HBA"
|
echo "host funmc funmc 127.0.0.1/32 md5" >> "$PG_HBA"
|
||||||
systemctl reload postgresql
|
systemctl reload postgresql
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}✓ 数据库配置完成${NC}"
|
|
||||||
echo "$DB_PASSWORD" > /tmp/funmc_db_password
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 下载并编译 FunMC
|
# 下载并编译 FunMC
|
||||||
build_funmc() {
|
build_funmc() {
|
||||||
echo -e "${YELLOW}[5/7] 编译 FunMC...${NC}"
|
echo -e "${YELLOW}[5/7] 编译 FunMC...${NC}"
|
||||||
|
|
||||||
# 创建目录
|
# 创建目录(含客户端下载目录)
|
||||||
mkdir -p $INSTALL_DIR $CONFIG_DIR $DATA_DIR $LOG_DIR
|
mkdir -p $INSTALL_DIR $INSTALL_DIR/downloads $CONFIG_DIR $DATA_DIR $LOG_DIR
|
||||||
|
|
||||||
# 克隆或更新代码
|
# 克隆或更新代码
|
||||||
if [ -d "$INSTALL_DIR/src" ]; then
|
if [ -d "$INSTALL_DIR/src" ]; then
|
||||||
@@ -146,31 +169,32 @@ build_funmc() {
|
|||||||
source $HOME/.cargo/env
|
source $HOME/.cargo/env
|
||||||
cargo build --release -p funmc-server -p funmc-relay-server
|
cargo build --release -p funmc-server -p funmc-relay-server
|
||||||
|
|
||||||
# 复制二进制文件
|
# 复制二进制文件(包名与二进制名不同:server / relay-server)
|
||||||
cp target/release/funmc-server $INSTALL_DIR/
|
cp target/release/server $INSTALL_DIR/funmc-server
|
||||||
cp target/release/funmc-relay-server $INSTALL_DIR/
|
cp target/release/relay-server $INSTALL_DIR/funmc-relay-server
|
||||||
|
|
||||||
# 编译管理面板前端
|
# 编译管理面板前端(使用官方 registry 避免镜像返回 HTML 导致 FETCH_ERROR)
|
||||||
cd $INSTALL_DIR/src/admin-panel
|
cd $INSTALL_DIR/src/admin-panel
|
||||||
npm install
|
npm install --registry https://registry.npmjs.org/
|
||||||
npm run build
|
npm run build
|
||||||
cp -r dist $INSTALL_DIR/admin-panel
|
cp -r dist $INSTALL_DIR/admin-panel
|
||||||
|
|
||||||
echo -e "${GREEN}✓ FunMC 编译完成${NC}"
|
echo -e "${GREEN}✓ FunMC 编译完成${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 配置服务
|
# 配置服务(-force 时重写配置;否则若已有配置则保留仅做迁移与重启)
|
||||||
configure_services() {
|
configure_services() {
|
||||||
echo -e "${YELLOW}[6/7] 配置服务...${NC}"
|
echo -e "${YELLOW}[6/7] 配置服务...${NC}"
|
||||||
|
|
||||||
DB_PASSWORD=$(cat /tmp/funmc_db_password)
|
WROTE_CONFIG=0
|
||||||
|
if [ "$FORCE_INSTALL" -eq 1 ] || [ ! -f "$CONFIG_DIR/server.env" ]; then
|
||||||
|
# 强制安装或首次安装:生成新配置
|
||||||
|
WROTE_CONFIG=1
|
||||||
|
DB_PASSWORD="12345678"
|
||||||
JWT_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 48)
|
JWT_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 48)
|
||||||
ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 12)
|
ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 12)
|
||||||
|
|
||||||
# 获取服务器 IP
|
|
||||||
SERVER_IP=$(curl -s ifconfig.me || curl -s ipinfo.io/ip || hostname -I | awk '{print $1}')
|
SERVER_IP=$(curl -s ifconfig.me || curl -s ipinfo.io/ip || hostname -I | awk '{print $1}')
|
||||||
|
|
||||||
# 创建主配置文件
|
|
||||||
cat > $CONFIG_DIR/server.env << EOF
|
cat > $CONFIG_DIR/server.env << EOF
|
||||||
# FunMC 服务端配置
|
# FunMC 服务端配置
|
||||||
DATABASE_URL=postgres://funmc:${DB_PASSWORD}@localhost/funmc
|
DATABASE_URL=postgres://funmc:${DB_PASSWORD}@localhost/funmc
|
||||||
@@ -192,16 +216,24 @@ ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
|||||||
# 客户端下载
|
# 客户端下载
|
||||||
CLIENT_DOWNLOAD_ENABLED=true
|
CLIENT_DOWNLOAD_ENABLED=true
|
||||||
CLIENT_VERSION=${FUNMC_VERSION}
|
CLIENT_VERSION=${FUNMC_VERSION}
|
||||||
|
DOWNLOADS_DIR=$INSTALL_DIR/downloads
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# 创建中继配置
|
|
||||||
cat > $CONFIG_DIR/relay.env << EOF
|
cat > $CONFIG_DIR/relay.env << EOF
|
||||||
RELAY_PORT=7900
|
RELAY_PORT=7900
|
||||||
JWT_SECRET=${JWT_SECRET}
|
JWT_SECRET=${JWT_SECRET}
|
||||||
RUST_LOG=info
|
RUST_LOG=info
|
||||||
EOF
|
EOF
|
||||||
|
else
|
||||||
|
# 更新模式:保留现有配置,仅确保 DB_PASSWORD 等变量存在供后续迁移使用
|
||||||
|
DB_PASSWORD=$(grep DATABASE_URL "$CONFIG_DIR/server.env" 2>/dev/null | sed -n 's/.*:\/\/funmc:\([^@]*\)@.*/\1/p')
|
||||||
|
if [ -z "$DB_PASSWORD" ]; then
|
||||||
|
DB_PASSWORD="12345678"
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}✓ 保留现有配置(未覆盖 server.env / relay.env)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
# 创建 systemd 服务文件
|
# 创建 systemd 服务文件(始终更新以便安装路径等变更生效)
|
||||||
cat > /etc/systemd/system/funmc-server.service << EOF
|
cat > /etc/systemd/system/funmc-server.service << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=FunMC API Server
|
Description=FunMC API Server
|
||||||
@@ -214,7 +246,7 @@ WorkingDirectory=$INSTALL_DIR
|
|||||||
EnvironmentFile=$CONFIG_DIR/server.env
|
EnvironmentFile=$CONFIG_DIR/server.env
|
||||||
ExecStart=$INSTALL_DIR/funmc-server
|
ExecStart=$INSTALL_DIR/funmc-server
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=10
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
@@ -238,9 +270,14 @@ RestartSec=5
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# 运行数据库迁移
|
# 安装 sqlx-cli(若未安装)并运行数据库迁移
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
if ! command -v sqlx &> /dev/null; then
|
||||||
|
cargo install sqlx-cli --no-default-features --features postgres
|
||||||
|
fi
|
||||||
cd $INSTALL_DIR/src/server
|
cd $INSTALL_DIR/src/server
|
||||||
DATABASE_URL="postgres://funmc:${DB_PASSWORD}@localhost/funmc" cargo sqlx migrate run
|
export DATABASE_URL="postgres://funmc:${DB_PASSWORD}@localhost/funmc"
|
||||||
|
sqlx migrate run
|
||||||
|
|
||||||
# 启动服务
|
# 启动服务
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
@@ -249,7 +286,8 @@ EOF
|
|||||||
|
|
||||||
echo -e "${GREEN}✓ 服务配置完成${NC}"
|
echo -e "${GREEN}✓ 服务配置完成${NC}"
|
||||||
|
|
||||||
# 保存凭据
|
# 仅强制/首次安装时写入凭据文件,更新模式不覆盖
|
||||||
|
if [ "$WROTE_CONFIG" -eq 1 ]; then
|
||||||
cat > $CONFIG_DIR/credentials.txt << EOF
|
cat > $CONFIG_DIR/credentials.txt << EOF
|
||||||
======================================
|
======================================
|
||||||
FunMC 服务端安装信息
|
FunMC 服务端安装信息
|
||||||
@@ -270,6 +308,7 @@ JWT 密钥: ${JWT_SECRET}
|
|||||||
======================================
|
======================================
|
||||||
EOF
|
EOF
|
||||||
chmod 600 $CONFIG_DIR/credentials.txt
|
chmod 600 $CONFIG_DIR/credentials.txt
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 配置防火墙
|
# 配置防火墙
|
||||||
|
|||||||
61
scripts/copy-client-to-downloads.sh
Normal file
61
scripts/copy-client-to-downloads.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 将 client/release 下的构建产物复制到服务器下载目录并命名为 FunMC-<版本>-<平台>.<后缀>
|
||||||
|
# 用法:
|
||||||
|
# bash scripts/copy-client-to-downloads.sh [版本号]
|
||||||
|
# bash scripts/copy-client-to-downloads.sh # 版本号从 /etc/funmc/server.env 的 CLIENT_VERSION 读取
|
||||||
|
# 需在项目根目录或指定 CLIENT_DIR、DOWNLOADS_DIR 运行。
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
CLIENT_DIR="${CLIENT_DIR:-$REPO_ROOT/client}"
|
||||||
|
DOWNLOADS_DIR="${DOWNLOADS_DIR:-/opt/funmc/downloads}"
|
||||||
|
|
||||||
|
if [ -n "$1" ]; then
|
||||||
|
VERSION="$1"
|
||||||
|
elif [ -f /etc/funmc/server.env ]; then
|
||||||
|
VERSION=$(grep -E '^CLIENT_VERSION=' /etc/funmc/server.env | cut -d= -f2)
|
||||||
|
fi
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "用法: $0 <版本号> 或确保 /etc/funmc/server.env 中有 CLIENT_VERSION"
|
||||||
|
echo "示例: $0 0.1.0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RELEASE="$CLIENT_DIR/release"
|
||||||
|
if [ ! -d "$RELEASE" ]; then
|
||||||
|
echo "未找到 $RELEASE,请先在 client 目录执行 npm run dist:linux 等构建"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$DOWNLOADS_DIR"
|
||||||
|
|
||||||
|
# 复制并重命名(Electron 产物: FunConnect-<package.json版本>-<平台>-<arch>.<ext>)
|
||||||
|
copied=0
|
||||||
|
for f in "$RELEASE"/FunConnect-*-Linux-x64.AppImage; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cp -v "$f" "$DOWNLOADS_DIR/FunMC-${VERSION}-linux-x64.AppImage"
|
||||||
|
copied=$((copied+1))
|
||||||
|
done
|
||||||
|
for f in "$RELEASE"/FunConnect-*-Win-x64.exe; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cp -v "$f" "$DOWNLOADS_DIR/FunMC-${VERSION}-windows-x64.exe"
|
||||||
|
copied=$((copied+1))
|
||||||
|
done
|
||||||
|
for f in "$RELEASE"/FunConnect-*-Mac-arm64.dmg; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cp -v "$f" "$DOWNLOADS_DIR/FunMC-${VERSION}-macos-arm64.dmg"
|
||||||
|
copied=$((copied+1))
|
||||||
|
done
|
||||||
|
for f in "$RELEASE"/FunConnect-*-Mac-x64.dmg; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cp -v "$f" "$DOWNLOADS_DIR/FunMC-${VERSION}-macos-x64.dmg"
|
||||||
|
copied=$((copied+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $copied -eq 0 ]; then
|
||||||
|
echo "未在 $RELEASE 中找到可复制的 FunConnect-* 文件,请先构建客户端"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "已复制 $copied 个文件到 $DOWNLOADS_DIR(版本 $VERSION)"
|
||||||
@@ -32,6 +32,24 @@ pub struct ClientBuild {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列出 downloads 目录下可用的文件名(供下载页仅对存在的文件显示「下载」)
|
||||||
|
pub async fn list_download_files() -> Json<Vec<String>> {
|
||||||
|
let downloads_dir = std::env::var("DOWNLOADS_DIR").unwrap_or_else(|_| "./downloads".to_string());
|
||||||
|
let mut names = Vec::new();
|
||||||
|
if let Ok(mut rd) = tokio::fs::read_dir(&downloads_dir).await {
|
||||||
|
while let Ok(Some(entry)) = rd.next_entry().await {
|
||||||
|
if let Ok(meta) = entry.metadata().await {
|
||||||
|
if meta.is_file() {
|
||||||
|
if let Ok(name) = entry.file_name().into_string() {
|
||||||
|
names.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Json(names)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_client_config(State(state): State<Arc<AppState>>) -> Json<ClientConfig> {
|
pub async fn get_client_config(State(state): State<Arc<AppState>>) -> Json<ClientConfig> {
|
||||||
let config = state.server_config.read().unwrap();
|
let config = state.server_config.read().unwrap();
|
||||||
|
|
||||||
@@ -103,8 +121,8 @@ pub async fn download_page(State(state): State<Arc<AppState>>) -> Html<String> {
|
|||||||
<div class="text-5xl mb-4">🪟</div>
|
<div class="text-5xl mb-4">🪟</div>
|
||||||
<h3 class="text-xl font-semibold mb-2">Windows</h3>
|
<h3 class="text-xl font-semibold mb-2">Windows</h3>
|
||||||
<p class="text-gray-500 text-sm mb-4">Windows 10/11</p>
|
<p class="text-gray-500 text-sm mb-4">Windows 10/11</p>
|
||||||
<a href="{server_url}/api/v1/download/FunMC-{version}-windows-x64.exe"
|
<a href="{server_url}/api/v1/download/FunMC-{version}-windows-x64.exe" data-download-file="FunMC-{version}-windows-x64.exe"
|
||||||
class="inline-block w-full py-3 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
|
class="dl-link inline-block w-full py-3 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
|
||||||
下载 .exe
|
下载 .exe
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,12 +135,12 @@ pub async fn download_page(State(state): State<Arc<AppState>>) -> Html<String> {
|
|||||||
<h3 class="text-xl font-semibold mb-2">macOS</h3>
|
<h3 class="text-xl font-semibold mb-2">macOS</h3>
|
||||||
<p class="text-gray-500 text-sm mb-4">macOS 11+</p>
|
<p class="text-gray-500 text-sm mb-4">macOS 11+</p>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<a href="{server_url}/api/v1/download/FunMC-{version}-macos-arm64.dmg"
|
<a href="{server_url}/api/v1/download/FunMC-{version}-macos-arm64.dmg" data-download-file="FunMC-{version}-macos-arm64.dmg"
|
||||||
class="inline-block w-full py-3 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
|
class="dl-link inline-block w-full py-3 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
|
||||||
Apple Silicon
|
Apple Silicon
|
||||||
</a>
|
</a>
|
||||||
<a href="{server_url}/api/v1/download/FunMC-{version}-macos-x64.dmg"
|
<a href="{server_url}/api/v1/download/FunMC-{version}-macos-x64.dmg" data-download-file="FunMC-{version}-macos-x64.dmg"
|
||||||
class="inline-block w-full py-2 px-4 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-sm">
|
class="dl-link inline-block w-full py-2 px-4 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors text-sm">
|
||||||
Intel Mac
|
Intel Mac
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,8 +153,8 @@ pub async fn download_page(State(state): State<Arc<AppState>>) -> Html<String> {
|
|||||||
<div class="text-5xl mb-4">🐧</div>
|
<div class="text-5xl mb-4">🐧</div>
|
||||||
<h3 class="text-xl font-semibold mb-2">Linux</h3>
|
<h3 class="text-xl font-semibold mb-2">Linux</h3>
|
||||||
<p class="text-gray-500 text-sm mb-4">Ubuntu/Debian/Fedora</p>
|
<p class="text-gray-500 text-sm mb-4">Ubuntu/Debian/Fedora</p>
|
||||||
<a href="{server_url}/api/v1/download/FunMC-{version}-linux-x64.AppImage"
|
<a href="{server_url}/api/v1/download/FunMC-{version}-linux-x64.AppImage" data-download-file="FunMC-{version}-linux-x64.AppImage"
|
||||||
class="inline-block w-full py-3 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
|
class="dl-link inline-block w-full py-3 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium">
|
||||||
下载 AppImage
|
下载 AppImage
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,8 +169,8 @@ pub async fn download_page(State(state): State<Arc<AppState>>) -> Html<String> {
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-4xl mb-3">🤖</div>
|
<div class="text-4xl mb-3">🤖</div>
|
||||||
<h4 class="font-semibold mb-2">Android</h4>
|
<h4 class="font-semibold mb-2">Android</h4>
|
||||||
<a href="{server_url}/api/v1/download/FunMC-{version}-android.apk"
|
<a href="{server_url}/api/v1/download/FunMC-{version}-android.apk" data-download-file="FunMC-{version}-android.apk"
|
||||||
class="inline-block w-full py-2 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
|
class="dl-link inline-block w-full py-2 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
|
||||||
下载 APK
|
下载 APK
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,6 +204,20 @@ pub async fn download_page(State(state): State<Arc<AppState>>) -> Html<String> {
|
|||||||
魔幻方开发 · FunMC
|
魔幻方开发 · FunMC
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){{
|
||||||
|
var apiBase = window.location.origin + '/api/v1';
|
||||||
|
fetch(apiBase + '/download/list').then(function(r){{ return r.json(); }}).then(function(list){{
|
||||||
|
var set = new Set(list || []);
|
||||||
|
document.querySelectorAll('a.dl-link[data-download-file]').forEach(function(a){{
|
||||||
|
var name = a.getAttribute('data-download-file');
|
||||||
|
if (!set.has(name)) {{
|
||||||
|
a.outerHTML = '<span class="inline-block w-full py-3 px-4 bg-gray-300 text-gray-500 rounded-lg cursor-not-allowed">暂无</span>';
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
}}).catch(function(){{}});
|
||||||
|
}})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>"##,
|
</html>"##,
|
||||||
server_name = config.server_name,
|
server_name = config.server_name,
|
||||||
@@ -256,11 +288,31 @@ pub async fn list_builds() -> Json<Vec<ClientBuild>> {
|
|||||||
Json(builds)
|
Json(builds)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_file(Path(filename): Path<String>) -> Result<Response, StatusCode> {
|
pub async fn download_file(Path(filename): Path<String>) -> Result<Response, Response> {
|
||||||
let downloads_dir = std::env::var("DOWNLOADS_DIR").unwrap_or_else(|_| "./downloads".to_string());
|
let downloads_dir = std::env::var("DOWNLOADS_DIR").unwrap_or_else(|_| "./downloads".to_string());
|
||||||
let file_path = std::path::Path::new(&downloads_dir).join(&filename);
|
let file_path = std::path::Path::new(&downloads_dir).join(&filename);
|
||||||
|
|
||||||
let file = File::open(&file_path).await.map_err(|_| StatusCode::NOT_FOUND)?;
|
let file = match File::open(&file_path).await {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => {
|
||||||
|
let escaped = filename.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """);
|
||||||
|
let html = format!(r##"<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head><meta charset="UTF-8"><title>文件不存在</title></head>
|
||||||
|
<body style="font-family:sans-serif;max-width:560px;margin:80px auto;padding:20px;">
|
||||||
|
<h1>文件不存在</h1>
|
||||||
|
<p>未找到 <strong>{}</strong>。</p>
|
||||||
|
<p>可能原因:该版本尚未构建或未上传到服务器。请联系管理员,或将构建好的客户端放入服务器的 <code>downloads</code> 目录后重试。</p>
|
||||||
|
<p><a href="/download">返回下载页</a></p>
|
||||||
|
</body>
|
||||||
|
</html>"##, escaped);
|
||||||
|
return Err(Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
||||||
|
.body(Body::from(html))
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let stream = ReaderStream::new(file);
|
let stream = ReaderStream::new(file);
|
||||||
let body = Body::from_stream(stream);
|
let body = Body::from_stream(stream);
|
||||||
@@ -289,6 +341,7 @@ pub async fn download_file(Path(filename): Path<String>) -> Result<Response, Sta
|
|||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
pub struct TriggerBuildBody {
|
pub struct TriggerBuildBody {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub platforms: Vec<String>,
|
pub platforms: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub fn router(_state: Arc<AppState>) -> Router<Arc<AppState>> {
|
|||||||
.route("/admin/builds/trigger", post(download::trigger_build))
|
.route("/admin/builds/trigger", post(download::trigger_build))
|
||||||
// Download
|
// Download
|
||||||
.route("/client-config", get(download::get_client_config))
|
.route("/client-config", get(download::get_client_config))
|
||||||
|
.route("/download/list", get(download::list_download_files))
|
||||||
.route("/download/:filename", get(download::download_file))
|
.route("/download/:filename", get(download::download_file))
|
||||||
// WebSocket signaling
|
// WebSocket signaling
|
||||||
.route("/ws", get(ws_handler))
|
.route("/ws", get(ws_handler))
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use uuid::Uuid;
|
|||||||
use funmc_shared::protocol::SignalingMessage;
|
use funmc_shared::protocol::SignalingMessage;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct SignalingSession {
|
pub struct SignalingSession {
|
||||||
pub user_id: Uuid,
|
pub user_id: Uuid,
|
||||||
pub tx: mpsc::UnboundedSender<SignalingMessage>,
|
pub tx: mpsc::UnboundedSender<SignalingMessage>,
|
||||||
|
|||||||
Reference in New Issue
Block a user