├── .cargo └── config.toml ├── .env ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build.yaml │ ├── ghcr.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README-DE.md ├── README-EN.md ├── README-NL.md ├── README-TW.md ├── README.md ├── build.rs ├── db_v2.sqlite3 ├── debian ├── changelog ├── compat ├── control.tpl ├── copyright ├── rules ├── rustdesk-server-hbbr.install ├── rustdesk-server-hbbr.postinst ├── rustdesk-server-hbbr.postrm ├── rustdesk-server-hbbr.prerm ├── rustdesk-server-hbbs.install ├── rustdesk-server-hbbs.postinst ├── rustdesk-server-hbbs.postrm ├── rustdesk-server-hbbs.prerm ├── rustdesk-server-utils.install └── source │ └── format ├── docker-classic └── Dockerfile ├── docker-compose.yml ├── docker ├── Dockerfile ├── healthcheck.sh └── rootfs │ ├── etc │ └── s6-overlay │ │ └── s6-rc.d │ │ ├── api │ │ ├── dependencies │ │ ├── run │ │ └── type │ │ ├── hbbr │ │ ├── dependencies │ │ ├── run │ │ └── type │ │ ├── hbbs │ │ ├── dependencies │ │ ├── run │ │ └── type │ │ ├── key-secret │ │ ├── type │ │ ├── up │ │ └── up.real │ │ └── user │ │ └── contents.d │ │ ├── api │ │ ├── hbbr │ │ ├── hbbs │ │ └── key-secret │ └── usr │ └── bin │ └── healthcheck.sh ├── libs └── hbb_common ├── rcd ├── rustdesk-hbbr └── rustdesk-hbbs ├── readme ├── api.png └── command_simple.png ├── src ├── common.rs ├── database.rs ├── hbbr.rs ├── jwt.rs ├── lib.rs ├── main.rs ├── mod.rs ├── peer.rs ├── relay_server.rs ├── rendezvous_server.rs └── utils.rs ├── systemd ├── rustdesk-hbbr.service └── rustdesk-hbbs.service ├── tests └── jwt_tests.rs └── ui ├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── html ├── .gitignore ├── index.html ├── main.js ├── package.json ├── style.css └── vite.config.js ├── icons ├── 128x128.png ├── 128x128@2x.png ├── 32x32.png ├── Square107x107Logo.png ├── Square142x142Logo.png ├── Square150x150Logo.png ├── Square284x284Logo.png ├── Square30x30Logo.png ├── Square310x310Logo.png ├── Square44x44Logo.png ├── Square71x71Logo.png ├── Square89x89Logo.png ├── StoreLogo.png ├── icon.icns ├── icon.ico └── icon.png ├── setup.nsi ├── setup └── service │ ├── nssm.exe │ └── run.cmd ├── src ├── adapter │ ├── mod.rs │ ├── service │ │ ├── mod.rs │ │ └── windows.rs │ └── view │ │ ├── desktop.rs │ │ └── mod.rs ├── lib.rs ├── main.rs └── usecase │ ├── mod.rs │ ├── presenter.rs │ ├── service.rs │ ├── view.rs │ └── watcher.rs └── tauri.conf.json /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-Ctarget-feature=+crt-static"] 3 | [target.i686-pc-windows-msvc] 4 | rustflags = ["-Ctarget-feature=+crt-static"] 5 | [target.'cfg(target_os="macos")'] 6 | rustflags = [ 7 | "-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null", 8 | ] -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=sqlite://./db_v2.sqlite3 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Describe the environment** 14 | - Install environment: docker, docker swarm, podman, kubernetes, or package 15 | - If available, the `docker-compose.yaml` file 16 | - If package, we need the distribution and release: Ubuntu 22.04, Debian 11, ... 17 | - Or if you're running the plain binary, how you're running it 18 | - In any case, you have to specify the version in use 19 | 20 | **How to Reproduce the bug** 21 | Steps to reproduce the behavior: 22 | 1. Given the previously described environment 23 | 2. Do this and that 24 | 3. I get this error 25 | 26 | **Expected behavior** 27 | This should happen instead. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | 32 | **Notes** 33 | - Please write in english only. If you provide some images in different languages, you're required to write a translation in english. 34 | - In any case, **NEVER** put here the content if your `id_ed25519` file 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context about the feature request here. 21 | 22 | **Notes** 23 | - Please write in english only. If you provide some images in different languages, you're required to write a translation in english. 24 | - In any case, **NEVER** put here the content if your `id_ed25519` file 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gitsubmodule" 4 | directory: "/" 5 | target-branch: "master" 6 | schedule: 7 | interval: "daily" 8 | commit-message: 9 | prefix: "Git submodule" 10 | labels: 11 | - "dependencies" 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | # ------------- NOTE 4 | # please setup some secrets before running this workflow: 5 | # DOCKER_IMAGE should be the target image name on docker hub (e.g. "rustdesk/rustdesk-server-s6" ) 6 | # DOCKER_IMAGE_CLASSIC should be the target image name on docker hub for the old build (e.g. "rustdesk/rustdesk-server" ) 7 | # DOCKER_USERNAME is the username you normally use to login at https://hub.docker.com/ 8 | # DOCKER_PASSWORD is a token you should create under "account settings / security" with read/write access 9 | 10 | on: 11 | workflow_dispatch: 12 | push: 13 | tags: 14 | - 'v[0-9]+.[0-9]+.[0-9]+' 15 | - '[0-9]+.[0-9]+.[0-9]+' 16 | - 'v[0-9]+.[0-9]+.[0-9]+-[0-9]+' 17 | - '[0-9]+.[0-9]+.[0-9]+-[0-9]+' 18 | 19 | env: 20 | CARGO_TERM_COLOR: always 21 | LATEST_TAG: latest 22 | 23 | jobs: 24 | 25 | # binary build 26 | build: 27 | 28 | name: Build - ${{ matrix.job.name }} 29 | runs-on: ubuntu-20.04 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | job: 34 | - { name: "amd64", target: "x86_64-unknown-linux-musl" } 35 | - { name: "arm64v8", target: "aarch64-unknown-linux-musl" } 36 | - { name: "armv7", target: "armv7-unknown-linux-musleabihf" } 37 | - { name: "i386", target: "i686-unknown-linux-musl" } 38 | #- { name: "amd64fb", target: "x86_64-unknown-freebsd" } 39 | 40 | steps: 41 | 42 | - name: Checkout 43 | uses: actions/checkout@v3 44 | with: 45 | submodules: recursive 46 | 47 | - name: Install toolchain 48 | uses: actions-rs/toolchain@v1 49 | with: 50 | toolchain: stable 51 | override: true 52 | default: true 53 | components: rustfmt 54 | profile: minimal 55 | target: ${{ matrix.job.target }} 56 | 57 | - name: Build 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: build 61 | args: --release --all-features --target=${{ matrix.job.target }} 62 | use-cross: true 63 | 64 | - name: Exec chmod 65 | run: chmod -v a+x target/${{ matrix.job.target }}/release/* 66 | 67 | - name: Publish Artifacts 68 | uses: actions/upload-artifact@v4 69 | with: 70 | name: binaries-linux-${{ matrix.job.name }} 71 | path: | 72 | target/${{ matrix.job.target }}/release/hbbr 73 | target/${{ matrix.job.target }}/release/hbbs 74 | target/${{ matrix.job.target }}/release/rustdesk-utils 75 | if-no-files-found: error 76 | 77 | build-win: 78 | name: Build - windows 79 | runs-on: windows-2019 80 | 81 | steps: 82 | 83 | - name: Checkout 84 | uses: actions/checkout@v3 85 | with: 86 | submodules: recursive 87 | 88 | - name: Install toolchain 89 | uses: actions-rs/toolchain@v1 90 | with: 91 | toolchain: stable 92 | override: true 93 | default: true 94 | components: rustfmt 95 | profile: minimal 96 | target: x86_64-pc-windows-msvc 97 | 98 | - name: Build 99 | uses: actions-rs/cargo@v1 100 | with: 101 | command: build 102 | args: --release --all-features --target=x86_64-pc-windows-msvc 103 | use-cross: true 104 | 105 | - name: Install NSIS 106 | run: | 107 | iwr -useb get.scoop.sh -outfile 'install.ps1' 108 | .\install.ps1 -RunAsAdmin 109 | scoop update 110 | scoop bucket add extras 111 | scoop install nsis 112 | 113 | - name: Install Node.js 114 | uses: actions/setup-node@v3 115 | with: 116 | node-version: 16 117 | 118 | - name: Sign exe files 119 | uses: GermanBluefox/code-sign-action@v7 120 | if: false 121 | with: 122 | certificate: '${{ secrets.WINDOWS_PFX_BASE64 }}' 123 | password: '${{ secrets.WINDOWS_PFX_PASSWORD }}' 124 | certificatesha1: '${{ secrets.WINDOWS_PFX_SHA1_THUMBPRINT }}' 125 | folder: 'target\x86_64-pc-windows-msvc\release' 126 | recursive: false 127 | 128 | - name: Build UI browser file 129 | run: | 130 | npm i 131 | npm run build 132 | working-directory: ./ui/html 133 | 134 | - name: Build UI setup file 135 | run: | 136 | rustup default nightly 137 | cargo build --release 138 | xcopy /y ..\target\x86_64-pc-windows-msvc\release\*.exe setup\bin\ 139 | xcopy /y target\release\*.exe setup\ 140 | mkdir setup\logs 141 | makensis /V1 setup.nsi 142 | mkdir SignOutput 143 | mv RustDeskServer.Setup.exe SignOutput\ 144 | mv ..\target\x86_64-pc-windows-msvc\release\*.exe SignOutput\ 145 | working-directory: ./ui 146 | 147 | - name: Sign UI setup file 148 | uses: GermanBluefox/code-sign-action@v7 149 | if: false 150 | with: 151 | certificate: '${{ secrets.WINDOWS_PFX_BASE64 }}' 152 | password: '${{ secrets.WINDOWS_PFX_PASSWORD }}' 153 | certificatesha1: '${{ secrets.WINDOWS_PFX_SHA1_THUMBPRINT }}' 154 | folder: './ui/SignOutput' 155 | recursive: false 156 | 157 | - name: Publish Artifacts 158 | uses: actions/upload-artifact@v4 159 | with: 160 | name: binaries-windows-x86_64 161 | path: | 162 | ui\SignOutput\hbbr.exe 163 | ui\SignOutput\hbbs.exe 164 | ui\SignOutput\rustdesk-utils.exe 165 | ui\SignOutput\RustDeskServer.Setup.exe 166 | if-no-files-found: error 167 | 168 | # github (draft) release with all binaries 169 | release: 170 | 171 | name: Github release 172 | needs: 173 | - build 174 | - build-win 175 | runs-on: ubuntu-20.04 176 | strategy: 177 | fail-fast: false 178 | matrix: 179 | job: 180 | - { os: "linux", name: "amd64", suffix: "" } 181 | - { os: "linux", name: "arm64v8", suffix: "" } 182 | - { os: "linux", name: "armv7", suffix: "" } 183 | - { os: "linux", name: "i386", suffix: "" } 184 | #- { os: "linux", name: "amd64fb", suffix: "" } 185 | - { os: "windows", name: "x86_64", suffix: "-unsigned" } 186 | 187 | steps: 188 | 189 | - name: Download binaries (${{ matrix.job.os }} - ${{ matrix.job.name }}) 190 | uses: actions/download-artifact@v4 191 | with: 192 | name: binaries-${{ matrix.job.os }}-${{ matrix.job.name }} 193 | path: ${{ matrix.job.name }} 194 | 195 | - name: Exec chmod 196 | run: chmod -v a+x ${{ matrix.job.name }}/* 197 | 198 | - name: Pack files (${{ matrix.job.os }} - ${{ matrix.job.name }}) 199 | run: | 200 | sudo apt update 201 | DEBIAN_FRONTEND=noninteractive sudo apt install -y zip 202 | zip ${{ matrix.job.name }}/rustdesk-server-${{ matrix.job.os }}-${{ matrix.job.name }}${{ matrix.job.suffix }}.zip ${{ matrix.job.name }}/* 203 | 204 | - name: Create Release (${{ matrix.job.os }} - (${{ matrix.job.name }}) 205 | uses: softprops/action-gh-release@v1 206 | with: 207 | draft: true 208 | files: ${{ matrix.job.name }}/rustdesk-server-${{ matrix.job.os }}-${{ matrix.job.name }}${{ matrix.job.suffix }}.zip 209 | 210 | # docker build and push of single-arch images 211 | docker: 212 | 213 | name: Docker push - ${{ matrix.job.name }} 214 | needs: build 215 | runs-on: ubuntu-22.04 216 | strategy: 217 | fail-fast: false 218 | matrix: 219 | job: 220 | - { name: "amd64", docker_platform: "linux/amd64", s6_platform: "x86_64" } 221 | - { name: "arm64v8", docker_platform: "linux/arm64", s6_platform: "aarch64" } 222 | - { name: "armv7", docker_platform: "linux/arm/v7", s6_platform: "armhf" } 223 | # - { name: "i386", docker_platform: "linux/386", s6_platform: "i686" } 224 | 225 | steps: 226 | 227 | - name: Checkout 228 | uses: actions/checkout@v3 229 | with: 230 | submodules: recursive 231 | 232 | - name: Download binaries 233 | uses: actions/download-artifact@v4 234 | with: 235 | name: binaries-linux-${{ matrix.job.name }} 236 | path: docker/rootfs/usr/bin 237 | 238 | - name: Make binaries executable 239 | run: chmod -v a+x docker/rootfs/usr/bin/* 240 | 241 | - name: Set up QEMU 242 | uses: docker/setup-qemu-action@v2 243 | 244 | - name: Set up Docker Buildx 245 | uses: docker/setup-buildx-action@v2 246 | 247 | - name: Log in to Docker Hub 248 | if: github.event_name != 'pull_request' 249 | uses: docker/login-action@v2 250 | with: 251 | username: ${{ secrets.DOCKER_USERNAME }} 252 | password: ${{ secrets.DOCKER_PASSWORD }} 253 | 254 | - name: Extract metadata (tags, labels) for Docker 255 | id: meta 256 | uses: docker/metadata-action@v4 257 | with: 258 | images: registry.hub.docker.com/${{ secrets.DOCKER_IMAGE }} 259 | 260 | - name: Get git tag 261 | id: vars 262 | run: | 263 | T=${GITHUB_REF#refs/*/} 264 | M=${T%%.*} 265 | echo "GIT_TAG=$T" >> $GITHUB_ENV 266 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 267 | 268 | - name: Build and push Docker image 269 | uses: docker/build-push-action@v5 270 | with: 271 | context: "./docker" 272 | platforms: ${{ matrix.job.docker_platform }} 273 | push: true 274 | provenance: false 275 | build-args: | 276 | S6_ARCH=${{ matrix.job.s6_platform }} 277 | tags: | 278 | ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }} 279 | ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-${{ matrix.job.name }} 280 | ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }} 281 | labels: ${{ steps.meta.outputs.labels }} 282 | 283 | # docker build and push of multiarch images 284 | docker-manifest: 285 | 286 | name: Docker manifest s6 287 | needs: docker 288 | runs-on: ubuntu-22.04 289 | 290 | steps: 291 | 292 | - name: Log in to Docker Hub 293 | if: github.event_name != 'pull_request' 294 | uses: docker/login-action@v2 295 | with: 296 | username: ${{ secrets.DOCKER_USERNAME }} 297 | password: ${{ secrets.DOCKER_PASSWORD }} 298 | 299 | - name: Get git tag 300 | id: vars 301 | run: | 302 | T=${GITHUB_REF#refs/*/} 303 | M=${T%%.*} 304 | echo "GIT_TAG=$T" >> $GITHUB_ENV 305 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 306 | 307 | # manifest for :1.2.3 tag 308 | # this has to run only if invoked by a new tag 309 | - name: Create and push manifest (:ve.rs.ion) 310 | uses: Noelware/docker-manifest-action@master 311 | if: github.event_name != 'workflow_dispatch' 312 | with: 313 | base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }} 314 | extra-images: | 315 | ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-amd64, 316 | ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-arm64v8, 317 | ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-armv7, 318 | # ${{ secrets.DOCKER_IMAGE }}:${{ env.GIT_TAG }}-i386 319 | push: true 320 | 321 | # manifest for :1 tag (major release) 322 | - name: Create and push manifest (:major) 323 | uses: Noelware/docker-manifest-action@master 324 | with: 325 | base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }} 326 | extra-images: | 327 | ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-amd64, 328 | ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-arm64v8, 329 | ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-armv7, 330 | # ${{ secrets.DOCKER_IMAGE }}:${{ env.MAJOR_TAG }}-i386 331 | push: true 332 | 333 | # manifest for :latest tag 334 | - name: Create and push manifest (:latest) 335 | uses: Noelware/docker-manifest-action@master 336 | with: 337 | base-image: ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }} 338 | extra-images: | 339 | ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-amd64, 340 | ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-arm64v8, 341 | ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-armv7, 342 | # ${{ secrets.DOCKER_IMAGE }}:${{ env.LATEST_TAG }}-i386 343 | push: true 344 | 345 | 346 | # docker build and push of single-arch images 347 | docker-classic: 348 | 349 | name: Docker push classic - ${{ matrix.job.name }} 350 | needs: build 351 | runs-on: ubuntu-22.04 352 | strategy: 353 | fail-fast: false 354 | matrix: 355 | job: 356 | - { name: "amd64", docker_platform: "linux/amd64" } 357 | - { name: "arm64v8", docker_platform: "linux/arm64" } 358 | - { name: "armv7", docker_platform: "linux/arm/v7" } 359 | 360 | steps: 361 | 362 | - name: Checkout 363 | uses: actions/checkout@v3 364 | with: 365 | submodules: recursive 366 | 367 | - name: Download binaries 368 | uses: actions/download-artifact@v4 369 | with: 370 | name: binaries-linux-${{ matrix.job.name }} 371 | path: docker-classic/ 372 | 373 | - name: Make binaries executable 374 | run: chmod -v a+x docker-classic/* 375 | 376 | - name: Set up QEMU 377 | uses: docker/setup-qemu-action@v2 378 | 379 | - name: Set up Docker Buildx 380 | uses: docker/setup-buildx-action@v2 381 | 382 | - name: Log in to Docker Hub 383 | if: github.event_name != 'pull_request' 384 | uses: docker/login-action@v2 385 | with: 386 | username: ${{ secrets.DOCKER_USERNAME }} 387 | password: ${{ secrets.DOCKER_PASSWORD }} 388 | 389 | - name: Extract metadata (tags, labels) for Docker 390 | id: meta 391 | uses: docker/metadata-action@v4 392 | with: 393 | images: registry.hub.docker.com/${{ secrets.DOCKER_IMAGE_CLASSIC }} 394 | 395 | - name: Get git tag 396 | id: vars 397 | run: | 398 | T=${GITHUB_REF#refs/*/} 399 | M=${T%%.*} 400 | echo "GIT_TAG=$T" >> $GITHUB_ENV 401 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 402 | 403 | - name: Build and push Docker image 404 | uses: docker/build-push-action@v5 405 | with: 406 | context: "./docker-classic" 407 | platforms: ${{ matrix.job.docker_platform }} 408 | push: true 409 | provenance: false 410 | tags: | 411 | ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }} 412 | ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-${{ matrix.job.name }} 413 | ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }} 414 | labels: ${{ steps.meta.outputs.labels }} 415 | 416 | # docker build and push of multiarch images 417 | docker-manifest-classic: 418 | 419 | name: Docker manifest classic 420 | needs: docker 421 | runs-on: ubuntu-22.04 422 | steps: 423 | 424 | - name: Log in to Docker Hub 425 | if: github.event_name != 'pull_request' 426 | uses: docker/login-action@v2 427 | with: 428 | username: ${{ secrets.DOCKER_USERNAME }} 429 | password: ${{ secrets.DOCKER_PASSWORD }} 430 | 431 | - name: Get git tag 432 | id: vars 433 | run: | 434 | T=${GITHUB_REF#refs/*/} 435 | M=${T%%.*} 436 | echo "GIT_TAG=$T" >> $GITHUB_ENV 437 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 438 | 439 | # manifest for :1.2.3 tag 440 | # this has to run only if invoked by a new tag 441 | - name: Create and push manifest (:ve.rs.ion) 442 | uses: Noelware/docker-manifest-action@master 443 | if: github.event_name != 'workflow_dispatch' 444 | with: 445 | base-image: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }} 446 | extra-images: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-amd64,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.GIT_TAG }}-armv7 447 | push: true 448 | 449 | # manifest for :1 tag (major release) 450 | - name: Create and push manifest (:major) 451 | uses: Noelware/docker-manifest-action@master 452 | with: 453 | base-image: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }} 454 | extra-images: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-amd64,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.MAJOR_TAG }}-armv7 455 | push: true 456 | 457 | # manifest for :latest tag 458 | - name: Create and push manifest (:latest) 459 | uses: Noelware/docker-manifest-action@master 460 | with: 461 | base-image: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }} 462 | extra-images: ${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-amd64,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-arm64v8,${{ secrets.DOCKER_IMAGE_CLASSIC }}:${{ env.LATEST_TAG }}-armv7 463 | push: true 464 | 465 | 466 | deb-package: 467 | 468 | name: debian package - ${{ matrix.job.name }} 469 | needs: build 470 | runs-on: ubuntu-22.04 471 | strategy: 472 | fail-fast: false 473 | matrix: 474 | job: 475 | - { name: "amd64", debian_platform: "amd64", crossbuild_package: "" } 476 | - { name: "arm64v8", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" } 477 | - { name: "armv7", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" } 478 | - { name: "i386", debian_platform: "i386", crossbuild_package: "crossbuild-essential-i386" } 479 | 480 | steps: 481 | 482 | - name: Checkout 483 | uses: actions/checkout@v3 484 | with: 485 | submodules: recursive 486 | 487 | - name: Set up QEMU 488 | uses: docker/setup-qemu-action@v2 489 | 490 | - name: Create packaging env 491 | run: | 492 | sudo apt update 493 | DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }} 494 | mkdir -p debian-build/${{ matrix.job.name }}/bin 495 | 496 | - name: Download binaries 497 | uses: actions/download-artifact@v4 498 | with: 499 | name: binaries-linux-${{ matrix.job.name }} 500 | path: debian-build/${{ matrix.job.name }}/bin 501 | 502 | - name: Build package for ${{ matrix.job.name }} arch 503 | run: | 504 | chmod -v a+x debian-build/${{ matrix.job.name }}/bin/* 505 | cp -vr debian systemd debian-build/${{ matrix.job.name }}/ 506 | cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.name }}/debian/control 507 | cd debian-build/${{ matrix.job.name }}/ 508 | debuild -i -us -uc -b -a${{ matrix.job.debian_platform }} 509 | 510 | - name: Create Release 511 | uses: softprops/action-gh-release@v1 512 | with: 513 | draft: true 514 | files: | 515 | debian-build/rustdesk-server-hbbr_*_${{ matrix.job.debian_platform }}.deb 516 | debian-build/rustdesk-server-hbbs_*_${{ matrix.job.debian_platform }}.deb 517 | debian-build/rustdesk-server-utils_*_${{ matrix.job.debian_platform }}.deb 518 | -------------------------------------------------------------------------------- /.github/workflows/ghcr.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish to ghcr.io 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v[0-9]+.[0-9]+.[0-9]+' 8 | - '[0-9]+.[0-9]+.[0-9]+' 9 | - 'v[0-9]+.[0-9]+.[0-9]+-[0-9]+' 10 | - '[0-9]+.[0-9]+.[0-9]+-[0-9]+' 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | LATEST_TAG: latest 15 | 16 | permissions: 17 | contents: read 18 | packages: write # So need to set "secrets.GITHUB_TOKEN" 19 | 20 | jobs: 21 | 22 | # Binary build 23 | build: 24 | name: Build - ${{ matrix.job.name }} 25 | runs-on: ubuntu-24.04 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | job: 30 | - { name: "amd64", target: "x86_64-unknown-linux-musl" } 31 | - { name: "arm64v8", target: "aarch64-unknown-linux-musl" } 32 | - { name: "armv7", target: "armv7-unknown-linux-musleabihf" } 33 | - { name: "i386", target: "i686-unknown-linux-musl" } 34 | #- { name: "amd64fb", target: "x86_64-unknown-freebsd" } 35 | 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | with: 40 | submodules: recursive 41 | 42 | - name: Install Rust toolchain 43 | uses: dtolnay/rust-toolchain@v1 44 | with: 45 | toolchain: stable 46 | targets: ${{ matrix.job.target }} 47 | components: "rustfmt" 48 | 49 | - uses: Swatinem/rust-cache@v2 50 | with: 51 | prefix-key: ${{ matrix.job.os }} 52 | 53 | - name: Build 54 | uses: actions-rs/cargo@v1 55 | with: 56 | command: build 57 | args: --release --all-features --target=${{ matrix.job.target }} 58 | use-cross: true 59 | 60 | - name: Exec chmod 61 | run: chmod -v a+x target/${{ matrix.job.target }}/release/* 62 | 63 | - name: Publish Artifacts 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: binaries-linux-${{ matrix.job.name }} 67 | path: | 68 | target/${{ matrix.job.target }}/release/hbbr 69 | target/${{ matrix.job.target }}/release/hbbs 70 | target/${{ matrix.job.target }}/release/rustdesk-utils 71 | if-no-files-found: error 72 | 73 | # Build and push single-arch Docker images to ghcr.io 74 | create-s6-overlay-images: 75 | name: Docker push s6-overlay - ${{ matrix.job.name }} 76 | needs: build 77 | runs-on: ubuntu-24.04 78 | strategy: 79 | fail-fast: false 80 | matrix: 81 | job: 82 | - { name: "amd64", docker_platform: "linux/amd64", s6_platform: "x86_64" } 83 | - { name: "arm64v8", docker_platform: "linux/arm64", s6_platform: "aarch64" } 84 | - { name: "armv7", docker_platform: "linux/arm/v7", s6_platform: "armhf" } 85 | # - { name: "i386", docker_platform: "linux/386", s6_platform: "i686" } 86 | 87 | steps: 88 | - name: Checkout 89 | uses: actions/checkout@v4 90 | with: 91 | submodules: recursive 92 | 93 | - name: Download binaries 94 | uses: actions/download-artifact@v4 95 | with: 96 | pattern: binaries-linux-${{ matrix.job.name }} 97 | path: docker/rootfs/usr/bin 98 | merge-multiple: true 99 | 100 | - name: Make binaries executable 101 | run: chmod -v a+x docker/rootfs/usr/bin/* 102 | 103 | - name: Set up QEMU 104 | uses: docker/setup-qemu-action@v3 105 | 106 | - name: Set up Docker Buildx 107 | uses: docker/setup-buildx-action@v3 108 | 109 | - name: Log in to GitHub Container Registry 110 | if: github.event_name != 'pull_request' 111 | uses: docker/login-action@v3 112 | with: 113 | registry: ghcr.io 114 | username: ${{ github.actor }} 115 | password: ${{ secrets.GITHUB_TOKEN }} 116 | 117 | - name: Extract metadata (tags, labels) for Docker 118 | id: meta 119 | uses: docker/metadata-action@v5 120 | with: 121 | images: ghcr.io/${{ github.repository }}-s6 122 | 123 | - name: Get git tag 124 | id: vars 125 | run: | 126 | T=${GITHUB_REF#refs/*/} 127 | M=${T%%.*} 128 | echo "GIT_TAG=$T" >> $GITHUB_ENV 129 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 130 | 131 | - name: Build and push Docker image 132 | uses: docker/build-push-action@v6 133 | with: 134 | context: "./docker" 135 | platforms: ${{ matrix.job.docker_platform }} 136 | push: true 137 | provenance: false 138 | build-args: | 139 | S6_ARCH=${{ matrix.job.s6_platform }} 140 | tags: | 141 | ghcr.io/${{ github.repository }}-s6:${{ env.LATEST_TAG }}-${{ matrix.job.name }} 142 | ghcr.io/${{ github.repository }}-s6:${{ env.GIT_TAG }}-${{ matrix.job.name }} 143 | ghcr.io/${{ github.repository }}-s6:${{ env.MAJOR_TAG }}-${{ matrix.job.name }} 144 | labels: ${{ steps.meta.outputs.labels }} 145 | 146 | # Set up minifest and tag for pushed image 147 | create-s6-overlay-images-manifest: 148 | name: Manifest for s6-overlay images 149 | needs: create-s6-overlay-images 150 | runs-on: ubuntu-24.04 151 | 152 | steps: 153 | - name: Log in to GitHub Container Registry 154 | if: github.event_name != 'pull_request' 155 | uses: docker/login-action@v3 156 | with: 157 | registry: ghcr.io 158 | username: ${{ github.actor }} 159 | password: ${{ secrets.GITHUB_TOKEN }} 160 | 161 | - name: Get git tag 162 | id: vars 163 | run: | 164 | T=${GITHUB_REF#refs/*/} 165 | M=${T%%.*} 166 | echo "GIT_TAG=$T" >> $GITHUB_ENV 167 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 168 | 169 | # Create and push manifest for :ve.rs.ion tag 170 | - name: Create and push manifest (:ve.rs.ion) 171 | uses: Noelware/docker-manifest-action@master 172 | if: github.event_name != 'workflow_dispatch' 173 | with: 174 | base-image: ghcr.io/${{ github.repository }}-s6:${{ env.GIT_TAG }} 175 | extra-images: | 176 | ghcr.io/${{ github.repository }}-s6:${{ env.GIT_TAG }}-amd64, 177 | ghcr.io/${{ github.repository }}-s6:${{ env.GIT_TAG }}-arm64v8, 178 | ghcr.io/${{ github.repository }}-s6:${{ env.GIT_TAG }}-armv7, 179 | # ghcr.io/${{ github.repository }}-s6:${{ env.GIT_TAG }}-i386 180 | push: true 181 | 182 | # Create and push manifest for :major tag 183 | - name: Create and push manifest (:major) 184 | uses: Noelware/docker-manifest-action@master 185 | with: 186 | base-image: ghcr.io/${{ github.repository }}-s6:${{ env.MAJOR_TAG }} 187 | extra-images: | 188 | ghcr.io/${{ github.repository }}-s6:${{ env.MAJOR_TAG }}-amd64, 189 | ghcr.io/${{ github.repository }}-s6:${{ env.MAJOR_TAG }}-arm64v8, 190 | ghcr.io/${{ github.repository }}-s6:${{ env.MAJOR_TAG }}-armv7, 191 | # ghcr.io/${{ github.repository }}-s6:${{ env.MAJOR_TAG }}-i386 192 | push: true 193 | 194 | # Create and push manifest for :latest tag 195 | - name: Create and push manifest (:latest) 196 | uses: Noelware/docker-manifest-action@master 197 | with: 198 | base-image: ghcr.io/${{ github.repository }}-s6:${{ env.LATEST_TAG }} 199 | extra-images: | 200 | ghcr.io/${{ github.repository }}-s6:${{ env.LATEST_TAG }}-amd64, 201 | ghcr.io/${{ github.repository }}-s6:${{ env.LATEST_TAG }}-arm64v8, 202 | ghcr.io/${{ github.repository }}-s6:${{ env.LATEST_TAG }}-armv7, 203 | # ghcr.io/${{ github.repository }}-s6:${{ env.LATEST_TAG }}-i386 204 | push: true 205 | 206 | # Build and push single-arch Docker images to ghcr.io 207 | create-classic-images: 208 | name: Docker push classic - ${{ matrix.job.name }} 209 | needs: build 210 | runs-on: ubuntu-24.04 211 | strategy: 212 | fail-fast: false 213 | matrix: 214 | job: 215 | - { name: "amd64", docker_platform: "linux/amd64" } 216 | - { name: "arm64v8", docker_platform: "linux/arm64" } 217 | - { name: "armv7", docker_platform: "linux/arm/v7" } 218 | - { name: "i386", docker_platform: "linux/386" } 219 | 220 | steps: 221 | - name: Checkout 222 | uses: actions/checkout@v4 223 | with: 224 | submodules: recursive 225 | 226 | - name: Download binaries 227 | uses: actions/download-artifact@v4 228 | with: 229 | pattern: binaries-linux-${{ matrix.job.name }} 230 | path: docker-classic 231 | merge-multiple: true 232 | 233 | - name: Make binaries executable 234 | run: chmod -v a+x docker-classic/* 235 | 236 | - name: Set up QEMU 237 | uses: docker/setup-qemu-action@v3 238 | 239 | - name: Set up Docker Buildx 240 | uses: docker/setup-buildx-action@v3 241 | 242 | - name: Log in to GitHub Container Registry 243 | if: github.event_name != 'pull_request' 244 | uses: docker/login-action@v3 245 | with: 246 | registry: ghcr.io 247 | username: ${{ github.actor }} 248 | password: ${{ secrets.GITHUB_TOKEN }} 249 | 250 | - name: Extract metadata (tags, labels) for Docker 251 | id: meta 252 | uses: docker/metadata-action@v5 253 | with: 254 | images: ghcr.io/${{ github.repository }} 255 | 256 | - name: Get git tag 257 | id: vars 258 | run: | 259 | T=${GITHUB_REF#refs/*/} 260 | M=${T%%.*} 261 | echo "GIT_TAG=$T" >> $GITHUB_ENV 262 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 263 | 264 | - name: Build and push Docker image 265 | uses: docker/build-push-action@v6 266 | with: 267 | context: "./docker-classic" 268 | platforms: ${{ matrix.job.docker_platform }} 269 | push: true 270 | provenance: false 271 | tags: | 272 | ghcr.io/${{ github.repository }}:${{ env.LATEST_TAG }}-${{ matrix.job.name }} 273 | ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-${{ matrix.job.name }} 274 | ghcr.io/${{ github.repository }}:${{ env.MAJOR_TAG }}-${{ matrix.job.name }} 275 | labels: ${{ steps.meta.outputs.labels }} 276 | 277 | # Set up minifest and tag for pushed image 278 | create-classic-images-manifest: 279 | name: Manifest for classic images 280 | needs: create-classic-images 281 | runs-on: ubuntu-24.04 282 | 283 | steps: 284 | - name: Log in to GitHub Container Registry 285 | if: github.event_name != 'pull_request' 286 | uses: docker/login-action@v3 287 | with: 288 | registry: ghcr.io 289 | username: ${{ github.actor }} 290 | password: ${{ secrets.GITHUB_TOKEN }} 291 | 292 | - name: Get git tag 293 | id: vars 294 | run: | 295 | T=${GITHUB_REF#refs/*/} 296 | M=${T%%.*} 297 | echo "GIT_TAG=$T" >> $GITHUB_ENV 298 | echo "MAJOR_TAG=$M" >> $GITHUB_ENV 299 | 300 | # Create and push manifest for :ve.rs.ion tag 301 | - name: Create and push manifest (:ve.rs.ion) 302 | uses: Noelware/docker-manifest-action@master 303 | if: github.event_name != 'workflow_dispatch' 304 | with: 305 | base-image: ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }} 306 | extra-images: | 307 | ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-amd64, 308 | ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-arm64v8, 309 | ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-armv7, 310 | ghcr.io/${{ github.repository }}:${{ env.GIT_TAG }}-i386 311 | push: true 312 | 313 | # Create and push manifest for :major tag 314 | - name: Create and push manifest (:major) 315 | uses: Noelware/docker-manifest-action@master 316 | with: 317 | base-image: ghcr.io/${{ github.repository }}:${{ env.MAJOR_TAG }} 318 | extra-images: | 319 | ghcr.io/${{ github.repository }}:${{ env.MAJOR_TAG }}-amd64, 320 | ghcr.io/${{ github.repository }}:${{ env.MAJOR_TAG }}-arm64v8, 321 | ghcr.io/${{ github.repository }}:${{ env.MAJOR_TAG }}-armv7, 322 | ghcr.io/${{ github.repository }}:${{ env.MAJOR_TAG }}-i386 323 | push: true 324 | 325 | # Create and push manifest for :latest tag 326 | - name: Create and push manifest (:latest) 327 | uses: Noelware/docker-manifest-action@master 328 | with: 329 | base-image: ghcr.io/${{ github.repository }}:${{ env.LATEST_TAG }} 330 | extra-images: | 331 | ghcr.io/${{ github.repository }}:${{ env.LATEST_TAG }}-amd64, 332 | ghcr.io/${{ github.repository }}:${{ env.LATEST_TAG }}-arm64v8, 333 | ghcr.io/${{ github.repository }}:${{ env.LATEST_TAG }}-armv7, 334 | ghcr.io/${{ github.repository }}:${{ env.LATEST_TAG }}-i386 335 | push: true 336 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | paths-ignore: 7 | - '**/README.md' 8 | pull_request: 9 | branches: [ "master" ] 10 | paths-ignore: 11 | - '**/README.md' 12 | 13 | jobs: 14 | check: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: recursive 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | - uses: Swatinem/rust-cache@v2 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: check 29 | 30 | test: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | with: 35 | submodules: recursive 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | profile: minimal 39 | toolchain: stable 40 | override: true 41 | - uses: Swatinem/rust-cache@v2 42 | - uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: --all 46 | 47 | fmt: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | with: 52 | submodules: recursive 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: stable 57 | override: true 58 | components: rustfmt 59 | - uses: Swatinem/rust-cache@v2 60 | - uses: actions-rs/cargo@v1 61 | with: 62 | command: build 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | clippy: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v4 72 | with: 73 | submodules: recursive 74 | - uses: actions-rs/toolchain@v1 75 | with: 76 | profile: minimal 77 | toolchain: stable 78 | override: true 79 | components: clippy 80 | - uses: Swatinem/rust-cache@v2 81 | - uses: actions-rs/cargo@v1 82 | with: 83 | command: clippy 84 | args: --all -- -D warnings 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | id* 3 | db* 4 | debian-build 5 | debian/.debhelper 6 | debian/debhelper-build-stamp 7 | .DS_Store 8 | .vscode 9 | src/version.rs 10 | db_v2.sqlite3 11 | test.* 12 | .idea 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/hbb_common"] 2 | path = libs/hbb_common 3 | url = https://github.com/rustdesk/hbb_common 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust.checkWith": "clippy", 3 | "rust.formatOnSave": true, 4 | "rust.checkOnSave": true, 5 | "rust.useNewErrorFormat": true 6 | } 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hbbs" 3 | version = "1.1.14" 4 | authors = ["rustdesk "] 5 | edition = "2021" 6 | build = "build.rs" 7 | default-run = "hbbs" 8 | 9 | [[bin]] 10 | name = "hbbr" 11 | path = "src/hbbr.rs" 12 | 13 | [[bin]] 14 | name = "rustdesk-utils" 15 | path = "src/utils.rs" 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | hbb_common = { path = "libs/hbb_common" } 21 | serde_derive = "1.0" 22 | serde = "1.0" 23 | serde_json = "1.0" 24 | lazy_static = "1.4" 25 | clap = "2" 26 | rust-ini = "0.18" 27 | minreq = { version = "2.4", features = ["punycode"] } 28 | machine-uid = "0.2" 29 | mac_address = "1.1.5" 30 | whoami = "1.2" 31 | base64 = "0.13" 32 | axum = { version = "0.5", features = ["headers"] } 33 | sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "sqlite", "macros", "chrono", "json" ] } 34 | deadpool = "0.8" 35 | async-trait = "0.1" 36 | async-speed-limit = { git = "https://github.com/open-trade/async-speed-limit" } 37 | uuid = { version = "1.0", features = ["v4"] } 38 | bcrypt = "0.13" 39 | chrono = { version = "0.4", features = ["serde"] } 40 | jsonwebtoken = "8" 41 | headers = "0.3" 42 | once_cell = "1.8" 43 | sodiumoxide = "0.2" 44 | tokio-tungstenite = "0.17" 45 | tungstenite = "0.17" 46 | regex = "1.4" 47 | tower-http = { version = "0.3", features = ["fs", "trace", "cors"] } 48 | http = "0.2" 49 | flexi_logger = { version = "0.22", features = ["async", "use_chrono_for_offset", "dont_minimize_extra_stacks"] } 50 | ipnetwork = "0.20" 51 | local-ip-address = "0.5.1" 52 | dns-lookup = "1.0.8" 53 | ping = "0.4.0" 54 | 55 | [target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] 56 | # https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support 57 | reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "native-tls", "gzip"], default-features=false } 58 | 59 | [target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies] 60 | reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false } 61 | 62 | [build-dependencies] 63 | hbb_common = { path = "libs/hbb_common" } 64 | 65 | [workspace] 66 | members = ["libs/hbb_common"] 67 | exclude = ["ui"] 68 | 69 | #https://github.com/johnthagen/min-sized-rust 70 | #https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles 71 | [profile.release] 72 | lto = true 73 | codegen-units = 1 74 | panic = 'abort' 75 | strip = true 76 | #opt-level = 'z' # only have smaller size after strip # Default is 3, better performance 77 | #rpath = true # Not needed 78 | -------------------------------------------------------------------------------- /README-DE.md: -------------------------------------------------------------------------------- 1 |

