Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/flutter-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ env:
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}-2"
CUSTOM_CLIENT_PRIVATE_KEY: "${{ secrets.CUSTOM_CLIENT_PRIVATE_KEY }}"
CUSTOM_CLIENT_CONFIG: "${{ secrets.CUSTOM_CLIENT_CONFIG }}"

jobs:
generate-bridge:
Expand Down Expand Up @@ -163,12 +165,27 @@ jobs:
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
shell: bash

- name: Sign and generate custom.txt
if: env.CUSTOM_CLIENT_PRIVATE_KEY != null && env.CUSTOM_CLIENT_CONFIG != null
shell: bash
run: |
pip3 install cryptography
python3 res/sign_custom.py \
--private-key-b64 "${{ secrets.CUSTOM_CLIENT_PRIVATE_KEY }}" \
--config "${{ secrets.CUSTOM_CLIENT_CONFIG }}" \
--output custom.txt

- name: Build rustdesk
run: |
# Windows: build RustDesk
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack
mv ./flutter/build/windows/x64/runner/Release ./rustdesk

# Place custom.txt alongside the executable
if (Test-Path custom.txt) {
Copy-Item custom.txt ./rustdesk/custom.txt
}

# Download usbmmidd_v2.zip and extract it to ./rustdesk
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
Expand-Archive usbmmidd_v2.zip -DestinationPath .
Expand Down Expand Up @@ -356,6 +373,16 @@ jobs:
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
shell: bash

- name: Sign and generate custom.txt
if: env.CUSTOM_CLIENT_PRIVATE_KEY != null && env.CUSTOM_CLIENT_CONFIG != null
shell: bash
run: |
pip3 install cryptography
python3 res/sign_custom.py \
--private-key-b64 "${{ secrets.CUSTOM_CLIENT_PRIVATE_KEY }}" \
--config "${{ secrets.CUSTOM_CLIENT_CONFIG }}" \
--output custom.txt

- name: Build rustdesk
id: build
shell: bash
Expand All @@ -367,6 +394,10 @@ jobs:
cargo build --locked --features inline,vram,hwcodec --release --bins
mkdir -p ./Release
mv ./target/release/rustdesk.exe ./Release/rustdesk.exe
# Place custom.txt alongside the executable
if [ -f custom.txt ]; then
cp custom.txt ./Release/custom.txt
fi
curl -LJ -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll
echo "output_folder=./Release" >> $GITHUB_OUTPUT
curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip
Expand Down Expand Up @@ -711,6 +742,16 @@ jobs:
cargo -V
rustc -V

- name: Sign and generate custom.txt
if: env.CUSTOM_CLIENT_PRIVATE_KEY != null && env.CUSTOM_CLIENT_CONFIG != null
shell: bash
run: |
pip3 install cryptography
python3 res/sign_custom.py \
--private-key-b64 "${{ secrets.CUSTOM_CLIENT_PRIVATE_KEY }}" \
--config "${{ secrets.CUSTOM_CLIENT_CONFIG }}" \
--output custom.txt

- name: Build rustdesk
run: |
if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then
Expand All @@ -722,6 +763,11 @@ jobs:
fi
./build.py --flutter --hwcodec --unix-file-copy-paste ${{ matrix.job.extra-build-args }}

# Place custom.txt in macOS app Resources directory
if [ -f custom.txt ]; then
cp custom.txt ./flutter/build/macos/Build/Products/Release/RustDesk.app/Contents/Resources/custom.txt
fi

- name: create unsigned dmg
if: env.UPLOAD_ARTIFACT == 'true'
run: |
Expand Down Expand Up @@ -1410,6 +1456,16 @@ jobs:
name: bridge-artifact
path: ./

- name: Sign and generate custom.txt
if: env.CUSTOM_CLIENT_PRIVATE_KEY != null && env.CUSTOM_CLIENT_CONFIG != null
shell: bash
run: |
pip3 install cryptography
python3 res/sign_custom.py \
--private-key-b64 "${{ secrets.CUSTOM_CLIENT_PRIVATE_KEY }}" \
--config "${{ secrets.CUSTOM_CLIENT_CONFIG }}" \
--output custom.txt

- uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26
name: Build rustdesk
id: vcpkg
Expand Down Expand Up @@ -1694,6 +1750,16 @@ jobs:
RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}')
echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV

- name: Sign and generate custom.txt
if: env.CUSTOM_CLIENT_PRIVATE_KEY != null && env.CUSTOM_CLIENT_CONFIG != null
shell: bash
run: |
pip3 install cryptography
python3 res/sign_custom.py \
--private-key-b64 "${{ secrets.CUSTOM_CLIENT_PRIVATE_KEY }}" \
--config "${{ secrets.CUSTOM_CLIENT_CONFIG }}" \
--output custom.txt

- uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26
name: Build rustdesk sciter binary for ${{ matrix.job.arch }}
id: vcpkg
Expand Down Expand Up @@ -1826,6 +1892,10 @@ jobs:
# make debian package
mkdir -p ./Release
mv ./target/release/rustdesk ./Release/rustdesk
# Place custom.txt alongside the executable
if [ -f custom.txt ]; then
cp custom.txt ./Release/custom.txt
fi
wget -O ./Release/libsciter-gtk.so https://github.com/c-smile/sciter-sdk/raw/master/bin.lnx/${{ matrix.job.sciter_arch }}/libsciter-gtk.so
export DEB_ARCH=${{ matrix.job.deb_arch }}
./build.py --package ./Release
Expand Down
8 changes: 8 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ def build_flutter_deb(version, features):
system2(
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")

# Include custom.txt if present
if os.path.isfile('../custom.txt'):
system2('cp ../custom.txt tmpdeb/usr/share/rustdesk/custom.txt')

system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
Expand Down Expand Up @@ -389,6 +393,10 @@ def build_deb_from_folder(version, binary_folder):
system2(
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")

# Include custom.txt if present
if os.path.isfile('../custom.txt'):
system2('cp ../custom.txt tmpdeb/usr/share/rustdesk/custom.txt')

system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
Expand Down
4 changes: 4 additions & 0 deletions res/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ package() {
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "${pkgdir}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/128x128@2x.png "${pkgdir}/usr/share/icons/hicolor/256x256/apps/rustdesk.png"
install -Dm 644 $HBB/res/scalable.svg "${pkgdir}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg"
# Include custom.txt if present
if [ -f "${HBB}/custom.txt" ]; then
install -Dm 644 "${HBB}/custom.txt" -t "${pkgdir}/usr/share/rustdesk"
fi
}
4 changes: 4 additions & 0 deletions res/rpm-flutter-suse.spec
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ install -Dm 644 $HBB/res/rustdesk.desktop -t "%{buildroot}/usr/share/rustdesk/fi
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "%{buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/128x128@2x.png "%{buildroot}/usr/share/icons/hicolor/256x256/apps/rustdesk.png"
install -Dm 644 $HBB/res/scalable.svg "%{buildroot}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg"
# Include custom.txt if present
if [ -f "${HBB}/custom.txt" ]; then
install -Dm 644 "${HBB}/custom.txt" -t "%{buildroot}/usr/share/rustdesk"
fi

%files
/usr/share/rustdesk/*
Expand Down
4 changes: 4 additions & 0 deletions res/rpm-flutter.spec
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ install -Dm 644 $HBB/res/rustdesk.desktop -t "%{buildroot}/usr/share/rustdesk/fi
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "%{buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/128x128@2x.png "%{buildroot}/usr/share/icons/hicolor/256x256/apps/rustdesk.png"
install -Dm 644 $HBB/res/scalable.svg "%{buildroot}/usr/share/icons/hicolor/scalable/apps/rustdesk.svg"
# Include custom.txt if present
if [ -f "${HBB}/custom.txt" ]; then
install -Dm 644 "${HBB}/custom.txt" -t "%{buildroot}/usr/share/rustdesk"
fi

%files
/usr/share/rustdesk/*
Expand Down
89 changes: 89 additions & 0 deletions res/sign_custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Sign custom client config and generate custom.txt.

Usage:
python3 sign_custom.py --private-key-b64 <base64_private_key> --config <json_config_string> --output <output_path>

The private key should be a 64-byte Ed25519 key (seed + public_key) encoded in Base64,
compatible with sodiumoxide's sign::SecretKey format.

The config should be a JSON string containing the custom client configuration.

The output will be a Base64-encoded string of (signature + message),
compatible with sodiumoxide's sign::verify() format.
"""

import argparse
import base64
import json
import sys

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization


def sign_config(private_key_b64: str, config_json: str) -> str:
"""Sign the config JSON with the given Ed25519 private key.

Args:
private_key_b64: Base64-encoded 64-byte private key (seed + public_key, sodiumoxide format)
config_json: JSON string of the custom client config

Returns:
Base64-encoded signed data (signature + message)
"""
sk_bytes = base64.b64decode(private_key_b64)
if len(sk_bytes) != 64:
print(f"Error: Private key must be 64 bytes, got {len(sk_bytes)}", file=sys.stderr)
sys.exit(1)

# sodiumoxide format: first 32 bytes = seed, last 32 bytes = public key
seed = sk_bytes[:32]
private_key = Ed25519PrivateKey.from_private_bytes(seed)

# Sign the message
message = config_json.encode("utf-8")
signature = private_key.sign(message)

# sodiumoxide sign format: signature (64 bytes) + message
signed_data = signature + message

return base64.b64encode(signed_data).decode("ascii")


def main():
parser = argparse.ArgumentParser(description="Sign custom client config")
parser.add_argument(
"--private-key-b64",
required=True,
help="Base64-encoded 64-byte Ed25519 private key (sodiumoxide format)",
)
parser.add_argument(
"--config",
required=True,
help="JSON string of the custom client config",
)
parser.add_argument(
"--output",
required=True,
help="Output path for the signed custom.txt file",
)
args = parser.parse_args()

# Validate JSON
try:
json.loads(args.config)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON config: {e}", file=sys.stderr)
sys.exit(1)

signed = sign_config(args.private_key_b64, args.config)

with open(args.output, "w") as f:
f.write(signed)

print(f"Signed custom.txt written to {args.output}")


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2184,7 +2184,7 @@ pub fn read_custom_client(config: &str) {
log::error!("Failed to decode custom client config");
return;
};
const KEY: &str = "5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=";
const KEY: &str = "rgvzRBBtKBZnTT6iL2zhpyKxrtyC0iSnZXIUshDcNs4=";
let Some(pk) = get_rs_pk(KEY) else {
log::error!("Failed to parse public key of custom client");
return;
Expand Down
Loading