forked from shaytan/rdgen
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca0d321873 | ||
|
|
80fc9f6a84 | ||
|
|
b567c74902 | ||
|
|
cc47350fab | ||
|
|
e882a604d7 | ||
|
|
1b7468a1f2 | ||
|
|
1424caffb0 | ||
|
|
47cf713307 | ||
|
|
fe505ed35c |
29
.github/patches/allowCustom.diff
vendored
Normal file
29
.github/patches/allowCustom.diff
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
diff --git a/src/common.rs b/src/common.rs
|
||||
index 56361a5da..92b221e9a 100644
|
||||
--- a/src/common.rs
|
||||
+++ b/src/common.rs
|
||||
@@ -1474,15 +1474,15 @@ pub fn read_custom_client(config: &str) {
|
||||
log::error!("Failed to decode custom client config");
|
||||
return;
|
||||
};
|
||||
- const KEY: &str = "5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=";
|
||||
- let Some(pk) = get_rs_pk(KEY) else {
|
||||
- log::error!("Failed to parse public key of custom client");
|
||||
- return;
|
||||
- };
|
||||
- let Ok(data) = sign::verify(&data, &pk) else {
|
||||
- log::error!("Failed to dec custom client config");
|
||||
- return;
|
||||
- };
|
||||
+ // const KEY: &str = "5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=";
|
||||
+ // let Some(pk) = get_rs_pk(KEY) else {
|
||||
+ // log::error!("Failed to parse public key of custom client");
|
||||
+ // return;
|
||||
+ // };
|
||||
+ // let Ok(data) = sign::verify(&data, &pk) else {
|
||||
+ // log::error!("Failed to dec custom client config");
|
||||
+ // return;
|
||||
+ // };
|
||||
let Ok(mut data) =
|
||||
serde_json::from_slice::<std::collections::HashMap<String, serde_json::Value>>(&data)
|
||||
else {
|
||||
13
.github/patches/removeSetupServerTip.diff
vendored
Normal file
13
.github/patches/removeSetupServerTip.diff
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart
|
||||
index d9dc3eec4..76f386b76 100644
|
||||
--- a/flutter/lib/desktop/pages/connection_page.dart
|
||||
+++ b/flutter/lib/desktop/pages/connection_page.dart
|
||||
@@ -131,7 +131,7 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
|
||||
if (!isIncomingOnly) startServiceWidget(),
|
||||
// ready && public
|
||||
// No need to show the guide if is custom client.
|
||||
- if (!isIncomingOnly) setupServerWidget(),
|
||||
+ //if (!isIncomingOnly) setupServerWidget(),
|
||||
],
|
||||
);
|
||||
|
||||
21
.github/workflows/generator-linux.yml
vendored
21
.github/workflows/generator-linux.yml
vendored
@@ -293,12 +293,13 @@ jobs:
|
||||
run: |
|
||||
sed -i -e 's|rs-ny.rustdesk.com|${{ inputs.server }}|' ./libs/hbb_common/src/config.rs
|
||||
sed -i -e 's|OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=|${{ inputs.key }}|' ./libs/hbb_common/src/config.rs
|
||||
sed -i -e 's|For faster connection, please set up your own server||' ./src/lang/en.rs
|
||||
sed -i -e '/const KEY:/,/};/d' ./src/common.rs
|
||||
sed -i -e '/let Ok(data) = sign::verify(&data, &pk)/,/};/d' ./src/common.rs
|
||||
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/allowCustom.diff
|
||||
git apply allowCustom.diff
|
||||
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/removeSetupServerTip.diff
|
||||
git apply removeSetupServerTip.diff
|
||||
echo -n "${{ inputs.custom }}" | cat > ./custom.txt
|
||||
sed -i '/intl:/a \ \ archive: ^3.6.1' ./flutter/pubspec.yaml
|
||||
sed -i -e '|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs
|
||||
sed -i -e 's|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs
|
||||
|
||||
- name: change url to custom
|
||||
if: fromJson(inputs.extras).urlLink != 'https://rustdesk.com'
|
||||
@@ -573,7 +574,8 @@ jobs:
|
||||
# old arch image does not make sense for arch since it is "arch" which always update to date
|
||||
# and failed to makepkg arm64 on x86_64
|
||||
- name: Patch archlinux PKGBUILD
|
||||
if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true'
|
||||
continue-on-error: true
|
||||
if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' && env.VERSION != 'master'
|
||||
run: |
|
||||
sed -i "s/x86_64/${{ matrix.job.arch }}/g" res/PKGBUILD
|
||||
if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then
|
||||
@@ -581,7 +583,8 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build archlinux package
|
||||
if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true'
|
||||
continue-on-error: true
|
||||
if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' && env.VERSION != 'master'
|
||||
uses: rustdesk-org/arch-makepkg-action@master
|
||||
with:
|
||||
packages:
|
||||
@@ -589,6 +592,8 @@ jobs:
|
||||
cd res && HBB=`pwd`/.. FLUTTER=1 makepkg -f
|
||||
|
||||
- name: Rename archlinux package
|
||||
continue-on-error: true
|
||||
if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' && env.VERSION != 'master'
|
||||
run: |
|
||||
cp ./res/rustdesk-${{ env.VERSION }}-0-x86_64.pkg.tar.zst ./output/${{ inputs.filename }}.pkg.tar.zst
|
||||
|
||||
@@ -599,7 +604,7 @@ jobs:
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.deb" -F "uuid=${{ inputs.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.rpm" -F "uuid=${{ inputs.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}-suse.rpm" -F "uuid=${{ inputs.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.pkg.tar.zst" -F "uuid=${{ inputs.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.pkg.tar.zst" -F "uuid=${{ inputs.uuid }}" ${{ secrets.GENURL }}/save_custom_client || true
|
||||
|
||||
- name: send file to api server
|
||||
if: ${{ fromJson(inputs.extras).rdgen == 'false' }}
|
||||
@@ -608,7 +613,7 @@ jobs:
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.deb" ${{ inputs.apiServer }}/api/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.rpm" ${{ inputs.apiServer }}/api/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}-suse.rpm" ${{ inputs.apiServer }}/api/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.pkg.tar.zst" ${{ inputs.apiServer }}/api/save_custom_client
|
||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ fromJson(inputs.extras).token }}" -F "file=@./output/${{ inputs.filename }}.pkg.tar.zst" ${{ inputs.apiServer }}/api/save_custom_client || true
|
||||
|
||||
- name: Report Status
|
||||
uses: fjogeleit/http-request-action@v1
|
||||
|
||||
9
.github/workflows/generator-macos.yml
vendored
9
.github/workflows/generator-macos.yml
vendored
@@ -238,11 +238,12 @@ jobs:
|
||||
|
||||
sed -i -e 's|rs-ny.rustdesk.com|${{ inputs.server }}|' ./libs/hbb_common/src/config.rs
|
||||
sed -i -e 's|OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=|${{ inputs.key }}|' ./libs/hbb_common/src/config.rs
|
||||
sed -i -e 's|For faster connection, please set up your own server||' ./src/lang/en.rs
|
||||
sed -i -e '|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs
|
||||
sed -i -e 's|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs
|
||||
|
||||
sed -i '' -e '/const KEY:/,/};/d' ./src/common.rs
|
||||
sed -i '' -e '/let Ok(data) = sign::verify(&data, &pk)/,/};/d' ./src/common.rs
|
||||
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/allowCustom.diff
|
||||
git apply allowCustom.diff
|
||||
wget https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/removeSetupServerTip.diff
|
||||
git apply removeSetupServerTip.diff
|
||||
|
||||
# Update pubspec.yaml with proper YAML formatting
|
||||
cp ./flutter/pubspec.yaml ./flutter/pubspec.yaml.bak
|
||||
|
||||
19
.github/workflows/generator-windows.yml
vendored
19
.github/workflows/generator-windows.yml
vendored
@@ -228,19 +228,24 @@ jobs:
|
||||
sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/mobile/pages/connection_page.dart
|
||||
sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./src/ui/index.tis
|
||||
|
||||
- name: allow custom.txt
|
||||
- name: set server, key, and apiserver
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i -e 's|rs-ny.rustdesk.com|${{ inputs.server }}|' ./libs/hbb_common/src/config.rs
|
||||
sed -i -e 's|OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=|${{ inputs.key }}|' ./libs/hbb_common/src/config.rs
|
||||
sed -i -e 's|For faster connection, please set up your own server||' ./src/lang/en.rs
|
||||
sed -i -e '/const KEY:/,/};/d' ./src/common.rs
|
||||
sed -i -e '/let Ok(data) = sign::verify(&data, &pk)/,/};/d' ./src/common.rs
|
||||
sed -i -e '|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs
|
||||
sed -i -e 's|https://admin.rustdesk.com|${{ inputs.apiServer }}|' ./src/common.rs
|
||||
# ./flutter/pubspec.yaml
|
||||
sed -i '/intl:/a \ \ archive: ^3.6.1' ./flutter/pubspec.yaml
|
||||
|
||||
- name: allow custom.txt
|
||||
continue-on-error: true
|
||||
run: |
|
||||
Invoke-WebRequest -Uri https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/allowCustom.diff -OutFile allowCustom.diff
|
||||
git apply allowCustom.diff
|
||||
Invoke-WebRequest -Uri https://raw.githubusercontent.com/bryangerlach/rdgen/refs/heads/master/.github/patches/removeSetupServerTip.diff -OutFile removeSetupServerTip.diff
|
||||
git apply removeSetupServerTip.diff
|
||||
|
||||
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v1
|
||||
@@ -525,6 +530,8 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
Expand-Archive -Path ./rustdesk/signed_files.zip -DestinationPath ./rustdesk/ -Force
|
||||
Remove-Item ./rustdesk/unsigned_files.zip
|
||||
Remove-Item ./rustdesk/signed_files.zip
|
||||
|
||||
|
||||
- name: Create custom.txt file
|
||||
@@ -596,6 +603,8 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
Expand-Archive -Path ./SignOutput/signed_files.zip -DestinationPath ./SignOutput/ -Force
|
||||
Remove-Item ./SignOutput/unsigned_files.zip
|
||||
Remove-Item ./SignOutput/signed_files.zip
|
||||
|
||||
- name: rename rustdesk.exe to filename.exe
|
||||
run: |
|
||||
|
||||
@@ -4,7 +4,7 @@ from PIL import Image
|
||||
class GenerateForm(forms.Form):
|
||||
#Platform
|
||||
platform = forms.ChoiceField(choices=[('windows','Windows'),('linux','Linux (currently unavailable)'),('android','Android'),('macos','macOS')], initial='windows')
|
||||
version = forms.ChoiceField(choices=[('1.3.8','nightly'),('1.3.7','1.3.7'),('1.3.6','1.3.6'),('1.3.5','1.3.5'),('1.3.4','1.3.4'),('1.3.3','1.3.3')], initial='1.3.7')
|
||||
version = forms.ChoiceField(choices=[('master','nightly'),('1.3.8','1.3.8'),('1.3.7','1.3.7'),('1.3.6','1.3.6'),('1.3.5','1.3.5'),('1.3.4','1.3.4'),('1.3.3','1.3.3')], initial='1.3.8')
|
||||
delayFix = forms.BooleanField(initial=True, required=False)
|
||||
|
||||
#General
|
||||
@@ -82,23 +82,24 @@ class GenerateForm(forms.Form):
|
||||
def clean_iconfile(self):
|
||||
print("checking icon")
|
||||
image = self.cleaned_data['iconfile']
|
||||
try:
|
||||
# Open the image using Pillow
|
||||
img = Image.open(image)
|
||||
if image:
|
||||
try:
|
||||
# Open the image using Pillow
|
||||
img = Image.open(image)
|
||||
|
||||
# Check if the image is a PNG (optional, but good practice)
|
||||
if img.format != 'PNG':
|
||||
raise forms.ValidationError("Only PNG images are allowed.")
|
||||
# Check if the image is a PNG (optional, but good practice)
|
||||
if img.format != 'PNG':
|
||||
raise forms.ValidationError("Only PNG images are allowed.")
|
||||
|
||||
# Get image dimensions
|
||||
width, height = img.size
|
||||
# Get image dimensions
|
||||
width, height = img.size
|
||||
|
||||
# Check for square dimensions
|
||||
if width != height:
|
||||
raise forms.ValidationError("Custom App Icon dimensions must be square.")
|
||||
# Check for square dimensions
|
||||
if width != height:
|
||||
raise forms.ValidationError("Custom App Icon dimensions must be square.")
|
||||
|
||||
return image
|
||||
except OSError: # Handle cases where the uploaded file is not a valid image
|
||||
raise forms.ValidationError("Invalid icon file.")
|
||||
except Exception as e: # Catch any other image processing errors
|
||||
raise forms.ValidationError(f"Error processing icon: {e}")
|
||||
return image
|
||||
except OSError: # Handle cases where the uploaded file is not a valid image
|
||||
raise forms.ValidationError("Invalid icon file.")
|
||||
except Exception as e: # Catch any other image processing errors
|
||||
raise forms.ValidationError(f"Error processing icon: {e}")
|
||||
@@ -80,6 +80,8 @@ def generator_view(request):
|
||||
filename = filename.replace(" ","_")
|
||||
else:
|
||||
filename = "rustdesk"
|
||||
if not all(char.isascii() for char in appname):
|
||||
appname = "rustdesk"
|
||||
myuuid = str(uuid.uuid4())
|
||||
protocol = _settings.PROTOCOL
|
||||
host = request.get_host()
|
||||
@@ -190,28 +192,6 @@ def generator_view(request):
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/generator-macos.yml/dispatches'
|
||||
else:
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/generator-windows.yml/dispatches'
|
||||
####changes were made to use hbb_common as a submodule in version 1.3.7, so if 1.3.3 through 1.3.6, use:
|
||||
if version == '1.3.3' or version == '1.3.4' or version == '1.3.5' or version == '1.3.6':
|
||||
if platform == 'windows':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre137-generator-windows.yml/dispatches'
|
||||
elif platform == 'linux':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre137-generator-linux.yml/dispatches'
|
||||
elif platform == 'android':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre137-generator-android.yml/dispatches'
|
||||
elif platform == 'macos':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre137-generator-macos.yml/dispatches'
|
||||
else:
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre137-generator-windows.yml/dispatches'
|
||||
####breaking changes were made in 1.3.3 version, so if 1.3.2 or lower, use:
|
||||
if version == '1.3.2' or version == '1.3.1' or version == '1.3.0':
|
||||
if platform == 'windows':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre133-generator-windows.yml/dispatches'
|
||||
elif platform == 'linux':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre133-generator-linux.yml/dispatches'
|
||||
elif platform == 'android':
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre133-generator-android.yml/dispatches'
|
||||
else:
|
||||
url = 'https://api.github.com/repos/'+_settings.GHUSER+'/'+_settings.REPONAME+'/actions/workflows/pre133-generator-windows.yml/dispatches'
|
||||
|
||||
#url = 'https://api.github.com/repos/'+_settings.GHUSER+'/rustdesk/actions/workflows/test.yml/dispatches'
|
||||
data = {
|
||||
|
||||
49
setup.md
49
setup.md
@@ -1,4 +1,35 @@
|
||||
## To fully host the client generator yourself, you will need to following:
|
||||
## Host the rdgen server with docker
|
||||
|
||||
1. First you will need to fork this repo on github
|
||||
2. Next, setup a 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 access, select Only select repositories, then pick your
|
||||
rdgen repo
|
||||
* give Read and Write access to actions and workflows
|
||||
* You might have to go to: https://github.com/USERNAME/rdgen/actions and hit green Enable Actions button so it works.
|
||||
3. Next, login to your Github account, go to your rdgen repo page (https://github.com/USERNAME/rdgen)
|
||||
* Click on Settings
|
||||
* In the left pane, click on Secrets and variables, then click Actions
|
||||
* Now click New repository secret
|
||||
* Set the Name to GENURL
|
||||
* Set the Secret to https://rdgen.hostname.com (or whatever your server will be accessed from)
|
||||
4. Now download the docker-compose.yml file and fill in the environment variables:
|
||||
* SECRET_KEY="your secret key" - generate a secret key by running: ```python3 -c 'import secrets; print(secrets.token_hex(100))'```
|
||||
* GHUSER="your github username"
|
||||
* GHBEARER="your fine-grained access token"
|
||||
* PROTOCOL="https" *optional - defaults to "https", change to "http" if you need to
|
||||
* REPONAME="rdgen" *optional - defaults to "rdgen", change this if you renamed the repo when you forked it
|
||||
5. Now just run ```docker compose up -d```
|
||||
|
||||
|
||||
## Host manually:
|
||||
|
||||
1. A Github account with a fork of this repo
|
||||
2. A Github fine-grained access token with permissions for your rdgen
|
||||
@@ -24,20 +55,6 @@
|
||||
* GENURL="example.com:8000" *this is the domain and port that you are
|
||||
running rdgen on, needs to be accessible on the internet, depending
|
||||
on how you have this setup the port may not be needed
|
||||
* optional github secrets (for signing the code):
|
||||
* WINDOWS_PFX_BASE64
|
||||
* WINDOWS_PFX_PASSWORD
|
||||
* WINDOWS_PFX_SHA1_THUMBPRINT
|
||||
|
||||
|
||||
## A few notes:
|
||||
|
||||
* If you change your repository name, make sure to change the url on lines
|
||||
172-203 of views.py to reflect the change
|
||||
* If you are running on http instead of https, make sure to make the change on
|
||||
line 75 of views.py
|
||||
|
||||
## To run rdgen on your server without docker:
|
||||
|
||||
```
|
||||
# Open to the directory you want to install rdgen (change /opt to wherever you want)
|
||||
@@ -69,7 +86,7 @@ open your web browser to yourdomain:8000
|
||||
|
||||
use nginx, caddy, traefik, etc. for ssl reverse proxy
|
||||
|
||||
## To autostart the server on boot, you can set up a systemd service called rdgen.service
|
||||
### To autostart the server on boot, you can set up a systemd service called rdgen.service
|
||||
|
||||
replace user, group, and port if you need to replace /opt with wherever you
|
||||
have installed rdgen save the following file as
|
||||
|
||||
Reference in New Issue
Block a user