#!/usr/bin/env python3 import os import glob import pathlib import platform import zipfile import urllib.request import shutil import hashlib import re import subprocess import argparse import sys from pathlib import Path windows = platform.platform().startswith('Windows') osx = platform.platform().startswith( 'Darwin') or platform.platform().startswith("macOS") hbb_name = 'rustdesk' + ('.exe' if windows else '') exe_path = 'target/release/' + hbb_name if windows: win_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64' flutter_build_dir = f'build/windows/{win_arch}/runner/Release/' elif osx: flutter_build_dir = 'build/macos/Build/Products/Release/' else: flutter_build_dir = 'build/linux/x64/release/bundle/' flutter_build_dir_2 = f'flutter/{flutter_build_dir}' skip_cargo = False def get_deb_arch() -> str: custom_arch = os.environ.get("DEB_ARCH") if custom_arch is None: return "amd64" return custom_arch def get_deb_extra_depends() -> str: custom_arch = os.environ.get("DEB_ARCH") if custom_arch == "armhf": # for arm32v7 libsciter-gtk.so return ", libatomic1" return "" def system2(cmd): exit_code = os.system(cmd) if exit_code != 0: sys.stderr.write(f"Error occurred when executing: `{cmd}`. Exiting.\n") sys.exit(-1) def get_version(): with open("Cargo.toml", encoding="utf-8") as fh: for line in fh: if line.startswith("version"): return line.replace("version", "").replace("=", "").replace('"', '').strip() return '' def parse_rc_features(feature): available_features = {} apply_features = {} if not feature: feature = [] def platform_check(platforms): if windows: return 'windows' in platforms elif osx: return 'osx' in platforms else: return 'linux' in platforms def get_all_features(): features = [] for (feat, feat_info) in available_features.items(): if platform_check(feat_info['platform']): features.append(feat) return features if isinstance(feature, str) and feature.upper() == 'ALL': return get_all_features() elif isinstance(feature, list): if windows: # download third party is deprecated, we use github ci instead. # feature.append('PrivacyMode') pass for feat in feature: if isinstance(feat, str) and feat.upper() == 'ALL': return get_all_features() if feat in available_features: if platform_check(available_features[feat]['platform']): apply_features[feat] = available_features[feat] else: print(f'Unrecognized feature {feat}') return apply_features else: raise Exception(f'Unsupported features param {feature}') def make_parser(): parser = argparse.ArgumentParser(description='Build script.') parser.add_argument( '-f', '--feature', dest='feature', metavar='N', type=str, nargs='+', default='', help='Integrate features, windows only.' 'Available: [Not used for now]. Special value is "ALL" and empty "". Default is empty.') parser.add_argument('--flutter', action='store_true', help='Build flutter package', default=False) parser.add_argument( '--hwcodec', action='store_true', help='Enable feature hwcodec' + ( '' if windows or osx else ', need libva-dev.') ) parser.add_argument( '--vram', action='store_true', help='Enable feature vram, only available on windows now.' ) parser.add_argument( '--portable', action='store_true', help='Build windows portable' ) parser.add_argument( '--unix-file-copy-paste', action='store_true', help='Build with unix file copy paste feature' ) parser.add_argument( '--drm', action='store_true', help='Linux only: build the DRM/KMS capture backend (bundles libdrmtap.so, ' 'dlopen-ed in-process by the root service). Off by default.' ) parser.add_argument( '--skip-cargo', action='store_true', help='Skip cargo build process, only flutter version + Linux supported currently' ) if windows: parser.add_argument( '--skip-portable-pack', action='store_true', help='Skip packing, only flutter version + Windows supported' ) parser.add_argument( "--package", type=str ) if osx: parser.add_argument( '--screencapturekit', action='store_true', help='Enable feature screencapturekit' ) return parser # Generate build script for docker # # it assumes all build dependencies are installed in environments # Note: do not use it in bare metal, or may break build environments def generate_build_script_for_docker(): with open("/tmp/build.sh", "w") as f: f.write(''' #!/bin/bash # environment export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation: " | cut -d' ' -f4-)/include" # flutter pushd /opt wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.0.5-stable.tar.xz tar -xvf flutter_linux_3.0.5-stable.tar.xz export PATH=`pwd`/flutter/bin:$PATH popd # flutter_rust_bridge dart pub global activate ffigen --version 5.0.1 pushd /tmp && git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1 && popd pushd /tmp/flutter_rust_bridge/frb_codegen && cargo install --path . --locked && popd pushd flutter && flutter pub get && popd ~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart # install vcpkg pushd /opt export VCPKG_ROOT=`pwd`/vcpkg git clone https://github.com/microsoft/vcpkg vcpkg/bootstrap-vcpkg.sh popd $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed" # build rustdesk ./build.py --flutter --hwcodec ''') system2("chmod +x /tmp/build.sh") system2("bash /tmp/build.sh") # Downloading third party resources is deprecated. # We can use this function in an offline build environment. # Even in an online environment, we recommend building third-party resources yourself. def download_extract_features(features, res_dir): import re proxy = '' def req(url): if not proxy: return url else: r = urllib.request.Request(url) r.set_proxy(proxy, 'http') r.set_proxy(proxy, 'https') return r for (feat, feat_info) in features.items(): includes = feat_info['include'] if 'include' in feat_info and feat_info['include'] else [] includes = [re.compile(p) for p in includes] excludes = feat_info['exclude'] if 'exclude' in feat_info and feat_info['exclude'] else [] excludes = [re.compile(p) for p in excludes] print(f'{feat} download begin') download_filename = feat_info['zip_url'].split('/')[-1] checksum_md5_response = urllib.request.urlopen( req(feat_info['checksum_url'])) for line in checksum_md5_response.read().decode('utf-8').splitlines(): if line.split()[1] == download_filename: checksum_md5 = line.split()[0] filename, _headers = urllib.request.urlretrieve(feat_info['zip_url'], download_filename) md5 = hashlib.md5(open(filename, 'rb').read()).hexdigest() if checksum_md5 != md5: raise Exception(f'{feat} download failed') print(f'{feat} download end. extract bein') zip_file = zipfile.ZipFile(filename) zip_list = zip_file.namelist() for f in zip_list: file_exclude = False for p in excludes: if p.match(f) is not None: file_exclude = True break if file_exclude: continue file_include = False if includes else True for p in includes: if p.match(f) is not None: file_include = True break if file_include: print(f'extract file {f}') zip_file.extract(f, res_dir) zip_file.close() os.remove(download_filename) print(f'{feat} extract end') def external_resources(flutter, args, res_dir): features = parse_rc_features(args.feature) if not features: return print(f'Build with features {list(features.keys())}') if os.path.isdir(res_dir) and not os.path.islink(res_dir): shutil.rmtree(res_dir) elif os.path.exists(res_dir): raise Exception(f'Find file {res_dir}, not a directory') os.makedirs(res_dir, exist_ok=True) download_extract_features(features, res_dir) if flutter: os.makedirs(flutter_build_dir_2, exist_ok=True) for f in pathlib.Path(res_dir).iterdir(): print(f'{f}') if f.is_file(): shutil.copy2(f, flutter_build_dir_2) else: shutil.copytree(f, f'{flutter_build_dir_2}{f.stem}') def get_features(args): features = ['inline'] if not args.flutter else [] if args.hwcodec: features.append('hwcodec') if args.vram: features.append('vram') if args.flutter: features.append('flutter') if args.unix_file_copy_paste: features.append('unix-file-copy-paste') if args.drm: # Say so rather than quietly handing back a stock build: the backend is Linux-only, so on # any other host the flag cannot be honoured and the resulting binary would look like a # DRM build without being one. if windows or osx: raise Exception('--drm is Linux only') features.append('drm') if osx: if args.screencapturekit: features.append('screencapturekit') print("features:", features) return features def generate_control_file(version): control_file_path = "../res/DEBIAN/control" system2('/bin/rm -rf %s' % control_file_path) content = """Package: rustdesk Section: net Priority: optional Version: %s Architecture: %s Maintainer: rustdesk Homepage: https://rustdesk.com Depends: libgtk-3-0t64 | libgtk-3-0, libxcb-randr0, libxdo3 | libxdo4, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2t64 | libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s Recommends: libayatana-appindicator3-1 Description: A remote control software. """ % (version, get_deb_arch(), get_deb_extra_depends()) file = open(control_file_path, "w") file.write(content) file.close() def ffi_bindgen_function_refactor(): # workaround ffigen system2( 'sed -i "s/ffi.NativeFunction> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") # Bundle libdrmtap.so only when this build actually enabled the `drm` feature, so stock packages # stay exactly what they were. The root service dlopens it in-process by absolute path. # `features` is the comma-joined string, so split it: a bare substring test would also match any # future feature merely containing "drm" (drm-lease, vaapi-drm) and rename the deb to the # consent-bypass variant without --drm ever being passed. ships_so = 'drm' in features.split(',') if ships_so: stage_libdrmtap_into_deb(build_libdrmtap_so()) system2('mkdir -p tmpdeb/DEBIAN') generate_control_file(version) if ships_so: retarget_control_to_drm_variant() system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') md5_file_folder("tmpdeb/") system2('dpkg-deb -b tmpdeb rustdesk.deb;') system2('/bin/rm -rf tmpdeb/') system2('/bin/rm -rf ../res/DEBIAN/control') os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version) if ships_so: # Named apart from the stock package so installing the consent-free variant is a deliberate act. os.rename('../rustdesk-%s.deb' % version, f'../{DRM_PACKAGE_NAME}-{version}.deb') os.chdir("..") def build_deb_from_folder(version, binary_folder, want_drm=False): os.chdir('flutter') system2('mkdir -p tmpdeb/usr/bin/') system2('mkdir -p tmpdeb/usr/share/rustdesk') system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/') system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/') system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/') system2('mkdir -p tmpdeb/usr/share/applications/') system2('mkdir -p tmpdeb/usr/share/polkit-1/actions') system2('rm tmpdeb/usr/bin/rustdesk || true') system2( f'cp -r ../{binary_folder}/* tmpdeb/usr/share/rustdesk/') system2( 'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') system2( 'cp ../res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png') system2( 'cp ../res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg') system2( 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') system2( 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') system2( "echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") # A staged bundle (binary_folder) carries its own libdrmtap.so.0* for a --drm build, so we do # not rebuild it here; the `cp -r` above placed it under usr/share/rustdesk/. Move it to the # private lib dir, then finalize the deb the same way build_flutter_deb does. bundled_glob = glob.glob('tmpdeb/usr/share/rustdesk/libdrmtap.so.0.*') ships_so = any(os.path.isfile(p) and not os.path.islink(p) for p in bundled_glob) # The variant must be decided by the EXPLICIT --drm request, not merely by what happens # to be staged. Cross-check the two and fail loudly on a mismatch: a drm binary staged # WITHOUT its libdrmtap.so.0.* would otherwise be silently shipped as the stock # `rustdesk` package (no drm deps, a dlopen that can never resolve), and a # bundle that DOES carry the .so would be shipped as the consent-bypass variant even # when --drm was never asked for. if want_drm and not ships_so: raise Exception( '--drm was requested but no real libdrmtap.so.0.* is staged under ' 'usr/share/rustdesk/ in the bundle; refusing to package a drm binary as the ' 'stock rustdesk package (it would ship without the capture library or its deps)') if ships_so and not want_drm: raise Exception( 'the staged bundle carries libdrmtap.so.0.* but --drm was not passed; refusing ' 'to silently ship the consent-bypass unattended-wayland variant (pass --drm to ' 'build it deliberately)') if ships_so: so = _single_real_so(bundled_glob, 'the staged --drm bundle') # The THIRD artifact source, and the last one that was missing the check: --package takes the # .so straight out of a bundle somebody else produced, so it has the same exposure as # DRMTAP_PREBUILT_DIR (see the comment on that branch). A CPU-only stub would ship, the loader # would accept it, and capture would degrade to PipeWire without a word. _assert_so_has_egl(so) stage_libdrmtap_into_deb(so) system2(f'rm -f {so}') system2('rm -f tmpdeb/usr/share/rustdesk/libdrmtap.so tmpdeb/usr/share/rustdesk/libdrmtap.so.0') system2('mkdir -p tmpdeb/DEBIAN') generate_control_file(version) if ships_so: retarget_control_to_drm_variant() system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') md5_file_folder("tmpdeb/") system2('dpkg-deb -b tmpdeb rustdesk.deb;') system2('/bin/rm -rf tmpdeb/') system2('/bin/rm -rf ../res/DEBIAN/control') os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version) if ships_so: os.rename('../rustdesk-%s.deb' % version, f'../{DRM_PACKAGE_NAME}-{version}.deb') os.chdir("..") def build_flutter_dmg(version, features): if not skip_cargo: # set minimum osx build target, now is 10.14, which is the same as the flutter xcode project system2( f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --locked --features {features} --release') # copy dylib system2( "cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib") os.chdir('flutter') # cargo builds a single-arch dylib for the host; restrict Xcode to the same arch # so the universal-by-default ARCHS_STANDARD doesn't try to link a missing slice. # FLUTTER_XCODE_* env vars are forwarded to xcodebuild as build settings. mac_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x86_64' system2( f'FLUTTER_XCODE_ARCHS={mac_arch} FLUTTER_XCODE_ONLY_ACTIVE_ARCH=YES flutter build macos --release') system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/') ''' system2( "create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app") os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg") ''' os.chdir("..") def build_flutter_arch_manjaro(version, features): if not skip_cargo: system2(f'cargo build --locked --features {features} --lib --release') ffi_bindgen_function_refactor() os.chdir('flutter') system2('flutter build linux --release') system2(f'strip {flutter_build_dir}/lib/librustdesk.so') os.chdir('../res') system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f') def build_flutter_windows(version, features, skip_portable_pack): if not skip_cargo: system2(f'cargo build --locked --features {features} --lib --release') if not os.path.exists("target/release/librustdesk.dll"): print("cargo build failed, please check rust source code.") exit(-1) os.chdir('flutter') system2('flutter build windows --release') os.chdir('..') shutil.copy2('target/release/deps/dylib_virtual_display.dll', flutter_build_dir_2) if skip_portable_pack: return os.chdir('libs/portable') system2('pip3 install -r requirements.txt') system2( f'python3 ./generate.py -f ../../{flutter_build_dir_2} -o . -e ../../{flutter_build_dir_2}/rustdesk.exe') os.chdir('../..') if os.path.exists('./rustdesk_portable.exe'): os.replace('./target/release/rustdesk-portable-packer.exe', './rustdesk_portable.exe') else: os.rename('./target/release/rustdesk-portable-packer.exe', './rustdesk_portable.exe') print( f'output location: {os.path.abspath(os.curdir)}/rustdesk_portable.exe') os.rename('./rustdesk_portable.exe', f'./rustdesk-{version}-install.exe') print( f'output location: {os.path.abspath(os.curdir)}/rustdesk-{version}-install.exe') def main(): global skip_cargo parser = make_parser() args = parser.parse_args() if os.path.exists(exe_path): os.unlink(exe_path) if os.path.isfile('/usr/bin/pacman'): system2('git checkout src/ui/common.tis') version = get_version() features = ','.join(get_features(args)) flutter = args.flutter if not flutter: system2('python3 res/inline-sciter.py') print(args.skip_cargo) if args.skip_cargo: skip_cargo = True portable = args.portable package = args.package if package: build_deb_from_folder(version, package, args.drm) return res_dir = 'resources' external_resources(flutter, args, res_dir) if windows: # build virtual display dynamic library os.chdir('libs/virtual_display/dylib') system2('cargo build --locked --release') os.chdir('../../..') if flutter: build_flutter_windows(version, features, args.skip_portable_pack) return system2('cargo build --locked --release --features ' + features) # system2('upx.exe target/release/rustdesk.exe') system2('mv target/release/rustdesk.exe target/release/RustDesk.exe') pa = os.environ.get('P') if pa: # https://certera.com/kb/tutorial-guide-for-safenet-authentication-client-for-code-signing/ system2( f'signtool sign /a /v /p {pa} /debug /f .\\cert.pfx /t http://timestamp.digicert.com ' 'target\\release\\rustdesk.exe') else: print('Not signed') os.makedirs(res_dir, exist_ok=True) system2( f'cp -rf target/release/RustDesk.exe {res_dir}') os.chdir('libs/portable') system2('pip3 install -r requirements.txt') system2( f'python3 ./generate.py -f ../../{res_dir} -o . -e ../../{res_dir}/rustdesk-{version}-win7-install.exe') system2(f'mv ../../{res_dir}/rustdesk-{version}-win7-install.exe ../..') elif os.path.isfile('/usr/bin/pacman'): # pacman -S -needed base-devel system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version) if flutter: build_flutter_arch_manjaro(version, features) else: system2('cargo build --locked --release --features ' + features) system2('git checkout src/ui/common.tis') system2('strip target/release/rustdesk') system2('ln -s res/pacman_install && ln -s res/PKGBUILD') system2('HBB=`pwd` makepkg -f') system2('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % ( version, version)) # pacman -U ./rustdesk.pkg.tar.zst elif os.path.isfile('/usr/bin/yum'): system2('cargo build --locked --release --features ' + features) system2('strip target/release/rustdesk') system2( "sed -i 's/Version: .*/Version: %s/g' res/rpm.spec" % version) system2('HBB=`pwd` rpmbuild -ba res/rpm.spec') system2( 'mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm' % ( version, version)) # yum localinstall rustdesk.rpm elif os.path.isfile('/usr/bin/zypper'): system2('cargo build --locked --release --features ' + features) system2('strip target/release/rustdesk') system2( "sed -i 's/Version: .*/Version: %s/g' res/rpm-suse.spec" % version) system2('HBB=`pwd` rpmbuild -ba res/rpm-suse.spec') system2( 'mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % ( version, version)) # yum localinstall rustdesk.rpm else: if flutter: if osx: build_flutter_dmg(version, features) pass else: # system2( # 'mv target/release/bundle/deb/rustdesk*.deb ./flutter/rustdesk.deb') build_flutter_deb(version, features) else: system2('cargo --locked bundle --release --features ' + features) if osx: system2( 'strip target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk') system2( 'cp libsciter.dylib target/release/bundle/osx/RustDesk.app/Contents/MacOS/') # https://github.com/sindresorhus/create-dmg system2('/bin/rm -rf *.dmg') pa = os.environ.get('P') if pa: system2(''' # buggy: rcodesign sign ... path/*, have to sign one by one # install rcodesign via cargo install apple-codesign #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/libsciter.dylib #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app # goto "Keychain Access" -> "My Certificates" for below id which starts with "Developer ID Application:" codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/* codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app '''.format(pa)) system2( 'create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version) os.rename('RustDesk %s.dmg' % version, 'rustdesk-%s.dmg' % version) if pa: system2(''' # https://pyoxidizer.readthedocs.io/en/apple-codesign-0.14.0/apple_codesign.html # https://pyoxidizer.readthedocs.io/en/stable/tugger_code_signing.html # https://developer.apple.com/developer-id/ # goto xcode and login with apple id, manager certificates (Developer ID Application and/or Developer ID Installer) online there (only download and double click (install) cer file can not export p12 because no private key) #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./rustdesk-{1}.dmg codesign -s "Developer ID Application: {0}" --force --options runtime ./rustdesk-{1}.dmg # https://appstoreconnect.apple.com/access/api # https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_getting_started.html#apple-codesign-app-store-connect-api-key # p8 file is generated when you generate api key (can download only once) rcodesign notary-submit --api-key-path ../.p12/api-key.json --staple rustdesk-{1}.dmg # verify: spctl -a -t exec -v /Applications/RustDesk.app '''.format(pa, version)) else: print('Not signed') else: # build deb package system2( 'mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb') system2('dpkg-deb -R rustdesk.deb tmpdeb') system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/') system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/') system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/') system2( 'cp res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') system2( 'cp res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png') system2( 'cp res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg') system2( 'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') system2( 'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') os.system('mkdir -p tmpdeb/etc/rustdesk/') os.system('cp -a res/startwm.sh tmpdeb/etc/rustdesk/') os.system('mkdir -p tmpdeb/etc/X11/rustdesk/') os.system('cp res/xorg.conf tmpdeb/etc/X11/rustdesk/') os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/') os.system('mkdir -p tmpdeb/etc/pam.d/') os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk') system2('strip tmpdeb/usr/bin/rustdesk') system2('mkdir -p tmpdeb/usr/share/rustdesk') system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/share/rustdesk/') system2('cp libsciter-gtk.so tmpdeb/usr/share/rustdesk/') md5_file_folder("tmpdeb/") system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/') os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version) def md5_file(fn): md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest() system2('echo "%s /%s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn)) def md5_file_folder(base_dir): base_path = Path(base_dir) for file in base_path.rglob('*'): if file.is_file() and 'DEBIAN' not in file.parts: relative_path = file.relative_to(base_path) md5_file(str(relative_path)) if __name__ == "__main__": main()