2 | Erstellen • 3 | Docker • 4 | S6-Overlay • 5 | Schlüsselpaar • 6 | Debian-Pakete • 7 | Umgebungsvariablen
8 | [English] | [Nederlands] | [繁體中文] | [简体中文]
9 |

10 | 11 | # RustDesk Server-Programm 12 | 13 | [![build](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml/badge.svg)](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml) 14 | 15 | [**Herunterladen**](https://github.com/rustdesk/rustdesk-server/releases) 16 | 17 | [**Handbuch**](https://rustdesk.com/docs/de/self-host/) 18 | 19 | [**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) 20 | 21 | Hosten Sie Ihren eigenen RustDesk-Server selbst, er ist kostenlos und quelloffen. 22 | 23 | ## Manuelles Erstellen 24 | 25 | ```bash 26 | cargo build --release 27 | ``` 28 | 29 | In target/release werden drei ausführbare Dateien erzeugt. 30 | 31 | - hbbs - RustDesk ID/Rendezvous-Server 32 | - hbbr - RustDesk Relay-Server 33 | - rustdesk-utils - RustDesk CLI-Utilities 34 | 35 | [Hier](https://github.com/rustdesk/rustdesk-server/releases) finden Sie aktualisierte Binärdateien. 36 | 37 | Wenn Sie Ihren eigenen Server entwickeln wollen, könnte [rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) ein besserer und einfacherer Start für Sie sein als dieses Repository. 38 | 39 | ## Docker-Image 40 | 41 | Docker-Images werden automatisch generiert und bei jedem Github-Release veröffentlicht. Wir haben 2 Arten von Images. 42 | 43 | ### Klassisches Image 44 | 45 | Diese Images sind mit `Ubuntu 20.04` gebaut, mit dem Zusatz der wichtigen Binärdateien (`hbbr` und `hbbs`). Sie sind auf [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server/) mit diesen Tags verfügbar: 46 | 47 | | Architektur | Image:Tag | 48 | | --- | --- | 49 | | amd64 | `rustdesk/rustdesk-server:latest` | 50 | | arm64v8 | `rustdesk/rustdesk-server:latest-arm64v8` | 51 | 52 | Sie können diese Images direkt mit `docker run` mit diesen Befehlen starten: 53 | 54 | ```bash 55 | docker run --name hbbs --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 56 | docker run --name hbbr --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 57 | ``` 58 | 59 | Oder ohne `--net=host`, aber die P2P-Direktverbindung kann dann nicht funktionieren. 60 | 61 | Bei Systemen, die SELinux verwenden, muss `/root` durch `/root:z` ersetzt werden, damit die Container korrekt laufen. Alternativ kann die SELinux-Containertrennung durch Hinzufügen der Option `--security-opt label=disable` vollständig deaktiviert werden. 62 | 63 | ```bash 64 | docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 65 | docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 66 | ``` 67 | 68 | Der Parameter `relay-server-ip` ist die IP-Adresse (oder der DNS-Name) des Servers, auf dem diese Container laufen. Der **optionale** Parameter `port` muss verwendet werden, wenn Sie einen anderen Port als **21117** für `hbbr` verwenden. 69 | 70 | Sie können auch Docker Compose verwenden, wobei diese Konfiguration als Vorlage dient: 71 | 72 | ```yaml 73 | version: '3' 74 | 75 | networks: 76 | rustdesk-net: 77 | external: false 78 | 79 | services: 80 | hbbs: 81 | container_name: hbbs 82 | ports: 83 | - 21115:21115 84 | - 21116:21116 85 | - 21116:21116/udp 86 | - 21118:21118 87 | image: rustdesk/rustdesk-server:latest 88 | command: hbbs -r rustdesk.example.com:21117 89 | volumes: 90 | - ./data:/root 91 | networks: 92 | - rustdesk-net 93 | depends_on: 94 | - hbbr 95 | restart: unless-stopped 96 | 97 | hbbr: 98 | container_name: hbbr 99 | ports: 100 | - 21117:21117 101 | - 21119:21119 102 | image: rustdesk/rustdesk-server:latest 103 | command: hbbr 104 | volumes: 105 | - ./data:/root 106 | networks: 107 | - rustdesk-net 108 | restart: unless-stopped 109 | ``` 110 | 111 | Bearbeiten Sie Zeile 16 so, dass sie auf Ihren Relay-Server verweist (den, der am Port 21117 lauscht). Sie können auch die Zeilen für die Volumes (Zeile 18 und 33) bearbeiten, wenn Sie dies wünschen. 112 | 113 | (Die Anerkennung für Docker Compose geht an @lukebarone und @QuiGonLeong.) 114 | 115 | ## S6-Overlay-basierte Images 116 | 117 | Diese Images sind mit `busybox:stable` gebaut, mit dem Zusatz Binärdateien (sowohl hbbr als auch hbbs) und [S6-overlay](https://github.com/just-containers/s6-overlay). Sie sind auf [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server-s6/) mit diesen Tags verfügbar: 118 | 119 | | Architektur | Version | Image:Tag | 120 | | --- | --- | --- | 121 | | multiarch | neueste | `rustdesk/rustdesk-server-s6:latest` | 122 | | amd64 | neueste | `rustdesk/rustdesk-server-s6:latest-amd64` | 123 | | i386 | neueste | `rustdesk/rustdesk-server-s6:latest-i386` | 124 | | arm64v8 | neueste | `rustdesk/rustdesk-server-s6:latest-arm64v8` | 125 | | armv7 | neueste | `rustdesk/rustdesk-server-s6:latest-armv7` | 126 | | multiarch | 2 | `rustdesk/rustdesk-server-s6:2` | 127 | | amd64 | 2 | `rustdesk/rustdesk-server-s6:2-amd64` | 128 | | i386 | 2 | `rustdesk/rustdesk-server-s6:2-i386` | 129 | | arm64v8 | 2 | `rustdesk/rustdesk-server-s6:2-arm64v8` | 130 | | armv7 | 2 | `rustdesk/rustdesk-server-s6:2-armv7` | 131 | | multiarch | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0` | 132 | | amd64 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-amd64` | 133 | | i386 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-i386` | 134 | | arm64v8 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-arm64v8` | 135 | | armv7 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-armv7` | 136 | 137 | Es wird dringend empfohlen, das Image `multiarch` entweder mit dem Tag `major version` oder `latest` zu verwenden. 138 | 139 | Das S6-Overlay fungiert als Supervisor und hält beide Prozesse am Laufen, sodass bei diesem Image keine zwei separaten Container benötigt werden. 140 | 141 | Sie können diese Images direkt mit `docker run` mit diesem Befehl starten: 142 | 143 | ```bash 144 | docker run --name rustdesk-server \ 145 | --net=host \ 146 | -e "RELAY=rustdeskrelay.example.com" \ 147 | -e "ENCRYPTED_ONLY=1" \ 148 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 149 | ``` 150 | 151 | oder ohne `--net=host`, aber die P2P-Direktverbindung kann dann nicht funktionieren. 152 | 153 | ```bash 154 | docker run --name rustdesk-server \ 155 | -p 21115:21115 -p 21116:21116 -p 21116:21116/udp \ 156 | -p 21117:21117 -p 21118:21118 -p 21119:21119 \ 157 | -e "RELAY=rustdeskrelay.example.com" \ 158 | -e "ENCRYPTED_ONLY=1" \ 159 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 160 | ``` 161 | 162 | Oder Sie können eine Docker Compose-Datei verwenden: 163 | 164 | ```yaml 165 | version: '3' 166 | 167 | services: 168 | rustdesk-server: 169 | container_name: rustdesk-server 170 | ports: 171 | - 21115:21115 172 | - 21116:21116 173 | - 21116:21116/udp 174 | - 21117:21117 175 | - 21118:21118 176 | - 21119:21119 177 | image: rustdesk/rustdesk-server-s6:latest 178 | environment: 179 | - "RELAY=rustdesk.example.com:21117" 180 | - "ENCRYPTED_ONLY=1" 181 | volumes: 182 | - ./data:/data 183 | restart: unless-stopped 184 | ``` 185 | 186 | Für dieses Container-Image können Sie diese Umgebungsvariablen verwenden, **zusätzlich** zu den im Abschnitt **Umgebungsvariablen** angegebenen Variablen: 187 | 188 | | Variable | optional | Beschreibung | 189 | | --- | --- | --- | 190 | | RELAY | nein | IP-Adresse/DNS-Name des Rechners, auf dem dieser Container läuft | 191 | | ENCRYPTED_ONLY | ja | Wenn auf **1** gesetzt, wird eine unverschlüsselte Verbindung nicht akzeptiert | 192 | | KEY_PUB | ja | Öffentlicher Teil des Schlüsselpaares | 193 | | KEY_PRIV | ja | Privater Teil des Schlüsselpaares | 194 | 195 | ### Verwaltung von Geheimnissen in S6-Overlay-basierten Images 196 | 197 | Sie können das Schlüsselpaar natürlich in einem Docker-Volume aufbewahren, aber empfehlenswert ist, die Schlüssel nicht in das Dateisystem zu schreiben. 198 | 199 | Beim Start des Containers wird das Vorhandensein des Schlüsselpaares geprüft (`/data/id_ed25519.pub` und `/data/id_ed25519`). Wenn einer dieser Schlüssel nicht existiert, wird er aus den Umgebungsvariablen oder den Docker-Geheimnissen neu erstellt. 200 | Dann wird die Gültigkeit des Schlüsselpaares überprüft: Wenn öffentlicher und privater Schlüssel nicht übereinstimmen, wird der Container angehalten. 201 | Wenn Sie keine Schlüssel angeben, erzeugt `hbbs` einen für Sie und legt ihn am Standardspeicherort ab. 202 | 203 | #### Umgebungsvariablen zum Speichern des Schlüsselpaars verwenden 204 | 205 | Sie können Docker-Umgebungsvariablen verwenden, um die Schlüssel zu speichern. Folgen Sie einfach diesen Beispielen: 206 | 207 | ```bash 208 | docker run --name rustdesk-server \ 209 | --net=host \ 210 | -e "RELAY=rustdeskrelay.example.com" \ 211 | -e "ENCRYPTED_ONLY=1" \ 212 | -e "DB_URL=/db/db_v2.sqlite3" \ 213 | -e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \ 214 | -e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \ 215 | -v "$PWD/db:/db" -d rustdesk/rustdesk-server-s6:latest 216 | ``` 217 | 218 | ```yaml 219 | version: '3' 220 | 221 | services: 222 | rustdesk-server: 223 | container_name: rustdesk-server 224 | ports: 225 | - 21115:21115 226 | - 21116:21116 227 | - 21116:21116/udp 228 | - 21117:21117 229 | - 21118:21118 230 | - 21119:21119 231 | image: rustdesk/rustdesk-server-s6:latest 232 | environment: 233 | - "RELAY=rustdesk.example.com:21117" 234 | - "ENCRYPTED_ONLY=1" 235 | - "DB_URL=/db/db_v2.sqlite3" 236 | - "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" 237 | - "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" 238 | volumes: 239 | - ./db:/db 240 | restart: unless-stopped 241 | ``` 242 | 243 | #### Docker-Geheimnisse zum Speichern des Schlüsselpaars verwenden 244 | 245 | Sie können alternativ auch Docker-Geheimnisse verwenden, um die Schlüssel zu speichern. 246 | Dies ist nützlich, wenn Sie **Docker Compose** oder **Docker Swarm** verwenden. 247 | Folgen Sie einfach diesem Beispiel: 248 | 249 | ```bash 250 | cat secrets/id_ed25519.pub | docker secret create key_pub - 251 | cat secrets/id_ed25519 | docker secret create key_priv - 252 | docker service create --name rustdesk-server \ 253 | --secret key_priv --secret key_pub \ 254 | --net=host \ 255 | -e "RELAY=rustdeskrelay.example.com" \ 256 | -e "ENCRYPTED_ONLY=1" \ 257 | -e "DB_URL=/db/db_v2.sqlite3" \ 258 | --mount "type=bind,source=$PWD/db,destination=/db" \ 259 | rustdesk/rustdesk-server-s6:latest 260 | ``` 261 | 262 | ```yaml 263 | version: '3' 264 | 265 | services: 266 | rustdesk-server: 267 | container_name: rustdesk-server 268 | ports: 269 | - 21115:21115 270 | - 21116:21116 271 | - 21116:21116/udp 272 | - 21117:21117 273 | - 21118:21118 274 | - 21119:21119 275 | image: rustdesk/rustdesk-server-s6:latest 276 | environment: 277 | - "RELAY=rustdesk.example.com:21117" 278 | - "ENCRYPTED_ONLY=1" 279 | - "DB_URL=/db/db_v2.sqlite3" 280 | volumes: 281 | - ./db:/db 282 | restart: unless-stopped 283 | secrets: 284 | - key_pub 285 | - key_priv 286 | 287 | secrets: 288 | key_pub: 289 | file: secrets/id_ed25519.pub 290 | key_priv: 291 | file: secrets/id_ed25519 292 | ``` 293 | 294 | ## Ein Schlüsselpaar erstellen 295 | 296 | Für die Verschlüsselung wird ein Schlüsselpaar benötigt, das Sie bereitstellen können, aber Sie benötigen eine Möglichkeit, es zu erstellen. 297 | 298 | Mit diesem Befehl können Sie ein Schlüsselpaar erzeugen: 299 | 300 | ```bash 301 | /usr/bin/rustdesk-utils genkeypair 302 | ``` 303 | 304 | Wenn Sie das Paket `rustdesk-utils` nicht auf Ihrem System installiert haben (oder dies nicht wollen), können Sie den gleichen Befehl mit Docker aufrufen: 305 | 306 | ```bash 307 | docker run --rm --entrypoint /usr/bin/rustdesk-utils rustdesk/rustdesk-server-s6:latest genkeypair 308 | ``` 309 | 310 | Die Ausgabe sieht dann etwa so aus: 311 | 312 | ```text 313 | Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA= 314 | Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA== 315 | ``` 316 | 317 | ## Debian-Pakete 318 | 319 | Für jede Binärdatei stehen separate Debian-Pakete zur Verfügung, die Sie in [Releases](https://github.com/rustdesk/rustdesk-server/releases) finden können. 320 | Diese Pakete sind für die folgenden Distributionen gedacht: 321 | 322 | - Ubuntu 22.04 LTS 323 | - Ubuntu 20.04 LTS 324 | - Ubuntu 18.04 LTS 325 | - Debian 11 Bullseye 326 | - Debian 10 Buster 327 | 328 | ## Umgebungsvariablen 329 | 330 | hbbs und hbbr können mit diesen Umgebungsvariablen konfiguriert werden. 331 | Sie können die Variablen wie üblich angeben oder eine `.env`-Datei verwenden. 332 | 333 | | Variable | Binärdatei | Beschreibung | 334 | | --- | --- | --- | 335 | | ALWAYS_USE_RELAY | hbbs | Wenn auf **Y** gesetzt, wird eine direkte Verbindung nicht zugelassen. | 336 | | DB_URL | hbbs | Pfad für die Datenbankdatei | 337 | | DOWNGRADE_START_CHECK | hbbr | Verzögerung (in Sekunden) vor der Downgrade-Prüfung | 338 | | DOWNGRADE_THRESHOLD | hbbr | Schwellenwert der Downgrade-Prüfung (Bit/ms)) | 339 | | KEY | hbbs/hbbr | Wenn gesetzt, wird die Verwendung eines bestimmten Schlüssels erzwungen. Wenn auf **_** gesetzt, wird die Verwendung eines beliebigen Schlüssels erzwungen. | 340 | | LIMIT_SPEED | hbbr | Höchstgeschwindigkeit (in Mb/s) | 341 | | PORT | hbbs/hbbr | Lauschender Port (21116 für hbbs - 21117 für hbbr) | 342 | | RELAY_SERVERS | hbbs | IP-Adresse/DNS-Name der Rechner, auf denen hbbr läuft (durch Komma getrennt) | 343 | | RUST_LOG | all | Debug-Level einstellen (error\|warn\|info\|debug\|trace) | 344 | | SINGLE_BANDWIDTH | hbbr | Maximale Bandbreite für eine einzelne Verbindung (in Mb/s) | 345 | | TOTAL_BANDWIDTH | hbbr | Maximale Gesamtbandbreite (in Mb/s) | 346 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 |

