Build your own branded RustDesk client on your computer β server, key, password, and permissions baked in. No cloud CI required. Python standard-library web app.
Open a local page in the browser Β βΒ pick a target Β βΒ hit build.
The finished installer lands in workspace/output/.
DVForge is a Python standard-library local web app that compiles a customized RustDesk remote-desktop client on your own machine. Configure your self-hosted server, branding, password and permissions, then select a supported target to produce a Windows .exe/.msi, macOS .dmg, Linux .deb/.rpm/.AppImage, or Android .apk. The interface binds to 127.0.0.1; toolchain downloads, source checkout and dependency resolution still require network access. Portable SDKs use .toolchains/, while explicitly enabled installation may install system packages and request administrator approval.
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.
Click to expand / collapse
- Screenshots
- Why DVForge?
- Feature highlights
- Reliability and wizard improvements
- Quick start (90 seconds)
- How it works
- Detailed installation
- Running the app
- Using the GUI
- Who can build what
- Android β which APK do I pick?
- Configuration reference
- Toolchains reference
- Code signing
- Output
- Build times (real-world)
- Multi-machine build farm
- Printer adapter (remote printing)
- Patches & tweaks reference
- Project layout
- API reference
- Environment variables & flags
- Troubleshooting
- Uninstall & clean
- FAQ
- Design principles
- For AI agents & automated tools
- Contributing
- Credits & license
- Community
Placeholders below β replace the files in
docs/images/with real captures. Seedocs/images/README.mdfor the exact shot list.
| Capability board | Baked-in config |
|---|---|
| The board lights only what this machine can build. | Everything that gets baked into the client. |
| Toolchain panel | Live build console |
|---|---|
| Detect, one-click install, on-disk sizes, remove. | Every command streamed live over SSE. |
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/ where supported; explicit system-package installation |
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.
- π₯οΈ 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
bothclient. - π§© 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 β SSE output, optional Show log, severity filters, and full-log copying even while the log is hidden. Reconnecting an open build stream replays its history; a fresh page does not automatically resume the entire wizard/session display.
- π 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.
This contribution branch adds the following general-purpose changes. Customer profiles, private branding, server-policy patches and credentials are not included.
- Five-step wizard: Environment, Config, Targets, Review and Build, with fixed footer navigation and a preflight/output summary before starting compilation.
- Dependency preparation: selected-target requirements, explicit automatic installation and post-install checks. On Linux, password-required installation tries Polkit; without an authentication agent it opens a local terminal for sudo. Passwords never go through the browser or build log. The terminal fallback needs a logged-in Linux desktop session, not an SSH session without a display.
- Sidebar installer race fixed: late installer metadata refreshes prerequisite rows, so per-tool installation and the bulk install button do not disappear depending on which API response finishes first.
- Optional log and filters: all output, errors, warnings, combined diagnostics, or normal output excluding errors/warnings. Counts remain visible with the log hidden.
- Progress: current-command progress where tools report counts/percentages, overall completed-phase percentage, elapsed time and approximate ETA. Unknown command progress is indeterminate. Phase completion is not a time-weighted completion percentage, and ETA can vary significantly between phases.
- Bridge/pub safety: failed dependency resolution stops codegen; package-config JSON and local package roots are validated before reuse. Dry runs do not expect files from commands that were never executed.
- Retry and artifact safety: preserve compatible caches, stop on critical native or packaging failures, reject missing/stale/wrong-architecture packages, and separate outputs by OS, version and architecture.
- Existing farm hardening: stricter transport/result checks; this is not a new generic authenticated worker controller.
- AppImage auto-install: the sidebar can prepare a rootless Podman packaging
image for Linux x86_64. On Debian/Ubuntu it installs missing Podman dependencies
through the existing approval flow, then prepares an Ubuntu 22.04 image with a
pinned RustDesk appimage-builder revision. Only packaging runs in the container;
compilation caches stay on the host. No dummy host
apt-keyshim is added.
Verification: the sanitized public copy passes 43 Python regression tests and mocked desktop/mobile wizard tests, including delayed installer metadata. Real local RustDesk 1.4.9 runs produced all seven selected macOS/Android outputs; earlier Linux x86_64 runs produced DEB/RPM packages. The user confirmed Linux authentication fallback and sidebar behavior on a Debian 13 derivative. These results do not prove every platform, packaging format or client runtime feature works.
Still open: Windows runtime/display-driver acceptance, native ARM acceptance, DRM/unattended Wayland and generic worker scheduling. The AppImage container installer is included, but its real image preparation and complete AppImage build are not yet acceptance-tested. Automated tests cover installer registration, sidebar readiness, wrapper creation and source-only mounts. Deselect AppImage to build DEB/RPM without preparing this packaging environment.
See the audit report, issue follow-up and contribution instructions.
git clone https://github.com/VenimK/DVForge.git
cd DVForge| 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.
# Linux / macOS
./run.sh
# Windows
run.batβ¦or just python3 app.py. The UI opens automatically at http://127.0.0.1:8765.
- Environment β verify requirements; explicitly enable installation if needed.
- Config β set your server, key, app name, password and permissions, then Next.
- Targets β choose supported targets; optionally use Preview plan.
- Review β check the configuration, target list and output locations.
- Build β start; enable Show log to inspect output or select a severity filter.
Your installer appears in workspace/output/<os>/<version>/<architecture>/.
DVForge is three cooperating layers:
web/β HTML + CSS + vanilla JS, with five wizard steps: Environment, Config, Targets, Review, Build, plus a left rail for toolchains and updates.app.pyβ aThreadingHTTPServerbuilt 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.builder/β the engine. Detection, toolchain management, config generation, source customization, and build orchestration (see Project layout for the module map).
βββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββ
β 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
When you hit Build, orchestrator.py runs (roughly) this sequence:
1. Prepare checkout β select the supported RustDesk revision in workspace/rustdesk-src
(preserve compatible caches; reset source customizations safely)
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. Verify & collect β workspace/output/<os>/<version>/<architecture>/
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.
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_.txtMUST be base64, not raw JSON. RustDesk'sread_custom_client()begins withdecode64().config_gen.pyemits the correct base64 payload (verified byte-for-byte against the originalload-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.pyhandles this automatically.π The signature check.
allowCustom.pystrips a 9-line signature-verification block fromsrc/common.rsand renamescustom.txtβcustom_.txt, so your unsigned/custom build will actually load its baked-in config.
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.
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 / potraceInstalls / 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.
# 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 -SkipVsBuildToolsInstalls / 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
cargocompile needslink.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.
powershell -NoProfile -ExecutionPolicy Bypass -File .\Setup-DVForge-WSL2.ps1Sets 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.
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.
./run.sh # Linux / macOS
run.bat # Windows
python3 app.py # any OS, directFlags & 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.)
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.
Environment and Review. Environment checks the requirements before continuing. Automatic dependency installation is explicit; Review shows selected targets and output paths before starting. Next/Back, Start and Cancel live in the fixed footer.
Build step. Two progress rows show the current command and complete session, elapsed time and approximate ETA. Enable Show log to reveal the console. Filter all output, errors, warnings, both, or normal output. Copy log retains the full unfiltered log. On success, verified artifacts are listed; Open folder opens the output location.
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
.shscripts need a Unix host; on Windows use WSL2). The board hides impossible combinations for you.
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.
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 |
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.
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 recreateworkspace/signing/remotely. Apple Developer ID / Keychain identities are not files β they must already exist on the Mac worker.
Finished installers are separated by OS, RustDesk version and architecture:
workspace/output/
βββ macos/1.4.9/aarch64/YourApp-1.4.9-aarch64.dmg
βββ windows/1.4.9/x86_64/YourApp-1.4.9.exe
βββ linux/1.4.9/x86_64/YourApp-1.4.9.deb
βββ android/1.4.9/aarch64/YourApp-arm64-v8a-release.apk
βββ android/1.4.9/universal/YourApp-release.apk
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 |
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.1on 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:
- Shared folder (NAS/SMB/NFS) β every worker watches the same
farm/directory. Startpython3 farm/worker.pyon each machine; submit withpython3 farm/submit.py --targets macos-arm64-dmg. - HTTP queue β run
farm/queue.pybehind nginx (HTTPS + bearer token) and let workers claim over HTTP withpython 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.
Version compatibility: the build UI's version dropdown (1.4.9 stable / 1.5.0 latest) picks the matching vcpkg commit for each RustDesk release. Workers report their available versions to the queue, which skips workers that can't build the job's version β no more outdated workers failing builds before the rating system catches them. See farm/README.md Β§ 3e.
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
:8765to 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.
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.
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 |
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/<os>/<version>/<arch>/ # verified 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
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/environment |
Wizard environment requirements and readiness |
GET /api/matrix |
Capability matrix (which targets are buildable) |
GET /api/config |
Current RustDesk.json |
GET /api/config/status |
Config validity / status |
GET /api/versions |
Available RustDesk build versions (for the dropdown / farm workers) |
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/environment/install |
Prepare selected-target dependencies when explicitly requested |
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 |
| 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 |
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 |
Reuse is managed by the builder: source customizations must match the selected revision while compatible build caches are preserved. Do not delete the whole workspace as a routine retry step. |
| Sidebar says "Auto install" but no button appears | Fixed a loading race between prerequisite detection and installer metadata. Update this branch, restart the server, refresh the page, or click re-scan. |
sudo: a password is required |
Dependency preparation requests Polkit approval or opens a local terminal. Keep DVForge running as the normal desktop user. |
No authentication agent found |
The terminal fallback needs a Linux desktop display and a terminal emulator. Start DVForge from the local desktop session rather than SSH without a display. |
AppImage recipe requires apt-key on Debian 13 |
Use the sidebar AppImage install action or Auto install to prepare the rootless packaging container. Debian/Ubuntu setup may request administrator approval for Podman packages. Actual AppImage acceptance remains pending. DEB/RPM do not require this environment. |
flutter pub get failed / unusable package config |
Fix the preceding solver/network/SDK error. Codegen stops instead of producing dummy bindings; do not blindly delete all caches. |
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. |
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 -ForceOr simply delete .toolchains/ to reset all portable tool downloads.
Do I need GitHub or any cloud account?
No cloud account is required for local builds. Network access is still needed for toolchains, source checkout, package resolution and any selected online services.
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.
- Zero pip dependencies β the server is pure Python stdlib. Nothing to install, nothing to break, trivial to audit.
- Portable where possible β SDKs use
.toolchains/; system dependencies may require explicit administrator-approved package installation. - 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.pyis verified byte-for-byte against the upstreamload-config.py, so builds match the proven GitHub Actions output.
A machine-oriented map so an LLM or agent can understand and operate this repo without spelunking.
What this project is: a local builder that compiles a customized RustDesk remote-desktop client. No cloud CI required, but dependency/source downloads need network access. 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-inCUSTOM_*vars and base64custom_.txt(runtime). Byte-identical to upstreamload-config.py.customize.pyβ applies all source patches, the Android native-embed, and the signature-check strip.orchestrator.pyβ checkout, customization, validated bridge generation, compilation and verified artifacts inworkspace/output/<os>/<version>/<architecture>/. Supports dry-run, cancellation and live logs.environment.py/terminal_approval.pyβ target-specific requirements, explicit installation and local Linux administrator approval without browser-handled passwords.pub_config.py/artifacts.pyβ dependency-state and artifact validation.web/wizard.js/web/build-view.jsβ wizard navigation, optional filtered log and progress display.signing.pyβ self-signed Windows/macOS/Android material.
Key invariants (do not violate):
custom_.txtis base64, never raw JSON (RustDesk'sread_custom_client()callsdecode64()first).- Android does not file-read
custom_.txtβ the config is embedded intoMainService.kt+native_model.dartand bundled as a Flutter asset. - Desktop builds are host-locked; Android is cross-platform.
- Windows host needs the MSVC linker (
link.exe, via VS Build Tools) for every target including Android. - Pinned versions: RustDesk
1.4.9/1.5.0(version dropdown; each pins its own vcpkg commit) Β· Rust1.75(macOS1.81) Β· Flutter3.24.5Β· LLVM15.0.6Β· NDKr28cΒ· JDK17Β· flutter_rust_bridge_codegen1.80.1Β· vcpkg9e593bb(1.5.0) /120deac(1.4.9).
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.
Contributions are welcome! To get started:
- Fork the repo and create a feature branch.
- Read
HANDOFF.mdfor architecture and current state. - Keep the zero-pip-dependency rule for
app.pyandbuilder/(standard library only). - Test with Preview plan (dry-run) before real builds β it prints the full command sequence.
- If you touch config generation, keep it byte-compatible with
patches/load-config-original.py. - 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.
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
Built for people who self-host RustDesk and want a client that already knows their server.
Discord: discord.gg/de2srV6sx Β Β·Β Donate: paypal.me/VenimK
β If DVForge saved you a CI pipeline, consider starring the repo.