From aeeaed1f428abe6fcc79f84b8c880154ccaf6711 Mon Sep 17 00:00:00 2001 From: Bryan Gerlach Date: Tue, 12 Nov 2024 21:08:41 -0600 Subject: [PATCH] add client generator --- .gitignore | 2 + api/forms.py | 67 +++++++ api/migrations/0004_githubrun.py | 21 +++ api/models_work.py | 5 + api/templates/base.html | 2 +- api/templates/generator.html | 248 ++++++++++++++++++++++++++ api/templates/installers.html | 23 +++ api/templates/waiting.html | 14 ++ api/urls.py | 6 + api/views.py | 1 + api/views_front.py | 14 +- api/views_generator.py | 291 +++++++++++++++++++++++++++++++ generator_setup.md | 35 ++++ rustdesk_server_api/settings.py | 3 + 14 files changed, 730 insertions(+), 2 deletions(-) create mode 100644 api/migrations/0004_githubrun.py create mode 100644 api/templates/generator.html create mode 100644 api/templates/waiting.html create mode 100644 api/views_generator.py create mode 100644 generator_setup.md diff --git a/.gitignore b/.gitignore index 91964e87..f0778887 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ test *.bat server.ico start.py +icon.png +logo.png # Build build diff --git a/api/forms.py b/api/forms.py index c3bda7d1..24c0463a 100644 --- a/api/forms.py +++ b/api/forms.py @@ -1,6 +1,73 @@ from django import forms from api.models import UserProfile +class GenerateForm(forms.Form): + #Platform + platform = forms.ChoiceField(choices=[('windows','Windows'),('linux','Linux (currently unavailable)'),('android','Android (testing now available)')], initial='windows') + version = forms.ChoiceField(choices=[('master','beta'),('1.3.2','1.3.2'),('1.3.1','1.3.1'),('1.3.0','1.3.0')], initial='1.3.2') + delayFix = forms.BooleanField(initial=True, required=False) + + #General + exename = forms.CharField(label="Name for EXE file", required=True) + appname = forms.CharField(label="Custom App Name", required=False) + direction = forms.ChoiceField(widget=forms.RadioSelect, choices=[ + ('incoming', 'Incoming Only'), + ('outgoing', 'Outgoing Only'), + ('both', 'Bidirectional') + ], initial='both') + installation = forms.ChoiceField(label="Disable Installation", choices=[ + ('installationY', 'No, enable installation'), + ('installationN', 'Yes, DISABLE installation') + ], initial='installationY') + settings = forms.ChoiceField(label="Disable Settings", choices=[ + ('settingsY', 'No, enable settings'), + ('settingsN', 'Yes, DISABLE settings') + ], initial='settingsY') + + #Custom Server + serverIP = forms.CharField(label="Host", required=False) + apiServer = forms.CharField(label="API Server", required=False) + key = forms.CharField(label="Key", required=False) + urlLink = forms.CharField(label="Custom URL for links", required=False) + + #Visual + iconfile = forms.FileField(label="Custom App Icon (in .png format)", required=False, widget=forms.FileInput(attrs={'accept': 'image/png'})) + logofile = forms.FileField(label="Custom App Logo (in .png format)", required=False, widget=forms.FileInput(attrs={'accept': 'image/png'})) + theme = forms.ChoiceField(choices=[ + ('light', 'Light'), + ('dark', 'Dark'), + ('system', 'Follow System') + ], initial='system') + themeDorO = forms.ChoiceField(choices=[('default', 'Default'),('override', 'Override')], initial='default') + + #Security + passApproveMode = forms.ChoiceField(choices=[('password','Accept sessions via password'),('click','Accept sessions via click'),('password-click','Accepts sessions via both')],initial='password-click') + permanentPassword = forms.CharField(widget=forms.PasswordInput(), required=False) + runasadmin = forms.ChoiceField(choices=[('false','No'),('true','Yes')], initial='false') + denyLan = forms.BooleanField(initial=False, required=False) + enableDirectIP = forms.BooleanField(initial=False, required=False) + #ipWhitelist = forms.BooleanField(initial=False, required=False) + autoClose = forms.BooleanField(initial=False, required=False) + + #Permissions + permissionsDorO = forms.ChoiceField(choices=[('default', 'Default'),('override', 'Override')], initial='default') + permissionsType = forms.ChoiceField(choices=[('custom', 'Custom'),('full', 'Full Access'),('view','Screen share')], initial='custom') + enableKeyboard = forms.BooleanField(initial=True, required=False) + enableClipboard = forms.BooleanField(initial=True, required=False) + enableFileTransfer = forms.BooleanField(initial=True, required=False) + enableAudio = forms.BooleanField(initial=True, required=False) + enableTCP = forms.BooleanField(initial=True, required=False) + enableRemoteRestart = forms.BooleanField(initial=True, required=False) + enableRecording = forms.BooleanField(initial=True, required=False) + enableBlockingInput = forms.BooleanField(initial=True, required=False) + enableRemoteModi = forms.BooleanField(initial=False, required=False) + + #Other + removeWallpaper = forms.BooleanField(initial=True, required=False) + + defaultManual = forms.CharField(widget=forms.Textarea, required=False) + overrideManual = forms.CharField(widget=forms.Textarea, required=False) + class AddPeerForm(forms.Form): clientID = forms.CharField(label="Client Rustdesk ID", required=True) alias = forms.CharField(label="Client alias", required=True) diff --git a/api/migrations/0004_githubrun.py b/api/migrations/0004_githubrun.py new file mode 100644 index 00000000..68a4fff5 --- /dev/null +++ b/api/migrations/0004_githubrun.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.3 on 2024-11-12 19:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_rustdesdevice_ip_rustdeskpeer_ip'), + ] + + operations = [ + migrations.CreateModel( + name='GithubRun', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.CharField(max_length=100, verbose_name='uuid')), + ('status', models.CharField(max_length=100, verbose_name='status')), + ], + ), + ] diff --git a/api/models_work.py b/api/models_work.py index 6813807e..c95e4143 100644 --- a/api/models_work.py +++ b/api/models_work.py @@ -144,3 +144,8 @@ class ShareLinkAdmin(admin.ModelAdmin): list_display = ('shash', 'uid', 'peers', 'is_used', 'is_expired', 'create_time') search_fields = ('peers', ) list_filter = ('is_used', 'uid', 'is_expired' ) + +class GithubRun(models.Model): + id = models.IntegerField(verbose_name="ID",primary_key=True) + uuid = models.CharField(verbose_name="uuid", max_length=100) + status = models.CharField(verbose_name="status", max_length=100) diff --git a/api/templates/base.html b/api/templates/base.html index 8285c39f..fa4001d3 100644 --- a/api/templates/base.html +++ b/api/templates/base.html @@ -65,7 +65,7 @@
  • Home
  • Share
  • Installers
  • - +
  • Client Generator
  • {% if u.is_admin %}
  • Admin Panel
  • diff --git a/api/templates/generator.html b/api/templates/generator.html new file mode 100644 index 00000000..fa1d7b88 --- /dev/null +++ b/api/templates/generator.html @@ -0,0 +1,248 @@ +{% extends "base.html" %} +{% block title %}RustDesk WebUI{% endblock %} +{% block legend_name %}Client Generator{% endblock %} +{% block content %} + + + + +

    RustDesk Custom Client Builder

    +
    +
    +

    Select Platform

    +
    + + + +
    + + + {{ form.version }} + +
    + +
    +
    +

    General

    + + {{ form.exename }}

    + + {{ form.appname }}

    + + {{ form.direction }}

    + + {{ form.installation }}

    + + {{ form.settings }}

    +
    + +
    +

    Custom Server

    + + {{ form.serverIP }}

    + + {{ form.key }}

    + + {{ form.apiServer }}

    + + {{ form.urlLink }}

    +
    +
    +
    +
    +

    Security

    + + {{ form.runasadmin }}

    + + {{ form.passApproveMode }}

    + + {{ form.permanentPassword }}
    *The password is used as default, but can be changed by the client


    + + +
    + +
    + +
    +
    + +
    +

    Visual

    + +
    {{ form.iconfile }}


    +


    + +
    {{ form.logofile }}


    +


    + + {{ form.theme }} {{ form.themeDorO }}
    *Default sets the theme but allows the client to change it, Override sets the theme permanently.


    +
    +
    +
    +
    +

    Permissions

    +
    The following Permissions can be set as default (the user can change the settins) or override (the settings cannot be changed).

    + {{ form.permissionsDorO }} + + {{ form.permissionsType }}

    +
    + + + + + + + + + +
    +
    + +
    +

    Other

    +
    +
    + {{ form.defaultManual }}

    +
    + {{ form.overrideManual }}

    +
    +
    +
    +
    + +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/api/templates/installers.html b/api/templates/installers.html index c2ce1ed8..e9910180 100644 --- a/api/templates/installers.html +++ b/api/templates/installers.html @@ -71,6 +71,29 @@ +
    +
    +
    + + + Client Generator + + + + + + + {% for filename, fileinfo in client_files.items %} + + + + + {% endfor %} + +
    FileDate
    {{filename}}{{fileinfo.modified}}
    +
    +
    +
    {% endblock %} diff --git a/api/templates/waiting.html b/api/templates/waiting.html new file mode 100644 index 00000000..97597618 --- /dev/null +++ b/api/templates/waiting.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}RustDesk WebUI{% endblock %} +{% block legend_name %}Client Generator{% endblock %} +{% block content %} +
    Please wait...This can take 20-30 minutes (or longer if there are other users).

    +You will be redirected to the Installers page to download your custom installer once generator is complete.

    +You can close this window at anytime, or stay here to get status updates.

    +

    Status: {{status}}

    + +{% endblock %} \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index 0285b3ed..9aefc6be 100644 --- a/api/urls.py +++ b/api/urls.py @@ -27,4 +27,10 @@ url(r'^delete_peer',views.delete_peer), url(r'^edit_peer',views.edit_peer), url(r'^assign_peer',views.assign_peer), + url(r'^generator',views.generator_view), + url(r'^check_for_file',views.check_for_file), + url(r'^download_client',views.download_client), + url(r'^creategh',views.create_github_run), + url(r'^updategh',views.update_github_run), + url(r'^save_custom_client',views.save_custom_client), ] diff --git a/api/views.py b/api/views.py index 30a38cbb..ca4e7f1c 100644 --- a/api/views.py +++ b/api/views.py @@ -22,3 +22,4 @@ from .views_front import * from .views_api import * +from .views_generator import * diff --git a/api/views_front.py b/api/views_front.py index 9d3b1193..fd820af0 100644 --- a/api/views_front.py +++ b/api/views_front.py @@ -1,4 +1,5 @@ # cython:language_level=3 +import os from django.shortcuts import render from django.http import HttpResponseRedirect from django.contrib.auth.hashers import make_password @@ -348,7 +349,18 @@ def share(request): @login_required(login_url='/api/user_action?action=login') def installers(request): - return render(request, 'installers.html') + custom = os.path.join('clients','custom') + client_custom_files = {} + if os.path.exists(custom): + for file in os.listdir(custom): + filepath = os.path.join(custom,file) + modified = datetime.datetime.fromtimestamp(os.path.getmtime(filepath)).strftime('%Y-%m-%d %I:%M:%S %p') + client_custom_files[file] = { + 'file': file, + 'modified': modified, + 'path': custom + } + return render(request, 'installers.html', {'client_custom_files': client_custom_files}) def get_conn_log(): logs = ConnLog.objects.all() diff --git a/api/views_generator.py b/api/views_generator.py new file mode 100644 index 00000000..c8bc2e31 --- /dev/null +++ b/api/views_generator.py @@ -0,0 +1,291 @@ +import io +from pathlib import Path +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from django.core.files.base import ContentFile +import os +import re +import requests +import base64 +import json +import uuid +import pathlib +from django.conf import settings as _settings +from django.db.models import Q +from .forms import GenerateForm +from .models import GithubRun +from PIL import Image +from urllib.parse import quote + +@login_required(login_url='/api/user_action?action=login') +def generator_view(request): + if request.method == 'POST': + form = GenerateForm(request.POST, request.FILES) + if form.is_valid(): + platform = form.cleaned_data['platform'] + version = form.cleaned_data['version'] + delayFix = form.cleaned_data['delayFix'] + server = form.cleaned_data['serverIP'] + key = form.cleaned_data['key'] + apiServer = form.cleaned_data['apiServer'] + urlLink = form.cleaned_data['urlLink'] + if not server: + server = 'rs-ny.rustdesk.com' #default rustdesk server + if not key: + key = 'OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=' #default rustdesk key + if not apiServer: + apiServer = server+":21114" + if not urlLink: + urlLink = "https://rustdesk.com" + direction = form.cleaned_data['direction'] + installation = form.cleaned_data['installation'] + settings = form.cleaned_data['settings'] + appname = form.cleaned_data['appname'] + filename = form.cleaned_data['exename'] + permPass = form.cleaned_data['permanentPassword'] + theme = form.cleaned_data['theme'] + themeDorO = form.cleaned_data['themeDorO'] + runasadmin = form.cleaned_data['runasadmin'] + passApproveMode = form.cleaned_data['passApproveMode'] + denyLan = form.cleaned_data['denyLan'] + enableDirectIP = form.cleaned_data['enableDirectIP'] + #ipWhitelist = form.cleaned_data['ipWhitelist'] + autoClose = form.cleaned_data['autoClose'] + permissionsDorO = form.cleaned_data['permissionsDorO'] + permissionsType = form.cleaned_data['permissionsType'] + enableKeyboard = form.cleaned_data['enableKeyboard'] + enableClipboard = form.cleaned_data['enableClipboard'] + enableFileTransfer = form.cleaned_data['enableFileTransfer'] + enableAudio = form.cleaned_data['enableAudio'] + enableTCP = form.cleaned_data['enableTCP'] + enableRemoteRestart = form.cleaned_data['enableRemoteRestart'] + enableRecording = form.cleaned_data['enableRecording'] + enableBlockingInput = form.cleaned_data['enableBlockingInput'] + enableRemoteModi = form.cleaned_data['enableRemoteModi'] + removeWallpaper = form.cleaned_data['removeWallpaper'] + defaultManual = form.cleaned_data['defaultManual'] + overrideManual = form.cleaned_data['overrideManual'] + + + filename = re.sub(r'[^\w\s-]', '_', filename).strip() + myuuid = str(uuid.uuid4()) + protocol = 'https' if request.is_secure() else 'http' + host = request.get_host() + full_url = f"{protocol}://{host}/api" + try: + iconfile = form.cleaned_data['iconfile'] + #iconbase64 = resize_and_encode_icon(iconfile) + iconlink = save_png(iconfile,myuuid,full_url) + except: + print("failed to get icon, using default") + #iconbase64 = b"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" + iconlink = "false" + try: + logofile = form.cleaned_data['logofile'] + #logobase64 = resize_and_encode_icon(logofile) + logolink = save_png(logofile,myuuid,full_url) + except: + print("failed to get logo") + #logobase64 = b"" + logolink = "false" + + ###create the custom.txt json here and send in as inputs below + decodedCustom = {} + if direction != "Both": + decodedCustom['conn-type'] = direction + if installation == "installationN": + decodedCustom['disable-installation'] = 'Y' + if settings == "settingsN": + decodedCustom['disable-settings'] = 'Y' + if appname.upper != "rustdesk".upper and appname != "": + decodedCustom['app-name'] = appname + decodedCustom['override-settings'] = {} + decodedCustom['default-settings'] = {} + if permPass != "": + decodedCustom['password'] = permPass + if theme != "system": + if themeDorO == "default": + decodedCustom['default-settings']['theme'] = theme + elif themeDorO == "override": + decodedCustom['override-settings']['theme'] = theme + decodedCustom['approve-mode'] = passApproveMode + decodedCustom['enable-lan-discovery'] = 'N' if denyLan else 'Y' + decodedCustom['direct-server'] = 'Y' if enableDirectIP else 'N' + decodedCustom['allow-auto-disconnect'] = 'Y' if autoClose else 'N' + decodedCustom['allow-remove-wallpaper'] = 'Y' if removeWallpaper else 'N' + if permissionsDorO == "default": + decodedCustom['default-settings']['access-mode'] = permissionsType + decodedCustom['default-settings']['enable-keyboard'] = 'Y' if enableKeyboard else 'N' + decodedCustom['default-settings']['enable-clipboard'] = 'Y' if enableClipboard else 'N' + decodedCustom['default-settings']['enable-file-transfer'] = 'Y' if enableFileTransfer else 'N' + decodedCustom['default-settings']['enable-audio'] = 'Y' if enableAudio else 'N' + decodedCustom['default-settings']['enable-tunnel'] = 'Y' if enableTCP else 'N' + decodedCustom['default-settings']['enable-remote-restart'] = 'Y' if enableRemoteRestart else 'N' + decodedCustom['default-settings']['enable-record-session'] = 'Y' if enableRecording else 'N' + decodedCustom['default-settings']['enable-block-input'] = 'Y' if enableBlockingInput else 'N' + decodedCustom['default-settings']['allow-remote-config-modification'] = 'Y' if enableRemoteModi else 'N' + else: + decodedCustom['override-settings']['access-mode'] = permissionsType + decodedCustom['override-settings']['enable-keyboard'] = 'Y' if enableKeyboard else 'N' + decodedCustom['override-settings']['enable-clipboard'] = 'Y' if enableClipboard else 'N' + decodedCustom['override-settings']['enable-file-transfer'] = 'Y' if enableFileTransfer else 'N' + decodedCustom['override-settings']['enable-audio'] = 'Y' if enableAudio else 'N' + decodedCustom['override-settings']['enable-tunnel'] = 'Y' if enableTCP else 'N' + decodedCustom['override-settings']['enable-remote-restart'] = 'Y' if enableRemoteRestart else 'N' + decodedCustom['override-settings']['enable-record-session'] = 'Y' if enableRecording else 'N' + decodedCustom['override-settings']['enable-block-input'] = 'Y' if enableBlockingInput else 'N' + decodedCustom['override-settings']['allow-remote-config-modification'] = 'Y' if enableRemoteModi else 'N' + + for line in defaultManual.splitlines(): + k, value = line.split('=') + decodedCustom['default-settings'][k.strip()] = value.strip() + + for line in overrideManual.splitlines(): + k, value = line.split('=') + decodedCustom['override-settings'][k.strip()] = value.strip() + + decodedCustomJson = json.dumps(decodedCustom) + + string_bytes = decodedCustomJson.encode("ascii") + base64_bytes = base64.b64encode(string_bytes) + encodedCustom = base64_bytes.decode("ascii") + + #github limits inputs to 10, so lump extras into one with json + extras = {} + extras['runasadmin'] = runasadmin + extras['urlLink'] = urlLink + extras['delayFix'] = 'true' if delayFix else 'false' + extras['version'] = version + extras['rdgen'] = 'false' + extra_input = json.dumps(extras) + + if _settings.GHUSER == '': + ####run the github actions through rdgen.crayoneater.org + url = 'https://rdgen.crayoneater.org/startgh' + data = { + "server":server, + "key":key, + "apiServer":apiServer, + "custom":encodedCustom, + "uuid":myuuid, + "iconlink":iconlink, + "logolink":logolink, + "appname":appname, + "extras":extra_input, + "filename":filename, + "platform":platform + } + response = requests.post(url, json=data) + else: + ####run the github actions through user's own github fork of rdgen + url = 'https://api.github.com/repos/'+_settings.GHUSER+'/rdgen/actions/workflows/generator-'+platform+'.yml/dispatches' + data = { + "ref":"master", + "inputs":{ + "server":server, + "key":key, + "apiServer":apiServer, + "custom":encodedCustom, + "uuid":myuuid, + "iconlink":iconlink, + "logolink":logolink, + "appname":appname, + "extras":extra_input, + "filename":filename + } + } + headers = { + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'Authorization': 'Bearer '+_settings.GHBEARER, + 'X-GitHub-Api-Version': '2022-11-28' + } + response = requests.post(url, json=data, headers=headers) + + print(response) + if response.status_code == 204 or response.status_code == 200: + create_github_run(myuuid) + return render(request, 'waiting.html', {'filename':filename, 'uuid':myuuid, 'status':"Starting generator...please wait", 'platform':platform}) + else: + return JsonResponse({"error": "Something went wrong"}) + else: + form = GenerateForm() + return render(request, 'generator.html', {'form': form}) + +@login_required(login_url='/api/user_action?action=login') +def check_for_file(request): + filename = request.GET['filename'] + uuid = request.GET['uuid'] + platform = request.GET['platform'] + gh_run = GithubRun.objects.filter(Q(uuid=uuid)).first() + status = gh_run.status + + if status == "Success": + return HttpResponseRedirect('/api/installers') + else: + return render(request, 'waiting.html', {'filename':filename, 'uuid':uuid, 'status':status, 'platform':platform}) + +@login_required(login_url='/api/user_action?action=login') +def download_client(request): + filename = request.GET['filename'] + uuid = request.GET['uuid'] + file_path = os.path.join('exe',uuid,filename) + with open(file_path, 'rb') as file: + response = HttpResponse(file, headers={ + 'Content-Type': 'application/vnd.microsoft.portable-executable', + 'Content-Disposition': f'attachment; filename="{filename}"' + }) + + return response + +def save_png(file, uuid, domain): + file_save_path = "png/%s/%s" % (uuid, quote(file.name)) + Path("png/%s" % uuid).mkdir(parents=True, exist_ok=True) + with open(file_save_path, "wb+") as f: + for chunk in file.chunks(): + f.write(chunk) + imageJson = {} + imageJson['url'] = domain + imageJson['uuid'] = uuid + imageJson['file'] = quote(file.name) + #return "%s/%s" % (domain, file_save_path) + return json.dumps(imageJson) + +def get_png(request): + filename = request.GET['filename'] + uuid = request.GET['uuid'] + #filename = filename+".exe" + file_path = os.path.join('png',uuid,filename) + with open(file_path, 'rb') as file: + response = HttpResponse(file, headers={ + 'Content-Type': 'application/vnd.microsoft.portable-executable', + 'Content-Disposition': f'attachment; filename="{filename}"' + }) + + return response + + +def create_github_run(myuuid): + new_github_run = GithubRun( + uuid=myuuid, + status="Starting generator...please wait" + ) + new_github_run.save() + +def update_github_run(request): + data = json.loads(request.body) + myuuid = data.get('uuid') + mystatus = data.get('status') + GithubRun.objects.filter(Q(uuid=myuuid)).update(status=mystatus) + return HttpResponse('') + +def save_custom_client(request): + file = request.FILES['file'] + file_save_path = "clients/custom/%s" % file.name + pathlib.Path("clients/custom").mkdir(parents=True, exist_ok=True) + with open(file_save_path, "wb+") as f: + for chunk in file.chunks(): + f.write(chunk) + + return HttpResponse("File saved successfully!") \ No newline at end of file diff --git a/generator_setup.md b/generator_setup.md new file mode 100644 index 00000000..f8bcdb19 --- /dev/null +++ b/generator_setup.md @@ -0,0 +1,35 @@ +## By default, the generator will use rdgen.crayoneater.org to generate your custom client installers. Follow these instructions to use your own github account to generate your custom client installers. + +## To fully host the client generator yourself, you will need to following: + +
      +
    1. A Github account with a fork of the rdgen rdgen
    2. +
    3. A Github fine-grained access token with permissions for your rdgen repository +
        +
      • login to your github account
      • +
      • click on your profile picture at the top right, click Settings
      • +
      • at the bottom of the left panel, click Developer Settings
      • +
      • click Personal access tokens
      • +
      • click Fine-grained tokens
      • +
      • click Generate new token
      • +
      • give a token name, change expiration to whatever you want
      • +
      • under Repository acces, select Only select repositories, then pick your rdgen repo
      • +
      • give Read and Write access to actions and workflows
      • +
      +
    4. +
    5. Setup environment variables / secrets: +
        +
      • environment variables on the server running rdgen: +
          +
        • GHUSER="your github username"
        • +
        • GHBEARER="your fine-graned access token"
        • +
      • +
      • optional github secrets (setup on your github account for your rdgen repo): +
          +
        • WINDOWS_PFX_BASE64
        • +
        • WINDOWS_PFX_PASSWORD
        • +
        • WINDOWS_PFX_SHA1_THUMBPRINT
        • +
      • +
      +
    6. +
    \ No newline at end of file diff --git a/rustdesk_server_api/settings.py b/rustdesk_server_api/settings.py index ec1d5cbb..5292857b 100644 --- a/rustdesk_server_api/settings.py +++ b/rustdesk_server_api/settings.py @@ -30,6 +30,9 @@ ALLOWED_HOSTS = ["*"] AUTH_USER_MODEL = 'api.UserProfile' #AppName.自定义user +GHUSER = os.environ.get("GHUSER", '') +GHBEARER = os.environ.get("GHBEARER", '') + # Application definition INSTALLED_APPS = [