2 | Manually • 3 | Docker • 4 | S6-overlay • 5 | Keypair • 6 | Debian • 7 | Variables
8 | [Deutsch] | [Nederlands] | [繁體中文] | [简体中文]
9 |

10 | 11 | # RustDesk Server Program 12 | 13 | [![build](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml/badge.svg)](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml) 14 | 15 | [**Download**](https://github.com/rustdesk/rustdesk-server/releases) 16 | 17 | [**Manual**](https://rustdesk.com/docs/en/self-host/) 18 | 19 | [**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) 20 | 21 | Self-host your own RustDesk server, it is free and open source. 22 | 23 | ## How to build manually 24 | 25 | ```bash 26 | cargo build --release 27 | ``` 28 | 29 | Three executables will be generated in target/release. 30 | 31 | - hbbs - RustDesk ID/Rendezvous server 32 | - hbbr - RustDesk relay server 33 | - rustdesk-utils - RustDesk CLI utilities 34 | 35 | You can find updated binaries on the [Releases](https://github.com/rustdesk/rustdesk-server/releases) page. 36 | 37 | If you want extra features, [RustDesk Server Pro](https://rustdesk.com/pricing.html) might suit you better. 38 | 39 | If you want to develop your own server, [rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) might be a better and simpler start for you than this repo. 40 | 41 | ## Docker images 42 | 43 | Docker images are automatically generated and published to [Docker Hub](https://hub.docker.com/r/rustdesk) and [GitHub Container Registry](https://github.com/rustdesk?tab=packages&repo_name=rustdesk-server) on every GitHub release. We have 2 kind of images. 44 | 45 | ### Classic image 46 | 47 | These images are built from scratch with two main binaries (`hbbs` and `hbbr`). They're available on [Docker Hub](https://hub.docker.com/r/rustdesk/rustdesk-server/) and [GitHub Container Registry](https://github.com/rustdesk/rustdesk-server/pkgs/container/rustdesk-server) with these architectures: 48 | 49 | * amd64 50 | * arm64v8 51 | * armv7 52 | 53 | You could use `latest` tag or major version tag `1` with supported architectures: 54 | 55 | | Version | image:tag | 56 | | ------------- | --------------------------------- | 57 | | latest | `rustdesk/rustdesk-server:latest` | 58 | | Major version | `rustdesk/rustdesk-server:1` | 59 | 60 | 61 | You can start these images directly with `docker run` with these commands: 62 | 63 | ```bash 64 | docker run --name hbbs --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 65 | docker run --name hbbr --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 66 | ``` 67 | 68 | or without `--net=host`, but P2P direct connection can not work. 69 | 70 | For systems using SELinux, replacing `/root` by `/root:z` is required for the containers to run correctly. Alternatively, SELinux container separation can be disabled completely adding the option `--security-opt label=disable`. 71 | 72 | ```bash 73 | docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 74 | docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 75 | ``` 76 | 77 | The `relay-server-ip` parameter is the IP address (or dns name) of the server running these containers. The **optional** `port` parameter has to be used if you use a port different than **21117** for `hbbr`. 78 | 79 | You can also use docker-compose, using this configuration as a template: 80 | 81 | ```yaml 82 | version: '3' 83 | 84 | networks: 85 | rustdesk-net: 86 | external: false 87 | 88 | services: 89 | hbbs: 90 | container_name: hbbs 91 | ports: 92 | - 21115:21115 93 | - 21116:21116 94 | - 21116:21116/udp 95 | - 21118:21118 96 | image: rustdesk/rustdesk-server:latest 97 | command: hbbs -r rustdesk.example.com:21117 98 | volumes: 99 | - ./data:/root 100 | networks: 101 | - rustdesk-net 102 | depends_on: 103 | - hbbr 104 | restart: unless-stopped 105 | 106 | hbbr: 107 | container_name: hbbr 108 | ports: 109 | - 21117:21117 110 | - 21119:21119 111 | image: rustdesk/rustdesk-server:latest 112 | command: hbbr 113 | volumes: 114 | - ./data:/root 115 | networks: 116 | - rustdesk-net 117 | restart: unless-stopped 118 | ``` 119 | 120 | Edit line 16 to point to your relay server (the one listening on port 21117). You can also edit the volume lines (line 18 and line 33) if you need. 121 | 122 | (docker-compose credit goes to @lukebarone and @QuiGonLeong) 123 | 124 | > [!NOTE] 125 | > The rustdesk/rustdesk-server:latest in China may be replaced with the latest version number on Docker Hub, such as `rustdesk-server:1.1.10-3`. Otherwise, the old version may be pulled due to image acceleration. 126 | 127 | > [!NOTE] 128 | > If you are experiencing issues pulling from Docker Hub, try pulling from the [GitHub Container Registry](https://github.com/rustdesk/rustdesk-server/pkgs/container/rustdesk-server) instead. 129 | 130 | ## S6-overlay based images 131 | 132 | These images are build against `busybox:stable` with the addition of the binaries (both `hbbs` and `hbbr`) and [S6-overlay](https://github.com/just-containers/s6-overlay). They're available on [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server-s6/) and [GitHub Container Registry](https://github.com/rustdesk/rustdesk-server/pkgs/container/rustdesk-server) with these architectures: 133 | 134 | * amd64 135 | * i386 136 | * arm64v8 137 | * armv7 138 | 139 | You could use `latest` tag or major version tag `1` with supported architectures: 140 | 141 | | Version | image:tag | 142 | | ------------- | ------------------------------------ | 143 | | latest | `rustdesk/rustdesk-server-s6:latest` | 144 | | Major version | `rustdesk/rustdesk-server-s6:1` | 145 | 146 | The S6-overlay acts as a supervisor and keeps both process running, so with this image, there's no need to have two separate running containers. 147 | 148 | You can start these images directly with `docker run` with this command: 149 | 150 | ```bash 151 | docker run --name rustdesk-server \ 152 | --net=host \ 153 | -e "RELAY=rustdeskrelay.example.com" \ 154 | -e "ENCRYPTED_ONLY=1" \ 155 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 156 | ``` 157 | 158 | or without `--net=host`, but P2P direct connection cannot work. 159 | 160 | ```bash 161 | docker run --name rustdesk-server \ 162 | -p 21115:21115 -p 21116:21116 -p 21116:21116/udp \ 163 | -p 21117:21117 -p 21118:21118 -p 21119:21119 \ 164 | -e "RELAY=rustdeskrelay.example.com" \ 165 | -e "ENCRYPTED_ONLY=1" \ 166 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 167 | ``` 168 | 169 | Or you can use a docker-compose file: 170 | 171 | ```yaml 172 | version: '3' 173 | 174 | services: 175 | rustdesk-server: 176 | container_name: rustdesk-server 177 | ports: 178 | - 21115:21115 179 | - 21116:21116 180 | - 21116:21116/udp 181 | - 21117:21117 182 | - 21118:21118 183 | - 21119:21119 184 | image: rustdesk/rustdesk-server-s6:latest 185 | environment: 186 | - "RELAY=rustdesk.example.com:21117" 187 | - "ENCRYPTED_ONLY=1" 188 | volumes: 189 | - ./data:/data 190 | restart: unless-stopped 191 | ``` 192 | 193 | For this container image, you can use these environment variables, **in addition** to the ones specified in the following **ENV variables** section: 194 | 195 | | variable | optional | description | 196 | | --- | --- | --- | 197 | | RELAY | no | the IP address/DNS name of the machine running this container | 198 | | ENCRYPTED_ONLY | yes | if set to **"1"** unencrypted connection will not be accepted | 199 | | KEY_PUB | yes | public part of the key pair | 200 | | KEY_PRIV | yes | private part of the key pair | 201 | 202 | ### Secret management in S6-overlay based images 203 | 204 | You can obviously keep the key pair in a docker volume, but the best practices tells you to not write the keys on the filesystem; so we provide a couple of options. 205 | 206 | On container startup, the presence of the keypair is checked (`/data/id_ed25519.pub` and `/data/id_ed25519`) and if one of these keys doesn't exist, it's recreated from ENV variables or docker secrets. 207 | Then the validity of the keypair is checked: if public and private keys doesn't match, the container will stop. 208 | If you provide no keys, `hbbs` will generate one for you, and it'll place it in the default location. 209 | 210 | #### Use ENV to store the key pair 211 | 212 | You can use docker environment variables to store the keys. Just follow this examples: 213 | 214 | ```bash 215 | docker run --name rustdesk-server \ 216 | --net=host \ 217 | -e "RELAY=rustdeskrelay.example.com" \ 218 | -e "ENCRYPTED_ONLY=1" \ 219 | -e "DB_URL=/db/db_v2.sqlite3" \ 220 | -e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \ 221 | -e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \ 222 | -v "$PWD/db:/db" -d rustdesk/rustdesk-server-s6:latest 223 | ``` 224 | 225 | ```yaml 226 | version: '3' 227 | 228 | services: 229 | rustdesk-server: 230 | container_name: rustdesk-server 231 | ports: 232 | - 21115:21115 233 | - 21116:21116 234 | - 21116:21116/udp 235 | - 21117:21117 236 | - 21118:21118 237 | - 21119:21119 238 | image: rustdesk/rustdesk-server-s6:latest 239 | environment: 240 | - "RELAY=rustdesk.example.com:21117" 241 | - "ENCRYPTED_ONLY=1" 242 | - "DB_URL=/db/db_v2.sqlite3" 243 | - "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" 244 | - "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" 245 | volumes: 246 | - ./db:/db 247 | restart: unless-stopped 248 | ``` 249 | 250 | #### Use Docker secrets to store the key pair 251 | 252 | You can alternatively use docker secrets to store the keys. 253 | This is useful if you're using **docker-compose** or **Docker Swarm**. 254 | Just follow this examples: 255 | 256 | ```bash 257 | cat secrets/id_ed25519.pub | docker secret create key_pub - 258 | cat secrets/id_ed25519 | docker secret create key_priv - 259 | docker service create --name rustdesk-server \ 260 | --secret key_priv --secret key_pub \ 261 | --net=host \ 262 | -e "RELAY=rustdeskrelay.example.com" \ 263 | -e "ENCRYPTED_ONLY=1" \ 264 | -e "DB_URL=/db/db_v2.sqlite3" \ 265 | --mount "type=bind,source=$PWD/db,destination=/db" \ 266 | rustdesk/rustdesk-server-s6:latest 267 | ``` 268 | 269 | ```yaml 270 | version: '3' 271 | 272 | services: 273 | rustdesk-server: 274 | container_name: rustdesk-server 275 | ports: 276 | - 21115:21115 277 | - 21116:21116 278 | - 21116:21116/udp 279 | - 21117:21117 280 | - 21118:21118 281 | - 21119:21119 282 | image: rustdesk/rustdesk-server-s6:latest 283 | environment: 284 | - "RELAY=rustdesk.example.com:21117" 285 | - "ENCRYPTED_ONLY=1" 286 | - "DB_URL=/db/db_v2.sqlite3" 287 | volumes: 288 | - ./db:/db 289 | restart: unless-stopped 290 | secrets: 291 | - key_pub 292 | - key_priv 293 | 294 | secrets: 295 | key_pub: 296 | file: secrets/id_ed25519.pub 297 | key_priv: 298 | file: secrets/id_ed25519 299 | ``` 300 | 301 | ## How to create a keypair 302 | 303 | A keypair is needed for encryption; you can provide it, as explained before, but you need a way to create one. 304 | 305 | You can use this command to generate a keypair: 306 | 307 | ```bash 308 | /usr/bin/rustdesk-utils genkeypair 309 | ``` 310 | 311 | If you don't have (or don't want) the `rustdesk-utils` package installed on your system, you can invoke the same command with docker: 312 | 313 | ```bash 314 | docker run --rm --entrypoint /usr/bin/rustdesk-utils rustdesk/rustdesk-server-s6:latest genkeypair 315 | ``` 316 | 317 | The output will be something like this: 318 | 319 | ```text 320 | Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA= 321 | Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA== 322 | ``` 323 | 324 | ## .deb packages 325 | 326 | Separate .deb packages are available for each binary, you can find them in the [Releases](https://github.com/rustdesk/rustdesk-server/releases). 327 | These packages are meant for the following distributions: 328 | 329 | - Ubuntu 24.04 LTS 330 | - Ubuntu 22.04 LTS 331 | - Ubuntu 20.04 LTS 332 | - Ubuntu 18.04 LTS 333 | - Debian 12 bookworm 334 | - Debian 11 bullseye 335 | - Debian 10 buster 336 | 337 | ## ENV variables 338 | 339 | `hbbs` and `hbbr` can be configured using these ENV variables. 340 | You can specify the variables as usual or use an `.env` file. 341 | 342 | | variable | binary | description | 343 | | --- | --- | --- | 344 | | ALWAYS_USE_RELAY | hbbs | if set to **"Y"** disallows direct peer connection | 345 | | DB_URL | hbbs | path for database file | 346 | | DOWNGRADE_START_CHECK | hbbr | delay (in seconds) before downgrade check | 347 | | DOWNGRADE_THRESHOLD | hbbr | threshold of downgrade check (bit/ms) | 348 | | KEY | hbbs/hbbr | if set force the use of a specific key, if set to **"_"** force the use of any key | 349 | | LIMIT_SPEED | hbbr | speed limit (in Mb/s) | 350 | | PORT | hbbs/hbbr | listening port (21116 for hbbs - 21117 for hbbr) | 351 | | RELAY | hbbs | IP address/DNS name of the machines running hbbr (separated by comma) | 352 | | RUST_LOG | all | set debug level (error\|warn\|info\|debug\|trace) | 353 | | SINGLE_BANDWIDTH | hbbr | max bandwidth for a single connection (in Mb/s) | 354 | | TOTAL_BANDWIDTH | hbbr | max total bandwidth (in Mb/s) | 355 | -------------------------------------------------------------------------------- /README-NL.md: -------------------------------------------------------------------------------- 1 |

2 | Opbouwen • 3 | Docker • 4 | S6-Overlay • 5 | Key paar • 6 | Debian pakketten • 7 | ENV variabelen
8 | [English] | [Deutsch] | [繁體中文] | [简体中文]
9 |

10 | 11 | # RustDesk Server Programa 12 | 13 | [![build](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml/badge.svg)](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml) 14 | 15 | [**Download**](https://github.com/rustdesk/rustdesk-server/releases) 16 | 17 | [**Handleiding**](https://rustdesk.com/docs/nl/self-host/) 18 | 19 | [**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) 20 | 21 | Zelf uw eigen RustDesk server hosten, het is gratis en open source. 22 | 23 | ## Hoe handmatig opbouwen 24 | 25 | ```bash 26 | cargo build --release 27 | ``` 28 | 29 | In target/release worden drie uitvoerbare bestanden gegenereerd. 30 | 31 | - hbbs - RustDesk ID/Rendezvous server 32 | - hbbr - RustDesk relay server 33 | - rustdesk-utils - RustDesk CLI hulpprogramma's 34 | 35 | U kunt bijgewerkte binaries vinden op [releases](https://github.com/rustdesk/rustdesk-server/releases) pagina. 36 | 37 | Als u uw eigen server wilt ontwikkelen, is [rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) misschien een betere en eenvoudigere start voor u dan deze repo. 38 | 39 | ## Docker bestanden (images) 40 | 41 | Docker bestanden (images) worden automatisch gegenereerd en gepubliceerd bij elke github release. We hebben 2 soorten bestanden (images). 42 | 43 | ### Klassiek bestand (image) 44 | 45 | Deze bestanden (images) zijn gebouwd voor `ubuntu-20.04` met als enige toevoeging de belangrijkste binaries (`hbbr` en `hbbs`). Ze zijn beschikbaar op [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server/) met deze tags: 46 | 47 | | architectuur | image:tag | 48 | | --- | --- | 49 | | amd64 | `rustdesk/rustdesk-server:latest` | 50 | | arm64v8 | `rustdesk/rustdesk-server:latest-arm64v8` | 51 | 52 | U kunt deze bestanden (images) direct starten via `docker run` met deze commando's: 53 | 54 | ```bash 55 | docker run --name hbbs --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 56 | docker run --name hbbr --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 57 | ``` 58 | 59 | of zonder `--net=host`, maar een directe P2P verbinding zal niet werken. 60 | 61 | Voor systemen die SELinux gebruiken is het vervangen van `/root` door `/root:z` nodig om de containers correct te laten draaien. Als alternatief kan SELinux containerscheiding volledig worden uitgeschakeld door de optie `--security-opt label=disable` toe te voegen. 62 | 63 | ```bash 64 | docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 65 | docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 66 | ``` 67 | 68 | De `relay-server-ip` parameter is het IP adres (of dns naam) van de server waarop deze containers draaien. De **optionele** `port` parameter moet gebruikt worden als je een andere poort dan **21117** gebruikt voor `hbbr`. 69 | 70 | U kunt ook docker-compose gebruiken, met deze configuratie als sjabloon: 71 | 72 | ```yaml 73 | version: '3' 74 | 75 | networks: 76 | rustdesk-net: 77 | external: false 78 | 79 | services: 80 | hbbs: 81 | container_name: hbbs 82 | ports: 83 | - 21115:21115 84 | - 21116:21116 85 | - 21116:21116/udp 86 | - 21118:21118 87 | image: rustdesk/rustdesk-server:latest 88 | command: hbbs -r rustdesk.example.com:21117 89 | volumes: 90 | - ./data:/root 91 | networks: 92 | - rustdesk-net 93 | depends_on: 94 | - hbbr 95 | restart: unless-stopped 96 | 97 | hbbr: 98 | container_name: hbbr 99 | ports: 100 | - 21117:21117 101 | - 21119:21119 102 | image: rustdesk/rustdesk-server:latest 103 | command: hbbr 104 | volumes: 105 | - ./data:/root 106 | networks: 107 | - rustdesk-net 108 | restart: unless-stopped 109 | ``` 110 | 111 | Bewerk regel 16 om te verwijzen naar uw relais-server (degene die luistert op poort 21117). U kunt ook de inhoudsregels (L18 en L33) bewerken indien nodig. 112 | 113 | (docker-compose erkenning gaat naar @lukebarone en @QuiGonLeong) 114 | 115 | ## S6-overlay gebaseerde bestanden 116 | 117 | Deze bestanden (images) zijn gebouwd tegen `busybox:stable` met toevoeging van de binaries (zowel hbbr als hbbs) en [S6-overlay](https://github.com/just-containers/s6-overlay). Ze zijn beschikbaar op [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server-s6/) met deze tags: 118 | 119 | | architectuur | versie | image:tag | 120 | | --- | --- | --- | 121 | | multiarch | latest | `rustdesk/rustdesk-server-s6:latest` | 122 | | amd64 | latest | `rustdesk/rustdesk-server-s6:latest-amd64` | 123 | | i386 | latest | `rustdesk/rustdesk-server-s6:latest-i386` | 124 | | arm64v8 | latest | `rustdesk/rustdesk-server-s6:latest-arm64v8` | 125 | | armv7 | latest | `rustdesk/rustdesk-server-s6:latest-armv7` | 126 | | multiarch | 2 | `rustdesk/rustdesk-server-s6:2` | 127 | | amd64 | 2 | `rustdesk/rustdesk-server-s6:2-amd64` | 128 | | i386 | 2 | `rustdesk/rustdesk-server-s6:2-i386` | 129 | | arm64v8 | 2 | `rustdesk/rustdesk-server-s6:2-arm64v8` | 130 | | armv7 | 2 | `rustdesk/rustdesk-server-s6:2-armv7` | 131 | | multiarch | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0` | 132 | | amd64 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-amd64` | 133 | | i386 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-i386` | 134 | | arm64v8 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-arm64v8` | 135 | | armv7 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-armv7` | 136 | 137 | Je wordt sterk aangeraden om het `multiarch` bestand (image) te gebruiken met de `major version` of `latest` tag. 138 | 139 | De S6-overlay fungeert als supervisor en houdt beide processen draaiende, dus met dit bestand (image) is het niet nodig om twee aparte draaiende containers te hebben. 140 | 141 | U kunt deze bestanden (images) direct starten via `docker run` met dit commando: 142 | 143 | ```bash 144 | docker run --name rustdesk-server \ 145 | --net=host \ 146 | -e "RELAY=rustdeskrelay.example.com" \ 147 | -e "ENCRYPTED_ONLY=1" \ 148 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 149 | ``` 150 | 151 | of zonder `--net=host`, maar een directe P2P verbinding zal niet werken. 152 | 153 | ```bash 154 | docker run --name rustdesk-server \ 155 | -p 21115:21115 -p 21116:21116 -p 21116:21116/udp \ 156 | -p 21117:21117 -p 21118:21118 -p 21119:21119 \ 157 | -e "RELAY=rustdeskrelay.example.com" \ 158 | -e "ENCRYPTED_ONLY=1" \ 159 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 160 | ``` 161 | 162 | Of u kunt een docker-compose bestand gebruiken: 163 | 164 | ```yaml 165 | version: '3' 166 | 167 | services: 168 | rustdesk-server: 169 | container_name: rustdesk-server 170 | ports: 171 | - 21115:21115 172 | - 21116:21116 173 | - 21116:21116/udp 174 | - 21117:21117 175 | - 21118:21118 176 | - 21119:21119 177 | image: rustdesk/rustdesk-server-s6:latest 178 | environment: 179 | - "RELAY=rustdesk.example.com:21117" 180 | - "ENCRYPTED_ONLY=1" 181 | volumes: 182 | - ./data:/data 183 | restart: unless-stopped 184 | ``` 185 | 186 | Voor dit container bestand (image) kunt u deze omgevingsvariabelen gebruiken, **naast** de variabelen in de volgende **ENV-variabelen** sectie: 187 | 188 | | variabele | optioneel | beschrijving | 189 | | --- | --- | --- | 190 | | RELAY | no | het IP-adres/DNS-naam van de machine waarop deze container draait | 191 | | ENCRYPTED_ONLY | yes | indien ingesteld op **"1"** wordt een niet-versleutelde verbinding niet geaccepteerd | 192 | | KEY_PUB | yes | het openbare deel van het key paar | 193 | | KEY_PRIV | yes | het private deel van het key paar | 194 | 195 | ### Geheim beheer in S6-overlay gebaseerde bestanden (images) 196 | 197 | U kunt uiteraard het key paar bewaren in een docker volume, maar de optimale werkwijzen vertellen u om de keys niet op het bestandssysteem te schrijven; dus bieden we een paar opties. 198 | 199 | Bij het opstarten van de container wordt de aanwezigheid van het key paar gecontroleerd (`/data/id_ed25519.pub` en `/data/id_ed25519`) en als een van deze keys niet bestaat, wordt deze opnieuw aangemaakt vanuit ENV variabelen of docker secrets. 200 | Vervolgens wordt de geldigheid van het key paar gecontroleerd: indien publieke en private keys niet overeenkomen, stopt de container. 201 | Als je geen keys opgeeft, zal `hbbs` er een voor je genereren en op de standaard locatie plaatsen. 202 | 203 | #### Gebruik ENV om het key paar op te slaan 204 | 205 | U kunt docker omgevingsvariabelen gebruiken om de keys op te slaan. Volg gewoon deze voorbeelden: 206 | 207 | ```bash 208 | docker run --name rustdesk-server \ 209 | --net=host \ 210 | -e "RELAY=rustdeskrelay.example.com" \ 211 | -e "ENCRYPTED_ONLY=1" \ 212 | -e "DB_URL=/db/db_v2.sqlite3" \ 213 | -e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \ 214 | -e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \ 215 | -v "$PWD/db:/db" -d rustdesk/rustdesk-server-s6:latest 216 | ``` 217 | 218 | ```yaml 219 | version: '3' 220 | 221 | services: 222 | rustdesk-server: 223 | container_name: rustdesk-server 224 | ports: 225 | - 21115:21115 226 | - 21116:21116 227 | - 21116:21116/udp 228 | - 21117:21117 229 | - 21118:21118 230 | - 21119:21119 231 | image: rustdesk/rustdesk-server-s6:latest 232 | environment: 233 | - "RELAY=rustdesk.example.com:21117" 234 | - "ENCRYPTED_ONLY=1" 235 | - "DB_URL=/db/db_v2.sqlite3" 236 | - "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" 237 | - "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" 238 | volumes: 239 | - ./db:/db 240 | restart: unless-stopped 241 | ``` 242 | 243 | #### Gebruik Docker secrets om het key paar op te slaan 244 | 245 | U kunt ook docker secrets gebruiken om de keys op te slaan. 246 | Dit is handig als je **docker-compose** of **docker swarm** gebruikt. 247 | Volg deze voorbeelden: 248 | 249 | ```bash 250 | cat secrets/id_ed25519.pub | docker secret create key_pub - 251 | cat secrets/id_ed25519 | docker secret create key_priv - 252 | docker service create --name rustdesk-server \ 253 | --secret key_priv --secret key_pub \ 254 | --net=host \ 255 | -e "RELAY=rustdeskrelay.example.com" \ 256 | -e "ENCRYPTED_ONLY=1" \ 257 | -e "DB_URL=/db/db_v2.sqlite3" \ 258 | --mount "type=bind,source=$PWD/db,destination=/db" \ 259 | rustdesk/rustdesk-server-s6:latest 260 | ``` 261 | 262 | ```yaml 263 | version: '3' 264 | 265 | services: 266 | rustdesk-server: 267 | container_name: rustdesk-server 268 | ports: 269 | - 21115:21115 270 | - 21116:21116 271 | - 21116:21116/udp 272 | - 21117:21117 273 | - 21118:21118 274 | - 21119:21119 275 | image: rustdesk/rustdesk-server-s6:latest 276 | environment: 277 | - "RELAY=rustdesk.example.com:21117" 278 | - "ENCRYPTED_ONLY=1" 279 | - "DB_URL=/db/db_v2.sqlite3" 280 | volumes: 281 | - ./db:/db 282 | restart: unless-stopped 283 | secrets: 284 | - key_pub 285 | - key_priv 286 | 287 | secrets: 288 | key_pub: 289 | file: secrets/id_ed25519.pub 290 | key_priv: 291 | file: secrets/id_ed25519 292 | ``` 293 | 294 | ## Hoe maak je een key paar 295 | 296 | Een key paar is nodig voor encryptie; u kunt het verstrekken, zoals eerder uitgelegd, maar u heeft een manier nodig om er een te maken. 297 | 298 | U kunt dit commando gebruiken om een key paar te genereren: 299 | 300 | ```bash 301 | /usr/bin/rustdesk-utils genkeypair 302 | ``` 303 | 304 | Als u het pakket `rustdesk-utils` niet op uw systeem hebt staan (of wilt), kunt u hetzelfde commando met docker uitvoeren: 305 | 306 | ```bash 307 | docker run --rm --entrypoint /usr/bin/rustdesk-utils rustdesk/rustdesk-server-s6:latest genkeypair 308 | ``` 309 | 310 | De uitvoer ziet er ongeveer zo uit: 311 | 312 | ```text 313 | Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA= 314 | Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA== 315 | ``` 316 | 317 | ## .deb pakketten 318 | 319 | Voor elke binary zijn aparte .deb-pakketten beschikbaar, u kunt ze vinden in de [releases](https://github.com/rustdesk/rustdesk-server/releases). 320 | Deze pakketten zijn bedoeld voor de volgende distributies: 321 | 322 | - Ubuntu 22.04 LTS 323 | - Ubuntu 20.04 LTS 324 | - Ubuntu 18.04 LTS 325 | - Debian 11 bullseye 326 | - Debian 10 buster 327 | 328 | ## ENV variabelen 329 | 330 | hbbs en hbbr kunnen worden geconfigureerd met deze ENV-variabelen. 331 | U kunt de variabelen zoals gebruikelijk opgeven of een `.env` bestand gebruiken. 332 | 333 | | variabele | binary | beschrijving | 334 | | --- | --- | --- | 335 | | ALWAYS_USE_RELAY | hbbs | indien ingesteld op **"Y"** wordt directe peer-verbinding niet toegestaan | 336 | | DB_URL | hbbs | path voor database bestand | 337 | | DOWNGRADE_START_CHECK | hbbr | vertraging (in seconden) voor downgrade-controle | 338 | | DOWNGRADE_THRESHOLD | hbbr | drempel van downgrade controle (bit/ms) | 339 | | KEY | hbbs/hbbr | indien ingesteld forceert dit het gebruik van een specifieke toets, indien ingesteld op **"_"** forceert dit het gebruik van een willekeurige toets | 340 | | LIMIT_SPEED | hbbr | snelheidslimiet (in Mb/s) | 341 | | PORT | hbbs/hbbr | luister-poort (21116 voor hbbs - 21117 voor hbbr) | 342 | | RELAY_SERVERS | hbbs | IP-adres/DNS-naam van de machines waarop hbbr draait (gescheiden door komma) | 343 | | RUST_LOG | all | debug-niveau instellen (error\|warn\|info\|debug\|trace) | 344 | | SINGLE_BANDWIDTH | hbbr | maximale bandbreedte voor een enkele verbinding (in Mb/s) | 345 | | TOTAL_BANDWIDTH | hbbr | maximale totale bandbreedte (in Mb/s) | 346 | -------------------------------------------------------------------------------- /README-TW.md: -------------------------------------------------------------------------------- 1 |

