Skip to content

Latest commit

Β 

History

70 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

DVForge β€” build your own branded RustDesk client, locally

DVForge

Build your own branded RustDesk client on your computer β€” server, key, password, and permissions baked in. No GitHub. No cloud CI. No pip installs.

Open a local page in the browser Β β†’Β  pick a target Β β†’Β  hit build.
The finished installer lands in workspace/output/.


PayPal Discord Python Platforms RustDesk License: GPL v3 Zero pip deps

VenimK deadboy18 Tiddley123 bryangerlach


πŸ“„ One-paragraph summary

DVForge is a tiny, zero-dependency (Python standard-library only) local web app that compiles a customized RustDesk remote-desktop client on your own machine. You point it at your self-hosted RustDesk server, set an app name, icon, baked-in password and permission set, pick a target platform, and it produces a ready-to-distribute installer β€” a Windows .exe/.msi, a macOS .dmg, Linux .deb/.rpm/.AppImage, or an Android .apk. It performs the exact same source customizations a GitHub Actions pipeline would, but everything runs offline on 127.0.0.1, and the heavy build toolchains (Flutter, Rust, NDK, JDK, …) install into a private, project-local .toolchains/ folder β€” nothing touches your system.

New here? Jump to β†’ Quick Start Β· How it works Β· FAQ Β· Troubleshooting

AI agent / LLM reading this repo? Jump to β†’ πŸ€– For AI agents & automated tools for a machine-oriented map of the whole codebase.


πŸ“‘ Table of contents

Click to expand / collapse

πŸ“Έ Screenshots

Placeholders below β€” replace the files in docs/images/ with real captures. See docs/images/README.md for the exact shot list.

Capability board Baked-in config
Capability board β€” which targets this machine can build Config tab β€” server, key, branding, permissions
The board lights only what this machine can build. Everything that gets baked into the client.
Toolchain panel Live build console
Toolchain panel β€” detect, install, sizes Build console β€” live streaming log
Detect, one-click install, on-disk sizes, remove. Every command streamed live over SSE.

🎯 Why DVForge?

RustDesk is fantastic, but distributing a client that already knows your server β€” with your branding, your baked-in password, and your permission policy β€” normally means running a CI pipeline on GitHub Actions. That's slow, public-ish, requires secrets in the cloud, and is a pain to iterate on.

DVForge removes GitHub from the loop entirely:

The old way (cloud CI) The DVForge way (local)
Push to GitHub, wait for Actions Click Build, watch the log stream live
Secrets live in the cloud Everything stays on 127.0.0.1
One workflow file per platform One folder, one UI, every platform
System-wide SDK installs Portable .toolchains/ β€” nothing system-wide
pip install a wall of deps Zero pip dependencies (stdlib only)
Opaque runner logs Full command visibility + dry-run preview

It's built for people who self-host RustDesk and want a client that already knows their server β€” MSPs, IT departments, homelab users, and anyone shipping branded remote-support tools.


