feat!: 移除内置公共服务器

- 移除公共 RustDesk ID 服务器 (用户需自行配置)
- 移除公共 TURN 服务器 (仅保留 Google STUN)
- 清理废弃代码: PublicServerInfo, is_using_public_server 等
- 更新前端 UI 和国际化文本
- 重新生成 TypeScript 类型

破坏性变更: 不再提供内置公共服务器。用户必须配置自己的
RustDesk 服务器和 TURN 服务器才能在生产环境中使用。
This commit is contained in:
mofeng-git
2026-01-08 16:53:19 +08:00
parent 9ab3d052f9
commit 3fa91772f0
20 changed files with 635 additions and 500 deletions

145
build.rs
View File

@@ -20,14 +20,13 @@ fn main() {
// Compile protobuf files for RustDesk protocol // Compile protobuf files for RustDesk protocol
compile_protos(); compile_protos();
// Generate secrets module from secrets.toml // Generate minimal secrets module
generate_secrets(); generate_secrets();
// Rerun if the script itself changes // Rerun if the script itself changes
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=protos/rendezvous.proto"); println!("cargo:rerun-if-changed=protos/rendezvous.proto");
println!("cargo:rerun-if-changed=protos/message.proto"); println!("cargo:rerun-if-changed=protos/message.proto");
println!("cargo:rerun-if-changed=secrets.toml");
} }
/// Compile protobuf files using protobuf-codegen (same as RustDesk server) /// Compile protobuf files using protobuf-codegen (same as RustDesk server)
@@ -52,123 +51,59 @@ pub mod message;
std::fs::write(protos_dir.join("mod.rs"), mod_content).unwrap(); std::fs::write(protos_dir.join("mod.rs"), mod_content).unwrap();
} }
/// Generate secrets module from secrets.toml /// Generate minimal secrets module with Google STUN server hardcoded
///
/// This reads the secrets.toml file and generates a Rust module with
/// compile-time constants for sensitive configuration values.
fn generate_secrets() { fn generate_secrets() {
let out_dir = std::env::var("OUT_DIR").unwrap(); let out_dir = std::env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("secrets_generated.rs"); let dest_path = Path::new(&out_dir).join("secrets_generated.rs");
// Default values if secrets.toml doesn't exist // Generate the secrets module - no public servers provided
let mut rustdesk_public_server = String::new(); let code = r#"// Auto-generated secrets module
let mut rustdesk_public_key = String::new(); // DO NOT EDIT - This file is generated by build.rs
let mut rustdesk_relay_key = String::new();
let mut ice_stun_server = String::new();
let mut ice_turn_urls = String::new();
let mut ice_turn_username = String::new();
let mut ice_turn_password = String::new();
// Try to read secrets.toml
if let Ok(content) = fs::read_to_string("secrets.toml") {
if let Ok(value) = content.parse::<toml::Value>() {
// RustDesk section
if let Some(rustdesk) = value.get("rustdesk") {
if let Some(v) = rustdesk.get("public_server").and_then(|v| v.as_str()) {
rustdesk_public_server = v.to_string();
}
if let Some(v) = rustdesk.get("public_key").and_then(|v| v.as_str()) {
rustdesk_public_key = v.to_string();
}
if let Some(v) = rustdesk.get("relay_key").and_then(|v| v.as_str()) {
rustdesk_relay_key = v.to_string();
}
}
// ICE section (for WebRTC)
if let Some(ice) = value.get("ice") {
if let Some(v) = ice.get("stun_server").and_then(|v| v.as_str()) {
ice_stun_server = v.to_string();
}
if let Some(v) = ice.get("turn_urls").and_then(|v| v.as_str()) {
ice_turn_urls = v.to_string();
}
if let Some(v) = ice.get("turn_username").and_then(|v| v.as_str()) {
ice_turn_username = v.to_string();
}
if let Some(v) = ice.get("turn_password").and_then(|v| v.as_str()) {
ice_turn_password = v.to_string();
}
}
} else {
println!("cargo:warning=Failed to parse secrets.toml");
}
} else {
println!("cargo:warning=secrets.toml not found, using empty defaults");
}
// Generate the secrets module
let code = format!(
r#"// Auto-generated secrets module
// DO NOT EDIT - This file is generated by build.rs from secrets.toml
/// RustDesk public server configuration
pub mod rustdesk {{
/// Public RustDesk ID server address (used when user leaves field empty)
pub const PUBLIC_SERVER: &str = "{}";
/// Public key for the RustDesk server (for client connection)
pub const PUBLIC_KEY: &str = "{}";
/// Relay server authentication key (licence_key for relay server)
pub const RELAY_KEY: &str = "{}";
/// Check if public server is configured
pub const fn has_public_server() -> bool {{
!PUBLIC_SERVER.is_empty()
}}
}}
/// ICE server configuration (for WebRTC NAT traversal) /// ICE server configuration (for WebRTC NAT traversal)
pub mod ice {{ pub mod ice {
/// Public STUN server URL /// Google public STUN server URL (hardcoded)
pub const STUN_SERVER: &str = "{}"; pub const STUN_SERVER: &str = "stun:stun.l.google.com:19302";
/// Public TURN server URLs (comma-separated) /// TURN server URLs - not provided, users must configure their own
pub const TURN_URLS: &str = "{}"; pub const TURN_URLS: &str = "";
/// TURN authentication username /// TURN authentication username
pub const TURN_USERNAME: &str = "{}"; pub const TURN_USERNAME: &str = "";
/// TURN authentication password /// TURN authentication password
pub const TURN_PASSWORD: &str = "{}"; pub const TURN_PASSWORD: &str = "";
/// Check if public ICE servers are configured /// Always returns true since we have STUN
pub const fn is_configured() -> bool {{ pub const fn is_configured() -> bool {
!STUN_SERVER.is_empty() || !TURN_URLS.is_empty() true
}}
/// Check if TURN servers are configured (requires credentials)
pub const fn has_turn() -> bool {{
!TURN_URLS.is_empty() && !TURN_USERNAME.is_empty() && !TURN_PASSWORD.is_empty()
}}
}}
"#,
escape_string(&rustdesk_public_server),
escape_string(&rustdesk_public_key),
escape_string(&rustdesk_relay_key),
escape_string(&ice_stun_server),
escape_string(&ice_turn_urls),
escape_string(&ice_turn_username),
escape_string(&ice_turn_password),
);
fs::write(&dest_path, code).expect("Failed to write secrets_generated.rs");
} }
/// Escape special characters in a string for use in Rust string literals /// Always returns false since TURN is not provided
fn escape_string(s: &str) -> String { pub const fn has_turn() -> bool {
s.replace('\\', "\\\\").replace('"', "\\\"") false
}
}
/// RustDesk public server configuration - NOT PROVIDED
pub mod rustdesk {
/// Public RustDesk ID server - NOT PROVIDED
pub const PUBLIC_SERVER: &str = "";
/// Public key for the RustDesk server - NOT PROVIDED
pub const PUBLIC_KEY: &str = "";
/// Relay server authentication key - NOT PROVIDED
pub const RELAY_KEY: &str = "";
/// Always returns false
pub const fn has_public_server() -> bool {
false
}
}
"#;
fs::write(&dest_path, code).expect("Failed to write secrets_generated.rs");
} }
/// Convert days since Unix epoch to year-month-day /// Convert days since Unix epoch to year-month-day

View File