2 | 自行建置 • 3 | Docker • 4 | S6-overlay • 5 | 金鑰對 • 6 | Debian • 7 | 環境參數
8 | [English] | [Deutsch] | [Nederlands] | [简体中文]
9 |

10 | 11 | # RustDesk Server Program 12 | 13 | [![build](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml/badge.svg)](https://github.com/rustdesk/rustdesk-server/actions/workflows/build.yaml) 14 | 15 | [**下載**](https://github.com/rustdesk/rustdesk-server/releases) 16 | 17 | [**說明文件**](https://rustdesk.com/docs/zh-tw/self-host/) 18 | 19 | [**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) 20 | 21 | 自行建置屬於您自己的 RustDesk 伺服器,它是免費的且開源。 22 | 23 | ## 如何自行建置 24 | 25 | ```bash 26 | cargo build --release 27 | ``` 28 | 29 | 在 target/release 中會產生三個可執行檔。 30 | 31 | - hbbs - RustDesk ID/會合伺服器 32 | - hbbr - RustDesk 中繼伺服器 33 | - rustdesk-utils - RustDesk 命令行工具 34 | 35 | 您可以在 [releases](https://github.com/rustdesk/rustdesk-server/releases) 頁面上找到更新的執行檔。 36 | 37 | 如果您需要額外功能,[RustDesk 專業版伺服器](https://rustdesk.com/pricing.html) 或許更適合您。 38 | 39 | 如果您想開發自己的伺服器,[rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) 可能是一個比這個倉庫更好、更簡單的開始。 40 | 41 | ## Docker 映像檔 42 | 43 | Docker 映像檔會在每次 GitHub 發布時自動生成並發布。我們有兩種映像檔。 44 | 45 | ### Classic 映像檔 46 | 47 | 這些映像檔是基於 `ubuntu-20.04` 建置的,僅添加了兩個主要的執行檔(`hbbr` 和 `hbbs`)。它們可在 [Docker Hub](https://hub.docker.com/r/rustdesk/rustdesk-server/) 上取得,帶有以下tags: 48 | 49 | | 架構 | image:tag | 50 | | ------- | ----------------------------------------- | 51 | | amd64 | `rustdesk/rustdesk-server:latest` | 52 | | arm64v8 | `rustdesk/rustdesk-server:latest-arm64v8` | 53 | 54 | 您可以使用以下指令,直接透過 ``docker run`` 來啟動這些映像檔: 55 | 56 | ```bash 57 | docker run --name hbbs --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 58 | docker run --name hbbr --net=host -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 59 | ``` 60 | 61 | 或刪去 `--net=host`, 但 P2P 直接連線會無法運作。 62 | 63 | 對於使用 SELinux 的系統,需要將 ``/root`` 替換為 ``/root:z``,以便容器正確運行。或者,也可以通過添加選項 ``--security-opt label=disable`` 完全禁用 SELinux 容器隔離。 64 | 65 | ```bash 66 | docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbs -r 67 | docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d rustdesk/rustdesk-server:latest hbbr 68 | ``` 69 | 70 | `relay-server-ip` 參數是執行這些容器的伺服器的 IP 地址(或 DNS 名稱)。如果您為 `hbbr` 使用的端口不是 **21117**,則必須使用 **可選** 的 `port` 參數。 71 | 72 | 您也可以使用 docker-compose 使用這個設定做為範例: 73 | 74 | ```yaml 75 | version: '3' 76 | 77 | networks: 78 | rustdesk-net: 79 | external: false 80 | 81 | services: 82 | hbbs: 83 | container_name: hbbs 84 | ports: 85 | - 21115:21115 86 | - 21116:21116 87 | - 21116:21116/udp 88 | - 21118:21118 89 | image: rustdesk/rustdesk-server:latest 90 | command: hbbs -r rustdesk.example.com:21117 91 | volumes: 92 | - ./data:/root 93 | networks: 94 | - rustdesk-net 95 | depends_on: 96 | - hbbr 97 | restart: unless-stopped 98 | 99 | hbbr: 100 | container_name: hbbr 101 | ports: 102 | - 21117:21117 103 | - 21119:21119 104 | image: rustdesk/rustdesk-server:latest 105 | command: hbbr 106 | volumes: 107 | - ./data:/root 108 | networks: 109 | - rustdesk-net 110 | restart: unless-stopped 111 | ``` 112 | 113 | 請編輯第 16 行,將其指向您的中繼伺服器 (監聽端口 21117 那一個)。 如果需要的話,您也可以編輯 volume (第 18 和 33 行)。 114 | 115 | (感謝 @lukebarone 和 @QuiGonLeong 協助提供 docker-compose 的設定範例) 116 | 117 | ## 基於 S6-overlay 的映象檔 118 | 119 | 這些映象檔是針對 `busybox:stable` 建置的,並添加了執行檔(hbbr 和 hbbs)以及 [S6-overlay](https://github.com/just-containers/s6-overlay)。 它們在以及這些 tags 在 [Docker hub](https://hub.docker.com/r/rustdesk/rustdesk-server-s6/) 可用: 120 | 121 | | 架構 | version | image:tag | 122 | | --------- | ------- | -------------------------------------------- | 123 | | multiarch | latest | `rustdesk/rustdesk-server-s6:latest` | 124 | | amd64 | latest | `rustdesk/rustdesk-server-s6:latest-amd64` | 125 | | i386 | latest | `rustdesk/rustdesk-server-s6:latest-i386` | 126 | | arm64v8 | latest | `rustdesk/rustdesk-server-s6:latest-arm64v8` | 127 | | armv7 | latest | `rustdesk/rustdesk-server-s6:latest-armv7` | 128 | | multiarch | 2 | `rustdesk/rustdesk-server-s6:2` | 129 | | amd64 | 2 | `rustdesk/rustdesk-server-s6:2-amd64` | 130 | | i386 | 2 | `rustdesk/rustdesk-server-s6:2-i386` | 131 | | arm64v8 | 2 | `rustdesk/rustdesk-server-s6:2-arm64v8` | 132 | | armv7 | 2 | `rustdesk/rustdesk-server-s6:2-armv7` | 133 | | multiarch | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0` | 134 | | amd64 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-amd64` | 135 | | i386 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-i386` | 136 | | arm64v8 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-arm64v8` | 137 | | armv7 | 2.0.0 | `rustdesk/rustdesk-server-s6:2.0.0-armv7` | 138 | 139 | 強烈建議您使用 `multiarch` 映象檔 可以選擇使用 `major version` 或 `latest` tags。 140 | 141 | S6-overlay 在此充當監督程序,保持兩個進程運行,因此使用此映象檔,您無需運行兩個獨立的容器。 142 | 143 | 您可以直接使用以下命令使用 `docker run` 來啟動這個映象檔: 144 | 145 | ```bash 146 | docker run --name rustdesk-server \ 147 | --net=host \ 148 | -e "RELAY=rustdeskrelay.example.com" \ 149 | -e "ENCRYPTED_ONLY=1" \ 150 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 151 | ``` 152 | 153 | 或刪去 `--net=host`, 但 P2P 直接連線會無法運作。 154 | 155 | ```bash 156 | docker run --name rustdesk-server \ 157 | -p 21115:21115 -p 21116:21116 -p 21116:21116/udp \ 158 | -p 21117:21117 -p 21118:21118 -p 21119:21119 \ 159 | -e "RELAY=rustdeskrelay.example.com" \ 160 | -e "ENCRYPTED_ONLY=1" \ 161 | -v "$PWD/data:/data" -d rustdesk/rustdesk-server-s6:latest 162 | ``` 163 | 164 | 或是您可以使用 docker-compose 文件: 165 | 166 | ```yaml 167 | version: '3' 168 | 169 | services: 170 | rustdesk-server: 171 | container_name: rustdesk-server 172 | ports: 173 | - 21115:21115 174 | - 21116:21116 175 | - 21116:21116/udp 176 | - 21117:21117 177 | - 21118:21118 178 | - 21119:21119 179 | image: rustdesk/rustdesk-server-s6:latest 180 | environment: 181 | - "RELAY=rustdesk.example.com:21117" 182 | - "ENCRYPTED_ONLY=1" 183 | volumes: 184 | - ./data:/data 185 | restart: unless-stopped 186 | ``` 187 | 188 | 對於此容器映象檔,您可以使用這些環境變數,**除了**以下**環境變數**部分指定的那些。 189 | 190 | | 環境變數 | 是否可選 | 敘述 | 191 | | -------------- | -------- | ------------------------------------------ | 192 | | RELAY | 否 | 運行此容器的機器的 IP 地址/ DNS 名稱 | 193 | | ENCRYPTED_ONLY | 是 | 如果設置為 **"1"**,將不接受未加密的連接。 | 194 | | KEY_PUB | 是 | 金鑰對中的公鑰(Public Key) | 195 | | KEY_PRIV | 是 | 金鑰對中的私鑰(Private Key) | 196 | 197 | ### 在基於 S6-overlay 的 Secret 管理 198 | 199 | 您可以將金鑰對保存在 Docker volume 中,但最佳實踐建議不要將金鑰寫入文件系統;因此,我們提供了一些選項。 200 | 201 | 在容器啟動時,會檢查金鑰對的是否存在(`/data/id_ed25519.pub` 和 `/data/id_ed25519`),如果其中一個金鑰不存在,則會從環境變數或 Docker Secret 重新生成它。 202 | 然後檢查金鑰對的有效性:如果公鑰和私鑰不匹配,容器將停止運行。 203 | 如果您未提供金鑰,`hbbs` 將為您產生一個,並將其放置在默認位置。 204 | 205 | #### 使用 ENV 存儲金鑰對 206 | 207 | 您可以使用 Docker 環境變數來儲存金鑰。只需按照以下範例操作: 208 | 209 | ```bash 210 | docker run --name rustdesk-server \ 211 | --net=host \ 212 | -e "RELAY=rustdeskrelay.example.com" \ 213 | -e "ENCRYPTED_ONLY=1" \ 214 | -e "DB_URL=/db/db_v2.sqlite3" \ 215 | -e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \ 216 | -e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \ 217 | -v "$PWD/db:/db" -d rustdesk/rustdesk-server-s6:latest 218 | ``` 219 | 220 | ```yaml 221 | version: '3' 222 | 223 | services: 224 | rustdesk-server: 225 | container_name: rustdesk-server 226 | ports: 227 | - 21115:21115 228 | - 21116:21116 229 | - 21116:21116/udp 230 | - 21117:21117 231 | - 21118:21118 232 | - 21119:21119 233 | image: rustdesk/rustdesk-server-s6:latest 234 | environment: 235 | - "RELAY=rustdesk.example.com:21117" 236 | - "ENCRYPTED_ONLY=1" 237 | - "DB_URL=/db/db_v2.sqlite3" 238 | - "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" 239 | - "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" 240 | volumes: 241 | - ./db:/db 242 | restart: unless-stopped 243 | ``` 244 | 245 | #### 使用 Docker Secret 來儲存金鑰對 246 | 247 | 您還可以使用 Docker Secret來儲存金鑰。 248 | 如果您使用 **docker-compose** 或 **docker swarm**,這很有用。 249 | 只需按照以下示例操作: 250 | 251 | ```bash 252 | cat secrets/id_ed25519.pub | docker secret create key_pub - 253 | cat secrets/id_ed25519 | docker secret create key_priv - 254 | docker service create --name rustdesk-server \ 255 | --secret key_priv --secret key_pub \ 256 | --net=host \ 257 | -e "RELAY=rustdeskrelay.example.com" \ 258 | -e "ENCRYPTED_ONLY=1" \ 259 | -e "DB_URL=/db/db_v2.sqlite3" \ 260 | --mount "type=bind,source=$PWD/db,destination=/db" \ 261 | rustdesk/rustdesk-server-s6:latest 262 | ``` 263 | 264 | ```yaml 265 | version: '3' 266 | 267 | services: 268 | rustdesk-server: 269 | container_name: rustdesk-server 270 | ports: 271 | - 21115:21115 272 | - 21116:21116 273 | - 21116:21116/udp 274 | - 21117:21117 275 | - 21118:21118 276 | - 21119:21119 277 | image: rustdesk/rustdesk-server-s6:latest 278 | environment: 279 | - "RELAY=rustdesk.example.com:21117" 280 | - "ENCRYPTED_ONLY=1" 281 | - "DB_URL=/db/db_v2.sqlite3" 282 | volumes: 283 | - ./db:/db 284 | restart: unless-stopped 285 | secrets: 286 | - key_pub 287 | - key_priv 288 | 289 | secrets: 290 | key_pub: 291 | file: secrets/id_ed25519.pub 292 | key_priv: 293 | file: secrets/id_ed25519 294 | ``` 295 | 296 | ## 如何建立金鑰對 297 | 298 | 加密需要一對金鑰;您可以按照前面所述提供它,但需要一種生成金鑰對的方法。 299 | 300 | 您可以使用以下命令生成一對金鑰: 301 | 302 | ```bash 303 | /usr/bin/rustdesk-utils genkeypair 304 | ``` 305 | 306 | 如果您沒有(或不想)在系統上安裝 `rustdesk-utils` 套件,您可以使用 Docker執行相同的命令: 307 | 308 | ```bash 309 | docker run --rm --entrypoint /usr/bin/rustdesk-utils rustdesk/rustdesk-server-s6:latest genkeypair 310 | ``` 311 | 312 | 輸出將類似於以下內容: 313 | 314 | ```text 315 | Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA= 316 | Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA== 317 | ``` 318 | 319 | ## .deb 套件 320 | 321 | 每個執行檔都有單獨的 .deb 套件可供使用,您可以在 [releases](https://github.com/rustdesk/rustdesk-server/releases) 中找到它們。 322 | 這些套件適用於以下發行版: 323 | 324 | - Ubuntu 22.04 LTS 325 | - Ubuntu 20.04 LTS 326 | - Ubuntu 18.04 LTS 327 | - Debian 11 bullseye 328 | - Debian 10 buster 329 | 330 | ## ENV 環境參數 331 | 332 | 可以使用這些 ENV 參數來配置 hbbs 和 hbbr。 333 | 您可以像往常一樣指定參數,或者使用 .env 文件。 334 | 335 | | 參數 | 執行檔 | 敘述 | 336 | | --------------------- | --------- | -------------------------------------------------------------------- | 337 | | ALWAYS_USE_RELAY | hbbs | 如果設為 **"Y"**,禁止直接點對點連接 | 338 | | DB_URL | hbbs | 資料庫的路徑 | 339 | | DOWNGRADE_START_CHECK | hbbr | 降級檢查之前的延遲時間(以秒為單位) | 340 | | DOWNGRADE_THRESHOLD | hbbr | 降級檢查的閾值(bit/ms) | 341 | | KEY | hbbs/hbbr | 如果設置了,將強制使用特定金鑰,如果設為 **"_"**,則強制使用任何金鑰 | 342 | | LIMIT_SPEED | hbbr | 速度限制(以Mb/s為單位) | 343 | | PORT | hbbs/hbbr | 監聽端口(hbbs為21116,hbbr為21117) | 344 | | RELAY_SERVERS | hbbs | 運行hbbr的機器的IP地址/DNS名稱(用逗號分隔) | 345 | | RUST_LOG | all | 設定 debug level (error\|warn\|info\|debug\|trace) | 346 | | SINGLE_BANDWIDTH | hbbr | 單個連接的最大頻寬(以Mb/s為單位) | 347 | | TOTAL_BANDWIDTH | hbbr | 最大總頻寬(以Mb/s為單位) | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 关于此分支 3 | 4 | 5 | 6 | [![build](https://github.com/lejianwen/rustdesk-server/actions/workflows/build.yaml/badge.svg)](https://github.com/lejianwen/rustdesk-server/actions/workflows/build.yaml) 7 | 8 | - 解决当客户端登录了`Api`账号时链接超时的问题 9 | - s6镜像添加了`Api`支持,`Api`开源地址 https://github.com/lejianwen/rustdesk-api 10 | - 是否必须登录才能链接, `MUST_LOGIN` 默认为 `N`,设置为 `Y` 则必须登录才能链接 11 | - `RUSTDESK_API_JWT_KEY`,设置后会通过`JWT`校验token的合法性 12 | 13 | ## docker镜像地址 14 | 15 | - s6 镜像 [lejianwen/rustdesk-server-s6](https://hub.docker.com/r/lejianwen/rustdesk-server-s6) 16 | 17 | ```yaml 18 | networks: 19 | rustdesk-net: 20 | external: false 21 | services: 22 | rustdesk: 23 | ports: 24 | - 21114:21114 25 | - 21115:21115 26 | - 21116:21116 27 | - 21116:21116/udp 28 | - 21117:21117 29 | - 21118:21118 30 | - 21119:21119 31 | image: lejianwen/rustdesk-server-s6:latest 32 | environment: 33 | - RELAY= 34 | - ENCRYPTED_ONLY=1 35 | - MUST_LOGIN=N 36 | - TZ=Asia/Shanghai 37 | - RUSTDESK_API_RUSTDESK_ID_SERVER= 38 | - RUSTDESK_API_RUSTDESK_RELAY_SERVER= 39 | - RUSTDESK_API_RUSTDESK_API_SERVER=http:// 40 | - RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub 41 | - RUSTDESK_API_JWT_KEY=xxxxxx # jwt key 42 | volumes: 43 | - /data/rustdesk/server:/data 44 | - /data/rustdesk/api:/app/data #将数据库挂载 45 | networks: 46 | - rustdesk-net 47 | restart: unless-stopped 48 | 49 | ``` 50 | 51 | - 普通镜像 [lejianwen/rustdesk-server](https://hub.docker.com/r/lejianwen/rustdesk-server) 52 | 53 | 54 | # API功能截图 55 | 56 | ![Api.png](./readme/api.png) 57 | 58 | ![commnd.png](./readme/command_simple.png) 59 | 60 | 更多查看 [RustDesk Api](https://github.com/lejianwen/rustdesk-api) 61 | 62 | 63 | --- 64 | 65 |

66 | 自行构建 • 67 | Docker • 68 | S6-overlay • 69 | 密钥 • 70 | Debian • 71 | 环境参数
72 | [English] | [Deutsch] | [Nederlands] | [繁体中文]
73 |