✨ Feature highlights

  • πŸ–₯️ Browser GUI, zero install β€” a Python stdlib HTTP server serves a local page at http://127.0.0.1:8765. No Electron, no Node, no framework.
  • 🧠 Capability-aware board β€” auto-detects your OS/CPU and lights only the targets this machine can actually produce. Wrong-OS targets are visibly disabled, not silently failing.
  • πŸ“¦ Portable toolchains β€” Flutter, Rust, LLVM, NDK, JDK, vcpkg, and more download into a project-local .toolchains/ folder. One click each, or "install missing." Delete the folder to reset.
  • 🎨 Full branding β€” app name, company name, icon, logo, accent colors, light/dark theme, slogan, download/URL links β€” all baked in.
  • πŸ” Baked-in security policy β€” server address, public key, API server, a permanent password, approve mode, and a per-feature permission matrix (keyboard, clipboard, file transfer, audio, recording, terminal, printer, camera, …).
  • πŸ” Connection direction lock β€” ship an incoming-only host, an outgoing-only controller, or a full both client.
  • 🧩 Feature patches β€” hide the connection manager, remove the update nag, strip the "set up your server" tip, hide offline peers, add a privacy screen, and more (toggle in the GUI).
  • πŸ“± Android from Linux/macOS β€” cross-compile every ABI (arm64-v8a, armeabi-v7a, x86_64, or a universal APK).
  • ✍️ Code signing β€” Windows Authenticode (PFX + timestamp), Android keystore, macOS Developer ID / notarization / self-signed .p12. Or generate a self-signed cert from the UI for local tests.
  • πŸ•΅οΈ Dry-run / Preview plan β€” print every command that would run, without compiling. See the whole plan first.
  • πŸ“‘ Live streaming logs β€” build output streams to the browser over Server-Sent Events; refresh-safe (logs replay).
  • 🌐 Optional build farm β€” offload each OS's build to a machine that can actually do it (a Mac builds DMGs, a Windows box builds EXEs), over a shared folder or a small HTTP queue.
  • πŸ–¨οΈ Open printer adapter β€” an included Rust crate that restores remote printing in custom builds (RustDesk's stock printer DLL refuses to run for non-RustDesk-signed executables).
  • 🧹 Clean uninstall β€” dedicated uninstall/clean scripts per OS; your config and branding are preserved by default.

πŸš€ Quick start (90 seconds)

1 Β· Get the app

git clone https://github.com/VenimK/DVForge.git
cd DVForge

2 Β· First-time machine setup β€” pick your OS

You are on Run this
🍎 macOS (DMG; add Android with --with-android) bash Setup-DVForge-macOS.sh
πŸͺŸ Windows (.exe / .msi) powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-Windows.ps1
🐧 Windows + WSL2 (Linux packages + Android APKs) powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-WSL2.ps1

Already inside the cloned folder and don't want it copied elsewhere? macOS: bash Setup-DVForge-macOS.sh --in-place Β· Windows: add -InPlace.

You can run both the Windows and WSL2 installers on one PC β€” Windows for .exe/.msi, WSL for Android + Linux packages.

3 Β· Launch

# Linux / macOS
./run.sh

# Windows
run.bat

…or just python3 app.py. The UI opens automatically at http://127.0.0.1:8765.

4 Β· Build

  1. Targets tab β†’ click a lit target (e.g. Windows x86_64 (exe)).
  2. Config tab β†’ set your server, key, app name, password, permissions.
  3. Build tab β†’ hit Preview plan once to see the commands, then Build. Watch it stream.

Your installer appears in workspace/output/v1.4.9/. Done. πŸŽ‰


πŸ› οΈ How it works

DVForge architecture: browser GUI β†’ Python stdlib server β†’ builder modules β†’ RustDesk source β†’ installer

DVForge is three cooperating layers:

  1. web/ β€” a hand-written browser GUI (HTML + CSS + vanilla JS, no framework) with a hardware-capability aesthetic. Three tabs: Targets, Config, Build, plus a left rail for toolchains and updates.
  2. app.py β€” a ThreadingHTTPServer built entirely on the Python standard library. It serves the static GUI, exposes a small JSON API, and fans out live build/install logs over Server-Sent Events (SSE). No Flask, no FastAPI, no pip.
  3. builder/ β€” the engine. Detection, toolchain management, config generation, source customization, and build orchestration (see Project layout for the module map).

The three jobs

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  1. DETECT    β”‚ ──▢ β”‚  2. CONFIG   β”‚ ──▢ β”‚  3. BUILD          β”‚
β”‚  hardware &   β”‚     β”‚  edit server β”‚     β”‚  checkout source,  β”‚
β”‚  OS β†’ which   β”‚     β”‚  key, brand, β”‚     β”‚  patch it, compile β”‚
β”‚  targets are  β”‚     β”‚  password,   β”‚     β”‚  per-OS, collect   β”‚
β”‚  buildable    β”‚     β”‚  permissions β”‚     β”‚  artifacts, stream β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   detect.py            config_gen.py           orchestrator.py
   prereqs.py                                    customize.py

The build pipeline

When you hit Build, orchestrator.py runs (roughly) this sequence:

1. Clean checkout    β†’  git clone RustDesk @ tag v1.4.9 into workspace/rustdesk-src
                        (any previous tree is removed first β€” customizations mutate it)
2. Apply patches     β†’  allowCustom (strip signature check), hidecm, xoffline,
                        removeNewVersionNotif, removeSetupServerTip, privacyScreen, …
3. Bake config       β†’  sed/patch server, key, API, app/company name, URLs, flags
                        into the RustDesk *source* (compiled-in customizations)
4. Emit custom_.txt  β†’  base64-encoded password + permissions (runtime config)
                        (Android: also embedded into MainService.kt + native_model.dart)
5. Bridge codegen    β†’  flutter_rust_bridge_codegen (local, 1.80.1)
6. Compile           β†’  cargo + flutter build (per target: exe/msi/dmg/deb/rpm/apk)
7. Collect           β†’  copy finished installers to workspace/output/v<version>/

Dry-run / Preview plan prints every command in this sequence without executing the compile β€” run it once to understand exactly what will happen on your machine.

How customizations are applied (the load-bearing detail)

Customizations land two different ways, and mixing them up is the classic footgun:

Kind What When it's read How
Compiled-in Server IP, public key, API server, app/company name, URLs, feature flags At build time sed-patched into the RustDesk source before compiling
Runtime Password, permissions, approve mode At client startup Read from a base64 custom_.txt file next to the binary

⚠️ custom_.txt MUST be base64, not raw JSON. RustDesk's read_custom_client() begins with decode64(). config_gen.py emits the correct base64 payload (verified byte-for-byte against the original load-config.py).

πŸ“± Android never file-reads custom_.txt. On Android the base64 config is embedded directly into native code (MainService.kt, native_model.dart) and bundled as a Flutter asset. customize.py handles this automatically.

πŸ”“ The signature check. allowCustom.py strips a 9-line signature-verification block from src/common.rs and renames custom.txt β†’ custom_.txt, so your unsigned/custom build will actually load its baked-in config.


πŸ“₯ Detailed installation

All setup scripts are idempotent (safe to re-run) and write a portable .toolchains/env.json so installed tools are picked up automatically on the next launch. Python 3.8+ is the only prerequisite you must have beforehand.

macOS

bash Setup-DVForge-macOS.sh                 # DMG toolchain
bash Setup-DVForge-macOS.sh --with-android  # + JDK 17, NDK r28c, Android SDK (API 34) for APKs
bash Setup-DVForge-macOS.sh --in-place      # use this folder, don't copy to ~/DVForge
bash Setup-DVForge-macOS.sh --skip-optional # skip sccache / ImageMagick / potrace

Installs / verifies: Xcode Command Line Tools (git, clang, iconutil), Homebrew, Python 3.8+, cmake, ninja, nasm, pkg-config, create-dmg, cocoapods, then via builder/toolchains.py: Rust 1.81 (macOS pin), Flutter 3.24.5, LLVM/libclang 15.0.6, vcpkg (pinned), sccache 0.11.0, ImageMagick, potrace. It pins rustup to 1.81-<host> and adds both Darwin targets so universal DMGs can lipo. Writes .toolchains/env.json + a DVForge block in ~/.zprofile.

Windows (desktop .exe / .msi)

# Default install β†’ C:\DVForge (short path = reliable deep-path builds)
powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-Windows.ps1

# Use the folder you already have open
powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-Windows.ps1 -InPlace

# Skip the large (~4–6 GB) Visual Studio Build Tools install
powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-Windows.ps1 -SkipVsBuildTools

Installs / verifies: Git, Python 3 (via winget if missing), LongPathsEnabled (so Flutter/MSBuild deep paths don't break), then: Rust 1.75 (MSVC), Flutter 3.24.5, LLVM/libclang 15, vcpkg (pinned), VS Build Tools 2022 (the C++ workload β€” link.exe + MSBuild), .NET 8 SDK, NuGet, sccache 0.11.0, ImageMagick (official installer β†’ .toolchains\imagemagick), and optionally JDK 17. Pins rustup to 1.75-x86_64-pc-windows-msvc.

🧱 The MSVC linker is mandatory on Windows β€” for every target, including Android. Rust's Windows host toolchain is MSVC, and any cargo compile needs link.exe. Until VS Build Tools (C++) are installed, board cells read "install first: msbuild." This one ~4–6 GB, admin-requiring piece is the only unavoidably heavy install; it's excluded from bulk "install missing" and requires an explicit click.

Windows + WSL2 (Linux packages + Android)

powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-WSL2.ps1

Sets up WSL + Debian, then installs Flutter / Android SDK+NDK / vcpkg / Rust / JDK / LLVM inside the Linux distro (~/DVForge), producing Android APKs + .deb/.rpm/.AppImage. Run this alongside the Windows script on the same PC to cover every target.

Native Linux

Clone the repo, ensure Python 3.8+, then ./run.sh and use the toolchain panel's install missing to fetch Flutter, Rust, LLVM, NDK, JDK, and vcpkg into .toolchains/. Produces .deb, .rpm, .AppImage, and Android APKs.


▢️ Running the app

./run.sh              # Linux / macOS
run.bat               # Windows
python3 app.py        # any OS, direct

Flags & environment:

Option Effect
--no-browser Don't auto-open the browser (useful for headless / farm workers)
RDLB_PORT=9000 Change the listen port (default 8765)

The server binds to 127.0.0.1 only β€” it is not exposed to your network by default. (See Build farm for the safe way to accept remote jobs.)


πŸ–±οΈ Using the GUI

Left rail β€” This machine + Toolchains. Shows your detected CPU/OS spec, plus every toolchain with its state, download/on-disk size, version, a per-tool remove (βœ•), and a total local footprint. Install missing grabs everything installable in one go (except VS Build Tools, which needs an explicit click). Check for updates pulls a newer DVForge commit when you want it.

Targets tab β€” "What can this machine build?" A board of every RustDesk target. Cell states:

State Meaning
🟠 Amber / ready This machine can build it right now β€” click to select
⬜ Outline Needs a tool β€” the cell says install first: <tool>
β–¨ Hatched / disabled Wrong OS (e.g. DMG on Windows) β€” can't build here

Config tab β€” "Baked-in config." Server / key / API, branding (name, icon, logo, accent, theme, slogan), password & approve mode, the full permission matrix, connection direction, and all the feature tweaks. A live preview shows the exact baked-in payload as you type.

Build tab β€” "Build." Select target(s), optionally Preview plan (dry-run), then Build. The console streams every command; Cancel stops a run. On success, artifacts are listed with an open folder button.


🧩 Who can build what

Desktop clients are host-locked β€” each is built on its own OS (this mirrors the original CI workflows and is not a limitation DVForge can lift). Android is cross-platform and builds on any Linux or macOS host.

You want Build it on You get Target ID
πŸͺŸ Windows app Windows portable .exe, optional .msi windows-x86_64-exe, windows-x86_64-msi
🍎 macOS Apple Silicon any Mac *-aarch64.dmg macos-arm64-dmg
🍎 macOS Intel any Mac *-x86_64.dmg (cross-compiled from Apple Silicon) macos-x86_64-dmg
🍎 macOS universal any Mac *-universal.dmg (both CPUs, slower) macos-universal-dmg
🐧 Linux (x86_64) Linux / WSL .deb / .rpm / .AppImage linux-x86_64-deb, linux-x86_64-rpm, linux-x86_64-appimage
🐧 Linux (ARM64) Linux / WSL .deb linux-aarch64-deb
πŸ“± Android phone / tablet Linux, WSL, or macOS .apk android-arm64, android-armv7, android-x86_64, android-universal

🚫 Windows PCs cannot build APKs (the Android .sh scripts need a Unix host; on Windows use WSL2). The board hides impossible combinations for you.


πŸ“± Android β€” which APK do I pick?

Pick the cell that matches the device CPU, not the computer you built on.

Device Board cell File name
Almost every phone (Galaxy A54 5G, Pixel, most 2019+) Android arm64-v8a YourApp-arm64-v8a-release.apk
Very old 32-bit phones Android armeabi-v7a …-armeabi-v7a-release.apk
Android x86 emulators / some tablets Android x86_64 …-x86_64-release.apk
One APK for every ABI (largest) Android universal …-release.apk
  • Only the target you selected is collected into output/.
  • πŸ›‘οΈ Sideloaded remote-desktop APKs always trip Play Protect ("install anyway"). That's expected β€” it is not a broken build.
  • ✍️ APKs use debug signing unless you add your own keystore (see Code signing).

Get the Android toolchain (Linux or Mac): click install on the board, or on macOS run bash Setup-DVForge-macOS.sh --with-android. That installs JDK 17, NDK r28c (16 KB page size β€” required on Android 15+), and the Android SDK (API 34) into .toolchains/, reusing the same Flutter you already have for DMGs.


βš™οΈ Configuration reference

Everything lives in configs/RustDesk.json β€” edit it in the GUI (recommended) or by hand. Below is the full field reference.

Core / server
Field Example Meaning
version 1.4.9 RustDesk source tag to build
platform windows Target platform hint
serverIP your.server.com Rendezvous/relay server β€” compiled in
key YourApiKeyHere Server public key β€” compiled in
apiServer https://api.you.com/ API server URL β€” compiled in
appname / exename / compname loadworker App name, executable name, company name
androidappid com.e4bdb2cd.client Android application ID
urlLink / downloadLink https://you.com Branding links baked into the client
slogan loadworker Client slogan
direction both both Β· incoming (host-only) Β· outgoing (controller-only)
Branding & theme
Field Example Meaning
iconFile / logoFile workspace/branding/icon.png Paths to icon/logo used at build
iconbase64 / logobase64 <base64> Embedded icon/logo bytes (so farm workers can recreate them)
theme light Default theme (light / dark)
themeDorO override Theme default-or-override
themeColor #19E646 Primary accent
themeSurfaceLight / themeSurfaceDark #19DBE6 / #18191E Surface colors
themeMeColor #21790B "Me" accent
Password & permissions
Field Example Meaning
passApproveMode password Approve mode
permanentPassword CHANGE_ME Baked-in permanent password (runtime, via base64 custom_.txt)
permissionsDorO default Permissions default-or-override
permissionsType custom Permission preset type
enableKeyboard, enableClipboard, enableFileTransfer, enableAudio, enableTCP, enableRemoteRestart, enableRecording, enableBlockingInput, enableRemoteModi, enablePrinter, enableCamera, enableTerminal on / off Per-feature permission toggles
overrideManual hide-tray=Y\n… Extra RustDesk config lines appended verbatim
defaultManual `` Default config lines
Tweaks (feature patches)
Field Meaning
hidecm Hide the connection manager window
removeNewVersionNotif Remove the "new version available" nag
removeWallpaper Remove wallpaper during sessions
denyLan Deny LAN discovery
enableDirectIP Allow direct IP access
autoClose Auto-close behavior
cycleMonitor Cycle monitors
xOffline Show/hide offline peers styling
statussort Sort peers by status
delayFix Input-delay fix
Signing (see the dedicated section)
Field Meaning
signWinPfx / signWinPassword / signWinTimestamp Windows Authenticode PFX, its password, RFC-3161 timestamp URL
signAndroidKeystore / signAndroidAlias / signAndroidStorePassword / signAndroidKeyPassword Android keystore + credentials
signMacIdentity macOS Developer ID / Keychain identity
signMacP12 / signMacP12Password macOS signing .p12 + password
signMacNotaryKey / signMacNotaryKeyId / signMacNotaryIssuer Apple notarization API key details

🧰 Toolchains reference

The left rail detects what you have and can download a portable copy of most tools into .toolchains/ (no admin). Click install next to a gap, or install missing.

Tool Version (pin) Needed for
Python 3.8+ the app itself (must pre-exist)
Git any source checkout
Rust 1.75 Β· (1.81 on macOS) every native build
Flutter 3.24.5 every Flutter UI
flutter_rust_bridge_codegen 1.80.1 Dart⇄Rust bridge codegen
LLVM / libclang 15.0.6 bindgen / ffigen
vcpkg pinned 120deac3…ba10b FFmpeg / hwcodec
JDK 17 Android
Android NDK r28c Android native lib (16 KB pages, Android 15+)
Android SDK API 34 flutter build apk
VS Build Tools (C++) 2022 Windows linker (link.exe) + MSBuild β€” required for all Windows-host builds
.NET 8 SDK / NuGet 8 Windows .msi packaging
sccache 0.11.0 compile caching (optional)
ImageMagick / potrace β€” icon/logo processing (optional)
Xcode + create-dmg β€” macOS .dmg

Xcode and Visual Studio cannot be silently sideloaded β€” the UI gives the exact install hint for those. Everything else installs one-click.

Reset: delete .toolchains/ to start tool downloads clean, then use the uninstall scripts.


πŸ” Code signing

DVForge can produce signed installers, or generate self-signed material from the UI for local tests.

Platform Options UI action
πŸͺŸ Windows Authenticode PFX + RFC-3161 timestamp POST /api/signing/self-signed generates a self-signed code-signing PFX (and trusts it locally) for testing
πŸ€– Android Your keystore + alias + passwords POST /api/signing/android-keystore generates a debug/keystore for testing; otherwise APKs use debug signing
🍎 macOS Developer ID / Keychain identity, or .p12, plus Apple notarization (API key) POST /api/signing/macos-self-signed generates a self-signed identity for local runs

Signing files (signWinPfx, signMacP12, keystores) are embedded as base64 in the saved config so a farm worker can recreate workspace/signing/ remotely. Apple Developer ID / Keychain identities are not files β€” they must already exist on the Mac worker.


πŸ“€ Output

Finished installers are collected here, versioned by the RustDesk tag:

workspace/output/v1.4.9/
β”œβ”€β”€ YourApp-1.4.9-aarch64.dmg
β”œβ”€β”€ YourApp-1.4.9.exe
β”œβ”€β”€ YourApp-1.4.9.msi
β”œβ”€β”€ YourApp-1.4.9.deb
β”œβ”€β”€ YourApp-1.4.9.rpm
β”œβ”€β”€ YourApp-arm64-v8a-release.apk
└── …

⏱️ Build times (real-world)

Measured on real hardware (WSL2 Debian) β€” your mileage varies with CPU, disk, and cache state. First runs are slowest (cold toolchain + full source compile); subsequent runs benefit from sccache.

Target Time Notes
Linux .rpm ~8m 29s
Linux .deb ~8m 13s
Linux .deb (ARM) ~7m 52s
Android arm64-v8a ~14m 56s
Android x86_64 ~14m 43s
Android armeabi-v7a ~18m 36s
Android universal (all ABIs) ~64 min first run; normal for all-ABI

🌐 Multi-machine build farm

Optional. Lets you offload each OS's build to a machine that can actually do it β€” a Mac builds DMGs, a Windows box builds EXEs, a Linux box builds APKs + packages. DVForge itself still stays on 127.0.0.1 on every machine; only a small job queue is shared.

Model: you drop a job β†’ a worker that matches the job's OS claims it β†’ the finished file lands in an outbox.

You submit a job   β†’   farm/inbox/*.json   (or the HTTP queue)
   Mac worker      β†’   claims macos-*  β†’  builds .dmg
   Windows worker  β†’   claims windows-* β†’ builds .exe / .msi
   Linux worker    β†’   claims linux-* + android-* β†’ builds packages / APKs
Finished files     β†’   farm/outbox/<job-id>/   (+ status.json)
Failures           β†’   farm/failed/

Two transports are supported:

  1. Shared folder (NAS/SMB/NFS) β€” every worker watches the same farm/ directory. Start python3 farm/worker.py on each machine; submit with python3 farm/submit.py --targets macos-arm64-dmg.
  2. HTTP queue β€” run farm/queue.py behind nginx (HTTPS + bearer token) and let workers claim over HTTP with python worker.py --queue "https://api.example" --token "…". There's a public submit UI (farm/public/) and live stats at /status, /health, /stats.

Worker rating: new machines start at 50%. Higher-rated idle workers of the same OS get jobs first. A worker with zero successes after 2+ jobs, or 5 failures in a row, is skipped until reset. You can pin a specific machine for a job.

Offline alerts: pass --notification-webhook <url> (or DVFORGE_WORKER_WEBHOOK) to a worker and the queue will POST to that URL when the worker drops offline or recovers β€” useful for a Discord/Slack channel. See farm/README.md Β§ 3d.

Stopping :8765 / :8766: --with-app / --with-queue intentionally leave those running after Ctrl+C so other machines keep claiming. Run ./farm/stop-farm.sh (farm\stop-farm.bat on Windows) to stop both and clean up stale worker locks.

πŸ”’ Security: never expose port 8766 or DVForge's :8765 to the public internet without nginx HTTPS + a token. Prefer blocking /api/build/ on any public vhost and letting the queue be the only public entry. One job = one OS.

Full walk-through (both PCs, NAS mounts, nginx, curl recipes, worker reset/pin): farm/README.md.


πŸ–¨οΈ Printer adapter (remote printing)

RustDesk's stock printer_driver_adapter.dll verifies the calling executable's Authenticode signature inside init() and refuses to run for anything not signed by RustDesk β€” so remote printing silently fails in custom builds:

ERROR [src\server.rs:160] printer service init failed: Failed to init printer driver

Signing with your own cert doesn't help β€” the check is an allow-list of specific RustDesk identities, not "is it signed." Everything else in RustDesk's printing chain is open source.

printer-adapter/ is a drop-in Rust replacement that reimplements the exact same four-function C ABI (init / uninit / get_prn_data / free_prn_data) with no signature check. It captures print jobs via a virtual printer whose Local Port name is a file path, polls that spool directory, and returns the bytes β€” no custom print driver, therefore no WHQL signing required.

Full explanation, ABI, and capture mechanics: printer-adapter/README.md.


🩹 Patches & tweaks reference

DVForge applies patches to the RustDesk (and sometimes Flutter) source at build time. The essential ones run always; the rest are GUI toggles.

Patch / file Effect
patches/allowCustom.py Β· allowCustom.diff Required. Strips the 9-line signature-check block from src/common.rs; renames custom.txt β†’ custom_.txt so baked-in config loads
patches/hidecm.diff Hide the connection manager (CM) window
patches/removeNewVersionNotif.diff Remove the "new version available" notification
patches/removeSetupServerTip.diff Remove the "set up your server" tip on the connection page
patches/xoffline.diff Change offline-peer display in the peer card
patches/privacyScreen.py Add privacy-screen support (PNG→C++ image embed)
patches/flutter_3.24.4_dropdown_menu_enableFilter.diff Enable filtering in Flutter's dropdown menu
patches/load-config-original.py Reference: the original CI load-config.py DVForge stays byte-compatible with

πŸ—‚οΈ Project layout

DVForge/
β”œβ”€β”€ app.py                       # Local HTTP UI + JSON API + SSE log stream (stdlib only)
β”œβ”€β”€ run.sh / run.bat             # Launchers (Linux/macOS Β· Windows)
β”œβ”€β”€ precheck.sh / precheck.bat   # Pre-flight environment check
β”‚
β”œβ”€β”€ Setup-DVForge-macOS.sh       # First-time toolchain setup, per OS
β”œβ”€β”€ Setup-DVForge-Windows.ps1
β”œβ”€β”€ Setup-DVForge-WSL2.ps1
β”œβ”€β”€ Uninstall-DVForge-*.{sh,ps1} # Matching uninstallers
β”œβ”€β”€ clean.sh / clean.bat         # Clean-slate reset (keeps config + branding)
β”‚
β”œβ”€β”€ builder/                     # ── the engine ──
β”‚   β”œβ”€β”€ detect.py                #   hardware/OS detection + capability matrix
β”‚   β”œβ”€β”€ prereqs.py               #   toolchain detection + per-OS install hints
β”‚   β”œβ”€β”€ toolchains.py            #   portable toolchain download/install + env.json
β”‚   β”œβ”€β”€ config_gen.py            #   RustDesk.json β†’ CUSTOM_* + base64 custom_.txt
β”‚   β”œβ”€β”€ customize.py             #   all sed/patch steps, Android embed, signature strip
β”‚   β”œβ”€β”€ orchestrator.py          #   build orchestration (checkoutβ†’patchβ†’compileβ†’collect)
β”‚   β”œβ”€β”€ signing.py               #   Windows/macOS/Android self-signed material
β”‚   └── precheck.py              #   environment pre-flight
β”‚
β”œβ”€β”€ web/                         # ── the browser GUI ──
β”‚   β”œβ”€β”€ index.html               #   spec readout, capability board, config form, console
β”‚   β”œβ”€β”€ app.js                   #   all client logic (vanilla JS)
β”‚   β”œβ”€β”€ advanced-keys.js         #   advanced config keys
β”‚   └── style.css                #   hardware-capability aesthetic
β”‚
β”œβ”€β”€ configs/
β”‚   └── RustDesk.json            # Your baked-in config (edit in the GUI)
β”‚
β”œβ”€β”€ patches/                     # allowCustom + feature diffs (see reference above)
β”‚
β”œβ”€β”€ farm/                        # ── optional multi-machine build farm ──
β”‚   β”œβ”€β”€ worker.py                #   claims + builds jobs matching its OS
β”‚   β”œβ”€β”€ queue.py                 #   HTTP job queue (run behind nginx + token)
β”‚   β”œβ”€β”€ submit.py                #   submit a job
β”‚   β”œβ”€β”€ stop-farm.sh / .bat      #   stop :8765 / :8766 + clean worker locks
β”‚   β”œβ”€β”€ inbox/ outbox/ failed/ … #   job flow directories
β”‚   β”œβ”€β”€ public/                  #   public submit UI (index/join/stats)
β”‚   └── README.md                #   full farm guide
β”‚
β”œβ”€β”€ printer-adapter/             # ── open printer_driver_adapter.dll replacement ──
β”‚   β”œβ”€β”€ src/lib.rs               #   the four-function C ABI, no signature check
β”‚   β”œβ”€β”€ Cargo.toml
β”‚   └── README.md
β”‚
β”œβ”€β”€ workspace/                   # (created at runtime)
β”‚   β”œβ”€β”€ rustdesk-src/            #   cloned + customized RustDesk source
β”‚   β”œβ”€β”€ branding/                #   your icons/logos
β”‚   β”œβ”€β”€ signing/                 #   generated signing material
β”‚   └── output/v<version>/       #   βœ… finished installers
β”‚
β”œβ”€β”€ .toolchains/                 # (created on install) portable SDKs + env.json
β”‚
β”œβ”€β”€ HANDOFF.md                   # Deep engineering context / test history
β”œβ”€β”€ LICENSE                      # GPL-3.0
└── README.md                    # You are here

πŸ”Œ API reference

app.py exposes a small JSON + SSE API on http://127.0.0.1:8765. Useful for scripting or for understanding the GUI's behavior.

GET endpoints
Endpoint Returns
GET /api/host Detected hardware + OS spec
GET /api/prereqs Toolchain detection results
GET /api/matrix Capability matrix (which targets are buildable)
GET /api/config Current RustDesk.json
GET /api/config/status Config validity / status
GET /api/build/stream SSE β€” live build log
GET /api/build/status Current build state / result
GET /api/toolchains Toolchain list with sizes/versions
GET /api/toolchains/stream SSE β€” live install log
GET /api/toolchains/status Install state
GET /api/update/status Update availability
GET /api/update/stream SSE β€” live update log
GET /api/branding/<file> Serve a branding asset (icon/logo)
POST endpoints
Endpoint Does
POST /api/config Save config (unpacks embedded icon/logo/signing blobs)
POST /api/preview Render the baked-in custom_.txt preview
POST /api/build/preflight Validate before building
POST /api/build/start Start a build (dry_run supported)
POST /api/build/cancel Cancel the running build
POST /api/toolchains/install Install a toolchain / "install missing"
POST /api/toolchains/cancel Cancel a running install
POST /api/toolchains/remove Remove a locally-installed toolchain
POST /api/signing/self-signed Generate a Windows self-signed PFX
POST /api/signing/android-keystore Generate an Android keystore
POST /api/signing/macos-self-signed Generate a macOS self-signed identity
POST /api/upload Upload a branding/signing file
POST /api/open-folder Open the output folder in the OS file manager
POST /api/update/start Pull a newer DVForge commit

🎚️ Environment variables & flags

Variable / flag Where Effect
RDLB_PORT app.py Listen port (default 8765)
--no-browser app.py / launchers Don't auto-open the browser
DVFORGE_URL farm worker DVForge instance the worker drives (default http://127.0.0.1:8765)
DVFORGE_FARM farm worker Path to the shared farm/ directory
DVFORGE_FARM_TOKEN farm/queue.py Bearer token for the HTTP queue

🩺 Troubleshooting

Hard-won fixes from real Windows/macOS/Linux test rounds (see HANDOFF.md for the full history):

Symptom Cause & fix
linker link.exe not found (Windows) Rust's Windows host is MSVC. Install VS Build Tools (C++) β€” required for all Windows-host builds, including Android. Board cells will say "install first: msbuild" until it's present.
msbuild still shows missing after install Detection uses vswhere; a fresh terminal (or re-scan) flips it green once link.exe is available.
WinError 740 on LLVM install The official LLVM installer requires admin. DVForge runs it elevated via one UAC prompt β€” approve it. LLVM is only needed for Windows desktop builds.
WinError 2 on a tool (e.g. flutter_rust_bridge_codegen) A tool isn't on PATH. run() resolves executables via shutil.which and adds ~/.cargo/bin; if a tool is genuinely missing you'll get a clear message β€” install it from the toolchain panel.
'charmap' codec can't decode 0x90 Old Unicode crash on non-cp1252 build output β€” fixed (all subprocess output is decoded utf-8 / errors="replace"). Update to latest.
rustdesk-src already exists Checkout is now always clean β€” any previous source tree is removed first (customizations mutate it, so reuse would corrupt the build).
Android .sh scripts fail on Windows They need bash; DVForge auto-finds Git Bash (<Git>\bin\bash.exe). If missing, install Git for Windows. Or build Android via WSL2.
ConnectionAbortedError / WinError 10053 spam Normal SSE disconnect when you close the browser tab β€” harmless, now swallowed.
A download URL 404s URLs are official but can move. The console prints the exact URL β€” it's a one-line fix in builder/toolchains.py.
Play Protect warns on the APK Expected for all sideloaded remote-desktop apps. Tap "install anyway." Add your own keystore for a cleaner install.
Remote printing fails in a custom build RustDesk's printer DLL is signature-gated. Use the included printer adapter.

🧽 Uninstall & clean

Clean build artifacts (keeps your config + branding + toolchains):

bash clean.sh            # Linux / macOS   (clean.bat on Windows)

Removes workspace/rustdesk-src, workspace/output, caches, and stray logs. Add --all for a deeper clean; your configs/ and workspace/branding/ are preserved.

Uninstall toolchains (per OS):

bash Uninstall-DVForge-macOS.sh
# Windows (toolchains only, keeps project + VS):
#   powershell -File .\Uninstall-DVForge-Windows.ps1
# Windows (also delete C:\DVForge):
#   powershell -File .\Uninstall-DVForge-Windows.ps1 -InstallRoot C:\DVForge -RemoveProject -Force
# WSL2:
#   powershell -File .\Uninstall-DVForge-WSL2.ps1
# WSL2 (full wipe incl. Debian distro):
#   powershell -File .\Uninstall-DVForge-WSL2.ps1 -RemoveDebian -RemoveWslConfig -Force

Or simply delete .toolchains/ to reset all portable tool downloads.


❓ FAQ

Do I need GitHub or any cloud account?

No. DVForge builds entirely locally. The only network access is downloading toolchains and cloning the RustDesk source once.

Do I need to pip install anything?

No. app.py and the whole server use only the Python standard library. Python 3.8+ is the single prerequisite.

Why can't my Windows PC build a Mac DMG (or vice-versa)?

Desktop builds are host-locked β€” this mirrors RustDesk's own CI and can't be lifted. Use the build farm to route each OS's build to a machine that can do it. Android is the exception β€” it builds on any Linux/macOS host.

Is my baked-in password secure?

It's stored base64-encoded in custom_.txt next to the binary (this is how RustDesk reads runtime config). Base64 is encoding, not encryption β€” treat any client that ships a permanent password as you would any credential.

My APK triggers Play Protect. Is the build broken?

No β€” every sideloaded remote-desktop APK does. Tap "install anyway," and add your own keystore for a smoother install.

Does DVForge publish or host the installers?

No. DVForge only builds. It never publishes releases or hosts downloads β€” the files stay in workspace/output/.

How do I update DVForge itself?

Check for updates in the left rail pulls a newer DVForge commit when you want it (POST /api/update/start).

Remote printing doesn't work in my branded build.

RustDesk's printer DLL only runs for RustDesk-signed executables. Use the included open printer adapter.


🧱 Design principles

  • Zero pip dependencies β€” the server is pure Python stdlib. Nothing to install, nothing to break, trivial to audit.
  • Nothing system-wide β€” every heavy SDK lives in a project-local .toolchains/. Delete the folder to reset. Your OS stays clean.
  • Local-only by default β€” the server binds 127.0.0.1. The farm is opt-in and documented with a security checklist.
  • Honest capability board β€” the UI never offers a build this machine can't do; it tells you exactly which tool is missing.
  • Full transparency β€” dry-run prints every command; the console streams the real thing live.
  • Byte-compatible with the original CI β€” config_gen.py is verified byte-for-byte against the upstream load-config.py, so builds match the proven GitHub Actions output.

πŸ€– For AI agents & automated tools

A machine-oriented map so an LLM or agent can understand and operate this repo without spelunking.

What this project is: a local, offline builder that compiles a customized RustDesk remote-desktop client. No cloud CI. Pure-stdlib Python server + vanilla-JS browser GUI + a builder/ engine.

Entry point: app.py β€” ThreadingHTTPServer, serves web/, exposes the JSON+SSE API in API reference. Runs toolchains.apply_persisted_env(ROOT) at startup to load .toolchains/env.json before detection. Default port 8765 (RDLB_PORT), --no-browser to suppress auto-open. Bind is 127.0.0.1.

Run it: python3 app.py (or ./run.sh / run.bat). First-time toolchains via Setup-DVForge-{macOS.sh,Windows.ps1,WSL2.ps1}.

Engine modules (builder/):

  • detect.py β€” OS/CPU β†’ capability matrix. Target IDs: windows-x86_64-{exe,msi}, macos-{arm64,x86_64,universal}-dmg, linux-{x86_64-deb,x86_64-rpm,x86_64-appimage,aarch64-deb}, android-{arm64,armv7,x86_64,universal}.
  • prereqs.py β€” toolchain presence + install hints.
  • toolchains.py β€” portable download/install registry; writes/loads .toolchains/env.json.
  • config_gen.py β€” configs/RustDesk.json β†’ compiled-in CUSTOM_* vars and base64 custom_.txt (runtime). Byte-identical to upstream load-config.py.
  • customize.py β€” applies all source patches, the Android native-embed, and the signature-check strip.
  • orchestrator.py β€” full pipeline: clean checkout β†’ patch β†’ bake β†’ bridge codegen β†’ compile β†’ collect to workspace/output/v<version>/. Supports dry-run + cancel + live-log callback.
  • signing.py β€” self-signed Windows/macOS/Android material.

Key invariants (do not violate):

  1. custom_.txt is base64, never raw JSON (RustDesk's read_custom_client() calls decode64() first).
  2. Android does not file-read custom_.txt β€” the config is embedded into MainService.kt + native_model.dart and bundled as a Flutter asset.
  3. Desktop builds are host-locked; Android is cross-platform.
  4. Windows host needs the MSVC linker (link.exe, via VS Build Tools) for every target including Android.
  5. Pinned versions: RustDesk v1.4.9 Β· Rust 1.75 (macOS 1.81) Β· Flutter 3.24.5 Β· LLVM 15.0.6 Β· NDK r28c Β· JDK 17 Β· flutter_rust_bridge_codegen 1.80.1 Β· vcpkg commit 120deac3062162151622ca4860575a33844ba10b.

Config file: configs/RustDesk.json β€” see Configuration reference for every field. Compiled-in vs runtime split documented there.

Subsystems: farm/ (multi-machine build queue, shared-folder or HTTP), printer-adapter/ (open replacement for the signature-gated printer DLL). Each has its own README.

Deeper context: HANDOFF.md documents architecture, test history, and remaining work.


🀝 Contributing

Contributions are welcome! To get started:

  1. Fork the repo and create a feature branch.
  2. Read HANDOFF.md for architecture and current state.
  3. Keep the zero-pip-dependency rule for app.py and builder/ (standard library only).
  4. Test with Preview plan (dry-run) before real builds β€” it prints the full command sequence.
  5. If you touch config generation, keep it byte-compatible with patches/load-config-original.py.
  6. Open a PR with a clear description; note which OS/target you tested on.

Found a bug or a 404'd download URL? Open an issue β€” the console prints the exact failing URL, which usually makes it a one-line fix.


πŸ“œ Credits & license

DVForge is licensed under the GNU GPL v3.0.

It incorporates work from rdgen by Bryan Gerlach (GPL-3.0) β€” the custom-client config shape, patches, and GitHub Actions generator this local builder is based on.

Clients you produce are based on RustDesk (AGPL-3.0).

Contributors: VenimK Β· deadboy18 Β· Tiddley123 Β· bryangerlach


πŸ’¬ Community

Built for people who self-host RustDesk and want a client that already knows their server.

Discord PayPal

Discord: discord.gg/de2srV6sx Β Β·Β  Donate: paypal.me/VenimK

⭐ If DVForge saved you a CI pipeline, consider starring the repo.

Releases

Packages

Contributors

Languages