Initial commit: rdgen with GitHub/Gitea dual build backend

This commit is contained in:
naeeo
2026-09-29 19:56:02 +08:00
commit a3686a49d4
103 changed files with 20007 additions and 0 deletions
+440
View File
@@ -0,0 +1,440 @@
import re
from django import forms
from django.contrib.auth.models import User
from PIL import Image
from . import versions as rdeskVersions
# 应用名/公司名会被插入到单/双引号包裹的 bash sed 脚本中:& \ | 会导致替换
# 静默失败,' " $ ` 会破坏 shell 引号,CR/LF 会破坏 sed 寻址。
UNSAFE_NAME_CHARS = re.compile(r'[&\\|\'"$`\r\n]')
UNSAFE_NAME_MESSAGE = "包含构建脚本不支持的字符(& \\ | ' \" $ ` 或换行符)。"
# 统一的控件样式:文本/数字/下拉/文件输入框 .form-control,复选/单选 .form-check-input
# 注意:Django 5.x 中 NumberInput 直接继承 Input(而非 TextInput),必须单独列出
_CONTROL_WIDGETS = (
forms.TextInput, forms.NumberInput, forms.PasswordInput, forms.EmailInput,
forms.Select, forms.Textarea, forms.FileInput,
)
def apply_control_classes(form):
for field in form.fields.values():
widget = field.widget
if isinstance(widget, _CONTROL_WIDGETS):
widget.attrs.setdefault('class', 'form-control')
elif isinstance(widget, forms.CheckboxInput):
widget.attrs.setdefault('class', 'form-check-input')
class GenerateForm(forms.Form):
sh_secret_field = forms.CharField(required=False, widget=forms.HiddenInput)
# 平台与版本
platform = forms.ChoiceField(
label="目标平台",
choices=[
('windows', 'Windows 64 位'),
('windows-x86', 'Windows 32 位'),
('linux', 'Linux'),
('android', 'Android'),
('macos', 'macOS'),
],
initial='windows',
)
version = forms.ChoiceField(
label="RustDesk 版本",
choices=[('master', '开发版(nightly 每夜构建)')] + [(v, v) for v in rdeskVersions.FALLBACK_VERSIONS],
initial='1.4.9',
help_text=(
"'master' 为开发版(每夜构建),包含最新特性,但稳定性可能不如正式版本。"
"正式版列表自动取自 GitHub 官方仓库 rustdesk/rustdesk,跟随官方发版更新。"
),
)
delayFix = forms.BooleanField(label="修复使用第三方 API 时的连接延迟问题", initial=True, required=False)
# 常规设置
exename = forms.CharField(
label="配置名称",
required=True,
help_text="仅限英文、数字与下划线,不含空格;将同时作为生成文件的名称。",
)
appname = forms.CharField(
label="自定义应用名称",
required=False,
help_text="留空则使用默认名称 rustdesk。",
)
direction = forms.ChoiceField(
label="连接方向",
widget=forms.RadioSelect,
choices=[
('incoming', '仅受控(只能被他人远程连接)'),
('outgoing', '仅主控(只能主动连接他人)'),
('both', '主控 / 受控双向(默认)'),
],
initial='both',
)
installation = forms.ChoiceField(
label="是否允许安装",
choices=[
('installationY', '允许安装(默认)'),
('installationN', '禁止安装(绿色单文件运行)'),
],
initial='installationY',
)
settings = forms.ChoiceField(
label="是否允许修改设置",
choices=[
('settingsY', '允许修改设置(默认)'),
('settingsN', '禁止修改设置'),
],
initial='settingsY',
)
androidappid = forms.CharField(
label="自定义 Android 应用 ID",
required=False,
help_text="替换默认 com.carriez.flutter_hbb,留空使用默认值。",
)
# 自定义服务器
serverIP = forms.CharField(
label="ID / 中继服务器地址",
required=False,
help_text="填写自建服务器的主机名或 IP。",
)
serverPort = forms.CharField(label="服务器端口", required=False)
apiServer = forms.CharField(
label="API 服务器地址",
required=False,
help_text="留空时默认使用「服务器地址:21114」。",
)
key = forms.CharField(label="公钥(Key)", required=False)
urlLink = forms.CharField(
label="自定义官网链接",
required=False,
help_text="替换默认的 https://rustdesk.com。",
)
downloadLink = forms.CharField(
label="更新下载链接",
required=False,
help_text="替换默认的 https://rustdesk.com/download。",
)
compname = forms.CharField(
label="版权公司名称",
required=False,
help_text="替换默认的 Purslane Tech Pte. Ltd.。",
)
# 外观
iconfile = forms.FileField(
label="应用图标",
required=False,
widget=forms.FileInput(attrs={'accept': 'image/png'}),
help_text="PNG 格式,建议 256×256 正方形。",
)
logofile = forms.FileField(
label="应用 Logo",
required=False,
widget=forms.FileInput(attrs={'accept': 'image/png'}),
help_text="PNG 格式。",
)
privacyfile = forms.FileField(
label="隐私屏图片",
required=False,
widget=forms.FileInput(attrs={'accept': 'image/png'}),
help_text="PNG 格式。",
)
iconbase64 = forms.CharField(required=False, widget=forms.HiddenInput)
logobase64 = forms.CharField(required=False, widget=forms.HiddenInput)
privacybase64 = forms.CharField(required=False, widget=forms.HiddenInput)
theme = forms.ChoiceField(
label="主题",
choices=[('light', '浅色'), ('dark', '深色'), ('system', '跟随系统')],
initial='system',
)
themeDorO = forms.ChoiceField(
label="主题生效方式",
choices=[('default', '默认(允许客户端更改)'), ('override', '强制锁定(客户端无法更改)')],
initial='default',
)
# 安全
passApproveMode = forms.ChoiceField(
label="连接确认方式",
choices=[
('password', '仅通过密码接受会话'),
('click', '仅通过点击接受会话'),
('password-click', '密码或点击均可(默认)'),
],
initial='password-click',
)
permanentPassword = forms.CharField(
label="设置固定访问密码",
widget=forms.PasswordInput(),
required=False,
help_text="该密码为客户端默认密码,用户仍可在客户端自行修改。",
)
denyLan = forms.BooleanField(label="禁止局域网发现", initial=False, required=False)
enableDirectIP = forms.BooleanField(label="允许直接 IP 直连", initial=False, required=False)
autoClose = forms.BooleanField(label="用户无操作时自动断开入站会话", initial=False, required=False)
hidecm = forms.BooleanField(label="允许在远程画面中隐藏连接窗口", initial=False, required=False)
# 权限
permissionsDorO = forms.ChoiceField(
label="权限生效方式",
choices=[('default', '默认(用户可更改)'), ('override', '强制锁定(用户无法更改)')],
initial='default',
)
permissionsType = forms.ChoiceField(
label="权限预设",
choices=[('custom', '自定义'), ('full', '完全控制'), ('view', '仅查看(屏幕共享)')],
initial='custom',
)
enableKeyboard = forms.BooleanField(label="键盘 / 鼠标控制", initial=True, required=False)
enableClipboard = forms.BooleanField(label="剪贴板同步", initial=True, required=False)
enableFileTransfer = forms.BooleanField(label="文件传输", initial=True, required=False)
enableAudio = forms.BooleanField(label="音频传输", initial=True, required=False)
enableTCP = forms.BooleanField(label="TCP 隧道", initial=True, required=False)
enableRemoteRestart = forms.BooleanField(label="远程重启", initial=True, required=False)
enableRecording = forms.BooleanField(label="会话录制", initial=True, required=False)
enableBlockingInput = forms.BooleanField(label="屏蔽被控端键鼠输入", initial=True, required=False)
enableRemoteModi = forms.BooleanField(label="允许修改远程配置", initial=False, required=False)
enablePrinter = forms.BooleanField(label="远程打印机", initial=True, required=False)
enableCamera = forms.BooleanField(label="远程摄像头", initial=True, required=False)
enableTerminal = forms.BooleanField(label="远程终端", initial=True, required=False)
# 其他
removeWallpaper = forms.BooleanField(label="入站会话期间移除桌面壁纸", initial=True, required=False)
defaultManual = forms.CharField(
label="默认高级设置",
widget=forms.Textarea(attrs={'rows': 4}),
required=False,
help_text="每行一项,格式:键=值。",
)
overrideManual = forms.CharField(
label="强制高级设置",
widget=forms.Textarea(attrs={'rows': 4}),
required=False,
help_text="每行一项,格式:键=值;设置后客户端用户无法修改。",
)
# 定制功能
xOffline = forms.BooleanField(label="通讯录中离线设备显示 X 标记", initial=False, required=False)
removeNewVersionNotif = forms.BooleanField(label="移除新版本更新通知", initial=False, required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
apply_control_classes(self)
def clean_version(self):
# 视图会在绑定前把 choices 刷新为官方最新列表;这里再做宽松校验,
# 兼容「打开页面后列表刷新 / 官方最新 tag 尚未进列表」等窗口期。
value = self.cleaned_data.get('version', '')
if not rdeskVersions.is_valid_version(value):
raise forms.ValidationError("版本号无效,请从列表中选择或填写形如 1.4.9 的正式版本号。")
return value
def clean_iconfile(self):
image = self.cleaned_data['iconfile']
if image:
try:
img = Image.open(image)
if img.format != 'PNG':
raise forms.ValidationError("仅支持 PNG 格式的图标。")
width, height = img.size
if width != height:
raise forms.ValidationError("应用图标必须为正方形图片。")
return image
except forms.ValidationError:
raise
except OSError:
raise forms.ValidationError("图标文件无效或已损坏。")
except Exception as e:
raise forms.ValidationError(f"处理图标时出错:{e}")
def _reject_unsafe_name_chars(self, field):
value = self.cleaned_data.get(field, '')
if value and UNSAFE_NAME_CHARS.search(value):
raise forms.ValidationError(UNSAFE_NAME_MESSAGE)
return value
def clean_appname(self):
return self._reject_unsafe_name_chars('appname')
def clean_compname(self):
return self._reject_unsafe_name_chars('compname')
class ApiTokenForm(forms.Form):
name = forms.CharField(label="令牌名称", max_length=100, required=True,
help_text="建议按用途命名,例如:办公网构建机、CI 流水线。")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
apply_control_classes(self)
class GitHubSettingsForm(forms.Form):
"""后台「构建平台配置」表单(GitHub / Gitea)。
非密钥项直接显示生效值;密钥项不回显明文,留空表示不修改,
勾选「清除」则删除后台覆盖、回退到环境变量/默认值。
"""
# 构建平台
build_platform = forms.ChoiceField(
label="构建平台",
choices=[('github', 'GitHub Actions'), ('gitea', 'Gitea Actions(自建)')],
widget=forms.RadioSelect,
required=False,
)
# Gitea 连接
gitea_server_url = forms.CharField(
label="Gitea 服务器地址",
max_length=255,
required=False,
widget=forms.TextInput(attrs={'placeholder': 'https://gitea.example.com'}),
)
gitea_owner = forms.CharField(label="Gitea 仓库属主", max_length=100, required=False)
gitea_repo = forms.CharField(label="Gitea 仓库名称", max_length=100, required=False)
gitea_branch = forms.CharField(label="Gitea 工作流分支", max_length=100, required=False)
gitea_token = forms.CharField(
label="Gitea 访问令牌",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '已保存时留空表示不修改'}),
required=False,
)
clear_gitea_token = forms.BooleanField(label="清除后台保存的令牌", required=False)
gitea_proxy = forms.CharField(
label="Gitea 访问代理",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '内网直连通常留空,如 http://host:port'}),
required=False,
)
clear_gitea_proxy = forms.BooleanField(label="清除后台保存的代理", required=False)
# GitHub 基础连接
ghuser = forms.CharField(label="GitHub 用户名", max_length=100, required=False)
reponame = forms.CharField(label="仓库名称", max_length=100, required=False)
ghbranch = forms.CharField(label="工作流分支", max_length=100, required=False)
ghbearer = forms.CharField(
label="GitHub 访问令牌",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '已保存时留空表示不修改'}),
required=False,
)
clear_ghbearer = forms.BooleanField(label="清除后台保存的令牌", required=False)
# 回调与加密
genurl = forms.CharField(
label="本平台外部访问地址",
max_length=255,
required=False,
widget=forms.TextInput(attrs={'placeholder': 'https://rdgen.example.com'}),
)
protocol = forms.ChoiceField(
label="平台访问协议",
choices=[('https', 'https'), ('http', 'http')],
required=False,
)
zip_password = forms.CharField(
label="配置压缩包密码",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '已保存时留空表示不修改'}),
required=False,
)
clear_zip_password = forms.BooleanField(label="清除后台保存的密码", required=False)
# 高级
sh_secret = forms.CharField(
label="自托管 Runner 密钥",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '已保存时留空表示不修改'}),
required=False,
)
clear_sh_secret = forms.BooleanField(label="清除后台保存的密钥", required=False)
webhook_secret = forms.CharField(
label="回调校验密钥",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '已保存时留空表示不修改'}),
required=False,
)
clear_webhook_secret = forms.BooleanField(label="清除后台保存的密钥", required=False)
# 网络代理
gh_proxy = forms.CharField(
label="GitHub 访问代理",
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password', 'placeholder': '留空直连,如 http://127.0.0.1:7890'}),
required=False,
)
clear_gh_proxy = forms.BooleanField(label="清除后台保存的代理", required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
apply_control_classes(self)
class MaintenanceSettingsForm(forms.Form):
"""后台「维护设置」:构建任务定时清理与数据库定时备份。"""
build_retention_days = forms.IntegerField(
label="构建任务保留天数",
min_value=0,
max_value=3650,
initial=30,
help_text="超过该天数的已结束任务(成功 / 失败 / 取消)及安装包产物会被自动清理;0 表示永不自动清理。进行中的任务不会被清理。",
)
db_backup_enabled = forms.BooleanField(
label="启用数据库定时备份(每天自动备份一次)",
required=False,
initial=True,
)
db_backup_keep = forms.IntegerField(
label="数据库备份保留份数",
min_value=1,
max_value=100,
initial=7,
help_text="超出份数的最旧备份会被自动删除。备份文件保存在持久化数据卷中,可在下方列表下载。",
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
apply_control_classes(self)
class AdminUserCreateForm(forms.Form):
username = forms.CharField(label="用户名", max_length=150, required=True)
email = forms.EmailField(label="邮箱", required=False)
password = forms.CharField(label="登录密码", widget=forms.PasswordInput, required=True, min_length=8,
help_text="至少 8 位。")
password_confirm = forms.CharField(label="确认密码", widget=forms.PasswordInput, required=True)
is_staff = forms.BooleanField(label="设为管理员(可访问后台管理、查看所有任务)", required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
apply_control_classes(self)
def clean_username(self):
username = self.cleaned_data['username'].strip()
if User.objects.filter(username=username).exists():
raise forms.ValidationError("该用户名已存在。")
return username
def clean(self):
cleaned = super().clean()
if cleaned.get('password') and cleaned.get('password') != cleaned.get('password_confirm'):
self.add_error('password_confirm', "两次输入的密码不一致。")
return cleaned
class AdminUserEditForm(forms.Form):
email = forms.EmailField(label="邮箱", required=False)
is_active = forms.BooleanField(label="账号启用", required=False)
is_staff = forms.BooleanField(label="管理员权限(可访问后台管理)", required=False)
new_password = forms.CharField(
label="重置密码(留空表示不修改)",
widget=forms.PasswordInput,
required=False,
min_length=8,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
apply_control_classes(self)