@@ -8,40 +8,28 @@ FROM debian:12-slim
ARG TARGETPLATFORM ARG TARGETPLATFORM
# Install runtime dependencies and create directories # Install runtime dependencies in a single layer
RUN apt-get update && apt-get install -y --no-install-recommends \ # Static linked: FFmpeg core, libyuv, libvpx, libjpeg-turbo
# Runtime libraries # Dynamic linked: hardware acceleration drivers, GPL codecs (x264/x265)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
# Core runtime (all platforms)
libasound2 \ libasound2 \
libv4l-0 \ libv4l-0 \
libudev1 \ libudev1 \
zlib1g \
libjpeg62-turbo \
libyuv0 \
# FFmpeg runtime
libavcodec59 \
libavformat59 \
libavutil57 \
libswscale6 \
libswresample4 \
# Video codecs
libx264-164 \
libx265-199 \
libvpx7 \
# Audio codec
libopus0 \
# Hardware acceleration
libva2 \
libva-drm2 \
libdrm2 \ libdrm2 \
# X11 (for VAAPI) libopus0 \
libx11-6 \
libxcb1 \
# Utilities
ca-certificates \ ca-certificates \
# Intel Media SDK (x86_64 only, ignored on other archs) # GPL codecs (must be dynamic for license compliance)
$([ "$TARGETPLATFORM" = "linux/amd64" ] && echo "libmfx1") \ libx264-164 \
&& rm -rf /var/lib/apt/lists/* \ libx265-199 && \
&& mkdir -p /etc/one-kvm/ventoy # Platform-specific hardware acceleration
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
apt-get install -y --no-install-recommends \
libva2 libva-drm2 libva-x11-2 libx11-6 libxcb1 libmfx1; \
fi && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /etc/one-kvm/ventoy
# Copy init script # Copy init script
COPY --chmod=755 init.sh /init.sh COPY --chmod=755 init.sh /init.sh

View File

@@ -28,37 +28,83 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
git \ git \
libclang-dev \ libclang-dev \
llvm \ llvm \
protobuf-compiler \
mold \ mold \
meson \ meson \
ninja-build \ ninja-build \
wget \ wget \
file \
gcc-aarch64-linux-gnu \ gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \ g++-aarch64-linux-gnu \
libc6-dev-arm64-cross \ libc6-dev-arm64-cross \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install ARM64 development libraries (without system ffmpeg) # Install ARM64 development libraries (without VAAPI/X11 for ARM)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev:arm64 \
libasound2-dev:arm64 \ libasound2-dev:arm64 \
libv4l-dev:arm64 \ libv4l-dev:arm64 \
libudev-dev:arm64 \ libudev-dev:arm64 \
zlib1g-dev:arm64 \ zlib1g-dev:arm64 \
libjpeg62-turbo-dev:arm64 \ # Note: libjpeg-turbo, libyuv, libvpx are built from source below for static linking
libyuv-dev:arm64 \
libx264-dev:arm64 \ libx264-dev:arm64 \
libx265-dev:arm64 \ libx265-dev:arm64 \
libvpx-dev:arm64 \
libopus-dev:arm64 \ libopus-dev:arm64 \
libva-dev:arm64 \
libdrm-dev:arm64 \ libdrm-dev:arm64 \
libx11-dev:arm64 \
libxcb1-dev:arm64 \
libxau-dev:arm64 \
libxdmcp-dev:arm64 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Build static libjpeg-turbo from source (cross-compile for ARM64)
RUN git clone --depth 1 https://github.com/libjpeg-turbo/libjpeg-turbo /tmp/libjpeg-turbo \
&& cd /tmp/libjpeg-turbo \
&& cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
-DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/aarch64-linux-gnu \
-DENABLE_SHARED=OFF -DENABLE_STATIC=ON \
&& cmake --build build -j$(nproc) \
&& cmake --install build \
&& rm -rf /tmp/libjpeg-turbo
# Build static libyuv from source (cross-compile for ARM64)
RUN git clone --depth 1 https://github.com/lemenkov/libyuv /tmp/libyuv \
&& cd /tmp/libyuv \
&& mkdir build && cd build \
&& cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \
-DCMAKE_PREFIX_PATH=/opt/one-kvm-libs/aarch64-linux-gnu \
-DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/aarch64-linux-gnu \
&& make -j$(nproc) \
&& mkdir -p /opt/one-kvm-libs/aarch64-linux-gnu/lib \
&& cp libyuv.a /opt/one-kvm-libs/aarch64-linux-gnu/lib/ \
&& cp -r ../include /opt/one-kvm-libs/aarch64-linux-gnu/ \
&& rm -rf /tmp/libyuv
# Build static libvpx from source (cross-compile for ARM64)
# CC/CXX/LD/AR must be environment variables, not configure arguments
RUN git clone --depth 1 https://github.com/webmproject/libvpx /tmp/libvpx \
&& cd /tmp/libvpx \
&& echo "=== libvpx: Configuring for ARM64 ===" \
&& export CC=aarch64-linux-gnu-gcc \
&& export CXX=aarch64-linux-gnu-g++ \
&& export LD=aarch64-linux-gnu-ld \
&& export AR=aarch64-linux-gnu-ar \
&& export CROSS=aarch64-linux-gnu- \
&& ./configure --prefix=/opt/one-kvm-libs/aarch64-linux-gnu \
--target=arm64-linux-gcc \
--enable-static --disable-shared --enable-pic \
--disable-examples --disable-tools --disable-docs \
--disable-unit-tests \
&& echo "=== libvpx: Building ===" \
&& make -j$(nproc) \
&& echo "=== libvpx: Checking architecture ===" \
&& file libvpx.a \
&& make install \
&& echo "=== libvpx: Verifying installed library ===" \
&& file /opt/one-kvm-libs/aarch64-linux-gnu/lib/libvpx.a \
&& rm -rf /tmp/libvpx
# Download and build FFmpeg with RKMPP support # Download and build FFmpeg with RKMPP support
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \ && wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \
@@ -73,7 +119,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \ -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \
-DCMAKE_INSTALL_PREFIX=/usr/aarch64-linux-gnu \ -DCMAKE_INSTALL_PREFIX=/usr/aarch64-linux-gnu \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \ -DBUILD_SHARED_LIBS=OFF \
-DBUILD_TEST=OFF \ -DBUILD_TEST=OFF \
&& make -j$(nproc) \ && make -j$(nproc) \
&& make install \ && make install \
@@ -94,6 +140,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& meson setup build --prefix=/usr/aarch64-linux-gnu --libdir=lib \ && meson setup build --prefix=/usr/aarch64-linux-gnu --libdir=lib \
--cross-file=/tmp/aarch64-cross.txt \ --cross-file=/tmp/aarch64-cross.txt \
--buildtype=release \ --buildtype=release \
--default-library=static \
-Dcpp_args=-fpermissive \ -Dcpp_args=-fpermissive \
-Dlibdrm=false \ -Dlibdrm=false \
-Dlibrga_demo=false \ -Dlibrga_demo=false \
@@ -102,7 +149,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& cd .. \ && cd .. \
# Create pkg-config wrapper for cross-compilation # Create pkg-config wrapper for cross-compilation
&& echo '#!/bin/sh' > /tmp/aarch64-pkg-config \ && echo '#!/bin/sh' > /tmp/aarch64-pkg-config \
&& echo 'export PKG_CONFIG_LIBDIR=/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig' >> /tmp/aarch64-pkg-config \ && echo 'export PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/aarch64-linux-gnu/lib/pkgconfig:/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig' >> /tmp/aarch64-pkg-config \
&& echo 'export PKG_CONFIG_PATH=""' >> /tmp/aarch64-pkg-config \ && echo 'export PKG_CONFIG_PATH=""' >> /tmp/aarch64-pkg-config \
&& echo 'export PKG_CONFIG_SYSROOT_DIR=""' >> /tmp/aarch64-pkg-config \ && echo 'export PKG_CONFIG_SYSROOT_DIR=""' >> /tmp/aarch64-pkg-config \
&& echo 'exec pkg-config "$@"' >> /tmp/aarch64-pkg-config \ && echo 'exec pkg-config "$@"' >> /tmp/aarch64-pkg-config \
@@ -110,7 +157,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
# Build FFmpeg with RKMPP (minimal build for encoding only) # Build FFmpeg with RKMPP (minimal build for encoding only)
&& cd ffmpeg-rockchip \ && cd ffmpeg-rockchip \
&& ./configure \ && ./configure \
--prefix=/usr/aarch64-linux-gnu \ --prefix=/opt/one-kvm-libs/aarch64-linux-gnu \
--cross-prefix=aarch64-linux-gnu- \ --cross-prefix=aarch64-linux-gnu- \
--arch=aarch64 \ --arch=aarch64 \
--target-os=linux \ --target-os=linux \
@@ -118,13 +165,14 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
--pkg-config=/tmp/aarch64-pkg-config \ --pkg-config=/tmp/aarch64-pkg-config \
--enable-gpl \ --enable-gpl \
--enable-version3 \ --enable-version3 \
--enable-shared \ --disable-shared \
--disable-static \ --enable-static \
# Hardware acceleration --enable-pic \
# Hardware acceleration (ARM: RKMPP + V4L2, no VAAPI)
--enable-libdrm \ --enable-libdrm \
--enable-rkmpp \ --enable-rkmpp \
--enable-rkrga \ --enable-rkrga \
--enable-vaapi \ --disable-vaapi \
--enable-v4l2-m2m \ --enable-v4l2-m2m \
# Software encoding libraries # Software encoding libraries
--enable-libx264 \ --enable-libx264 \
@@ -151,10 +199,6 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
--disable-decoders \ --disable-decoders \
# Disable all encoders, enable only needed ones # Disable all encoders, enable only needed ones
--disable-encoders \ --disable-encoders \
--enable-encoder=h264_vaapi \
--enable-encoder=hevc_vaapi \
--enable-encoder=vp8_vaapi \
--enable-encoder=vp9_vaapi \
--enable-encoder=h264_rkmpp \ --enable-encoder=h264_rkmpp \
--enable-encoder=hevc_rkmpp \ --enable-encoder=hevc_rkmpp \
--enable-encoder=h264_v4l2m2m \ --enable-encoder=h264_v4l2m2m \
@@ -194,14 +238,22 @@ RUN rustup target add aarch64-unknown-linux-gnu
# Create symlink for mold to work with cross-compiler # Create symlink for mold to work with cross-compiler
RUN ln -s /usr/bin/mold /usr/bin/aarch64-linux-gnu-ld.mold RUN ln -s /usr/bin/mold /usr/bin/aarch64-linux-gnu-ld.mold
# Copy entrypoint script
COPY build/cross/entrypoint.sh /usr/local/bin/cross-entrypoint.sh
RUN chmod +x /usr/local/bin/cross-entrypoint.sh
# Configure environment for cross-compilation # Configure environment for cross-compilation
# Use PKG_CONFIG_LIBDIR to completely replace default search paths
# This ensures we use our custom-built FFmpeg instead of system FFmpeg
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \ ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \ CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \ CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \ AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \
PKG_CONFIG_LIBDIR=/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig \ PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/aarch64-linux-gnu/lib/pkgconfig:/usr/aarch64-linux-gnu/lib/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig \
PKG_CONFIG_PATH="" \ PKG_CONFIG_PATH="" \
PKG_CONFIG_ALLOW_CROSS=1 \ PKG_CONFIG_ALLOW_CROSS=1 \
LIBRARY_PATH="/opt/one-kvm-libs/aarch64-linux-gnu/lib" \
CPATH="/opt/one-kvm-libs/aarch64-linux-gnu/include" \
FFMPEG_STATIC=1 \
LIBYUV_STATIC=1 \
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-fuse-ld=mold" RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc -C link-arg=-fuse-ld=mold"
ENTRYPOINT ["/usr/local/bin/cross-entrypoint.sh"]

View File

@@ -28,37 +28,79 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
git \ git \
libclang-dev \ libclang-dev \
llvm \ llvm \
protobuf-compiler \
mold \ mold \
meson \ meson \
ninja-build \ ninja-build \
wget \ wget \
file \
gcc-arm-linux-gnueabihf \ gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf \ g++-arm-linux-gnueabihf \
libc6-dev-armhf-cross \ libc6-dev-armhf-cross \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install ARMv7 development libraries (without system ffmpeg) # Install ARMv7 development libraries (without VAAPI/X11 for ARM)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libssl-dev:armhf \
libasound2-dev:armhf \ libasound2-dev:armhf \
libv4l-dev:armhf \ libv4l-dev:armhf \
libudev-dev:armhf \ libudev-dev:armhf \
zlib1g-dev:armhf \ zlib1g-dev:armhf \
libjpeg62-turbo-dev:armhf \ # Note: libjpeg-turbo, libyuv, libvpx are built from source below for static linking
libyuv-dev:armhf \
libx264-dev:armhf \ libx264-dev:armhf \
libx265-dev:armhf \ libx265-dev:armhf \
libvpx-dev:armhf \
libopus-dev:armhf \ libopus-dev:armhf \
libva-dev:armhf \
libdrm-dev:armhf \ libdrm-dev:armhf \
libx11-dev:armhf \
libxcb1-dev:armhf \
libxau-dev:armhf \
libxdmcp-dev:armhf \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Build static libjpeg-turbo from source (cross-compile for ARMv7)
RUN git clone --depth 1 https://github.com/libjpeg-turbo/libjpeg-turbo /tmp/libjpeg-turbo \
&& cd /tmp/libjpeg-turbo \
&& cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=arm \
-DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \
-DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/armv7-linux-gnueabihf \
-DENABLE_SHARED=OFF -DENABLE_STATIC=ON \
&& cmake --build build -j$(nproc) \
&& cmake --install build \
&& rm -rf /tmp/libjpeg-turbo
# Build static libyuv from source (cross-compile for ARMv7)
RUN git clone --depth 1 https://github.com/lemenkov/libyuv /tmp/libyuv \
&& cd /tmp/libyuv \
&& mkdir build && cd build \
&& cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_SYSTEM_PROCESSOR=arm \
-DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc \
-DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \
-DCMAKE_PREFIX_PATH=/opt/one-kvm-libs/armv7-linux-gnueabihf \
-DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/armv7-linux-gnueabihf \
&& make -j$(nproc) \
&& mkdir -p /opt/one-kvm-libs/armv7-linux-gnueabihf/lib \
&& cp libyuv.a /opt/one-kvm-libs/armv7-linux-gnueabihf/lib/ \
&& cp -r ../include /opt/one-kvm-libs/armv7-linux-gnueabihf/ \
&& rm -rf /tmp/libyuv
# Build static libvpx from source (cross-compile for ARMv7)
# CC/CXX/LD/AR must be environment variables, not configure arguments
RUN git clone --depth 1 https://github.com/webmproject/libvpx /tmp/libvpx \
&& cd /tmp/libvpx \
&& export CC=arm-linux-gnueabihf-gcc \
&& export CXX=arm-linux-gnueabihf-g++ \
&& export LD=arm-linux-gnueabihf-ld \
&& export AR=arm-linux-gnueabihf-ar \
&& export CROSS=arm-linux-gnueabihf- \
&& ./configure --prefix=/opt/one-kvm-libs/armv7-linux-gnueabihf \
--target=armv7-linux-gcc \
--enable-static --disable-shared --enable-pic \
--disable-examples --disable-tools --disable-docs \
--disable-unit-tests \
&& make -j$(nproc) \
&& echo "=== libvpx: Checking architecture ===" \
&& file libvpx.a \
&& make install \
&& rm -rf /tmp/libvpx
# Download and build FFmpeg with RKMPP support # Download and build FFmpeg with RKMPP support
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \ && wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \
@@ -73,7 +115,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
-DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \ -DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++ \
-DCMAKE_INSTALL_PREFIX=/usr/arm-linux-gnueabihf \ -DCMAKE_INSTALL_PREFIX=/usr/arm-linux-gnueabihf \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \ -DBUILD_SHARED_LIBS=OFF \
-DBUILD_TEST=OFF \ -DBUILD_TEST=OFF \
&& make -j$(nproc) \ && make -j$(nproc) \
&& make install \ && make install \
@@ -94,6 +136,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& meson setup build --prefix=/usr/arm-linux-gnueabihf --libdir=lib \ && meson setup build --prefix=/usr/arm-linux-gnueabihf --libdir=lib \
--cross-file=/tmp/armhf-cross.txt \ --cross-file=/tmp/armhf-cross.txt \
--buildtype=release \ --buildtype=release \
--default-library=static \
-Dcpp_args=-fpermissive \ -Dcpp_args=-fpermissive \
-Dlibdrm=false \ -Dlibdrm=false \
-Dlibrga_demo=false \ -Dlibrga_demo=false \
@@ -102,7 +145,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& cd .. \ && cd .. \
# Create pkg-config wrapper for cross-compilation # Create pkg-config wrapper for cross-compilation
&& echo '#!/bin/sh' > /tmp/armhf-pkg-config \ && echo '#!/bin/sh' > /tmp/armhf-pkg-config \
&& echo 'export PKG_CONFIG_LIBDIR=/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig' >> /tmp/armhf-pkg-config \ && echo 'export PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/armv7-linux-gnueabihf/lib/pkgconfig:/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig' >> /tmp/armhf-pkg-config \
&& echo 'export PKG_CONFIG_PATH=""' >> /tmp/armhf-pkg-config \ && echo 'export PKG_CONFIG_PATH=""' >> /tmp/armhf-pkg-config \
&& echo 'export PKG_CONFIG_SYSROOT_DIR=""' >> /tmp/armhf-pkg-config \ && echo 'export PKG_CONFIG_SYSROOT_DIR=""' >> /tmp/armhf-pkg-config \
&& echo 'exec pkg-config "$@"' >> /tmp/armhf-pkg-config \ && echo 'exec pkg-config "$@"' >> /tmp/armhf-pkg-config \
@@ -110,7 +153,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
# Build FFmpeg with RKMPP (minimal build for encoding only) # Build FFmpeg with RKMPP (minimal build for encoding only)
&& cd ffmpeg-rockchip \ && cd ffmpeg-rockchip \
&& ./configure \ && ./configure \
--prefix=/usr/arm-linux-gnueabihf \ --prefix=/opt/one-kvm-libs/armv7-linux-gnueabihf \
--cross-prefix=arm-linux-gnueabihf- \ --cross-prefix=arm-linux-gnueabihf- \
--arch=arm \ --arch=arm \
--target-os=linux \ --target-os=linux \
@@ -118,13 +161,14 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
--pkg-config=/tmp/armhf-pkg-config \ --pkg-config=/tmp/armhf-pkg-config \
--enable-gpl \ --enable-gpl \
--enable-version3 \ --enable-version3 \
--enable-shared \ --disable-shared \
--disable-static \ --enable-static \
# Hardware acceleration --enable-pic \
# Hardware acceleration (ARM: RKMPP + V4L2, no VAAPI)
--enable-libdrm \ --enable-libdrm \
--enable-rkmpp \ --enable-rkmpp \
--enable-rkrga \ --enable-rkrga \
--enable-vaapi \ --disable-vaapi \
--enable-v4l2-m2m \ --enable-v4l2-m2m \
# Software encoding libraries # Software encoding libraries
--enable-libx264 \ --enable-libx264 \
@@ -151,10 +195,6 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
--disable-decoders \ --disable-decoders \
# Disable all encoders, enable only needed ones # Disable all encoders, enable only needed ones
--disable-encoders \ --disable-encoders \
--enable-encoder=h264_vaapi \
--enable-encoder=hevc_vaapi \
--enable-encoder=vp8_vaapi \
--enable-encoder=vp9_vaapi \
--enable-encoder=h264_rkmpp \ --enable-encoder=h264_rkmpp \
--enable-encoder=hevc_rkmpp \ --enable-encoder=hevc_rkmpp \
--enable-encoder=h264_v4l2m2m \ --enable-encoder=h264_v4l2m2m \
@@ -194,14 +234,22 @@ RUN rustup target add armv7-unknown-linux-gnueabihf
# Create symlink for mold to work with cross-compiler # Create symlink for mold to work with cross-compiler
RUN ln -s /usr/bin/mold /usr/bin/arm-linux-gnueabihf-ld.mold RUN ln -s /usr/bin/mold /usr/bin/arm-linux-gnueabihf-ld.mold
# Copy entrypoint script
COPY build/cross/entrypoint.sh /usr/local/bin/cross-entrypoint.sh
RUN chmod +x /usr/local/bin/cross-entrypoint.sh
# Configure environment for cross-compilation # Configure environment for cross-compilation
# Use PKG_CONFIG_LIBDIR to completely replace default search paths
# This ensures we use our custom-built FFmpeg instead of system FFmpeg
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \ ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \
CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc \ CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc \
CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ \ CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ \
AR_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-ar \ AR_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-ar \
PKG_CONFIG_LIBDIR=/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig \ PKG_CONFIG_LIBDIR=/opt/one-kvm-libs/armv7-linux-gnueabihf/lib/pkgconfig:/usr/arm-linux-gnueabihf/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig \
PKG_CONFIG_PATH="" \ PKG_CONFIG_PATH="" \
PKG_CONFIG_ALLOW_CROSS=1 \ PKG_CONFIG_ALLOW_CROSS=1 \
LIBRARY_PATH="/opt/one-kvm-libs/armv7-linux-gnueabihf/lib" \
CPATH="/opt/one-kvm-libs/armv7-linux-gnueabihf/include" \
FFMPEG_STATIC=1 \
LIBYUV_STATIC=1 \
RUSTFLAGS="-C linker=arm-linux-gnueabihf-gcc -C link-arg=-fuse-ld=mold" RUSTFLAGS="-C linker=arm-linux-gnueabihf-gcc -C link-arg=-fuse-ld=mold"
ENTRYPOINT ["/usr/local/bin/cross-entrypoint.sh"]

View File

@@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ENV PATH="/root/.cargo/bin:${PATH}" ENV PATH="/root/.cargo/bin:${PATH}"
# Install build dependencies # Install build dependencies
# Note: libyuv, libvpx are built from source below for static linking
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
# Build tools # Build tools
build-essential \ build-essential \
@@ -27,8 +28,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
git \ git \
libclang-dev \ libclang-dev \
llvm \ llvm \
protobuf-compiler \
libssl-dev \
mold \ mold \
wget \ wget \
# Core system libraries # Core system libraries
@@ -36,37 +35,69 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libv4l-dev \ libv4l-dev \
libudev-dev \ libudev-dev \
zlib1g-dev \ zlib1g-dev \
# Video/image processing # Note: libjpeg-turbo is built from source below for static linking
libjpeg62-turbo-dev \ # Video codec libraries (dynamic, for software fallback)
libyuv-dev \
# Video codec libraries (for FFmpeg build)
libx264-dev \ libx264-dev \
libx265-dev \ libx265-dev \
libvpx-dev \
# Audio codec # Audio codec
libopus-dev \ libopus-dev \
# Hardware acceleration # Hardware acceleration
libva-dev \ libva-dev \
libdrm-dev \ libdrm-dev \
libmfx-dev \ libmfx-dev \
# X11 libraries # X11 libraries (for VAAPI)
libx11-dev \ libx11-dev \
libxcb1-dev \ libxcb1-dev \
libxau-dev \ libxau-dev \
libxdmcp-dev \ libxdmcp-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Build static libjpeg-turbo from source (needed by libyuv)
RUN git clone --depth 1 https://github.com/libjpeg-turbo/libjpeg-turbo /tmp/libjpeg-turbo \
&& cd /tmp/libjpeg-turbo \
&& cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/x86_64-linux-gnu \
-DENABLE_SHARED=OFF -DENABLE_STATIC=ON \
&& cmake --build build -j$(nproc) \
&& cmake --install build \
&& rm -rf /tmp/libjpeg-turbo
# Build static libyuv from source (uses libjpeg-turbo headers)
RUN git clone --depth 1 https://github.com/lemenkov/libyuv /tmp/libyuv \
&& cd /tmp/libyuv \
&& mkdir build && cd build \
&& cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_PREFIX_PATH=/opt/one-kvm-libs/x86_64-linux-gnu \
-DCMAKE_INSTALL_PREFIX=/opt/one-kvm-libs/x86_64-linux-gnu \
&& make -j$(nproc) \
&& mkdir -p /opt/one-kvm-libs/x86_64-linux-gnu/lib \
&& cp libyuv.a /opt/one-kvm-libs/x86_64-linux-gnu/lib/ \
&& cp -r ../include /opt/one-kvm-libs/x86_64-linux-gnu/ \
&& rm -rf /tmp/libyuv
# Build static libvpx from source
RUN git clone --depth 1 https://github.com/webmproject/libvpx /tmp/libvpx \
&& cd /tmp/libvpx \
&& ./configure --prefix=/opt/one-kvm-libs/x86_64-linux-gnu \
--enable-static --disable-shared --enable-pic \
--disable-examples --disable-tools --disable-docs \
&& make -j$(nproc) \
&& make install \
&& rm -rf /tmp/libvpx
# Download and build FFmpeg with minimal configuration for encoding only # Download and build FFmpeg with minimal configuration for encoding only
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
&& wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \ && wget -q https://files.mofeng.run/src/image/other/ffmpeg.tar.gz \
&& tar -xzf ffmpeg.tar.gz \ && tar -xzf ffmpeg.tar.gz \
&& cd ffmpeg/ffmpeg-rockchip \ && cd ffmpeg/ffmpeg-rockchip \
&& export PKG_CONFIG_PATH="/opt/one-kvm-libs/x86_64-linux-gnu/lib/pkgconfig:$PKG_CONFIG_PATH" \
&& ./configure \ && ./configure \
--prefix=/usr/local \ --prefix=/opt/one-kvm-libs/x86_64-linux-gnu \
--enable-gpl \ --enable-gpl \
--enable-version3 \ --enable-version3 \
--enable-shared \ --disable-shared \
--disable-static \ --enable-static \
--enable-pic \
# Hardware acceleration # Hardware acceleration
--enable-libdrm \ --enable-libdrm \
--enable-vaapi \ --enable-vaapi \
@@ -128,13 +159,22 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
--disable-debug \ --disable-debug \
&& make -j$(nproc) \ && make -j$(nproc) \
&& make install \ && make install \
&& ldconfig \
&& cd / \ && cd / \
&& rm -rf /tmp/ffmpeg-build && rm -rf /tmp/ffmpeg-build
# Add Rust target # Add Rust target
RUN rustup target add x86_64-unknown-linux-gnu RUN rustup target add x86_64-unknown-linux-gnu
# Configure mold as the linker and use custom FFmpeg # Copy entrypoint script
COPY build/cross/entrypoint.sh /usr/local/bin/cross-entrypoint.sh
RUN chmod +x /usr/local/bin/cross-entrypoint.sh
# Configure environment for static linking
ENV RUSTFLAGS="-C link-arg=-fuse-ld=mold" \ ENV RUSTFLAGS="-C link-arg=-fuse-ld=mold" \
PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:${PKG_CONFIG_PATH}" PKG_CONFIG_PATH="/opt/one-kvm-libs/x86_64-linux-gnu/lib/pkgconfig" \
LIBRARY_PATH="/opt/one-kvm-libs/x86_64-linux-gnu/lib" \
CPATH="/opt/one-kvm-libs/x86_64-linux-gnu/include" \
FFMPEG_STATIC=1 \
LIBYUV_STATIC=1
ENTRYPOINT ["/usr/local/bin/cross-entrypoint.sh"]

View File

@@ -14,7 +14,6 @@ RustDesk 模块实现 RustDesk 协议集成,允许使用标准 RustDesk 客户
- 视频/音频/HID 转换 - 视频/音频/HID 转换
- 端到端加密 (Curve25519 + XSalsa20-Poly1305) - 端到端加密 (Curve25519 + XSalsa20-Poly1305)
- 签名认证 (Ed25519) - 签名认证 (Ed25519)
- 公共服务器支持 (通过 secrets.toml)
- 动态编码器协商 (H264/H265/VP8/VP9) - 动态编码器协商 (H264/H265/VP8/VP9)
- 输入节流 (防止 HID EAGAIN) - 输入节流 (防止 HID EAGAIN)
- CapsLock 状态同步 - CapsLock 状态同步
@@ -660,7 +659,7 @@ pub struct RustDeskConfig {
/// Rendezvous 服务器地址 (hbbs) /// Rendezvous 服务器地址 (hbbs)
/// 格式: "rs.example.com" 或 "192.168.1.100:21116" /// 格式: "rs.example.com" 或 "192.168.1.100:21116"
/// 如果为空,使用 secrets.toml 中配置的公共服务器 /// 必填项 - 不再提供公共服务器,需自行配置
pub rendezvous_server: String, pub rendezvous_server: String,
/// 中继服务器地址 (hbbr),默认与 rendezvous 同主机 /// 中继服务器地址 (hbbr),默认与 rendezvous 同主机
@@ -703,15 +702,9 @@ impl RustDeskConfig {
/// 检查配置是否有效 /// 检查配置是否有效
pub fn is_valid(&self) -> bool; pub fn is_valid(&self) -> bool;
/// 检查是否使用公共服务器
pub fn is_using_public_server(&self) -> bool;
/// 获取有效的 Rendezvous 服务器地址 /// 获取有效的 Rendezvous 服务器地址
pub fn effective_rendezvous_server(&self) -> &str; pub fn effective_rendezvous_server(&self) -> &str;
/// 获取公共服务器信息 (如果配置了)
pub fn public_server_info() -> Option<PublicServerInfo>;
/// 获取带默认端口的 Rendezvous 地址 /// 获取带默认端口的 Rendezvous 地址
pub fn rendezvous_addr(&self) -> String; pub fn rendezvous_addr(&self) -> String;
@@ -721,14 +714,6 @@ impl RustDeskConfig {
/// 确保 UUID 存在 (自动生成并标记需要保存) /// 确保 UUID 存在 (自动生成并标记需要保存)
pub fn ensure_uuid(&mut self) -> ([u8; 16], bool); pub fn ensure_uuid(&mut self) -> ([u8; 16], bool);
} }
/// 公共服务器信息
#[derive(Serialize, Deserialize)]
#[typeshare]
pub struct PublicServerInfo {
pub server: String, // 服务器地址
pub public_key: String, // 公钥 (Base64)
}
``` ```
### 配置文件示例 ### 配置文件示例
@@ -744,23 +729,7 @@ device_password = "mypassword"
# 密钥和 UUID 由程序自动生成和保存 # 密钥和 UUID 由程序自动生成和保存
``` ```
**使用公共服务器:** **注意**: 不再提供公共服务器,需自行配置 RustDesk 服务器。
```toml
[rustdesk]
enabled = true
rendezvous_server = "" # 留空使用 secrets.toml 中的公共服务器
device_id = "123456789"
device_password = "mypassword"
```
**secrets.toml 公共服务器配置:**
```toml
[rustdesk]
# 公共服务器配置 (可选)
public_server = "rs-ny.rustdesk.com"
public_key = "xxx...base64...xxx"
relay_key = "xxx...key...xxx"
```
--- ---
@@ -868,7 +837,7 @@ pub enum RustDeskError {
```rust ```rust
let config = RustDeskConfig { let config = RustDeskConfig {
enabled: true, enabled: true,
rendezvous_server: "".to_string(), // 使用公共服务器 rendezvous_server: "hbbs.example.com:21116".to_string(), // 必填 - 配置您的服务器
device_id: "123456789".to_string(), device_id: "123456789".to_string(),
device_password: "mypassword".to_string(), device_password: "mypassword".to_string(),
..Default::default() ..Default::default()
@@ -1271,7 +1240,7 @@ docker run -d --name hbbr \
| 协议 | RustDesk Protocol | 同 | | 协议 | RustDesk Protocol | 同 |
| P2P | 支持 | 支持 | | P2P | 支持 | 支持 |
| 中继 | 支持 | 提供中继服务 | | 中继 | 支持 | 提供中继服务 |
| 公共服务器 | 可配置 (secrets.toml) | N/A | | 公共服务器 | 不提供,需自建 | N/A |
| 多连接 | 支持 | N/A | | 多连接 | 支持 | N/A |
| 输入节流 | 60Hz 限流 | 无限制 | | 输入节流 | 60Hz 限流 | 无限制 |

View File

@@ -97,10 +97,76 @@ mod ffmpeg {
build_ffmpeg_ram(builder); build_ffmpeg_ram(builder);
} }
/// Link system FFmpeg using pkg-config (for Linux development) /// Link system FFmpeg using pkg-config or custom path
/// Supports both static and dynamic linking based on FFMPEG_STATIC env var
fn link_system_ffmpeg(builder: &mut Build) { fn link_system_ffmpeg(builder: &mut Build) {
use std::process::Command; use std::process::Command;
// Check if static linking is requested
let use_static = std::env::var("FFMPEG_STATIC").map(|v| v == "1").unwrap_or(false);
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
// Try custom library path first:
// 1. Check ONE_KVM_LIBS_PATH environment variable (explicit override)
// 2. Fall back to architecture-based detection
let custom_lib_path = if let Ok(path) = std::env::var("ONE_KVM_LIBS_PATH") {
path
} else {
match target_arch.as_str() {
"x86_64" => "/opt/one-kvm-libs/x86_64-linux-gnu",
"aarch64" => "/opt/one-kvm-libs/aarch64-linux-gnu",
"arm" => "/opt/one-kvm-libs/armv7-linux-gnueabihf",
_ => "",
}
.to_string()
};
// Check if our custom FFmpeg exists
if !custom_lib_path.is_empty() {
let lib_dir = Path::new(&custom_lib_path).join("lib");
let include_dir = Path::new(&custom_lib_path).join("include");
let avcodec_lib = lib_dir.join("libavcodec.a");
if avcodec_lib.exists() {
println!("cargo:info=Using custom FFmpeg from {}", custom_lib_path);
println!("cargo:rustc-link-search=native={}", lib_dir.display());
builder.include(&include_dir);
// Static link FFmpeg core libraries
println!("cargo:rustc-link-lib=static=avcodec");
println!("cargo:rustc-link-lib=static=avutil");
// Link hardware acceleration dependencies (dynamic)
// These vary by architecture
if target_arch == "x86_64" {
// VAAPI for x86_64
println!("cargo:rustc-link-lib=va");
println!("cargo:rustc-link-lib=va-drm");
println!("cargo:rustc-link-lib=va-x11"); // Required for vaGetDisplay
println!("cargo:rustc-link-lib=mfx");
} else {
// RKMPP for ARM
println!("cargo:rustc-link-lib=rockchip_mpp");
println!("cargo:rustc-link-lib=rga");
}
// Software codec dependencies (dynamic - GPL)
println!("cargo:rustc-link-lib=x264");
println!("cargo:rustc-link-lib=x265");
// VPX - check if static version exists in our custom path
let vpx_static = lib_dir.join("libvpx.a");
if vpx_static.exists() {
println!("cargo:rustc-link-lib=static=vpx");
} else {
println!("cargo:rustc-link-lib=vpx");
}
return;
}
}
// Fallback to pkg-config
// Only need libavcodec and libavutil for encoding // Only need libavcodec and libavutil for encoding
let libs = ["libavcodec", "libavutil"]; let libs = ["libavcodec", "libavutil"];
@@ -120,9 +186,15 @@ mod ffmpeg {
} }
} }
// Get libs - always use dynamic linking on Linux // Get libs - use --static flag for static linking
let pkg_config_args = if use_static {
vec!["--static", "--libs", lib]
} else {
vec!["--libs", lib]
};
if let Ok(output) = Command::new("pkg-config") if let Ok(output) = Command::new("pkg-config")
.args(["--libs", lib]) .args(&pkg_config_args)
.output() .output()
{ {
if output.status.success() { if output.status.success() {
@@ -131,7 +203,18 @@ mod ffmpeg {
if flag.starts_with("-L") { if flag.starts_with("-L") {
println!("cargo:rustc-link-search=native={}", &flag[2..]); println!("cargo:rustc-link-search=native={}", &flag[2..]);
} else if flag.starts_with("-l") { } else if flag.starts_with("-l") {
println!("cargo:rustc-link-lib={}", &flag[2..]); let lib_name = &flag[2..];
if use_static {
// For static linking, link FFmpeg libs statically, others dynamically
if lib_name.starts_with("av") || lib_name == "swresample" {
println!("cargo:rustc-link-lib=static={}", lib_name);
} else {
// Runtime libraries (va, drm, etc.) must be dynamic
println!("cargo:rustc-link-lib={}", lib_name);
}
} else {
println!("cargo:rustc-link-lib={}", lib_name);
}
} }
} }
} else { } else {
@@ -142,8 +225,12 @@ mod ffmpeg {
} }
} }
if use_static {
println!("cargo:info=Using system FFmpeg via pkg-config (static linking)");
} else {
println!("cargo:info=Using system FFmpeg via pkg-config (dynamic linking)"); println!("cargo:info=Using system FFmpeg via pkg-config (dynamic linking)");
} }
}
fn link_vcpkg(builder: &mut Build, mut path: PathBuf) -> PathBuf { fn link_vcpkg(builder: &mut Build, mut path: PathBuf) -> PathBuf {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
@@ -203,14 +290,15 @@ mod ffmpeg {
let dyn_libs: Vec<&str> = if target_os == "windows" { let dyn_libs: Vec<&str> = if target_os == "windows" {
["User32", "bcrypt", "ole32", "advapi32"].to_vec() ["User32", "bcrypt", "ole32", "advapi32"].to_vec()
} else if target_os == "linux" { } else if target_os == "linux" {
// Note: VA-API libraries (va, va-drm, va-x11) must remain dynamic // Base libraries for all Linux platforms
// as they load drivers at runtime. Same for libmfx. let mut v = vec!["drm", "stdc++"];
// All dependencies use dynamic linking on Linux.
let mut v = vec!["drm", "X11", "stdc++"];
// x86_64: needs X11 for VAAPI and zlib
if target_arch == "x86_64" { if target_arch == "x86_64" {
v.push("X11");
v.push("z"); v.push("z");
} }
// ARM (aarch64, arm): no X11 needed, uses RKMPP/V4L2
v v
} else { } else {
panic!("Unsupported OS: {}. Only Windows and Linux are supported.", target_os); panic!("Unsupported OS: {}. Only Windows and Linux are supported.", target_os);

View File

@@ -148,40 +148,31 @@ fn link_vcpkg(mut path: PathBuf) -> bool {
println!("cargo:rustc-link-search=native={}", lib_path.display()); println!("cargo:rustc-link-search=native={}", lib_path.display());
// For Linux: use dynamic linking // Check if static linking is requested via environment variable
#[cfg(target_os = "linux")] let use_static = env::var("LIBYUV_STATIC").map(|v| v == "1").unwrap_or(false);
{
println!("cargo:rustc-link-lib=yuv");
println!("cargo:rustc-link-lib=jpeg");
println!("cargo:rustc-link-lib=stdc++");
}
// For Windows/macOS: keep static linking
#[cfg(not(target_os = "linux"))]
{
let static_lib = lib_path.join("libyuv.a"); let static_lib = lib_path.join("libyuv.a");
let static_lib_win = lib_path.join("yuv.lib");
if static_lib.exists() || static_lib_win.exists() {
println!("cargo:rustc-link-lib=static=yuv");
} else {
println!("cargo:rustc-link-lib=yuv");
}
let jpeg_static = lib_path.join("libjpeg.a"); let jpeg_static = lib_path.join("libjpeg.a");
let jpeg_static_win = lib_path.join("jpeg.lib");
let turbojpeg_static = lib_path.join("libturbojpeg.a"); let turbojpeg_static = lib_path.join("libturbojpeg.a");
if use_static && static_lib.exists() {
// Static linking (for deb packaging)
println!("cargo:rustc-link-lib=static=yuv");
if turbojpeg_static.exists() { if turbojpeg_static.exists() {
println!("cargo:rustc-link-lib=static=turbojpeg"); println!("cargo:rustc-link-lib=static=turbojpeg");
} else if jpeg_static.exists() || jpeg_static_win.exists() { } else if jpeg_static.exists() {
println!("cargo:rustc-link-lib=static=jpeg"); println!("cargo:rustc-link-lib=static=jpeg");
} else { } else {
println!("cargo:rustc-link-lib=jpeg"); println!("cargo:rustc-link-lib=jpeg");
} }
#[cfg(target_os = "windows")]
println!("cargo:rustc-link-lib=stdc++"); println!("cargo:rustc-link-lib=stdc++");
println!("cargo:info=Using libyuv from vcpkg (static linking)");
} else {
// Dynamic linking (default for development)
println!("cargo:rustc-link-lib=yuv");
println!("cargo:rustc-link-lib=jpeg");
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:info=Using libyuv from vcpkg (dynamic linking)");
} }
println!( println!(
@@ -238,21 +229,72 @@ fn link_pkg_config() -> bool {
} }
fn link_system() -> bool { fn link_system() -> bool {
// Try common system library paths (dynamic linking only) // Check if static linking is requested
let lib_paths = [ let use_static = env::var("LIBYUV_STATIC").map(|v| v == "1").unwrap_or(false);
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
// Build custom library paths based on target architecture:
// 1. Check ONE_KVM_LIBS_PATH environment variable (explicit override)
// 2. Fall back to architecture-based detection
let custom_lib_path = if let Ok(path) = env::var("ONE_KVM_LIBS_PATH") {
format!("{}/lib", path)
} else {
match target_arch.as_str() {
"x86_64" => "/opt/one-kvm-libs/x86_64-linux-gnu/lib",
"aarch64" => "/opt/one-kvm-libs/aarch64-linux-gnu/lib",
"arm" => "/opt/one-kvm-libs/armv7-linux-gnueabihf/lib",
_ => "",
}
.to_string()
};
// Try common system library paths (custom paths first)
let mut lib_paths: Vec<String> = Vec::new();
// Add custom build path first (highest priority)
if !custom_lib_path.is_empty() {
lib_paths.push(custom_lib_path);
}
// Then standard paths
lib_paths.extend([
"/usr/local/lib", // Custom builds
"/usr/local/lib64",
"/usr/lib", "/usr/lib",
"/usr/lib64", "/usr/lib64",
"/usr/local/lib",
"/usr/local/lib64",
"/usr/lib/x86_64-linux-gnu", // Debian/Ubuntu x86_64 "/usr/lib/x86_64-linux-gnu", // Debian/Ubuntu x86_64
"/usr/lib/aarch64-linux-gnu", // Debian/Ubuntu ARM64 "/usr/lib/aarch64-linux-gnu", // Debian/Ubuntu ARM64
"/usr/lib/arm-linux-gnueabihf", // Debian/Ubuntu ARMv7 "/usr/lib/arm-linux-gnueabihf", // Debian/Ubuntu ARMv7
]; ].iter().map(|s| s.to_string()));
for path in &lib_paths { for path in &lib_paths {
let lib_path = Path::new(path); let lib_path = Path::new(path);
let libyuv_static = lib_path.join("libyuv.a");
let libyuv_so = lib_path.join("libyuv.so"); let libyuv_so = lib_path.join("libyuv.so");
// Prefer static library if LIBYUV_STATIC=1
if use_static && libyuv_static.exists() {
println!("cargo:rustc-link-search=native={}", path);
println!("cargo:rustc-link-lib=static=yuv");
// Check for static libjpeg-turbo in the same directory
let turbojpeg_static = lib_path.join("libturbojpeg.a");
let jpeg_static = lib_path.join("libjpeg.a");
if turbojpeg_static.exists() {
println!("cargo:rustc-link-lib=static=turbojpeg");
} else if jpeg_static.exists() {
println!("cargo:rustc-link-lib=static=jpeg");
} else {
// Fall back to dynamic jpeg
println!("cargo:rustc-link-lib=jpeg");
}
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:info=Using system libyuv from {} (static linking)", path);
return true;
}
// Fall back to dynamic linking
if libyuv_so.exists() { if libyuv_so.exists() {
println!("cargo:rustc-link-search=native={}", path); println!("cargo:rustc-link-search=native={}", path);
println!("cargo:rustc-link-lib=yuv"); println!("cargo:rustc-link-lib=yuv");

View File

@@ -5,8 +5,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare; use typeshare::typeshare;
use crate::secrets;
/// RustDesk configuration /// RustDesk configuration
#[typeshare] #[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -15,9 +13,8 @@ pub struct RustDeskConfig {
/// Enable RustDesk protocol /// Enable RustDesk protocol
pub enabled: bool, pub enabled: bool,
/// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100" /// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100:21116"
/// Port defaults to 21116 if not specified /// Required for RustDesk to function
/// If empty, uses the public server from secrets.toml
pub rendezvous_server: String, pub rendezvous_server: String,
/// Relay server address (hbbr), if different from rendezvous server /// Relay server address (hbbr), if different from rendezvous server
@@ -79,40 +76,18 @@ impl Default for RustDeskConfig {
impl RustDeskConfig { impl RustDeskConfig {
/// Check if the configuration is valid for starting the service /// Check if the configuration is valid for starting the service
/// Returns true if enabled and has a valid server (user-configured or public) /// Returns true if enabled and has a valid server
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
self.enabled self.enabled
&& !self.effective_rendezvous_server().is_empty() && !self.rendezvous_server.is_empty()
&& !self.device_id.is_empty() && !self.device_id.is_empty()
&& !self.device_password.is_empty() && !self.device_password.is_empty()
} }
/// Check if using the public server (user left rendezvous_server empty) /// Get the rendezvous server (user-configured)
pub fn is_using_public_server(&self) -> bool {
self.rendezvous_server.is_empty() && secrets::rustdesk::has_public_server()
}
/// Get the effective rendezvous server (user-configured or public fallback)
pub fn effective_rendezvous_server(&self) -> &str { pub fn effective_rendezvous_server(&self) -> &str {
if self.rendezvous_server.is_empty() {
secrets::rustdesk::PUBLIC_SERVER
} else {
&self.rendezvous_server &self.rendezvous_server
} }
}
/// Get public server info for display (server address and public key)
/// Returns None if no public server is configured
pub fn public_server_info() -> Option<PublicServerInfo> {
if secrets::rustdesk::has_public_server() {
Some(PublicServerInfo {
server: secrets::rustdesk::PUBLIC_SERVER.to_string(),
public_key: secrets::rustdesk::PUBLIC_KEY.to_string(),
})
} else {
None
}
}
/// Generate a new random device ID /// Generate a new random device ID
pub fn generate_device_id() -> String { pub fn generate_device_id() -> String {
@@ -148,8 +123,10 @@ impl RustDeskConfig {
/// Get the rendezvous server address with default port /// Get the rendezvous server address with default port
pub fn rendezvous_addr(&self) -> String { pub fn rendezvous_addr(&self) -> String {
let server = self.effective_rendezvous_server(); let server = &self.rendezvous_server;
if server.contains(':') { if server.is_empty() {
String::new()
} else if server.contains(':') {
server.to_string() server.to_string()
} else { } else {
format!("{}:21116", server) format!("{}:21116", server)
@@ -165,8 +142,8 @@ impl RustDeskConfig {
format!("{}:21117", s) format!("{}:21117", s)
} }
}).or_else(|| { }).or_else(|| {
// Default: same host as effective rendezvous server // Default: same host as rendezvous server
let server = self.effective_rendezvous_server(); let server = &self.rendezvous_server;
if !server.is_empty() { if !server.is_empty() {
let host = server.split(':').next().unwrap_or(""); let host = server.split(':').next().unwrap_or("");
if !host.is_empty() { if !host.is_empty() {
@@ -181,16 +158,6 @@ impl RustDeskConfig {
} }
} }
/// Public server information for display to users
#[derive(Debug, Clone, Serialize, Deserialize)]
#[typeshare]
pub struct PublicServerInfo {
/// Public server address
pub server: String,
/// Public key for client connection
pub public_key: String,
}
/// Generate a random 9-digit device ID /// Generate a random 9-digit device ID
pub fn generate_device_id() -> String { pub fn generate_device_id() -> String {
use rand::Rng; use rand::Rng;
@@ -239,6 +206,10 @@ mod tests {
config.rendezvous_server = "example.com:21116".to_string(); config.rendezvous_server = "example.com:21116".to_string();
assert_eq!(config.rendezvous_addr(), "example.com:21116"); assert_eq!(config.rendezvous_addr(), "example.com:21116");
// Empty server returns empty string
config.rendezvous_server = String::new();
assert_eq!(config.rendezvous_addr(), "");
} }
#[test] #[test]
@@ -252,6 +223,10 @@ mod tests {
// Explicit relay server // Explicit relay server
config.relay_server = Some("relay.example.com".to_string()); config.relay_server = Some("relay.example.com".to_string());
assert_eq!(config.relay_addr(), Some("relay.example.com:21117".to_string())); assert_eq!(config.relay_addr(), Some("relay.example.com:21117".to_string()));
// No rendezvous server, relay is None
config.rendezvous_server = String::new();
assert_eq!(config.relay_addr(), None);
} }
#[test] #[test]
@@ -262,10 +237,8 @@ mod tests {
config.rendezvous_server = "custom.example.com".to_string(); config.rendezvous_server = "custom.example.com".to_string();
assert_eq!(config.effective_rendezvous_server(), "custom.example.com"); assert_eq!(config.effective_rendezvous_server(), "custom.example.com");
// When empty, falls back to public server (if configured) // When empty, returns empty
config.rendezvous_server = String::new(); config.rendezvous_server = String::new();
// This will return PUBLIC_SERVER from secrets assert_eq!(config.effective_rendezvous_server(), "");
let effective = config.effective_rendezvous_server();
assert!(!effective.is_empty() || !secrets::rustdesk::has_public_server());
} }
} }

View File

@@ -239,16 +239,10 @@ impl RustDeskService {
let config = service_config_punch.clone(); let config = service_config_punch.clone();
tokio::spawn(async move { tokio::spawn(async move {
// Get relay_key from config, or use public server's relay_key if using public server // Get relay_key from config (no public server fallback)
let relay_key = { let relay_key = {
let cfg = config.read(); let cfg = config.read();
cfg.relay_key.clone().unwrap_or_else(|| { cfg.relay_key.clone().unwrap_or_default()
if cfg.is_using_public_server() {
crate::secrets::rustdesk::RELAY_KEY.to_string()
} else {
String::new()
}
})
}; };
// Try P2P direct connection first // Try P2P direct connection first
@@ -295,16 +289,10 @@ impl RustDeskService {
let config = service_config.clone(); let config = service_config.clone();
tokio::spawn(async move { tokio::spawn(async move {
// Get relay_key from config, or use public server's relay_key if using public server // Get relay_key from config (no public server fallback)
let relay_key = { let relay_key = {
let cfg = config.read(); let cfg = config.read();
cfg.relay_key.clone().unwrap_or_else(|| { cfg.relay_key.clone().unwrap_or_default()
if cfg.is_using_public_server() {
crate::secrets::rustdesk::RELAY_KEY.to_string()
} else {
String::new()
}
})
}; };
if let Err(e) = handle_relay_request( if let Err(e) = handle_relay_request(

View File

@@ -4,7 +4,7 @@ use axum::{extract::State, Json};
use std::sync::Arc; use std::sync::Arc;
use crate::error::Result; use crate::error::Result;
use crate::rustdesk::config::{PublicServerInfo, RustDeskConfig}; use crate::rustdesk::config::RustDeskConfig;
use crate::state::AppState; use crate::state::AppState;
use super::apply::apply_rustdesk_config; use super::apply::apply_rustdesk_config;
@@ -23,8 +23,6 @@ pub struct RustDeskConfigResponse {
pub has_keypair: bool, pub has_keypair: bool,
/// 是否已设置 relay key /// 是否已设置 relay key
pub has_relay_key: bool, pub has_relay_key: bool,
/// 是否使用公共服务器(用户留空时)
pub using_public_server: bool,
} }
impl From<&RustDeskConfig> for RustDeskConfigResponse { impl From<&RustDeskConfig> for RustDeskConfigResponse {
@@ -37,7 +35,6 @@ impl From<&RustDeskConfig> for RustDeskConfigResponse {
has_password: !config.device_password.is_empty(), has_password: !config.device_password.is_empty(),
has_keypair: config.public_key.is_some() && config.private_key.is_some(), has_keypair: config.public_key.is_some() && config.private_key.is_some(),
has_relay_key: config.relay_key.is_some(), has_relay_key: config.relay_key.is_some(),
using_public_server: config.is_using_public_server(),
} }
} }
} }
@@ -48,8 +45,6 @@ pub struct RustDeskStatusResponse {
pub config: RustDeskConfigResponse, pub config: RustDeskConfigResponse,
pub service_status: String, pub service_status: String,
pub rendezvous_status: Option<String>, pub rendezvous_status: Option<String>,
/// 公共服务器信息(仅当有公共服务器配置时返回)
pub public_server: Option<PublicServerInfo>,
} }
/// 获取 RustDesk 配置 /// 获取 RustDesk 配置
@@ -73,14 +68,10 @@ pub async fn get_rustdesk_status(State(state): State<Arc<AppState>>) -> Json<Rus
} }
}; };
// 获取公共服务器信息
let public_server = RustDeskConfig::public_server_info();
Json(RustDeskStatusResponse { Json(RustDeskStatusResponse {
config: RustDeskConfigResponse::from(&config), config: RustDeskConfigResponse::from(&config),
service_status, service_status,
rendezvous_status, rendezvous_status,
public_server,
}) })
} }

View File

@@ -1814,11 +1814,29 @@ pub struct IceServerInfo {
} }
/// Get ICE servers configuration for client-side WebRTC /// Get ICE servers configuration for client-side WebRTC
/// Returns user-configured servers, or Google STUN as fallback if none configured
pub async fn webrtc_ice_servers(State(state): State<Arc<AppState>>) -> Json<IceServersResponse> { pub async fn webrtc_ice_servers(State(state): State<Arc<AppState>>) -> Json<IceServersResponse> {
use crate::webrtc::config::public_ice;
let config = state.config.get(); let config = state.config.get();
let mut ice_servers = Vec::new(); let mut ice_servers = Vec::new();
// Add STUN server // Check if user has configured custom ICE servers
let has_custom_stun = config
.stream
.stun_server
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or(false);
let has_custom_turn = config
.stream
.turn_server
.as_ref()
.map(|s| !s.is_empty())
.unwrap_or(false);
if has_custom_stun || has_custom_turn {
// Use user-configured ICE servers
if let Some(ref stun) = config.stream.stun_server { if let Some(ref stun) = config.stream.stun_server {
if !stun.is_empty() { if !stun.is_empty() {
ice_servers.push(IceServerInfo { ice_servers.push(IceServerInfo {
@@ -1829,12 +1847,10 @@ pub async fn webrtc_ice_servers(State(state): State<Arc<AppState>>) -> Json<IceS
} }
} }
// Add TURN server (with credentials)
if let Some(ref turn) = config.stream.turn_server { if let Some(ref turn) = config.stream.turn_server {
if !turn.is_empty() { if !turn.is_empty() {
let username = config.stream.turn_username.clone(); let username = config.stream.turn_username.clone();
let credential = config.stream.turn_password.clone(); let credential = config.stream.turn_password.clone();
// Only add TURN if credentials are provided
if username.is_some() && credential.is_some() { if username.is_some() && credential.is_some() {
ice_servers.push(IceServerInfo { ice_servers.push(IceServerInfo {
urls: vec![turn.clone()], urls: vec![turn.clone()],
@@ -1844,6 +1860,17 @@ pub async fn webrtc_ice_servers(State(state): State<Arc<AppState>>) -> Json<IceS
} }
} }
} }
} else {
// No custom servers configured - use Google STUN as default
if let Some(stun) = public_ice::stun_server() {
ice_servers.push(IceServerInfo {
urls: vec![stun],
username: None,
credential: None,
});
}
// Note: TURN servers are not provided - users must configure their own
}
Json(IceServersResponse { ice_servers }) Json(IceServersResponse { ice_servers })
} }

View File

@@ -4,21 +4,21 @@ use serde::{Deserialize, Serialize};
use crate::secrets; use crate::secrets;
/// Public ICE server utilities /// ICE server utilities - Only provides Google STUN, no TURN
pub mod public_ice { pub mod public_ice {
use super::*; use super::*;
/// Check if public ICE servers are configured (at compile time) /// Always returns true (we have Google STUN)
pub fn is_configured() -> bool { pub fn is_configured() -> bool {
secrets::ice::is_configured() secrets::ice::is_configured()
} }
/// Check if public TURN servers are configured (requires credentials) /// Always returns false (TURN not provided)
pub fn has_turn() -> bool { pub fn has_turn() -> bool {
secrets::ice::has_turn() secrets::ice::has_turn()
} }
/// Get the public STUN server URL /// Get the Google STUN server URL
pub fn stun_server() -> Option<String> { pub fn stun_server() -> Option<String> {
let server = secrets::ice::STUN_SERVER; let server = secrets::ice::STUN_SERVER;
if server.is_empty() { if server.is_empty() {
@@ -28,33 +28,15 @@ pub mod public_ice {
} }
} }
/// Get public TURN servers as TurnServer structs /// Always returns empty vector (TURN not provided)
pub fn turn_servers() -> Vec<TurnServer> { pub fn turn_servers() -> Vec<TurnServer> {
if !secrets::ice::has_turn() { vec![]
return vec![];
} }
let urls: Vec<String> = secrets::ice::TURN_URLS /// Get all public ICE servers (STUN only, no TURN)
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if urls.is_empty() {
return vec![];
}
vec![TurnServer {
urls,
username: secrets::ice::TURN_USERNAME.to_string(),
credential: secrets::ice::TURN_PASSWORD.to_string(),
}]
}
/// Get all public ICE servers (STUN + TURN) for WebRTC configuration
pub fn get_all_servers() -> (Vec<String>, Vec<TurnServer>) { pub fn get_all_servers() -> (Vec<String>, Vec<TurnServer>) {
let stun_servers = stun_server().into_iter().collect(); let stun_servers = stun_server().into_iter().collect();
let turn_servers = turn_servers(); let turn_servers = vec![];
(stun_servers, turn_servers) (stun_servers, turn_servers)
} }
} }

View File

@@ -199,15 +199,22 @@ impl PeerConnection {
pc.on_data_channel(Box::new(move |dc: Arc<RTCDataChannel>| { pc.on_data_channel(Box::new(move |dc: Arc<RTCDataChannel>| {
let data_channel = data_channel.clone(); let data_channel = data_channel.clone();
let hid = hid_clone.clone(); let hid = hid_clone.clone();
let label = dc.label().to_string();
Box::pin(async move { Box::pin(async move {
info!("Data channel opened with HID support: {}", dc.label()); // Handle both reliable (hid) and unreliable (hid-unreliable) channels
let is_hid_channel = label == "hid" || label == "hid-unreliable";
// Store data channel if is_hid_channel {
info!("HID DataChannel opened: {} (unreliable: {})", label, label == "hid-unreliable");
// Store the reliable data channel for sending responses
if label == "hid" {
*data_channel.write().await = Some(dc.clone()); *data_channel.write().await = Some(dc.clone());
}
// Set up message handler with HID processing // Set up message handler with HID processing
// Immediately spawn task in tokio runtime for low latency // Both channels use the same HID processing logic
dc.on_message(Box::new(move |msg: DataChannelMessage| { dc.on_message(Box::new(move |msg: DataChannelMessage| {
let hid = hid.clone(); let hid = hid.clone();
@@ -239,6 +246,9 @@ impl PeerConnection {
// Return empty future (actual work is spawned above) // Return empty future (actual work is spawned above)
Box::pin(async {}) Box::pin(async {})
})); }));
} else {
info!("Non-HID DataChannel opened: {}", label);
}
}) })
})); }));

View File

@@ -256,12 +256,6 @@ export const extensionsApi = {
// ===== RustDesk 配置 API ===== // ===== RustDesk 配置 API =====
/** 公共服务器信息 */
export interface PublicServerInfo {
server: string
public_key: string
}
/** RustDesk 配置响应 */ /** RustDesk 配置响应 */
export interface RustDeskConfigResponse { export interface RustDeskConfigResponse {
enabled: boolean enabled: boolean
@@ -271,7 +265,6 @@ export interface RustDeskConfigResponse {
has_password: boolean has_password: boolean
has_keypair: boolean has_keypair: boolean
has_relay_key: boolean has_relay_key: boolean
using_public_server: boolean
} }
/** RustDesk 状态响应 */ /** RustDesk 状态响应 */
@@ -279,7 +272,6 @@ export interface RustDeskStatusResponse {
config: RustDeskConfigResponse config: RustDeskConfigResponse
service_status: string service_status: string
rendezvous_status: string | null rendezvous_status: string | null
public_server: PublicServerInfo | null
} }
/** RustDesk 配置更新 */ /** RustDesk 配置更新 */

