feat: Enhance installation script and download functionality
- Add support for a force installation mode in the install script, allowing users to overwrite existing configurations and databases. - Improve database setup logic to ensure existing users and databases are only dropped during a force installation. - Introduce a new API endpoint to list available download files, enhancing the user experience on the download page by only displaying existing files. - Update HTML templates to reflect the availability of download files dynamically.
This commit is contained in:
90
install.sh
90
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
|
||||||
@@ -30,6 +32,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
|
||||||
@@ -38,6 +46,15 @@ if [ "$EUID" -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 解析参数:-force 或 --force 为强制覆盖安装,否则为更新(不覆盖数据)
|
||||||
|
FORCE_INSTALL=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ "$arg" = "-force" ] || [ "$arg" = "--force" ]; then
|
||||||
|
FORCE_INSTALL=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# 检测系统
|
# 检测系统
|
||||||
detect_os() {
|
detect_os() {
|
||||||
if [ -f /etc/os-release ]; then
|
if [ -f /etc/os-release ]; then
|
||||||
@@ -99,32 +116,37 @@ install_nodejs() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 配置数据库(固定密码 12345678,强制删除旧库与用户并重建)
|
# 配置数据库(-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
|
||||||
|
|
||||||
# 强制断开对 funmc 库的所有连接,再删除库和用户(避免 role already exists / 无法删除库)
|
if [ "$FORCE_INSTALL" -eq 1 ]; then
|
||||||
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
|
# 强制断开对 funmc 库的所有连接,再删除库和用户
|
||||||
sudo -u postgres psql -d postgres -c "DROP DATABASE IF EXISTS funmc;" 2>/dev/null || true
|
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 USER IF EXISTS funmc;" 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 -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;"
|
|
||||||
|
|
||||||
# 配置 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}✓ 数据库配置完成(密码 12345678,已强制覆盖旧数据)${NC}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 下载并编译 FunMC
|
# 下载并编译 FunMC
|
||||||
@@ -160,19 +182,20 @@ build_funmc() {
|
|||||||
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="12345678"
|
WROTE_CONFIG=0
|
||||||
JWT_SECRET=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 48)
|
if [ "$FORCE_INSTALL" -eq 1 ] || [ ! -f "$CONFIG_DIR/server.env" ]; then
|
||||||
ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 12)
|
# 强制安装或首次安装:生成新配置
|
||||||
|
WROTE_CONFIG=1
|
||||||
|
DB_PASSWORD="12345678"
|
||||||
|
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)
|
||||||
|
SERVER_IP=$(curl -s ifconfig.me || curl -s ipinfo.io/ip || hostname -I | awk '{print $1}')
|
||||||
|
|
||||||
# 获取服务器 IP
|
cat > $CONFIG_DIR/server.env << EOF
|
||||||
SERVER_IP=$(curl -s ifconfig.me || curl -s ipinfo.io/ip || hostname -I | awk '{print $1}')
|
|
||||||
|
|
||||||
# 创建主配置文件
|
|
||||||
cat > $CONFIG_DIR/server.env << EOF
|
|
||||||
# FunMC 服务端配置
|
# FunMC 服务端配置
|
||||||
DATABASE_URL=postgres://funmc:${DB_PASSWORD}@localhost/funmc
|
DATABASE_URL=postgres://funmc:${DB_PASSWORD}@localhost/funmc
|
||||||
JWT_SECRET=${JWT_SECRET}
|
JWT_SECRET=${JWT_SECRET}
|
||||||
@@ -196,14 +219,21 @@ CLIENT_VERSION=${FUNMC_VERSION}
|
|||||||
DOWNLOADS_DIR=$INSTALL_DIR/downloads
|
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
|
||||||
@@ -256,8 +286,9 @@ EOF
|
|||||||
|
|
||||||
echo -e "${GREEN}✓ 服务配置完成${NC}"
|
echo -e "${GREEN}✓ 服务配置完成${NC}"
|
||||||
|
|
||||||
# 保存凭据
|
# 仅强制/首次安装时写入凭据文件,更新模式不覆盖
|
||||||
cat > $CONFIG_DIR/credentials.txt << EOF
|
if [ "$WROTE_CONFIG" -eq 1 ]; then
|
||||||
|
cat > $CONFIG_DIR/credentials.txt << EOF
|
||||||
======================================
|
======================================
|
||||||
FunMC 服务端安装信息
|
FunMC 服务端安装信息
|
||||||
======================================
|
======================================
|
||||||
@@ -276,7 +307,8 @@ JWT 密钥: ${JWT_SECRET}
|
|||||||
请妥善保管此文件!
|
请妥善保管此文件!
|
||||||
======================================
|
======================================
|
||||||
EOF
|
EOF
|
||||||
chmod 600 $CONFIG_DIR/credentials.txt
|
chmod 600 $CONFIG_DIR/credentials.txt
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 配置防火墙
|
# 配置防火墙
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user