mirror of
https://github.com/openwrt/luci.git
synced 2026-04-15 10:51:51 +00:00
luci-app-rustdesk-server: add new application
Add LuCI web interface for managing RustDesk Server on OpenWrt. RustDesk is an open-source remote desktop application, providing a self-hosted alternative to proprietary solutions like TeamViewer and AnyDesk. It enables secure remote access without relying on third-party servers. This package provides configuration for the RustDesk server components: - hbbs: ID/Rendezvous server for connection brokering - hbbr: Relay server for NAT traversal Features: - Modern JavaScript UI with live status polling - Service management (start/stop/restart/enable/disable) - Public key display with copy button and regeneration - Firewall rule auto-management based on port settings - Tabbed configuration for ID Server (hbbs) and Relay Server (hbbr) - Log viewer with auto-refresh - Input validation (paths, ports, CIDR notation) - i18n ready with POT template Signed-off-by: Guilherme Cardoso <luminoso+github@gmail.com>
This commit is contained in:
committed by
Paul Donald
parent
d074b87514
commit
3724867919
20
applications/luci-app-rustdesk-server/Makefile
Normal file
20
applications/luci-app-rustdesk-server/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=20250610
|
||||
PKG_RELEASE:=4
|
||||
PKG_NAME:=luci-app-rustdesk-server
|
||||
PKG_MAINTAINER:=Guilherme Cardoso <luminoso@gmail.com>
|
||||
|
||||
LUCI_TITLE:=LuCI support for RustDesk Server
|
||||
LUCI_DEPENDS:=+luci-base +rpcd +rpcd-mod-ucode
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/rustdesk-server
|
||||
endef
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
299
applications/luci-app-rustdesk-server/README.md
Normal file
299
applications/luci-app-rustdesk-server/README.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# luci-app-rustdesk-server
|
||||
|
||||
LuCI web interface for managing [RustDesk Server](https://github.com/rustdesk/rustdesk-server) on OpenWrt.
|
||||
|
||||
RustDesk is a full-featured open source remote control alternative to TeamViewer and AnyDesk. This LuCI application provides a web-based interface to configure and manage the self-hosted RustDesk server components (hbbs and hbbr) on OpenWrt routers.
|
||||
|
||||
## Features
|
||||
|
||||
- **Service Management** - Start/Stop/Restart services directly from the UI
|
||||
- **Boot Enable/Disable** - Toggle service startup at boot
|
||||
- **Status Monitoring** - Real-time status of HBBS and HBBR services with live polling
|
||||
- **Public Key Display** - View and copy the generated public key for client configuration
|
||||
- **Key Regeneration** - Regenerate encryption keys when needed
|
||||
- **Log Viewer** - View service logs with auto-refresh and auto-scroll features
|
||||
- **Firewall Hints** - Displays required ports for manual firewall configuration
|
||||
- **Tabbed Configuration** - Organized settings for ID Server (hbbs) and Relay Server (hbbr)
|
||||
- **Input Validation** - Validates paths, ports, and configuration values
|
||||
- **i18n Ready** - Full translation support with POT template
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
luci-app-rustdesk-server/
|
||||
├── Makefile # OpenWrt package build file
|
||||
├── htdocs/luci-static/resources/view/rustdesk-server/
|
||||
│ └── general.js # Main UI view (JavaScript)
|
||||
├── po/templates/
|
||||
│ └── rustdesk-server.pot # Translation template
|
||||
└── root/
|
||||
├── etc/
|
||||
│ ├── config/rustdesk-server # UCI configuration
|
||||
│ ├── init.d/rustdesk-server # procd init script
|
||||
│ └── uci-defaults/50-luci-rustdesk-server # First-run setup
|
||||
└── usr/share/
|
||||
├── luci/menu.d/luci-app-rustdesk-server.json # Menu entry
|
||||
└── rpcd/
|
||||
├── acl.d/luci-app-rustdesk-server.json # ACL permissions
|
||||
└── ucode/rustdesk-server.uc # RPC backend
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### OpenWrt Dependencies
|
||||
- OpenWrt 23.05 or later with LuCI installed
|
||||
- `luci-base` - LuCI core framework
|
||||
- `rpcd` - RPC daemon
|
||||
- `rpcd-mod-ucode` - ucode support for rpcd
|
||||
|
||||
### RustDesk Server Binaries
|
||||
The RustDesk server binaries (`hbbs`, `hbbr`) must be installed separately. They are **not included** in this package.
|
||||
|
||||
#### Installing RustDesk Server Binaries
|
||||
|
||||
1. **Download from GitHub Releases:**
|
||||
```bash
|
||||
# Check your architecture
|
||||
uname -m
|
||||
|
||||
# Download appropriate binaries from:
|
||||
# https://github.com/rustdesk/rustdesk-server/releases
|
||||
|
||||
# Example for aarch64:
|
||||
wget https://github.com/rustdesk/rustdesk-server/releases/download/1.1.11/rustdesk-server-linux-arm64v8.zip
|
||||
unzip rustdesk-server-linux-arm64v8.zip
|
||||
cp amd64/hbbs amd64/hbbr /usr/bin/
|
||||
chmod +x /usr/bin/hbbs /usr/bin/hbbr
|
||||
```
|
||||
|
||||
2. **Or build from source:**
|
||||
```bash
|
||||
# See https://github.com/rustdesk/rustdesk-server for build instructions
|
||||
```
|
||||
|
||||
3. **Verify installation:**
|
||||
```bash
|
||||
/usr/bin/hbbs --version
|
||||
/usr/bin/hbbr --version
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### From OpenWrt Package Repository
|
||||
```bash
|
||||
opkg update
|
||||
opkg install luci-app-rustdesk-server
|
||||
```
|
||||
|
||||
### From Source (Development)
|
||||
```bash
|
||||
# Clone the LuCI repository
|
||||
git clone https://github.com/openwrt/luci.git
|
||||
cd luci
|
||||
|
||||
# Build the package
|
||||
make package/luci-app-rustdesk-server/compile
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
1. Copy the application files to your OpenWrt device:
|
||||
```bash
|
||||
# Copy htdocs to /www
|
||||
cp -r htdocs/luci-static /www/luci-static/
|
||||
|
||||
# Copy root files
|
||||
cp -r root/* /
|
||||
|
||||
# Set permissions
|
||||
chmod +x /etc/init.d/rustdesk-server
|
||||
```
|
||||
|
||||
2. Reload rpcd to register the new RPC methods:
|
||||
```bash
|
||||
/etc/init.d/rpcd reload
|
||||
```
|
||||
|
||||
3. Clear LuCI cache:
|
||||
```bash
|
||||
rm -rf /tmp/luci-*
|
||||
```
|
||||
|
||||
4. Access the interface at: **Services → RustDesk Server**
|
||||
|
||||
## Configuration
|
||||
|
||||
### Binary Location
|
||||
The application expects `hbbs` and `hbbr` binaries to be installed in `/usr/bin`.
|
||||
|
||||
### Firewall Configuration
|
||||
Firewall rules must be configured manually in **Network → Firewall → Traffic Rules**. The application displays the required ports in the Service Status section.
|
||||
|
||||
The standard RustDesk port layout is:
|
||||
|
||||
| Port | Protocol | Service | Calculation |
|
||||
|------|----------|---------|-------------|
|
||||
| HBBS-1 | TCP | NAT type test | server_port - 1 |
|
||||
| HBBS | TCP/UDP | ID server / Hole punching | server_port |
|
||||
| HBBS+2 | TCP | Web client support | server_port + 2 |
|
||||
| HBBR | TCP | Relay server | relay_port |
|
||||
| HBBR+2 | TCP | Web client support | relay_port + 2 |
|
||||
|
||||
**Example:** With default ports (`server_port=21116` and `relay_port=21117`):
|
||||
- TCP ports: 21115, 21116, 21117, 21118, 21119
|
||||
- UDP port: 21116
|
||||
|
||||
### Logging
|
||||
Enable logging in General settings to write service output to `/var/log/rustdesk-server.log`. View logs in real-time using the Logs tab.
|
||||
|
||||
### Database Location
|
||||
The database is stored in `/tmp/rustdesk_db_v2.sqlite3`. This is a non-persistent location and will be cleared on reboot. This is intentional for embedded systems like OpenWrt where persistent storage may be limited.
|
||||
|
||||
## Client Configuration
|
||||
|
||||
After starting the service:
|
||||
|
||||
1. Go to the LuCI interface and note your router's IP address
|
||||
2. Copy the **Public Key** from the Service Status section
|
||||
3. In RustDesk client settings, configure:
|
||||
- **ID Server**: Your router's IP:21116 (or custom port if configured)
|
||||
- **Relay Server**: Your router's IP:21117 (or custom port if configured)
|
||||
- **Key**: The public key from step 2
|
||||
|
||||
## UCI Configuration Reference
|
||||
|
||||
The configuration is stored in `/etc/config/rustdesk-server`:
|
||||
|
||||
```uci
|
||||
config rustdesk-server
|
||||
option enabled '1' # Enable ID server (hbbs)
|
||||
option enabled_relay '1' # Enable Relay server (hbbr)
|
||||
|
||||
# HBBS options
|
||||
option server_port '21116' # ID server port
|
||||
option server_key '' # Custom key (optional)
|
||||
|
||||
# HBBR options
|
||||
option relay_port '21117' # Relay server port
|
||||
|
||||
# Environment variables
|
||||
option server_env_rust_log 'info'
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `/etc/config/rustdesk-server` | UCI configuration file |
|
||||
| `/etc/init.d/rustdesk-server` | procd init script |
|
||||
| `/etc/rustdesk/` | Key storage directory |
|
||||
| `/etc/rustdesk/id_ed25519.pub` | Public key (auto-generated) |
|
||||
| `/var/log/rustdesk-server.log` | Service log file (when enabled) |
|
||||
| `/usr/share/rpcd/ucode/rustdesk-server.uc` | RPC backend |
|
||||
| `/usr/share/luci/menu.d/luci-app-rustdesk-server.json` | Menu entry |
|
||||
| `/usr/share/rpcd/acl.d/luci-app-rustdesk-server.json` | ACL permissions |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service won't start
|
||||
1. **Check binaries exist:**
|
||||
```bash
|
||||
ls -la /usr/bin/hbbs /usr/bin/hbbr
|
||||
```
|
||||
2. **Verify binaries are executable:**
|
||||
```bash
|
||||
chmod +x /usr/bin/hbbs /usr/bin/hbbr
|
||||
```
|
||||
3. **Check system log:**
|
||||
```bash
|
||||
logread | grep rustdesk-server
|
||||
```
|
||||
4. **Verify at least one server is enabled** in the configuration
|
||||
|
||||
### Key not generated
|
||||
The public key (`id_ed25519.pub`) is generated automatically when HBBS starts for the first time. If missing:
|
||||
1. Ensure the key directory exists: `mkdir -p /etc/rustdesk`
|
||||
2. Start the service and wait a few seconds
|
||||
3. Check if key was created: `cat /etc/rustdesk/id_ed25519.pub`
|
||||
|
||||
### Firewall / Connection issues
|
||||
1. Verify firewall rules are configured in **Network → Firewall → Traffic Rules**
|
||||
2. Check that required ports are open (TCP: 21115-21119, UDP: 21116)
|
||||
3. Reload firewall:
|
||||
```bash
|
||||
/etc/init.d/firewall reload
|
||||
```
|
||||
4. Verify the service is running:
|
||||
```bash
|
||||
pidof hbbs hbbr
|
||||
```
|
||||
5. Check if ports are listening:
|
||||
```bash
|
||||
netstat -tlnp | grep -E '2111[5-9]'
|
||||
```
|
||||
6. Test connectivity from client:
|
||||
```bash
|
||||
nc -zv <router-ip> 21116
|
||||
```
|
||||
|
||||
### RPC errors in browser console
|
||||
1. Reload rpcd:
|
||||
```bash
|
||||
/etc/init.d/rpcd reload
|
||||
```
|
||||
2. Clear LuCI cache:
|
||||
```bash
|
||||
rm -rf /tmp/luci-*
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Building Translations
|
||||
```bash
|
||||
# Scan for translatable strings
|
||||
./build/i18n-scan.pl applications/luci-app-rustdesk-server > applications/luci-app-rustdesk-server/po/templates/rustdesk-server.pot
|
||||
|
||||
# Update existing translations
|
||||
./build/i18n-update.pl applications/luci-app-rustdesk-server
|
||||
```
|
||||
|
||||
### Testing Changes
|
||||
1. Make changes to files
|
||||
2. Copy to device and reload rpcd
|
||||
3. Clear browser cache and LuCI cache
|
||||
4. Refresh the page
|
||||
|
||||
## Security Considerations
|
||||
|
||||
This application implements multiple layers of input validation and sanitization to prevent shell injection attacks:
|
||||
|
||||
### Frontend Validation (JavaScript)
|
||||
All user inputs are validated before being saved to UCI configuration:
|
||||
|
||||
| Field Type | Validation |
|
||||
|------------|------------|
|
||||
| Ports | Numeric only, range 1-65535, supports ranges and comma-separated lists |
|
||||
| CIDR masks | Strict IP/prefix format validation |
|
||||
| Keys | Alphanumeric and base64 characters only (`A-Za-z0-9+/=`) |
|
||||
| URLs | Must start with `http://` or `https://`, no shell metacharacters |
|
||||
| Paths | Must start with `/`, no shell metacharacters (`;|&$\`(){}[]<>'"\\!`) |
|
||||
| Server lists | Alphanumeric, dots, colons, commas, hyphens, underscores only |
|
||||
| Numeric fields | Use LuCI's built-in `uinteger` datatype |
|
||||
|
||||
### Backend Validation (Init Script)
|
||||
The init script (`/etc/init.d/rustdesk-server`) includes comprehensive validation functions that re-validate all configuration values before using them in shell commands:
|
||||
|
||||
- `validate_numeric()` - Ensures values contain only digits
|
||||
- `validate_port()` - Validates port range (1-65535)
|
||||
- `validate_path()` - Checks for shell metacharacters and requires leading `/`
|
||||
- `validate_url()` - Validates URL format and rejects dangerous characters
|
||||
- `validate_key()` - Allows only base64-safe characters
|
||||
- `validate_server_list()` - Allows only hostname/IP-safe characters
|
||||
- `validate_cidr()` - Allows only digits, dots, and slash
|
||||
- `validate_log_level()` - Whitelist of valid log levels
|
||||
|
||||
Invalid values are rejected and logged with warnings to syslog.
|
||||
|
||||
### RPC Backend Validation (ucode)
|
||||
The RPC backend (`rustdesk-server.uc`) validates:
|
||||
- `service_action`: Whitelist of allowed actions (`start`, `stop`, `restart`, `reload`, `enable`, `disable`)
|
||||
- `get_log` lines parameter: Clamped to range 10-1000
|
||||
@@ -0,0 +1,604 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require poll';
|
||||
|
||||
/*
|
||||
* Constants - only frontend-relevant values
|
||||
*/
|
||||
const CONSTANTS = {
|
||||
// Default ports (used in placeholders)
|
||||
HBBS_DEFAULT_PORT: '21116',
|
||||
HBBR_DEFAULT_PORT: '21117',
|
||||
|
||||
// Polling interval (seconds)
|
||||
POLL_INTERVAL: 3,
|
||||
|
||||
// Colors for status display
|
||||
COLORS: {
|
||||
SUCCESS: 'green',
|
||||
ERROR: 'red',
|
||||
MUTED: 'gray',
|
||||
INFO: '#888'
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* RPC declarations
|
||||
*/
|
||||
const callGetStatus = rpc.declare({
|
||||
object: 'luci.rustdesk-server',
|
||||
method: 'get_status'
|
||||
});
|
||||
|
||||
const callGetPublicKey = rpc.declare({
|
||||
object: 'luci.rustdesk-server',
|
||||
method: 'get_public_key'
|
||||
});
|
||||
|
||||
const callGetVersion = rpc.declare({
|
||||
object: 'luci.rustdesk-server',
|
||||
method: 'get_version'
|
||||
});
|
||||
|
||||
const callRegenerateKey = rpc.declare({
|
||||
object: 'luci.rustdesk-server',
|
||||
method: 'regenerate_key'
|
||||
});
|
||||
|
||||
const callServiceAction = rpc.declare({
|
||||
object: 'luci.rustdesk-server',
|
||||
method: 'service_action',
|
||||
params: ['action']
|
||||
});
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
function handleAction(action) {
|
||||
return fs.exec_direct('/etc/init.d/rustdesk-server', [action]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shell metacharacters regex - prevents command injection
|
||||
*/
|
||||
const SHELL_METACHARS = /[;&|$`(){}[\]<>'"\\!]/;
|
||||
|
||||
/**
|
||||
* Validates a safe string value (no shell metacharacters)
|
||||
* Used for URL and path validation
|
||||
* @param {string} value - The value to check
|
||||
* @returns {boolean|string} True if safe, error message if not
|
||||
*/
|
||||
function containsShellMetachars(value) {
|
||||
if (SHELL_METACHARS.test(value))
|
||||
return _('Invalid characters detected');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a key string (alphanumeric and base64 characters only)
|
||||
* @param {string} section_id - The section ID
|
||||
* @param {string} value - The key value
|
||||
* @returns {boolean|string} True if valid, error message if invalid
|
||||
*/
|
||||
function validateKey(section_id, value) {
|
||||
if (!value || value.length === 0)
|
||||
return true;
|
||||
if (/[^A-Za-z0-9+/=]/.test(value))
|
||||
return _('Invalid characters.') + ' ' + _('Only alphanumeric and base64 characters (+/=) allowed.');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a URL (must start with http:// or https://)
|
||||
* @param {string} section_id - The section ID
|
||||
* @param {string} value - The URL value
|
||||
* @returns {boolean|string} True if valid, error message if invalid
|
||||
*/
|
||||
function validateURL(section_id, value) {
|
||||
if (!value || value.length === 0)
|
||||
return true;
|
||||
const shellCheck = containsShellMetachars(value);
|
||||
if (shellCheck !== true)
|
||||
return shellCheck;
|
||||
if (!/^https?:\/\//.test(value))
|
||||
return _('URL must start with http:// or https://');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a status indicator HTML string
|
||||
* @param {boolean} isActive - Whether the status is active/good
|
||||
* @param {string} activeText - Text to show when active
|
||||
* @param {string} inactiveText - Text to show when inactive
|
||||
* @param {string} [suffix] - Optional suffix to append
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
function createStatusIndicator(isActive, activeText, inactiveText, suffix) {
|
||||
const color = isActive ? CONSTANTS.COLORS.SUCCESS : CONSTANTS.COLORS.ERROR;
|
||||
const symbol = isActive ? '●' : '○';
|
||||
const text = isActive ? activeText : inactiveText;
|
||||
let html = '<span style="color:' + color + '">' + symbol + ' ' + text + '</span>';
|
||||
|
||||
if (suffix) {
|
||||
html += ' <small style="color:' + CONSTANTS.COLORS.INFO + '">' + suffix + '</small>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a checkmark status indicator
|
||||
* @param {boolean} isActive - Whether the status is active/good
|
||||
* @param {string} activeText - Text to show when active
|
||||
* @param {string} inactiveText - Text to show when inactive
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
function createCheckIndicator(isActive, activeText, inactiveText) {
|
||||
const color = isActive ? CONSTANTS.COLORS.SUCCESS : CONSTANTS.COLORS.MUTED;
|
||||
const symbol = isActive ? '✓' : '✗';
|
||||
const text = isActive ? activeText : inactiveText;
|
||||
return '<span style="color:' + color + '">' + symbol + ' ' + text + '</span>';
|
||||
}
|
||||
|
||||
// Track if key exists globally for button state
|
||||
let keyExistsGlobal = false;
|
||||
// Track if any server is enabled in config
|
||||
let anyServerEnabledGlobal = false;
|
||||
// Track boot enabled state
|
||||
let bootEnabledGlobal = false;
|
||||
|
||||
return view.extend({
|
||||
render() {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('rustdesk-server', _('RustDesk Server'),
|
||||
_('Remote Desktop Software Server configuration.') +
|
||||
' <a href="https://github.com/rustdesk/rustdesk-server" target="_blank">' + _('Server') + '</a> | ' +
|
||||
'<a href="https://github.com/rustdesk/rustdesk" target="_blank">' + _('Client') + '</a>');
|
||||
|
||||
/*
|
||||
Firewall Notice
|
||||
*/
|
||||
s = m.section(form.NamedSection, 'firewall_info');
|
||||
s.render = () => E('div', { 'class': 'alert-message notice' }, [
|
||||
E('h4', {}, _('Firewall Configuration Required')),
|
||||
E('p', {}, _('Required ports (when using default settings): TCP 21115-21119, UDP 21116.')),
|
||||
E('p', {}, _('Configure in Network → Firewall → Traffic Rules.'))
|
||||
]);
|
||||
|
||||
/*
|
||||
Status Section (custom render)
|
||||
*/
|
||||
s = m.section(form.NamedSection, 'global');
|
||||
s.render = L.bind((view, section_id) => {
|
||||
return E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', _('Service Status')),
|
||||
|
||||
// Status Table for HBBS and HBBR
|
||||
E('table', { 'class': 'table cbi-section-table', 'id': 'status_table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Component')),
|
||||
E('th', { 'class': 'th' }, _('Service Status')),
|
||||
E('th', { 'class': 'th' }, _('Binary')),
|
||||
E('th', { 'class': 'th' }, _('Enabled'))
|
||||
]),
|
||||
E('tr', { 'class': 'tr', 'id': 'hbbs_row' }, [
|
||||
E('td', { 'class': 'td' }, _('HBBS (ID Server)')),
|
||||
E('td', { 'class': 'td', 'id': 'hbbs_status' }, '-'),
|
||||
E('td', { 'class': 'td', 'id': 'hbbs_binary' }, '-'),
|
||||
E('td', { 'class': 'td', 'id': 'hbbs_enabled' }, '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr', 'id': 'hbbr_row' }, [
|
||||
E('td', { 'class': 'td' }, _('HBBR (Relay Server)')),
|
||||
E('td', { 'class': 'td', 'id': 'hbbr_status' }, '-'),
|
||||
E('td', { 'class': 'td', 'id': 'hbbr_binary' }, '-'),
|
||||
E('td', { 'class': 'td', 'id': 'hbbr_enabled' }, '-')
|
||||
])
|
||||
]),
|
||||
|
||||
// Public Key with Regenerate button inline
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Public Key')),
|
||||
E('div', { 'class': 'cbi-value-field', 'style': 'display: flex; align-items: center; flex-wrap: wrap; gap: 8px;' }, [
|
||||
E('span', { 'id': 'public_key', 'style': 'word-break: break-all; flex: 1; min-width: 200px;' }, '-'),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-negative',
|
||||
'id': 'regenerate_key_btn',
|
||||
'disabled': true,
|
||||
'title': _('Regenerate the key pair (requires existing key)'),
|
||||
'click': (ev) => {
|
||||
if (!keyExistsGlobal) {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Cannot regenerate: No public key exists yet.') + ' ' + _('Start the service first to generate the initial key.')), 5000, 'warning');
|
||||
return;
|
||||
}
|
||||
if (!confirm(_('This will regenerate the key pair and restart the service.') + ' ' + _('All existing clients will need to be reconfigured.') + ' ' + _('Continue?'))) {
|
||||
return;
|
||||
}
|
||||
ev.target.disabled = true;
|
||||
ev.target.textContent = _('Regenerating...');
|
||||
|
||||
L.resolveDefault(callRegenerateKey(), {}).then((res) => {
|
||||
if (res && res.success) {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Keys deleted. Starting service to generate new keys...')), 5000, 'notice');
|
||||
// Use RPC to start service for reliable execution
|
||||
// Add small delay to ensure service has fully stopped
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
}).then(() => L.resolveDefault(callServiceAction('start'), {}));
|
||||
} else {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Key regeneration failed: ') + (res.message || 'Could not delete keys')), 5000, 'error');
|
||||
throw new Error('Regeneration failed');
|
||||
}
|
||||
}).then((startRes) => {
|
||||
if (startRes && startRes.success) {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Service started with new key')), 5000, 'notice');
|
||||
} else if (startRes) {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Service start may have failed. Check status above.')), 5000, 'warning');
|
||||
}
|
||||
}).catch((err) => {
|
||||
if (err.message !== 'Regeneration failed') {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Error: ') + err.message), 5000, 'error');
|
||||
}
|
||||
}).finally(() => {
|
||||
const btn = document.getElementById('regenerate_key_btn');
|
||||
if (btn) {
|
||||
btn.disabled = !keyExistsGlobal;
|
||||
btn.textContent = _('Regenerate Key');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, _('Regenerate Key'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Boot at startup toggle
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Start at Boot')),
|
||||
E('div', { 'class': 'cbi-value-field', 'style': 'display: flex; align-items: center; gap: 12px;' }, [
|
||||
E('span', { 'id': 'boot_status' }, '-'),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'id': 'enable_boot_btn',
|
||||
'click': (ev) => {
|
||||
const action = bootEnabledGlobal ? 'disable' : 'enable';
|
||||
ev.target.disabled = true;
|
||||
ev.target.textContent = _('Processing...');
|
||||
|
||||
L.resolveDefault(callServiceAction(action), {}).then((res) => {
|
||||
if (res && res.success) {
|
||||
bootEnabledGlobal = !bootEnabledGlobal;
|
||||
ui.addTimeLimitedNotification(null, E('p',
|
||||
bootEnabledGlobal ? _('Service enabled at boot') : _('Service disabled at boot')
|
||||
), 5000, 'notice');
|
||||
} else {
|
||||
const errMsg = res.error || res.message || (res.exit_code !== undefined ? 'Exit code: ' + res.exit_code : JSON.stringify(res));
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Failed: ') + errMsg), 5000, 'error');
|
||||
}
|
||||
}).catch((err) => {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Error: ') + err.message), 5000, 'error');
|
||||
}).finally(() => {
|
||||
const btn = document.getElementById('enable_boot_btn');
|
||||
const statusEl = document.getElementById('boot_status');
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = bootEnabledGlobal ? _('Disable') : _('Enable');
|
||||
}
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = createCheckIndicator(bootEnabledGlobal, _('Enabled'), _('Disabled'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}, _('Loading...'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Service Control section
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Service Control')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('div', { 'style': 'margin-bottom: 8px;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-apply',
|
||||
'id': 'start_btn',
|
||||
'disabled': true,
|
||||
'title': _('Enable ID Server or Relay Server first'),
|
||||
'click': (ev) => {
|
||||
if (!anyServerEnabledGlobal) {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Cannot start service: Enable the ID Server or Relay Server in the configuration first.') + ' ' + _('Check "Enable ID Server" or "Enable Relay Server" below and click "Save & Apply".')), 5000, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ev.target.disabled = true;
|
||||
handleAction('start').then(() => {
|
||||
ev.target.disabled = !anyServerEnabledGlobal;
|
||||
}).catch((err) => {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Failed to start service: ') + err.message), 5000, 'error');
|
||||
ev.target.disabled = !anyServerEnabledGlobal;
|
||||
});
|
||||
}
|
||||
}, _('Start')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'click': (ev) => {
|
||||
ev.target.disabled = true;
|
||||
handleAction('stop').then(() => {
|
||||
ev.target.disabled = false;
|
||||
}).catch((err) => {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Failed to stop service: ') + err.message), 5000, 'error');
|
||||
ev.target.disabled = false;
|
||||
});
|
||||
}
|
||||
}, _('Stop')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-action',
|
||||
'click': (ev) => {
|
||||
ev.target.disabled = true;
|
||||
handleAction('restart').then(() => {
|
||||
ev.target.disabled = false;
|
||||
}).catch((err) => {
|
||||
ui.addTimeLimitedNotification(null, E('p', _('Failed to restart service: ') + err.message), 5000, 'error');
|
||||
ev.target.disabled = false;
|
||||
});
|
||||
}
|
||||
}, _('Restart'))
|
||||
]),
|
||||
// Info message about Start requirement
|
||||
E('div', {
|
||||
'class': 'cbi-value-description',
|
||||
'style': 'color: #666; font-size: 0.9em; margin-top: 4px;'
|
||||
}, [
|
||||
E('em', {}, [
|
||||
_('Start will only work if at least "Enable ID Server" or "Enable Relay Server" is checked in the Configuration section below.')
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
}, o, this);
|
||||
|
||||
/*
|
||||
Polling for status updates
|
||||
*/
|
||||
poll.add(() => {
|
||||
return Promise.all([
|
||||
L.resolveDefault(callGetStatus(), {}),
|
||||
L.resolveDefault(callGetPublicKey(), {}),
|
||||
L.resolveDefault(callGetVersion(), {}),
|
||||
uci.load('rustdesk-server')
|
||||
]).then(([status = {}, keyInfo = {}, verInfo = {}]) => {
|
||||
|
||||
// Get enabled status from UCI
|
||||
const sections = uci.sections('rustdesk-server', 'rustdesk-server');
|
||||
let hbbsEnabled = false;
|
||||
let hbbrEnabled = false;
|
||||
if (sections && sections.length > 0) {
|
||||
hbbsEnabled = (sections[0].enabled == '1');
|
||||
hbbrEnabled = (sections[0].enabled_relay == '1');
|
||||
}
|
||||
|
||||
// HBBS Status (Service Status column)
|
||||
const hbbsStatusEl = document.getElementById('hbbs_status');
|
||||
if (hbbsStatusEl) {
|
||||
let suffix = '';
|
||||
if (status.hbbs_pid) {
|
||||
suffix = '(PID: ' + status.hbbs_pid + ')';
|
||||
if (verInfo.hbbs_version) suffix += ' [' + verInfo.hbbs_version + ']';
|
||||
}
|
||||
hbbsStatusEl.innerHTML = createStatusIndicator(
|
||||
!!status.hbbs_pid, _('Running'), _('Stopped'), suffix
|
||||
);
|
||||
}
|
||||
|
||||
// HBBS Binary column
|
||||
const hbbsBinaryEl = document.getElementById('hbbs_binary');
|
||||
if (hbbsBinaryEl) {
|
||||
hbbsBinaryEl.innerHTML = createCheckIndicator(status.hbbs_exists, _('Found'), _('Not Found'));
|
||||
}
|
||||
|
||||
// HBBS Enabled column
|
||||
const hbbsEnabledEl = document.getElementById('hbbs_enabled');
|
||||
if (hbbsEnabledEl) {
|
||||
hbbsEnabledEl.innerHTML = createCheckIndicator(hbbsEnabled, _('Yes'), _('No'));
|
||||
}
|
||||
|
||||
// HBBR Status (Service Status column)
|
||||
const hbbrStatusEl = document.getElementById('hbbr_status');
|
||||
if (hbbrStatusEl) {
|
||||
let suffix = '';
|
||||
if (status.hbbr_pid) {
|
||||
suffix = '(PID: ' + status.hbbr_pid + ')';
|
||||
if (verInfo.hbbr_version) suffix += ' [' + verInfo.hbbr_version + ']';
|
||||
}
|
||||
hbbrStatusEl.innerHTML = createStatusIndicator(
|
||||
!!status.hbbr_pid, _('Running'), _('Stopped'), suffix
|
||||
);
|
||||
}
|
||||
|
||||
// HBBR Binary column
|
||||
const hbbrBinaryEl = document.getElementById('hbbr_binary');
|
||||
if (hbbrBinaryEl) {
|
||||
hbbrBinaryEl.innerHTML = createCheckIndicator(status.hbbr_exists, _('Found'), _('Not Found'));
|
||||
}
|
||||
|
||||
// HBBR Enabled column
|
||||
const hbbrEnabledEl = document.getElementById('hbbr_enabled');
|
||||
if (hbbrEnabledEl) {
|
||||
hbbrEnabledEl.innerHTML = createCheckIndicator(hbbrEnabled, _('Yes'), _('No'));
|
||||
}
|
||||
|
||||
// Public Key - update global state
|
||||
keyExistsGlobal = !!(keyInfo.key_exists && keyInfo.public_key);
|
||||
|
||||
const keyEl = document.getElementById('public_key');
|
||||
const regenBtn = document.getElementById('regenerate_key_btn');
|
||||
|
||||
if (keyEl) {
|
||||
if (keyInfo.key_exists && keyInfo.public_key) {
|
||||
keyEl.innerHTML = '<code style="font-size:0.9em">' + keyInfo.public_key + '</code>' +
|
||||
' <button class="btn cbi-button cbi-button-action" onclick="navigator.clipboard.writeText(\'' +
|
||||
keyInfo.public_key + '\');this.textContent=\'✓\';setTimeout(()=>this.textContent=\'' + _('Copy') + '\',1000)">' + _('Copy') + '</button>';
|
||||
} else {
|
||||
keyEl.innerHTML = '<em style="color:' + CONSTANTS.COLORS.MUTED + '">' + _('Not generated yet - start the service') + '</em>';
|
||||
}
|
||||
}
|
||||
|
||||
// Update regenerate button state
|
||||
if (regenBtn) {
|
||||
regenBtn.disabled = !keyExistsGlobal;
|
||||
if (!keyExistsGlobal) {
|
||||
regenBtn.title = _('Start the service first to generate the initial key');
|
||||
} else {
|
||||
regenBtn.title = _('Regenerate the key pair (will restart service)');
|
||||
}
|
||||
}
|
||||
|
||||
// Update Start button state based on config
|
||||
anyServerEnabledGlobal = hbbsEnabled || hbbrEnabled;
|
||||
const startBtn = document.getElementById('start_btn');
|
||||
if (startBtn) {
|
||||
startBtn.disabled = !anyServerEnabledGlobal;
|
||||
if (!anyServerEnabledGlobal) {
|
||||
startBtn.title = _('Enable ID Server or Relay Server in Configuration first');
|
||||
} else {
|
||||
startBtn.title = _('Start the service');
|
||||
}
|
||||
}
|
||||
|
||||
// Update boot enabled status
|
||||
bootEnabledGlobal = status.boot_enabled || false;
|
||||
const bootStatusEl = document.getElementById('boot_status');
|
||||
const bootBtn = document.getElementById('enable_boot_btn');
|
||||
if (bootStatusEl) {
|
||||
bootStatusEl.innerHTML = createCheckIndicator(bootEnabledGlobal, _('Enabled'), _('Disabled'));
|
||||
}
|
||||
if (bootBtn) {
|
||||
bootBtn.textContent = bootEnabledGlobal ? _('Disable') : _('Enable');
|
||||
}
|
||||
});
|
||||
}, CONSTANTS.POLL_INTERVAL);
|
||||
|
||||
/*
|
||||
Configuration Section
|
||||
*/
|
||||
s = m.section(form.TypedSection, 'rustdesk-server', _('Configuration'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
s.tab('hbbs', _('ID Server (hbbs)'));
|
||||
s.tab('hbbr', _('Relay Server (hbbr)'));
|
||||
|
||||
/* HBBS Settings */
|
||||
o = s.taboption('hbbs', form.Flag, 'enabled', _('Enable'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('hbbs', form.Value, 'server_port', _('Port (-p, --port)'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = CONSTANTS.HBBS_DEFAULT_PORT;
|
||||
o.description = _('Sets the listening port for the ID/Rendezvous server');
|
||||
|
||||
o = s.taboption('hbbs', form.Value, 'server_key', _('Key (-k, --key)'));
|
||||
o.description = _('Only allow clients with the same key. If empty, uses auto-generated key');
|
||||
o.validate = validateKey;
|
||||
|
||||
o = s.taboption('hbbs', form.DynamicList, 'server_relay_servers', _('Relay Servers (-r, --relay-servers)'));
|
||||
o.description = _('Default relay servers. Add one server per entry (hostname or hostname:port)');
|
||||
o.datatype = 'or(host,hostport)';
|
||||
|
||||
o = s.taboption('hbbs', form.DynamicList, 'server_rendezvous_servers', _('Rendezvous Servers (-R, --rendezvous-servers)'));
|
||||
o.description = _('Additional rendezvous servers. Add one server per entry (hostname or hostname:port)');
|
||||
o.datatype = 'or(host,hostport)';
|
||||
|
||||
o = s.taboption('hbbs', form.Value, 'server_mask', _('LAN Mask (--mask)'));
|
||||
o.description = _('Determine if the connection comes from LAN. Use CIDR notation.');
|
||||
o.placeholder = '192.168.0.0/16';
|
||||
o.datatype = 'cidr4';
|
||||
|
||||
o = s.taboption('hbbs', form.Value, 'server_rmem', _('UDP Recv Buffer (-M, --rmem)'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = '0';
|
||||
o.description = _('Sets UDP receive buffer size (0 = system default)');
|
||||
|
||||
o = s.taboption('hbbs', form.Value, 'server_serial', _('Serial Number (-s, --serial)'));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = '0';
|
||||
o.description = _('Sets configure update serial number');
|
||||
|
||||
o = s.taboption('hbbs', form.Value, 'server_software_url', _('Software Download URL (-u, --software-url)'));
|
||||
o.description = _('Sets the download URL of RustDesk software for clients');
|
||||
o.validate = validateURL;
|
||||
|
||||
/* HBBS Settings - Environment Variables */
|
||||
o = s.taboption('hbbs', form.Flag, 'server_env_always_use_relay', _('ALWAYS_USE_RELAY'));
|
||||
o.description = _('Force all connections to use relay servers');
|
||||
o.default = o.disabled;
|
||||
|
||||
o = s.taboption('hbbs', form.ListValue, 'server_env_rust_log', _('RUST_LOG'));
|
||||
o.description = _('Logging level for the ID server');
|
||||
o.value('', _('Default'));
|
||||
o.value('error', _('Error'));
|
||||
o.value('warn', _('Warning'));
|
||||
o.value('info', _('Info'));
|
||||
o.value('debug', _('Debug'));
|
||||
o.value('trace', _('Trace'));
|
||||
o.default = '';
|
||||
|
||||
/* HBBR Settings */
|
||||
o = s.taboption('hbbr', form.Flag, 'enabled_relay', _('Enable'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_port', _('Port (-p, --port)'));
|
||||
o.datatype = 'port';
|
||||
o.placeholder = CONSTANTS.HBBR_DEFAULT_PORT;
|
||||
o.description = _('Sets the listening port for the relay server');
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_key', _('Key (-k, --key)'));
|
||||
o.description = _('Only allow clients with the same key. If empty, uses auto-generated key');
|
||||
o.validate = validateKey;
|
||||
|
||||
/* HBBR Settings - Environment Variables */
|
||||
o = s.taboption('hbbr', form.ListValue, 'relay_env_rust_log', _('RUST_LOG'));
|
||||
o.description = _('Logging level for the relay server');
|
||||
o.value('', _('Default'));
|
||||
o.value('error', _('Error'));
|
||||
o.value('warn', _('Warning'));
|
||||
o.value('info', _('Info'));
|
||||
o.value('debug', _('Debug'));
|
||||
o.value('trace', _('Trace'));
|
||||
o.default = '';
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_env_limit_speed', _('LIMIT_SPEED'));
|
||||
o.datatype = 'uinteger';
|
||||
o.description = _('Speed limit per connection in Mb/s (0 = default)');
|
||||
o.placeholder = '0';
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_env_single_bandwidth', _('SINGLE_BANDWIDTH'));
|
||||
o.datatype = 'uinteger';
|
||||
o.description = _('Bandwidth limit per single connection in MB/s (0 = default)');
|
||||
o.placeholder = '0';
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_env_total_bandwidth', _('TOTAL_BANDWIDTH'));
|
||||
o.datatype = 'uinteger';
|
||||
o.description = _('Total bandwidth limit in MB/s (0 = default)');
|
||||
o.placeholder = '0';
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_env_downgrade_threshold', _('DOWNGRADE_THRESHOLD'));
|
||||
o.datatype = 'uinteger';
|
||||
o.description = _('Threshold for connection downgrade');
|
||||
|
||||
o = s.taboption('hbbr', form.Value, 'relay_env_downgrade_start_check', _('DOWNGRADE_START_CHECK'));
|
||||
o.datatype = 'uinteger';
|
||||
o.description = _('Start check time for connection downgrade');
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
'require tools.views as views';
|
||||
|
||||
return views.LogreadBox('hbb', _('RustDesk Server Log'));
|
||||
@@ -0,0 +1,434 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "RustDesk Server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Remote Desktop Software Server configuration."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Client"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Service Status"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Component"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Binary"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "HBBS (ID Server)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "HBBR (Relay Server)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Stopped"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Found"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Not Found"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Firewall Ports"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Required firewall ports (configure in Network → Firewall → Traffic Rules):"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "(NAT test)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "(ID server)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "(Relay)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "(Web clients)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "(Hole punching)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Ports are relative to configured ID Server port. If using non-default ports, adjust accordingly."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Public Key"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Regenerate Key"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Regenerate the key pair (requires existing key)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Cannot regenerate: No public key exists yet. Start the service first to generate the initial key."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "This will regenerate the key pair and restart the service. All existing clients will need to be reconfigured. Continue?"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Regenerating..."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Keys deleted. Starting service to generate new keys..."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Key regeneration failed: "
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Service started with new key"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Service start may have failed. Check status above."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Start the service first to generate the initial key"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Regenerate the key pair (will restart service)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Not generated yet - start the service"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Start at Boot"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Processing..."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Service enabled at boot"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Service disabled at boot"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Failed: "
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Error: "
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Disable"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Service Control"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Enable ID Server or Relay Server first"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Cannot start service: The ID Server or Relay Server must be enabled in the configuration first. Please check \"Enable ID Server\" or \"Enable Relay Server\" below and click \"Save & Apply\"."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Failed to start service: "
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Failed to stop service: "
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Failed to restart service: "
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Start"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Stop"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Restart"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Start will only work if at least \"Enable ID Server\" or \"Enable Relay Server\" is checked in the Configuration section below."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Enable ID Server or Relay Server in Configuration first"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Start the service"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "ID Server (hbbs)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Relay Server (hbbr)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Enable ID Server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Enable Relay Server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Port (-p, --port)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Sets the listening port for the ID/Rendezvous server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Key (-k, --key)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Only allow clients with the same key. If empty, uses auto-generated key"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Invalid characters. Only alphanumeric and base64 characters (+/=) allowed"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Relay Servers (-r, --relay-servers)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Default relay servers. Add one server per entry (hostname or hostname:port)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Rendezvous Servers (-R, --rendezvous-servers)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Additional rendezvous servers. Add one server per entry (hostname or hostname:port)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "LAN Mask (--mask)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Determine if the connection comes from LAN. Use CIDR notation."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "UDP Recv Buffer (-M, --rmem)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Sets UDP receive buffer size (0 = system default)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Serial Number (-s, --serial)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Sets configure update serial number"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Software Download URL (-u, --software-url)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Sets the download URL of RustDesk software for clients"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Invalid characters detected"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "URL must start with http:// or https://"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "ALWAYS_USE_RELAY"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Force all connections to use relay servers"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "RUST_LOG"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Logging level for the ID server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Logging level for the relay server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Debug"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Trace"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Sets the listening port for the relay server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "LIMIT_SPEED"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Speed limit per connection in Mb/s (0 = default)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "SINGLE_BANDWIDTH"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Bandwidth limit per single connection in MB/s (0 = default)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "TOTAL_BANDWIDTH"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Total bandwidth limit in MB/s (0 = default)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "DOWNGRADE_THRESHOLD"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Threshold for connection downgrade"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "DOWNGRADE_START_CHECK"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/htdocs/luci-static/resources/view/rustdesk-server/general.js
|
||||
msgid "Start check time for connection downgrade"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-rustdesk-server/root/usr/share/rpcd/acl.d/luci-app-rustdesk-server.json
|
||||
msgid "Grant access to RustDesk Server configuration"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,3 @@
|
||||
config rustdesk-server
|
||||
option enabled '0'
|
||||
option enabled_relay '0'
|
||||
129
applications/luci-app-rustdesk-server/root/etc/init.d/rustdesk-server
Executable file
129
applications/luci-app-rustdesk-server/root/etc/init.d/rustdesk-server
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
START=98
|
||||
STOP=10
|
||||
USE_PROCD=1
|
||||
|
||||
NAME=rustdesk-server
|
||||
# Use wrapper scripts that set working directory for key generation
|
||||
# See /usr/libexec/rustdesk-hbbs and /usr/libexec/rustdesk-hbbr for details
|
||||
PROG_HBBS=/usr/libexec/rustdesk-hbbs
|
||||
PROG_HBBR=/usr/libexec/rustdesk-hbbr
|
||||
BIN_HBBS=/usr/bin/hbbs
|
||||
BIN_HBBR=/usr/bin/hbbr
|
||||
KEY_DIR=/etc/rustdesk
|
||||
|
||||
get_config() {
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
config_get_bool enabled_relay "$1" enabled_relay 0
|
||||
|
||||
# HBBS settings
|
||||
config_get server_port "$1" server_port
|
||||
config_get server_key "$1" server_key
|
||||
config_get server_relay_servers "$1" server_relay_servers
|
||||
config_get server_rendezvous_servers "$1" server_rendezvous_servers
|
||||
config_get server_mask "$1" server_mask
|
||||
config_get server_rmem "$1" server_rmem
|
||||
config_get server_serial "$1" server_serial
|
||||
config_get server_software_url "$1" server_software_url
|
||||
config_get_bool server_env_always_use_relay "$1" server_env_always_use_relay 0
|
||||
config_get server_env_rust_log "$1" server_env_rust_log
|
||||
|
||||
# HBBR settings
|
||||
config_get relay_port "$1" relay_port
|
||||
config_get relay_key "$1" relay_key
|
||||
config_get relay_env_rust_log "$1" relay_env_rust_log
|
||||
config_get relay_env_limit_speed "$1" relay_env_limit_speed
|
||||
config_get relay_env_single_bandwidth "$1" relay_env_single_bandwidth
|
||||
config_get relay_env_total_bandwidth "$1" relay_env_total_bandwidth
|
||||
config_get relay_env_downgrade_threshold "$1" relay_env_downgrade_threshold
|
||||
config_get relay_env_downgrade_start_check "$1" relay_env_downgrade_start_check
|
||||
}
|
||||
|
||||
start_hbbs() {
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
[ -x "$BIN_HBBS" ] || {
|
||||
logger -t "$NAME" "Error: $BIN_HBBS not found or not executable"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Convert UCI lists (space-separated) to comma-separated for CLI
|
||||
local relay_servers_csv="${server_relay_servers// /,}"
|
||||
local rendezvous_servers_csv="${server_rendezvous_servers// /,}"
|
||||
|
||||
# Build command arguments
|
||||
local cmd_args="${server_port:+ -p $server_port}${relay_servers_csv:+ -r $relay_servers_csv}${server_key:+ -k $server_key}${rendezvous_servers_csv:+ -R $rendezvous_servers_csv}${server_mask:+ --mask $server_mask}${server_rmem:+ -M $server_rmem}${server_serial:+ -s $server_serial}${server_software_url:+ -u $server_software_url}"
|
||||
|
||||
procd_open_instance hbbs
|
||||
procd_set_param command "$PROG_HBBS" $cmd_args
|
||||
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param pidfile /var/run/hbbs.pid
|
||||
|
||||
# Environment variables
|
||||
procd_append_param env "DB_URL=/tmp/rustdesk_db_v2.sqlite3"
|
||||
[ "$server_env_always_use_relay" = "1" ] && procd_append_param env "ALWAYS_USE_RELAY=Y"
|
||||
[ -n "$server_env_rust_log" ] && procd_append_param env "RUST_LOG=$server_env_rust_log"
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
start_hbbr() {
|
||||
[ "$enabled_relay" = "1" ] || return 0
|
||||
[ -x "$BIN_HBBR" ] || {
|
||||
logger -t "$NAME" "Error: $BIN_HBBR not found or not executable"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Build command arguments
|
||||
local cmd_args="${relay_port:+ -p $relay_port}${relay_key:+ -k $relay_key}"
|
||||
|
||||
procd_open_instance hbbr
|
||||
procd_set_param command "$PROG_HBBR" $cmd_args
|
||||
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param pidfile /var/run/hbbr.pid
|
||||
|
||||
# Environment variables
|
||||
[ -n "$relay_env_rust_log" ] && procd_append_param env "RUST_LOG=$relay_env_rust_log"
|
||||
[ "${relay_env_limit_speed:-0}" != "0" ] && procd_append_param env "LIMIT_SPEED=$relay_env_limit_speed"
|
||||
[ "${relay_env_single_bandwidth:-0}" != "0" ] && procd_append_param env "SINGLE_BANDWIDTH=$relay_env_single_bandwidth"
|
||||
[ "${relay_env_total_bandwidth:-0}" != "0" ] && procd_append_param env "TOTAL_BANDWIDTH=$relay_env_total_bandwidth"
|
||||
[ -n "$relay_env_downgrade_threshold" ] && procd_append_param env "DOWNGRADE_THRESHOLD=$relay_env_downgrade_threshold"
|
||||
[ -n "$relay_env_downgrade_start_check" ] && procd_append_param env "DOWNGRADE_START_CHECK=$relay_env_downgrade_start_check"
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
start_service() {
|
||||
mkdir -p "$KEY_DIR"
|
||||
|
||||
config_load "$NAME"
|
||||
config_foreach get_config "$NAME"
|
||||
|
||||
[ "$enabled" != "1" ] && [ "$enabled_relay" != "1" ] && {
|
||||
logger -t "$NAME" "Services disabled. Not starting."
|
||||
return 0
|
||||
}
|
||||
|
||||
start_hbbs
|
||||
start_hbbr
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
logger -t "$NAME" "Service stopping"
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$NAME"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# luci-app-rustdesk-server UCI defaults
|
||||
# This script runs on first install to set up sensible defaults
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
set rustdesk-server.@rustdesk-server[0]=rustdesk-server
|
||||
set rustdesk-server.@rustdesk-server[0].enabled='0'
|
||||
set rustdesk-server.@rustdesk-server[0].enabled_relay='0'
|
||||
commit rustdesk-server
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
10
applications/luci-app-rustdesk-server/root/usr/libexec/rustdesk-hbbr
Executable file
10
applications/luci-app-rustdesk-server/root/usr/libexec/rustdesk-hbbr
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
# Wrapper script for hbbr (RustDesk Relay server)
|
||||
#
|
||||
# Purpose: This wrapper changes to /etc/rustdesk before exec'ing hbbr.
|
||||
# This is for consistency with hbbs (which needs the working directory
|
||||
# for key generation). Using exec ensures the process name remains
|
||||
# "hbbr" (not "sh") so syslog filtering works correctly.
|
||||
|
||||
cd /etc/rustdesk
|
||||
exec /usr/bin/hbbr "$@"
|
||||
10
applications/luci-app-rustdesk-server/root/usr/libexec/rustdesk-hbbs
Executable file
10
applications/luci-app-rustdesk-server/root/usr/libexec/rustdesk-hbbs
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
# Wrapper script for hbbs (RustDesk ID/Rendezvous server)
|
||||
#
|
||||
# Purpose: This wrapper changes to /etc/rustdesk before exec'ing hbbs.
|
||||
# hbbs generates its keypair (id_ed25519, id_ed25519.pub) in its
|
||||
# working directory. Using exec ensures the process name remains
|
||||
# "hbbs" (not "sh") so syslog filtering works correctly.
|
||||
|
||||
cd /etc/rustdesk
|
||||
exec /usr/bin/hbbs "$@"
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"admin/services/rustdesk-server": {
|
||||
"title": "RustDesk Server",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [
|
||||
"luci-app-rustdesk-server"
|
||||
],
|
||||
"uci": {
|
||||
"rustdesk-server": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin/services/rustdesk-server/general": {
|
||||
"title": "General",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "rustdesk-server/general"
|
||||
}
|
||||
},
|
||||
"admin/services/rustdesk-server/logs": {
|
||||
"title": "Log",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "rustdesk-server/logs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"luci-app-rustdesk-server": {
|
||||
"description": "Grant access to RustDesk Server configuration",
|
||||
"read": {
|
||||
"uci": [
|
||||
"rustdesk-server"
|
||||
],
|
||||
"ubus": {
|
||||
"luci.rustdesk-server": [
|
||||
"get_status",
|
||||
"get_public_key",
|
||||
"get_version"
|
||||
],
|
||||
"log": [
|
||||
"read"
|
||||
],
|
||||
"file": [
|
||||
"stat",
|
||||
"exec"
|
||||
]
|
||||
},
|
||||
"file": {
|
||||
"/usr/bin/hbbs": [
|
||||
"read"
|
||||
],
|
||||
"/usr/bin/hbbr": [
|
||||
"read"
|
||||
],
|
||||
"/etc/rustdesk/id_ed25519.pub": [
|
||||
"read"
|
||||
],
|
||||
"/etc/rustdesk/*": [
|
||||
"list"
|
||||
],
|
||||
"/etc/rc.d/S98rustdesk-server": [
|
||||
"read"
|
||||
]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [
|
||||
"rustdesk-server"
|
||||
],
|
||||
"ubus": {
|
||||
"luci.rustdesk-server": [
|
||||
"service_action",
|
||||
"regenerate_key"
|
||||
]
|
||||
},
|
||||
"cgi-io": [
|
||||
"exec"
|
||||
],
|
||||
"file": {
|
||||
"/etc/init.d/rustdesk-server start": [
|
||||
"exec"
|
||||
],
|
||||
"/etc/init.d/rustdesk-server stop": [
|
||||
"exec"
|
||||
],
|
||||
"/etc/init.d/rustdesk-server restart": [
|
||||
"exec"
|
||||
],
|
||||
"/etc/init.d/rustdesk-server reload": [
|
||||
"exec"
|
||||
],
|
||||
"/etc/init.d/rustdesk-server enable": [
|
||||
"exec"
|
||||
],
|
||||
"/etc/init.d/rustdesk-server disable": [
|
||||
"exec"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env ucode
|
||||
'use strict';
|
||||
|
||||
import { popen, access, readfile, unlink } from 'fs';
|
||||
import { process_list, init_enabled, init_action } from 'luci.sys';
|
||||
|
||||
const BIN_DIR = '/usr/bin';
|
||||
const KEY_DIR = '/etc/rustdesk';
|
||||
|
||||
/*
|
||||
* Helper functions to reduce code duplication
|
||||
*/
|
||||
|
||||
// Shell escape a string to prevent command injection
|
||||
function shellquote(s) {
|
||||
return `'${replace(s, "'", "'\\''")}'`;
|
||||
}
|
||||
|
||||
// Get PID of a process by name using luci.sys.process_list()
|
||||
function getProcessPid(process_name) {
|
||||
for (let proc in process_list()) {
|
||||
if (index(proc.COMMAND, process_name) >= 0) {
|
||||
return proc.PID;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Execute a command and return trimmed output (for version queries only)
|
||||
function execCommand(bin, args) {
|
||||
let result = null;
|
||||
let cmd = shellquote(bin) + ' ' + args;
|
||||
let pp = popen(cmd, 'r');
|
||||
if (pp) {
|
||||
let output = pp.read('all');
|
||||
pp.close();
|
||||
if (output) {
|
||||
result = trim(output);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if a file exists
|
||||
function fileExists(path) {
|
||||
return !!access(path);
|
||||
}
|
||||
|
||||
// Read file content and trim
|
||||
function readFileContent(path) {
|
||||
let content = readfile(path);
|
||||
return content ? trim(content) : null;
|
||||
}
|
||||
|
||||
// Safe file deletion
|
||||
function safeUnlink(path) {
|
||||
return unlink(path) || false;
|
||||
}
|
||||
|
||||
const methods = {
|
||||
get_status: {
|
||||
call: function() {
|
||||
// Check if service is enabled for boot using luci.sys.init_enabled()
|
||||
let boot_enabled = init_enabled('rustdesk-server');
|
||||
|
||||
return {
|
||||
hbbs_pid: getProcessPid('hbbs'),
|
||||
hbbr_pid: getProcessPid('hbbr'),
|
||||
hbbs_exists: fileExists(BIN_DIR + '/hbbs'),
|
||||
hbbr_exists: fileExists(BIN_DIR + '/hbbr'),
|
||||
boot_enabled: boot_enabled
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
get_public_key: {
|
||||
call: function() {
|
||||
let key_path = KEY_DIR + '/id_ed25519.pub';
|
||||
let key_exists = fileExists(key_path);
|
||||
let public_key = null;
|
||||
|
||||
if (key_exists) {
|
||||
public_key = readFileContent(key_path);
|
||||
}
|
||||
|
||||
return {
|
||||
key_exists: key_exists,
|
||||
public_key: public_key,
|
||||
key_path: key_path
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
service_action: {
|
||||
args: { action: 'action' },
|
||||
call: function(req) {
|
||||
let action = '';
|
||||
|
||||
if (req && req.args && req.args.action) {
|
||||
action = req.args.action;
|
||||
}
|
||||
|
||||
// Validate action - whitelist approach
|
||||
const valid_actions = ['start', 'stop', 'restart', 'reload', 'enable', 'disable'];
|
||||
if (index(valid_actions, action) < 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid action. Allowed: ' + join(', ', valid_actions)
|
||||
};
|
||||
}
|
||||
|
||||
// Use luci.sys.init_action() for service control
|
||||
let result = init_action('rustdesk-server', action);
|
||||
|
||||
return {
|
||||
success: (result === 0),
|
||||
action: action,
|
||||
exit_code: result
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
get_version: {
|
||||
call: function() {
|
||||
return {
|
||||
hbbs_version: fileExists(BIN_DIR + '/hbbs') ? execCommand(BIN_DIR + '/hbbs', '--version 2>&1') : null,
|
||||
hbbr_version: fileExists(BIN_DIR + '/hbbr') ? execCommand(BIN_DIR + '/hbbr', '--version 2>&1') : null
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
regenerate_key: {
|
||||
call: function() {
|
||||
let key_priv = KEY_DIR + '/id_ed25519';
|
||||
let key_pub = KEY_DIR + '/id_ed25519.pub';
|
||||
|
||||
// Step 1: Stop the service first so keys are not in use
|
||||
// init_action is synchronous - waits for service to fully stop
|
||||
init_action('rustdesk-server', 'stop');
|
||||
|
||||
// Step 2: Remove existing keys
|
||||
let priv_deleted = safeUnlink(key_priv);
|
||||
let pub_deleted = safeUnlink(key_pub);
|
||||
|
||||
// Verify keys are deleted
|
||||
let keys_deleted = !fileExists(key_priv) && !fileExists(key_pub);
|
||||
|
||||
// The UI will call restart to regenerate the keys
|
||||
// hbbs automatically generates new keys on startup if they don't exist
|
||||
|
||||
return {
|
||||
success: keys_deleted,
|
||||
keys_deleted: keys_deleted,
|
||||
priv_deleted: priv_deleted,
|
||||
pub_deleted: pub_deleted,
|
||||
key_path: key_pub,
|
||||
message: keys_deleted ? 'Keys deleted. Restart service to generate new keys.' : 'Failed to delete keys'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.rustdesk-server': methods };
|
||||
Reference in New Issue
Block a user