View File

@@ -570,18 +570,17 @@ export default {
// WebRTC / ICE // WebRTC / ICE
webrtcSettings: 'WebRTC Settings', webrtcSettings: 'WebRTC Settings',
webrtcSettingsDesc: 'Configure STUN/TURN servers for NAT traversal', webrtcSettingsDesc: 'Configure STUN/TURN servers for NAT traversal',
usingPublicIceServers: 'Using public ICE servers', publicIceServersHint: 'Empty uses Google public STUN, configure your own TURN for production',
publicIceServersHint: 'Leave empty to use built-in public STUN/TURN servers for NAT traversal',
stunServer: 'STUN Server', stunServer: 'STUN Server',
stunServerPlaceholder: 'stun:stun.l.google.com:19302', stunServerPlaceholder: 'stun:stun.l.google.com:19302',
stunServerHint: 'Custom STUN server (leave empty to use public server)', stunServerHint: 'Custom STUN server (leave empty to use Google public server)',
turnServer: 'TURN Server', turnServer: 'TURN Server',
turnServerPlaceholder: 'turn:turn.example.com:3478', turnServerPlaceholder: 'turn:turn.example.com:3478',
turnServerHint: 'Custom TURN relay server (leave empty to use public server)', turnServerHint: 'Custom TURN relay server (required for production)',
turnUsername: 'TURN Username', turnUsername: 'TURN Username',
turnPassword: 'TURN Password', turnPassword: 'TURN Password',
turnPasswordConfigured: 'Password already configured. Leave empty to keep current password.', turnPasswordConfigured: 'Password already configured. Leave empty to keep current password.',
turnCredentialsHint: 'Credentials for TURN server authentication (only needed for custom servers)', turnCredentialsHint: 'Credentials for TURN server authentication',
iceConfigNote: 'Note: Changes require reconnecting the WebRTC session to take effect.', iceConfigNote: 'Note: Changes require reconnecting the WebRTC session to take effect.',
}, },
virtualKeyboard: { virtualKeyboard: {
@@ -703,7 +702,7 @@ export default {
serverSettings: 'Server Settings', serverSettings: 'Server Settings',
rendezvousServer: 'ID Server', rendezvousServer: 'ID Server',
rendezvousServerPlaceholder: 'hbbs.example.com:21116', rendezvousServerPlaceholder: 'hbbs.example.com:21116',
rendezvousServerHint: 'Leave empty to use public server', rendezvousServerHint: 'Configure your RustDesk server address',
relayServer: 'Relay Server', relayServer: 'Relay Server',
relayServerPlaceholder: 'hbbr.example.com:21117', relayServerPlaceholder: 'hbbr.example.com:21117',
relayServerHint: 'Relay server address, auto-derived from ID server if empty', relayServerHint: 'Relay server address, auto-derived from ID server if empty',
@@ -711,10 +710,6 @@ export default {
relayKeyPlaceholder: 'Enter relay server key', relayKeyPlaceholder: 'Enter relay server key',
relayKeySet: '••••••••', relayKeySet: '••••••••',
relayKeyHint: 'Authentication key for relay server (if server uses -k option)', relayKeyHint: 'Authentication key for relay server (if server uses -k option)',
publicServerInfo: 'Public Server Info',
publicServerAddress: 'Server Address',
publicServerKey: 'Connection Key',
usingPublicServer: 'Using public server',
deviceInfo: 'Device Info', deviceInfo: 'Device Info',
deviceId: 'Device ID', deviceId: 'Device ID',
deviceIdHint: 'Use this ID in RustDesk client to connect', deviceIdHint: 'Use this ID in RustDesk client to connect',

View File

@@ -570,18 +570,17 @@ export default {
// WebRTC / ICE // WebRTC / ICE
webrtcSettings: 'WebRTC 设置', webrtcSettings: 'WebRTC 设置',
webrtcSettingsDesc: '配置 STUN/TURN 服务器以实现 NAT 穿透', webrtcSettingsDesc: '配置 STUN/TURN 服务器以实现 NAT 穿透',
usingPublicIceServers: '正在使用公共 ICE 服务器', publicIceServersHint: '留空将使用 Google 公共 STUN 服务器TURN 服务器需自行配置',
publicIceServersHint: '留空以使用内置的公共 STUN/TURN 服务器进行 NAT 穿透',
stunServer: 'STUN 服务器', stunServer: 'STUN 服务器',
stunServerPlaceholder: 'stun:stun.l.google.com:19302', stunServerPlaceholder: 'stun:stun.l.google.com:19302',
stunServerHint: '自定义 STUN 服务器(留空则使用公共服务器)', stunServerHint: '自定义 STUN 服务器(留空则使用 Google 公共服务器)',
turnServer: 'TURN 服务器', turnServer: 'TURN 服务器',
turnServerPlaceholder: 'turn:turn.example.com:3478', turnServerPlaceholder: 'turn:turn.example.com:3478',
turnServerHint: '自定义 TURN 中继服务器(留空则使用公共服务器', turnServerHint: '自定义 TURN 中继服务器(生产环境必须配置',
turnUsername: 'TURN 用户名', turnUsername: 'TURN 用户名',
turnPassword: 'TURN 密码', turnPassword: 'TURN 密码',
turnPasswordConfigured: '密码已配置。留空则保持当前密码。', turnPasswordConfigured: '密码已配置。留空则保持当前密码。',
turnCredentialsHint: '用于 TURN 服务器认证的凭据(仅自定义服务器需要)', turnCredentialsHint: '用于 TURN 服务器认证的凭据',
iceConfigNote: '注意:更改后需要重新连接 WebRTC 会话才能生效。', iceConfigNote: '注意:更改后需要重新连接 WebRTC 会话才能生效。',
}, },
virtualKeyboard: { virtualKeyboard: {
@@ -703,7 +702,7 @@ export default {
serverSettings: '服务器设置', serverSettings: '服务器设置',
rendezvousServer: 'ID 服务器', rendezvousServer: 'ID 服务器',
rendezvousServerPlaceholder: 'hbbs.example.com:21116', rendezvousServerPlaceholder: 'hbbs.example.com:21116',
rendezvousServerHint: '留空则使用公共服务器', rendezvousServerHint: '请配置您的 RustDesk 服务器地址',
relayServer: '中继服务器', relayServer: '中继服务器',
relayServerPlaceholder: 'hbbr.example.com:21117', relayServerPlaceholder: 'hbbr.example.com:21117',
relayServerHint: '中继服务器地址,留空则自动从 ID 服务器推导', relayServerHint: '中继服务器地址,留空则自动从 ID 服务器推导',
@@ -711,10 +710,6 @@ export default {
relayKeyPlaceholder: '输入中继服务器密钥', relayKeyPlaceholder: '输入中继服务器密钥',
relayKeySet: '••••••••', relayKeySet: '••••••••',
relayKeyHint: '中继服务器认证密钥(如果服务器使用 -k 选项)', relayKeyHint: '中继服务器认证密钥(如果服务器使用 -k 选项)',
publicServerInfo: '公共服务器信息',
publicServerAddress: '服务器地址',
publicServerKey: '连接密钥',
usingPublicServer: '正在使用公共服务器',
deviceInfo: '设备信息', deviceInfo: '设备信息',
deviceId: '设备 ID', deviceId: '设备 ID',
deviceIdHint: '此 ID 用于 RustDesk 客户端连接', deviceIdHint: '此 ID 用于 RustDesk 客户端连接',

View File

@@ -314,9 +314,8 @@ export interface RustDeskConfig {
/** Enable RustDesk protocol */ /** Enable RustDesk protocol */
enabled: boolean; enabled: boolean;
/** /**
* Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100" * Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100:21116"
* Port defaults to 21116 if not specified * Required for RustDesk to function
* If empty, uses the public server from secrets.toml
*/ */
rendezvous_server: string; rendezvous_server: string;
/** /**
@@ -517,14 +516,6 @@ export interface MsdConfigUpdate {
virtual_drive_size_mb?: number; virtual_drive_size_mb?: number;
} }
/** Public server information for display to users */
export interface PublicServerInfo {
/** Public server address */
server: string;
/** Public key for client connection */
public_key: string;
}
export interface RustDeskConfigUpdate { export interface RustDeskConfigUpdate {
enabled?: boolean; enabled?: boolean;
rendezvous_server?: string; rendezvous_server?: string;

View File

@@ -105,6 +105,12 @@ const mousePosition = ref({ x: 0, y: 0 })
const lastMousePosition = ref({ x: 0, y: 0 }) // Track last position for relative mode const lastMousePosition = ref({ x: 0, y: 0 }) // Track last position for relative mode
const isPointerLocked = ref(false) // Track pointer lock state const isPointerLocked = ref(false) // Track pointer lock state
// Mouse move throttling (60 Hz = ~16.67ms interval)
const MOUSE_SEND_INTERVAL_MS = 16
let mouseSendTimer: ReturnType<typeof setInterval> | null = null
let pendingMouseMove: { type: 'move' | 'move_abs'; x: number; y: number } | null = null
let accumulatedDelta = { x: 0, y: 0 } // For relative mode: accumulate deltas between sends
// Cursor visibility (from localStorage, updated via storage event) // Cursor visibility (from localStorage, updated via storage event)
const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false') const cursorVisible = ref(localStorage.getItem('hidShowCursor') !== 'false')
@@ -1479,20 +1485,21 @@ function handleMouseMove(e: MouseEvent) {
const y = Math.round((e.clientY - rect.top) / rect.height * 32767) const y = Math.round((e.clientY - rect.top) / rect.height * 32767)
mousePosition.value = { x, y } mousePosition.value = { x, y }
sendMouseEvent({ type: 'move_abs', x, y }) // Queue for throttled sending (absolute mode: just update pending position)
pendingMouseMove = { type: 'move_abs', x, y }
ensureMouseSendTimer()
} else { } else {
// Relative mode: use movementX/Y when pointer is locked // Relative mode: use movementX/Y when pointer is locked
if (isPointerLocked.value) { if (isPointerLocked.value) {
const dx = e.movementX const dx = e.movementX
const dy = e.movementY const dy = e.movementY
// Only send if there's actual movement // Only accumulate if there's actual movement
if (dx !== 0 || dy !== 0) { if (dx !== 0 || dy !== 0) {
// Clamp to i8 range (-127 to 127) // Accumulate deltas for throttled sending
const clampedDx = Math.max(-127, Math.min(127, dx)) accumulatedDelta.x += dx
const clampedDy = Math.max(-127, Math.min(127, dy)) accumulatedDelta.y += dy
ensureMouseSendTimer()
sendMouseEvent({ type: 'move', x: clampedDx, y: clampedDy })
} }
// Update display position (accumulated delta for display only) // Update display position (accumulated delta for display only)
@@ -1504,6 +1511,50 @@ function handleMouseMove(e: MouseEvent) {
} }
} }
// Start the mouse send timer if not already running
function ensureMouseSendTimer() {
if (mouseSendTimer !== null) return
// Send immediately on first event, then throttle
flushMouseMove()
mouseSendTimer = setInterval(() => {
if (!flushMouseMove()) {
// No pending data, stop the timer
if (mouseSendTimer !== null) {
clearInterval(mouseSendTimer)
mouseSendTimer = null
}
}
}, MOUSE_SEND_INTERVAL_MS)
}
// Flush pending mouse move data, returns true if data was sent
function flushMouseMove(): boolean {
if (mouseMode.value === 'absolute') {
if (pendingMouseMove) {
sendMouseEvent(pendingMouseMove)
pendingMouseMove = null
return true
}
} else {
// Relative mode: send accumulated delta
if (accumulatedDelta.x !== 0 || accumulatedDelta.y !== 0) {
// Clamp to i8 range (-127 to 127)
const clampedDx = Math.max(-127, Math.min(127, accumulatedDelta.x))
const clampedDy = Math.max(-127, Math.min(127, accumulatedDelta.y))
sendMouseEvent({ type: 'move', x: clampedDx, y: clampedDy })
// Subtract sent amount (keep remainder for next send if clamped)
accumulatedDelta.x -= clampedDx
accumulatedDelta.y -= clampedDy
return true
}
}
return false
}
// Track pressed mouse button for window-level mouseup handling // Track pressed mouse button for window-level mouseup handling
const pressedMouseButton = ref<'left' | 'right' | 'middle' | null>(null) const pressedMouseButton = ref<'left' | 'right' | 'middle' | null>(null)
@@ -1746,6 +1797,12 @@ onUnmounted(() => {
// Reset initial device info flag // Reset initial device info flag
initialDeviceInfoReceived = false initialDeviceInfoReceived = false
// Clear mouse send timer
if (mouseSendTimer !== null) {
clearInterval(mouseSendTimer)
mouseSendTimer = null
}
// Clear ttyd poll interval // Clear ttyd poll interval
if (ttydPollInterval) { if (ttydPollInterval) {
clearInterval(ttydPollInterval) clearInterval(ttydPollInterval)

View File

@@ -47,12 +47,6 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '@/components/ui/dialog' } from '@/components/ui/dialog'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { import {
Monitor, Monitor,
Keyboard, Keyboard,
@@ -82,7 +76,6 @@ import {
ExternalLink, ExternalLink,
Copy, Copy,
ScreenShare, ScreenShare,
CircleHelp,
} from 'lucide-vue-next' } from 'lucide-vue-next'
const { t, locale } = useI18n() const { t, locale } = useI18n()
@@ -2094,28 +2087,7 @@ onMounted(async () => {
v-model="rustdeskLocalConfig.rendezvous_server" v-model="rustdeskLocalConfig.rendezvous_server"
:placeholder="t('extensions.rustdesk.rendezvousServerPlaceholder')" :placeholder="t('extensions.rustdesk.rendezvousServerPlaceholder')"
/> />
<div class="flex items-center gap-1">
<p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.rendezvousServerHint') }}</p> <p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.rendezvousServerHint') }}</p>
<TooltipProvider v-if="rustdeskStatus?.public_server">
<Tooltip>
<TooltipTrigger as-child>
<CircleHelp class="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent side="right" class="max-w-xs">
<div class="space-y-1.5 text-xs">
<p class="font-medium">{{ t('extensions.rustdesk.publicServerInfo') }}</p>
<div class="space-y-1">
<p><span class="text-muted-foreground">{{ t('extensions.rustdesk.publicServerAddress') }}:</span> {{ rustdeskStatus.public_server.server }}</p>
<p><span class="text-muted-foreground">{{ t('extensions.rustdesk.publicServerKey') }}:</span> <code class="text-[10px] break-all">{{ rustdeskStatus.public_server.public_key }}</code></p>
</div>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<p v-if="rustdeskStatus?.config?.using_public_server" class="text-xs text-blue-500">
{{ t('extensions.rustdesk.usingPublicServer') }}
</p>
</div> </div>
</div> </div>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">