74 | 75 | # RustDesk Server Program 76 | 77 | 78 | 79 | [**下载**](https://github.com/lejianwen/rustdesk-server/releases) 80 | 81 | [**说明文件**](https://rustdesk.com/docs/zh-cn/self-host/) 82 | 83 | 自行搭建属于你的RustDesk服务器,所有的一切都是免费且开源的 84 | 85 | ## 如何自行构建 86 | 87 | ```bash 88 | cargo build --release 89 | ``` 90 | 91 | 执行后会在target/release目录下生成三个对应平台的可执行程序 92 | 93 | - hbbs - RustDesk ID/会和服务器 94 | - hbbr - RustDesk 中继服务器 95 | - rustdesk-utils - RustDesk 命令行工具 96 | 97 | 您可以在 [releases](https://github.com/lejianwen/rustdesk-server/releases) 页面中找到最新的服务端软件。 98 | 99 | 如果您需要额外的功能支持,[RustDesk 专业版服务器](https://rustdesk.com/pricing.html) 获取更适合您。 100 | 101 | 如果您想开发自己的服务器,[rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) 应该会比直接使用这个仓库更简单快捷。 102 | 103 | ## Docker 镜像 104 | 105 | Docker镜像会在每次 GitHub 发布新的release版本时自动构建。我们提供两种类型的镜像。 106 | 107 | ### Classic 传统镜像 108 | 109 | 这个类型的镜像是基于 `ubuntu-20.04` 进行构建,镜像仅包含两个主要的可执行程序(`hbbr` 和 `hbbs`)。它们可以通过以下tag在 [Docker Hub](https://hub.docker.com/r/lejianwen/rustdesk-server/) 上获得: 110 | 111 | | 架构 | image:tag | 112 | |---------| ----------------------------------------- | 113 | | amd64 | `lejianwen/rustdesk-server:latest` | 114 | | arm64v8 | `lejianwen/rustdesk-server:latest-arm64v8` | 115 | 116 | 您可以使用以下命令,直接通过 ``docker run`` 來启动这些镜像: 117 | 118 | ```bash 119 | docker run --name hbbs --net=host -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbs -r 120 | docker run --name hbbr --net=host -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbr 121 | ``` 122 | 123 | 或不使用 `--net=host` 参数启动, 但这样 P2P 直连功能将无法工作。 124 | 125 | 对于使用了 SELinux 的系统,您需要将 ``/root`` 替换为 ``/root:z``,以保证容器的正常运行。或者,也可以通过添加参数 ``--security-opt label=disable`` 来完全禁用 SELinux 容器隔离。 126 | 127 | ```bash 128 | docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbs -r 129 | docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbr 130 | ``` 131 | 132 | `relay-server-ip` 参数是运行这些容器的服务器的 IP 地址(或 DNS 名称)。如果你不想使用 **21117** 作为 `hbbr` 的服务端口,可使用可选参数 `port` 进行指定。 133 | 134 | 您也可以使用 docker-compose 进行构建,以下为配置示例: 135 | 136 | ```yaml 137 | version: '3' 138 | 139 | networks: 140 | rustdesk-net: 141 | external: false 142 | 143 | services: 144 | hbbs: 145 | container_name: hbbs 146 | ports: 147 | - 21115:21115 148 | - 21116:21116 149 | - 21116:21116/udp 150 | - 21118:21118 151 | image: lejianwen/rustdesk-server:latest 152 | command: hbbs -r rustdesk.example.com:21117 153 | volumes: 154 | - ./data:/root 155 | networks: 156 | - rustdesk-net 157 | depends_on: 158 | - hbbr 159 | restart: unless-stopped 160 | 161 | hbbr: 162 | container_name: hbbr 163 | ports: 164 | - 21117:21117 165 | - 21119:21119 166 | image: lejianwen/rustdesk-server:latest 167 | command: hbbr 168 | volumes: 169 | - ./data:/root 170 | networks: 171 | - rustdesk-net 172 | restart: unless-stopped 173 | ``` 174 | 175 | 编辑第16行来指定你的中继服务器 (默认端口监听在 21117 的那一个)。 如果需要的话,您也可以编辑 volume 信息 (第 18 和 33 行)。 176 | 177 | (感谢 @lukebarone 和 @QuiGonLeong 协助提供的 docker-compose 配置示例) 178 | 179 | ## 基于 S6-overlay 的镜像 180 | 181 | > 这些镜像是针对 `busybox:stable` 构建的,并添加了可执行程序(hbbr 和 hbbs)以及 [S6-overlay](https://github.com/just-containers/s6-overlay)。 它们可以使用以下tag在 [Docker hub](https://hub.docker.com/r/lejianwen/rustdesk-server-s6/) 上获取: 182 | 183 | 184 | | 架構 | version | image:tag | 185 | | --------- | ------- | -------------------------------------------- | 186 | | multiarch | latest | `lejianwen/rustdesk-server-s6:latest` | 187 | | amd64 | latest | `lejianwen/rustdesk-server-s6:latest-amd64` | 188 | | i386 | latest | `lejianwen/rustdesk-server-s6:latest-i386` | 189 | | arm64v8 | latest | `lejianwen/rustdesk-server-s6:latest-arm64v8` | 190 | | armv7 | latest | `lejianwen/rustdesk-server-s6:latest-armv7` | 191 | | multiarch | 2 | `lejianwen/rustdesk-server-s6:2` | 192 | | amd64 | 2 | `lejianwen/rustdesk-server-s6:2-amd64` | 193 | | i386 | 2 | `lejianwen/rustdesk-server-s6:2-i386` | 194 | | arm64v8 | 2 | `lejianwen/rustdesk-server-s6:2-arm64v8` | 195 | | armv7 | 2 | `lejianwen/rustdesk-server-s6:2-armv7` | 196 | | multiarch | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0` | 197 | | amd64 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-amd64` | 198 | | i386 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-i386` | 199 | | arm64v8 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-arm64v8` | 200 | | armv7 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-armv7` | 201 | 202 | 强烈建议您使用`major version` 或 `latest` tag 的 `multiarch` 架构的镜像。 203 | 204 | S6-overlay 在此处作为监控程序,用以保证两个进程的运行,因此使用此镜像,您无需运行两个容器。 205 | 206 | 您可以使用 `docker run` 命令直接启动镜像,如下: 207 | 208 | ```bash 209 | docker run --name rustdesk-server \ 210 | --net=host \ 211 | -e "RELAY=rustdeskrelay.example.com" \ 212 | -e "ENCRYPTED_ONLY=1" \ 213 | -v "$PWD/data:/data" -d lejianwen/rustdesk-server-s6:latest 214 | ``` 215 | 216 | 或刪去 `--net=host` 参数, 但 P2P 直连功能将无法工作。 217 | 218 | ```bash 219 | docker run --name rustdesk-server \ 220 | -p 21115:21115 -p 21116:21116 -p 21116:21116/udp \ 221 | -p 21117:21117 -p 21118:21118 -p 21119:21119 \ 222 | -e "RELAY=rustdeskrelay.example.com" \ 223 | -e "ENCRYPTED_ONLY=1" \ 224 | -v "$PWD/data:/data" -d lejianwen/rustdesk-server-s6:latest 225 | ``` 226 | 227 | 或着您也可以使用 docker-compose 文件: 228 | 229 | ```yaml 230 | version: '3' 231 | 232 | services: 233 | rustdesk-server: 234 | container_name: rustdesk-server 235 | ports: 236 | - 21114:21114 237 | - 21115:21115 238 | - 21116:21116 239 | - 21116:21116/udp 240 | - 21117:21117 241 | - 21118:21118 242 | - 21119:21119 243 | image: lejianwen/rustdesk-server-s6:latest 244 | environment: 245 | - "RELAY=rustdesk.example.com:21117" 246 | - "ENCRYPTED_ONLY=1" 247 | volumes: 248 | - ./data:/data 249 | restart: unless-stopped 250 | ``` 251 | 252 | 对于此容器镜像,除了在下面的环境变量部分指定的变量之外,您还可以使用以下`环境变量` 253 | 254 | | 环境变量 | 是否可选 | 描述 | 255 | |----------------|------|--------------------------| 256 | | RELAY | 否 | 运行此容器的宿主机的 IP 地址/ DNS 名称 | 257 | | ENCRYPTED_ONLY | 是 | 如果设置为 **"1"**,将不接受未加密的连接。 | 258 | | KEY_PUB | 是 | 密钥对中的公钥(Public Key) | 259 | | KEY_PRIV | 是 | 密钥对中的私钥(Private Key) | 260 | 261 | ### 基于 S6-overlay 镜像的密钥管理 262 | 263 | 您可以将密钥对保存在 Docker volume 中,但我们建议不要将密钥写入文件系統中;因此,我们提供了一些方案。 264 | 265 | 在容器启动时,会检查密钥对是否存在(`/data/id_ed25519.pub` 和 `/data/id_ed25519`),如果其中一個密钥不存在,则会从环境变量或 Docker Secret 中重新生成它。 266 | 然后检查密钥对的可用性:如果公钥和私钥不匹配,容器将停止运行。 267 | 如果您未提供密钥,`hbbs` 将会在默认位置生成一个。 268 | 269 | #### 使用 ENV 存储密钥对 270 | 271 | 您可以使用 Docker 环境变量來存储密钥。如下: 272 | 273 | ```bash 274 | docker run --name rustdesk-server \ 275 | --net=host \ 276 | -e "RELAY=rustdeskrelay.example.com" \ 277 | -e "ENCRYPTED_ONLY=1" \ 278 | -e "DB_URL=/db/db_v2.sqlite3" \ 279 | -e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \ 280 | -e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \ 281 | -v "$PWD/db:/db" -d lejianwen/rustdesk-server-s6:latest 282 | ``` 283 | 284 | ```yaml 285 | version: '3' 286 | 287 | services: 288 | rustdesk-server: 289 | container_name: rustdesk-server 290 | ports: 291 | - 21114:21114 292 | - 21115:21115 293 | - 21116:21116 294 | - 21116:21116/udp 295 | - 21117:21117 296 | - 21118:21118 297 | - 21119:21119 298 | image: lejianwen/rustdesk-server-s6:latest 299 | environment: 300 | - "RELAY=rustdesk.example.com:21117" 301 | - "ENCRYPTED_ONLY=1" 302 | - "DB_URL=/db/db_v2.sqlite3" 303 | - "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" 304 | - "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" 305 | volumes: 306 | - ./db:/db 307 | restart: unless-stopped 308 | ``` 309 | 310 | #### 使用 Docker Secret 來保存密钥对 311 | 312 | 您还可以使用 Docker Secret 來保存密钥。 313 | 如果您使用 **docker-compose** 或 **docker swarm**,推荐您使用。 314 | 只需按照以下示例操作: 315 | 316 | ```bash 317 | cat secrets/id_ed25519.pub | docker secret create key_pub - 318 | cat secrets/id_ed25519 | docker secret create key_priv - 319 | docker service create --name rustdesk-server \ 320 | --secret key_priv --secret key_pub \ 321 | --net=host \ 322 | -e "RELAY=rustdeskrelay.example.com" \ 323 | -e "ENCRYPTED_ONLY=1" \ 324 | -e "DB_URL=/db/db_v2.sqlite3" \ 325 | --mount "type=bind,source=$PWD/db,destination=/db" \ 326 | lejianwen/rustdesk-server-s6:latest 327 | ``` 328 | 329 | ```yaml 330 | version: '3' 331 | 332 | services: 333 | rustdesk-server: 334 | container_name: rustdesk-server 335 | ports: 336 | - 21114:21114 337 | - 21115:21115 338 | - 21116:21116 339 | - 21116:21116/udp 340 | - 21117:21117 341 | - 21118:21118 342 | - 21119:21119 343 | image: lejianwen/rustdesk-server-s6:latest 344 | environment: 345 | - "RELAY=rustdesk.example.com:21117" 346 | - "ENCRYPTED_ONLY=1" 347 | - "DB_URL=/db/db_v2.sqlite3" 348 | volumes: 349 | - ./db:/db 350 | restart: unless-stopped 351 | secrets: 352 | - key_pub 353 | - key_priv 354 | 355 | secrets: 356 | key_pub: 357 | file: secrets/id_ed25519.pub 358 | key_priv: 359 | file: secrets/id_ed25519 360 | ``` 361 | 362 | ## 如何生成密钥对 363 | 364 | 加密需要一对密钥;您可以按照前面所述提供它,但需要一个工具去生成密钥对。 365 | 366 | 您可以使用以下命令生成一对密钥: 367 | 368 | ```bash 369 | /usr/bin/rustdesk-utils genkeypair 370 | ``` 371 | 372 | 如果您沒有(或不想)在系统上安装 `rustdesk-utils` 套件,您可以使用 Docker 执行相同的命令: 373 | 374 | ```bash 375 | docker run --rm --entrypoint /usr/bin/rustdesk-utils lejianwen/rustdesk-server-s6:latest genkeypair 376 | ``` 377 | 378 | 运行后的输出内容如下: 379 | 380 | ```text 381 | Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA= 382 | Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA== 383 | ``` 384 | 385 | ## .deb 套件 386 | 387 | 每个可执行文件都有单独的 .deb 套件可供使用,您可以在 [releases](https://github.com/lejianwen/rustdesk-server/releases) 页面中找到它們。 388 | 這些套件适用于以下发行版: 389 | 390 | - Ubuntu 22.04 LTS 391 | - Ubuntu 20.04 LTS 392 | - Ubuntu 18.04 LTS 393 | - Debian 11 bullseye 394 | - Debian 10 buster 395 | 396 | ## ENV 环境变量 397 | 398 | 可以使用这些`环境变量`参数來配置 hbbs 和 hbbr。 399 | 您可以像往常一样指定参数,或者使用 .env 文件。 400 | 401 | | 参数 | 可执行文件 | 描述 | 402 | |-----------------------|---------------|--------------------------------------------------| 403 | | ALWAYS_USE_RELAY | hbbs | 如果设定为 **"Y"**,将关闭直接点对点连接功能 | 404 | | DB_URL | hbbs | 数据库配置 | 405 | | DOWNGRADE_START_CHECK | hbbr | 降级检查之前的延迟是啊尽(以秒为单位) | 406 | | DOWNGRADE_THRESHOLD | hbbr | 降级检查的阈值(bit/ms) | 407 | | KEY | hbbs/hbbr | 如果设置了此参数,将强制使用指定密钥对,如果设为 **"_"**,则强制使用任意密钥 | 408 | | LIMIT_SPEED | hbbr | 速度限制(以Mb/s为单位) | 409 | | PORT | hbbs/hbbr | 监听端口(hbbs为21116,hbbr为21117) | 410 | | RELAY_SERVERS | hbbs | 运行hbbr的机器的IP地址/DNS名称(用逗号分隔) | 411 | | RUST_LOG | all | 设置 debug level (error\|warn\|info\|debug\|trace) | 412 | | SINGLE_BANDWIDTH | hbbr | 单个连接的最大带宽(以Mb/s为单位) | 413 | | TOTAL_BANDWIDTH | hbbr | 最大总带宽(以Mb/s为单位) | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | hbb_common::gen_version(); 3 | } 4 | -------------------------------------------------------------------------------- /db_v2.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/db_v2.sqlite3 -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | rustdesk-server (1.1.14) UNRELEASED; urgency=medium 2 | * Fix windows crash 3 | 4 | rustdesk-server (1.1.13) UNRELEASED; urgency=medium 5 | * Version check and refactor hbb_common to share with rustdesk client 6 | 7 | rustdesk-server (1.1.12) UNRELEASED; urgency=medium 8 | * WS real ip 9 | * Bump s6-overlay to v3.2.0.0 and fix env warnings 10 | 11 | rustdesk-server (1.1.11-1) UNRELEASED; urgency=medium 12 | * set reuse port to make restart friendly 13 | * revert hbbr `-k` to not ruin back-compatibility 14 | 15 | rustdesk-server (1.1.11) UNRELEASED; urgency=medium 16 | * change -k to default '-', so you need not to set -k any more 17 | 18 | rustdesk-server (1.1.10-3) UNRELEASED; urgency=medium 19 | * fix on -2 20 | 21 | rustdesk-server (1.1.10-2) UNRELEASED; urgency=medium 22 | 23 | * fix hangup signal exit when run with nohup 24 | * some minors 25 | 26 | rustdesk-server (1.1.9) UNRELEASED; urgency=medium 27 | 28 | * remove unsafe 29 | 30 | rustdesk-server (1.1.8) UNRELEASED; urgency=medium 31 | 32 | * fix test_hbbs and mask in lan 33 | 34 | rustdesk-server (1.1.7) UNRELEASED; urgency=medium 35 | 36 | * ipv6 support 37 | 38 | -- rustdesk Wed, 11 Jan 2023 11:27:00 +0800 39 | 40 | rustdesk-server (1.1.6) UNRELEASED; urgency=medium 41 | 42 | * Initial release 43 | 44 | -- open-trade Fri, 15 Jul 2022 12:27:27 +0200 45 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control.tpl: -------------------------------------------------------------------------------- 1 | Source: rustdesk-server 2 | Section: net 3 | Priority: optional 4 | Maintainer: open-trade 5 | Build-Depends: debhelper (>= 10), pkg-config 6 | Standards-Version: 4.5.0 7 | Homepage: https://rustdesk.com/ 8 | 9 | Package: rustdesk-server-hbbs 10 | Architecture: {{ ARCH }} 11 | Depends: systemd ${misc:Depends} 12 | Description: RustDesk server 13 | Self-host your own RustDesk server, it is free and open source. 14 | 15 | Package: rustdesk-server-hbbr 16 | Architecture: {{ ARCH }} 17 | Depends: systemd ${misc:Depends} 18 | Description: RustDesk server 19 | Self-host your own RustDesk server, it is free and open source. 20 | This package contains the RustDesk relay server. 21 | 22 | Package: rustdesk-server-utils 23 | Architecture: {{ ARCH }} 24 | Depends: ${misc:Depends} 25 | Description: RustDesk server 26 | Self-host your own RustDesk server, it is free and open source. 27 | This package contains the rustdesk-utils binary. 28 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ 4 | 5 | override_dh_builddeb: 6 | dh_builddeb -- -Zgzip 7 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbr.install: -------------------------------------------------------------------------------- 1 | bin/hbbr usr/bin 2 | systemd/rustdesk-hbbr.service lib/systemd/system 3 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbr.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-hbbr.service 5 | 6 | if [ "$1" = "configure" ]; then 7 | mkdir -p /var/log/rustdesk-server 8 | fi 9 | 10 | case "$1" in 11 | configure|abort-upgrade|abort-deconfigure|abort-remove) 12 | mkdir -p /var/lib/rustdesk-server/ 13 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true 14 | if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then 15 | deb-systemd-invoke enable "${SERVICE}" >/dev/null || true 16 | else 17 | deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true 18 | fi 19 | systemctl --system daemon-reload >/dev/null || true 20 | if [ -n "$2" ]; then 21 | deb-systemd-invoke restart "${SERVICE}" >/dev/null || true 22 | else 23 | deb-systemd-invoke start "${SERVICE}" >/dev/null || true 24 | fi 25 | ;; 26 | esac 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbr.postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-hbbr.service 5 | 6 | systemctl --system daemon-reload >/dev/null || true 7 | 8 | if [ "$1" = "purge" ]; then 9 | rm -rf /var/log/rustdesk-server/rustdesk-hbbr.* 10 | deb-systemd-helper purge "${SERVICE}" >/dev/null || true 11 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true 12 | fi 13 | 14 | if [ "$1" = "remove" ]; then 15 | deb-systemd-helper mask "${SERVICE}" >/dev/null || true 16 | fi 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbr.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-hbbr.service 5 | 6 | case "$1" in 7 | remove|deconfigure) 8 | deb-systemd-invoke stop "${SERVICE}" >/dev/null || true 9 | deb-systemd-invoke disable "${SERVICE}" >/dev/null || true 10 | ;; 11 | esac 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbs.install: -------------------------------------------------------------------------------- 1 | bin/hbbs usr/bin 2 | systemd/rustdesk-hbbs.service lib/systemd/system 3 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbs.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-hbbs.service 5 | 6 | if [ "$1" = "configure" ]; then 7 | mkdir -p /var/log/rustdesk-server 8 | fi 9 | 10 | case "$1" in 11 | configure|abort-upgrade|abort-deconfigure|abort-remove) 12 | mkdir -p /var/lib/rustdesk-server/ 13 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true 14 | if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then 15 | deb-systemd-invoke enable "${SERVICE}" >/dev/null || true 16 | else 17 | deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true 18 | fi 19 | systemctl --system daemon-reload >/dev/null || true 20 | if [ -n "$2" ]; then 21 | deb-systemd-invoke restart "${SERVICE}" >/dev/null || true 22 | else 23 | deb-systemd-invoke start "${SERVICE}" >/dev/null || true 24 | fi 25 | ;; 26 | esac 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbs.postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-hbbs.service 5 | 6 | systemctl --system daemon-reload >/dev/null || true 7 | 8 | if [ "$1" = "purge" ]; then 9 | rm -rf /var/lib/rustdesk-server/ /var/log/rustdesk-server/rustdesk-hbbs.* 10 | deb-systemd-helper purge "${SERVICE}" >/dev/null || true 11 | deb-systemd-helper unmask "${SERVICE}" >/dev/null || true 12 | fi 13 | 14 | if [ "$1" = "remove" ]; then 15 | deb-systemd-helper mask "${SERVICE}" >/dev/null || true 16 | fi 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /debian/rustdesk-server-hbbs.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SERVICE=rustdesk-hbbs.service 5 | 6 | case "$1" in 7 | remove|deconfigure) 8 | deb-systemd-invoke stop "${SERVICE}" >/dev/null || true 9 | deb-systemd-invoke disable "${SERVICE}" >/dev/null || true 10 | ;; 11 | esac 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /debian/rustdesk-server-utils.install: -------------------------------------------------------------------------------- 1 | bin/rustdesk-utils usr/bin 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /docker-classic/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | COPY hbbs /usr/bin/hbbs 3 | COPY hbbr /usr/bin/hbbr 4 | WORKDIR /root 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | rustdesk-net: 3 | external: false 4 | services: 5 | rustdesk: 6 | ports: 7 | - 21114:21114 8 | - 21115:21115 9 | - 21116:21116 10 | - 21116:21116/udp 11 | - 21117:21117 12 | - 21118:21118 13 | - 21119:21119 14 | image: lejianwen/rustdesk-server-s6:latest 15 | environment: 16 | - RELAY= 17 | - ENCRYPTED_ONLY=1 18 | - MUST_LOGIN=N 19 | - TZ=Asia/Shanghai 20 | - RUSTDESK_API_RUSTDESK_ID_SERVER= 21 | - RUSTDESK_API_RUSTDESK_RELAY_SERVER= 22 | - RUSTDESK_API_RUSTDESK_API_SERVER=http:// 23 | volumes: 24 | - /data/rustdesk/server:/data 25 | - /data/rustdesk/api:/app/data #将数据库挂载 26 | - /data/rustdesk/server:/app/conf/data #挂载key文件到api容器,可以不用使用 RUSTDESK_API_RUSTDESK_KEY 27 | networks: 28 | - rustdesk-net 29 | restart: unless-stopped -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lejianwen/rustdesk-api:latest 2 | 3 | ARG S6_OVERLAY_VERSION=3.2.0.0 4 | ARG S6_ARCH=x86_64 5 | ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp 6 | ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz /tmp 7 | RUN \ 8 | tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \ 9 | tar -C / -Jxpf /tmp/s6-overlay-${S6_ARCH}.tar.xz && \ 10 | rm /tmp/s6-overlay*.tar.xz && \ 11 | ln -s /run /var/run 12 | 13 | COPY rootfs / 14 | 15 | ENV RELAY=relay.example.com 16 | ENV ENCRYPTED_ONLY=0 17 | 18 | EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119 19 | 20 | HEALTHCHECK --interval=10s --timeout=5s CMD /usr/bin/healthcheck.sh 21 | 22 | WORKDIR /app 23 | 24 | VOLUME /app/data 25 | 26 | VOLUME /data 27 | 28 | ENTRYPOINT ["/init"] 29 | -------------------------------------------------------------------------------- /docker/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/hbbr || exit 1 4 | /package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/hbbs || exit 1 5 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/api/dependencies: -------------------------------------------------------------------------------- 1 | key-secret 2 | hbbs 3 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/api/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv sh 2 | sleep 3 3 | cd /app 4 | ./apimain 5 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/api/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/hbbr/dependencies: -------------------------------------------------------------------------------- 1 | key-secret 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/hbbr/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv sh 2 | cd /data 3 | PARAMS= 4 | [ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k _" 5 | /usr/bin/hbbr $PARAMS 6 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/hbbr/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/hbbs/dependencies: -------------------------------------------------------------------------------- 1 | key-secret 2 | hbbr 3 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/hbbs/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv sh 2 | sleep 2 3 | cd /data 4 | PARAMS= 5 | [ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k _" 6 | /usr/bin/hbbs -r $RELAY $PARAMS 7 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/hbbs/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/key-secret/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/key-secret/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/key-secret/up.real 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/key-secret/up.real: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv sh 2 | 3 | if [ ! -d /data ] ; then 4 | mkdir /data 5 | fi 6 | 7 | # normal docker secrets 8 | if [ ! -f /data/id_ed25519.pub ] && [ -r /run/secrets/key_pub ] ; then 9 | cp /run/secrets/key_pub /data/id_ed25519.pub 10 | echo "Public key created from secret" 11 | fi 12 | 13 | if [ ! -f /data/id_ed25519 ] && [ -r /run/secrets/key_priv ] ; then 14 | cp /run/secrets/key_priv /data/id_ed25519 15 | echo "Private key created from secret" 16 | fi 17 | 18 | # ENV variables 19 | if [ ! -f /data/id_ed25519.pub ] && [ ! "$KEY_PUB" = "" ] ; then 20 | echo -n "$KEY_PUB" > /data/id_ed25519.pub 21 | echo "Public key created from ENV variable" 22 | fi 23 | 24 | if [ ! -f /data/id_ed25519 ] && [ ! "$KEY_PRIV" = "" ] ; then 25 | echo -n "$KEY_PRIV" > /data/id_ed25519 26 | echo "Private key created from ENV variable" 27 | fi 28 | 29 | # check if both keys provided 30 | if [ -f /data/id_ed25519.pub ] && [ ! -f /data/id_ed25519 ] ; then 31 | echo "Private key missing." 32 | echo "You must provide BOTH the private and the public key." 33 | /run/s6/basedir/bin/halt 34 | exit 1 35 | fi 36 | 37 | if [ ! -f /data/id_ed25519.pub ] && [ -f /data/id_ed25519 ] ; then 38 | echo "Public key missing." 39 | echo "You must provide BOTH the private and the public key." 40 | /run/s6/basedir/bin/halt 41 | exit 1 42 | fi 43 | 44 | # here we have either no keys or both 45 | 46 | # if we have both keys, we fix permissions and ownership 47 | # and check for keypair validation 48 | if [ -f /data/id_ed25519.pub ] && [ -f /data/id_ed25519 ] ; then 49 | chmod 0600 /data/id_ed25519.pub /data/id_ed25519 50 | chown root:root /data/id_ed25519.pub /data/id_ed25519 51 | /usr/bin/rustdesk-utils validatekeypair "$(cat /data/id_ed25519.pub)" "$(cat /data/id_ed25519)" || { 52 | echo "Key pair not valid" 53 | /run/s6/basedir/bin/halt 54 | exit 1 55 | } 56 | fi 57 | 58 | # if we have no keypair, hbbs will generate one 59 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/api -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/hbbr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/hbbr -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/hbbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/hbbs -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/key-secret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/key-secret -------------------------------------------------------------------------------- /docker/rootfs/usr/bin/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/hbbr || exit 1 4 | /package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/hbbs || exit 1 5 | -------------------------------------------------------------------------------- /libs/hbb_common: -------------------------------------------------------------------------------- 1 | 83419b6549636ee39dacef7776c473f5802e08d6 -------------------------------------------------------------------------------- /rcd/rustdesk-hbbr: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # PROVIDE: rustdesk_hbbr 4 | # REQUIRE: LOGIN 5 | # KEYWORD: shutdown 6 | # 7 | # Add the following lines to /etc/rc.conf.local or /etc/rc.conf 8 | # to enable this service: 9 | # 10 | # rustdesk_hbbr_enable (bool): Set to NO by default. 11 | # Set it to YES to enable rustdesk_hbbr. 12 | # rustdesk_hbbr_args (string): Set extra arguments to pass to rustdesk_hbbr 13 | # Default is "-k _". 14 | # rustdesk_hbbr_user (string): Set user that rustdesk_hbbr will run under 15 | # Default is "root". 16 | # rustdesk_hbbr_group (string): Set group that rustdesk_hbbr will run under 17 | # Default is "wheel". 18 | 19 | . /etc/rc.subr 20 | 21 | name=rustdesk_hbbr 22 | desc="Rustdesk Relay Server" 23 | rcvar=rustdesk_hbbr_enable 24 | 25 | load_rc_config $name 26 | 27 | : ${rustdesk_hbbr_enable:=NO} 28 | : ${rustdesk_hbbr_args="-k _"} 29 | : ${rustdesk_hbbr_user:=rustdesk} 30 | : ${rustdesk_hbbr_group:=rustdesk} 31 | 32 | pidfile=/var/run/rustdesk_hbbr.pid 33 | command=/usr/sbin/daemon 34 | procname=/usr/local/sbin/hbbr 35 | rustdesk_hbbr_chdir=/var/db/rustdesk-server 36 | command_args="-p ${pidfile} -o /var/log/rustdesk-hbbr.log ${procname} ${rustdesk_hbbr_args}" 37 | ## If you want the daemon do its log over syslog comment out the above line and remove the comment from the below replacement 38 | #command_args="-p ${pidfile} -T ${name} ${procname} ${rustdesk_hbbr_args}" 39 | 40 | start_precmd=rustdesk_hbbr_startprecmd 41 | 42 | rustdesk_hbbr_startprecmd() 43 | { 44 | if [ -e ${pidfile} ]; then 45 | chown ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} ${pidfile}; 46 | else 47 | install -o ${rustdesk_hbbr_user} -g ${rustdesk_hbbr_group} /dev/null ${pidfile}; 48 | fi 49 | if [ -e ${rustdesk_hbbr_chdir} ]; then 50 | chown -R ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} ${rustdesk_hbbr_chdir}; 51 | chmod -R 770 ${rustdesk_hbbr_chdir}; 52 | else 53 | mkdir -m 770 ${rustdesk_hbbr_chdir}; 54 | chown ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} ${rustdesk_hbbr_chdir}; 55 | fi 56 | if [ -e /var/log/rustdesk-hbbr.log ]; then 57 | chown -R ${rustdesk_hbbr_user}:${rustdesk_hbbr_group} /var/log/rustdesk-hbbr.log; 58 | chmod 660 /var/log/rustdesk-hbbr.log; 59 | else 60 | install -o ${rustdesk_hbbr_user} -g ${rustdesk_hbbr_group} /dev/null /var/log/rustdesk-hbbr.log; 61 | chmod 660 /var/log/rustdesk-hbbr.log; 62 | fi 63 | } 64 | 65 | run_rc_command "$1" 66 | -------------------------------------------------------------------------------- /rcd/rustdesk-hbbs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # PROVIDE: rustdesk_hbbs 4 | # REQUIRE: LOGIN 5 | # KEYWORD: shutdown 6 | # 7 | # Add the following lines to /etc/rc.conf.local or /etc/rc.conf 8 | # to enable this service: 9 | # 10 | # rustdesk_hbbs_enable (bool): Set to NO by default. 11 | # Set it to YES to enable rustdesk_hbbs. 12 | # rustdesk_hbbs_ip (string): Set IP address/hostname of relay server to use 13 | # Defaults to "127.0.0.1", please replace with your server hostname/IP. 14 | # rustdesk_hbbs_args (string): Set extra arguments to pass to rustdesk_hbbs 15 | # Default is "-r ${rustdesk_hbbs_ip} -k _". 16 | # rustdesk_hbbs_user (string): Set user that rustdesk_hbbs will run under 17 | # Default is "root". 18 | # rustdesk_hbbs_group (string): Set group that rustdesk_hbbs will run under 19 | # Default is "wheel". 20 | 21 | . /etc/rc.subr 22 | 23 | name=rustdesk_hbbs 24 | desc="Rustdesk ID/Rendezvous Server" 25 | rcvar=rustdesk_hbbs_enable 26 | 27 | load_rc_config $name 28 | 29 | : ${rustdesk_hbbs_enable:=NO} 30 | : ${rustdesk_hbbs_ip:=127.0.0.1} 31 | : ${rustdesk_hbbs_args="-r ${rustdesk_hbbs_ip} -k _"} 32 | : ${rustdesk_hbbs_user:=rustdesk} 33 | : ${rustdesk_hbbs_group:=rustdesk} 34 | 35 | pidfile=/var/run/rustdesk_hbbs.pid 36 | command=/usr/sbin/daemon 37 | procname=/usr/local/sbin/hbbs 38 | rustdesk_hbbs_chdir=/var/db/rustdesk-server 39 | command_args="-p ${pidfile} -o /var/log/rustdesk-hbbs.log ${procname} ${rustdesk_hbbs_args}" 40 | ## If you want the daemon to do its log over syslog, comment out the above line and remove the comment from the below replacement 41 | #command_args="-p ${pidfile} -T ${name} ${procname} ${rustdesk_hbbs_args}" 42 | 43 | start_precmd=rustdesk_hbbs_startprecmd 44 | 45 | rustdesk_hbbs_startprecmd() 46 | { 47 | if [ -e ${pidfile} ]; then 48 | chown ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} ${pidfile}; 49 | else 50 | install -o ${rustdesk_hbbs_user} -g ${rustdesk_hbbs_group} /dev/null ${pidfile}; 51 | fi 52 | if [ -e ${rustdesk_hbbs_chdir} ]; then 53 | chown -R ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} ${rustdesk_hbbs_chdir}; 54 | chmod -R 770 ${rustdesk_hbbs_chdir}; 55 | else 56 | mkdir -m 770 ${rustdesk_hbbs_chdir}; 57 | chown ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} ${rustdesk_hbbs_chdir}; 58 | fi 59 | if [ -e /var/log/rustdesk-hbbs.log ]; then 60 | chown -R ${rustdesk_hbbs_user}:${rustdesk_hbbs_group} /var/log/rustdesk-hbbs.log; 61 | chmod 660 /var/log/rustdesk-hbbs.log; 62 | else 63 | install -o ${rustdesk_hbbs_user} -g ${rustdesk_hbbs_group} /dev/null /var/log/rustdesk-hbbs.log; 64 | chmod 660 /var/log/rustdesk-hbbs.log; 65 | fi 66 | } 67 | 68 | run_rc_command "$1" 69 | -------------------------------------------------------------------------------- /readme/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/readme/api.png -------------------------------------------------------------------------------- /readme/command_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/readme/command_simple.png -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use clap::App; 2 | use hbb_common::{ 3 | allow_err, anyhow::{Context, Result}, get_version_number, log, tokio, ResultType 4 | }; 5 | use ini::Ini; 6 | use sodiumoxide::crypto::sign; 7 | use std::{ 8 | io::prelude::*, 9 | io::Read, 10 | net::SocketAddr, 11 | time::{Instant, SystemTime}, 12 | }; 13 | 14 | #[allow(dead_code)] 15 | pub(crate) fn get_expired_time() -> Instant { 16 | let now = Instant::now(); 17 | now.checked_sub(std::time::Duration::from_secs(3600)) 18 | .unwrap_or(now) 19 | } 20 | 21 | #[allow(dead_code)] 22 | pub(crate) fn test_if_valid_server(host: &str, name: &str) -> ResultType { 23 | use std::net::ToSocketAddrs; 24 | let res = if host.contains(':') { 25 | host.to_socket_addrs()?.next().context("") 26 | } else { 27 | format!("{}:{}", host, 0) 28 | .to_socket_addrs()? 29 | .next() 30 | .context("") 31 | }; 32 | if res.is_err() { 33 | log::error!("Invalid {} {}: {:?}", name, host, res); 34 | } 35 | res 36 | } 37 | 38 | #[allow(dead_code)] 39 | pub(crate) fn get_servers(s: &str, tag: &str) -> Vec { 40 | let servers: Vec = s 41 | .split(',') 42 | .filter(|x| !x.is_empty() && test_if_valid_server(x, tag).is_ok()) 43 | .map(|x| x.to_owned()) 44 | .collect(); 45 | log::info!("{}={:?}", tag, servers); 46 | servers 47 | } 48 | 49 | #[allow(dead_code)] 50 | #[inline] 51 | fn arg_name(name: &str) -> String { 52 | name.to_uppercase().replace('_', "-") 53 | } 54 | 55 | #[allow(dead_code)] 56 | pub fn init_args(args: &str, name: &str, about: &str) { 57 | let matches = App::new(name) 58 | .version(crate::version::VERSION) 59 | .author("Purslane Ltd. ") 60 | .about(about) 61 | .args_from_usage(args) 62 | .get_matches(); 63 | if let Ok(v) = Ini::load_from_file(".env") { 64 | if let Some(section) = v.section(None::) { 65 | section 66 | .iter() 67 | .for_each(|(k, v)| std::env::set_var(arg_name(k), v)); 68 | } 69 | } 70 | if let Some(config) = matches.value_of("config") { 71 | if let Ok(v) = Ini::load_from_file(config) { 72 | if let Some(section) = v.section(None::) { 73 | section 74 | .iter() 75 | .for_each(|(k, v)| std::env::set_var(arg_name(k), v)); 76 | } 77 | } 78 | } 79 | for (k, v) in matches.args { 80 | if let Some(v) = v.vals.first() { 81 | std::env::set_var(arg_name(k), v.to_string_lossy().to_string()); 82 | } 83 | } 84 | } 85 | 86 | #[allow(dead_code)] 87 | #[inline] 88 | pub fn get_arg(name: &str) -> String { 89 | get_arg_or(name, "".to_owned()) 90 | } 91 | 92 | #[allow(dead_code)] 93 | #[inline] 94 | pub fn get_arg_or(name: &str, default: String) -> String { 95 | std::env::var(arg_name(name)).unwrap_or(default) 96 | } 97 | 98 | #[allow(dead_code)] 99 | #[inline] 100 | pub fn now() -> u64 { 101 | SystemTime::now() 102 | .duration_since(SystemTime::UNIX_EPOCH) 103 | .map(|x| x.as_secs()) 104 | .unwrap_or_default() 105 | } 106 | 107 | pub fn gen_sk(wait: u64) -> (String, Option) { 108 | let sk_file = "id_ed25519"; 109 | if wait > 0 && !std::path::Path::new(sk_file).exists() { 110 | std::thread::sleep(std::time::Duration::from_millis(wait)); 111 | } 112 | if let Ok(mut file) = std::fs::File::open(sk_file) { 113 | let mut contents = String::new(); 114 | if file.read_to_string(&mut contents).is_ok() { 115 | let contents = contents.trim(); 116 | let sk = base64::decode(contents).unwrap_or_default(); 117 | if sk.len() == sign::SECRETKEYBYTES { 118 | let mut tmp = [0u8; sign::SECRETKEYBYTES]; 119 | tmp[..].copy_from_slice(&sk); 120 | let pk = base64::encode(&tmp[sign::SECRETKEYBYTES / 2..]); 121 | log::info!("Private key comes from {}", sk_file); 122 | return (pk, Some(sign::SecretKey(tmp))); 123 | } else { 124 | // don't use log here, since it is async 125 | println!("Fatal error: malformed private key in {sk_file}."); 126 | std::process::exit(1); 127 | } 128 | } 129 | } else { 130 | let gen_func = || { 131 | let (tmp, sk) = sign::gen_keypair(); 132 | (base64::encode(tmp), sk) 133 | }; 134 | let (mut pk, mut sk) = gen_func(); 135 | for _ in 0..300 { 136 | if !pk.contains('/') && !pk.contains(':') { 137 | break; 138 | } 139 | (pk, sk) = gen_func(); 140 | } 141 | let pub_file = format!("{sk_file}.pub"); 142 | if let Ok(mut f) = std::fs::File::create(&pub_file) { 143 | f.write_all(pk.as_bytes()).ok(); 144 | if let Ok(mut f) = std::fs::File::create(sk_file) { 145 | let s = base64::encode(&sk); 146 | if f.write_all(s.as_bytes()).is_ok() { 147 | log::info!("Private/public key written to {}/{}", sk_file, pub_file); 148 | log::debug!("Public key: {}", pk); 149 | return (pk, Some(sk)); 150 | } 151 | } 152 | } 153 | } 154 | ("".to_owned(), None) 155 | } 156 | 157 | #[cfg(unix)] 158 | pub async fn listen_signal() -> Result<()> { 159 | use hbb_common::tokio; 160 | use hbb_common::tokio::signal::unix::{signal, SignalKind}; 161 | 162 | tokio::spawn(async { 163 | let mut s = signal(SignalKind::terminate())?; 164 | let terminate = s.recv(); 165 | let mut s = signal(SignalKind::interrupt())?; 166 | let interrupt = s.recv(); 167 | let mut s = signal(SignalKind::quit())?; 168 | let quit = s.recv(); 169 | 170 | tokio::select! { 171 | _ = terminate => { 172 | log::info!("signal terminate"); 173 | } 174 | _ = interrupt => { 175 | log::info!("signal interrupt"); 176 | } 177 | _ = quit => { 178 | log::info!("signal quit"); 179 | } 180 | } 181 | Ok(()) 182 | }) 183 | .await? 184 | } 185 | 186 | #[cfg(not(unix))] 187 | pub async fn listen_signal() -> Result<()> { 188 | let () = std::future::pending().await; 189 | unreachable!(); 190 | } 191 | 192 | 193 | pub fn check_software_update() { 194 | const ONE_DAY_IN_SECONDS: u64 = 60 * 60 * 24; 195 | std::thread::spawn(move || loop { 196 | std::thread::spawn(move || allow_err!(check_software_update_())); 197 | std::thread::sleep(std::time::Duration::from_secs(ONE_DAY_IN_SECONDS)); 198 | }); 199 | } 200 | 201 | #[tokio::main(flavor = "current_thread")] 202 | async fn check_software_update_() -> hbb_common::ResultType<()> { 203 | let (request, url) = hbb_common::version_check_request(hbb_common::VER_TYPE_RUSTDESK_SERVER.to_string()); 204 | let latest_release_response = reqwest::Client::builder().build()? 205 | .post(url) 206 | .json(&request) 207 | .send() 208 | .await?; 209 | 210 | let bytes = latest_release_response.bytes().await?; 211 | let resp: hbb_common::VersionCheckResponse = serde_json::from_slice(&bytes)?; 212 | let response_url = resp.url; 213 | let latest_release_version = response_url.rsplit('/').next().unwrap_or_default(); 214 | if get_version_number(&latest_release_version) > get_version_number(crate::version::VERSION) { 215 | log::info!("new version is available: {}", latest_release_version); 216 | } 217 | Ok(()) 218 | } -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use hbb_common::{log, ResultType}; 3 | use sqlx::{ 4 | sqlite::SqliteConnectOptions, ConnectOptions, Connection, Error as SqlxError, SqliteConnection, 5 | }; 6 | use std::{ops::DerefMut, str::FromStr}; 7 | //use sqlx::postgres::PgPoolOptions; 8 | //use sqlx::mysql::MySqlPoolOptions; 9 | 10 | type Pool = deadpool::managed::Pool; 11 | 12 | pub struct DbPool { 13 | url: String, 14 | } 15 | 16 | #[async_trait] 17 | impl deadpool::managed::Manager for DbPool { 18 | type Type = SqliteConnection; 19 | type Error = SqlxError; 20 | async fn create(&self) -> Result { 21 | let mut opt = SqliteConnectOptions::from_str(&self.url).unwrap(); 22 | opt.log_statements(log::LevelFilter::Debug); 23 | SqliteConnection::connect_with(&opt).await 24 | } 25 | async fn recycle( 26 | &self, 27 | obj: &mut SqliteConnection, 28 | ) -> deadpool::managed::RecycleResult { 29 | Ok(obj.ping().await?) 30 | } 31 | } 32 | 33 | #[derive(Clone)] 34 | pub struct Database { 35 | pool: Pool, 36 | } 37 | 38 | #[derive(Default)] 39 | pub struct Peer { 40 | pub guid: Vec, 41 | pub id: String, 42 | pub uuid: Vec, 43 | pub pk: Vec, 44 | pub user: Option>, 45 | pub info: String, 46 | pub status: Option, 47 | } 48 | 49 | impl Database { 50 | pub async fn new(url: &str) -> ResultType { 51 | if !std::path::Path::new(url).exists() { 52 | std::fs::File::create(url).ok(); 53 | } 54 | let n: usize = std::env::var("MAX_DATABASE_CONNECTIONS") 55 | .unwrap_or_else(|_| "1".to_owned()) 56 | .parse() 57 | .unwrap_or(1); 58 | log::debug!("MAX_DATABASE_CONNECTIONS={}", n); 59 | let pool = Pool::new( 60 | DbPool { 61 | url: url.to_owned(), 62 | }, 63 | n, 64 | ); 65 | let _ = pool.get().await?; // test 66 | let db = Database { pool }; 67 | db.create_tables().await?; 68 | Ok(db) 69 | } 70 | 71 | async fn create_tables(&self) -> ResultType<()> { 72 | sqlx::query!( 73 | " 74 | create table if not exists peer ( 75 | guid blob primary key not null, 76 | id varchar(100) not null, 77 | uuid blob not null, 78 | pk blob not null, 79 | created_at datetime not null default(current_timestamp), 80 | user blob, 81 | status tinyint, 82 | note varchar(300), 83 | info text not null 84 | ) without rowid; 85 | create unique index if not exists index_peer_id on peer (id); 86 | create index if not exists index_peer_user on peer (user); 87 | create index if not exists index_peer_created_at on peer (created_at); 88 | create index if not exists index_peer_status on peer (status); 89 | " 90 | ) 91 | .execute(self.pool.get().await?.deref_mut()) 92 | .await?; 93 | Ok(()) 94 | } 95 | 96 | pub async fn get_peer(&self, id: &str) -> ResultType> { 97 | Ok(sqlx::query_as!( 98 | Peer, 99 | "select guid, id, uuid, pk, user, status, info from peer where id = ?", 100 | id 101 | ) 102 | .fetch_optional(self.pool.get().await?.deref_mut()) 103 | .await?) 104 | } 105 | 106 | pub async fn insert_peer( 107 | &self, 108 | id: &str, 109 | uuid: &[u8], 110 | pk: &[u8], 111 | info: &str, 112 | ) -> ResultType> { 113 | let guid = uuid::Uuid::new_v4().as_bytes().to_vec(); 114 | sqlx::query!( 115 | "insert into peer(guid, id, uuid, pk, info) values(?, ?, ?, ?, ?)", 116 | guid, 117 | id, 118 | uuid, 119 | pk, 120 | info 121 | ) 122 | .execute(self.pool.get().await?.deref_mut()) 123 | .await?; 124 | Ok(guid) 125 | } 126 | 127 | pub async fn update_pk( 128 | &self, 129 | guid: &Vec, 130 | id: &str, 131 | pk: &[u8], 132 | info: &str, 133 | ) -> ResultType<()> { 134 | sqlx::query!( 135 | "update peer set id=?, pk=?, info=? where guid=?", 136 | id, 137 | pk, 138 | info, 139 | guid 140 | ) 141 | .execute(self.pool.get().await?.deref_mut()) 142 | .await?; 143 | Ok(()) 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use hbb_common::tokio; 150 | #[test] 151 | fn test_insert() { 152 | insert(); 153 | } 154 | 155 | #[tokio::main(flavor = "multi_thread")] 156 | async fn insert() { 157 | let db = super::Database::new("test.sqlite3").await.unwrap(); 158 | let mut jobs = vec![]; 159 | for i in 0..10000 { 160 | let cloned = db.clone(); 161 | let id = i.to_string(); 162 | let a = tokio::spawn(async move { 163 | let empty_vec = Vec::new(); 164 | cloned 165 | .insert_peer(&id, &empty_vec, &empty_vec, "") 166 | .await 167 | .unwrap(); 168 | }); 169 | jobs.push(a); 170 | } 171 | for i in 0..10000 { 172 | let cloned = db.clone(); 173 | let id = i.to_string(); 174 | let a = tokio::spawn(async move { 175 | cloned.get_peer(&id).await.unwrap(); 176 | }); 177 | jobs.push(a); 178 | } 179 | hbb_common::futures::future::join_all(jobs).await; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/hbbr.rs: -------------------------------------------------------------------------------- 1 | use clap::App; 2 | mod common; 3 | mod relay_server; 4 | use flexi_logger::*; 5 | use hbb_common::{config::RELAY_PORT, ResultType}; 6 | use relay_server::*; 7 | mod version; 8 | 9 | fn main() -> ResultType<()> { 10 | let _logger = Logger::try_with_env_or_str("info")? 11 | .log_to_stdout() 12 | .format(opt_format) 13 | .write_mode(WriteMode::Async) 14 | .start()?; 15 | let args = format!( 16 | "-p, --port=[NUMBER(default={RELAY_PORT})] 'Sets the listening port' 17 | -k, --key=[KEY] 'Only allow the client with the same key' 18 | ", 19 | ); 20 | let matches = App::new("hbbr") 21 | .version(version::VERSION) 22 | .author("Purslane Ltd. ") 23 | .about("RustDesk Relay Server") 24 | .args_from_usage(&args) 25 | .get_matches(); 26 | if let Ok(v) = ini::Ini::load_from_file(".env") { 27 | if let Some(section) = v.section(None::) { 28 | section.iter().for_each(|(k, v)| std::env::set_var(k, v)); 29 | } 30 | } 31 | let mut port = RELAY_PORT; 32 | if let Ok(v) = std::env::var("PORT") { 33 | let v: i32 = v.parse().unwrap_or_default(); 34 | if v > 0 { 35 | port = v + 1; 36 | } 37 | } 38 | start( 39 | matches.value_of("port").unwrap_or(&port.to_string()), 40 | matches 41 | .value_of("key") 42 | .unwrap_or(&std::env::var("KEY").unwrap_or_default()), 43 | )?; 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/jwt.rs: -------------------------------------------------------------------------------- 1 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; 2 | use once_cell::sync::Lazy; 3 | use serde::{Deserialize, Serialize}; 4 | use std::env; 5 | pub static SECRET: Lazy = 6 | Lazy::new(|| env::var("RUSTDESK_API_JWT_KEY").unwrap_or_else(|_| "".to_string())); 7 | 8 | // 定义一个结构体来表示 JWT 的 payload 9 | #[derive(Debug, Serialize, Deserialize)] 10 | pub struct Claims { 11 | user_id: u32, 12 | exp: usize, 13 | } 14 | 15 | pub fn generate_token(user_id: u32, exp: i64) -> Result { 16 | println!("secret: {:}", SECRET.to_string()); 17 | let claims = Claims { 18 | user_id, 19 | exp: (chrono::Utc::now() + chrono::Duration::seconds(exp)).timestamp() as usize, 20 | }; 21 | 22 | let token = encode( 23 | &Header::default(), 24 | &claims, 25 | &EncodingKey::from_secret(SECRET.as_ref()), 26 | ); 27 | 28 | match token { 29 | Ok(t) => Ok(t), 30 | Err(e) => Err(e.to_string()), 31 | } 32 | } 33 | // 验证 JWT 的函数 34 | pub fn verify_token(token: &str) -> Result { 35 | // 解码 JWT 36 | let validation = Validation::new(Algorithm::HS256); 37 | 38 | let decoded = decode::( 39 | &token, 40 | &DecodingKey::from_secret(SECRET.as_ref()), 41 | &validation, 42 | ); 43 | match decoded { 44 | Ok(token_data) => { 45 | let now = chrono::Utc::now().timestamp() as usize; 46 | if token_data.claims.exp > now { 47 | Ok(token_data.claims) 48 | } else { 49 | Err("Token status invalid or expired".to_string()) 50 | } 51 | } 52 | Err(_) => Err("Invalid token".to_string()), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod rendezvous_server; 2 | pub use rendezvous_server::*; 3 | pub mod common; 4 | mod database; 5 | mod peer; 6 | mod version; 7 | pub mod jwt; -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // https://tools.ietf.org/rfc/rfc5128.txt 2 | // https://blog.csdn.net/bytxl/article/details/44344855 3 | 4 | use flexi_logger::*; 5 | use hbb_common::{bail, config::RENDEZVOUS_PORT, ResultType}; 6 | use hbbs::{common::*, *}; 7 | 8 | const RMEM: usize = 0; 9 | 10 | fn main() -> ResultType<()> { 11 | let _logger = Logger::try_with_env_or_str("info")? 12 | .log_to_stdout() 13 | .format(opt_format) 14 | .write_mode(WriteMode::Async) 15 | .start()?; 16 | let args = format!( 17 | "-c --config=[FILE] +takes_value 'Sets a custom config file' 18 | -p, --port=[NUMBER(default={RENDEZVOUS_PORT})] 'Sets the listening port' 19 | -s, --serial=[NUMBER(default=0)] 'Sets configure update serial number' 20 | -R, --rendezvous-servers=[HOSTS] 'Sets rendezvous servers, separated by comma' 21 | -u, --software-url=[URL] 'Sets download url of RustDesk software of newest version' 22 | -r, --relay-servers=[HOST] 'Sets the default relay servers, separated by comma' 23 | -M, --rmem=[NUMBER(default={RMEM})] 'Sets UDP recv buffer size, set system rmem_max first, e.g., sudo sysctl -w net.core.rmem_max=52428800. vi /etc/sysctl.conf, net.core.rmem_max=52428800, sudo sysctl –p' 24 | , --mask=[MASK] 'Determine if the connection comes from LAN, e.g. 192.168.0.0/16' 25 | -k, --key=[KEY] 'Only allow the client with the same key' 26 | , --must-login=[Y|N] 'Only allow the client with login'", 27 | ); 28 | init_args(&args, "hbbs", "RustDesk ID/Rendezvous Server"); 29 | let port = get_arg_or("port", RENDEZVOUS_PORT.to_string()).parse::()?; 30 | if port < 3 { 31 | bail!("Invalid port"); 32 | } 33 | let rmem = get_arg("rmem").parse::().unwrap_or(RMEM); 34 | let serial: i32 = get_arg("serial").parse().unwrap_or(0); 35 | crate::common::check_software_update(); 36 | RendezvousServer::start(port, serial, &get_arg_or("key", "-".to_owned()), rmem)?; 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /src/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod relay_server; 2 | pub mod rendezvous_server; 3 | mod sled_async; 4 | use sled_async::*; 5 | -------------------------------------------------------------------------------- /src/peer.rs: -------------------------------------------------------------------------------- 1 | use crate::common::*; 2 | use crate::database; 3 | use hbb_common::{ 4 | bytes::Bytes, 5 | log, 6 | rendezvous_proto::*, 7 | tokio::sync::{Mutex, RwLock}, 8 | ResultType, 9 | }; 10 | use serde_derive::{Deserialize, Serialize}; 11 | use std::{collections::HashMap, collections::HashSet, net::SocketAddr, sync::Arc, time::Instant}; 12 | 13 | type IpBlockMap = HashMap, Instant))>; 14 | type UserStatusMap = HashMap, Arc<(Option>, bool)>>; 15 | type IpChangesMap = HashMap)>; 16 | lazy_static::lazy_static! { 17 | pub(crate) static ref IP_BLOCKER: Mutex = Default::default(); 18 | pub(crate) static ref USER_STATUS: RwLock = Default::default(); 19 | pub(crate) static ref IP_CHANGES: Mutex = Default::default(); 20 | } 21 | pub const IP_CHANGE_DUR: u64 = 180; 22 | pub const IP_CHANGE_DUR_X2: u64 = IP_CHANGE_DUR * 2; 23 | pub const DAY_SECONDS: u64 = 3600 * 24; 24 | pub const IP_BLOCK_DUR: u64 = 60; 25 | 26 | #[derive(Debug, Default, Serialize, Deserialize, Clone)] 27 | pub(crate) struct PeerInfo { 28 | #[serde(default)] 29 | pub(crate) ip: String, 30 | } 31 | 32 | pub(crate) struct Peer { 33 | pub(crate) socket_addr: SocketAddr, 34 | pub(crate) last_reg_time: Instant, 35 | pub(crate) guid: Vec, 36 | pub(crate) uuid: Bytes, 37 | pub(crate) pk: Bytes, 38 | // pub(crate) user: Option>, 39 | pub(crate) info: PeerInfo, 40 | // pub(crate) disabled: bool, 41 | pub(crate) reg_pk: (u32, Instant), // how often register_pk 42 | } 43 | 44 | impl Default for Peer { 45 | fn default() -> Self { 46 | Self { 47 | socket_addr: "0.0.0.0:0".parse().unwrap(), 48 | last_reg_time: get_expired_time(), 49 | guid: Vec::new(), 50 | uuid: Bytes::new(), 51 | pk: Bytes::new(), 52 | info: Default::default(), 53 | // user: None, 54 | // disabled: false, 55 | reg_pk: (0, get_expired_time()), 56 | } 57 | } 58 | } 59 | 60 | pub(crate) type LockPeer = Arc>; 61 | 62 | #[derive(Clone)] 63 | pub(crate) struct PeerMap { 64 | map: Arc>>, 65 | pub(crate) db: database::Database, 66 | } 67 | 68 | impl PeerMap { 69 | pub(crate) async fn new() -> ResultType { 70 | let db = std::env::var("DB_URL").unwrap_or({ 71 | let mut db = "db_v2.sqlite3".to_owned(); 72 | #[cfg(all(windows, not(debug_assertions)))] 73 | { 74 | if let Some(path) = hbb_common::config::Config::icon_path().parent() { 75 | db = format!("{}\\{}", path.to_str().unwrap_or("."), db); 76 | } 77 | } 78 | #[cfg(not(windows))] 79 | { 80 | db = format!("./{db}"); 81 | } 82 | db 83 | }); 84 | log::info!("DB_URL={}", db); 85 | let pm = Self { 86 | map: Default::default(), 87 | db: database::Database::new(&db).await?, 88 | }; 89 | Ok(pm) 90 | } 91 | 92 | #[inline] 93 | pub(crate) async fn update_pk( 94 | &mut self, 95 | id: String, 96 | peer: LockPeer, 97 | addr: SocketAddr, 98 | uuid: Bytes, 99 | pk: Bytes, 100 | ip: String, 101 | ) -> register_pk_response::Result { 102 | log::info!("update_pk {} {:?} {:?} {:?}", id, addr, uuid, pk); 103 | let (info_str, guid) = { 104 | let mut w = peer.write().await; 105 | w.socket_addr = addr; 106 | w.uuid = uuid.clone(); 107 | w.pk = pk.clone(); 108 | w.last_reg_time = Instant::now(); 109 | w.info.ip = ip; 110 | ( 111 | serde_json::to_string(&w.info).unwrap_or_default(), 112 | w.guid.clone(), 113 | ) 114 | }; 115 | if guid.is_empty() { 116 | match self.db.insert_peer(&id, &uuid, &pk, &info_str).await { 117 | Err(err) => { 118 | log::error!("db.insert_peer failed: {}", err); 119 | return register_pk_response::Result::SERVER_ERROR; 120 | } 121 | Ok(guid) => { 122 | peer.write().await.guid = guid; 123 | } 124 | } 125 | } else { 126 | if let Err(err) = self.db.update_pk(&guid, &id, &pk, &info_str).await { 127 | log::error!("db.update_pk failed: {}", err); 128 | return register_pk_response::Result::SERVER_ERROR; 129 | } 130 | log::info!("pk updated instead of insert"); 131 | } 132 | register_pk_response::Result::OK 133 | } 134 | 135 | #[inline] 136 | pub(crate) async fn get(&self, id: &str) -> Option { 137 | let p = self.map.read().await.get(id).cloned(); 138 | if p.is_some() { 139 | return p; 140 | } else if let Ok(Some(v)) = self.db.get_peer(id).await { 141 | let peer = Peer { 142 | guid: v.guid, 143 | uuid: v.uuid.into(), 144 | pk: v.pk.into(), 145 | // user: v.user, 146 | info: serde_json::from_str::(&v.info).unwrap_or_default(), 147 | // disabled: v.status == Some(0), 148 | ..Default::default() 149 | }; 150 | let peer = Arc::new(RwLock::new(peer)); 151 | self.map.write().await.insert(id.to_owned(), peer.clone()); 152 | return Some(peer); 153 | } 154 | None 155 | } 156 | 157 | #[inline] 158 | pub(crate) async fn get_or(&self, id: &str) -> LockPeer { 159 | if let Some(p) = self.get(id).await { 160 | return p; 161 | } 162 | let mut w = self.map.write().await; 163 | if let Some(p) = w.get(id) { 164 | return p.clone(); 165 | } 166 | let tmp = LockPeer::default(); 167 | w.insert(id.to_owned(), tmp.clone()); 168 | tmp 169 | } 170 | 171 | #[inline] 172 | pub(crate) async fn get_in_memory(&self, id: &str) -> Option { 173 | self.map.read().await.get(id).cloned() 174 | } 175 | 176 | #[inline] 177 | pub(crate) async fn is_in_memory(&self, id: &str) -> bool { 178 | self.map.read().await.contains_key(id) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use dns_lookup::{lookup_addr, lookup_host}; 2 | use hbb_common::{bail, ResultType}; 3 | use sodiumoxide::crypto::sign; 4 | use std::{ 5 | env, 6 | net::{IpAddr, TcpStream}, 7 | process, str, 8 | }; 9 | 10 | fn print_help() { 11 | println!( 12 | "Usage: 13 | rustdesk-util [command]\n 14 | Available Commands: 15 | genkeypair Generate a new keypair 16 | validatekeypair [public key] [secret key] Validate an existing keypair 17 | doctor [rustdesk-server] Check for server connection problems" 18 | ); 19 | process::exit(0x0001); 20 | } 21 | 22 | fn error_then_help(msg: &str) { 23 | println!("ERROR: {msg}\n"); 24 | print_help(); 25 | } 26 | 27 | fn gen_keypair() { 28 | let (pk, sk) = sign::gen_keypair(); 29 | let public_key = base64::encode(pk); 30 | let secret_key = base64::encode(sk); 31 | println!("Public Key: {public_key}"); 32 | println!("Secret Key: {secret_key}"); 33 | } 34 | 35 | fn validate_keypair(pk: &str, sk: &str) -> ResultType<()> { 36 | let sk1 = base64::decode(sk); 37 | if sk1.is_err() { 38 | bail!("Invalid secret key"); 39 | } 40 | let sk1 = sk1.unwrap(); 41 | 42 | let secret_key = sign::SecretKey::from_slice(sk1.as_slice()); 43 | if secret_key.is_none() { 44 | bail!("Invalid Secret key"); 45 | } 46 | let secret_key = secret_key.unwrap(); 47 | 48 | let pk1 = base64::decode(pk); 49 | if pk1.is_err() { 50 | bail!("Invalid public key"); 51 | } 52 | let pk1 = pk1.unwrap(); 53 | 54 | let public_key = sign::PublicKey::from_slice(pk1.as_slice()); 55 | if public_key.is_none() { 56 | bail!("Invalid Public key"); 57 | } 58 | let public_key = public_key.unwrap(); 59 | 60 | let random_data_to_test = b"This is meh."; 61 | let signed_data = sign::sign(random_data_to_test, &secret_key); 62 | let verified_data = sign::verify(&signed_data, &public_key); 63 | if verified_data.is_err() { 64 | bail!("Key pair is INVALID"); 65 | } 66 | let verified_data = verified_data.unwrap(); 67 | 68 | if random_data_to_test != &verified_data[..] { 69 | bail!("Key pair is INVALID"); 70 | } 71 | 72 | Ok(()) 73 | } 74 | 75 | fn doctor_tcp(address: std::net::IpAddr, port: &str, desc: &str) { 76 | let start = std::time::Instant::now(); 77 | let conn = format!("{address}:{port}"); 78 | if let Ok(_stream) = TcpStream::connect(conn.as_str()) { 79 | let elapsed = std::time::Instant::now().duration_since(start); 80 | println!( 81 | "TCP Port {} ({}): OK in {} ms", 82 | port, 83 | desc, 84 | elapsed.as_millis() 85 | ); 86 | } else { 87 | println!("TCP Port {port} ({desc}): ERROR"); 88 | } 89 | } 90 | 91 | fn doctor_ip(server_ip_address: std::net::IpAddr, server_address: Option<&str>) { 92 | println!("\nChecking IP address: {server_ip_address}"); 93 | println!("Is IPV4: {}", server_ip_address.is_ipv4()); 94 | println!("Is IPV6: {}", server_ip_address.is_ipv6()); 95 | 96 | // reverse dns lookup 97 | // TODO: (check) doesn't seem to do reverse lookup on OSX... 98 | let reverse = lookup_addr(&server_ip_address).unwrap(); 99 | if let Some(server_address) = server_address { 100 | if reverse == server_address { 101 | println!("Reverse DNS lookup: '{reverse}' MATCHES server address"); 102 | } else { 103 | println!( 104 | "Reverse DNS lookup: '{reverse}' DOESN'T MATCH server address '{server_address}'" 105 | ); 106 | } 107 | } 108 | 109 | // TODO: ICMP ping? 110 | 111 | // port check TCP (UDP is hard to check) 112 | doctor_tcp(server_ip_address, "21114", "API"); 113 | doctor_tcp(server_ip_address, "21115", "hbbs extra port for nat test"); 114 | doctor_tcp(server_ip_address, "21116", "hbbs"); 115 | doctor_tcp(server_ip_address, "21117", "hbbr tcp"); 116 | doctor_tcp(server_ip_address, "21118", "hbbs websocket"); 117 | doctor_tcp(server_ip_address, "21119", "hbbr websocket"); 118 | 119 | // TODO: key check 120 | } 121 | 122 | fn doctor(server_address_unclean: &str) { 123 | let server_address3 = server_address_unclean.trim(); 124 | let server_address2 = server_address3.to_lowercase(); 125 | let server_address = server_address2.as_str(); 126 | println!("Checking server: {server_address}\n"); 127 | if let Ok(server_ipaddr) = server_address.parse::() { 128 | // user requested an ip address 129 | doctor_ip(server_ipaddr, None); 130 | } else { 131 | // the passed string is not an ip address 132 | let ips: Vec = lookup_host(server_address).unwrap(); 133 | println!("Found {} IP addresses: ", ips.len()); 134 | 135 | ips.iter().for_each(|ip| println!(" - {ip}")); 136 | 137 | ips.iter() 138 | .for_each(|ip| doctor_ip(*ip, Some(server_address))); 139 | } 140 | } 141 | 142 | fn main() { 143 | let args: Vec<_> = env::args().collect(); 144 | if args.len() <= 1 { 145 | print_help(); 146 | } 147 | 148 | let command = args[1].to_lowercase(); 149 | match command.as_str() { 150 | "genkeypair" => gen_keypair(), 151 | "validatekeypair" => { 152 | if args.len() <= 3 { 153 | error_then_help("You must supply both the public and the secret key"); 154 | } 155 | let res = validate_keypair(args[2].as_str(), args[3].as_str()); 156 | if let Err(e) = res { 157 | println!("{e}"); 158 | process::exit(0x0001); 159 | } 160 | println!("Key pair is VALID"); 161 | } 162 | "doctor" => { 163 | if args.len() <= 2 { 164 | error_then_help("You must supply the rustdesk-server address"); 165 | } 166 | doctor(args[2].as_str()); 167 | } 168 | _ => print_help(), 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /systemd/rustdesk-hbbr.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=Rustdesk Relay Server 4 | 5 | [Service] 6 | Type=simple 7 | LimitNOFILE=1000000 8 | ExecStart=/usr/bin/hbbr 9 | WorkingDirectory=/var/lib/rustdesk-server/ 10 | User= 11 | Group= 12 | Restart=always 13 | StandardOutput=append:/var/log/rustdesk-server/hbbr.log 14 | StandardError=append:/var/log/rustdesk-server/hbbr.error 15 | # Restart service after 10 seconds if node service crashes 16 | RestartSec=10 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | 21 | -------------------------------------------------------------------------------- /systemd/rustdesk-hbbs.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=Rustdesk Signal Server 4 | 5 | [Service] 6 | Type=simple 7 | LimitNOFILE=1000000 8 | ExecStart=/usr/bin/hbbs 9 | WorkingDirectory=/var/lib/rustdesk-server/ 10 | User= 11 | Group= 12 | Restart=always 13 | StandardOutput=append:/var/log/rustdesk-server/hbbs.log 14 | StandardError=append:/var/log/rustdesk-server/hbbs.error 15 | # Restart service after 10 seconds if node service crashes 16 | RestartSec=10 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | 21 | -------------------------------------------------------------------------------- /tests/jwt_tests.rs: -------------------------------------------------------------------------------- 1 | use hbb_common::tokio; 2 | use hbbs::jwt; 3 | 4 | #[test] 5 | fn test_generate_token() { 6 | std::env::set_var("RUSTDESK_API_JWT_KEY", "testjwt"); 7 | let token = jwt::generate_token(1, 3600).unwrap(); 8 | println!("Generated Token: {}", token); 9 | assert!(!token.is_empty(), "Generated token should not be empty"); 10 | } 11 | 12 | #[tokio::test] 13 | async fn test_verify_token() { 14 | std::env::set_var("RUSTDESK_API_JWT_KEY", "testjwt"); 15 | let token = jwt::generate_token(1, 2).unwrap(); 16 | // let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE3MzY4NzA4NjF9.u5pxmwNMrYUwtkspF1FuZj-R5ANAR9WT9_dMHuQhV0Y"; 17 | println!("Token : {:?}, now: {:?}", token, chrono::Utc::now().timestamp()); 18 | 19 | // hbb_common::sleep(3f32).await; 20 | // println!("Token : {:?}, now: {:?}", token, chrono::Utc::now().timestamp()); 21 | let result = jwt::verify_token(&token); 22 | println!("Token Verification Result: {:?}", result); 23 | assert!(result.is_ok(), "Token should be valid"); 24 | } -------------------------------------------------------------------------------- /ui/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-Ctarget-feature=+crt-static"] 3 | [target.i686-pc-windows-msvc] 4 | rustflags = ["-Ctarget-feature=+crt-static"] 5 | [target.'cfg(target_os="macos")'] 6 | rustflags = [ 7 | "-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null", 8 | ] -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | -------------------------------------------------------------------------------- /ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustdesk_server" 3 | version = "0.1.2" 4 | description = "rustdesk server gui" 5 | authors = ["elilchen"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [build-dependencies] 11 | tauri-build = { version = "1.2", features = [] } 12 | winres = "0.1" 13 | 14 | [dependencies] 15 | async-std = { version = "1.12", features = ["attributes", "unstable"] } 16 | crossbeam-channel = "0.5" 17 | derive-new = "0.5" 18 | notify = "5.1" 19 | once_cell = "1.17" 20 | serde_json = "1.0" 21 | serde = { version = "1.0", features = ["derive"] } 22 | tauri = { version = "1.2", features = ["fs-exists", "fs-read-dir", "fs-read-file", "fs-write-file", "path-all", "shell-open", "system-tray"] } 23 | windows-service = "0.5.0" 24 | 25 | [features] 26 | # by default Tauri runs in production mode 27 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 28 | default = ["custom-protocol"] 29 | # this feature is used used for production builds where `devPath` points to the filesystem 30 | # DO NOT remove this 31 | custom-protocol = ["tauri/custom-protocol"] 32 | -------------------------------------------------------------------------------- /ui/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build(); 3 | if cfg!(target_os = "windows") { 4 | let mut res = winres::WindowsResource::new(); 5 | res.set_icon("icons\\icon.ico"); 6 | res.set_manifest( 7 | r#" 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | "#, 18 | ); 19 | res.compile().unwrap(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ui/html/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /ui/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RustDesk Server 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /ui/html/main.js: -------------------------------------------------------------------------------- 1 | import 'codemirror/lib/codemirror.css'; 2 | import './style.css'; 3 | import 'codemirror/mode/toml/toml.js'; 4 | import CodeMirror from 'codemirror'; 5 | 6 | const { event, fs, path, tauri } = window.__TAURI__; 7 | 8 | class View { 9 | constructor() { 10 | Object.assign(this, { 11 | content: '', 12 | action_time: 0, 13 | is_auto_scroll: true, 14 | is_edit_mode: false, 15 | is_file_changed: false, 16 | is_form_changed: false, 17 | is_content_changed: false 18 | }, ...arguments); 19 | addEventListener('DOMContentLoaded', this.init.bind(this)); 20 | } 21 | async init() { 22 | this.editor = this.renderEditor(); 23 | this.editor.on('scroll', this.editorScroll.bind(this)); 24 | this.editor.on('keypress', this.editorSave.bind(this)); 25 | this.form = this.renderForm(); 26 | this.form.addEventListener('change', this.formChange.bind(this)); 27 | event.listen('__update__', this.appAction.bind(this)); 28 | event.emit('__action__', '__init__'); 29 | while (true) { 30 | let now = Date.now(); 31 | try { 32 | await this.update(); 33 | this.render(); 34 | } catch (e) { 35 | console.error(e); 36 | } 37 | await new Promise(r => setTimeout(r, Math.max(0, 33 - (Date.now() - now)))); 38 | } 39 | } 40 | async update() { 41 | if (this.is_file_changed) { 42 | this.is_file_changed = false; 43 | let now = Date.now(), 44 | file = await path.resolveResource(this.file); 45 | if (await fs.exists(file)) { 46 | let content = await fs.readTextFile(file); 47 | if (this.action_time < now) { 48 | this.content = content; 49 | this.is_content_changed = true; 50 | } 51 | } else { 52 | if (now >= this.action_time) { 53 | if (this.is_edit_mode) { 54 | this.content = `# https://github.com/rustdesk/rustdesk-server#env-variables 55 | RUST_LOG=info 56 | `; 57 | } 58 | this.is_content_changed = true; 59 | } 60 | console.warn(`${this.file} file is missing`); 61 | } 62 | } 63 | } 64 | async editorSave(editor, e) { 65 | if (e.ctrlKey && e.keyCode === 19 && this.is_edit_mode && !this.locked) { 66 | this.locked = true; 67 | try { 68 | let now = Date.now(), 69 | content = this.editor.doc.getValue(), 70 | file = await path.resolveResource(this.file); 71 | await fs.writeTextFile(file, content); 72 | event.emit('__action__', 'restart'); 73 | } catch (e) { 74 | console.error(e); 75 | } finally { 76 | this.locked = false; 77 | } 78 | } 79 | } 80 | editorScroll(e) { 81 | let info = this.editor.getScrollInfo(), 82 | distance = info.height - info.top - info.clientHeight, 83 | is_end = distance < 1; 84 | if (this.is_auto_scroll !== is_end) { 85 | this.is_auto_scroll = is_end; 86 | this.is_form_changed = true; 87 | } 88 | } 89 | formChange(e) { 90 | switch (e.target.tagName.toLowerCase()) { 91 | case 'input': 92 | this.is_auto_scroll = e.target.checked; 93 | break; 94 | } 95 | } 96 | appAction(e) { 97 | let [action, data] = e.payload; 98 | switch (action) { 99 | case 'file': 100 | if (data === '.env') { 101 | this.is_edit_mode = true; 102 | this.file = `bin/${data}`; 103 | } else { 104 | this.is_edit_mode = false; 105 | this.file = `logs/${data}`; 106 | } 107 | this.action_time = Date.now(); 108 | this.is_file_changed = true; 109 | this.is_form_changed = true; 110 | break; 111 | } 112 | } 113 | render() { 114 | if (this.is_form_changed) { 115 | this.is_form_changed = false; 116 | this.renderForm(); 117 | } 118 | if (this.is_content_changed) { 119 | this.is_content_changed = false; 120 | this.renderEditor(); 121 | } 122 | if (this.is_auto_scroll && !this.is_edit_mode) { 123 | this.renderScrollbar(); 124 | } 125 | } 126 | renderForm() { 127 | let form = this.form || document.querySelector('form'), 128 | label = form.querySelectorAll('label'), 129 | input = form.querySelector('input'); 130 | input.checked = this.is_auto_scroll; 131 | if (this.is_edit_mode) { 132 | label[0].style.display = 'none'; 133 | label[1].style.display = 'block'; 134 | } else { 135 | label[0].style.display = 'block'; 136 | label[1].style.display = 'none'; 137 | } 138 | return form; 139 | } 140 | renderEditor() { 141 | let editor = this.editor || CodeMirror.fromTextArea(document.querySelector('textarea'), { 142 | mode: { name: 'toml' }, 143 | lineNumbers: true, 144 | autofocus: true 145 | }); 146 | editor.setOption('readOnly', !this.is_edit_mode); 147 | editor.doc.setValue(this.content); 148 | editor.doc.clearHistory(); 149 | this.content = ''; 150 | editor.focus(); 151 | return editor; 152 | } 153 | renderScrollbar() { 154 | let info = this.editor.getScrollInfo(); 155 | this.editor.scrollTo(info.left, info.height); 156 | } 157 | } 158 | 159 | new View(); -------------------------------------------------------------------------------- /ui/html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rustdesk_server", 3 | "private": true, 4 | "version": "0.1.2", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^4.1.0" 13 | }, 14 | "dependencies": { 15 | "codemirror": "v5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/html/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | visibility: visible !important; 3 | margin: 0; 4 | background: #fff; 5 | } 6 | 7 | .CodeMirror { 8 | height: calc(100vh - 20px); 9 | } 10 | 11 | form { 12 | height: 20px; 13 | position: fixed; 14 | right: 0; 15 | bottom: 0; 16 | left: 5px; 17 | font-size: 13px; 18 | background: #fff; 19 | } 20 | 21 | form>label { 22 | display: none; 23 | vertical-align: middle; 24 | } 25 | 26 | form>label>input, 27 | form>label>p { 28 | height: 19px; 29 | padding: 0; 30 | display: inline-block; 31 | margin: 0; 32 | vertical-align: middle; 33 | cursor: pointer; 34 | user-select: none; 35 | } -------------------------------------------------------------------------------- /ui/html/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | server: { 5 | port: '5177', 6 | strictPort: true 7 | } 8 | }); -------------------------------------------------------------------------------- /ui/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/128x128.png -------------------------------------------------------------------------------- /ui/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/128x128@2x.png -------------------------------------------------------------------------------- /ui/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/32x32.png -------------------------------------------------------------------------------- /ui/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /ui/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /ui/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /ui/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /ui/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /ui/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /ui/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /ui/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /ui/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /ui/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/StoreLogo.png -------------------------------------------------------------------------------- /ui/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/icon.icns -------------------------------------------------------------------------------- /ui/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/icon.ico -------------------------------------------------------------------------------- /ui/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/icons/icon.png -------------------------------------------------------------------------------- /ui/setup.nsi: -------------------------------------------------------------------------------- 1 | Unicode true 2 | 3 | #################################################################### 4 | # Includes 5 | 6 | !include nsDialogs.nsh 7 | !include MUI2.nsh 8 | !include x64.nsh 9 | !include LogicLib.nsh 10 | 11 | #################################################################### 12 | # File Info 13 | 14 | !define APP_NAME "RustDeskServer" 15 | !define PRODUCT_NAME "rustdesk_server" 16 | !define PRODUCT_DESCRIPTION "Installer for ${PRODUCT_NAME}" 17 | !define COPYRIGHT "Copyright © 2021" 18 | !define VERSION "1.1.14" 19 | 20 | VIProductVersion "${VERSION}.0" 21 | VIAddVersionKey "ProductName" "${PRODUCT_NAME}" 22 | VIAddVersionKey "ProductVersion" "${VERSION}" 23 | VIAddVersionKey "FileDescription" "${PRODUCT_DESCRIPTION}" 24 | VIAddVersionKey "LegalCopyright" "${COPYRIGHT}" 25 | VIAddVersionKey "FileVersion" "${VERSION}" 26 | 27 | #################################################################### 28 | # Installer Attributes 29 | 30 | Name "${APP_NAME}" 31 | Outfile "${APP_NAME}.Setup.exe" 32 | Caption "Setup - ${APP_NAME}" 33 | BrandingText "${APP_NAME}" 34 | 35 | ShowInstDetails show 36 | RequestExecutionLevel admin 37 | SetOverwrite on 38 | 39 | InstallDir "$PROGRAMFILES64\${APP_NAME}" 40 | 41 | #################################################################### 42 | # Pages 43 | 44 | !define MUI_ICON "icons\icon.ico" 45 | !define MUI_ABORTWARNING 46 | !define MUI_LANGDLL_ALLLANGUAGES 47 | !define MUI_FINISHPAGE_SHOWREADME "" 48 | !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Startup Shortcut" 49 | !define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateStartupShortcut 50 | !define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe" 51 | 52 | !insertmacro MUI_PAGE_DIRECTORY 53 | !insertmacro MUI_PAGE_INSTFILES 54 | !insertmacro MUI_PAGE_FINISH 55 | 56 | #################################################################### 57 | # Language 58 | 59 | !insertmacro MUI_LANGUAGE "English" ; The first language is the default language 60 | !insertmacro MUI_LANGUAGE "French" 61 | !insertmacro MUI_LANGUAGE "German" 62 | !insertmacro MUI_LANGUAGE "Spanish" 63 | !insertmacro MUI_LANGUAGE "SpanishInternational" 64 | !insertmacro MUI_LANGUAGE "SimpChinese" 65 | !insertmacro MUI_LANGUAGE "TradChinese" 66 | !insertmacro MUI_LANGUAGE "Japanese" 67 | !insertmacro MUI_LANGUAGE "Korean" 68 | !insertmacro MUI_LANGUAGE "Italian" 69 | !insertmacro MUI_LANGUAGE "Dutch" 70 | !insertmacro MUI_LANGUAGE "Danish" 71 | !insertmacro MUI_LANGUAGE "Swedish" 72 | !insertmacro MUI_LANGUAGE "Norwegian" 73 | !insertmacro MUI_LANGUAGE "NorwegianNynorsk" 74 | !insertmacro MUI_LANGUAGE "Finnish" 75 | !insertmacro MUI_LANGUAGE "Greek" 76 | !insertmacro MUI_LANGUAGE "Russian" 77 | !insertmacro MUI_LANGUAGE "Portuguese" 78 | !insertmacro MUI_LANGUAGE "PortugueseBR" 79 | !insertmacro MUI_LANGUAGE "Polish" 80 | !insertmacro MUI_LANGUAGE "Ukrainian" 81 | !insertmacro MUI_LANGUAGE "Czech" 82 | !insertmacro MUI_LANGUAGE "Slovak" 83 | !insertmacro MUI_LANGUAGE "Croatian" 84 | !insertmacro MUI_LANGUAGE "Bulgarian" 85 | !insertmacro MUI_LANGUAGE "Hungarian" 86 | !insertmacro MUI_LANGUAGE "Thai" 87 | !insertmacro MUI_LANGUAGE "Romanian" 88 | !insertmacro MUI_LANGUAGE "Latvian" 89 | !insertmacro MUI_LANGUAGE "Macedonian" 90 | !insertmacro MUI_LANGUAGE "Estonian" 91 | !insertmacro MUI_LANGUAGE "Turkish" 92 | !insertmacro MUI_LANGUAGE "Lithuanian" 93 | !insertmacro MUI_LANGUAGE "Slovenian" 94 | !insertmacro MUI_LANGUAGE "Serbian" 95 | !insertmacro MUI_LANGUAGE "SerbianLatin" 96 | !insertmacro MUI_LANGUAGE "Arabic" 97 | !insertmacro MUI_LANGUAGE "Farsi" 98 | !insertmacro MUI_LANGUAGE "Hebrew" 99 | !insertmacro MUI_LANGUAGE "Indonesian" 100 | !insertmacro MUI_LANGUAGE "Mongolian" 101 | !insertmacro MUI_LANGUAGE "Luxembourgish" 102 | !insertmacro MUI_LANGUAGE "Albanian" 103 | !insertmacro MUI_LANGUAGE "Breton" 104 | !insertmacro MUI_LANGUAGE "Belarusian" 105 | !insertmacro MUI_LANGUAGE "Icelandic" 106 | !insertmacro MUI_LANGUAGE "Malay" 107 | !insertmacro MUI_LANGUAGE "Bosnian" 108 | !insertmacro MUI_LANGUAGE "Kurdish" 109 | !insertmacro MUI_LANGUAGE "Irish" 110 | !insertmacro MUI_LANGUAGE "Uzbek" 111 | !insertmacro MUI_LANGUAGE "Galician" 112 | !insertmacro MUI_LANGUAGE "Afrikaans" 113 | !insertmacro MUI_LANGUAGE "Catalan" 114 | !insertmacro MUI_LANGUAGE "Esperanto" 115 | !insertmacro MUI_LANGUAGE "Asturian" 116 | !insertmacro MUI_LANGUAGE "Basque" 117 | !insertmacro MUI_LANGUAGE "Pashto" 118 | !insertmacro MUI_LANGUAGE "ScotsGaelic" 119 | !insertmacro MUI_LANGUAGE "Georgian" 120 | !insertmacro MUI_LANGUAGE "Vietnamese" 121 | !insertmacro MUI_LANGUAGE "Welsh" 122 | !insertmacro MUI_LANGUAGE "Armenian" 123 | !insertmacro MUI_LANGUAGE "Corsican" 124 | !insertmacro MUI_LANGUAGE "Tatar" 125 | !insertmacro MUI_LANGUAGE "Hindi" 126 | 127 | #################################################################### 128 | # Sections 129 | 130 | Section "Install" 131 | SetShellVarContext all 132 | nsExec::Exec 'sc stop hbbr' 133 | nsExec::Exec 'sc stop hbbs' 134 | nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe' 135 | Sleep 500 136 | 137 | SetOutPath $INSTDIR 138 | File /r "setup\*.*" 139 | WriteUninstaller $INSTDIR\uninstall.exe 140 | 141 | CreateDirectory "$SMPROGRAMS\${APP_NAME}" 142 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" 143 | CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" 144 | CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" 145 | 146 | nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=in action=allow program="$INSTDIR\bin\hbbs.exe" enable=yes' 147 | nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=out action=allow program="$INSTDIR\bin\hbbs.exe" enable=yes' 148 | nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=in action=allow program="$INSTDIR\bin\hbbr.exe" enable=yes' 149 | nsExec::Exec 'netsh advfirewall firewall add rule name="${APP_NAME}" dir=out action=allow program="$INSTDIR\bin\hbbr.exe" enable=yes' 150 | ExecWait 'powershell.exe -NoProfile -windowstyle hidden try { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 } catch {}; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ($\'/silent$\', $\'/install$\') -Wait' 151 | SectionEnd 152 | 153 | Section "Uninstall" 154 | SetShellVarContext all 155 | nsExec::Exec 'sc stop hbbr' 156 | nsExec::Exec 'sc stop hbbs' 157 | nsExec::Exec 'taskkill /F /IM ${PRODUCT_NAME}.exe' 158 | Sleep 500 159 | 160 | RMDir /r "$SMPROGRAMS\${APP_NAME}" 161 | Delete "$SMSTARTUP\${APP_NAME}.lnk" 162 | Delete "$DESKTOP\${APP_NAME}.lnk" 163 | nsExec::Exec 'sc delete hbbr' 164 | nsExec::Exec 'sc delete hbbs' 165 | nsExec::Exec 'netsh advfirewall firewall delete rule name="${APP_NAME}"' 166 | RMDir /r "$INSTDIR\bin" 167 | RMDir /r "$INSTDIR\logs" 168 | RMDir /r "$INSTDIR\service" 169 | Delete "$INSTDIR\${PRODUCT_NAME}.exe" 170 | Delete "$INSTDIR\uninstall.exe" 171 | SectionEnd 172 | 173 | #################################################################### 174 | # Functions 175 | 176 | Function CreateStartupShortcut 177 | CreateShortCut "$SMSTARTUP\${APP_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" 178 | FunctionEnd 179 | -------------------------------------------------------------------------------- /ui/setup/service/nssm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lejianwen/rustdesk-server/813e78d686637b705b85ed2bcb8c7fcd23a02583/ui/setup/service/nssm.exe -------------------------------------------------------------------------------- /ui/setup/service/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | %~d0 3 | cd "%~dp0" 4 | set nssm="%cd%\nssm" 5 | cd .. 6 | 7 | %nssm% install %1 "%cd%\bin\%1.exe" 8 | 9 | %nssm% set %1 DisplayName %1 10 | %nssm% set %1 Description rustdesk %1 server 11 | %nssm% set %1 Start SERVICE_AUTO_START 12 | 13 | %nssm% set %1 ObjectName LocalSystem 14 | %nssm% set %1 Type SERVICE_WIN32_OWN_PROCESS 15 | 16 | %nssm% set %1 AppThrottle 1000 17 | %nssm% set %1 AppExit Default Restart 18 | %nssm% set %1 AppRestartDelay 0 19 | 20 | %nssm% set %1 AppStdout "%cd%\logs\%1.out" 21 | %nssm% set %1 AppStderr "%cd%\logs\%1.err" 22 | 23 | %nssm% start %1 -------------------------------------------------------------------------------- /ui/src/adapter/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod view; 2 | pub mod service; 3 | 4 | pub use view::*; 5 | pub use service::*; 6 | -------------------------------------------------------------------------------- /ui/src/adapter/service/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod windows; 2 | 3 | pub use windows::*; 4 | -------------------------------------------------------------------------------- /ui/src/adapter/service/windows.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::OsStr, process::Command}; 2 | 3 | use crate::{path, usecase::service::*}; 4 | use derive_new::new; 5 | use windows_service::{ 6 | service::ServiceAccess, 7 | service_manager::{ServiceManager, ServiceManagerAccess}, 8 | }; 9 | 10 | #[derive(Debug, new)] 11 | pub struct WindowsDesktopService { 12 | #[new(value = "DesktopServiceState::Stopped")] 13 | pub state: DesktopServiceState, 14 | } 15 | 16 | impl IDesktopService for WindowsDesktopService { 17 | fn start(&mut self) { 18 | call( 19 | [ 20 | "echo.", 21 | "%nssm% stop hbbr", 22 | "%nssm% remove hbbr confirm", 23 | "%nssm% stop hbbs", 24 | "%nssm% remove hbbs confirm", 25 | "mkdir logs", 26 | "echo.", 27 | "service\\run.cmd hbbs", 28 | "echo.", 29 | "service\\run.cmd hbbr", 30 | "echo.", 31 | "@ping 127.1 -n 3 >nul", 32 | ] 33 | .join(" & "), 34 | ); 35 | self.check(); 36 | } 37 | fn stop(&mut self) { 38 | call( 39 | [ 40 | "echo.", 41 | "%nssm% stop hbbr", 42 | "%nssm% remove hbbr confirm", 43 | "echo.", 44 | "%nssm% stop hbbs", 45 | "%nssm% remove hbbs confirm", 46 | "echo.", 47 | "@ping 127.1 -n 3 >nul", 48 | ] 49 | .join(" & "), 50 | ); 51 | self.check(); 52 | } 53 | fn restart(&mut self) { 54 | nssm(["restart", "hbbs"].map(|x| x.to_owned())); 55 | nssm(["restart", "hbbr"].map(|x| x.to_owned())); 56 | self.check(); 57 | } 58 | fn pause(&mut self) { 59 | call( 60 | [ 61 | "echo.", 62 | "%nssm% stop hbbr", 63 | "echo.", 64 | "%nssm% stop hbbs", 65 | "echo.", 66 | "@ping 127.1 -n 3 >nul", 67 | ] 68 | .join(" & "), 69 | ); 70 | self.check(); 71 | } 72 | fn check(&mut self) -> DesktopServiceState { 73 | self.state = match service_status("hbbs").as_str() { 74 | "Running" => DesktopServiceState::Started, 75 | // "Stopped" => DeskServerServiceState::Paused, 76 | _ => DesktopServiceState::Stopped, 77 | }; 78 | self.state.to_owned() 79 | } 80 | } 81 | 82 | fn call(cmd: String) { 83 | Command::new("cmd") 84 | .current_dir(&path()) 85 | .env("nssm", "service\\nssm.exe") 86 | .arg("/c") 87 | .arg("start") 88 | .arg("cmd") 89 | .arg("/c") 90 | .arg(cmd) 91 | .output() 92 | .expect("cmd exec error!"); 93 | } 94 | 95 | fn exec(program: S, args: I) -> String 96 | where 97 | I: IntoIterator, 98 | S: AsRef, 99 | { 100 | match Command::new(program).args(args).output() { 101 | Ok(out) => String::from_utf8(out.stdout).unwrap_or("".to_owned()), 102 | Err(e) => e.to_string(), 103 | } 104 | } 105 | 106 | fn nssm(args: I) -> String 107 | where 108 | I: IntoIterator, 109 | { 110 | exec( 111 | format!("{}\\service\\nssm.exe", path().to_str().unwrap_or_default()), 112 | args, 113 | ) 114 | .replace("\0", "") 115 | .trim() 116 | .to_owned() 117 | } 118 | 119 | fn service_status(name: &str) -> String { 120 | match ServiceManager::local_computer(None::<&OsStr>, ServiceManagerAccess::CONNECT) { 121 | Ok(manager) => match manager.open_service(name, ServiceAccess::QUERY_STATUS) { 122 | Ok(service) => match service.query_status() { 123 | Ok(status) => format!("{:?}", status.current_state), 124 | Err(e) => e.to_string(), 125 | }, 126 | Err(e) => e.to_string(), 127 | }, 128 | Err(e) => e.to_string(), 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /ui/src/adapter/view/desktop.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | process::exit, 3 | time::{Duration, Instant}, 4 | }; 5 | 6 | use crate::{ 7 | path, 8 | usecase::{view::Event, DesktopServiceState}, 9 | BUFFER, 10 | }; 11 | use async_std::task::sleep; 12 | use crossbeam_channel::{Receiver, Sender}; 13 | use tauri::{ 14 | CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, 15 | SystemTrayMenuItem, WindowEvent, 16 | }; 17 | 18 | pub async fn run(sender: Sender, receiver: Receiver) { 19 | let setup_sender = sender.clone(); 20 | let menu_sender = sender.clone(); 21 | let tray_sender = sender.clone(); 22 | let menu = Menu::new() 23 | .add_submenu(Submenu::new( 24 | "Service", 25 | Menu::new() 26 | .add_item(CustomMenuItem::new("restart", "Restart")) 27 | .add_native_item(MenuItem::Separator) 28 | .add_item(CustomMenuItem::new("start", "Start")) 29 | .add_item(CustomMenuItem::new("stop", "Stop")), 30 | )) 31 | .add_submenu(Submenu::new( 32 | "Logs", 33 | Menu::new() 34 | .add_item(CustomMenuItem::new("hbbs.out", "hbbs.out")) 35 | .add_item(CustomMenuItem::new("hbbs.err", "hbbs.err")) 36 | .add_native_item(MenuItem::Separator) 37 | .add_item(CustomMenuItem::new("hbbr.out", "hbbr.out")) 38 | .add_item(CustomMenuItem::new("hbbr.err", "hbbr.err")), 39 | )) 40 | .add_submenu(Submenu::new( 41 | "Configuration", 42 | Menu::new().add_item(CustomMenuItem::new(".env", ".env")), 43 | )); 44 | let tray = SystemTray::new().with_menu( 45 | SystemTrayMenu::new() 46 | .add_item(CustomMenuItem::new("restart", "Restart")) 47 | .add_native_item(SystemTrayMenuItem::Separator) 48 | .add_item(CustomMenuItem::new("start", "Start")) 49 | .add_item(CustomMenuItem::new("stop", "Stop")) 50 | .add_native_item(SystemTrayMenuItem::Separator) 51 | .add_item(CustomMenuItem::new("exit", "Exit GUI")), 52 | ); 53 | let mut app = tauri::Builder::default() 54 | .on_window_event(|event| match event.event() { 55 | // WindowEvent::Resized(size) => { 56 | // if size.width == 0 && size.height == 0 { 57 | // event.window().hide().unwrap(); 58 | // } 59 | // } 60 | WindowEvent::CloseRequested { api, .. } => { 61 | api.prevent_close(); 62 | event.window().minimize().unwrap(); 63 | event.window().hide().unwrap(); 64 | } 65 | _ => {} 66 | }) 67 | .menu(menu) 68 | .on_menu_event(move |event| { 69 | // println!( 70 | // "send {}: {}", 71 | // std::time::SystemTime::now() 72 | // .duration_since(std::time::UNIX_EPOCH) 73 | // .unwrap_or_default() 74 | // .as_millis(), 75 | // event.menu_item_id() 76 | // ); 77 | menu_sender 78 | .send(Event::ViewAction(event.menu_item_id().to_owned())) 79 | .unwrap_or_default() 80 | }) 81 | .system_tray(tray) 82 | .on_system_tray_event(move |app, event| match event { 83 | SystemTrayEvent::LeftClick { .. } => { 84 | let main = app.get_window("main").unwrap(); 85 | if main.is_visible().unwrap() { 86 | main.hide().unwrap(); 87 | } else { 88 | main.show().unwrap(); 89 | main.unminimize().unwrap(); 90 | main.set_focus().unwrap(); 91 | } 92 | } 93 | SystemTrayEvent::MenuItemClick { id, .. } => { 94 | tray_sender.send(Event::ViewAction(id)).unwrap_or_default(); 95 | } 96 | _ => {} 97 | }) 98 | .setup(move |app| { 99 | setup_sender.send(Event::ViewInit).unwrap_or_default(); 100 | app.listen_global("__action__", move |msg| { 101 | match msg.payload().unwrap_or_default() { 102 | r#""__init__""# => setup_sender.send(Event::BrowserInit).unwrap_or_default(), 103 | r#""restart""# => setup_sender 104 | .send(Event::BrowserAction("restart".to_owned())) 105 | .unwrap_or_default(), 106 | _ => (), 107 | } 108 | }); 109 | Ok(()) 110 | }) 111 | .invoke_handler(tauri::generate_handler![root]) 112 | .build(tauri::generate_context!()) 113 | .expect("error while running tauri application"); 114 | let mut now = Instant::now(); 115 | let mut blink = false; 116 | let mut span = 0; 117 | let mut title = "".to_owned(); 118 | let product = "RustDesk Server"; 119 | let buffer = BUFFER.get().unwrap().to_owned(); 120 | loop { 121 | for _ in 1..buffer { 122 | match receiver.recv_timeout(Duration::from_nanos(1)) { 123 | Ok(event) => { 124 | let main = app.get_window("main").unwrap(); 125 | let menu = main.menu_handle(); 126 | let tray = app.tray_handle(); 127 | match event { 128 | Event::BrowserUpdate((action, data)) => match action.as_str() { 129 | "file" => { 130 | let list = ["hbbs.out", "hbbs.err", "hbbr.out", "hbbr.err", ".env"]; 131 | let id = data.as_str(); 132 | if list.contains(&id) { 133 | for file in list { 134 | menu.get_item(file) 135 | .set_selected(file == id) 136 | .unwrap_or_default(); 137 | } 138 | // println!( 139 | // "emit {}: {}", 140 | // std::time::SystemTime::now() 141 | // .duration_since(std::time::UNIX_EPOCH) 142 | // .unwrap_or_default() 143 | // .as_millis(), 144 | // data 145 | // ); 146 | app.emit_all("__update__", (action, data)) 147 | .unwrap_or_default(); 148 | } 149 | } 150 | _ => (), 151 | }, 152 | Event::ViewRenderAppExit => exit(0), 153 | Event::ViewRenderServiceState(state) => { 154 | let enabled = |id, enabled| { 155 | menu.get_item(id).set_enabled(enabled).unwrap_or_default(); 156 | tray.get_item(id).set_enabled(enabled).unwrap_or_default(); 157 | }; 158 | title = format!("{} {:?}", product, state); 159 | main.set_title(title.as_str()).unwrap_or_default(); 160 | match state { 161 | DesktopServiceState::Started => { 162 | enabled("start", false); 163 | enabled("stop", true); 164 | enabled("restart", true); 165 | blink = false; 166 | } 167 | DesktopServiceState::Stopped => { 168 | enabled("start", true); 169 | enabled("stop", false); 170 | enabled("restart", false); 171 | blink = true; 172 | } 173 | _ => { 174 | enabled("start", false); 175 | enabled("stop", false); 176 | enabled("restart", false); 177 | blink = true; 178 | } 179 | } 180 | } 181 | _ => (), 182 | } 183 | } 184 | Err(_) => break, 185 | } 186 | } 187 | let elapsed = now.elapsed().as_micros(); 188 | if elapsed > 16666 { 189 | now = Instant::now(); 190 | // println!("{}ms", elapsed as f64 * 0.001); 191 | let iteration = app.run_iteration(); 192 | if iteration.window_count == 0 { 193 | break; 194 | } 195 | if blink { 196 | if span > 1000000 { 197 | span = 0; 198 | app.get_window("main") 199 | .unwrap() 200 | .set_title(title.as_str()) 201 | .unwrap_or_default(); 202 | } else { 203 | span += elapsed; 204 | if span > 500000 { 205 | app.get_window("main") 206 | .unwrap() 207 | .set_title(product) 208 | .unwrap_or_default(); 209 | } 210 | } 211 | } 212 | } else { 213 | sleep(Duration::from_micros(999)).await; 214 | } 215 | } 216 | } 217 | 218 | #[tauri::command] 219 | fn root() -> String { 220 | path().to_str().unwrap_or_default().to_owned() 221 | } 222 | -------------------------------------------------------------------------------- /ui/src/adapter/view/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod desktop; 2 | 3 | pub use desktop::*; 4 | -------------------------------------------------------------------------------- /ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{env::current_exe, path::PathBuf}; 2 | 3 | use once_cell::sync::OnceCell; 4 | 5 | pub mod adapter; 6 | pub mod usecase; 7 | 8 | pub static BUFFER: OnceCell = OnceCell::new(); 9 | 10 | pub fn path() -> PathBuf { 11 | current_exe() 12 | .unwrap_or_default() 13 | .as_path() 14 | .parent() 15 | .unwrap() 16 | .to_owned() 17 | } 18 | -------------------------------------------------------------------------------- /ui/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | use async_std::{ 7 | prelude::FutureExt, 8 | task::{spawn, spawn_local}, 9 | }; 10 | use crossbeam_channel::bounded; 11 | use rustdesk_server::{ 12 | usecase::{presenter, view, watcher}, 13 | BUFFER, 14 | }; 15 | 16 | #[async_std::main] 17 | async fn main() { 18 | let buffer = BUFFER.get_or_init(|| 10).to_owned(); 19 | let (view_sender, presenter_receiver) = bounded(buffer); 20 | let (presenter_sender, view_receiver) = bounded(buffer); 21 | spawn_local(view::create(presenter_sender.clone(), presenter_receiver)) 22 | .join(spawn(presenter::create(view_sender, view_receiver))) 23 | .join(spawn(watcher::create(presenter_sender))) 24 | .await; 25 | } 26 | -------------------------------------------------------------------------------- /ui/src/usecase/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod presenter; 2 | pub mod service; 3 | pub mod view; 4 | pub mod watcher; 5 | 6 | pub use presenter::*; 7 | pub use service::*; 8 | pub use view::*; 9 | pub use watcher::*; 10 | -------------------------------------------------------------------------------- /ui/src/usecase/presenter.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | use super::{service, DesktopServiceState, Event}; 4 | use crate::BUFFER; 5 | use async_std::task::sleep; 6 | use crossbeam_channel::{Receiver, Sender}; 7 | 8 | pub async fn create(sender: Sender, receiver: Receiver) { 9 | let mut now = Instant::now(); 10 | let buffer = BUFFER.get().unwrap().to_owned(); 11 | let send = |event| sender.send(event).unwrap_or_default(); 12 | if let Some(mut service) = service::create() { 13 | let mut service_state = DesktopServiceState::Unknown; 14 | let mut file = "hbbs.out".to_owned(); 15 | send(Event::ViewRenderServiceState(service_state.to_owned())); 16 | loop { 17 | for _ in 1..buffer { 18 | match receiver.recv_timeout(Duration::from_nanos(1)) { 19 | Ok(event) => match event { 20 | Event::BrowserInit => { 21 | send(Event::BrowserUpdate(("file".to_owned(), file.to_owned()))); 22 | } 23 | Event::BrowserAction(action) => match action.as_str() { 24 | "restart" => service.restart(), 25 | _ => (), 26 | }, 27 | Event::FileChange(path) => { 28 | if path == file { 29 | send(Event::BrowserUpdate(("file".to_owned(), file.to_owned()))); 30 | } 31 | } 32 | Event::ViewAction(action) => match action.as_str() { 33 | "start" => service.start(), 34 | "stop" => service.stop(), 35 | "restart" => service.restart(), 36 | "pause" => service.pause(), 37 | "exit" => send(Event::ViewRenderAppExit), 38 | _ => { 39 | file = action; 40 | send(Event::BrowserUpdate(("file".to_owned(), file.to_owned()))); 41 | } 42 | }, 43 | _ => (), 44 | }, 45 | Err(_) => break, 46 | } 47 | } 48 | sleep(Duration::from_micros(999)).await; 49 | if now.elapsed().as_millis() > 999 { 50 | let state = service.check(); 51 | if state != service_state { 52 | service_state = state.to_owned(); 53 | send(Event::ViewRenderServiceState(state)); 54 | } 55 | now = Instant::now(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ui/src/usecase/service.rs: -------------------------------------------------------------------------------- 1 | use crate::adapter; 2 | 3 | pub fn create() -> Option> { 4 | if cfg!(target_os = "windows") { 5 | return Some(Box::new(adapter::WindowsDesktopService::new())); 6 | } 7 | None 8 | } 9 | 10 | #[derive(Debug, Clone, PartialEq)] 11 | pub enum DesktopServiceState { 12 | Paused, 13 | Started, 14 | Stopped, 15 | Unknown, 16 | } 17 | 18 | pub trait IDesktopService { 19 | fn start(&mut self); 20 | fn stop(&mut self); 21 | fn restart(&mut self); 22 | fn pause(&mut self); 23 | fn check(&mut self) -> DesktopServiceState; 24 | } 25 | -------------------------------------------------------------------------------- /ui/src/usecase/view.rs: -------------------------------------------------------------------------------- 1 | use super::DesktopServiceState; 2 | use crate::adapter::desktop; 3 | use crossbeam_channel::{Receiver, Sender}; 4 | 5 | pub async fn create(sender: Sender, receiver: Receiver) { 6 | desktop::run(sender, receiver).await; 7 | } 8 | 9 | #[derive(Debug, Clone, PartialEq)] 10 | pub enum Event { 11 | BrowserAction(String), 12 | BrowserInit, 13 | BrowserUpdate((String, String)), 14 | BrowserRender(String), 15 | FileChange(String), 16 | ViewAction(String), 17 | ViewInit, 18 | ViewUpdate(String), 19 | ViewRender(String), 20 | ViewRenderAppExit, 21 | ViewRenderServiceState(DesktopServiceState), 22 | } 23 | -------------------------------------------------------------------------------- /ui/src/usecase/watcher.rs: -------------------------------------------------------------------------------- 1 | use std::{path::Path, time::Duration}; 2 | 3 | use super::Event; 4 | use crate::path; 5 | use async_std::task::{sleep, spawn_blocking}; 6 | use crossbeam_channel::{bounded, Sender}; 7 | use notify::{Config, RecommendedWatcher, RecursiveMode, Result, Watcher}; 8 | 9 | pub async fn create(sender: Sender) { 10 | loop { 11 | let watch_sender = sender.clone(); 12 | match spawn_blocking(|| { 13 | watch( 14 | format!("{}/logs/", path().to_str().unwrap_or_default()), 15 | watch_sender, 16 | ) 17 | }) 18 | .await 19 | { 20 | Ok(_) => (), 21 | Err(e) => println!("error: {e}"), 22 | } 23 | sleep(Duration::from_secs(1)).await; 24 | } 25 | } 26 | 27 | fn watch>(path: P, sender: Sender) -> Result<()> { 28 | let (tx, rx) = bounded(10); 29 | let mut watcher = RecommendedWatcher::new(tx, Config::default())?; 30 | watcher.watch(path.as_ref(), RecursiveMode::Recursive)?; 31 | for res in rx { 32 | let event = res?; 33 | for p in event.paths { 34 | let path = p 35 | .file_name() 36 | .unwrap_or_default() 37 | .to_str() 38 | .unwrap_or_default() 39 | .to_owned(); 40 | if path.len() > 0 { 41 | sender.send(Event::FileChange(path)).unwrap_or_default(); 42 | } 43 | } 44 | } 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /ui/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeBuildCommand": "npm run build", 4 | "beforeDevCommand": "npm run dev", 5 | "devPath": "http://127.0.0.1:5177/", 6 | "distDir": "html/dist", 7 | "withGlobalTauri": true 8 | }, 9 | "package": { 10 | "productName": "rustdesk_server", 11 | "version": "0.1.2" 12 | }, 13 | "tauri": { 14 | "allowlist": { 15 | "all": false, 16 | "fs": { 17 | "scope": [ 18 | "$RESOURCE/bin/.env", 19 | "$RESOURCE/logs/*" 20 | ], 21 | "all": false, 22 | "exists": true, 23 | "readDir": true, 24 | "readFile": true, 25 | "writeFile": true 26 | }, 27 | "path": { 28 | "all": true 29 | }, 30 | "shell": { 31 | "all": false, 32 | "open": true 33 | } 34 | }, 35 | "bundle": { 36 | "active": true, 37 | "category": "DeveloperTool", 38 | "copyright": "", 39 | "deb": { 40 | "depends": [] 41 | }, 42 | "externalBin": [], 43 | "icon": [ 44 | "icons/32x32.png", 45 | "icons/128x128.png", 46 | "icons/128x128@2x.png", 47 | "icons/icon.icns", 48 | "icons/icon.ico" 49 | ], 50 | "identifier": "rustdesk.server", 51 | "longDescription": "", 52 | "macOS": { 53 | "entitlements": null, 54 | "exceptionDomain": "", 55 | "frameworks": [], 56 | "providerShortName": null, 57 | "signingIdentity": null 58 | }, 59 | "resources": [], 60 | "shortDescription": "", 61 | "targets": "all", 62 | "windows": { 63 | "certificateThumbprint": null, 64 | "digestAlgorithm": "sha256", 65 | "timestampUrl": "" 66 | } 67 | }, 68 | "security": { 69 | "csp": null 70 | }, 71 | "systemTray": { 72 | "iconPath": "icons/icon.ico", 73 | "iconAsTemplate": true 74 | }, 75 | "updater": { 76 | "active": false 77 | }, 78 | "windows": [ 79 | { 80 | "center": true, 81 | "fullscreen": false, 82 | "height": 600, 83 | "resizable": true, 84 | "title": "RustDesk Server", 85 | "width": 980 86 | } 87 | ] 88 | } 89 | } 90 | --------------------------------------------------------------------------------