diff --git a/DOCKER_SUPPORT.md b/DOCKER_SUPPORT.md index d5262c55..a0da2cc1 100644 --- a/DOCKER_SUPPORT.md +++ b/DOCKER_SUPPORT.md @@ -21,7 +21,7 @@ The enhanced installer is primarily designed for **native RustDesk installations #### Option 1: Continue with Native Installation (Not Recommended) You can choose to install BetterDesk Console alongside your Docker installation, but this may cause conflicts: -- Port conflicts (21115, 21116, 21117, 21114) +- Port conflicts (21115, 21116, 21117, 21120) - Database access issues - Service management complexity @@ -62,7 +62,7 @@ services: - "21115:21115" - "21116:21116/tcp" - "21116:21116/udp" - - "21114:21114" # API port + - "21120:21120" # API port ``` ### Detecting Your Setup diff --git a/README.md b/README.md index c5ebdff7..faeefc61 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -# 🚀 BetterDesk Console +# 🚀 BetterDesk Console
![License](https://img.shields.io/badge/license-MIT-blue.svg) ![RustDesk](https://img.shields.io/badge/RustDesk-1.1.14-green.svg) ![Python](https://img.shields.io/badge/Python-3.8+-blue.svg) -![Version](https://img.shields.io/badge/version-1.3.0--secure-brightgreen.svg) -![Security](https://img.shields.io/badge/API-localhost--only-orange.svg) +![Version](https://img.shields.io/badge/version-1.5.0-brightgreen.svg) +![Security](https://img.shields.io/badge/API-X--API--Key--Auth-green.svg) +![Access](https://img.shields.io/badge/LAN-Accessible-blue.svg) **A modern, feature-rich web management console for RustDesk with real-time device monitoring and bidirectional ban enforcement** @@ -60,14 +61,14 @@ - **Glassmorphism Design**: Sleek, modern UI with blur effects and gradients - **Material Icons**: Google Material Design icons (fully offline) -- **Responsive Layout**: Works on desktop, tablet, and mobile +- **Responsive Layout**: Works on desktop and tablet - **Dark Theme**: Easy on the eyes, perfect for NOC environments - **Real-Time Updates**: Auto-refresh device status - **Search & Filter**: Quickly find devices in large deployments ### 🔧 Enhanced HBBS Server -- **HTTP API**: RESTful API on port 21120 (localhost only, not exposed to internet) +- **HTTP API**: RESTful API on port 21120 with X-API-Key authentication (LAN accessible) - **Real-Time Status**: Memory-based device status (no database lag) - **Authentic Algorithm**: Uses RustDesk's official 30-second timeout logic - **Thread-Safe**: Shared PeerMap with Arc/RwLock for concurrent access @@ -98,6 +99,21 @@ ### 🛡️ Security & Reliability +- **Authentication System (v1.5.0)**: + - User login with bcrypt password hashing + - Role-based access control (Admin, Operator, Viewer) + - Session management with 24-hour tokens + - **\ud83c\udf10 Sidebar navigation** with 5 main sections (Dashboard, Public Key, Settings, User Management, About) + - **\ud83d\udd11 Password-protected public key access** - requires password verification + - **\u2699\ufe0f Settings page** with password change functionality + - **\ud83d\udc65 User management panel** (admin only) - create, edit, delete, activate/deactivate users + - **\ud83d\udcdd Extended About page** with open source credits and license information + - Audit logging for all actions +- **API Security (v1.4.0)**: + - X-API-Key header authentication for HBBS API + - 64-character random API keys + - Secure key storage with 600 permissions + - LAN accessible (0.0.0.0) with authentication protection - **Input Validation**: Comprehensive validation for all user inputs - **XSS Protection**: Sanitization of user-provided content - **SQL Injection Prevention**: Parameterized queries throughout @@ -131,9 +147,21 @@ ![Details](screenshots/device-details.png) *Detailed device information modal* -### Mobile Responsive -![Mobile](screenshots/mobile-view.png) -*Fully responsive design for mobile devices* +### Public Key Management +![Public Key](screenshots/public_key_page.png) +*Secure public key access with password protection* + +### Settings +![Settings](screenshots/settings_page.png) +*User settings and password management* + +### User Management +![User Management](screenshots/user_mgmt.png) +*Multi-user administration panel* + +### About +![About](screenshots/about.png) +*System information and version details* --- @@ -142,7 +170,7 @@ ``` ┌─────────────────────────────────────────────────────────┐ │ RustDesk Clients │ -│ (Desktop, Mobile, Web) │ +│ (Desktop, Tablet, Web) │ └──────────────────────┬──────────────────────────────────┘ │ Heartbeat (~30-45s) ▼ @@ -164,7 +192,7 @@ ┌────────────────┐ ┌─────────────────┐ │ HTTP API │ │ SQLite DB │ │ (Port 21120) │ │ (Persistence) │ -│ (Localhost) │ │ │ +│ (LAN Access) │ │ │ └────────┬───────┘ └─────────────────┘ │ ▼ @@ -223,6 +251,31 @@ sudo ./install-improved.sh - ✅ **Dynamic .pub file scanning** - works with any public key filename - ✅ **Multiple backup options** - automatic, manual, or existing backup - ✅ **Key regeneration with warnings** - prevents accidental key changes +- ✅ **API key generation** - automatic X-API-Key authentication setup +- ✅ **LAN access configuration** - web console and API accessible on network + +### 🔄 Updating Existing Installation + +If you already have BetterDesk Console installed and want to upgrade to v1.4.0 with authentication: + +```bash +cd Rustdesk-FreeConsole + +# Make the update script executable +chmod +x update-to-v1.4.0.sh + +# Run as root +sudo ./update-to-v1.4.0.sh +``` + +**Update features:** +- ✅ Automatic backup before changes +- ✅ Database migration to add authentication tables +- ✅ API key generation and configuration +- ✅ Preserves existing configuration +- ✅ Creates default admin user (if needed) +- ✅ Updates systemd services for LAN access +- ✅ Rollback capability if update fails ### 🪟 Windows Installation @@ -253,14 +306,14 @@ The installers automatically use the correct binaries for your platform: ### 🔑 Key Protection (IMPORTANT!) -**v9+ includes comprehensive encryption key protection:** +**v1.5.0+ includes comprehensive encryption key protection:** ⚠️ **Your RustDesk encryption keys are CRITICAL!** - Losing keys = ALL clients disconnected - Changing keys = "Key mismatch" errors on all devices - Keys must be backed up before any installation -**BetterDesk v9+ automatically:** +**BetterDesk v1.5.0+ automatically:** - ✅ Detects existing encryption keys - ✅ Scans for ANY `.pub` file (not just `id_ed25519.pub`) - ✅ Offers multiple backup options (automatic, manual, existing) @@ -282,35 +335,25 @@ Options: **If you experience "Key mismatch" errors:** ```bash -# Use the repair tool -sudo bash repair-keys.sh - -# Or restore from automatic backup +# Restore from automatic backup BACKUP=$(ls -d /opt/rustdesk-backup-* | sort | tail -1) sudo cp $BACKUP/id_ed25519* /opt/rustdesk/ sudo systemctl restart rustdesksignal ``` -📖 **Full guide**: [docs/KEY_TROUBLESHOOTING.md](docs/KEY_TROUBLESHOOTING.md) -🚑 **Quick fixes**: [docs/QUICK_FIX.md](docs/QUICK_FIX.md) +📖 **Full guide**: [docs/KEY_TROUBLESHOOTING.md](docs/KEY_TROUBLESHOOTING.md) -### What's New in v1.3.0 (Latest) +### What's New in v1.5.0 (Latest) -- **🔑 Encryption Key Protection**: Automatic detection and preservation of existing keys -- **🔍 Dynamic Key Scanning**: Web console finds any `.pub` file automatically -- **💾 Enhanced Backup System**: Multiple backup options with verification -- **🐳 Improved Docker Support**: Better detection and handling of Docker installations -- **🔧 Key Repair Tool**: New `repair-keys.sh` utility for troubleshooting -- **📚 Comprehensive Documentation**: KEY_TROUBLESHOOTING.md and QUICK_FIX.md guides -- **⚠️ Visual Warnings**: Clear indicators for dangerous operations -- **✅ Pre-flight Checks**: Validation before any destructive operations - -### What's New in v1.1.0 - -- **Device Banning System**: Ban/unban devices with reason tracking -- **Soft Delete**: Devices marked as deleted instead of permanent removal -- **Enhanced Security**: Input validation, XSS protection, SQL injection prevention -- **Improved UI**: Visual ban indicators, new statistics card, confirmation dialogs +- **🔐 Authentication System**: User login with bcrypt password hashing +- **👥 Role-Based Access Control**: Admin, Operator, and Viewer roles +- **🌐 Sidebar Navigation**: Modern UI with 5 main sections +- **🔑 Password-Protected Public Key**: Requires verification to view +- **⚙️ Settings Page**: Change password functionality with token regeneration +- **👤 User Management**: Admin panel to create, edit, delete users +- **📝 Extended About Page**: Open source credits and license info +- **🛡️ CSRF Protection**: Flask-WTF security +- **⏱️ Rate Limiting**: 5 login attempts per minute ### 🔒 Manual Installation on SSH Server (Security Update) @@ -330,28 +373,7 @@ sudo bash ~/MANUAL_INSTALL.sh 2. Backup old binaries (timestamped) 3. Install new binaries from `~/build/hbbs-patch/rustdesk-server/target/release/` 4. Restart services -5. Verify: - - HTTP API listening on `127.0.0.1:21120` (localhost only) - - RustDesk ports 21115-21117 operational - - Port 21120 NOT accessible from external network - -**Post-installation verification:** -```bash -# On server (should work) -curl http://localhost:21120/api/health - -# From outside (should FAIL with "Connection refused") -curl http://SERVER_IP:21120/api/health -``` - -**To access API remotely, use SSH tunnel:** -```bash -# Create tunnel -ssh -L 21120:localhost:21120 your-user@your-server - -# Then access locally -curl http://localhost:21120/api/health -``` +5. Verify API is responding on port 21120 --- @@ -446,22 +468,32 @@ sudo bash repair-keys.sh ### HBBS API Port -Default: `21120` (localhost only - not exposed to internet) +Default: `21120` (LAN accessible with X-API-Key authentication) -**Security**: The API is bound to `127.0.0.1` (localhost) by design. This means: -- ✅ API is only accessible from the same machine -- ✅ Cannot be accessed from network/internet even without firewall -- ✅ Web console connects locally or via SSH tunnel -- ✅ No risk of unauthorized data exposure +**Security (v1.4.0)**: The API now supports LAN access with proper authentication: +- \u2705 Binds to `0.0.0.0:21120` (accessible on LAN) +- \u2705 Requires X-API-Key header for all requests +- \u2705 64-character random API key generated during installation +- \u2705 Key stored securely in `/opt/rustdesk/.api_key` with 600 permissions +- \u2705 Web console automatically uses API key +- \u2705 No authentication = no access (secure by design) -To change, edit `/etc/systemd/system/rustdesksignal.service`: +**API Key Location**: `/opt/rustdesk/.api_key` + +To change port, edit `/etc/systemd/system/rustdesksignal.service`: ```ini -ExecStart=/opt/rustdesk/hbbs -k _ -p 21115 --api-port 21115 +ExecStart=/opt/rustdesk/hbbs -k _ -p 21115 --api-port 21120 ``` ### Web Console Port -Default: `5000` +Default: `5000` (accessible on LAN) + +The web console binds to `0.0.0.0:5000` for LAN access and includes: +- User authentication (bcrypt passwords) +- Session management (24-hour tokens) +- Role-based access control +- Audit logging To change, edit `/opt/BetterDeskConsole/app.py`: ```python @@ -471,12 +503,13 @@ app.run(host='0.0.0.0', port=5000) ### Firewall Configuration ```bash -# Allow web console (if needed externally) -sudo ufw allow 5000/tcp +# Allow web console on LAN +sudo ufw allow from 192.168.0.0/16 to any port 5000 proto tcp -# HBBS API (usually internal only) -# Port 21120 does NOT need to be opened - it's localhost only! -# Only open RustDesk ports: +# Allow HBBS API on LAN (if needed for external tools) +sudo ufw allow from 192.168.0.0/16 to any port 21120 proto tcp + +# Standard RustDesk ports sudo ufw allow 21115/tcp sudo ufw allow 21116/tcp sudo ufw allow 21116/udp @@ -489,15 +522,29 @@ sudo ufw allow 21117/tcp ### Base URL ``` -http://localhost:21120/api +http://:21120/api +``` + +### Authentication + +**All API requests require X-API-Key header:** +```bash +curl -H "X-API-Key: YOUR_API_KEY_HERE" http://192.168.1.100:21120/api/health +``` + +**API Key Location**: `/opt/rustdesk/.api_key` + +To retrieve your API key: +```bash +sudo cat /opt/rustdesk/.api_key ``` -**Note**: API is bound to localhost only and cannot be accessed from external networks. ### Endpoints #### Health Check ```http GET /api/health +Headers: X-API-Key: ``` **Response**: @@ -512,6 +559,7 @@ GET /api/health #### List All Peers ```http GET /api/peers +Headers: X-API-Key: ``` **Response**: @@ -534,6 +582,14 @@ GET /api/peers } ``` +**Error Response (No/Invalid API Key)**: +```json +{ + "error": "Unauthorized: Invalid or missing API key" +} +``` +Status Code: 401 + ### Status Detection Algorithm A device is considered **online** if: @@ -574,7 +630,10 @@ BetterDeskConsole/ │ ├── dashboard.png │ ├── devices-list.png │ ├── device-details.png -│ └── mobile-view.png +│ ├── public_key_page.png +│ ├── settings_page.png +│ ├── user_mgmt.png +│ └── about.png ├── web/ # Web console │ ├── app.py # Flask application │ ├── app_demo.py # Demo with mock data @@ -782,23 +841,14 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ### Additional Documentation ([docs/](docs/)) - **[CHANGELOG.md](docs/CHANGELOG.md)** - Complete version history - **[CONTRIBUTING.md](docs/CONTRIBUTING.md)** - How to contribute -- **[DEPRECATION_NOTICE.md](docs/DEPRECATION_NOTICE.md)** - Deprecated features info -- **[RELEASE_NOTES_v1.2.0.md](docs/RELEASE_NOTES_v1.2.0.md)** - Latest release notes -- **[RELEASE_NOTES_v1.3.0.md](docs/RELEASE_NOTES_v1.3.0.md)** - Version 1.3.0 release notes +- **[INSTALLATION_V1.4.0.md](docs/INSTALLATION_V1.4.0.md)** - Detailed installation guide - **[UPDATE_GUIDE.md](docs/UPDATE_GUIDE.md)** - Update instructions -- **[DEVELOPMENT_ROADMAP.md](docs/DEVELOPMENT_ROADMAP.md)** - Future plans -- **[PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md)** - Project structure overview -- **[PROJECT_ORGANIZATION.md](docs/PROJECT_ORGANIZATION.md)** - Project organization details +- **[KEY_TROUBLESHOOTING.md](docs/KEY_TROUBLESHOOTING.md)** - Key troubleshooting guide - **[PORT_SECURITY.md](docs/PORT_SECURITY.md)** - Port security information -- **[GITHUB_RELEASE_GUIDE.md](docs/GITHUB_RELEASE_GUIDE.md)** - GitHub release guide -- **[RELEASE_READY.md](docs/RELEASE_READY.md)** - Release readiness checklist -- **[SECURITY_CLEANUP_REPORT.md](docs/SECURITY_CLEANUP_REPORT.md)** - Security cleanup report -- **[SECURITY_PLACEHOLDERS.md](docs/SECURITY_PLACEHOLDERS.md)** - Security placeholders -- **[SECURITY_URGENT.md](docs/SECURITY_URGENT.md)** - Urgent security items +- **[PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md)** - Project structure overview ### Technical Documentation - **[hbbs-patch/](hbbs-patch/)** - HBBS modification documentation -- **[archive/](archive/)** - Archived scripts and old files - **[dev_modules/](dev_modules/)** - Development and testing tools --- diff --git a/VERSION b/VERSION index 1b9ec07e..e1d3178f 100644 --- a/VERSION +++ b/VERSION @@ -1,13 +1,17 @@ -v9 (1.3.0-secure) +v1.5.0 -BetterDesk Console Version 9 -Enhanced Installation Script with Encryption Key Protection +BetterDesk Console Version 1.5.0 +Authentication System & Modern UI -New in v9: -- 🔑 Comprehensive encryption key protection -- 🔍 Dynamic .pub file scanning (works with any key filename) -- 💾 Enhanced backup system with multiple options -- 🐳 Improved Docker detection and handling +New in v1.5.0: +- 🔐 Authentication system with bcrypt password hashing +- 👥 Role-based access control (Admin, Operator, Viewer) +- 🎨 Sidebar navigation with 5 main sections +- 🔑 Password-protected public key access +- ⚙️ Settings page with password change functionality +- 👤 User Management panel (admin only) +- 🛡️ CSRF protection and rate limiting +- 📊 Extended About page with open source credits - 🔧 New repair-keys.sh diagnostic tool - 📚 Complete troubleshooting documentation - ⚠️ Visual warnings for dangerous operations diff --git a/archive/RELEASE_READY.md b/archive/RELEASE_READY.md deleted file mode 100644 index 4ad4740f..00000000 --- a/archive/RELEASE_READY.md +++ /dev/null @@ -1,262 +0,0 @@ -# 🎉 Release Readiness Checklist - BetterDesk Console v1.2.0-v8 - -## ✅ Code & Binaries - -- [x] **Precompiled binaries** included in `hbbs-patch/bin/` - - [x] hbbs-v8 (9.5 MB) - Signal server with bidirectional ban enforcement - - [x] hbbr-v8 (5.0 MB) - Relay server with bidirectional ban enforcement - - [x] SHA256 checksums documented - -- [x] **Web console** fully functional - - [x] Flask backend with ban management - - [x] Modern glassmorphism UI - - [x] Material Icons (offline) - - [x] Device management, banning, notes - -- [x] **Installation system** verified - - [x] install.sh uses precompiled binaries - - [x] Automatic backup of existing files - - [x] Service restart functionality - - [x] No compilation required - - [x] Reduced dependencies (no Rust/git needed) - -## ✅ Documentation - -- [x] **Main README.md** updated - - [x] Version badge: 1.2.0-v8 - - [x] Bidirectional ban enforcement description - - [x] Precompiled binaries mentioned - - [x] Installation time: 2-3 minutes - - [x] No compilation requirements - -- [x] **CHANGELOG.md** updated - - [x] Version 1.2.0-v8 entry - - [x] Bidirectional ban enforcement details - - [x] Precompiled binaries explanation - - [x] Migration notes - -- [x] **LICENSE** appropriate - - [x] AGPL-3.0 (compatible with RustDesk) - - [x] Copyright attribution - -- [x] **Technical documentation** - - [x] hbbs-patch/BAN_ENFORCEMENT.md - Bidirectional bans - - [x] hbbs-patch/SECURITY_AUDIT.md - Security review - - [x] hbbs-patch/bin/README.md - Binary documentation - - [x] hbbs-patch/bin/CHECKSUMS.md - SHA256 verification - - [x] docs/INSTALLATION_V8.md - Complete installation guide - - [x] PROJECT_STRUCTURE.md - Updated structure - -## ✅ Security & Privacy - -- [x] **No sensitive data** in files - - [x] SSH credentials removed (0 instances found) - - [x] IP addresses replaced with placeholders - - [x] All occurrences: YOUR_SERVER_IP, YOUR_SSH_USER - -- [x] **No git history** with sensitive data - - [x] Not a git repository (clean start possible) - -- [x] **Security documentation** - - [x] SECURITY_AUDIT.md - Vulnerability assessment - - [x] SECURITY_PLACEHOLDERS.md - Guide for users - - [x] SECURITY_CLEANUP_REPORT.md - Cleanup summary - -- [x] **.gitignore** comprehensive - - [x] Credentials patterns - - [x] Backup files - - [x] Old binary versions - - [x] Sensitive data patterns - -## ✅ Code Quality - -- [x] **Functional verification** - - [x] Bidirectional ban enforcement working - - [x] Web console operational - - [x] Database migrations included - - [x] Service files present - -- [x] **Clean codebase** - - [x] Old binaries removed (v2-v5) - - [x] Deprecated code in separate directory - - [x] No TODO or FIXME markers in critical code - -## ✅ Repository Structure - -``` -BetterDeskConsole/ -├── ✅ README.md (updated) -├── ✅ LICENSE (AGPL-3.0) -├── ✅ VERSION (1.2.0-v8) -├── ✅ CHANGELOG.md (v8 entry) -├── ✅ .gitignore (comprehensive) -├── ✅ PROJECT_STRUCTURE.md (updated) -│ -├── ✅ install.sh (precompiled binaries) -├── ✅ update.sh (for upgrades) -├── ✅ restore_hbbs.sh (rollback) -│ -├── ✅ web/ (Flask console) -│ ├── ✅ app.py (ban management) -│ ├── ✅ requirements.txt -│ ├── ✅ betterdesk.service -│ ├── ✅ templates/index.html -│ └── ✅ static/ (CSS, JS, icons) -│ -├── ✅ hbbs-patch/ -│ ├── ✅ bin/ (NEW - precompiled) -│ │ ├── ✅ hbbs-v8 (9.5 MB) -│ │ ├── ✅ hbbr-v8 (5.0 MB) -│ │ ├── ✅ README.md -│ │ └── ✅ CHECKSUMS.md -│ │ -│ ├── ✅ src/ (source patches) -│ ├── ✅ build.sh (rebuild script) -│ ├── ✅ deploy-v8.sh -│ ├── ✅ BAN_ENFORCEMENT.md (v8) -│ ├── ✅ SECURITY_AUDIT.md -│ └── ✅ test scripts -│ -├── ✅ docs/ -│ ├── ✅ INSTALLATION_V8.md -│ ├── ✅ UPDATE_GUIDE.md -│ └── ✅ other guides -│ -├── ✅ migrations/ (database) -└── ✅ screenshots/ (UI examples) -``` - -## 📊 Statistics - -- **Total Files**: ~100+ -- **Lines of Code**: ~10,000+ -- **Documentation**: 15+ markdown files -- **Installation Time**: 2-3 minutes (vs 20 min before) -- **Dependencies Removed**: git, cargo, rustc (~500 MB saved) -- **Binary Size**: 14.5 MB total (hbbs + hbbr) -- **Ban Enforcement**: 100% effective, bidirectional - -## 🚀 Ready for Publication - -### GitHub Release Steps - -1. **Initialize git repository** - ```bash - git init - git add . - git commit -m "Initial commit: BetterDesk Console v1.2.0-v8" - ``` - -2. **Create GitHub repository** - ```bash - gh repo create BetterDeskConsole --public --source=. --remote=origin - ``` - -3. **Push to GitHub** - ```bash - git branch -M main - git push -u origin main - ``` - -4. **Create release** - ```bash - gh release create v1.2.0-v8 \ - --title "BetterDesk Console v1.2.0-v8 - Precompiled Binaries + Bidirectional Bans" \ - --notes "See CHANGELOG.md for details" \ - hbbs-patch/bin/hbbs-v8 \ - hbbs-patch/bin/hbbr-v8 - ``` - -5. **Tag binaries** - ```bash - git tag -a v1.2.0-v8 -m "Version 1.2.0-v8 with precompiled binaries" - git push origin v1.2.0-v8 - ``` - -## 🎯 Next Steps (Post-Release) - -1. **Community Engagement** - - [ ] Submit to RustDesk community forum - - [ ] Reddit post in r/selfhosted - - [ ] Tweet about release - -2. **Monitoring** - - [ ] Watch for issues/bug reports - - [ ] Monitor installation success rate - - [ ] Gather user feedback - -3. **Future Improvements** - - [ ] Multi-architecture binaries (ARM64) - - [ ] Docker container - - [ ] Web console authentication - - [ ] Automated testing suite - -## ✅ Final Verification - -Run these commands before publishing: - -```bash -# 1. Verify no sensitive data -grep -r "192.168.0.110" . --exclude-dir=.git -grep -r "unitronix@" . --exclude-dir=.git - -# 2. Verify binaries exist -ls -lh hbbs-patch/bin/hbbs-v8 hbbs-patch/bin/hbbr-v8 - -# 3. Verify checksums -sha256sum hbbs-patch/bin/*-v8 - -# 4. Test installer (dry run) -bash -n install.sh - -# 5. Verify documentation links -find docs -name "*.md" -exec grep -l "YOUR_SERVER_IP" {} \; -``` - -## 📝 Release Notes Draft - -```markdown -# BetterDesk Console v1.2.0-v8 - -## 🚀 Major Changes - -- **Precompiled Binaries**: Installation now takes 2-3 minutes (vs 20 minutes) -- **Bidirectional Ban Enforcement**: Banned devices blocked in BOTH directions -- **No Compilation Required**: Removed Rust toolchain dependency -- **Simplified Installation**: Just Python3 + pip3 needed - -## 📦 What's Included - -- HBBS v8 (9.5 MB) - Signal server with bidirectional bans -- HBBR v8 (5.0 MB) - Relay server -- Web management console (Flask + Material Design) -- Complete documentation - -## 🔧 Installation - -```bash -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -sudo chmod +x install.sh -sudo ./install.sh -``` - -## 📖 Documentation - -- [Installation Guide](docs/INSTALLATION_V8.md) -- [Ban Enforcement Technical Docs](hbbs-patch/BAN_ENFORCEMENT.md) -- [Security Audit](hbbs-patch/SECURITY_AUDIT.md) - -## 🔐 Security - -- SHA256 checksums provided -- Full source code available -- AGPL-3.0 license -- Security audit included -``` - ---- - -**Status**: ✅ **READY FOR RELEASE** - -All systems go! 🎉 diff --git a/archive/SECURITY_CLEANUP_REPORT.md b/archive/SECURITY_CLEANUP_REPORT.md deleted file mode 100644 index 34b17fb3..00000000 --- a/archive/SECURITY_CLEANUP_REPORT.md +++ /dev/null @@ -1,161 +0,0 @@ -# 🔒 Bezpieczeństwo Danych - Raport Czyszczenia - -**Data:** 6 stycznia 2026 -**Status:** ✅ ZAKOŃCZONE - ---- - -## 📊 Podsumowanie Zmian - -### Pliki Zabezpieczone (18 plików): -1. ✅ README.md -2. ✅ hbbs-patch/deploy.ps1 -3. ✅ hbbs-patch/deploy-v6.ps1 -4. ✅ hbbs-patch/deploy-v8.sh -5. ✅ hbbs-patch/QUICKSTART.md -6. ✅ hbbs-patch/BAN_ENFORCEMENT.md -7. ✅ hbbs-patch/test_ban_enforcement.ps1 -8. ✅ hbbs-patch/diagnose_ban.ps1 -9. ✅ docs/UPDATE_REFERENCE.md -10. ✅ docs/UPDATE_GUIDE.md -11. ✅ docs/QUICKSTART_UPDATE.md -12. ✅ dev_modules/update.ps1 -13. ✅ dev_modules/test_ban_api.sh -14. ✅ deprecated/BAN_ENFORCER_TEST.md (częściowo) -15. ✅ .gitignore (zaktualizowany) -16. ✅ SECURITY_PLACEHOLDERS.md (nowy) -17. ✅ SECURITY_AUDIT.md (stworzony wcześniej) -18. ✅ Ten raport - ---- - -## 🔄 Zamienione Dane - -| Dane Wrażliwe | Placeholder | Wystąpienia | -|---------------|-------------|-------------| -| `192.168.0.110` | `YOUR_SERVER_IP` | ~150+ | -| `unitronix` | `YOUR_SSH_USER` | ~150+ | - ---- - -## 📁 Pozostałe Pliki - -### Deprecated (Przestarzałe pliki - ~33 wystąpienia) -Pliki w katalogu `deprecated/` zostały częściowo zaktualizowane, ale zawierają starą dokumentację która nie jest już używana: -- `deprecated/BAN_ENFORCER.md` - stary system banowania -- `deprecated/BAN_ENFORCER_TEST.md` - stare testy - -**Rekomendacja:** Te pliki są przestarzałe i nie powinny być używane. Rozważ: -1. Całkowite usunięcie katalogu `deprecated/` przed publikacją -2. Lub dokończenie czyszczenia tych plików - ---- - -## 🛡️ Zabezpieczenia Wdrożone - -### 1. Placeholders w Kodzie ✅ -Wszystkie aktywne pliki używają placeholderów zamiast rzeczywistych danych. - -### 2. Dokumentacja Bezpieczeństwa ✅ -- [SECURITY_PLACEHOLDERS.md](SECURITY_PLACEHOLDERS.md) - instrukcja użycia -- [SECURITY_AUDIT.md](hbbs-patch/SECURITY_AUDIT.md) - audyt bezpieczeństwa - -### 3. .gitignore Zaktualizowany ✅ -Dodano ochronę przed przypadkowym commit'em: -```gitignore -.env -.env.local -config.local.* -*_local.sh -*_local.ps1 -``` - -### 4. Szablony Konfiguracji ✅ -Użytkownicy mogą bezpiecznie tworzyć lokalne pliki konfiguracyjne. - ---- - -## ⚠️ Co Dalej? - -### Przed publikacją na GitHub: - -1. **Sprawdź historię git:** - ```bash - git log --all --full-history -- "*" | grep -i "192.168" - ``` - -2. **Jeśli znajdziesz wrażliwe dane w historii:** - ```bash - # UWAGA: To przepisze całą historię! - git filter-branch --tree-filter 'find . -type f -exec sed -i "s/192.168.0.110/YOUR_SERVER_IP/g" {} \;' HEAD - ``` - - Lub użyj BFG Repo-Cleaner: - ```bash - bfg --replace-text passwords.txt - git reflog expire --expire=now --all - git gc --prune=now --aggressive - ``` - -3. **Usuń deprecated/ przed publikacją:** - ```bash - git rm -r deprecated/ - git commit -m "Remove deprecated files with sensitive data" - ``` - -4. **Przeglądnij każdy plik przed push:** - ```bash - git diff --name-only origin/main - ``` - -5. **Weryfikacja finalna:** - ```bash - # Sprawdź czy nie ma więcej wrażliwych danych - grep -r "192.168.0.110" . - grep -r "unitronix@" . - ``` - ---- - -## ✅ Checklist Przed Publikacją - -- [ ] Usunięto katalog `deprecated/` lub wyczyszczono go z danych -- [ ] Sprawdzono historię git pod kątem wrażliwych danych -- [ ] Przeczytano [SECURITY_PLACEHOLDERS.md](SECURITY_PLACEHOLDERS.md) -- [ ] Zweryfikowano że wszystkie przykłady używają placeholderów -- [ ] Zaktualizowano README.md z linkiem do SECURITY_PLACEHOLDERS.md -- [ ] Przetestowano czy skrypty działają po zamianie placeholderów -- [ ] Dodano badge "Security" do README.md - ---- - -## 🔐 Bezpieczne Praktyki - -### DO: -✅ Używaj zmiennych środowiskowych -✅ Twórz lokalne pliki konfiguracyjne (z .gitignore) -✅ Regularnie sprawdzaj czy nie commit'ujesz wrażliwych danych -✅ Używaj SSH keys zamiast haseł - -### NIE RÓB: -❌ Nie commituj plików `.env` -❌ Nie wklejaj prawdziwych IP w issue/PR -❌ Nie udostępniaj zrzutów ekranu z danymi -❌ Nie hardcoduj credentials w kodzie - ---- - -## 📞 Kontakt - -Jeśli znajdziesz jakieś wrażliwe dane które pominąłem: -1. **NIE** zgłaszaj ich publicznie w issue -2. Wyślij prywatną wiadomość do maintainera -3. Lub stwórz private security advisory na GitHub - ---- - -**Status Bezpieczeństwa:** 🟢 BEZPIECZNY do publikacji (po wykonaniu checklist) - ---- - -*Raport wygenerowany automatycznie przez GitHub Copilot* diff --git a/archive/SECURITY_PLACEHOLDERS.md b/archive/SECURITY_PLACEHOLDERS.md deleted file mode 100644 index b1895fea..00000000 --- a/archive/SECURITY_PLACEHOLDERS.md +++ /dev/null @@ -1,219 +0,0 @@ -# Security Placeholders - Configuration Guide - -## 🔐 About Placeholders - -This repository contains **placeholders** instead of actual server credentials for security reasons. Before using any scripts or following the documentation, you must replace these placeholders with your actual values. - ---- - -## 📝 Placeholders Used - -| Placeholder | Description | Example Value | -|------------|-------------|---------------| -| `YOUR_SERVER_IP` | Your RustDesk server IP address | `192.168.1.100` or `server.example.com` | -| `YOUR_SSH_USER` | SSH username for server access | `admin`, `rustdesk`, etc. | - ---- - -## 🔄 How to Replace Placeholders - -### Option 1: Manual Replacement (Recommended for beginners) - -When you see a command like this: -```bash -ssh YOUR_SSH_USER@YOUR_SERVER_IP -``` - -Replace it with your actual values: -```bash -ssh admin@192.168.1.100 -``` - -### Option 2: Global Find & Replace (For advanced users) - -If you want to configure multiple files at once: - -**Windows (PowerShell):** -```powershell -# Navigate to project directory -cd C:\path\to\BetterDeskConsole - -# Replace server IP -(Get-ChildItem -Recurse -Include *.md,*.ps1,*.sh).ForEach{ - (Get-Content $_.FullName) -replace 'YOUR_SERVER_IP', '192.168.1.100' | - Set-Content $_.FullName -} - -# Replace SSH user -(Get-ChildItem -Recurse -Include *.md,*.ps1,*.sh).ForEach{ - (Get-Content $_.FullName) -replace 'YOUR_SSH_USER', 'admin' | - Set-Content $_.FullName -} -``` - -**Linux/macOS:** -```bash -# Replace server IP -find . -type f \( -name "*.md" -o -name "*.ps1" -o -name "*.sh" \) \ - -exec sed -i 's/YOUR_SERVER_IP/192.168.1.100/g' {} + - -# Replace SSH user -find . -type f \( -name "*.md" -o -name "*.ps1" -o -name "*.sh" \) \ - -exec sed -i 's/YOUR_SSH_USER/admin/g' {} + -``` - -### Option 3: Environment Variables (Most secure) - -Set environment variables instead of hardcoding values: - -**PowerShell:** -```powershell -$env:RUSTDESK_SERVER="192.168.1.100" -$env:RUSTDESK_USER="admin" - -# Use in scripts -.\update.ps1 -RemoteHost $env:RUSTDESK_SERVER -RemoteUser $env:RUSTDESK_USER -``` - -**Bash:** -```bash -export RUSTDESK_SERVER="192.168.1.100" -export RUSTDESK_USER="admin" - -# Use in scripts -ssh $RUSTDESK_USER@$RUSTDESK_SERVER -``` - ---- - -## 📂 Files Containing Placeholders - -The following files contain placeholders that may need to be replaced: - -### Documentation -- [README.md](README.md) -- [docs/UPDATE_GUIDE.md](docs/UPDATE_GUIDE.md) -- [docs/UPDATE_REFERENCE.md](docs/UPDATE_REFERENCE.md) -- [docs/QUICKSTART_UPDATE.md](docs/QUICKSTART_UPDATE.md) -- [hbbs-patch/QUICKSTART.md](hbbs-patch/QUICKSTART.md) -- [hbbs-patch/BAN_ENFORCEMENT.md](hbbs-patch/BAN_ENFORCEMENT.md) - -### Scripts -- [hbbs-patch/deploy.ps1](hbbs-patch/deploy.ps1) -- [hbbs-patch/deploy-v6.ps1](hbbs-patch/deploy-v6.ps1) -- [hbbs-patch/deploy-v8.sh](hbbs-patch/deploy-v8.sh) -- [hbbs-patch/test_ban_enforcement.ps1](hbbs-patch/test_ban_enforcement.ps1) -- [hbbs-patch/diagnose_ban.ps1](hbbs-patch/diagnose_ban.ps1) -- [dev_modules/update.ps1](dev_modules/update.ps1) -- [dev_modules/test_ban_api.sh](dev_modules/test_ban_api.sh) - ---- - -## ⚠️ Security Warnings - -### DO NOT: -- ❌ Commit files with real credentials to public repositories -- ❌ Share screenshots containing real IP addresses or usernames -- ❌ Push configuration files with actual server details - -### DO: -- ✅ Keep placeholders in version control -- ✅ Use environment variables for sensitive data -- ✅ Create a local `.env` file (add to `.gitignore`) -- ✅ Document your actual values in a secure password manager - ---- - -## 🔒 Best Practices - -### 1. Create a Local Configuration File - -Create `.env` file (excluded from git): -```bash -# .env - DO NOT COMMIT THIS FILE -RUSTDESK_SERVER_IP=192.168.1.100 -RUSTDESK_SSH_USER=admin -RUSTDESK_DB_PATH=/opt/rustdesk/db_v2.sqlite3 -``` - -### 2. Add to .gitignore - -```gitignore -# Sensitive configuration -.env -.env.local -config.local.ps1 -*_local.sh -``` - -### 3. Use Configuration Templates - -Create `config.template` files: -```powershell -# config.template.ps1 -$ServerIP = "YOUR_SERVER_IP" -$SSHUser = "YOUR_SSH_USER" -``` - -Then copy and customize: -```powershell -Copy-Item config.template.ps1 config.local.ps1 -# Edit config.local.ps1 with your values -``` - ---- - -## 🚀 Quick Start - -1. **Clone the repository:** - ```bash - git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git - cd Rustdesk-FreeConsole - ``` - -2. **Configure your credentials:** - - **Option A - Environment Variables (Recommended):** - ```powershell - # Windows - $env:RUSTDESK_SERVER="192.168.1.100" - $env:RUSTDESK_USER="admin" - ``` - - **Option B - Direct Replacement:** - Follow "Option 2: Global Find & Replace" above - -3. **Test connection:** - ```bash - ssh YOUR_SSH_USER@YOUR_SERVER_IP # Replace placeholders! - ``` - -4. **Run scripts:** - ```powershell - # After replacing placeholders - .\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER - ``` - ---- - -## 📞 Support - -If you have questions about configuration: -1. Check the [main README](README.md) -2. Review [UPDATE_GUIDE.md](docs/UPDATE_GUIDE.md) -3. See [Security Audit](hbbs-patch/SECURITY_AUDIT.md) - ---- - -## ✅ Verification Checklist - -Before running any script, verify: -- [ ] All `YOUR_SERVER_IP` replaced with actual IP -- [ ] All `YOUR_SSH_USER` replaced with actual username -- [ ] SSH connection works: `ssh YOUR_SSH_USER@YOUR_SERVER_IP` -- [ ] Server paths are correct: `/opt/rustdesk/`, `/opt/BetterDeskConsole/` -- [ ] No actual credentials committed to git - ---- - -**Remember:** Security is not just about technology—it's about practice. Always think before you commit! 🔐 diff --git a/archive/install.sh b/archive/install.sh deleted file mode 100644 index 515fe183..00000000 --- a/archive/install.sh +++ /dev/null @@ -1,427 +0,0 @@ -#!/bin/bash - -############################################################################# -# BetterDesk Console - Installation Script v8 -# -# This script installs the enhanced RustDesk HBBS/HBBR servers with -# bidirectional ban enforcement and web management console. -# -# Features: -# - Automatic backup of existing RustDesk installation -# - Precompiled HBBS/HBBR binaries with ban enforcement (no compilation needed) -# - Bidirectional ban checking (source + target devices) -# - Installs Flask web console with glassmorphism UI -# - Configures systemd services -# - Uses Google Material Icons (offline) -# -# Ban Enforcement Features (v8): -# - Prevents banned devices from initiating connections (source check) -# - Prevents connections to banned devices (target check) -# - Real-time database sync -# - Works for both P2P and relay connections -# -# Author: GitHub Copilot -# License: MIT -############################################################################# - -set -e # Exit on error - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Configuration -RUSTDESK_DIR="/opt/rustdesk" -BACKUP_DIR="/opt/rustdesk-backup-$(date +%Y%m%d-%H%M%S)" -CONSOLE_DIR="/opt/BetterDeskConsole" -TEMP_DIR="/tmp/betterdesk-install" -HBBS_API_PORT=21114 -VERSION="v8" # Current version with bidirectional ban enforcement - -# Helper functions -print_header() { - echo -e "\n${BLUE}========================================${NC}" - echo -e "${BLUE}$1${NC}" - echo -e "${BLUE}========================================${NC}\n" -} - -print_success() { - echo -e "${GREEN}✓ $1${NC}" -} - -print_error() { - echo -e "${RED}✗ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠ $1${NC}" -} - -print_info() { - echo -e "${BLUE}ℹ $1${NC}" -} - -check_root() { - if [[ $EUID -ne 0 ]]; then - print_error "This script must be run as root (use sudo)" - exit 1 - fi -} - -check_dependencies() { - print_header "Checking Dependencies" - - local missing_deps=() - - # Check for required commands (removed cargo - using precompiled binaries) - for cmd in python3 pip3 curl systemctl; do - if ! command -v $cmd &> /dev/null; then - missing_deps+=($cmd) - fi - done - - if [ ${#missing_deps[@]} -ne 0 ]; then - print_error "Missing dependencies: ${missing_deps[*]}" - echo "" - echo "Please install missing dependencies:" - echo " Ubuntu/Debian: sudo apt install python3 python3-pip curl systemd" - echo " CentOS/RHEL: sudo yum install python3 python3-pip curl systemd" - exit 1 - fi - - print_success "All dependencies found" -} - -backup_rustdesk() { - print_header "Backing Up Existing RustDesk Installation" - - if [ ! -d "$RUSTDESK_DIR" ]; then - print_warning "No existing RustDesk installation found at $RUSTDESK_DIR" - print_info "Will proceed with fresh installation" - return 0 - fi - - echo -e "${YELLOW}Found existing RustDesk installation${NC}" - echo "" - echo "Options:" - echo " 1) Create automatic backup to $BACKUP_DIR" - echo " 2) I have already created a manual backup" - echo " 3) Skip backup (not recommended)" - echo "" - read -p "Choose option [1-3]: " backup_choice - - case $backup_choice in - 1) - print_info "Creating backup..." - cp -r "$RUSTDESK_DIR" "$BACKUP_DIR" - print_success "Backup created at: $BACKUP_DIR" - ;; - 2) - print_info "Using manual backup" - ;; - 3) - print_warning "Skipping backup - YOU ARE RESPONSIBLE FOR ANY DATA LOSS" - read -p "Are you SURE? Type 'yes' to continue: " confirm - if [ "$confirm" != "yes" ]; then - print_error "Installation cancelled" - exit 1 - fi - ;; - *) - print_error "Invalid option" - exit 1 - ;; - esac -} - -install_binaries() { - print_header "Installing Enhanced HBBS/HBBR $VERSION" - - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local bin_dir="$script_dir/hbbs-patch/bin" - - if [ ! -f "$bin_dir/hbbs-$VERSION" ] || [ ! -f "$bin_dir/hbbr-$VERSION" ]; then - print_error "Precompiled binaries not found in: $bin_dir" - print_info "Expected files: hbbs-$VERSION, hbbr-$VERSION" - exit 1 - fi - - # Create RustDesk directory if it doesn't exist - mkdir -p "$RUSTDESK_DIR" - - # Stop existing services - print_info "Stopping RustDesk services..." - systemctl stop rustdesksignal.service 2>/dev/null || true - systemctl stop rustdeskrelay.service 2>/dev/null || true - pkill -9 hbbs 2>/dev/null || true - pkill -9 hbbr 2>/dev/null || true - sleep 2 - - # Backup existing binaries - if [ -f "$RUSTDESK_DIR/hbbs" ]; then - print_info "Backing up old hbbs..." - cp "$RUSTDESK_DIR/hbbs" "$RUSTDESK_DIR/hbbs.backup.$(date +%Y%m%d-%H%M%S)" - fi - - if [ -f "$RUSTDESK_DIR/hbbr" ]; then - print_info "Backing up old hbbr..." - cp "$RUSTDESK_DIR/hbbr" "$RUSTDESK_DIR/hbbr.backup.$(date +%Y%m%d-%H%M%S)" - fi - - # Install new binaries - print_info "Installing HBBS $VERSION (with bidirectional ban enforcement)..." - cp "$bin_dir/hbbs-$VERSION" "$RUSTDESK_DIR/hbbs" - chmod +x "$RUSTDESK_DIR/hbbs" - - print_info "Installing HBBR $VERSION..." - cp "$bin_dir/hbbr-$VERSION" "$RUSTDESK_DIR/hbbr" - chmod +x "$RUSTDESK_DIR/hbbr" - - print_success "Binaries installed successfully" - - # Restart services - print_info "Restarting RustDesk services..." - systemctl daemon-reload 2>/dev/null || true - systemctl start rustdesksignal.service 2>/dev/null || true - systemctl start rustdeskrelay.service 2>/dev/null || true - - # Wait for services to start - sleep 3 - - # Verify services - local services_ok=true - if systemctl is-active --quiet rustdesksignal.service; then - print_success "HBBS service is running" - else - print_warning "HBBS service not running (may need manual start)" - services_ok=false - fi - - if systemctl is-active --quiet rustdeskrelay.service 2>/dev/null; then - print_success "HBBR service is running" - else - print_info "HBBR service not configured (optional)" - fi - - # Display version info - echo "" - print_info "HBBS/HBBR version: $VERSION" - print_info "Features:" - echo " ✓ Bidirectional ban enforcement" - echo " ✓ Source device ban check (prevents banned devices from initiating connections)" - echo " ✓ Target device ban check (prevents connections to banned devices)" - echo " ✓ Real-time ban database sync" - echo "" -} - -clone_rustdesk_server() { - # This function is no longer needed - using precompiled binaries - print_info "Using precompiled binaries - skipping source clone" -} - -apply_patches() { - # This function is no longer needed - binaries are pre-patched - print_info "Binaries are pre-patched - skipping patch application" -} - -compile_hbbs() { - # This function is no longer needed - using precompiled binaries - print_info "Using precompiled binaries - skipping compilation" -} - -install_hbbs() { - # This function has been replaced by install_binaries() - # Kept for compatibility but redirects to new function - print_info "Redirecting to install_binaries()..." -} - -run_database_migrations() { - print_header "Running Database Migrations" - - local db_path="$RUSTDESK_DIR/db_v2.sqlite3" - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local migrations_dir="$script_dir/migrations" - - # Check if database exists - if [ ! -f "$db_path" ]; then - print_warning "Database not found at $db_path" - print_info "Database will be created automatically when HBBS starts" - print_info "Skipping migrations - they will be applied on first run" - return 0 - fi - - # Create backup of database - local backup_file="$db_path.backup-$(date +%Y%m%d-%H%M%S)" - print_info "Creating database backup..." - cp "$db_path" "$backup_file" - print_success "Database backed up to: $backup_file" - - # Check if migrations directory exists - if [ ! -d "$migrations_dir" ]; then - print_error "Migrations directory not found: $migrations_dir" - exit 1 - fi - - # Run v1.0.1 migration (soft delete) - print_info "Running migration v1.0.1 (soft delete)..." - if python3 "$migrations_dir/v1.0.1_soft_delete.py"; then - print_success "Migration v1.0.1 completed successfully" - else - print_warning "Migration v1.0.1 failed or already applied" - fi - - # Run v1.1.0 migration (device bans) - print_info "Running migration v1.1.0 (device bans)..." - if python3 "$migrations_dir/v1.1.0_device_bans.py"; then - print_success "Migration v1.1.0 completed successfully" - else - print_warning "Migration v1.1.0 failed or already applied" - fi - - print_success "Database migrations completed" - echo "" -} - -install_web_console() { - print_header "Installing Web Management Console" - - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local web_dir="$script_dir/web" - - if [ ! -d "$web_dir" ]; then - print_error "Web directory not found: $web_dir" - exit 1 - fi - - # Create console directory - mkdir -p "$CONSOLE_DIR" - - # Copy files - print_info "Copying web console files..." - cp -r "$web_dir"/* "$CONSOLE_DIR/" - - # Install Python dependencies - print_info "Installing Python dependencies..." - pip3 install -r "$CONSOLE_DIR/requirements.txt" - - # Create systemd service - print_info "Creating systemd service..." - cat > /etc/systemd/system/betterdesk.service < /dev/null; then - print_success "Web Console is accessible" - else - print_error "Web Console is not accessible" - fi -} - -cleanup() { - print_header "Cleaning Up" - - print_info "Removing temporary files..." - rm -rf "$TEMP_DIR" - - print_success "Cleanup completed" -} - -show_summary() { - print_header "Installation Complete!" - - echo -e "${GREEN}BetterDesk Console has been successfully installed!${NC}" - echo "" - echo "Access points:" - echo " • Web Console: http://$(hostname -I | awk '{print $1}'):5000" - echo " • HBBS API: http://localhost:$HBBS_API_PORT/api/health" - echo "" - echo "Services:" - echo " • HBBS: sudo systemctl status rustdesksignal.service" - echo " • Web Console: sudo systemctl status betterdesk.service" - echo "" - - if [ -d "$BACKUP_DIR" ]; then - echo "Backup location:" - echo " • $BACKUP_DIR" - echo "" - fi - - echo "Documentation:" - echo " • README.md in the installation directory" - echo " • GitHub: https://github.com/UNITRONIX/Rustdesk-FreeConsole" - echo "" - - print_info "Enjoy your enhanced RustDesk experience!" -} - -# Main installation flow -main() { - clear - print_header "BetterDesk Console Installer $VERSION" - echo "This script will install:" - echo " • Enhanced RustDesk HBBS/HBBR with bidirectional ban enforcement" - echo " • Web Management Console with Material Design" - echo " • Real-time device status monitoring" - echo "" - echo "Installation method: Precompiled binaries (no compilation required)" - echo "" - - check_root - check_dependencies - backup_rustdesk - install_binaries - run_database_migrations - install_web_console - test_installation - cleanup - show_summary -} - -# Run main function -main "$@" diff --git a/archive/restore_hbbs.sh b/archive/restore_hbbs.sh deleted file mode 100644 index 9465ab0b..00000000 --- a/archive/restore_hbbs.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# Przywróć oryginalny HBBS (bez patcha) i restartuj serwis - -echo "Stopping rustdesksignal service..." -systemctl stop rustdesksignal - -echo "Restoring original HBBS binary..." -cp /opt/rustdesk/hbbs.backup /opt/rustdesk/hbbs -chmod +x /opt/rustdesk/hbbs - -echo "Starting rustdesksignal service..." -systemctl start rustdesksignal - -echo "Checking service status..." -systemctl status rustdesksignal --no-pager -l - -echo "" -echo "HBBS restored successfully!" -echo "Ban Enforcer daemon will continue to provide 95% ban protection." diff --git a/archive/update.sh b/archive/update.sh deleted file mode 100644 index 8ebaa71e..00000000 --- a/archive/update.sh +++ /dev/null @@ -1,399 +0,0 @@ -#!/bin/bash - -############################################################################# -# BetterDesk Console - Update Script (v1.1.0) -# -# This script updates existing BetterDesk installation to version 1.1.0 -# with device banning system and soft delete functionality. -# -# Features: -# - Automatic database backup before migration -# - Executes database migrations (v1.0.1 soft delete + v1.1.0 bans) -# - Updates web console files (app.py, script.js, index.html) -# - Restarts BetterDesk service -# - Verifies installation -# -# Requirements: -# - Existing BetterDesk Console installation -# - Root/sudo access -# - Python 3.x with Flask -# -# Usage: -# sudo ./update.sh [OPTIONS] -# sudo ./update.sh --rustdesk-dir /custom/path/rustdesk -# sudo ./update.sh --console-dir /custom/path/console -# -# Author: GitHub Copilot -# License: MIT -############################################################################# - -set -e # Exit on error - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' # No Color - -# Default configuration -RUSTDESK_DIR="/opt/rustdesk" -CONSOLE_DIR="/opt/BetterDeskConsole" - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - case $1 in - --rustdesk-dir) - RUSTDESK_DIR="$2" - shift 2 - ;; - --console-dir) - CONSOLE_DIR="$2" - shift 2 - ;; - -h|--help) - echo "Usage: sudo ./update.sh [OPTIONS]" - echo "" - echo "Options:" - echo " --rustdesk-dir PATH Path to RustDesk installation (default: /opt/rustdesk)" - echo " --console-dir PATH Path to BetterDesk Console (default: /opt/BetterDeskConsole)" - echo " -h, --help Show this help message" - echo "" - echo "Examples:" - echo " sudo ./update.sh" - echo " sudo ./update.sh --rustdesk-dir /custom/rustdesk" - echo " sudo ./update.sh --console-dir /var/www/betterdesk" - exit 0 - ;; - *) - echo "Unknown option: $1" - echo "Use --help for usage information" - exit 1 - ;; - esac -done - -# Set derived paths -DB_PATH="$RUSTDESK_DIR/db_v2.sqlite3" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BACKUP_DIR="/opt/betterdesk-backup-$(date +%Y%m%d-%H%M%S)" - -# Helper functions -print_header() { - echo -e "\n${BLUE}========================================${NC}" - echo -e "${BLUE}$1${NC}" - echo -e "${BLUE}========================================${NC}\n" -} - -print_success() { - echo -e "${GREEN}✓ $1${NC}" -} - -print_error() { - echo -e "${RED}✗ $1${NC}" - exit 1 -} - -print_warning() { - echo -e "${YELLOW}⚠ $1${NC}" -} - -print_info() { - echo -e "${CYAN}→ $1${NC}" -} - -# Check if running as root -if [[ $EUID -ne 0 ]]; then - print_error "This script must be run as root (use sudo)" -fi - -print_header "BetterDesk Console - Update to v1.1.0" - -echo -e "${CYAN}Configuration:${NC}" -echo " RustDesk directory: $RUSTDESK_DIR" -echo " Console directory: $CONSOLE_DIR" -echo " Database path: $DB_PATH" -echo "" -echo -e "${CYAN}This update includes:${NC}" -echo " • Soft delete system for devices (v1.0.1)" -echo " • Device banning system (v1.1.0)" -echo " • Enhanced UI with ban controls" -echo " • Input validation and security improvements" -echo "" -echo -e "${YELLOW}⚠ WARNING: This will modify the database and restart services${NC}" -echo "" -read -p "Continue with update? [y/N]: " -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo "Update cancelled." - exit 0 -fi - -# Check if BetterDesk is installed -print_header "Step 1: Checking Installation" - -if [ ! -d "$CONSOLE_DIR" ]; then - print_error "BetterDesk Console not found at $CONSOLE_DIR" -fi -print_success "Found BetterDesk Console" - -if [ ! -f "$DB_PATH" ]; then - print_error "Database not found at $DB_PATH" -fi -print_success "Found database" - -# Check if service exists -if ! systemctl list-unit-files | grep -q "betterdesk.service"; then - print_warning "BetterDesk service not found, will skip restart" - SERVICE_EXISTS=false -else - print_success "Found BetterDesk service" - SERVICE_EXISTS=true -fi - -# Create backup -print_header "Step 2: Creating Backup" - -mkdir -p "$BACKUP_DIR" -print_info "Backup directory: $BACKUP_DIR" - -# Backup database -print_info "Backing up database..." -cp "$DB_PATH" "$BACKUP_DIR/db_v2.sqlite3.backup" -print_success "Database backed up" - -# Backup web files -print_info "Backing up web console files..." -if [ -f "$CONSOLE_DIR/app.py" ]; then - cp "$CONSOLE_DIR/app.py" "$BACKUP_DIR/app.py.backup" -fi -if [ -f "$CONSOLE_DIR/static/script.js" ]; then - cp "$CONSOLE_DIR/static/script.js" "$BACKUP_DIR/script.js.backup" -fi -if [ -f "$CONSOLE_DIR/templates/index.html" ]; then - cp "$CONSOLE_DIR/templates/index.html" "$BACKUP_DIR/index.html.backup" -fi -print_success "Web files backed up" - -echo "" -print_success "Backup completed: $BACKUP_DIR" - -# Execute migrations -print_header "Step 3: Database Migration" - -print_info "Running migration v1.0.1 (soft delete)..." -if [ -f "$SCRIPT_DIR/migrations/v1.0.1_soft_delete.py" ]; then - python3 "$SCRIPT_DIR/migrations/v1.0.1_soft_delete.py" </dev/null || true -print_success "Permissions set" - -# Restart service -if [ "$SERVICE_EXISTS" = true ]; then - print_header "Step 5: Restarting Service" - - print_info "Stopping BetterDesk service..." - systemctl stop betterdesk - sleep 2 - - print_info "Starting BetterDesk service..." - systemctl start betterdesk - sleep 3 - - if systemctl is-active --quiet betterdesk; then - print_success "BetterDesk service is running" - else - print_error "Failed to start BetterDesk service" - fi -else - print_header "Step 5: Service Restart (Skipped)" - print_warning "Please restart BetterDesk manually" -fi - -# Verify installation -print_header "Step 6: Verification" - -# Check database schema -print_info "Verifying database schema..." -COLUMNS=$(sqlite3 "$DB_PATH" "PRAGMA table_info(peer);" | wc -l) -if [ "$COLUMNS" -ge 16 ]; then - print_success "Database schema updated (16+ columns)" -else - print_warning "Database may not have all new columns ($COLUMNS found)" -fi - -# Check if web console is accessible (if service is running) -if [ "$SERVICE_EXISTS" = true ]; then - print_info "Checking web console..." - sleep 2 - if curl -s http://localhost:5000/api/stats > /dev/null 2>&1; then - print_success "Web console is responding" - else - print_warning "Web console may not be responding on port 5000" - fi -fi - -# Install Ban Enforcer (optional but recommended) -print_header "Step 7: Install Ban Enforcer (Optional)" - -echo -e "${CYAN}Ban Enforcer blocks banned devices from connecting to RustDesk.${NC}" -echo -e "${CYAN}RustDesk server doesn't check is_banned column by default.${NC}" -echo "" -echo -e "Without enforcer: Devices show as banned in UI but can still connect" -echo -e "With enforcer: Devices are actively blocked from connecting" -echo "" - -if [ -f "$CONSOLE_DIR/ban_enforcer.py" ] && systemctl is-active --quiet rustdesk-ban-enforcer 2>/dev/null; then - print_info "Ban Enforcer is already installed and running" - read -p "Do you want to update it? (y/N): " UPDATE_ENFORCER - if [[ "$UPDATE_ENFORCER" =~ ^[Yy]$ ]]; then - print_info "Updating Ban Enforcer..." - cp ban_enforcer.py "$CONSOLE_DIR/" - chmod +x "$CONSOLE_DIR/ban_enforcer.py" - systemctl restart rustdesk-ban-enforcer - print_success "Ban Enforcer updated" - fi -else - read -p "Install Ban Enforcer? (y/N): " INSTALL_ENFORCER - - if [[ "$INSTALL_ENFORCER" =~ ^[Yy]$ ]]; then - if [ -f "ban_enforcer.py" ] && [ -f "rustdesk-ban-enforcer.service" ]; then - print_info "Installing Ban Enforcer..." - - # Copy files - cp ban_enforcer.py "$CONSOLE_DIR/" - chmod +x "$CONSOLE_DIR/ban_enforcer.py" - - # Configure and install service - sed "s|Environment=\"DB_PATH=/opt/rustdesk/db_v2.sqlite3\"|Environment=\"DB_PATH=$DB_PATH\"|g" \ - rustdesk-ban-enforcer.service > /tmp/rustdesk-ban-enforcer.service - cp /tmp/rustdesk-ban-enforcer.service /etc/systemd/system/ - chmod 644 /etc/systemd/system/rustdesk-ban-enforcer.service - rm /tmp/rustdesk-ban-enforcer.service - - # Enable and start - systemctl daemon-reload - systemctl enable rustdesk-ban-enforcer - systemctl start rustdesk-ban-enforcer - - sleep 2 - if systemctl is-active --quiet rustdesk-ban-enforcer; then - print_success "Ban Enforcer installed and running" - else - print_warning "Ban Enforcer installed but failed to start" - echo "Check logs: sudo journalctl -u rustdesk-ban-enforcer -n 50" - fi - else - print_warning "Ban Enforcer files not found in current directory" - print_info "You can install it later using: ./install_ban_enforcer.sh" - fi - else - print_info "Ban Enforcer installation skipped" - print_warning "Banned devices will show in UI but may still connect" - echo "" - echo "To install later, run:" - echo " sudo ./install_ban_enforcer.sh" - echo "" - echo "Or manually:" - echo " sudo cp ban_enforcer.py $CONSOLE_DIR/" - echo " sudo cp rustdesk-ban-enforcer.service /etc/systemd/system/" - echo " sudo systemctl daemon-reload" - echo " sudo systemctl enable --now rustdesk-ban-enforcer" - fi -fi - -# Final summary -print_header "Update Complete!" - -echo -e "${GREEN}✓ Database migrated to v1.1.0${NC}" -echo -e "${GREEN}✓ Web console files updated${NC}" -echo -e "${GREEN}✓ Backup created: $BACKUP_DIR${NC}" -if [ "$SERVICE_EXISTS" = true ]; then - echo -e "${GREEN}✓ Service restarted${NC}" -fi -if systemctl is-active --quiet rustdesk-ban-enforcer 2>/dev/null; then - echo -e "${GREEN}✓ Ban Enforcer active${NC}" -fi -echo "" -echo -e "${CYAN}New Features:${NC}" -echo " • Soft delete for devices (is_deleted, deleted_at, updated_at)" -echo " • Device banning system (is_banned, banned_at, banned_by, ban_reason)" -echo " • Ban/Unban buttons in web interface" -echo " • Enhanced input validation and security" -echo " • Banned devices statistics card" -if systemctl is-active --quiet rustdesk-ban-enforcer 2>/dev/null; then - echo " • Active connection blocking for banned devices ✓" -fi -echo "" -echo -e "${CYAN}Access the console:${NC}" -echo " http://localhost:5000" -echo "" -if systemctl is-active --quiet rustdesk-ban-enforcer 2>/dev/null; then - echo -e "${CYAN}Ban Enforcer Status:${NC}" - echo " Service: $(systemctl is-active rustdesk-ban-enforcer)" - echo " Logs: sudo journalctl -u rustdesk-ban-enforcer -f" - echo "" -fi -echo -e "${YELLOW}Rollback Instructions (if needed):${NC}" -echo " 1. Stop service: sudo systemctl stop betterdesk" -echo " 2. Restore database: sudo cp $BACKUP_DIR/db_v2.sqlite3.backup $DB_PATH" -echo " 3. Restore files: sudo cp $BACKUP_DIR/*.backup $CONSOLE_DIR/" -echo " 4. Start service: sudo systemctl start betterdesk" -echo "" -print_success "Update completed successfully!" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f5f8be44..81d6539a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,168 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.0] - 2026-01-11 + +### 🔐 Security & Authentication Update + +**Major Security Enhancement**: Added comprehensive authentication system for both web console and HBBS HTTP API, plus LAN access capabilities. + +### Added + +#### Authentication System +- **User Login System**: + - bcrypt password hashing (cost 12) + - 24-hour session tokens stored in database + - Login page with secure password handling + - Session validation on all protected routes + +- **Role-Based Access Control (RBAC)**: + - Three roles: admin, operator, viewer + - Role-specific permissions for device management + - Admin-only access to user management + - Audit logging for all actions + +- **User Management Panel**: + - Create/edit/delete users + - Activate/deactivate accounts + - Role assignment + - Password reset functionality + - User list with status indicators + +#### API Security +- **X-API-Key Authentication**: + - 64-character random API keys generated during installation + - Middleware verification on all HBBS API endpoints + - Stored securely in `/opt/rustdesk/.api_key` with 600 permissions + - Automatic key injection by web console + - 401 Unauthorized for missing/invalid keys + +- **LAN Access**: + - HBBS API now binds to `0.0.0.0:21120` (accessible on LAN) + - Web console binds to `0.0.0.0:5000` (accessible on LAN) + - Protected by authentication (API key + user login) + - External tools can use API with X-API-Key header + +#### Web Console Features +- **Sidebar Navigation**: Modern sidebar menu with icon-based navigation +- **Password-Protected Key Access**: Public key requires password verification +- **Password Change**: Users can change their passwords in Settings +- **Removed Devices Tab**: Simplified interface, devices managed on Dashboard +- **Expanded About Page**: Credits, GitHub links, open source information +- **Enhanced Settings**: Password change, session management, preferences + +#### Installation & Updates +- **API Key Generation**: Automatic during installation via `openssl rand` +- **Environment Variables**: HBBS_API_KEY, FLASK_HOST, FLASK_PORT, FLASK_DEBUG +- **Service Configuration**: Updated systemd services with new environment vars +- **Update Script**: `update-to-v1.4.0.sh` for existing installations +- **Backward Compatibility**: Preserves existing configurations during update + +### Changed + +- **API Binding**: Changed from `127.0.0.1` (localhost-only) to `0.0.0.0` (LAN-accessible) +- **Authentication Required**: All API endpoints now require X-API-Key header +- **Web Console Access**: Requires user login instead of open access +- **Session Management**: 24-hour sessions instead of permanent access +- **Database Schema**: Added `users`, `sessions`, `audit_log` tables + +### Security + +- ✅ **Authentication**: All services protected by authentication +- ✅ **Encrypted Passwords**: bcrypt hashing for user passwords +- ✅ **API Key Auth**: X-API-Key header prevents unauthorized API access +- ✅ **Session Tokens**: Time-limited tokens with automatic expiration +- ✅ **Audit Trail**: All administrative actions logged +- ✅ **Secure Storage**: API keys with 600 permissions +- ✅ **XSS Protection**: Input sanitization throughout +- ✅ **SQL Injection Prevention**: Parameterized queries only +- ✅ **CSRF Protection**: Session-based validation + +### Technical Details + +- **Files Modified**: + - `hbbs-patch/src/http_api.rs` - Added X-API-Key middleware + - `web/app_v14.py` - Added authentication, user management, API key loading + - `web/auth.py` - Password hashing, session management, user CRUD + - `web/templates/login.html` - New login page + - `web/templates/index_v14.html` - Sidebar navigation, user management UI + - `web/static/script_v14.js` - User management, password change, key verification + - `install-improved.sh` - API key generation and service configuration + - `update-to-v1.4.0.sh` - Update script for existing installations + +- **Database Migration**: `migrations/v1.4.0_auth_system.py` + - Creates `users` table with roles + - Creates `sessions` table for session management + - Creates `audit_log` table for action tracking + - Generates default admin user + +- **API Endpoints** (all require X-API-Key): + - `GET /api/health` - Health check + - `GET /api/peers` - List all peers with online status + +- **Web Endpoints** (all require login except `/login`): + - `GET /login` - Login page + - `POST /login` - Authenticate user + - `GET /logout` - End session + - `GET /` - Dashboard + - `GET /api/users` - List users (admin only) + - `POST /api/users` - Create user (admin only) + - `PUT /api/users/` - Update user (admin only) + - `DELETE /api/users/` - Delete user (admin only) + - `POST /api/change-password` - Change password + - `POST /api/verify-password` - Verify password for key access + +### Migration Path + +**For new installations:** +```bash +sudo ./install-improved.sh +``` +- Automatically generates API key +- Configures services for LAN access +- Creates default admin user + +**For existing installations:** +```bash +sudo ./update-to-v1.4.0.sh +``` +- Creates automatic backup +- Runs database migration +- Generates API key if not exists +- Updates systemd services +- Preserves existing configuration +- Rollback capability on failure + +### Documentation + +- Updated `README.md` with authentication instructions +- Updated `hbbs-patch/README.md` with API security documentation +- Updated `hbbs-patch/SECURITY_AUDIT.md` with v1.4.0 security review +- Updated `docs/PORT_SECURITY.md` with LAN access notes +- Added API key retrieval instructions + +### Known Issues + +- 26 Pylance type warnings in `app_v14.py` for `log_audit()` parameters (non-critical) +- API key must be manually distributed to external tools + +### Upgrade Notes + +**Breaking Changes:** +- Existing API clients must add `X-API-Key` header +- Web console now requires user login +- Sessions expire after 24 hours + +**Recommended Actions After Upgrade:** +1. Login with default admin credentials (shown after migration) +2. Change admin password immediately +3. Delete `/opt/BetterDeskConsole/admin_credentials.txt` +4. Create additional users with appropriate roles +5. Update external tools with API key from `/opt/rustdesk/.api_key` +6. Configure firewall for LAN access if needed + +--- + ## [1.3.0-secure] - 2026-01-10 ### 🔒 Security Update: Localhost-Only API Binding diff --git a/docs/DEPRECATION_NOTICE.md b/docs/DEPRECATION_NOTICE.md deleted file mode 100644 index 27fb9c88..00000000 --- a/docs/DEPRECATION_NOTICE.md +++ /dev/null @@ -1,98 +0,0 @@ -# ⚠️ Deprecation Notice - -## Ban Enforcer (Python Daemon) - DEPRECATED - -**Effective Date**: January 5, 2026 -**Version**: 1.2.0 - ---- - -### Status: OBSOLETE - -The Python-based `ban_enforcer.py` daemon has been **replaced** by native HBBS ban checking as of version 1.2.0. - -### What Changed? - -**Old System (v1.1.0):** -- External Python daemon running every 2 seconds -- Cleared UUID/PK of banned devices -- ~95% effectiveness due to race conditions -- Required systemd service management -- Additional process overhead - -**New System (v1.2.0):** -- Native ban check integrated into HBBS server -- Checks `is_banned` during device registration -- **100% effectiveness** - no race conditions -- No external processes needed -- Minimal performance impact (~1ms per check) - -### Migration Guide - -If you're using Ban Enforcer from v1.1.0: - -1. **Install Patched HBBS Binary** - ```bash - cd hbbs-patch - ./build.sh - sudo ./install.sh - ``` - -2. **Stop Ban Enforcer Service** - ```bash - sudo systemctl stop rustdesk-ban-enforcer - sudo systemctl disable rustdesk-ban-enforcer - ``` - -3. **Verify Ban Functionality** - - Ban a test device through web console - - Try to connect from that device - - Connection should be rejected with "UUID mismatch" error - -4. **Remove Service (Optional)** - ```bash - sudo rm /etc/systemd/system/rustdesk-ban-enforcer.service - sudo systemctl daemon-reload - ``` - -### Why Keep the Files? - -The Ban Enforcer code remains in the repository for: -- **Reference**: Understanding the evolution of the ban system -- **Rollback**: Emergency fallback if needed -- **Educational**: Learning how external enforcement worked -- **Historical**: Documenting the project's development - -### Recommendation - -**Do NOT use Ban Enforcer for new installations.** Always use the native HBBS ban check (v1.2.0+). - -For existing users: Migrate to the native system at your earliest convenience for improved reliability and performance. - ---- - -### Files Affected - -These files are now **deprecated** but kept for reference: -- `ban_enforcer.py` - Python daemon script -- `install_ban_enforcer.sh` - Installation script -- `rustdesk-ban-enforcer.service` - Systemd service file -- `BAN_ENFORCER.md` - Documentation -- `BAN_ENFORCER_TEST.md` - Testing guide - -### Support - -Ban Enforcer will **NOT** receive: -- Bug fixes -- Security updates -- Feature enhancements -- Compatibility updates - -All future development focuses on the native HBBS ban check system. - ---- - -**For questions or issues**, please refer to: -- Native ban system: [hbbs-patch/BAN_CHECK_PATCH.md](hbbs-patch/BAN_CHECK_PATCH.md) -- Quick setup: [hbbs-patch/QUICKSTART.md](hbbs-patch/QUICKSTART.md) -- General issues: [GitHub Issues](https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues) diff --git a/docs/DEVELOPMENT_ROADMAP.md b/docs/DEVELOPMENT_ROADMAP.md deleted file mode 100644 index fd9e6d02..00000000 --- a/docs/DEVELOPMENT_ROADMAP.md +++ /dev/null @@ -1,869 +0,0 @@ -# BetterDesk Console - Enhanced Features Roadmap & Implementation Plan - -## Project Vision - -Transform BetterDesk Console from a monitoring tool into a **full-featured Enterprise Remote Access Management System** with advanced security, device management, and access control capabilities. - ---- - -## 🎯 Development Phases - -### Phase 1: Bug Fixes & Stability (v1.0.1) - **1-2 Days** -**Status**: Ready to implement -**Complexity**: Low - -#### Features -- [ ] Fix device deletion functionality -- [ ] Improve device ID change handling (add warnings) -- [ ] Add confirmation dialogs for destructive operations -- [ ] Better error messages and user feedback -- [ ] Input validation on all forms - -#### Technical Implementation -- **Files to modify**: `web/app.py`, `web/static/script.js` -- **Database**: Add soft-delete flag instead of hard delete -- **UI**: Bootstrap modal confirmations - ---- - -### Phase 2: Authentication & Basic Security (v1.1) - **3-5 Days** -**Status**: Architecture defined -**Complexity**: Medium - -#### Features -- [ ] User authentication system (login/logout) -- [ ] Session management with Flask-Login -- [ ] Password hashing (bcrypt) -- [ ] Role-based access control (Admin, Viewer, Operator) -- [ ] User management page -- [ ] Audit logs for all actions - -#### Technical Implementation -```python -# New database tables -users(id, username, password_hash, role, created_at, last_login) -sessions(id, user_id, token, expires_at, ip_address) -audit_logs(id, user_id, action, target, timestamp, details) -``` - -**Security Considerations**: -- HTTPS enforcement (self-signed cert generation) -- CSRF protection (Flask-WTF) -- Rate limiting (Flask-Limiter) -- Secure session cookies (httpOnly, secure, sameSite) - ---- - -### Phase 3: Device Banning System (v1.2) - **5-7 Days** -**Status**: Architecture designed -**Complexity**: High - -#### Features -- [ ] **Bidirectional Ban Logic**: - - Banned device cannot connect to ANY device - - Any device cannot connect to banned device - - Ban applies to both peer and target - -- [ ] Web console ban management: - - Ban/unban devices from UI - - Temporary bans (time-limited) - - Ban reasons and notes - - Ban history log - -- [ ] HBBS-level enforcement: - - Check ban status before allowing connection - - Real-time ban list synchronization - - Connection rejection with custom message - -#### HBBS Modifications Required - -**New File**: `src/ban_manager.rs` -```rust -pub struct BanManager { - banned_ids: Arc>>, - db: Database, -} - -impl BanManager { - // Check if connection should be allowed - pub async fn is_connection_allowed(&self, peer_id: &str, target_id: &str) -> bool { - let banned = self.banned_ids.read().await; - // Block if either peer or target is banned - !banned.contains(peer_id) && !banned.contains(target_id) - } - - // Real-time ban list updates - pub async fn sync_from_db(&self) { - // Periodic database sync - } -} -``` - -**Modify**: `src/rendezvous_server.rs` -```rust -// In handle_request or connection logic -if !ban_manager.is_connection_allowed(&peer_id, &target_id).await { - return Err(ConnectionBlocked::Banned); -} -``` - -**New HTTP API Endpoints**: -``` -POST /api/device/ban - Ban a device -POST /api/device/unban - Unban a device -GET /api/bans - List all banned devices -``` - -**Database Schema**: -```sql -CREATE TABLE device_bans ( - id INTEGER PRIMARY KEY, - device_id TEXT NOT NULL, - banned_by TEXT, - reason TEXT, - banned_at TIMESTAMP, - expires_at TIMESTAMP NULL, - is_active BOOLEAN DEFAULT 1 -); - -CREATE INDEX idx_device_bans_device_id ON device_bans(device_id); -CREATE INDEX idx_device_bans_active ON device_bans(is_active); -``` - ---- - -### Phase 4: API Key Management (v1.3) - **4-6 Days** -**Status**: Design phase -**Complexity**: Medium-High - -#### Features -- [ ] **API Key System**: - - Generate unique API keys for devices - - Key-based authentication (instead of ID only) - - Key rotation and expiration - - Scoped permissions per key - -- [ ] **Quick Connect Codes**: - - Generate short-lived connection codes (6-8 chars) - - QR code generation for mobile - - One-time use codes - - Time-limited validity (15 minutes default) - -#### Technical Design - -**API Key Format**: -``` -BDC_[type]_[random32chars]_[checksum4] - -Example: BDC_DEV_a3f9c2d8e1b4f7a2c5d8e1b4f7a2_9x4k -``` - -**Quick Connect Code Format**: -``` -[A-Z0-9]{8} - Human readable, expires in 15min -Example: K7M9P2X5 -``` - -**Database Schema**: -```sql -CREATE TABLE api_keys ( - id INTEGER PRIMARY KEY, - key_hash TEXT NOT NULL UNIQUE, - device_id TEXT NOT NULL, - name TEXT, - scopes TEXT, -- JSON array: ["connect", "view", "control"] - created_at TIMESTAMP, - expires_at TIMESTAMP, - last_used_at TIMESTAMP, - is_active BOOLEAN DEFAULT 1 -); - -CREATE TABLE quick_connect_codes ( - id INTEGER PRIMARY KEY, - code TEXT NOT NULL UNIQUE, - device_id TEXT NOT NULL, - created_at TIMESTAMP, - expires_at TIMESTAMP, - used_at TIMESTAMP NULL, - used_by TEXT NULL -); -``` - -**HBBS Modifications**: -```rust -// New authentication middleware -pub async fn verify_api_key(key: &str) -> Option { - // Hash key, check database - // Return device permissions -} -``` - -**Web UI Features**: -- API key management page -- Copy/revoke/regenerate buttons -- Usage statistics per key -- QR code generator for quick connect - ---- - -### Phase 5: Device Validation & Approval (v1.4) - **3-4 Days** -**Status**: Design phase -**Complexity**: Medium - -#### Features -- [ ] **New Device Approval Workflow**: - - First-time connections require admin approval - - Pending devices list in web console - - Approve/reject with reason - - Automatic approval rules (whitelist subnets) - -- [ ] **Device Fingerprinting**: - - Hardware ID verification - - OS/version tracking - - Detect device ID changes - - Alert on suspicious behavior - -#### HBBS Modifications - -**New File**: `src/device_validator.rs` -```rust -pub struct DeviceValidator { - pending_devices: Arc>>, - db: Database, -} - -pub struct PendingDevice { - id: String, - fingerprint: String, - first_seen: Instant, - ip_address: String, - os_info: String, -} - -impl DeviceValidator { - pub async fn validate_device(&self, device: &Device) -> ValidationResult { - // Check if device is approved - // Check fingerprint matches - // Check for suspicious changes - } -} -``` - -**Modified Connection Flow**: -``` -1. Device connects → Generate fingerprint -2. Check if approved in database -3. If not approved → Add to pending_devices -4. If approved → Check fingerprint matches -5. If mismatch → Flag for re-validation -``` - -**Web API**: -``` -GET /api/devices/pending - List pending approvals -POST /api/devices/approve/:id - Approve device -POST /api/devices/reject/:id - Reject device -``` - ---- - -### Phase 6: Security Hardening (v1.5) - **5-7 Days** -**Status**: Security audit required -**Complexity**: High - -#### Features -- [ ] **Per-Device Security Keys**: - - Unique encryption keys per device pair - - Key exchange protocol - - Encrypted connection metadata - - Certificate pinning - -- [ ] **Network Security**: - - IP whitelist/blacklist per device - - Geofencing (country-based restrictions) - - Connection rate limiting - - DDoS protection - -- [ ] **Monitoring & Alerts**: - - Failed connection attempts tracking - - Brute force detection - - Email/webhook notifications - - Security dashboard - -#### Technical Implementation - -**Device-Specific Keys**: -```sql -CREATE TABLE device_keys ( - id INTEGER PRIMARY KEY, - device_id TEXT NOT NULL, - public_key TEXT NOT NULL, - private_key_encrypted TEXT, -- Only if server-managed - key_type TEXT, -- 'ed25519', 'rsa2048' - created_at TIMESTAMP, - rotated_at TIMESTAMP, - expires_at TIMESTAMP -); -``` - -**Connection Rules**: -```sql -CREATE TABLE connection_rules ( - id INTEGER PRIMARY KEY, - device_id TEXT NOT NULL, - rule_type TEXT, -- 'ip_whitelist', 'ip_blacklist', 'geo_allow', 'geo_deny' - rule_value TEXT, -- IP range, country code, etc. - priority INTEGER, - is_active BOOLEAN DEFAULT 1 -); -``` - -**HBBS Integration**: -```rust -// In connection handler -let security_check = SecurityManager::validate_connection( - &peer_id, - &target_id, - &client_ip, - &client_key -).await; - -if !security_check.allowed { - log_security_event(&security_check); - return Err(SecurityViolation); -} -``` - ---- - -### Phase 7: Modified Desktop Client (v2.0) - **15-30 Days** -**Status**: Research phase -**Complexity**: Very High - -⚠️ **WARNING**: This requires deep RustDesk client modification and compilation - -#### Features -- [ ] **API Connection Mode**: - - Connect using API key instead of ID - - Quick connect code support - - QR code scanner for mobile - - Server-managed connection presets - -- [ ] **Enhanced Client UI**: - - BetterDesk branding option - - Pre-configured server settings - - Connection history sync - - Favorite devices from web console - -- [ ] **Client-Side Security**: - - Hardware-based device fingerprint - - Certificate validation - - Encrypted credentials storage - - Auto-lock on idle - -#### Development Challenges - -**RustDesk Client Architecture**: -- Flutter-based UI (Dart) -- Rust backend (sciter-rs or flutter_rust_bridge) -- Native platform code (C++ for Windows/Linux) -- Complex build system (requires: Rust, Flutter, LLVM, Visual Studio) - -**Required Modifications**: - -1. **Connection Protocol** (`src/client.rs`): -```rust -// Add API key authentication -pub enum ConnectionMethod { - DeviceId(String), - ApiKey(String), - QuickConnect(String), -} - -impl Client { - pub async fn connect_with_api_key(&mut self, key: &str) -> Result<()> { - // Validate key with HBBS - // Retrieve target device info - // Establish connection - } -} -``` - -2. **UI Modifications** (`flutter/lib/`): -```dart -// Add quick connect input -class QuickConnectScreen extends StatelessWidget { - // QR scanner - // Code input field - // Recent connections from server -} -``` - -3. **Build Process**: -```bash -# Windows -./build.py --skip-cargo -vcpkg install ... - -# Linux -cargo build --release -flutter build linux -``` - -**Branding Customization**: -- Replace icons/logos -- Custom color scheme -- "Powered by RustDesk" attribution -- BetterDesk splash screen - ---- - -### Phase 8: Smart Client Generator (v2.1) - **7-10 Days** -**Status**: Concept phase -**Complexity**: High - -#### Features -- [ ] **Portable Client Generator**: - - Generate pre-configured client executables - - Single-server mode (only connects to your HBBS) - - Embedded API key - - Custom branding per client - - No manual configuration needed - -- [ ] **Client Types**: - - **Support Client**: For technicians, includes admin features - - **User Client**: For end users, simplified interface - - **Kiosk Client**: Auto-connect mode, full-screen, unattended - -#### Technical Approach - -**Option A: Configuration File Embedding** -```bash -# Generate client with embedded config -./generate_client.sh \ - --server hbbs.example.com \ - --api-key BDC_DEV_... \ - --branding ./branding.json \ - --output betterdesk-client.exe -``` - -**Option B: Server-Side Generation** -- Web UI: "Generate Client" button -- Backend builds custom client on-demand -- Downloads pre-configured executable -- Requires: CI/CD pipeline, build servers, code signing - -**Implementation** (Realistic Approach): -```python -# In web console -@app.route('/api/generate-client', methods=['POST']) -def generate_client(): - # Create custom config file - config = { - "custom-rendezvous-server": "hbbs.example.com:21116", - "api-key": request.json['api_key'], - "client-name": request.json['name'], - } - - # Package with client binary - # Return download link - return jsonify({"download_url": "/downloads/client-xyz.exe"}) -``` - -**Simpler Alternative** (More Realistic): -- Generate configuration file only (.toml or .json) -- User downloads standard RustDesk client -- Imports configuration file -- Client applies settings automatically - ---- - -## 🔐 Security Considerations - -### Current Security Posture -✅ Local-only by default (127.0.0.1) -✅ SQLite database (file-based) -✅ No external dependencies -❌ No authentication -❌ No encryption at rest -❌ No HTTPS enforcement - -### Enhanced Security Roadmap - -#### Immediate (v1.1) -- [ ] Flask session security (secret key) -- [ ] HTTPS with self-signed cert -- [ ] Password hashing (bcrypt, cost=12) -- [ ] Input sanitization (prevent SQL injection) -- [ ] XSS protection (Content Security Policy) -- [ ] CSRF tokens on all forms - -#### Short-term (v1.2-1.3) -- [ ] API rate limiting (per IP, per user) -- [ ] Audit logging (all admin actions) -- [ ] Database encryption at rest (SQLCipher) -- [ ] Secrets management (dotenv, Vault) -- [ ] Security headers (HSTS, X-Frame-Options) - -#### Long-term (v2.0+) -- [ ] Certificate-based authentication -- [ ] Hardware security module (HSM) support -- [ ] Multi-factor authentication (TOTP) -- [ ] Intrusion detection system (IDS) -- [ ] Compliance reporting (SOC 2, ISO 27001) - -### Network Architecture - -**Default Setup** (Local Only): -``` -[Web Console :5000] ← localhost only -[HBBS API :21114] ← localhost only -[HBBS Server :21115-21119] ← 0.0.0.0 (RustDesk clients) -``` - -**Production Setup** (Exposed): -``` -Internet - ↓ -[Reverse Proxy: Nginx/Caddy] - ├─ :443 → Web Console :5000 (HTTPS + Auth) - └─ :21115-21119 → HBBS (RustDesk protocol) - -Internal Network - ↓ -[HBBS API :21114] ← localhost only (no external access) -``` - -**Firewall Rules**: -```bash -# Allow RustDesk clients (required) -ufw allow 21115:21119/tcp - -# Web console (only if needed externally) -ufw allow 443/tcp - -# Block API port (internal only) -ufw deny 21114/tcp -``` - ---- - -## 📊 Development Timeline Estimates - -### Realistic Timeline (Single Developer) -- **Phase 1 (v1.0.1)**: 2 days -- **Phase 2 (v1.1)**: 5 days -- **Phase 3 (v1.2)**: 7 days -- **Phase 4 (v1.3)**: 6 days -- **Phase 5 (v1.4)**: 4 days -- **Phase 6 (v1.5)**: 7 days -- **Phase 7 (v2.0)**: 30 days (client modification) -- **Phase 8 (v2.1)**: 10 days - -**Total**: ~71 days (~3.5 months of full-time development) - -### Team-Based Timeline (3 developers) -- **Backend Developer**: HBBS modifications, API -- **Frontend Developer**: Web console UI/UX -- **Client Developer**: Desktop client modifications - -**Total**: ~30-40 days (~2 months with parallel work) - ---- - -## 🛠️ Technical Stack Analysis - -### Current Stack -- **Backend**: Python 3.8+, Flask 3.0 -- **Frontend**: HTML5, CSS3, JavaScript (ES6+) -- **HBBS**: Rust 1.70+, Axum, Tokio -- **Database**: SQLite 3 -- **Icons**: Material Icons (offline) - -### Required Additions for Advanced Features - -#### Python Packages -```txt -Flask-Login==0.6.3 # User authentication -Flask-WTF==1.2.1 # CSRF protection -Flask-Limiter==3.5.0 # Rate limiting -bcrypt==4.1.2 # Password hashing -PyJWT==2.8.0 # JWT tokens -cryptography==41.0.7 # Encryption utilities -qrcode==7.4.2 # QR code generation -Pillow==10.1.0 # Image processing -python-dotenv==1.0.0 # Environment variables -``` - -#### Rust Crates (HBBS) -```toml -[dependencies] -# Existing -axum = { version = "0.7", features = ["http1", "json", "tokio"] } -tower-http = { version = "0.5", features = ["cors"] } -tokio = { version = "1", features = ["full"] } - -# New additions -jsonwebtoken = "9.2" # JWT validation -argon2 = "0.5" # Password hashing -sha2 = "0.10" # Hashing -hex = "0.4" # Hex encoding -uuid = { version = "1.6", features = ["v4"] } # UUID generation -chrono = "0.4" # Timestamp handling -``` - ---- - -## 💾 Database Schema Evolution - -### Current Schema -```sql --- From RustDesk original -CREATE TABLE peer ( - guid TEXT PRIMARY KEY, - id TEXT NOT NULL UNIQUE, - uuid TEXT, - pk BLOB, - created_at INTEGER, - user TEXT, - status INTEGER, - note TEXT, - info TEXT -); -``` - -### Enhanced Schema (v1.x) - -```sql --- Users table (v1.1) -CREATE TABLE users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL UNIQUE, - email TEXT, - password_hash TEXT NOT NULL, - role TEXT NOT NULL DEFAULT 'viewer', -- admin, operator, viewer - is_active BOOLEAN DEFAULT 1, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - last_login TIMESTAMP, - failed_login_attempts INTEGER DEFAULT 0, - locked_until TIMESTAMP -); - --- Sessions table (v1.1) -CREATE TABLE sessions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - token TEXT NOT NULL UNIQUE, - ip_address TEXT, - user_agent TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NOT NULL, - last_activity TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); - --- Audit logs (v1.1) -CREATE TABLE audit_logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER, - action TEXT NOT NULL, -- login, ban_device, approve_device, etc. - target_type TEXT, -- device, user, api_key - target_id TEXT, - details TEXT, -- JSON - ip_address TEXT, - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL -); - --- Device bans (v1.2) -CREATE TABLE device_bans ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - device_id TEXT NOT NULL, - banned_by INTEGER, - reason TEXT, - ban_type TEXT DEFAULT 'manual', -- manual, auto, temporary - banned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP, - unbanned_at TIMESTAMP, - unbanned_by INTEGER, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (banned_by) REFERENCES users(id), - FOREIGN KEY (unbanned_by) REFERENCES users(id) -); - -CREATE INDEX idx_device_bans_device_id ON device_bans(device_id, is_active); - --- API keys (v1.3) -CREATE TABLE api_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - key_id TEXT NOT NULL UNIQUE, -- Public identifier - key_hash TEXT NOT NULL UNIQUE, -- SHA256 of actual key - device_id TEXT, - name TEXT, - description TEXT, - scopes TEXT, -- JSON array - created_by INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP, - last_used_at TIMESTAMP, - usage_count INTEGER DEFAULT 0, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (device_id) REFERENCES peer(id) ON DELETE CASCADE, - FOREIGN KEY (created_by) REFERENCES users(id) -); - --- Quick connect codes (v1.3) -CREATE TABLE quick_connect_codes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - code TEXT NOT NULL UNIQUE, - device_id TEXT NOT NULL, - created_by INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP NOT NULL, - used_at TIMESTAMP, - used_by TEXT, -- Device ID that used the code - ip_address TEXT, - FOREIGN KEY (device_id) REFERENCES peer(id) ON DELETE CASCADE, - FOREIGN KEY (created_by) REFERENCES users(id) -); - --- Device approvals (v1.4) -CREATE TABLE device_approvals ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - device_id TEXT NOT NULL, - fingerprint TEXT, - os_info TEXT, - ip_address TEXT, - requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - approved_at TIMESTAMP, - approved_by INTEGER, - rejected_at TIMESTAMP, - rejected_by INTEGER, - rejection_reason TEXT, - status TEXT DEFAULT 'pending', -- pending, approved, rejected - FOREIGN KEY (approved_by) REFERENCES users(id), - FOREIGN KEY (rejected_by) REFERENCES users(id) -); - --- Connection rules (v1.5) -CREATE TABLE connection_rules ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - device_id TEXT, - rule_type TEXT NOT NULL, -- ip_whitelist, ip_blacklist, geo_allow, geo_deny, time_window - rule_value TEXT NOT NULL, - priority INTEGER DEFAULT 0, - created_by INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (device_id) REFERENCES peer(id) ON DELETE CASCADE, - FOREIGN KEY (created_by) REFERENCES users(id) -); - --- Security events (v1.5) -CREATE TABLE security_events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - event_type TEXT NOT NULL, -- failed_auth, banned_connection, suspicious_activity - severity TEXT DEFAULT 'info', -- info, warning, critical - device_id TEXT, - ip_address TEXT, - details TEXT, -- JSON - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - acknowledged_at TIMESTAMP, - acknowledged_by INTEGER, - FOREIGN KEY (acknowledged_by) REFERENCES users(id) -); - --- Device keys (v1.5) -CREATE TABLE device_keys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - device_id TEXT NOT NULL, - key_type TEXT NOT NULL, -- ed25519, rsa2048 - public_key TEXT NOT NULL, - private_key_encrypted TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - rotated_at TIMESTAMP, - expires_at TIMESTAMP, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY (device_id) REFERENCES peer(id) ON DELETE CASCADE -); -``` - ---- - -## 🚦 Implementation Priority Matrix - -### Must Have (Critical) -1. ✅ Bug fixes (v1.0.1) -2. ✅ Authentication (v1.1) -3. ✅ Device banning (v1.2) - -### Should Have (High Value) -4. ✅ API keys (v1.3) -5. ✅ Device validation (v1.4) -6. ✅ Security hardening (v1.5) - -### Nice to Have (Future) -7. ⚠️ Modified client (v2.0) - Complex, requires client build expertise -8. ⚠️ Client generator (v2.1) - Depends on v2.0 - ---- - -## 🎬 Immediate Next Steps - -### What I Can Implement Now (Today/Tomorrow) - -**Phase 1: v1.0.1 Bug Fixes** ✅ Ready -- Fix device deletion -- Add confirmation dialogs -- Better error messages -- Input validation - -Shall I proceed with implementing Phase 1 now? This includes: -1. Soft-delete mechanism for devices -2. Confirmation modals with JavaScript -3. Improved error handling in Flask -4. Input validation and sanitization - ---- - -## 📞 Questions for You - -Before proceeding with advanced features: - -1. **Client Modification**: Are you prepared to compile RustDesk client from source? This requires: - - Windows: Visual Studio 2019+, LLVM, vcpkg - - Linux: GCC, Flutter SDK, Rust toolchain - - ~50GB disk space for build dependencies - -2. **Security Requirements**: - - Will this be deployed on public internet or private network only? - - Do you need compliance certifications (SOC 2, ISO)? - - Multi-factor authentication required? - -3. **Scale**: - - How many devices do you expect? (10s, 100s, 1000s?) - - How many concurrent connections? - - Single server or distributed? - -4. **Development Resources**: - - Solo developer or team? - - Timeline expectations (weeks/months)? - - Budget for infrastructure (build servers, testing devices)? - ---- - -## 📝 Recommendation - -**Start with Phases 1-6** (v1.0.1 - v1.5): -- These are achievable without client modification -- Provide 80% of requested functionality -- Solid foundation for future enhancements -- Can be completed in ~30-40 days - -**Defer Phases 7-8** (v2.0+) until: -- Core features are stable -- User feedback collected -- Client build infrastructure established -- Clear ROI for client modifications - -Shall I proceed with implementing **Phase 1 (v1.0.1)** now? diff --git a/docs/GITHUB_RELEASE_CHECKLIST.md b/docs/GITHUB_RELEASE_CHECKLIST.md deleted file mode 100644 index 8668f01f..00000000 --- a/docs/GITHUB_RELEASE_CHECKLIST.md +++ /dev/null @@ -1,214 +0,0 @@ -# 📋 GitHub Release Checklist - v1.2.0 - -## Pre-Release Checks - -### ✅ Code & Documentation -- [x] All code changes committed -- [x] CHANGELOG.md updated with v1.2.0 entry -- [x] README.md updated with new features -- [x] VERSION file created (1.2.0) -- [x] RELEASE_NOTES_v1.2.0.md created -- [x] DEPRECATION_NOTICE.md created -- [x] HBBS patch documentation complete -- [x] Build scripts tested and functional - -### ✅ Testing -- [x] Web console loads and functions -- [x] Device listing works -- [x] Ban/unban functionality tested -- [x] HBBS patch compiles successfully -- [x] Native ban check works (100% effectiveness) -- [x] API endpoints respond correctly -- [x] Database migrations tested - -### ✅ Files & Structure -- [x] .gitignore comprehensive (Python, Rust, OS, IDE) -- [x] LICENSE file present -- [x] README.md complete with badges -- [x] requirements.txt present -- [x] All scripts executable permissions set -- [x] No sensitive data in repository -- [x] No large binaries committed - -### ✅ HBBS Patch -- [x] build.sh script complete -- [x] install.sh script exists -- [x] QUICKSTART.md guide written -- [x] BAN_CHECK_PATCH.md technical doc -- [x] Patch files (database_patch.rs, peer_patch.rs) -- [x] README.md in hbbs-patch/ - ---- - -## GitHub Actions - -### 1. Prepare Repository - -```bash -# Ensure you're on main branch -git checkout main - -# Pull latest changes (if team environment) -git pull origin main - -# Verify all changes staged -git status - -# Final commit if needed -git add . -git commit -m "Release v1.2.0 - Native HBBS Ban Check" - -# Tag the release -git tag -a v1.2.0 -m "Version 1.2.0 - Native Guardian" - -# Push with tags -git push origin main --tags -``` - -### 2. Create GitHub Release - -Go to: `https://github.com/YOUR_USERNAME/betterdesk-console/releases/new` - -**Tag version**: `v1.2.0` - -**Release title**: `v1.2.0 - Native Guardian 🔒` - -**Description**: Copy from `RELEASE_NOTES_v1.2.0.md` - -**Attachments**: None needed (source code auto-attached) - -**Options**: -- [x] Set as the latest release -- [ ] Set as a pre-release (only if beta) -- [ ] Create a discussion for this release (optional) - -### 3. Post-Release - -- [ ] Verify release appears on GitHub -- [ ] Test installation from fresh clone -- [ ] Update project website (if applicable) -- [ ] Announce on social media/forums -- [ ] Monitor GitHub issues for bug reports - ---- - -## Key Files Checklist - -### Root Directory -- [x] README.md (updated) -- [x] CHANGELOG.md (v1.2.0 entry) -- [x] LICENSE (MIT) -- [x] VERSION (1.2.0) -- [x] RELEASE_NOTES_v1.2.0.md -- [x] DEPRECATION_NOTICE.md -- [x] CONTRIBUTING.md -- [x] .gitignore -- [x] install.sh -- [x] update.sh -- [x] check_database.py - -### Web Console (web/) -- [x] app.py (Flask backend) -- [x] requirements.txt -- [x] templates/index.html -- [x] static/style.css -- [x] static/script.js - -### Migrations (migrations/) -- [x] v1.0.1_soft_delete.py -- [x] v1.1.0_device_bans.py -- [x] README.md (if exists) - -### HBBS Patch (hbbs-patch/) -- [x] build.sh (automated build) -- [x] install.sh (installation script) -- [x] QUICKSTART.md (user guide) -- [x] BAN_CHECK_PATCH.md (technical docs) -- [x] README.md (patch overview) -- [x] database_patch.rs (code snippet) -- [x] peer_patch.rs (code snippet) - -### Deprecated (kept for reference) -- [x] ban_enforcer.py -- [x] install_ban_enforcer.sh -- [x] rustdesk-ban-enforcer.service -- [x] BAN_ENFORCER.md -- [x] BAN_ENFORCER_TEST.md - ---- - -## Release Notes Preview - -Copy this for GitHub Release: - ---- - -## 🔥 Major Update: Native HBBS Ban Check - -Version 1.2.0 replaces the external Python ban enforcer with native ban checking integrated directly into the HBBS server. - -### Key Features -- ✅ **100% Reliable**: No race conditions or timing windows -- ✅ **Zero Maintenance**: No external daemon to manage -- ✅ **Better Performance**: Minimal overhead (~1ms per check) -- ✅ **Native Integration**: Built into HBBS source code - -### What's Changed -- Device bans now enforced at registration level in HBBS -- Ban Enforcer (Python daemon) deprecated -- New build system for compiling patched HBBS -- Complete documentation and migration guides - -### Upgrade Path -Existing users (v1.1.0) should: -1. Build patched HBBS binary -2. Install on server -3. Disable Ban Enforcer service - -See [RELEASE_NOTES_v1.2.0.md](RELEASE_NOTES_v1.2.0.md) for full details. - ---- - -## Security & Compatibility -- ✅ No database schema changes -- ✅ Backward compatible API -- ✅ Works with all RustDesk client versions -- ✅ Tested with RustDesk Server v1.1.14 - ---- - -## Documentation -- 📖 [Quick Start Guide](hbbs-patch/QUICKSTART.md) -- 🔧 [HBBS Patch Technical Docs](hbbs-patch/BAN_CHECK_PATCH.md) -- 📝 [Full Changelog](CHANGELOG.md) -- ⚠️ [Migration Guide](DEPRECATION_NOTICE.md) - ---- - -**Full Changelog**: v1.1.0...v1.2.0 - ---- - -## Notes for Repository Maintainer - -### Before Publishing: -1. Replace `YOUR_USERNAME` in URLs with actual GitHub username -2. Update repository links in documentation -3. Ensure all scripts have executable permissions -4. Test fresh installation on clean system -5. Verify all documentation links work - -### After Publishing: -1. Monitor GitHub Issues for immediate bugs -2. Be ready to create hotfix (v1.2.1) if critical issues found -3. Update any external documentation/websites -4. Consider creating demo video/screenshots - -### Future Considerations (v1.3.0+): -- WebSocket support for real-time updates -- Device groups/tags -- Advanced filtering and sorting -- Device statistics/charts -- Multi-user authentication -- Restore from soft delete -- Bulk operations diff --git a/docs/GITHUB_RELEASE_GUIDE.md b/docs/GITHUB_RELEASE_GUIDE.md deleted file mode 100644 index b7cfabc5..00000000 --- a/docs/GITHUB_RELEASE_GUIDE.md +++ /dev/null @@ -1,339 +0,0 @@ -# ✅ Projekt Gotowy do Publikacji na GitHub - -**Data weryfikacji:** 10 stycznia 2026 -**Status:** ✅ READY TO RELEASE - ---- - -## 📦 Nowa Wersja - -### v1.3.0-secure - -**Poprzednia wersja:** v1.2.0-v8 -**Nowa wersja:** v1.3.0-secure - -**Dlaczego taka nazwa:** -- `1.3.0` - Semantic versioning (minor update z breaking change) -- `-secure` - Wyraźnie wskazuje na fokus bezpieczeństwa -- Jasny komunikat dla użytkowników o charakterze wydania - -**Alternatywne nazwy (do wyboru):** -- `v1.3.0-secure` ⭐ **POLECANA** - najlepsza dla tego release -- `v1.3.0-localhost` - alternatywa skupiona na funkcjonalności -- `v1.3.0` - tradycyjna bez suffixu - ---- - -## 🎯 Główne Zmiany - -### Bezpieczeństwo API - -**Port:** 21114 → 21120 -- Unika konfliktu z RustDesk Pro -- Jasno wskazuje na localhost-only service - -**Binding:** 0.0.0.0 → 127.0.0.1 -- API dostępne TYLKO z localhost -- Zero ekspozycji sieciowej -- Connection refused z sieci (bezpieczne) - -**Nowe parametry:** -- `--api-port 21120` - konfiguracja przez CLI -- SSH tunnel support dla zdalnego dostępu - ---- - -## ✅ Weryfikacja Bezpieczeństwa - -### Kompletna ✓ - -- [x] **Brak IP 192.168.0.110** w README.md (0 wystąpień) -- [x] **Brak haseł/kluczy** w dokumentacji (0 wystąpień) -- [x] **Username "UNITRONIX"** tylko w URL GitHub (6 wystąpień - OK) -- [x] **Baza danych** NIE w repozytorium (tylko kod obsługi) -- [x] **Pliki .gitignore** poprawnie skonfigurowane (*.db, *.sqlite3) -- [x] **Prywatne dane** całkowicie wyczyszczone - -### Archiwalne pliki (bezpieczne) - -Znalezione wystąpienia IP/username tylko w: -- `SECURITY_CLEANUP_REPORT.md` - dokumentacja procesu czyszczenia -- `RELEASE_READY.md` - instrukcje weryfikacji -- `archive/` - folder archiwalny - -Wszystkie są **dokumentacją bezpieczeństwa**, nie faktycznymi danymi. - ---- - -## 📦 Zawartość Release - -### Binaria (23.67 MB total) - -**Linux (x86_64):** -- `hbbs-v8-api` - 9.59 MB (SHA256: 7B09A6C0...) -- `hbbr-v8-api` - 4.73 MB (SHA256: DF1B3FD3...) -- Data: 10.01.2026 10:25 UTC -- Zawiera: localhost-only binding, port 21120 - -**Windows (x64):** -- `hbbs-v8-api.exe` - 6.58 MB (SHA256: EE1AB9C3...) -- `hbbr-v8-api.exe` - 2.76 MB (SHA256: 37F452AE...) -- Data: 10.01.2026 04:42 UTC -- Kompatybilne z nową konfiguracją - -**Lokalizacja:** `hbbs-patch/bin-with-api/` - -### Dokumentacja - -**Zaktualizowane:** -- ✅ `VERSION` → 1.3.0-secure -- ✅ `CHANGELOG.md` → nowy wpis z v1.3.0-secure -- ✅ `README.md` → badge wersji + security badge -- ✅ `hbbs-patch/bin-with-api/CHECKSUMS.md` → sumy SHA256 -- ✅ `RELEASE_NOTES_v1.3.0.md` → kompletne release notes - -**Istniejące (bez zmian):** -- `README.md` - 656 linii (zaktualizowany port 21120) -- `CHANGELOG.md` - 397 linii (z nowym entry) -- `PORT_SECURITY.md` - 337 linii -- `CONTRIBUTING.md` - dokumentacja dla contributors -- `LICENSE` - MIT License - ---- - -## 🚀 Kroki Publikacji - -### 1. Przegląd Końcowy (opcjonalny) - -```bash -# Sprawdź stan Git -git status - -# Przejrzyj zmiany -git diff - -# Zweryfikuj binaria -sha256sum hbbs-patch/bin-with-api/hbbs-v8-api -``` - -### 2. Commit Zmian - -```bash -# Dodaj wszystkie pliki -git add . - -# Commit z opisem -git commit -m "Release v1.3.0-secure: Localhost-only API binding - -Major Changes: -- Changed API port from 21114 to 21120 -- API now binds to localhost (127.0.0.1) only -- Added --api-port CLI parameter -- Updated all documentation -- Added CHECKSUMS.md for binary verification - -Security: -- Zero network exposure for API -- Connection refused from external networks -- No private data in documentation -- SSH tunnel support for remote access - -Binaries: -- Updated Linux binaries (10.01.2026 10:25) -- Windows binaries compatible (retained from v1.2.0-v8) -- Total size: 23.67 MB -- SHA256 checksums included - -Documentation: -- Updated README with security badges -- New RELEASE_NOTES_v1.3.0.md -- Complete PORT_SECURITY.md guide -- Migration instructions from v1.2.0-v8" -``` - -### 3. Utwórz Tag - -```bash -# Utwórz annotated tag -git tag -a v1.3.0-secure -m "Release v1.3.0-secure - -Localhost-Only API Binding - -This release focuses on security enhancement: -- API port changed from 21114 to 21120 -- API binds exclusively to localhost (127.0.0.1) -- Zero network exposure -- SSH tunnel support for remote access - -Full release notes: RELEASE_NOTES_v1.3.0.md -" - -# Weryfikuj tag -git tag -l -n9 v1.3.0-secure -``` - -### 4. Push do GitHub - -```bash -# Push commits -git push origin main - -# Push tags -git push origin --tags - -# Lub wszystko razem -git push origin main --tags -``` - -### 5. Utwórz GitHub Release - -**Na stronie GitHub:** - -1. Idź do: **Releases** → **Create a new release** - -2. **Tag version:** `v1.3.0-secure` - -3. **Release title:** `v1.3.0-secure - Localhost-Only API Binding` - -4. **Description:** (skopiuj z RELEASE_NOTES_v1.3.0.md) - -```markdown -## 🔒 Security Enhancement Release - -### What's New - -**API Port:** 21114 → 21120 -**API Binding:** 0.0.0.0 → 127.0.0.1 (localhost only) - -This release eliminates network exposure of the HTTP API. - -### Key Features - -✅ Zero network exposure - API accessible only from localhost -✅ SSH tunnel support for remote access -✅ No port forwarding needed for 21120 -✅ Updated binaries with security code -✅ Complete documentation with migration guide - -### Installation - -**Linux:** -```bash -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -sudo ./install-improved.sh -``` - -**Windows:** -```powershell -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -.\install-improved.ps1 # Run as Administrator -``` - -### Upgrade from v1.2.0-v8 - -Automatic: -```bash -cd Rustdesk-FreeConsole -git pull -sudo ./install-improved.sh -``` - -### Full Documentation - -- [Complete Release Notes](RELEASE_NOTES_v1.3.0.md) -- [Changelog](CHANGELOG.md) -- [Port Security Guide](PORT_SECURITY.md) -- [Binary Checksums](hbbs-patch/bin-with-api/CHECKSUMS.md) -``` - -5. **Attach Binaries** (opcjonalnie): - - Można dodać binaria jako assets - - Lub pozostawić w repozytorium (już są w bin-with-api/) - -6. **Publish release** - ---- - -## 📊 Statystyki Projektu - -### Pliki - -``` -📁 BetterDeskConsole/ -├── 📄 README.md (656 linii) -├── 📄 CHANGELOG.md (397 linii) -├── 📄 VERSION (1.3.0-secure) -├── 📄 LICENSE (MIT) -├── 📄 CONTRIBUTING.md -├── 📄 PORT_SECURITY.md (337 linii) -├── 📄 RELEASE_NOTES_v1.3.0.md (nowy) -├── 📁 hbbs-patch/ -│ ├── 📁 bin-with-api/ (4 binaria, 23.67 MB) -│ │ └── CHECKSUMS.md (nowy) -│ ├── 📁 src/ (kod źródłowy Rust) -│ └── 📄 README.md, build.sh, deploy-v8.sh -├── 📁 web/ -│ ├── app.py (konsola Flask) -│ ├── 📁 templates/ (HTML) -│ └── 📁 static/ (CSS, JS, Material Icons) -├── 📁 docs/ (dokumentacja) -├── 📁 migrations/ (skrypty migracji bazy) -└── 📁 dev_modules/ (narzędzia deweloperskie) -``` - -### Rozmiar - -- **Binaria:** 23.67 MB -- **Całość projektu:** ~30-35 MB (z dokumentacją) -- **Web assets:** Offline-ready (Material Icons included) - ---- - -## 🎉 Co Dalej - -### Po Publikacji - -1. **Ogłoszenie:** - - Dodaj post na GitHub Discussions - - Powiadom użytkowników o bezpieczeństwie - -2. **Monitorowanie:** - - Sprawdzaj GitHub Issues - - Odpowiadaj na pytania o migrację - -3. **Social Media** (opcjonalnie): - - Tweet o security update - - Post na Reddit r/rustdesk - -### Przyszłe Wersje - -**v1.4.0** (sugestie): -- API authentication (JWT tokens) -- Rate limiting dla API -- HTTPS dla web console -- Audit logging - ---- - -## 📞 Kontakt - -**Issues:** https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues -**Discussions:** https://github.com/UNITRONIX/Rustdesk-FreeConsole/discussions - ---- - -## ✅ Checklist Finalna - -- [x] VERSION zaktualizowany -- [x] CHANGELOG zaktualizowany -- [x] README zaktualizowany (badges) -- [x] CHECKSUMS.md utworzony -- [x] RELEASE_NOTES_v1.3.0.md utworzony -- [x] Binaria zweryfikowane (SHA256) -- [x] Bezpieczeństwo sprawdzone (no private data) -- [x] Dokumentacja kompletna -- [x] Git ready (clean state) - -**🎉 PROJEKT GOTOWY DO PUBLIKACJI! 🎉** diff --git a/docs/INSTALLATION_V1.4.0.md b/docs/INSTALLATION_V1.4.0.md new file mode 100644 index 00000000..24a2872c --- /dev/null +++ b/docs/INSTALLATION_V1.4.0.md @@ -0,0 +1,354 @@ +# 🚀 BetterDesk Console v1.4.0 - Installation & Update Guide + +## 📋 What's New in v1.4.0 + +### 🔐 Authentication System +- **User login with username/password** +- **Session management** (24-hour sessions) +- **Password hashing** with bcrypt +- **Default admin account** created automatically + +### 👥 Role-Based Access Control +- **Admin**: Full access (manage users, devices, settings) +- **Operator**: Can ban/unban, edit devices, view audit log +- **Viewer**: Read-only access + +### 🎨 New UI Features +- **Sidebar navigation menu** with glassmorphism design +- **Responsive design** for mobile/tablet +- **User profile display** in sidebar +- **Better page organization** + +### 🔒 Security Improvements +- **All API endpoints protected** with authentication +- **Audit logging** for all operations +- **XSS protection** with MarkupSafe +- **Session expiry** and automatic cleanup +- **CSRF protection ready** (can be enabled) + +--- + +## 🆕 New Installation + +### Quick Install (Recommended) + +```bash +# Download and run installer +curl -O https://raw.githubusercontent.com/UNITRONIX/Rustdesk-FreeConsole/main/install-improved.sh +chmod +x install-improved.sh +sudo ./install-improved.sh +``` + +The installer will: +1. Install RustDesk HBBS/HBBR servers (if not present) +2. Install web console with authentication +3. Create database with auth tables +4. Generate default admin credentials +5. Configure systemd services + +--- + +## 🔄 Updating from v1.3.0 or older + +### Automatic Update (Recommended) + +```bash +# Download update script +cd /path/to/Rustdesk-FreeConsole +chmod +x update-to-v1.4.0.sh +sudo ./update-to-v1.4.0.sh +``` + +### What the Update Script Does: + +1. **Detects current version** automatically +2. **Creates backup** of: + - Web console files + - Database + - Configuration +3. **Installs new dependencies**: + - `bcrypt` (password hashing) + - `markupsafe` (XSS protection) +4. **Updates web files**: + - New `auth.py` module + - Updated `app.py` with auth endpoints + - New `login.html` template + - Updated `index.html` with sidebar + - New CSS and JavaScript files +5. **Runs database migration**: + - Adds `users` table + - Adds `sessions` table + - Adds `audit_log` table + - Creates default admin user +6. **Restarts services** +7. **Shows admin credentials** (if new) + +### Manual Update Steps + +If you prefer manual update: + +```bash +# 1. Backup +sudo cp -r /opt/BetterDeskConsole /opt/BetterDeskConsole.backup +sudo cp /opt/rustdesk/db_v2.sqlite3 /opt/rustdesk/db_v2.sqlite3.backup + +# 2. Install dependencies +sudo pip3 install bcrypt markupsafe --break-system-packages + +# 3. Copy new files +cd /path/to/Rustdesk-FreeConsole +sudo cp web/auth.py /opt/BetterDeskConsole/web/ +sudo cp web/app_v14.py /opt/BetterDeskConsole/web/app.py +sudo cp web/templates/login.html /opt/BetterDeskConsole/web/templates/ +sudo cp web/templates/index_v14.html /opt/BetterDeskConsole/web/templates/index.html +sudo cp web/static/sidebar.css /opt/BetterDeskConsole/web/static/ +sudo cp web/static/sidebar.js /opt/BetterDeskConsole/web/static/ +sudo cp web/static/script_v14.js /opt/BetterDeskConsole/web/static/script.js + +# 4. Run migration +sudo python3 migrations/v1.4.0_auth_system.py + +# 5. Restart service +sudo systemctl restart betterdesk +``` + +--- + +## 🔑 First Login + +After installation/update, you'll receive default admin credentials: + +``` +======================================== +DEFAULT ADMIN CREDENTIALS: +======================================== +Username: admin +Password: +======================================== +``` + +**⚠️ IMPORTANT:** +1. **Save these credentials** in a secure location +2. **Login immediately** and change the password +3. **Delete credentials file**: `sudo rm /opt/BetterDeskConsole/admin_credentials.txt` + +### Accessing the Console + +```bash +# Local access (on server) +http://localhost:5000 + +# Remote access (via SSH tunnel - RECOMMENDED) +ssh -L 8080:localhost:5000 user@your-server +# Then open: http://localhost:8080 +``` + +--- + +## 👥 User Management + +### Creating Additional Users (Admin Only) + +You can create additional users via Python console: + +```python +cd /opt/BetterDeskConsole/web +python3 -c " +from auth import create_user, ROLE_ADMIN, ROLE_OPERATOR, ROLE_VIEWER + +# Create operator +create_user('operator1', 'SecurePassword123', ROLE_OPERATOR) + +# Create viewer +create_user('viewer1', 'SecurePassword123', ROLE_VIEWER) + +print('Users created successfully') +" +``` + +### User Roles Explained + +| Role | Permissions | +|------|-------------| +| **Admin** | Full access: manage users, devices, settings, view audit log | +| **Operator** | Ban/unban devices, edit device info, view audit log | +| **Viewer** | Read-only: view devices and statistics | + +--- + +## 🔒 Security Best Practices + +### 1. Change Default Password + +``` +1. Login with default credentials +2. Click your name in sidebar +3. Go to Settings +4. Click "Change Password" +5. Enter current password and new password +``` + +### 2. Use SSH Tunnel (Recommended) + +**Never expose port 5000 to the internet!** + +```bash +# From your local machine +ssh -L 8080:localhost:5000 user@your-server + +# Keep this terminal open +# Access console at: http://localhost:8080 +``` + +### 3. Firewall Configuration + +```bash +# Block external access to console +sudo ufw deny 5000 + +# Allow only SSH +sudo ufw allow 22 + +# Allow RustDesk ports +sudo ufw allow 21115:21117/tcp +sudo ufw allow 21116/udp +``` + +### 4. Regular Backups + +```bash +# Backup database +sudo cp /opt/rustdesk/db_v2.sqlite3 /backup/db_v2.sqlite3.$(date +%Y%m%d) + +# Backup web console +sudo tar -czf /backup/betterdesk-$(date +%Y%m%d).tar.gz /opt/BetterDeskConsole +``` + +--- + +## 🐛 Troubleshooting + +### Login Issues + +**Problem:** "Invalid username or password" + +**Solutions:** +1. Check credentials file: `sudo cat /opt/BetterDeskConsole/admin_credentials.txt` +2. Reset admin password: + ```python + cd /opt/BetterDeskConsole/web + python3 -c " + from auth import reset_password + reset_password(1, 'NewPassword123') # User ID 1 is admin + print('Password reset successfully') + " + ``` + +### Session Expired + +**Problem:** Automatically logged out + +**Solution:** Sessions expire after 24 hours. This is normal security behavior. + +### Migration Errors + +**Problem:** Database migration fails + +**Solutions:** +1. Check database permissions: + ```bash + ls -la /opt/rustdesk/db_v2.sqlite3 + sudo chown root:root /opt/rustdesk/db_v2.sqlite3 + ``` + +2. Restore from backup if needed: + ```bash + sudo cp /opt/rustdesk/db_v2.sqlite3.backup-pre-v1.4.0 /opt/rustdesk/db_v2.sqlite3 + ``` + +3. Try migration again: + ```bash + sudo python3 migrations/v1.4.0_auth_system.py + ``` + +### Service Won't Start + +**Check logs:** +```bash +sudo journalctl -u betterdesk -n 50 --no-pager +``` + +**Common issues:** +- Missing dependencies: `sudo pip3 install bcrypt markupsafe --break-system-packages` +- Database locked: `sudo systemctl restart rustdesksignal` +- Port conflict: `sudo netstat -tulpn | grep 5000` + +--- + +## 📊 Audit Log + +View user actions: + +```bash +# View audit log in database +sqlite3 /opt/rustdesk/db_v2.sqlite3 " +SELECT + u.username, + a.action, + a.device_id, + a.timestamp, + a.ip_address +FROM audit_log a +LEFT JOIN users u ON a.user_id = u.id +ORDER BY a.timestamp DESC +LIMIT 50; +" +``` + +--- + +## 🔄 Rollback to v1.3.0 + +If you need to rollback: + +```bash +# Stop service +sudo systemctl stop betterdesk + +# Restore backup +sudo rm -rf /opt/BetterDeskConsole +sudo cp -r /opt/BetterDeskConsole.backup /opt/BetterDeskConsole + +# Restore database +sudo cp /opt/rustdesk/db_v2.sqlite3.backup /opt/rustdesk/db_v2.sqlite3 + +# Restart service +sudo systemctl start betterdesk +``` + +--- + +## 📞 Support + +- **GitHub Issues**: https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues +- **Documentation**: See `docs/` folder +- **Security Issues**: Use GitHub Security Advisories (private reporting) + +--- + +## ✅ Post-Installation Checklist + +- [ ] Login with default credentials works +- [ ] Changed admin password +- [ ] Deleted credentials file +- [ ] Configured firewall (block port 5000 externally) +- [ ] Set up SSH tunnel for remote access +- [ ] Tested device list loading +- [ ] Tested ban/unban functionality +- [ ] Created backup of database +- [ ] Verified audit logging works + +--- + +*Last updated: 14 stycznia 2026* +*Version: 1.4.0* diff --git a/docs/INSTALLATION_V8.md b/docs/INSTALLATION_V8.md deleted file mode 100644 index 2d72f102..00000000 --- a/docs/INSTALLATION_V8.md +++ /dev/null @@ -1,306 +0,0 @@ -# Installation Guide - Version 8 (Precompiled Binaries) - -## What's New in v8 - -### 🚀 Major Changes - -1. **Precompiled Binaries** - No more compilation required! - - Installation time reduced from ~20 minutes to ~2 minutes - - No need for Rust/Cargo toolchain - - Smaller dependency footprint - - Faster deployments and updates - -2. **Bidirectional Ban Enforcement** - - Source ban check: Banned devices cannot initiate connections - - Target ban check: Cannot connect to banned devices - - Works for both P2P and relay connections - - Real-time database sync - -3. **Simplified Dependencies** - - Removed: git, cargo (Rust toolchain) - - Required: python3, pip3, curl, systemd - - ~500MB disk space saved - -## Installation Methods - -### Method 1: Automatic Installation (Recommended) - -```bash -# Clone repository -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole - -# Run installer -sudo chmod +x install.sh -sudo ./install.sh -``` - -**What the installer does:** -1. Checks dependencies (python3, pip3, curl, systemctl) -2. Backs up existing RustDesk installation -3. Installs precompiled HBBS/HBBR v8 binaries -4. Installs web console with dependencies -5. Configures systemd services -6. Verifies installation - -**Installation time:** ~2-3 minutes - -### Method 2: Manual Installation - -If you prefer manual installation or have a custom setup: - -```bash -# 1. Backup existing installation -sudo cp /opt/rustdesk/hbbs /opt/rustdesk/hbbs.backup -sudo cp /opt/rustdesk/hbbr /opt/rustdesk/hbbr.backup - -# 2. Stop services -sudo systemctl stop rustdesksignal.service -sudo systemctl stop rustdeskrelay.service - -# 3. Install v8 binaries -sudo cp hbbs-patch/bin/hbbs-v8 /opt/rustdesk/hbbs -sudo cp hbbs-patch/bin/hbbr-v8 /opt/rustdesk/hbbr -sudo chmod +x /opt/rustdesk/hbbs /opt/rustdesk/hbbr - -# 4. Start services -sudo systemctl start rustdesksignal.service -sudo systemctl start rustdeskrelay.service - -# 5. Install web console -sudo mkdir -p /opt/BetterDeskConsole -sudo cp -r web/* /opt/BetterDeskConsole/ -sudo pip3 install -r web/requirements.txt - -# 6. Create systemd service for web console -sudo cp web/betterdesk.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable betterdesk.service -sudo systemctl start betterdesk.service -``` - -### Method 3: Upgrade from Previous Version - -If you're upgrading from an older BetterDesk Console: - -```bash -# Pull latest changes -cd Rustdesk-FreeConsole -git pull - -# Run installer (it will detect existing installation and upgrade) -sudo ./install.sh -``` - -**What gets upgraded:** -- HBBS/HBBR binaries (v8 with bidirectional bans) -- Web console files -- Python dependencies - -**What stays the same:** -- Your database (devices, bans, notes) -- Configuration files -- Service files (unless you choose to recreate) - -## Verification - -After installation, verify everything works: - -### 1. Check Services - -```bash -# HBBS service -sudo systemctl status rustdesksignal.service - -# HBBR service (if using relay) -sudo systemctl status rustdeskrelay.service - -# Web console -sudo systemctl status betterdesk.service -``` - -### 2. Check Ports - -```bash -# RustDesk ports (should show hbbs/hbbr) -sudo netstat -tlnp | grep -E "21115|21116|21117|21118|21119" - -# Web console port (default: 5000) -sudo netstat -tlnp | grep 5000 -``` - -### 3. Test Web Console - -Open in browser: -``` -http://YOUR_SERVER_IP:5000 -``` - -You should see the BetterDesk Console dashboard. - -### 4. Test Ban Enforcement - -```bash -# Watch logs -sudo tail -f /var/log/rustdesk/signalserver.log - -# In web console: Ban a device -# Try to connect from that device -# You should see in logs: -# "WARN Blocked loading banned device [ID] from database" -# "Punch hole REJECTED - initiator [ID] is banned" -``` - -## Troubleshooting - -### Services Won't Start - -```bash -# Check logs -sudo journalctl -u rustdesksignal.service -n 50 -sudo journalctl -u betterdesk.service -n 50 - -# Check HBBS manually -cd /opt/rustdesk -./hbbs -k _ -r YOUR_SERVER_IP:21117 -``` - -### Web Console Connection Failed - -```bash -# Verify Python dependencies -pip3 list | grep -E "flask|requests" - -# Check if port 5000 is blocked -sudo ufw allow 5000/tcp # if using ufw - -# Test manually -cd /opt/BetterDeskConsole -python3 app.py -``` - -### Ban Enforcement Not Working - -```bash -# Verify database has ban columns -sqlite3 /opt/rustdesk/db_v2.sqlite3 "PRAGMA table_info(peer);" -# Should show: is_banned, ban_reason, banned_by, banned_at - -# Check if v8 binary is actually running -ps aux | grep hbbs -lsof -p $(pgrep hbbs) | grep db_v2.sqlite3 # should show database access -``` - -### Binary Architecture Mismatch - -If you get "cannot execute binary file": - -```bash -# Check your architecture -uname -m # should be x86_64 - -# If you have ARM or different architecture, rebuild from source: -cd hbbs-patch -./build.sh -``` - -## Rollback - -If something goes wrong and you need to rollback: - -```bash -# Restore from automatic backup -sudo cp /opt/rustdesk/hbbs.backup.TIMESTAMP /opt/rustdesk/hbbs -sudo cp /opt/rustdesk/hbbr.backup.TIMESTAMP /opt/rustdesk/hbbr -sudo systemctl restart rustdesksignal.service -sudo systemctl restart rustdeskrelay.service - -# Or restore from full backup directory -sudo cp -r /opt/rustdesk-backup-TIMESTAMP/* /opt/rustdesk/ -sudo systemctl restart rustdesksignal.service -``` - -## Uninstallation - -To completely remove BetterDesk Console: - -```bash -# Stop services -sudo systemctl stop betterdesk.service -sudo systemctl disable betterdesk.service - -# Remove web console -sudo rm -rf /opt/BetterDeskConsole -sudo rm /etc/systemd/system/betterdesk.service -sudo systemctl daemon-reload - -# Restore original RustDesk (if you have backup) -sudo cp /opt/rustdesk/hbbs.backup /opt/rustdesk/hbbs -sudo cp /opt/rustdesk/hbbr.backup /opt/rustdesk/hbbr -sudo systemctl restart rustdesksignal.service -sudo systemctl restart rustdeskrelay.service -``` - -## Binary Information - -The precompiled binaries are: - -- **Source**: Compiled from RustDesk Server v1.1.14 + v8 patches -- **Architecture**: Linux x86_64 -- **Compiled**: January 2026 -- **Size**: - - HBBS: ~9.5 MB - - HBBR: ~5.0 MB -- **Patches**: 8 patches applied (see [build.sh](hbbs-patch/build.sh)) -- **Location**: `hbbs-patch/bin/` - -## Security Notes - -1. **Binary Authenticity**: All patches are documented and auditable -2. **No Obfuscation**: Binaries compiled with standard Rust toolchain -3. **Open Source**: Full source and build script available -4. **Rebuild Option**: You can always rebuild from source using `build.sh` -5. **Checksum Verification**: Generate checksums for your binaries: - ```bash - sha256sum hbbs-patch/bin/* - ``` - -## Next Steps - -After successful installation: - -1. **Configure Firewall**: Allow ports 21115-21119 and 5000 -2. **Setup SSL** (optional): Use nginx/caddy as reverse proxy for HTTPS -3. **Create Admin Account**: Add authentication to web console (recommended) -4. **Backup Strategy**: Setup automated backups of `/opt/rustdesk/db_v2.sqlite3` -5. **Monitor Logs**: Setup log rotation and monitoring - -## Getting Help - -- **Documentation**: See [docs/](docs/) directory -- **Security Audit**: [SECURITY_AUDIT.md](hbbs-patch/SECURITY_AUDIT.md) -- **Ban Enforcement**: [BAN_ENFORCEMENT.md](hbbs-patch/BAN_ENFORCEMENT.md) -- **Build from Source**: [build.sh](hbbs-patch/build.sh) -- **Issues**: Create GitHub issue - -## Performance - -Expected performance with v8 precompiled binaries: - -- **Installation**: ~2 minutes (vs ~20 min compilation) -- **Memory**: Same as vanilla RustDesk (~50-100 MB) -- **CPU**: Minimal impact (<1% on modern hardware) -- **Ban Check**: ~1ms per connection attempt -- **Database**: Same queries as before + 1 ban check - -## Compatibility - -- **RustDesk Clients**: All versions compatible with v1.1.14 server -- **Operating Systems**: - - ✅ Ubuntu 20.04, 22.04, 24.04 - - ✅ Debian 11, 12 - - ✅ CentOS 8, 9 - - ✅ Rocky Linux 8, 9 - - ✅ Other x86_64 Linux distributions -- **Python**: 3.8+ required -- **Database**: SQLite 3 diff --git a/docs/PROJECT_ORGANIZATION.md b/docs/PROJECT_ORGANIZATION.md deleted file mode 100644 index 9e47d9d9..00000000 --- a/docs/PROJECT_ORGANIZATION.md +++ /dev/null @@ -1,295 +0,0 @@ -# 📁 Organizacja Projektu BetterDesk Console - -## 📂 Struktura Katalogów - -``` -BetterDesk-Console/ -│ -├── 📄 README.md # Główna dokumentacja projektu -├── 📄 LICENSE # Licencja MIT -├── 📄 VERSION # Wersja projektu (1.2.0-v8) -├── 📄 CHANGELOG.md # Historia zmian -├── 📄 CONTRIBUTING.md # Wytyczne dla kontrybutorów -├── 📄 PROJECT_STRUCTURE.md # Opis struktury technicznej -│ -├── 🔧 install-improved.sh # ⭐ Instalator Linux (v9 - UŻYWAJ TEGO) -├── 🔧 install-improved.ps1 # ⭐ Instalator Windows (v9 - UŻYWAJ TEGO) -│ -├── 📁 hbbs-patch/ # ⭐ Zmodyfikowane serwery HBBS/HBBR -│ ├── 📁 src/ # Kod źródłowy modyfikacji -│ │ ├── peer.rs # Zarządzanie peer-ami (20s timeout) -│ │ ├── database.rs # Metody bazodanowe (ban checking) -│ │ ├── http_api.rs # HTTP API (Axum, port 21114) -│ │ ├── main.rs # Punkt wejścia HBBS -│ │ └── rendezvous_server.rs # Główny serwer sygnałowy -│ │ -│ ├── 📁 bin-with-api/ # ⭐ BINARIA Z HTTP API (używane przez instalatory) -│ │ ├── hbbs-v8-api # Linux binary (10 MB) -│ │ ├── hbbr-v8-api # Linux binary (4.9 MB) -│ │ ├── hbbs-v8-api.exe # Windows binary (6.58 MB) -│ │ └── hbbr-v8-api.exe # Windows binary (2.76 MB) -│ │ -│ ├── 📁 bin/ # Stare binaria (fallback, bez API) -│ ├── 📁 hbbs-ban-check-package/ # Backup kompilacji -│ ├── 🔧 build.sh # Skrypt kompilacji Linux -│ ├── 🔧 build-windows-local.ps1 # Skrypt kompilacji Windows -│ └── 📄 README.md # Dokumentacja patchy -│ -├── 📁 web/ # ⭐ Konsola webowa Flask -│ ├── app.py # Główna aplikacja Flask -│ ├── requirements.txt # Zależności Pythona -│ ├── betterdesk.service # Systemd service (Linux) -│ ├── 📁 templates/ # Szablony HTML -│ │ └── index.html # Główny interfejs -│ └── 📁 static/ # Zasoby statyczne -│ ├── style.css # CSS (glassmorphism) -│ ├── script.js # JavaScript -│ └── MATERIAL_ICONS.md # Info o ikonach -│ -├── 📁 docs/ # Szczegółowa dokumentacja -│ ├── UPDATE_GUIDE.md # Instrukcje aktualizacji -│ ├── INSTALLATION_V8.md # Instalacja v8 -│ ├── DEVELOPMENT_ROADMAP.md # Mapa rozwoju -│ └── RELEASE_NOTES_v1.2.0.md # Notatki wydania -│ -├── 📁 migrations/ # Migracje bazy danych -│ ├── v1.0.1_soft_delete.py # Soft delete dla urządzeń -│ └── v1.1.0_device_bans.py # System banowania -│ -├── 📁 dev_modules/ # Narzędzia deweloperskie -│ ├── check_database.py # Sprawdzanie DB -│ ├── test_ban_api.sh # Testowanie API banów -│ └── update.ps1 # Stary update script -│ -├── 📁 screenshots/ # Zrzuty ekranu do dokumentacji -│ -└── 📁 archive/ # ⭐ ARCHIWUM (stare/nieużywane pliki) - ├── hbbs-patch-backup-*/ # Stare backupy - ├── install.sh # Stary instalator (v1-v8) - ├── update.sh # Stary update script - ├── restore_hbbs.sh # Stary restore script - └── *.md # Stara dokumentacja -``` - ---- - -## 🎯 Która Wersja Instalatora? - -### ✅ UŻYWAJ (Aktualne, zalecane) - -| Plik | System | Wersja | Cechy | -|------|--------|--------|-------| -| **install-improved.sh** | Linux | v9 | Docker support, custom paths, --break-system-packages | -| **install-improved.ps1** | Windows | v9 | Path detection, validation, Windows services | - -### ⚠️ ARCHIWUM (Nieaktualne, tylko do referencji) - -| Plik | System | Status | -|------|--------|--------| -| archive/install.sh | Linux | Zastąpiony przez install-improved.sh | -| archive/update.sh | Linux | Zastąpiony przez install-improved.sh | -| archive/restore_hbbs.sh | Linux | Przestarzały | - ---- - -## 🔑 Kluczowe Pliki do Edycji - -### Modyfikujesz funkcjonalność serwera? -→ Edytuj: `hbbs-patch/src/*.rs` -→ Kompiluj: `bash hbbs-patch/build.sh` (Linux) lub `.\hbbs-patch\build-windows-local.ps1` (Windows) - -### Modyfikujesz interfejs webowy? -→ Edytuj: `web/templates/index.html`, `web/static/style.css`, `web/static/script.js` -→ Restart: `sudo systemctl restart betterdesk` (Linux) - -### Modyfikujesz logikę Flask? -→ Edytuj: `web/app.py` -→ Restart: `sudo systemctl restart betterdesk` (Linux) - -### Modyfikujesz instalator? -→ Edytuj: `install-improved.sh` (Linux) lub `install-improved.ps1` (Windows) -→ Test: Uruchom w środowisku testowym przed wdrożeniem - ---- - -## ⚡ Binaria - Ważne! - -### Struktura `hbbs-patch/bin-with-api/` - -``` -bin-with-api/ -├── hbbs-v8-api ← Linux (ELF 64-bit LSB executable, x86-64) -├── hbbr-v8-api ← Linux (ELF 64-bit LSB executable, x86-64) -├── hbbs-v8-api.exe ← Windows (PE32+ executable, x86-64) -└── hbbr-v8-api.exe ← Windows (PE32+ executable, x86-64) -``` - -### ⛔ NIGDY nie mieszaj binariów między platformami! - -- **Linux installer** (`install-improved.sh`) używa plików **BEZ rozszerzenia .exe** -- **Windows installer** (`install-improved.ps1`) używa plików **Z rozszerzeniem .exe** - -### Skąd się biorą binaria? - -```bash -# Linux (kompilacja na serwerze SSH lub natywnym Linux) -cd hbbs-patch -bash build.sh - -# Windows (kompilacja lokalna z Rust toolchain) -cd hbbs-patch -.\build-windows-local.ps1 -``` - -Po kompilacji binaria trafiają automatycznie do `bin-with-api/`. - ---- - -## 🔄 Workflow Rozwoju - -### 1. Zmiana kodu źródłowego - -```bash -# Edytuj pliki w hbbs-patch/src/ -nano hbbs-patch/src/peer.rs - -# Kompiluj -cd hbbs-patch -bash build.sh # Linux -# LUB -.\build-windows-local.ps1 # Windows -``` - -### 2. Testowanie lokalne - -```bash -# Zatrzymaj istniejące serwisy -sudo systemctl stop rustdesksignal rustdeskrelay - -# Uruchom nowe binaria ręcznie -cd hbbs-patch/bin-with-api -./hbbs-v8-api -h # Test - -# Jeśli działa, zainstaluj -cd ../.. -sudo ./install-improved.sh -``` - -### 3. Wdrożenie produkcyjne - -```bash -# Utwórz backup (automatyczny w instalatorze) -# Uruchom instalator -sudo ./install-improved.sh - -# Sprawdź logi -sudo journalctl -u rustdesksignal -f -``` - ---- - -## 📊 Zależności między Komponentami - -``` -┌─────────────────────────────────────────────┐ -│ Klient RustDesk │ -│ (Desktop/Mobile/Web) │ -└──────────────┬──────────────────────────────┘ - │ heartbeat (~20-30s) - ▼ -┌─────────────────────────────────────────────┐ -│ HBBS Server (hbbs-v8-api) │ -│ - peer.rs: zarządzanie połączeniami │ -│ - database.rs: ban checking │ -│ - http_api.rs: REST API (21114) │ -└─────────────┬───────────────────────────────┘ - │ - ┌─────────┴──────────┐ - ▼ ▼ -┌─────────┐ ┌──────────────┐ -│ SQLite │ │ Arc │ -│ (bany, │◄────►│ (status w │ -│ devices)│ │ pamięci) │ -└─────────┘ └──────┬───────┘ - │ HTTP GET /api/peers - ▼ - ┌──────────────────┐ - │ Flask Web App │ - │ (port 5000) │ - │ - app.py │ - │ - templates/ │ - │ - static/ │ - └──────────────────┘ -``` - ---- - -## 🔧 Utrzymanie i Troubleshooting - -### Sprawdź status serwisów - -```bash -# Linux -sudo systemctl status rustdesksignal -sudo systemctl status rustdeskrelay -sudo systemctl status betterdesk - -# Windows -Get-Service RustDesk* -``` - -### Logi - -```bash -# Linux -sudo journalctl -u rustdesksignal -f -sudo journalctl -u betterdesk -f - -# Windows -Get-EventLog -LogName Application -Source RustDesk* -``` - -### Restart po zmianach - -```bash -# Linux -sudo systemctl restart rustdesksignal rustdeskrelay betterdesk - -# Windows -Restart-Service RustDesk* -``` - ---- - -## 📦 Co Należy do Repozytorium? - -### ✅ Commituj: -- Kod źródłowy (`hbbs-patch/src/`) -- Skrypty (`*.sh`, `*.ps1`) -- Dokumentację (`*.md`) -- Szablony i static files (`web/`) -- Binaria w `hbbs-patch/bin-with-api/` (precompiled releases) - -### ⛔ NIE commituj: -- Katalogi kompilacji (`hbbs-patch/rustdesk-server-*/`, `target/`) -- Pliki ZIP (`*.zip`, `*.tar.gz`) -- Backupy (`*backup*`, `*.old`) -- Logi (`*.log`) -- Bazy danych (`*.sqlite3`, `*.db`) -- Klucze prywatne (`id_*`) - ---- - -## 🆘 Pytania? - -Jeśli coś jest niejasne: -1. Sprawdź `README.md` - główna dokumentacja -2. Zobacz `docs/` - szczegółowe instrukcje -3. Przejrzyj `hbbs-patch/README.md` - technikalia -4. Zajrzyj do `archive/` - historia projektu - -**Podstawowe zasady:** -- Wszystkie nowe funkcje dokumentuj w CHANGELOG.md -- Testy przed wdrożeniem produkcyjnym -- Backupy przed każdą większą zmianą -- Binaria specyficzne dla platformy NIE są zamienne diff --git a/docs/QUICKSTART_UPDATE.md b/docs/QUICKSTART_UPDATE.md deleted file mode 100644 index 15e90982..00000000 --- a/docs/QUICKSTART_UPDATE.md +++ /dev/null @@ -1,351 +0,0 @@ -# Quick Start - Update Scripts - -## For Linux Users (Direct Access) - -```bash -# 1. Download/clone the repository -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole - -# 2. Make script executable -chmod +x update.sh - -# 3. Run the update (default paths) -sudo ./update.sh - -# OR with custom paths: -sudo ./update.sh --rustdesk-dir /custom/path/rustdesk -sudo ./update.sh --console-dir /var/www/betterdesk -sudo ./update.sh --rustdesk-dir /custom/rustdesk --console-dir /custom/console -``` - -**Available options:** -- `--rustdesk-dir PATH` - Custom RustDesk installation path -- `--console-dir PATH` - Custom BetterDesk Console path -- `--help` - Show usage information - -**Example Output:** -``` -======================================== -BetterDesk Console - Update to v1.1.0 -======================================== - -This update includes: - • Soft delete system for devices (v1.0.1) - • Device banning system (v1.1.0) - • Enhanced UI with ban controls - • Input validation and security improvements - -⚠ WARNING: This will modify the database and restart services - -Continue with update? [y/N]: y - -======================================== -Step 1: Checking Installation -======================================== - -✓ Found BetterDesk Console -✓ Found database -✓ Found BetterDesk service - -======================================== -Step 2: Creating Backup -======================================== - -→ Backup directory: /opt/betterdesk-backup-20260105-083000 -→ Backing up database... -✓ Database backed up -→ Backing up web console files... -✓ Web files backed up - -✓ Backup completed: /opt/betterdesk-backup-20260105-083000 - -======================================== -Step 3: Database Migration -======================================== - -→ Running migration v1.0.1 (soft delete)... -✓ Migration v1.0.1 completed - -→ Running migration v1.1.0 (device bans)... -✓ Migration v1.1.0 completed - -======================================== -Update Complete! -======================================== - -✓ Database migrated to v1.1.0 -✓ Web console files updated -✓ Backup created: /opt/betterdesk-backup-20260105-083000 -✓ Service restarted - -Access the console: - http://localhost:5000 -``` - ---- - -## For Windows Users (Remote Update) - -```powershell -# 1. Open PowerShell as Administrator -# 2. Navigate to project directory -cd C:\Path\To\BetterDeskConsole - -# 3. Run update script with your server details (default paths) -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER - -# Optional: Custom RustDesk directory -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -RustDeskPath "/custom/path/rustdesk" - -# Optional: Custom console directory -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -RemotePath "/var/www/betterdesk" - -# Optional: All custom paths -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -RemotePath "/var/www/betterdesk" -RustDeskPath "/custom/rustdesk" -``` - -**Parameters:** -- `-RemoteHost` - Server IP or hostname (required) -- `-RemoteUser` - SSH username (required) -- `-RemotePath` - Console directory (default: `/opt/BetterDeskConsole`) -- `-RustDeskPath` - RustDesk directory (default: `/opt/rustdesk`) -- `-DbPath` - Database path (auto-set from RustDeskPath) - -**Example Output:** -``` -======================================== -BetterDesk Console - Update to v1.1.0 -======================================== - -Configuration: - Remote host: YOUR_SSH_USER@YOUR_SERVER_IP - Console directory: /opt/BetterDeskConsole - RustDesk directory: /opt/rustdesk - Database path: /opt/rustdesk/db_v2.sqlite3 - -This update includes: - • Soft delete system for devices (v1.0.1) - • Device banning system (v1.1.0) - • Enhanced UI with ban controls - • Input validation and security improvements - -⚠ This will modify the database and restart services - -Continue with update? [y/N]: y - -======================================== -Step 1: Checking Local Files -======================================== - -✓ Found: v1.0.1_soft_delete.py -✓ Found: v1.1.0_device_bans.py -✓ Found: app.py -✓ Found: script.js -✓ Found: index.html - -======================================== -Step 2: Testing SSH Connection -======================================== - -→ Testing connection to YOUR_SERVER_IP... -✓ SSH connection successful -→ Checking remote installation... -✓ BetterDesk installation found on remote server - -======================================== -Step 3: Creating Remote Backup -======================================== - -→ Creating backup directory: /opt/betterdesk-backup-20260105-083500 -✓ Backup created: /opt/betterdesk-backup-20260105-083500 - -======================================== -Step 4: Uploading Migration Scripts -======================================== - -→ Uploading v1.0.1_soft_delete.py... -✓ Uploaded v1.0.1_soft_delete.py -→ Uploading v1.1.0_device_bans.py... -✓ Uploaded v1.1.0_device_bans.py - -======================================== -Step 5: Running Database Migrations -======================================== - -→ Executing migration v1.0.1 (soft delete)... -✓ Migration v1.0.1 completed - -→ Executing migration v1.1.0 (device bans)... -✓ Migration v1.1.0 completed - -======================================== -Step 6: Updating Web Console Files -======================================== - -→ Uploading app.py... -✓ Updated app.py -→ Uploading script.js... -✓ Updated script.js -→ Uploading index.html... -✓ Updated index.html - -======================================== -Step 7: Restarting BetterDesk Service -======================================== - -✓ Service restarted successfully - -======================================== -Step 8: Verification -======================================== - -→ Verifying database schema... -✓ Database schema updated (16 columns) -→ Checking web console... -✓ Web console is responding -→ Total devices: 51 -→ Banned devices: 0 - -======================================== -Update Complete! -======================================== - -✓ Database migrated to v1.1.0 -✓ Web console files updated -✓ Backup created: /opt/betterdesk-backup-20260105-083500 -✓ Service restarted - -New Features: - • Soft delete for devices - • Device banning system - • Ban/Unban buttons in web interface - • Enhanced input validation and security - • Banned devices statistics card - -Access the console: - http://YOUR_SERVER_IP:5000 -``` - ---- - -## Verification - -After update, verify the installation: - -### Check Web Console -Open in browser: -``` -http://YOUR_SERVER_IP:5000 -``` - -You should see: -- New "Banned" statistics card (5th card) -- Ban/Unban buttons in device list -- Visual indicators for banned devices - -### Check API -```bash -curl http://localhost:5000/api/stats -``` - -Expected response: -```json -{ - "success": true, - "stats": { - "total": 51, - "active": 14, - "inactive": 37, - "banned": 0, - "with_notes": 21 - } -} -``` - -### Check Database Schema -```bash -sqlite3 /opt/rustdesk/db_v2.sqlite3 "PRAGMA table_info(peer);" | grep -E "is_banned|is_deleted" -``` - -Should show: -``` -9|is_deleted|INTEGER|0||0 -10|deleted_at|INTEGER|0||0 -11|updated_at|INTEGER|0||0 -12|is_banned|INTEGER|0||0 -13|banned_at|INTEGER|0||0 -14|banned_by|VARCHAR(100)|0||0 -15|ban_reason|TEXT|0||0 -``` - ---- - -## Rollback (If Needed) - -Both scripts create automatic backups. To rollback: - -```bash -# 1. Stop service -sudo systemctl stop betterdesk - -# 2. Find your backup directory -ls -ltr /opt/ | grep betterdesk-backup - -# 3. Restore database -sudo cp /opt/betterdesk-backup-YYYYMMDD-HHMMSS/db_v2.sqlite3.backup /opt/rustdesk/db_v2.sqlite3 - -# 4. Restore web files -sudo cp /opt/betterdesk-backup-YYYYMMDD-HHMMSS/app.py.backup /opt/BetterDeskConsole/app.py -sudo cp /opt/betterdesk-backup-YYYYMMDD-HHMMSS/script.js.backup /opt/BetterDeskConsole/static/script.js -sudo cp /opt/betterdesk-backup-YYYYMMDD-HHMMSS/index.html.backup /opt/BetterDeskConsole/templates/index.html - -# 5. Start service -sudo systemctl start betterdesk -``` - ---- - -## Common Issues - -### Permission Denied (Linux) -```bash -chmod +x update.sh -sudo ./update.sh -``` - -### SSH Connection Failed (Windows) -```powershell -# Test SSH manually first -ssh YOUR_SSH_USER@YOUR_SERVER_IP - -# If prompted for password, set up SSH keys: -ssh-keygen -ssh-copy-id YOUR_SSH_USER@YOUR_SERVER_IP -``` - -### PowerShell Execution Policy -```powershell -# Allow script execution -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -# Or run with bypass -powershell -ExecutionPolicy Bypass -File update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -``` - -### Service Won't Start -```bash -# Check logs -journalctl -u betterdesk -n 50 --no-pager - -# Check if port 5000 is already in use -sudo netstat -tlnp | grep 5000 - -# Try manual start -cd /opt/BetterDeskConsole -python3 app.py -``` - ---- - -For more help, see [UPDATE_GUIDE.md](UPDATE_GUIDE.md) diff --git a/docs/QUICK_FIX.md b/docs/QUICK_FIX.md deleted file mode 100644 index 3e62008b..00000000 --- a/docs/QUICK_FIX.md +++ /dev/null @@ -1,220 +0,0 @@ -# 🔧 Quick Reference: Key Problems & Solutions - -## 🚨 Most Common Issues - -### Issue #1: "The keys do not match" - -**Quick Fix:** -```bash -sudo bash repair-keys.sh -# Select option 5: Restore from backup -``` - -**If no backup exists:** -```bash -sudo bash repair-keys.sh -# Select option 4: Regenerate keys -# Then reconfigure ALL clients with new key -``` - ---- - -### Issue #2: BetterDesk Shows Wrong Key - -**Cause**: Multiple `.pub` files exist, or wrong file name - -**Quick Fix** (automatic with v9+): -```bash -cd /path/to/Rustdesk-FreeConsole -git pull -sudo bash install-improved.sh -# Select option to keep existing keys -``` - -**Manual Fix**: -```bash -# Find all .pub files -ls -lah /opt/rustdesk/*.pub - -# Remove wrong ones (BACKUP FIRST!) -sudo cp -r /opt/rustdesk /opt/rustdesk-backup-manual -sudo rm /opt/rustdesk/wrong_key.pub - -# Restart web console -sudo systemctl restart betterdesk -``` - ---- - -### Issue #3: Installation Broke My Working Setup - -**Immediate Recovery:** -```bash -# Find most recent backup -BACKUP=$(ls -d /opt/rustdesk-backup-* | sort | tail -1) -echo "Using backup: $BACKUP" - -# Stop services -sudo systemctl stop rustdesksignal rustdeskrelay betterdesk - -# Restore everything -sudo cp -r $BACKUP/* /opt/rustdesk/ - -# Fix permissions -sudo chmod 600 /opt/rustdesk/id_ed25519 -sudo chmod 644 /opt/rustdesk/*.pub - -# Start services -sudo systemctl start rustdesksignal rustdeskrelay betterdesk - -# Verify -cat /opt/rustdesk/id_ed25519.pub -``` - ---- - -## 📋 Pre-Installation Checklist - -**Before running `install-improved.sh`:** - -- [ ] Create manual backup: `sudo cp -r /opt/rustdesk /opt/rustdesk-backup-$(date +%Y%m%d)` -- [ ] Save current public key: `cat /opt/rustdesk/id_ed25519.pub > ~/rustdesk_key_backup.txt` -- [ ] Note your RustDesk directory location -- [ ] Check for multiple `.pub` files: `ls /opt/rustdesk/*.pub` -- [ ] Verify services are running: `systemctl status rustdesksignal` - -**During installation:** - -- ✅ Choose **automatic backup** when prompted -- ✅ Select **keep existing keys** when asked -- ❌ Don't skip backups -- ❌ Don't regenerate keys unless necessary - ---- - -## 🐳 Docker-Specific Issues - -### Docker Installation Detected - -**Problem**: Script warns about Docker but you want web console only - -**Solution**: -```bash -sudo bash install-improved.sh -# Select option 2: "Install ONLY Web Console for existing Docker RustDesk" -``` - -**Finding Docker volume path:** -```bash -# Find your RustDesk container -docker ps | grep rustdesk - -# Inspect volume mounts -docker inspect | grep -A 10 Mounts - -# Typical locations: -# - /var/lib/docker/volumes/rustdesk_data/_data -# - /data (inside container) -# - Custom bind mount specified in docker-compose.yml -``` - -**Accessing keys in Docker:** -```bash -# Option 1: Exec into container -docker exec -it sh -cat /data/id_ed25519.pub - -# Option 2: Copy from container -docker cp :/data/id_ed25519.pub ~/rustdesk_key.txt -cat ~/rustdesk_key.txt - -# Option 3: Check volume on host -sudo cat /var/lib/docker/volumes/rustdesk_data/_data/id_ed25519.pub -``` - ---- - -## 🔧 Using repair-keys.sh - -**Location**: `/path/to/Rustdesk-FreeConsole/repair-keys.sh` - -**Features:** - -1. **Show Info** - Display all keys and their locations -2. **Fix Permissions** - Automatically correct file permissions -3. **Export Key** - Save public key to file for distribution -4. **Regenerate** - Create new keys (last resort) -5. **Restore** - Recover from backup - -**Usage:** -```bash -cd /path/to/Rustdesk-FreeConsole -sudo bash repair-keys.sh -``` - ---- - -## 📞 Emergency Contacts - -**Can't Fix It? Need Help?** - -1. Collect diagnostics: -```bash -sudo journalctl -u rustdesksignal -n 50 > ~/rustdesk_logs.txt -ls -lah /opt/rustdesk/*.pub >> ~/rustdesk_logs.txt -cat /opt/rustdesk/id_ed25519.pub >> ~/rustdesk_logs.txt -``` - -2. Create GitHub issue: - - https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues/new - - Include output from above - - Describe what you tried - - Mention if this is Docker or native installation - -3. Check existing issues: - - Search for "key mismatch" or "keys do not match" - - https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues - ---- - -## ✅ Verification After Fix - -**Check everything works:** - -```bash -# 1. Services running -systemctl status rustdesksignal rustdeskrelay betterdesk - -# 2. Correct key displayed -cat /opt/rustdesk/id_ed25519.pub - -# 3. Web console shows same key -curl -s http://localhost:5000 | grep -oP 'public-key.*?
' | head -1 - -# 4. API responding -curl -s http://localhost:21114/api/health | jq . - -# 5. Test client connection -# (configure client with public key and try to connect) -``` - -**Expected Results:** -- ✅ All services show "active (running)" -- ✅ Public key file readable and contains valid key -- ✅ Web console shows same key as file -- ✅ API returns success -- ✅ Client connects without errors - ---- - -## 📚 Full Documentation - -For complete troubleshooting guide, see: -- [KEY_TROUBLESHOOTING.md](KEY_TROUBLESHOOTING.md) - Detailed solutions -- [INSTALLATION_V8.md](INSTALLATION_V8.md) - Installation guide -- [UPDATE_GUIDE.md](UPDATE_GUIDE.md) - Updating existing installation - ---- - -**Last Updated**: 2026-01-13 -**Version**: BetterDesk v9+ diff --git a/docs/RELEASE_NOTES_v1.2.0.md b/docs/RELEASE_NOTES_v1.2.0.md deleted file mode 100644 index d613c14c..00000000 --- a/docs/RELEASE_NOTES_v1.2.0.md +++ /dev/null @@ -1,184 +0,0 @@ -# BetterDesk Console v1.2.0 - Release Notes - -**Release Date**: January 5, 2026 -**Codename**: "Native Guardian" - ---- - -## 🎯 Overview - -Version 1.2.0 marks a **major architectural improvement** in device ban enforcement. The external Python daemon has been replaced with native ban checking integrated directly into the HBBS server, providing 100% reliable ban enforcement with zero race conditions. - ---- - -## 🔥 What's New - -### Native HBBS Ban Check - -The headline feature of this release is the **native ban enforcement system**: - -- **100% Reliability**: Bans enforced at device registration - no timing windows -- **Better Performance**: Single SQL query per registration (~1ms overhead) -- **Zero Maintenance**: No external daemon process to manage -- **Native Integration**: Ban check built into HBBS source code - -**Technical Details:** -- Modified `src/database.rs`: Added `is_device_banned()` method -- Modified `src/peer.rs`: Registration logic checks ban status -- Banned devices receive standard RustDesk `UUID_MISMATCH` error -- Fail-open design: continues if database unavailable - -### Build System - -Complete tooling for compiling and deploying patched HBBS: - -- `hbbs-patch/build.sh`: Automated build script with dependency checks -- `hbbs-patch/install.sh`: One-command server installation -- Full documentation: `QUICKSTART.md` and `BAN_CHECK_PATCH.md` -- Supports both local and server-side compilation - -### Documentation - -- Comprehensive HBBS patch documentation -- Migration guide from Ban Enforcer to native system -- Technical deep-dive into ban check implementation -- Build and deployment guides - ---- - -## 📦 What's Included - -### Web Console -- Modern glassmorphism UI -- Real-time device monitoring -- Ban/unban management interface -- Device notes and soft delete -- RESTful API - -### HBBS Patches -- Native ban check integration -- HTTP API for status queries -- Database schema v1.1.0 support -- Compatible with RustDesk v1.1.14 - -### Build Tools -- Automated patch application -- Rust compilation scripts -- Installation automation -- Backup and rollback support - ---- - -## ⚠️ Breaking Changes - -### Ban Enforcer Deprecated - -The Python `ban_enforcer.py` daemon is now **obsolete**: - -- ❌ No longer receives updates -- ❌ Not recommended for new installations -- ✅ Replaced by native HBBS ban check - -**Migration Required**: Users on v1.1.0 should upgrade to native ban enforcement. - ---- - -## 🚀 Upgrade Instructions - -### New Installation - -1. Clone repository -2. Run web console installation: `./install.sh` -3. Build HBBS patch: `cd hbbs-patch && ./build.sh` -4. Install patched HBBS: `./install.sh` - -### Upgrading from v1.1.0 - -1. Pull latest code: `git pull` -2. Update web console: `./update.sh` -3. Build and install HBBS patch (see above) -4. Stop Ban Enforcer: `sudo systemctl stop rustdesk-ban-enforcer` -5. Disable service: `sudo systemctl disable rustdesk-ban-enforcer` - -**No database migration needed** - schema remains compatible. - ---- - -## 📊 Performance Comparison - -| Metric | Ban Enforcer (v1.1.0) | Native Check (v1.2.0) | -|--------|----------------------|----------------------| -| Effectiveness | ~95% (race conditions) | **100%** (no windows) | -| CPU Usage | 2s polling loop | Per-registration only | -| Memory | ~50MB Python process | Integrated into HBBS | -| Latency | 0-2s window | Immediate rejection | -| Reliability | Daemon can crash | Built into server | -| Maintenance | Separate service | Zero extra processes | - ---- - -## 🔧 Technical Requirements - -### Build Environment -- **Rust**: 1.90+ (for HBBS compilation) -- **Python**: 3.8+ (for web console) -- **Git**: For cloning repository -- **Build Tools**: gcc, make, libclang - -### Runtime -- **HBBS**: Patched v1.1.14 -- **SQLite**: v3.x (included with HBBS) -- **Flask**: 3.0.0 (web console) - -### Server -- **OS**: Linux (tested on Ubuntu 20.04+, Debian 11+) -- **RAM**: 512MB minimum (1GB recommended) -- **Disk**: 50MB for binaries + database space - ---- - -## 🐛 Known Issues - -None at release time. - ---- - -## 📝 Notes - -- **Ban Enforcer files kept**: Remain in repo for reference and rollback -- **Database compatible**: No schema changes from v1.1.0 -- **API unchanged**: Web console API remains backward compatible -- **Client compatible**: Works with all RustDesk client versions - ---- - -## 🙏 Credits - -- RustDesk team for the excellent open-source remote desktop -- Community contributors for testing and feedback -- Rust and Python communities for amazing tools - ---- - -## 📖 Documentation - -- **Quick Start**: [QUICKSTART.md](QUICKSTART.md) -- **HBBS Patch**: [hbbs-patch/BAN_CHECK_PATCH.md](hbbs-patch/BAN_CHECK_PATCH.md) -- **API Docs**: [README.md#api-documentation](README.md#api-documentation) -- **Migration**: [DEPRECATION_NOTICE.md](DEPRECATION_NOTICE.md) -- **Changelog**: [CHANGELOG.md](CHANGELOG.md) - ---- - -## 🔗 Links - -- **Repository**: https://github.com/UNITRONIX/Rustdesk-FreeConsole -- **Issues**: https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues -- **RustDesk**: https://github.com/rustdesk/rustdesk -- **License**: MIT - ---- - -**Enjoy BetterDesk Console v1.2.0!** 🎉 - -For questions, issues, or contributions, please visit our GitHub repository. diff --git a/docs/RELEASE_NOTES_v1.3.0.md b/docs/RELEASE_NOTES_v1.3.0.md deleted file mode 100644 index 7df2cd49..00000000 --- a/docs/RELEASE_NOTES_v1.3.0.md +++ /dev/null @@ -1,304 +0,0 @@ -# 🎉 Release Notes - BetterDesk Console v1.3.0-secure - -**Release Date:** 10 stycznia 2026 -**Focus:** Security Enhancement - Localhost-Only API Binding - ---- - -## 🔒 What's New - -### Critical Security Enhancement - -**API Port Changed: 21114 → 21120** -- New port clearly indicates localhost-only service -- Avoids conflict with RustDesk Pro (which uses 21114 for public API) -- All documentation and examples updated - -**Localhost-Only Binding: 0.0.0.0 → 127.0.0.1** -- API now binds **exclusively** to localhost (127.0.0.1) -- **Zero network exposure** - cannot be accessed from external networks -- Connection attempts from network properly refused -- No firewall configuration needed for port 21120 - -### New Features - -✅ **--api-port Parameter** -- Command-line configuration support -- Flexible deployment options -- Example: `hbbs --api-port 21120` - -✅ **SSH Tunnel Support** -- Remote access via secure tunnel -- Instructions in README and PORT_SECURITY.md -- Example: `ssh -L 21120:localhost:21120 user@server` - -✅ **Updated Binaries** -- Linux: hbbs-v8-api (9.59 MB), hbbr-v8-api (4.73 MB) -- Built: 10.01.2026 10:25 UTC -- Contains security code: "localhost only" binding -- Windows binaries: Compatible (retained from previous build) - -✅ **Enhanced Documentation** -- PORT_SECURITY.md - Complete port analysis -- Updated README with 6 security references -- SSH tunnel instructions -- Verification commands - ---- - -## 📦 Download - -### For Linux (Ubuntu 20.04+, Debian 11+) - -```bash -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -chmod +x install-improved.sh -sudo ./install-improved.sh -``` - -**Binaries included:** -- `hbbs-patch/bin-with-api/hbbs-v8-api` (9.59 MB) -- `hbbs-patch/bin-with-api/hbbr-v8-api` (4.73 MB) - -### For Windows (Windows 10+, Server 2016+) - -```powershell -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -# Run as Administrator -.\install-improved.ps1 -``` - -**Binaries included:** -- `hbbs-patch/bin-with-api/hbbs-v8-api.exe` (6.58 MB) -- `hbbs-patch/bin-with-api/hbbr-v8-api.exe` (2.76 MB) - ---- - -## 🔐 Security - -### What's Protected - -✅ **API Endpoints:** -- `http://localhost:21120/api/health` - Service health check -- `http://localhost:21120/api/peers` - Device list - -✅ **Access Control:** -- **Allowed:** localhost (127.0.0.1) only -- **Blocked:** All network/internet access -- **Firewall:** Port 21120 does NOT need to be opened - -### What's Public (Unchanged) - -RustDesk client ports remain publicly accessible (required): -- TCP 21115 - HBBS Signal Server -- TCP 21116 - HBBS Signal Server (NAT) -- TCP 21117 - HBBR Relay Server -- UDP 21116 - NAT Type Test - ---- - -## 🔄 Upgrade from v1.2.0-v8 - -### Automatic Upgrade (Recommended) - -```bash -cd Rustdesk-FreeConsole -git pull -sudo ./install-improved.sh -``` - -**What happens:** -1. Backups created automatically -2. New binaries installed -3. Systemd service updated (--api-port 21120) -4. Web console updated (port 21120) -5. Services restarted - -### Manual Upgrade - -**1. Update systemd service:** -```bash -sudo nano /etc/systemd/system/rustdesksignal.service -# Change: ExecStart=/opt/rustdesk/hbbs -# To: ExecStart=/opt/rustdesk/hbbs --api-port 21120 -sudo systemctl daemon-reload -``` - -**2. Update web console:** -```bash -sudo nano /opt/BetterDeskConsole/app.py -# Change: HBBS_API_URL = 'http://localhost:21114/api' -# To: HBBS_API_URL = 'http://localhost:21120/api' -``` - -**3. Restart services:** -```bash -sudo systemctl restart rustdesksignal betterdesk -``` - ---- - -## ✅ Verification - -### 1. Check API Binding - -```bash -ss -tlnp | grep 21120 -``` - -**Expected:** `127.0.0.1:21120` (localhost only) ✅ - -### 2. Test Local Access - -```bash -curl http://localhost:21120/api/health -``` - -**Expected:** `{"success":true,"data":"RustDesk API is running","error":null}` ✅ - -### 3. Test External Access - -```bash -curl http://YOUR_SERVER_IP:21120/api/health -``` - -**Expected:** Connection refused ✅ (this is correct - security working) - -### 4. Verify RustDesk Ports - -```bash -ss -tlnp | grep -E '21115|21116|21117' -``` - -**Expected:** All ports listening on 0.0.0.0 (public access) ✅ - ---- - -## 🌐 Remote Access - -For remote API access (e.g., development workstation to production server): - -### SSH Tunnel Method - -```bash -# Create tunnel -ssh -L 21120:localhost:21120 user@your-server.com - -# In another terminal, access API -curl http://localhost:21120/api/health -``` - -### Web Console Access - -```bash -# Tunnel both API and web console -ssh -L 21120:localhost:21120 -L 5000:localhost:5000 user@your-server.com - -# Open browser -http://localhost:5000 -``` - ---- - -## 📊 Checksums - -Verify binary integrity with SHA256: - -### Linux Binaries - -``` -7B09A6C024188AF5AAC8E94C64B4B97D68A92ABF7F902B34A7D91A9D99E44558 hbbs-v8-api -DF1B3FD3EF8793FD3A786E2BFBB330EE43A6C92D1A5915414F36011BE778E3FB hbbr-v8-api -``` - -### Windows Binaries - -``` -EE1AB9C341B078D852EA32ED33CCD8664BC6A3D6EA818D321529B9654C69CD74 hbbs-v8-api.exe -37F452AE97407992DE1561B5F90747D9396E591C21E70B27897EEBEB652C1D25 hbbr-v8-api.exe -``` - -**Verification:** -```bash -# Linux -sha256sum hbbs-v8-api hbbr-v8-api - -# Windows (PowerShell) -Get-FileHash hbbs-v8-api.exe -Algorithm SHA256 -``` - -See [CHECKSUMS.md](hbbs-patch/bin-with-api/CHECKSUMS.md) for details. - ---- - -## 🐛 Known Issues - -None reported. All tests passed: -- ✅ API responds on localhost -- ✅ External access blocked -- ✅ RustDesk clients connect normally -- ✅ Web console operational -- ✅ Ban enforcement working (bidirectional) - ---- - -## 📝 Full Changelog - -See [CHANGELOG.md](CHANGELOG.md) for complete version history. - -### Summary of Changes - -**Changed:** -- API port: 21114 → 21120 -- API binding: 0.0.0.0 → 127.0.0.1 -- Systemd service: Added --api-port parameter -- Web console: Updated to port 21120 - -**Added:** -- PORT_SECURITY.md documentation -- SSH tunnel instructions -- CHECKSUMS.md for binary verification -- Security badges in README - -**Fixed:** -- Port conflict with RustDesk Pro -- Network security (eliminated accidental exposure) - -**Security:** -- Zero network exposure -- Localhost-only API access -- No private data in documentation - ---- - -## 🤝 Contributing - -Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. - -**Report issues:** -- GitHub Issues: https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues - ---- - -## 📄 License - -MIT License - See [LICENSE](LICENSE) file for details. - ---- - -## 🙏 Credits - -- **RustDesk Team** - Original server implementation -- **Community** - Testing and feedback -- **Contributors** - Security enhancements and documentation - ---- - -## 🔗 Links - -- **Repository:** https://github.com/UNITRONIX/Rustdesk-FreeConsole -- **Documentation:** [README.md](README.md) -- **Security:** [PORT_SECURITY.md](PORT_SECURITY.md) -- **RustDesk:** https://rustdesk.com/ diff --git a/docs/RELEASE_NOTES_v9.md b/docs/RELEASE_NOTES_v9.md deleted file mode 100644 index 021a56c5..00000000 --- a/docs/RELEASE_NOTES_v9.md +++ /dev/null @@ -1,317 +0,0 @@ -# 🔑 BetterDesk v9 - Encryption Key Protection Update - -## 📢 Important Update for All Users - -**Version**: v9 -**Date**: January 13, 2026 -**Priority**: HIGH - Addresses critical user-reported issues - ---- - -## ⚠️ What Was The Problem? - -We received multiple reports from users experiencing: - -1. **"The keys do not match"** errors after BetterDesk installation -2. **"Remote desktop is offline"** - intermittent connectivity issues -3. **Public key mismatch** - WebConsole showing different key than expected -4. **Connection breakage** - working setups broken after installation - -### Root Causes Identified: - -- ❌ Installation script didn't protect existing encryption keys -- ❌ Keys could be accidentally regenerated during installation -- ❌ Web console hardcoded `id_ed25519.pub` filename (some users had different names) -- ❌ No warning before potentially destructive operations -- ❌ Insufficient backup procedures - -**User Quote:** -> "After I shutdown BetterDesk and removed the folder, I used this to get it working again: rm -f /opt/rustdesk/id_ed25519* && ssh-keygen..." -> — Affected User - ---- - -## ✅ What We Fixed - -### 1. 🔐 Comprehensive Key Protection - -**Before v9:** -```bash -# Installation could silently regenerate keys -# No warnings, no protection -``` - -**After v9:** -```bash -🔑 EXISTING ENCRYPTION KEYS DETECTED 🔑 -Found: id_ed25519.pub - -⚠️ CRITICAL: These keys authenticate your RustDesk server - Changing keys = ALL clients disconnected - -Options: - 1) Keep existing keys (RECOMMENDED) - 2) Regenerate keys (⚠️ BREAKS connections) - 3) Show key information -``` - -### 2. 🔍 Dynamic Key File Scanning - -**Before v9:** -```python -# Hardcoded path in web/app.py -PUB_KEY_PATH = '/opt/rustdesk/id_ed25519.pub' -# Failed if user had different filename! -``` - -**After v9:** -```python -# Automatically scans for ANY .pub file -def get_public_key(): - # Try default path first - if os.path.exists(PUB_KEY_PATH): - return f"[id_ed25519.pub] {content}" - - # Scan for any .pub file - for file in os.listdir(rustdesk_dir): - if file.endswith('.pub'): - return f"[{file}] {content}" -``` - -### 3. 💾 Enhanced Backup System - -**Before v9:** -- Simple backup prompt -- Easy to skip -- No verification - -**After v9:** -- **Multiple options** (automatic, manual, existing backup) -- **Visual warnings** with emojis and colors -- **Backup verification** - checks size and contents -- **Mandatory confirmation** for risky operations -- **Key fingerprint display** for verification - -### 4. 🔧 New Repair Tool - -Created `repair-keys.sh` with features: -- Show all key files and their details -- Verify and fix permissions -- Export public keys -- Regenerate keys with backups -- Restore from any backup - -### 5. 📚 Comprehensive Documentation - -New guides created: -- `docs/KEY_TROUBLESHOOTING.md` - Complete troubleshooting guide -- `docs/QUICK_FIX.md` - Fast solutions for common issues -- Updated README with troubleshooting section - -### 6. 🐳 Better Docker Handling - -**Before v9:** -```bash -Docker detected? → Continue anyway? [y/N] -# Easy to accidentally break Docker setup -``` - -**After v9:** -```bash -🐳 Docker RustDesk installation detected! - -Options: - 1) Exit and use Docker-compose (RECOMMENDED) - 2) Install ONLY Web Console for Docker - 3) Continue native (WILL NOT WORK WITH DOCKER) - -Choose [1-3]: -``` - ---- - -## 🆕 New Features in v9 - -| Feature | Description | Impact | -|---------|-------------|--------| -| **Key Detection** | Automatically finds existing keys | Prevents accidental overwrite | -| **Multiple Backups** | 4 backup options with verification | Data safety | -| **Dynamic Scanning** | Finds any `.pub` file, not just default | Works with custom key names | -| **Visual Warnings** | Color-coded, emoji-enhanced alerts | Clear communication | -| **repair-keys.sh** | Diagnostic and repair utility | Easy troubleshooting | -| **Rollback Support** | Easy restoration from backups | Quick recovery | -| **Permission Fixes** | Automatic permission correction | Resolves common issues | -| **Docker Detection** | Smart handling of containerized installs | Prevents conflicts | - ---- - -## 🔄 Upgrading to v9 - -### For New Installations - -Simply use the latest version: -```bash -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -sudo bash install-improved.sh -``` - -The installer will: -- ✅ Detect your existing RustDesk installation -- ✅ Find and protect your encryption keys -- ✅ Create automatic backups -- ✅ Guide you through safe installation - -### For Existing BetterDesk Users - -If you already have BetterDesk installed: - -```bash -cd /path/to/Rustdesk-FreeConsole -git pull # Get v9 updates -sudo bash install-improved.sh -``` - -**During upgrade:** -- Select **Option 1**: Keep existing keys (RECOMMENDED) -- Let the script create automatic backup -- Verify everything works after installation - -### For Users Who Had Issues - -If BetterDesk broke your keys: - -**Option 1: Restore from backup** -```bash -cd Rustdesk-FreeConsole -sudo bash repair-keys.sh -# Select: 5) Restore keys from backup -``` - -**Option 2: Manual restore** -```bash -# Find backup -ls -d /opt/rustdesk-backup-* - -# Restore -BACKUP=$(ls -d /opt/rustdesk-backup-* | sort | tail -1) -sudo systemctl stop rustdesksignal rustdeskrelay -sudo cp $BACKUP/id_ed25519* /opt/rustdesk/ -sudo chmod 600 /opt/rustdesk/id_ed25519 -sudo chmod 644 /opt/rustdesk/id_ed25519.pub -sudo systemctl start rustdesksignal rustdeskrelay -``` - -**Option 3: If no backup exists** -```bash -# Regenerate keys (will require reconfiguring ALL clients) -sudo bash repair-keys.sh -# Select: 4) Regenerate keys -# Follow prompts and save new public key -``` - ---- - -## 📋 What To Do After Upgrading - -### 1. Verify Keys - -```bash -# Check your current public key -cat /opt/rustdesk/id_ed25519.pub - -# Compare with WebConsole -# Open http://your-server:5000 -# Key should match exactly -``` - -### 2. Test Connections - -- Open RustDesk client -- Try connecting to a device -- Should work without "key mismatch" errors - -### 3. Save Your Key - -```bash -# Export for safekeeping -cat /opt/rustdesk/id_ed25519.pub > ~/rustdesk_public_key_backup.txt - -# Or use repair tool -sudo bash repair-keys.sh -# Select: 3) Export public key -``` - -### 4. Verify Backups - -```bash -# Check automatic backups exist -ls -lah /opt/rustdesk-backup-* - -# Verify keys are in backup -ls -lah /opt/rustdesk-backup-*/id_ed25519* -``` - ---- - -## 🛡️ Prevention Checklist - -Before **any** future RustDesk modifications: - -- [ ] Backup keys manually: `sudo cp -r /opt/rustdesk /opt/rustdesk-backup-manual` -- [ ] Save public key: `cat /opt/rustdesk/id_ed25519.pub > ~/key_backup.txt` -- [ ] Note current key fingerprint -- [ ] Test restoration procedure -- [ ] Document any custom configurations - ---- - -## 📊 What Users Are Saying - -### Before v9: -> ❌ "BetterDesk broke my RustDesk installation" -> ❌ "Keys do not match - all clients disconnected" -> ❌ "Had to regenerate keys and reconfigure 50+ devices" - -### After v9: -> ✅ "Installation preserved my keys perfectly" -> ✅ "Clear warnings prevented me from making mistakes" -> ✅ "Repair tool fixed my issue in 30 seconds" - ---- - -## 🔗 Additional Resources - -- **Quick Fixes**: [docs/QUICK_FIX.md](docs/QUICK_FIX.md) -- **Full Troubleshooting**: [docs/KEY_TROUBLESHOOTING.md](docs/KEY_TROUBLESHOOTING.md) -- **Installation Guide**: [docs/INSTALLATION_V8.md](docs/INSTALLATION_V8.md) -- **GitHub Issues**: https://github.com/UNITRONIX/Rustdesk-FreeConsole/issues - ---- - -## 💬 Feedback & Support - -We want to hear from you! - -- 🐛 **Report issues**: Open a GitHub issue -- 💡 **Suggest features**: Start a discussion -- ⭐ **Success story**: Share your experience -- 🤝 **Contribute**: PRs welcome! - ---- - -## 🙏 Thank You - -Special thanks to users who reported these issues and helped us improve BetterDesk: -- Users who detailed their "key mismatch" problems -- Community members who shared workarounds -- Everyone who tested v9 pre-release - -**Your feedback makes BetterDesk better! 🚀** - ---- - -**Version**: v9 -**Release Date**: January 13, 2026 -**Compatibility**: RustDesk 1.1.14+ -**License**: MIT diff --git a/docs/RELEASE_READY.md b/docs/RELEASE_READY.md deleted file mode 100644 index 4ad4740f..00000000 --- a/docs/RELEASE_READY.md +++ /dev/null @@ -1,262 +0,0 @@ -# 🎉 Release Readiness Checklist - BetterDesk Console v1.2.0-v8 - -## ✅ Code & Binaries - -- [x] **Precompiled binaries** included in `hbbs-patch/bin/` - - [x] hbbs-v8 (9.5 MB) - Signal server with bidirectional ban enforcement - - [x] hbbr-v8 (5.0 MB) - Relay server with bidirectional ban enforcement - - [x] SHA256 checksums documented - -- [x] **Web console** fully functional - - [x] Flask backend with ban management - - [x] Modern glassmorphism UI - - [x] Material Icons (offline) - - [x] Device management, banning, notes - -- [x] **Installation system** verified - - [x] install.sh uses precompiled binaries - - [x] Automatic backup of existing files - - [x] Service restart functionality - - [x] No compilation required - - [x] Reduced dependencies (no Rust/git needed) - -## ✅ Documentation - -- [x] **Main README.md** updated - - [x] Version badge: 1.2.0-v8 - - [x] Bidirectional ban enforcement description - - [x] Precompiled binaries mentioned - - [x] Installation time: 2-3 minutes - - [x] No compilation requirements - -- [x] **CHANGELOG.md** updated - - [x] Version 1.2.0-v8 entry - - [x] Bidirectional ban enforcement details - - [x] Precompiled binaries explanation - - [x] Migration notes - -- [x] **LICENSE** appropriate - - [x] AGPL-3.0 (compatible with RustDesk) - - [x] Copyright attribution - -- [x] **Technical documentation** - - [x] hbbs-patch/BAN_ENFORCEMENT.md - Bidirectional bans - - [x] hbbs-patch/SECURITY_AUDIT.md - Security review - - [x] hbbs-patch/bin/README.md - Binary documentation - - [x] hbbs-patch/bin/CHECKSUMS.md - SHA256 verification - - [x] docs/INSTALLATION_V8.md - Complete installation guide - - [x] PROJECT_STRUCTURE.md - Updated structure - -## ✅ Security & Privacy - -- [x] **No sensitive data** in files - - [x] SSH credentials removed (0 instances found) - - [x] IP addresses replaced with placeholders - - [x] All occurrences: YOUR_SERVER_IP, YOUR_SSH_USER - -- [x] **No git history** with sensitive data - - [x] Not a git repository (clean start possible) - -- [x] **Security documentation** - - [x] SECURITY_AUDIT.md - Vulnerability assessment - - [x] SECURITY_PLACEHOLDERS.md - Guide for users - - [x] SECURITY_CLEANUP_REPORT.md - Cleanup summary - -- [x] **.gitignore** comprehensive - - [x] Credentials patterns - - [x] Backup files - - [x] Old binary versions - - [x] Sensitive data patterns - -## ✅ Code Quality - -- [x] **Functional verification** - - [x] Bidirectional ban enforcement working - - [x] Web console operational - - [x] Database migrations included - - [x] Service files present - -- [x] **Clean codebase** - - [x] Old binaries removed (v2-v5) - - [x] Deprecated code in separate directory - - [x] No TODO or FIXME markers in critical code - -## ✅ Repository Structure - -``` -BetterDeskConsole/ -├── ✅ README.md (updated) -├── ✅ LICENSE (AGPL-3.0) -├── ✅ VERSION (1.2.0-v8) -├── ✅ CHANGELOG.md (v8 entry) -├── ✅ .gitignore (comprehensive) -├── ✅ PROJECT_STRUCTURE.md (updated) -│ -├── ✅ install.sh (precompiled binaries) -├── ✅ update.sh (for upgrades) -├── ✅ restore_hbbs.sh (rollback) -│ -├── ✅ web/ (Flask console) -│ ├── ✅ app.py (ban management) -│ ├── ✅ requirements.txt -│ ├── ✅ betterdesk.service -│ ├── ✅ templates/index.html -│ └── ✅ static/ (CSS, JS, icons) -│ -├── ✅ hbbs-patch/ -│ ├── ✅ bin/ (NEW - precompiled) -│ │ ├── ✅ hbbs-v8 (9.5 MB) -│ │ ├── ✅ hbbr-v8 (5.0 MB) -│ │ ├── ✅ README.md -│ │ └── ✅ CHECKSUMS.md -│ │ -│ ├── ✅ src/ (source patches) -│ ├── ✅ build.sh (rebuild script) -│ ├── ✅ deploy-v8.sh -│ ├── ✅ BAN_ENFORCEMENT.md (v8) -│ ├── ✅ SECURITY_AUDIT.md -│ └── ✅ test scripts -│ -├── ✅ docs/ -│ ├── ✅ INSTALLATION_V8.md -│ ├── ✅ UPDATE_GUIDE.md -│ └── ✅ other guides -│ -├── ✅ migrations/ (database) -└── ✅ screenshots/ (UI examples) -``` - -## 📊 Statistics - -- **Total Files**: ~100+ -- **Lines of Code**: ~10,000+ -- **Documentation**: 15+ markdown files -- **Installation Time**: 2-3 minutes (vs 20 min before) -- **Dependencies Removed**: git, cargo, rustc (~500 MB saved) -- **Binary Size**: 14.5 MB total (hbbs + hbbr) -- **Ban Enforcement**: 100% effective, bidirectional - -## 🚀 Ready for Publication - -### GitHub Release Steps - -1. **Initialize git repository** - ```bash - git init - git add . - git commit -m "Initial commit: BetterDesk Console v1.2.0-v8" - ``` - -2. **Create GitHub repository** - ```bash - gh repo create BetterDeskConsole --public --source=. --remote=origin - ``` - -3. **Push to GitHub** - ```bash - git branch -M main - git push -u origin main - ``` - -4. **Create release** - ```bash - gh release create v1.2.0-v8 \ - --title "BetterDesk Console v1.2.0-v8 - Precompiled Binaries + Bidirectional Bans" \ - --notes "See CHANGELOG.md for details" \ - hbbs-patch/bin/hbbs-v8 \ - hbbs-patch/bin/hbbr-v8 - ``` - -5. **Tag binaries** - ```bash - git tag -a v1.2.0-v8 -m "Version 1.2.0-v8 with precompiled binaries" - git push origin v1.2.0-v8 - ``` - -## 🎯 Next Steps (Post-Release) - -1. **Community Engagement** - - [ ] Submit to RustDesk community forum - - [ ] Reddit post in r/selfhosted - - [ ] Tweet about release - -2. **Monitoring** - - [ ] Watch for issues/bug reports - - [ ] Monitor installation success rate - - [ ] Gather user feedback - -3. **Future Improvements** - - [ ] Multi-architecture binaries (ARM64) - - [ ] Docker container - - [ ] Web console authentication - - [ ] Automated testing suite - -## ✅ Final Verification - -Run these commands before publishing: - -```bash -# 1. Verify no sensitive data -grep -r "192.168.0.110" . --exclude-dir=.git -grep -r "unitronix@" . --exclude-dir=.git - -# 2. Verify binaries exist -ls -lh hbbs-patch/bin/hbbs-v8 hbbs-patch/bin/hbbr-v8 - -# 3. Verify checksums -sha256sum hbbs-patch/bin/*-v8 - -# 4. Test installer (dry run) -bash -n install.sh - -# 5. Verify documentation links -find docs -name "*.md" -exec grep -l "YOUR_SERVER_IP" {} \; -``` - -## 📝 Release Notes Draft - -```markdown -# BetterDesk Console v1.2.0-v8 - -## 🚀 Major Changes - -- **Precompiled Binaries**: Installation now takes 2-3 minutes (vs 20 minutes) -- **Bidirectional Ban Enforcement**: Banned devices blocked in BOTH directions -- **No Compilation Required**: Removed Rust toolchain dependency -- **Simplified Installation**: Just Python3 + pip3 needed - -## 📦 What's Included - -- HBBS v8 (9.5 MB) - Signal server with bidirectional bans -- HBBR v8 (5.0 MB) - Relay server -- Web management console (Flask + Material Design) -- Complete documentation - -## 🔧 Installation - -```bash -git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git -cd Rustdesk-FreeConsole -sudo chmod +x install.sh -sudo ./install.sh -``` - -## 📖 Documentation - -- [Installation Guide](docs/INSTALLATION_V8.md) -- [Ban Enforcement Technical Docs](hbbs-patch/BAN_ENFORCEMENT.md) -- [Security Audit](hbbs-patch/SECURITY_AUDIT.md) - -## 🔐 Security - -- SHA256 checksums provided -- Full source code available -- AGPL-3.0 license -- Security audit included -``` - ---- - -**Status**: ✅ **READY FOR RELEASE** - -All systems go! 🎉 diff --git a/docs/SECURITY_CLEANUP_REPORT.md b/docs/SECURITY_CLEANUP_REPORT.md deleted file mode 100644 index 34b17fb3..00000000 --- a/docs/SECURITY_CLEANUP_REPORT.md +++ /dev/null @@ -1,161 +0,0 @@ -# 🔒 Bezpieczeństwo Danych - Raport Czyszczenia - -**Data:** 6 stycznia 2026 -**Status:** ✅ ZAKOŃCZONE - ---- - -## 📊 Podsumowanie Zmian - -### Pliki Zabezpieczone (18 plików): -1. ✅ README.md -2. ✅ hbbs-patch/deploy.ps1 -3. ✅ hbbs-patch/deploy-v6.ps1 -4. ✅ hbbs-patch/deploy-v8.sh -5. ✅ hbbs-patch/QUICKSTART.md -6. ✅ hbbs-patch/BAN_ENFORCEMENT.md -7. ✅ hbbs-patch/test_ban_enforcement.ps1 -8. ✅ hbbs-patch/diagnose_ban.ps1 -9. ✅ docs/UPDATE_REFERENCE.md -10. ✅ docs/UPDATE_GUIDE.md -11. ✅ docs/QUICKSTART_UPDATE.md -12. ✅ dev_modules/update.ps1 -13. ✅ dev_modules/test_ban_api.sh -14. ✅ deprecated/BAN_ENFORCER_TEST.md (częściowo) -15. ✅ .gitignore (zaktualizowany) -16. ✅ SECURITY_PLACEHOLDERS.md (nowy) -17. ✅ SECURITY_AUDIT.md (stworzony wcześniej) -18. ✅ Ten raport - ---- - -## 🔄 Zamienione Dane - -| Dane Wrażliwe | Placeholder | Wystąpienia | -|---------------|-------------|-------------| -| `192.168.0.110` | `YOUR_SERVER_IP` | ~150+ | -| `unitronix` | `YOUR_SSH_USER` | ~150+ | - ---- - -## 📁 Pozostałe Pliki - -### Deprecated (Przestarzałe pliki - ~33 wystąpienia) -Pliki w katalogu `deprecated/` zostały częściowo zaktualizowane, ale zawierają starą dokumentację która nie jest już używana: -- `deprecated/BAN_ENFORCER.md` - stary system banowania -- `deprecated/BAN_ENFORCER_TEST.md` - stare testy - -**Rekomendacja:** Te pliki są przestarzałe i nie powinny być używane. Rozważ: -1. Całkowite usunięcie katalogu `deprecated/` przed publikacją -2. Lub dokończenie czyszczenia tych plików - ---- - -## 🛡️ Zabezpieczenia Wdrożone - -### 1. Placeholders w Kodzie ✅ -Wszystkie aktywne pliki używają placeholderów zamiast rzeczywistych danych. - -### 2. Dokumentacja Bezpieczeństwa ✅ -- [SECURITY_PLACEHOLDERS.md](SECURITY_PLACEHOLDERS.md) - instrukcja użycia -- [SECURITY_AUDIT.md](hbbs-patch/SECURITY_AUDIT.md) - audyt bezpieczeństwa - -### 3. .gitignore Zaktualizowany ✅ -Dodano ochronę przed przypadkowym commit'em: -```gitignore -.env -.env.local -config.local.* -*_local.sh -*_local.ps1 -``` - -### 4. Szablony Konfiguracji ✅ -Użytkownicy mogą bezpiecznie tworzyć lokalne pliki konfiguracyjne. - ---- - -## ⚠️ Co Dalej? - -### Przed publikacją na GitHub: - -1. **Sprawdź historię git:** - ```bash - git log --all --full-history -- "*" | grep -i "192.168" - ``` - -2. **Jeśli znajdziesz wrażliwe dane w historii:** - ```bash - # UWAGA: To przepisze całą historię! - git filter-branch --tree-filter 'find . -type f -exec sed -i "s/192.168.0.110/YOUR_SERVER_IP/g" {} \;' HEAD - ``` - - Lub użyj BFG Repo-Cleaner: - ```bash - bfg --replace-text passwords.txt - git reflog expire --expire=now --all - git gc --prune=now --aggressive - ``` - -3. **Usuń deprecated/ przed publikacją:** - ```bash - git rm -r deprecated/ - git commit -m "Remove deprecated files with sensitive data" - ``` - -4. **Przeglądnij każdy plik przed push:** - ```bash - git diff --name-only origin/main - ``` - -5. **Weryfikacja finalna:** - ```bash - # Sprawdź czy nie ma więcej wrażliwych danych - grep -r "192.168.0.110" . - grep -r "unitronix@" . - ``` - ---- - -## ✅ Checklist Przed Publikacją - -- [ ] Usunięto katalog `deprecated/` lub wyczyszczono go z danych -- [ ] Sprawdzono historię git pod kątem wrażliwych danych -- [ ] Przeczytano [SECURITY_PLACEHOLDERS.md](SECURITY_PLACEHOLDERS.md) -- [ ] Zweryfikowano że wszystkie przykłady używają placeholderów -- [ ] Zaktualizowano README.md z linkiem do SECURITY_PLACEHOLDERS.md -- [ ] Przetestowano czy skrypty działają po zamianie placeholderów -- [ ] Dodano badge "Security" do README.md - ---- - -## 🔐 Bezpieczne Praktyki - -### DO: -✅ Używaj zmiennych środowiskowych -✅ Twórz lokalne pliki konfiguracyjne (z .gitignore) -✅ Regularnie sprawdzaj czy nie commit'ujesz wrażliwych danych -✅ Używaj SSH keys zamiast haseł - -### NIE RÓB: -❌ Nie commituj plików `.env` -❌ Nie wklejaj prawdziwych IP w issue/PR -❌ Nie udostępniaj zrzutów ekranu z danymi -❌ Nie hardcoduj credentials w kodzie - ---- - -## 📞 Kontakt - -Jeśli znajdziesz jakieś wrażliwe dane które pominąłem: -1. **NIE** zgłaszaj ich publicznie w issue -2. Wyślij prywatną wiadomość do maintainera -3. Lub stwórz private security advisory na GitHub - ---- - -**Status Bezpieczeństwa:** 🟢 BEZPIECZNY do publikacji (po wykonaniu checklist) - ---- - -*Raport wygenerowany automatycznie przez GitHub Copilot* diff --git a/docs/SECURITY_PLACEHOLDERS.md b/docs/SECURITY_PLACEHOLDERS.md deleted file mode 100644 index b1895fea..00000000 --- a/docs/SECURITY_PLACEHOLDERS.md +++ /dev/null @@ -1,219 +0,0 @@ -# Security Placeholders - Configuration Guide - -## 🔐 About Placeholders - -This repository contains **placeholders** instead of actual server credentials for security reasons. Before using any scripts or following the documentation, you must replace these placeholders with your actual values. - ---- - -## 📝 Placeholders Used - -| Placeholder | Description | Example Value | -|------------|-------------|---------------| -| `YOUR_SERVER_IP` | Your RustDesk server IP address | `192.168.1.100` or `server.example.com` | -| `YOUR_SSH_USER` | SSH username for server access | `admin`, `rustdesk`, etc. | - ---- - -## 🔄 How to Replace Placeholders - -### Option 1: Manual Replacement (Recommended for beginners) - -When you see a command like this: -```bash -ssh YOUR_SSH_USER@YOUR_SERVER_IP -``` - -Replace it with your actual values: -```bash -ssh admin@192.168.1.100 -``` - -### Option 2: Global Find & Replace (For advanced users) - -If you want to configure multiple files at once: - -**Windows (PowerShell):** -```powershell -# Navigate to project directory -cd C:\path\to\BetterDeskConsole - -# Replace server IP -(Get-ChildItem -Recurse -Include *.md,*.ps1,*.sh).ForEach{ - (Get-Content $_.FullName) -replace 'YOUR_SERVER_IP', '192.168.1.100' | - Set-Content $_.FullName -} - -# Replace SSH user -(Get-ChildItem -Recurse -Include *.md,*.ps1,*.sh).ForEach{ - (Get-Content $_.FullName) -replace 'YOUR_SSH_USER', 'admin' | - Set-Content $_.FullName -} -``` - -**Linux/macOS:** -```bash -# Replace server IP -find . -type f \( -name "*.md" -o -name "*.ps1" -o -name "*.sh" \) \ - -exec sed -i 's/YOUR_SERVER_IP/192.168.1.100/g' {} + - -# Replace SSH user -find . -type f \( -name "*.md" -o -name "*.ps1" -o -name "*.sh" \) \ - -exec sed -i 's/YOUR_SSH_USER/admin/g' {} + -``` - -### Option 3: Environment Variables (Most secure) - -Set environment variables instead of hardcoding values: - -**PowerShell:** -```powershell -$env:RUSTDESK_SERVER="192.168.1.100" -$env:RUSTDESK_USER="admin" - -# Use in scripts -.\update.ps1 -RemoteHost $env:RUSTDESK_SERVER -RemoteUser $env:RUSTDESK_USER -``` - -**Bash:** -```bash -export RUSTDESK_SERVER="192.168.1.100" -export RUSTDESK_USER="admin" - -# Use in scripts -ssh $RUSTDESK_USER@$RUSTDESK_SERVER -``` - ---- - -## 📂 Files Containing Placeholders - -The following files contain placeholders that may need to be replaced: - -### Documentation -- [README.md](README.md) -- [docs/UPDATE_GUIDE.md](docs/UPDATE_GUIDE.md) -- [docs/UPDATE_REFERENCE.md](docs/UPDATE_REFERENCE.md) -- [docs/QUICKSTART_UPDATE.md](docs/QUICKSTART_UPDATE.md) -- [hbbs-patch/QUICKSTART.md](hbbs-patch/QUICKSTART.md) -- [hbbs-patch/BAN_ENFORCEMENT.md](hbbs-patch/BAN_ENFORCEMENT.md) - -### Scripts -- [hbbs-patch/deploy.ps1](hbbs-patch/deploy.ps1) -- [hbbs-patch/deploy-v6.ps1](hbbs-patch/deploy-v6.ps1) -- [hbbs-patch/deploy-v8.sh](hbbs-patch/deploy-v8.sh) -- [hbbs-patch/test_ban_enforcement.ps1](hbbs-patch/test_ban_enforcement.ps1) -- [hbbs-patch/diagnose_ban.ps1](hbbs-patch/diagnose_ban.ps1) -- [dev_modules/update.ps1](dev_modules/update.ps1) -- [dev_modules/test_ban_api.sh](dev_modules/test_ban_api.sh) - ---- - -## ⚠️ Security Warnings - -### DO NOT: -- ❌ Commit files with real credentials to public repositories -- ❌ Share screenshots containing real IP addresses or usernames -- ❌ Push configuration files with actual server details - -### DO: -- ✅ Keep placeholders in version control -- ✅ Use environment variables for sensitive data -- ✅ Create a local `.env` file (add to `.gitignore`) -- ✅ Document your actual values in a secure password manager - ---- - -## 🔒 Best Practices - -### 1. Create a Local Configuration File - -Create `.env` file (excluded from git): -```bash -# .env - DO NOT COMMIT THIS FILE -RUSTDESK_SERVER_IP=192.168.1.100 -RUSTDESK_SSH_USER=admin -RUSTDESK_DB_PATH=/opt/rustdesk/db_v2.sqlite3 -``` - -### 2. Add to .gitignore - -```gitignore -# Sensitive configuration -.env -.env.local -config.local.ps1 -*_local.sh -``` - -### 3. Use Configuration Templates - -Create `config.template` files: -```powershell -# config.template.ps1 -$ServerIP = "YOUR_SERVER_IP" -$SSHUser = "YOUR_SSH_USER" -``` - -Then copy and customize: -```powershell -Copy-Item config.template.ps1 config.local.ps1 -# Edit config.local.ps1 with your values -``` - ---- - -## 🚀 Quick Start - -1. **Clone the repository:** - ```bash - git clone https://github.com/UNITRONIX/Rustdesk-FreeConsole.git - cd Rustdesk-FreeConsole - ``` - -2. **Configure your credentials:** - - **Option A - Environment Variables (Recommended):** - ```powershell - # Windows - $env:RUSTDESK_SERVER="192.168.1.100" - $env:RUSTDESK_USER="admin" - ``` - - **Option B - Direct Replacement:** - Follow "Option 2: Global Find & Replace" above - -3. **Test connection:** - ```bash - ssh YOUR_SSH_USER@YOUR_SERVER_IP # Replace placeholders! - ``` - -4. **Run scripts:** - ```powershell - # After replacing placeholders - .\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER - ``` - ---- - -## 📞 Support - -If you have questions about configuration: -1. Check the [main README](README.md) -2. Review [UPDATE_GUIDE.md](docs/UPDATE_GUIDE.md) -3. See [Security Audit](hbbs-patch/SECURITY_AUDIT.md) - ---- - -## ✅ Verification Checklist - -Before running any script, verify: -- [ ] All `YOUR_SERVER_IP` replaced with actual IP -- [ ] All `YOUR_SSH_USER` replaced with actual username -- [ ] SSH connection works: `ssh YOUR_SSH_USER@YOUR_SERVER_IP` -- [ ] Server paths are correct: `/opt/rustdesk/`, `/opt/BetterDeskConsole/` -- [ ] No actual credentials committed to git - ---- - -**Remember:** Security is not just about technology—it's about practice. Always think before you commit! 🔐 diff --git a/docs/SECURITY_URGENT.md b/docs/SECURITY_URGENT.md deleted file mode 100644 index 60a2419c..00000000 --- a/docs/SECURITY_URGENT.md +++ /dev/null @@ -1,323 +0,0 @@ -# 🚨 PILNE OSTRZEŻENIE BEZPIECZEŃSTWA - -## ⚠️ KRYTYCZNE ZAGROŻENIE: Niezabezpieczone HTTP API - -**Data wykrycia:** 10 stycznia 2026 -**Priorytet:** 🔴 KRYTYCZNY -**Status:** Wymaga natychmiastowej naprawy przed wdrożeniem produkcyjnym - ---- - -## 🔍 Opis Problemu - -HTTP API (port 21114) **NIE MA ŻADNEJ AUTENTYKACJI** i nasłuchuje na `0.0.0.0` (wszystkie interfejsy sieciowe). - -### Co to oznacza? - -```bash -# KAŻDY w Twojej sieci może wykonać: -curl http://YOUR_SERVER_IP:21114/api/peers - -# I otrzyma: -{ - "success": true, - "data": [ - {"id": "123456789", "note": "CEO Laptop", "online": true}, - {"id": "987654321", "note": "Finance PC", "online": false} - ] -} -``` - -**Potencjalne konsekwencje:** -- ✖️ Wyciek informacji o wszystkich urządzeniach w sieci -- ✖️ Tracking online/offline statusu użytkowników -- ✖️ Ekspozycja device IDs do potencjalnych ataków -- ✖️ Naruszenie prywatności (GDPR/RODO) -- ✖️ Reconnaissance dla atakujących - ---- - -## 🛠️ NATYCHMIASTOWE DZIAŁANIA - -### Opcja 1: **Firewall (Najszybsze - 2 minuty)** - -```bash -# Linux (iptables) -sudo iptables -A INPUT -p tcp --dport 21114 -s 127.0.0.1 -j ACCEPT -sudo iptables -A INPUT -p tcp --dport 21114 -j DROP - -# Lub (ufw) -sudo ufw deny 21114 -sudo ufw allow from 127.0.0.1 to any port 21114 - -# Windows -New-NetFirewallRule -DisplayName "Block HBBS API" -Direction Inbound -LocalPort 21114 -Protocol TCP -Action Block -New-NetFirewallRule -DisplayName "Allow HBBS API Localhost" -Direction Inbound -LocalAddress 127.0.0.1 -LocalPort 21114 -Protocol TCP -Action Allow -``` - -**Efekt:** API dostępne tylko lokalnie (localhost), konsola webowa działa, zewnętrzny dostęp zablokowany. - -### Opcja 2: **Zmiana nasłuchiwania (5 minut)** - -Edytuj `hbbs-patch/src/http_api.rs`: - -```rust -// PRZED (niebezpieczne): -let addr = SocketAddr::from(([0, 0, 0, 0], port)); - -// PO (bezpieczne): -let addr = SocketAddr::from(([127, 0, 0, 1], port)); -``` - -Rekompiluj i wdroż: -```bash -cd hbbs-patch -bash build.sh # Linux -# LUB -.\build-windows-local.ps1 # Windows - -sudo systemctl restart rustdesksignal -``` - ---- - -## 🔐 PEŁNE ZABEZPIECZENIE (Zalecane) - -### 1. Autentykacja API Key - -Edytuj `hbbs-patch/src/http_api.rs`: - -```rust -use axum::{ - extract::Extension, - http::{Request, StatusCode, header::HeaderMap}, - middleware::{self, Next}, - response::Response, - routing::get, - Router, -}; -use std::env; - -// API Key middleware -async fn check_api_key( - headers: HeaderMap, - request: Request, - next: Next, -) -> Result { - // Pobierz klucz z zmiennej środowiskowej - let expected_key = env::var("HBBS_API_KEY").unwrap_or_else(|_| { - log::warn!("HBBS_API_KEY not set, using default (INSECURE!)"); - "CHANGE_ME_INSECURE_DEFAULT".to_string() - }); - - // Sprawdź nagłówek X-API-Key - if let Some(api_key) = headers.get("X-API-Key") { - if api_key.to_str().ok() == Some(&expected_key) { - return Ok(next.run(request).await); - } - } - - log::warn!("Unauthorized API access attempt from {:?}", request.uri()); - Err(StatusCode::UNAUTHORIZED) -} - -pub async fn start_api_server(/* ... */) -> Result<(), Box> { - // ... existing code ... - - let app = Router::new() - .route("/api/health", get(health_check)) - .route("/api/peers", get(get_online_peers)) - .layer(middleware::from_fn(check_api_key)) // ← DODAJ TO - .layer(axum::Extension(state)); - - // Opcjonalnie: bind tylko do localhost - let addr = SocketAddr::from(([127, 0, 0, 1], port)); - - log::info!("HTTP API server listening on {} (with API Key auth)", addr); - - // ... rest of code ... -} -``` - -**Aktualizacja `app.py`:** - -```python -import os - -# Na początku pliku -API_KEY = os.environ.get('HBBS_API_KEY', 'CHANGE_ME_INSECURE_DEFAULT') - -# W funkcjach wywołujących API: -headers = {'X-API-Key': API_KEY} -response = requests.get(f'{HBBS_API_URL}/peers', timeout=2, headers=headers) -``` - -**Ustawienie klucza:** - -```bash -# Linux (dodaj do /etc/environment lub .bashrc) -export HBBS_API_KEY="$(openssl rand -hex 32)" - -# Systemd service -sudo nano /etc/systemd/system/rustdesksignal.service -# Dodaj linię: -Environment="HBBS_API_KEY=your-secure-random-key-here" - -# Flask service -sudo nano /etc/systemd/system/betterdesk.service -# Dodaj linię: -Environment="HBBS_API_KEY=your-secure-random-key-here" - -sudo systemctl daemon-reload -sudo systemctl restart rustdesksignal betterdesk -``` - -```powershell -# Windows (jako Administrator) -[System.Environment]::SetEnvironmentVariable("HBBS_API_KEY", "your-secure-random-key", "Machine") - -# Restart serwisów -Restart-Service RustDesk* -``` - -### 2. Rate Limiting - -```bash -pip install Flask-Limiter -``` - -```python -# app.py -from flask_limiter import Limiter -from flask_limiter.util import get_remote_address - -limiter = Limiter( - app=app, - key_func=get_remote_address, - default_limits=["200 per day", "50 per hour"], - storage_uri="memory://" -) - -@app.route('/api/devices') -@limiter.limit("30 per minute") -def get_devices(): - # ... existing code ... -``` - -### 3. CORS Protection - -```bash -cargo add tower-http --features cors -``` - -```rust -// http_api.rs -use tower_http::cors::{CorsLayer, Any}; -use http::Method; - -let cors = CorsLayer::new() - .allow_origin("http://localhost:5000".parse::().unwrap()) - .allow_methods([Method::GET]) - .allow_headers([HeaderName::from_static("x-api-key")]); - -let app = Router::new() - .route("/api/peers", get(get_online_peers)) - .layer(cors) - .layer(middleware::from_fn(check_api_key)) - .layer(axum::Extension(state)); -``` - ---- - -## ✅ WERYFIKACJA ZABEZPIECZEŃ - -### Test 1: Firewall działa - -```bash -# Z innego komputera w sieci: -curl http://YOUR_SERVER_IP:21114/api/health -# Powinno: Connection refused lub timeout - -# Z serwera lokalnie: -curl http://localhost:21114/api/health -# Powinno: {"success":true,"data":"RustDesk API is running"} -``` - -### Test 2: API Key działa - -```bash -# Bez klucza: -curl http://localhost:21114/api/peers -# Powinno: 401 Unauthorized - -# Z kluczem: -curl -H "X-API-Key: YOUR_KEY" http://localhost:21114/api/peers -# Powinno: {"success":true,"data":[...]} -``` - -### Test 3: Rate Limiting działa - -```bash -# Wyślij 50 requestów szybko: -for i in {1..50}; do curl http://localhost:5000/api/devices; done -# Po ~30 requestach powinno: 429 Too Many Requests -``` - ---- - -## 📋 CHECKLIST WDROŻENIA - -### Minimalne zabezpieczenie (przed produkcją): -- [ ] Firewall blokuje port 21114 z zewnątrz -- [ ] HBBS API nasłuchuje tylko na 127.0.0.1 -- [ ] Logi monitorowane pod kątem podejrzanej aktywności - -### Pełne zabezpieczenie (zalecane): -- [ ] API Key authentication zaimplementowane -- [ ] Rate limiting w Flask -- [ ] CORS skonfigurowany -- [ ] Klucze w zmiennych środowiskowych -- [ ] HTTPS/TLS dla produkcji (certyfikat SSL) -- [ ] Monitoring i alerty - ---- - -## 🚦 BIEŻĄCY STATUS ZABEZPIECZEŃ - -| Warstwa | Status | Uwagi | -|---------|--------|-------| -| SQL Injection | ✅ ZABEZPIECZONE | Parametryzowane zapytania | -| XSS | ⚠️ CZĘŚCIOWE | Podstawowa sanityzacja | -| Buffer Overflow | ✅ ZABEZPIECZONE | Rust type safety | -| Race Conditions | ✅ ZABEZPIECZONE | Arc/RwLock | -| **Authentication** | 🔴 **BRAK** | **WYMAGA NAPRAWY** | -| Authorization | 🔴 BRAK | Wymaga naprawy | -| Rate Limiting | 🔴 BRAK | Wymaga naprawy | -| CORS | 🔴 BRAK | Wymaga naprawy | -| HTTPS/TLS | ⚠️ OPCJONALNE | Zalecane dla WAN | - ---- - -## 📞 DALSZE KROKI - -1. **Natychmiast:** Zastosuj firewall (Opcja 1) -2. **Dziś:** Zmień bind na 127.0.0.1 (Opcja 2) -3. **W tym tygodniu:** Implementuj API Key authentication -4. **Przy okazji:** Rate limiting + CORS - ---- - -## ⚖️ ODPOWIEDZIALNOŚĆ - -**Obecny stan:** -System działa poprawnie funkcjonalnie, ale ma krytyczną lukę w zabezpieczeniach. -**NIE WDRAŻAJ DO PRODUKCJI** bez zastosowania minimum Opcji 1 lub 2. - -**Po zastosowaniu poprawek:** -System bezpieczny dla użytku wewnętrznego w sieci lokalnej. Dla ekspozycji na internet dodatkowy HTTPS + hardening. - ---- - -**Autor analizy:** GitHub Copilot -**Data:** 10 stycznia 2026 -**Wersja dokumentu:** 1.0 diff --git a/docs/UPDATE_REFERENCE.md b/docs/UPDATE_REFERENCE.md deleted file mode 100644 index b59ab105..00000000 --- a/docs/UPDATE_REFERENCE.md +++ /dev/null @@ -1,227 +0,0 @@ -# Update Scripts - Quick Reference - -## 📋 Command Syntax - -### Linux -```bash -sudo ./update.sh [OPTIONS] -``` - -### Windows -```powershell -.\update.ps1 -RemoteHost -RemoteUser [OPTIONS] -``` - ---- - -## 🔧 Parameters - -### Linux (update.sh) - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `--rustdesk-dir PATH` | RustDesk installation directory | `/opt/rustdesk` | -| `--console-dir PATH` | BetterDesk Console directory | `/opt/BetterDeskConsole` | -| `--help` | Show help message | - | - -### Windows (update.ps1) - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `-RemoteHost` | Server IP/hostname | **(required)** | -| `-RemoteUser` | SSH username | **(required)** | -| `-RemotePath` | BetterDesk Console directory | `/opt/BetterDeskConsole` | -| `-RustDeskPath` | RustDesk installation directory | `/opt/rustdesk` | -| `-DbPath` | Database file path | `{RustDeskPath}/db_v2.sqlite3` | - ---- - -## 💡 Usage Examples - -### Basic Update (Default Paths) - -**Linux:** -```bash -sudo ./update.sh -``` - -**Windows:** -```powershell -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -``` - ---- - -### Custom RustDesk Directory - -**Linux:** -```bash -sudo ./update.sh --rustdesk-dir /custom/rustdesk -``` - -**Windows:** -```powershell -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -RustDeskPath "/custom/rustdesk" -``` - ---- - -### Custom Console Directory - -**Linux:** -```bash -sudo ./update.sh --console-dir /var/www/betterdesk -``` - -**Windows:** -```powershell -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER -RemotePath "/var/www/betterdesk" -``` - ---- - -### Both Custom Directories - -**Linux:** -```bash -sudo ./update.sh --rustdesk-dir /home/user/rustdesk --console-dir /home/user/console -``` - -**Windows:** -```powershell -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER ` - -RustDeskPath "/home/user/rustdesk" ` - -RemotePath "/home/user/console" -``` - ---- - -### Custom Database Path (Windows only) - -**Windows:** -```powershell -.\update.ps1 -RemoteHost YOUR_SERVER_IP -RemoteUser YOUR_SSH_USER ` - -DbPath "/custom/database/location/db.sqlite3" -``` - ---- - -## 📁 Default File Locations - -### Standard Installation -``` -/opt/rustdesk/ -├── db_v2.sqlite3 # Database -├── hbbs # HBBS binary -└── id_ed25519.pub # Public key - -/opt/BetterDeskConsole/ -├── app.py # Flask backend -├── static/ -│ └── script.js # Frontend JS -└── templates/ - └── index.html # UI template -``` - -### Custom Installation Example -``` -/home/admin/services/rustdesk/ -└── db_v2.sqlite3 - -/var/www/betterdesk/ -├── app.py -├── static/ -└── templates/ -``` - ---- - -## ✅ Quick Verification - -After update, check: - -```bash -# Check service -systemctl status betterdesk - -# Check database columns -sqlite3 /opt/rustdesk/db_v2.sqlite3 "PRAGMA table_info(peer);" | grep -E "is_banned|is_deleted" - -# Check web console -curl http://localhost:5000/api/stats -``` - -Expected API response: -```json -{ - "success": true, - "stats": { - "total": 51, - "active": 14, - "inactive": 37, - "banned": 0, - "with_notes": 21 - } -} -``` - ---- - -## 🔙 Rollback - -If update fails, restore from backup: - -```bash -# Find backup -ls -ltr /opt/ | grep betterdesk-backup - -# Restore -BACKUP_DIR="/opt/betterdesk-backup-YYYYMMDD-HHMMSS" -sudo systemctl stop betterdesk -sudo cp $BACKUP_DIR/db_v2.sqlite3.backup /opt/rustdesk/db_v2.sqlite3 -sudo cp $BACKUP_DIR/*.backup /opt/BetterDeskConsole/ -sudo systemctl start betterdesk -``` - ---- - -## 🆘 Troubleshooting - -### Linux: Permission Denied -```bash -chmod +x update.sh -sudo ./update.sh -``` - -### Windows: SSH Connection Failed -```powershell -# Test connection -ssh YOUR_SSH_USER@YOUR_SERVER_IP - -# Set up SSH keys -ssh-keygen -ssh-copy-id YOUR_SSH_USER@YOUR_SERVER_IP -``` - -### Windows: Execution Policy -```powershell -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -``` - -### Database Migration Failed -```bash -# Check permissions -ls -l /opt/rustdesk/db_v2.sqlite3 - -# Run manually -sudo python3 migrations/v1.1.0_device_bans.py -``` - ---- - -## 📚 See Also - -- [UPDATE_GUIDE.md](UPDATE_GUIDE.md) - Full documentation -- [QUICKSTART_UPDATE.md](QUICKSTART_UPDATE.md) - Detailed examples -- [CHANGELOG.md](CHANGELOG.md) - Version history -- [README.md](README.md) - Main documentation diff --git a/hbbs-patch/BAN_CHECK_PATCH.md b/hbbs-patch/BAN_CHECK_PATCH.md deleted file mode 100644 index e293208c..00000000 --- a/hbbs-patch/BAN_CHECK_PATCH.md +++ /dev/null @@ -1,149 +0,0 @@ -# HBBS Ban Check Patch - -Modyfikacja RustDesk Server (hbbs) v1.1.14 dodająca sprawdzanie zbanowanych urządzeń. - -## Zmiany - -### 1. database.rs - Dodanie metody sprawdzania bana - -Dodaj nową metodę do struktury `Database`: - -```rust -impl Database { - // ... existing methods ... - - /// Check if a device is banned - pub async fn is_device_banned(&self, id: &str) -> ResultType { - let result = sqlx::query!( - "SELECT is_banned FROM peer WHERE id = ? AND is_deleted = 0", - id - ) - .fetch_optional(self.pool.get().await?.deref_mut()) - .await?; - - Ok(result.map(|r| r.is_banned.unwrap_or(0) == 1).unwrap_or(false)) - } -} -``` - -### 2. peer.rs - Sprawdzenie bana podczas rejestracji - -Modyfikuj metodę `update_pk`: - -```rust -impl PeerMap { - #[inline] - pub(crate) async fn update_pk( - &mut self, - id: String, - peer: LockPeer, - addr: SocketAddr, - uuid: Bytes, - pk: Bytes, - ip: String, - ) -> register_pk_response::Result { - log::info!("update_pk {} {:?} {:?} {:?}", id, addr, uuid, pk); - - // *** NOWE: Sprawdź czy urządzenie jest zbanowane *** - match self.db.is_device_banned(&id).await { - Ok(true) => { - log::warn!("Registration rejected: device {} is BANNED", id); - return register_pk_response::Result::UUID_MISMATCH; // Odrzuć rejestrację - } - Err(e) => { - log::error!("Failed to check ban status for {}: {}", id, e); - // W razie błędu bazy, przepuść (fail-open) - } - _ => {} - } - - // ... reszta oryginalnego kodu ... - let (info_str, guid) = { - let mut w = peer.write().await; - w.socket_addr = addr; - w.uuid = uuid.clone(); - w.pk = pk.clone(); - w.last_reg_time = Instant::now(); - w.info.ip = ip; - ( - serde_json::to_string(&w.info).unwrap_or_default(), - w.guid.clone(), - ) - }; - // ... reszta metody bez zmian ... - } -} -``` - -### 3. Migracja bazy danych - -Upewnij się, że tabela `peer` ma kolumnę `is_banned`: - -```sql -ALTER TABLE peer ADD COLUMN is_banned INTEGER DEFAULT 0; -ALTER TABLE peer ADD COLUMN banned_at INTEGER; -ALTER TABLE peer ADD COLUMN banned_by VARCHAR(100); -ALTER TABLE peer ADD COLUMN ban_reason TEXT; -CREATE INDEX IF NOT EXISTS idx_peer_is_banned ON peer(is_banned); -``` - -## Kompilacja - -```bash -cd rustdesk-server -cargo build --release --bin hbbs - -# Skompilowany binarny: -# target/release/hbbs -``` - -## Instalacja - -```bash -# Backup starego -sudo systemctl stop hbbs -sudo cp /opt/rustdesk/hbbs /opt/rustdesk/hbbs.backup - -# Zainstaluj nowy -sudo cp target/release/hbbs /opt/rustdesk/ -sudo chmod +x /opt/rustdesk/hbbs - -# Restart -sudo systemctl start hbbs -sudo systemctl status hbbs -``` - -## Weryfikacja - -```bash -# Sprawdź logi -sudo journalctl -u hbbs -f - -# Podczas próby połączenia zbanowanego urządzenia: -# "Registration rejected: device 123456789 is BANNED" -``` - -## Jak to działa - -1. **Klient próbuje się połączyć** → wysyła `RegisterPk` request -2. **HBBS odbiera request** → wywołuje `update_pk()` -3. **Sprawdzenie bazy** → `is_device_banned()` query -4. **Jeśli is_banned=1** → zwraca `UUID_MISMATCH` (odrzucenie) -5. **Klient dostaje błąd** → nie może się połączyć - -## Różnice vs Ban Enforcer - -| Ban Enforcer (stary) | HBBS Patch (nowy) | -|---|---| -| Czyści dane co 2s | Sprawdza przy każdej rejestracji | -| Wyścig z RustDesk | Natywna integracja | -| Możliwe "okna" | 100% skuteczność | -| +1 demon | Bez dodatkowych procesów | -| Modyfikuje bazę | Tylko odczyt | - -## Uwagi - -- Używamy `UUID_MISMATCH` jako kodu błędu (RustDesk go rozumie) -- "Fail-open" - jeśli baza nie odpowiada, przepuszczamy ruch (bezpieczeństwo > dostępność) -- Index na `is_banned` przyspiesza query -- Kompatybilne z istniejącymi kolumnami bazy diff --git a/hbbs-patch/README.md b/hbbs-patch/README.md index bca4aefa..a251437c 100644 --- a/hbbs-patch/README.md +++ b/hbbs-patch/README.md @@ -64,7 +64,7 @@ This matches the **exact same mechanism** used by the RustDesk desktop client to **Purpose**: Entry point for HBBS server **Modifications**: -- Added `api_port` parameter (default: 21114) +- Added `api_port` parameter (default: 21120) - Passes API port to `RendezvousServer::start()` - No changes to core HBBS functionality @@ -76,7 +76,7 @@ RendezvousServer::start( serial, &get_arg_or("key", "-".to_owned()), rmem, - 21114 // API port + 21120 // API port )?; ``` @@ -175,7 +175,7 @@ RendezvousServer::start( ▼ ┌─────────────────────────────┐ │ HTTP API Server │ - │ (Port 21114) │ + │ (Port 21120) │ └─────────────┬───────────────┘ │ Query status ▼ @@ -248,7 +248,7 @@ Binary output: `target/release/hbbs` ### Health Check: ```bash -curl http://localhost:21114/api/health +curl http://localhost:21120/api/health ``` Response: @@ -262,7 +262,7 @@ Response: ### List Peers: ```bash -curl http://localhost:21114/api/peers +curl http://localhost:21120/api/peers ``` Response: @@ -298,16 +298,62 @@ Response: ## Security Considerations -1. **API Binding**: By default, API listens on `0.0.0.0:21114` - - Consider using a firewall to restrict access - - Or modify `http_api.rs` to bind to `127.0.0.1` only +### API Authentication (v1.4.0+) -2. **No Authentication**: Current implementation has no API authentication - - Suitable for internal networks - - For public exposure, add authentication middleware +**X-API-Key Authentication**: The HBBS API now requires authentication for all requests: -3. **CORS**: Enabled for all origins (`*`) - - Modify `http_api.rs` CORS settings if needed +1. **API Key Generation**: During installation, a 64-character random API key is generated: + ```bash + openssl rand -base64 48 | tr -d '/+=' | cut -c1-64 + ``` + +2. **Key Storage**: Stored securely in `/opt/rustdesk/.api_key` with 600 permissions + +3. **Usage**: All API requests must include the `X-API-Key` header: + ```bash + curl -H "X-API-Key: YOUR_API_KEY" http://192.168.1.100:21120/api/health + ``` + +4. **Verification**: Middleware checks the header against stored key: + ```rust + async fn verify_api_key( + State(state): State>, + headers: HeaderMap, + request: Request, + next: Next, + ) -> Result { + let api_key = headers + .get("X-API-Key") + .and_then(|v| v.to_str().ok()) + .ok_or(StatusCode::UNAUTHORIZED)?; + + if api_key != state.api_key { + return Err(StatusCode::UNAUTHORIZED); + } + + Ok(next.run(request).await) + } + ``` + +### Network Access + +1. **API Binding**: API listens on `0.0.0.0:21120` (LAN accessible) + - Protected by X-API-Key authentication + - Web console automatically provides key + - External tools need API key from `/opt/rustdesk/.api_key` + +2. **Firewall Recommendations**: + ```bash + # Allow API on LAN only + sudo ufw allow from 192.168.0.0/16 to any port 21120 proto tcp + + # Or allow web console only (API via localhost) + sudo ufw allow 5000/tcp + ``` + +3. **CORS**: Enabled for all origins with credentials support + - Safe due to API key requirement + - Modify `http_api.rs` if stricter CORS needed --- diff --git a/hbbs-patch/SECURITY_AUDIT.md b/hbbs-patch/SECURITY_AUDIT.md index 9f4c1b22..71abf427 100644 --- a/hbbs-patch/SECURITY_AUDIT.md +++ b/hbbs-patch/SECURITY_AUDIT.md @@ -1,10 +1,79 @@ # Audyt Bezpieczeństwa - Modyfikacje RustDesk Server -**Data:** 6 stycznia 2026 -**Wersja:** v8 (dwukierunkowe blokowanie banów) +**Data audytu:** 6 stycznia 2026 +**Ostatnia aktualizacja:** 11 stycznia 2026 (v1.4.0 - dodano autentykację API) +**Wersja:** v8 (dwukierunkowe blokowanie banów) + v1.4.0 (API key authentication) **Audytor:** GitHub Copilot --- +## ✅ Zmiany Bezpieczeństwa v1.4.0 (11 stycznia 2026) + +### 🔐 Autentykacja API (X-API-Key) + +**Rozwiązane zagrożenie:** Brak autentykacji HTTP API + +**Implementacja:** +1. **Generowanie klucza API**: + - 64-znakowy losowy klucz przy instalacji + - Algorytm: `openssl rand -base64 48 | tr -d '/+=' | cut -c1-64` + - Przechowywany w `/opt/rustdesk/.api_key` z uprawnieniami 600 + +2. **Middleware weryfikacji** (http_api.rs): +```rust +async fn verify_api_key( + State(state): State>, + headers: HeaderMap, + request: Request, + next: Next, +) -> Result { + let api_key = headers + .get("X-API-Key") + .and_then(|v| v.to_str().ok()) + .ok_or(StatusCode::UNAUTHORIZED)?; + + if api_key != state.api_key { + return Err(StatusCode::UNAUTHORIZED); + } + + Ok(next.run(request).await) +} +``` + +3. **Wszystkie endpointy chronione**: + - `/api/health` - wymaga X-API-Key + - `/api/peers` - wymaga X-API-Key + - Brak klucza = 401 Unauthorized + - Nieprawidłowy klucz = 401 Unauthorized + +4. **Dostęp LAN**: + - API nasłuchuje na `0.0.0.0:21120` (dostępne w sieci LAN) + - Konsola web automatycznie dodaje X-API-Key do wszystkich żądań + - Zewnętrzne narzędzia muszą pobrać klucz z `/opt/rustdesk/.api_key` + +**Status:** ✅ ZAIMPLEMENTOWANE + +### 🌐 Konsola Web - System Uwierzytelniania + +**Funkcje bezpieczeństwa:** +1. **Logowanie użytkowników**: + - Hashowanie haseł bcrypt (cost 12) + - Tokeny sesji (24 godziny) + - Kontrola dostępu oparta na rolach (admin/operator/viewer) + +2. **Zarządzanie użytkownikami**: + - Panel administracyjny do tworzenia/edycji/usuwania użytkowników + - Audit log dla wszystkich akcji + - Ochrona hasłem dostępu do klucza publicznego + +3. **Ochrona danych**: + - Parametryzowane zapytania SQL + - Walidacja danych wejściowych + - Ochrona XSS/CSRF + +**Status:** ✅ ZAIMPLEMENTOWANE + +--- + ## 1. Streszczenie Wykonawcze ### 🔴 Krytyczne zagrożenia: 2 @@ -12,6 +81,8 @@ ### 🟡 Średnie zagrożenia: 2 ### 🟢 Niskie zagrożenia: 3 +**Uwaga:** Zagrożenia poniżej dotyczą głównie mechanizmu banowania urządzeń, nie API HTTP. + --- ## 2. Krytyczne Zagrożenia diff --git a/hbbs-patch/bin-with-api/CHECKSUMS.md b/hbbs-patch/bin-with-api/CHECKSUMS.md index c7db3948..245ef573 100644 --- a/hbbs-patch/bin-with-api/CHECKSUMS.md +++ b/hbbs-patch/bin-with-api/CHECKSUMS.md @@ -1,120 +1,28 @@ -# Binary Checksums - v1.3.0-secure +# HBBS/HBBR v8-api Binary Checksums +# Generated: 2026-01-15 19:41:12 +# All binaries compiled with HTTP API integration on port 21120 -Verification checksums for BetterDesk Console binaries with secure API (port 21120, localhost-only binding). +## hbbs-v8-api (Linux x86_64) +- **SHA256**: `DBCECD4DF6F3DFE13BD08DF1A03998A72D26FC6C168067D234A0EC7DCADDFA73` +- **Size**: 9.61 MB +- **Date**: 2026-01-15 19:12:42 +- **Features**: HTTP API, ban enforcement, fail-closed security -## SHA256 Checksums +## hbbr-v8-api (Linux x86_64) +- **SHA256**: `CA8B9D3E25E9EBE1C4BDA91E5E763B3715524E18B338C4FC4A07BC6B1F10B270` +- **Size**: 3.03 MB +- **Date**: 2026-01-15 19:12:43 +- **Features**: Relay server, fail-closed security -### Linux Binaries (x86_64) +## hbbs-v8-api.exe (Windows x86_64) +- **SHA256**: `BEF45AEA8D9320A09C5440EB39319D69042E5DFE5A45028EB169AB252A94A7A3` +- **Size**: 7.23 MB +- **Date**: 2026-01-15 19:41:12 +- **Features**: HTTP API, ban enforcement, fail-closed security -``` -7B09A6C024188AF5AAC8E94C64B4B97D68A92ABF7F902B34A7D91A9D99E44558 hbbs-v8-api -DF1B3FD3EF8793FD3A786E2BFBB330EE43A6C92D1A5915414F36011BE778E3FB hbbr-v8-api -``` +## hbbr-v8-api.exe (Windows x86_64) +- **SHA256**: `164FA5508F9DCC09AB7FA95AA1F90960BE1B5D71849323D99686EA83307D181F` +- **Size**: 2.75 MB +- **Date**: 2026-01-15 19:40:43 +- **Features**: Relay server, fail-closed security -**Build Date:** 10.01.2026 10:25 -**Size:** HBBS 9.59 MB, HBBR 4.73 MB -**Platform:** Linux x86_64 (Ubuntu 20.04+, Debian 11+) - -### Windows Binaries (x64) - -``` -EE1AB9C341B078D852EA32ED33CCD8664BC6A3D6EA818D321529B9654C69CD74 hbbs-v8-api.exe -37F452AE97407992DE1561B5F90747D9396E591C21E70B27897EEBEB652C1D25 hbbr-v8-api.exe -``` - -**Build Date:** 10.01.2026 04:42 -**Size:** HBBS 6.58 MB, HBBR 2.76 MB -**Platform:** Windows x64 (Windows 10+, Server 2016+) - -## Verification - -### Linux/macOS - -```bash -# Verify single file -sha256sum hbbs-v8-api -# Compare with checksum above - -# Verify all Linux binaries -sha256sum hbbs-v8-api hbbr-v8-api -``` - -### Windows (PowerShell) - -```powershell -# Verify single file -Get-FileHash hbbs-v8-api.exe -Algorithm SHA256 - -# Verify all Windows binaries -Get-ChildItem *.exe | ForEach-Object { Get-FileHash $_.Name -Algorithm SHA256 } -``` - -## Security Features - -All binaries (both Linux and Windows) include: - -✅ **Port 21120 API** - Changed from default 21114 -✅ **Localhost-only binding** - API accessible only from 127.0.0.1 -✅ **--api-port parameter** - Command-line configuration support -✅ **Zero network exposure** - API cannot be accessed from external networks -✅ **Bidirectional ban enforcement** - Source + target ban checks -✅ **Real-time database sync** - No restart required for ban changes - -## Build Information - -- **Base Version:** RustDesk Server 1.1.14 -- **Compiler:** cargo 1.92.0, rustc 1.92.0 -- **Source:** Modified hbbs-patch with security enhancements -- **Configuration:** - - HTTP API on port 21120 (instead of 21114) - - Binding to 127.0.0.1 only (not 0.0.0.0) - - API endpoints: `/api/health`, `/api/peers` - -## Verification Log - -```bash -# Example successful verification -$ sha256sum hbbs-v8-api -7B09A6C024188AF5AAC8E94C64B4B97D68A92ABF7F902B34A7D91A9D99E44558 hbbs-v8-api -✓ Checksum matches -``` - -## Troubleshooting - -### Checksum Mismatch - -If checksums don't match: - -1. **Re-download the binary** - May be corrupted during transfer -2. **Check file size** - Should match sizes listed above -3. **Verify platform** - Don't mix Linux/Windows binaries -4. **Check Git LFS** - Ensure large files downloaded correctly - -### Binary Won't Execute - -**Linux:** -```bash -chmod +x hbbs-v8-api hbbr-v8-api -./hbbs-v8-api --help -``` - -**Windows:** -```powershell -# Run as Administrator -.\hbbs-v8-api.exe --help -``` - -## Change Log - -### v1.3.0-secure (10.01.2026) - -- Changed API port from 21114 to 21120 -- Added localhost-only binding (127.0.0.1) -- Security enhancement: API not exposed to network -- Added --api-port command-line parameter -- Updated Linux binaries (10:25 UTC) -- Retained Windows binaries from previous build (compatible) - -### Previous Versions - -See [CHANGELOG.md](../../CHANGELOG.md) for full history. diff --git a/hbbs-patch/bin-with-api/hbbr-v8-api b/hbbs-patch/bin-with-api/hbbr-v8-api index e366119f..5e63b572 100644 Binary files a/hbbs-patch/bin-with-api/hbbr-v8-api and b/hbbs-patch/bin-with-api/hbbr-v8-api differ diff --git a/hbbs-patch/bin-with-api/hbbr-v8-api.exe b/hbbs-patch/bin-with-api/hbbr-v8-api.exe index 508130b6..7660ec65 100644 Binary files a/hbbs-patch/bin-with-api/hbbr-v8-api.exe and b/hbbs-patch/bin-with-api/hbbr-v8-api.exe differ diff --git a/hbbs-patch/bin-with-api/hbbs-v8-api b/hbbs-patch/bin-with-api/hbbs-v8-api index a2438509..ff75f495 100644 Binary files a/hbbs-patch/bin-with-api/hbbs-v8-api and b/hbbs-patch/bin-with-api/hbbs-v8-api differ diff --git a/hbbs-patch/bin-with-api/hbbs-v8-api.exe b/hbbs-patch/bin-with-api/hbbs-v8-api.exe index 19326436..d891f3d9 100644 Binary files a/hbbs-patch/bin-with-api/hbbs-v8-api.exe and b/hbbs-patch/bin-with-api/hbbs-v8-api.exe differ diff --git a/hbbs-patch/bin/CHECKSUMS.md b/hbbs-patch/bin/CHECKSUMS.md deleted file mode 100644 index d9d9c080..00000000 --- a/hbbs-patch/bin/CHECKSUMS.md +++ /dev/null @@ -1,81 +0,0 @@ -# SHA256 Checksums for BetterDesk Console v8 Binaries - -Generated: 2026-01-06 - -## Binaries - -### hbbs-v8 (Signal Server) -``` -SHA256: 402964335B0AA4B57E37FA52E55C41F386FE5C13487F9CA9319D6A03420A56AA -Size: 9,501,528 bytes (9.5 MB) -File: hbbs-v8 -``` - -### hbbr-v8 (Relay Server) -``` -SHA256: 9C9CB8F1BF1C5800A7419592A24BA9A00ECBB075D903D2DB09B3F5F936DB3A71 -Size: 4,961,976 bytes (5.0 MB) -File: hbbr-v8 -``` - -## Verification - -### Linux/macOS -```bash -sha256sum hbbs-v8 hbbr-v8 -``` - -### Windows (PowerShell) -```powershell -Get-FileHash hbbs-v8, hbbr-v8 -Algorithm SHA256 | Format-Table -``` - -### Expected Output -``` -402964335b0aa4b57e37fa52e55c41f386fe5c13487f9ca9319d6a03420a56aa hbbs-v8 -9c9cb8f1bf1c5800a7419592a24ba9a00ecbb075d903d2db09b3f5f936db3a71 hbbr-v8 -``` - -## Build Information - -- **Base Version**: RustDesk Server v1.1.14 -- **Build Date**: 2026-01-06 -- **Architecture**: x86_64-unknown-linux-gnu -- **Compiler**: rustc 1.75+ (stable channel) -- **Patches Applied**: 8 (see build.sh) -- **Build Script**: ../build.sh - -## Security Notes - -1. **Authenticity**: These binaries were compiled from open source code -2. **Reproducibility**: You can rebuild using `../build.sh` and verify patches -3. **Source Verification**: All patches documented in source files -4. **No Obfuscation**: Standard Rust compilation, no custom modifications -5. **Audit Available**: See ../SECURITY_AUDIT.md for security review - -## Re-compilation - -If you want to verify the binaries or compile for different architecture: - -```bash -cd .. -./build.sh -``` - -This will: -1. Clone RustDesk Server v1.1.14 -2. Apply all 8 patches automatically -3. Compile HBBS and HBBR -4. Generate new binaries in build directory - -Compare checksums of your compiled binaries with these to verify integrity. - -## Version History - -- **v8** (2026-01-06): Bidirectional ban enforcement -- **v7** (2026-01-06): IP-based source tracking (not released) -- **v6** (2026-01-05): HBBR ban enforcement (not released) -- **v5** (2026-01-05): Relay server compilation (not released) -- **v2-v4**: Development versions (not released) - -Only v8 binaries are included in this repository for production use. diff --git a/hbbs-patch/bin/README.md b/hbbs-patch/bin/README.md deleted file mode 100644 index 355b1f6b..00000000 --- a/hbbs-patch/bin/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# Precompiled Binaries - -This directory contains precompiled RustDesk server binaries with enhanced ban enforcement. - -## Files - -- **hbbs-v8** - Signal server (HBBS) with bidirectional ban enforcement -- **hbbr-v8** - Relay server (HBBR) with bidirectional ban enforcement - -## Features (v8) - -### Bidirectional Ban Enforcement - -The v8 binaries include comprehensive ban checking: - -1. **Source Device Ban Check** - - Prevents banned devices from initiating any connections - - Blocks at punch hole request stage (P2P) - - Blocks at relay request stage (relay connections) - -2. **Target Device Ban Check** - - Prevents connections to banned devices - - Protects banned devices from receiving unwanted connection attempts - -3. **Real-time Database Sync** - - Ban status checked against SQLite database - - Instant enforcement when devices are banned via web console - - No server restart required - -### Technical Details - -- **Base Version**: RustDesk Server v1.1.14 -- **Compiled**: January 2026 -- **Architecture**: x86_64 Linux -- **Dependencies**: rusqlite (for ban database access) -- **Build Script**: [build.sh](../build.sh) - -## Patches Applied - -These binaries include the following patches: - -1. **Cargo.toml**: Add rusqlite dependency -2. **database.rs**: `is_device_banned()` async function -3. **peer.rs**: - - `update_pk()` - ban check at registration - - `find_by_addr()` - map socket address to device ID -4. **rendezvous_server.rs**: `handle_punch_hole_request()` - dual ban check -5. **relay_server.rs**: `handle_relay_request()` - dual ban check - -## Installation - -These binaries are automatically used by [install.sh](../../install.sh): - -```bash -sudo ./install.sh -``` - -The installer will: -1. Create backup of existing binaries -2. Copy hbbs-v8 and hbbr-v8 to /opt/rustdesk/ -3. Set correct permissions -4. Restart services - -## Manual Installation - -If you prefer manual installation: - -```bash -# Backup existing binaries -sudo cp /opt/rustdesk/hbbs /opt/rustdesk/hbbs.backup -sudo cp /opt/rustdesk/hbbr /opt/rustdesk/hbbr.backup - -# Install new binaries -sudo cp hbbs-v8 /opt/rustdesk/hbbs -sudo cp hbbr-v8 /opt/rustdesk/hbbr -sudo chmod +x /opt/rustdesk/hbbs /opt/rustdesk/hbbr - -# Restart services -sudo systemctl restart rustdesksignal.service -sudo systemctl restart rustdeskrelay.service -``` - -## Verification - -After installation, verify ban enforcement: - -```bash -# Check logs for ban enforcement messages -sudo tail -f /var/log/rustdesk/signalserver.log - -# Ban a device via web console: http://YOUR_SERVER_IP:5000 - -# Look for these log messages: -# - "WARN Blocked loading banned device [ID] from database" -# - "Punch hole REJECTED - initiator [ID] is banned" -# - "Punch hole REJECTED - target [ID] is banned" -# - "Relay REJECTED - initiator [ID] is banned" -# - "Relay REJECTED - target [ID] is banned" -``` - -## Rebuild from Source - -If you need to rebuild these binaries: - -```bash -cd ../ -./build.sh -``` - -The build script will: -1. Clone RustDesk Server v1.1.14 -2. Apply all patches automatically -3. Compile HBBS and HBBR -4. Create installation package - -Build time: ~15-20 minutes on modern hardware - -## Security Notes - -- These binaries are compiled from audited source code -- All patches are documented in [BAN_ENFORCEMENT.md](../BAN_ENFORCEMENT.md) -- Security audit available: [SECURITY_AUDIT.md](../SECURITY_AUDIT.md) -- No network calls except RustDesk protocol -- Database access is read-only for ban checks - -## Compatibility - -- **OS**: Linux x86_64 (tested on Ubuntu 20.04+, Debian 11+) -- **RustDesk Client**: All versions compatible with v1.1.14 server -- **Database**: SQLite 3 (db_v2.sqlite3 with ban columns) - -## Support - -For issues or questions: -- Check [BAN_ENFORCEMENT.md](../BAN_ENFORCEMENT.md) for troubleshooting -- Review [SECURITY_AUDIT.md](../SECURITY_AUDIT.md) for security concerns -- See [QUICKSTART.md](../QUICKSTART.md) for quick setup guide - -## License - -Same as RustDesk Server: AGPLv3 diff --git a/hbbs-patch/bin/hbbr-v8 b/hbbs-patch/bin/hbbr-v8 deleted file mode 100644 index ab97afc8..00000000 Binary files a/hbbs-patch/bin/hbbr-v8 and /dev/null differ diff --git a/hbbs-patch/bin/hbbs-v8 b/hbbs-patch/bin/hbbs-v8 deleted file mode 100644 index 2b24b449..00000000 Binary files a/hbbs-patch/bin/hbbs-v8 and /dev/null differ diff --git a/hbbs-patch/build-windows-local.ps1 b/hbbs-patch/build-windows-local.ps1 deleted file mode 100644 index ff55f860..00000000 --- a/hbbs-patch/build-windows-local.ps1 +++ /dev/null @@ -1,328 +0,0 @@ -# RustDesk HBBS/HBBR Windows Build Script (Local) -# -# This script builds RustDesk HBBS and HBBR for Windows locally -# with HTTP API and ban enforcement features. -# -# Requirements: -# - Windows 10/11 -# - Rust toolchain (cargo, rustc) -# - Git -# -# Usage: -# .\build-windows-local.ps1 -# -# Output: -# - hbbs-ban-check-package/hbbs.exe -# - hbbs-ban-check-package/hbbr.exe -# - bin-with-api/hbbs-v8-api.exe -# - bin-with-api/hbbr-v8-api.exe - -param( - [Parameter(Mandatory=$false)] - [switch]$SkipClone = $false, - - [Parameter(Mandatory=$false)] - [switch]$Clean = $false -) - -$ErrorActionPreference = "Stop" - -# Configuration -$RUSTDESK_VERSION = "1.1.14" -$GITHUB_REPO = "https://github.com/rustdesk/rustdesk-server.git" -$OUTPUT_DIR = "hbbs-ban-check-package" -$BIN_API_DIR = "bin-with-api" - -function Write-Header { - param([string]$Message) - Write-Host "`n========================================" -ForegroundColor Cyan - Write-Host $Message -ForegroundColor Cyan - Write-Host "========================================`n" -ForegroundColor Cyan -} - -function Write-Step { - param([string]$Step, [string]$Message) - Write-Host "[$Step] " -NoNewline -ForegroundColor Blue - Write-Host $Message -ForegroundColor White -} - -function Write-Success { - param([string]$Message) - Write-Host "? $Message" -ForegroundColor Green -} - -function Write-Error { - param([string]$Message) - Write-Host "? $Message" -ForegroundColor Red -} - -function Write-Warning { - param([string]$Message) - Write-Host "? $Message" -ForegroundColor Yellow -} - -Write-Header "RustDesk Windows Build Script (Local)" - -# Step 1: Check Rust installation -Write-Step "1/9" "Checking Rust installation..." -try { - $cargoVersion = cargo --version - $rustcVersion = rustc --version - Write-Host " Cargo: $cargoVersion" - Write-Host " Rustc: $rustcVersion" - Write-Success "Rust toolchain ready" -} -catch { - Write-Error "Rust not found. Please install from https://rustup.rs/" - exit 1 -} - -# Step 2: Check Git -Write-Step "2/9" "Checking Git..." -try { - $gitVersion = git --version - Write-Host " $gitVersion" - Write-Success "Git ready" -} -catch { - Write-Error "Git not found. Please install Git for Windows" - exit 1 -} - -# Step 3: Clone or use existing source -Write-Step "3/9" "Preparing RustDesk source..." - -$sourceDir = "rustdesk-server-$RUSTDESK_VERSION" - -if ($Clean -and (Test-Path $sourceDir)) { - Write-Host " Cleaning old source directory..." - Remove-Item -Path $sourceDir -Recurse -Force -} - -if (-not (Test-Path $sourceDir) -and -not $SkipClone) { - Write-Host " Downloading RustDesk Server v$RUSTDESK_VERSION..." - - $archiveFile = "rustdesk-server-$RUSTDESK_VERSION.zip" - if (-not (Test-Path $archiveFile)) { - $downloadUrl = "https://github.com/rustdesk/rustdesk-server/archive/refs/tags/$RUSTDESK_VERSION.zip" - Write-Host " Downloading from GitHub..." - Invoke-WebRequest -Uri $downloadUrl -OutFile $archiveFile -UseBasicParsing - Write-Success "Download complete" - } - - Write-Host " Extracting archive..." - Expand-Archive -Path $archiveFile -DestinationPath . -Force - Write-Success "Source extracted" -} -elseif (Test-Path $sourceDir) { - Write-Success "Source directory exists" -} -else { - Write-Error "Source directory not found and -SkipClone specified" - exit 1 -} - -# Step 4: Initialize submodules -Write-Step "4/9" "Initializing git submodules..." -Push-Location $sourceDir - -if (Test-Path ".git") { - Write-Host " Updating submodules..." - git submodule update --init --recursive 2>$null -} - -# Check if hbb_common is present -if (-not (Test-Path "libs\hbb_common\Cargo.toml")) { - Write-Warning "hbb_common not found, cloning directly..." - - if (Test-Path "libs\hbb_common") { - Remove-Item -Path "libs\hbb_common" -Recurse -Force - } - - Write-Host " Cloning hbb_common..." - git clone --depth 1 https://github.com/rustdesk/hbb_common.git libs\hbb_common - - if (Test-Path "libs\hbb_common\Cargo.toml") { - Write-Success "hbb_common ready" - } - else { - Write-Error "Failed to get hbb_common" - Pop-Location - exit 1 - } -} -else { - Write-Success "hbb_common present" -} - -# Step 5: Copy custom source files -Write-Step "5/9" "Applying custom modifications..." - -$srcDir = "..\src" - -if (Test-Path "$srcDir\http_api.rs") { - Write-Host " Copying http_api.rs..." - Copy-Item "$srcDir\http_api.rs" "src\http_api.rs" -Force -} -else { - Write-Error "http_api.rs not found in $srcDir" - Pop-Location - exit 1 -} - -if (Test-Path "$srcDir\main.rs") { - Write-Host " Copying main.rs..." - Copy-Item "$srcDir\main.rs" "src\main.rs" -Force -} - -if (Test-Path "$srcDir\peer.rs") { - Write-Host " Copying peer.rs..." - Copy-Item "$srcDir\peer.rs" "src\peer.rs" -Force -} - -if (Test-Path "$srcDir\rendezvous_server.rs") { - Write-Host " Copying rendezvous_server.rs..." - Copy-Item "$srcDir\rendezvous_server.rs" "src\rendezvous_server.rs" -Force -} - -Write-Success "Custom files applied" - -# Step 6: Patch lib.rs -Write-Step "6/9" "Patching lib.rs..." - -$librsContent = Get-Content "src\lib.rs" -Raw - -if ($librsContent -notmatch "pub mod http_api;") { - Write-Host " Adding http_api module to lib.rs..." - - # Find the last 'pub mod' line and add after it - $lines = Get-Content "src\lib.rs" - $newLines = @() - $added = $false - - for ($i = 0; $i -lt $lines.Count; $i++) { - $newLines += $lines[$i] - - if (-not $added -and $lines[$i] -match "^pub mod " -and ($i + 1 -lt $lines.Count) -and $lines[$i + 1] -notmatch "^pub mod ") { - $newLines += "pub mod http_api;" - $added = $true - } - } - - $newLines | Set-Content "src\lib.rs" - Write-Success "lib.rs patched" -} -else { - Write-Success "lib.rs already patched" -} - -# Step 7: Update Cargo.toml -Write-Step "7/9" "Updating Cargo.toml dependencies..." - -$cargoContent = Get-Content "Cargo.toml" -Raw - -$modified = $false - -if ($cargoContent -notmatch 'axum = ') { - Write-Host " Adding axum dependency..." - $cargoContent = $cargoContent -replace '(\[dependencies\])', "`$1`naxum = `"0.5`"" - $modified = $true -} - -if ($cargoContent -notmatch 'sqlx = ') { - Write-Host " Adding sqlx dependency..." - $cargoContent = $cargoContent -replace '(\[dependencies\])', "`$1`nsqlx = { version = `"0.6`", features = [`"sqlite`", `"runtime-tokio-native-tls`"] }" - $modified = $true -} - -if ($modified) { - $cargoContent | Set-Content "Cargo.toml" - Write-Success "Dependencies updated" -} -else { - Write-Success "Dependencies already present" -} - -# Step 8: Build -Write-Step "8/9" "Building for Windows (this may take several minutes)..." -Write-Host "" - -$env:RUSTFLAGS = "-C target-feature=+crt-static" - -cargo build --release 2>&1 | ForEach-Object { - if ($_ -match "Compiling|Finished|error|warning") { - Write-Host $_ - } -} - -if ($LASTEXITCODE -ne 0) { - Write-Host "" - Write-Error "Build failed with exit code $LASTEXITCODE" - Pop-Location - exit 1 -} - -Write-Host "" -Write-Success "Build complete!" - -# Step 9: Package binaries -Write-Step "9/9" "Packaging binaries..." - -Pop-Location # Back to hbbs-patch directory - -# Create output directories -if (-not (Test-Path $OUTPUT_DIR)) { - New-Item -ItemType Directory -Path $OUTPUT_DIR | Out-Null -} - -if (-not (Test-Path $BIN_API_DIR)) { - New-Item -ItemType Directory -Path $BIN_API_DIR | Out-Null -} - -# Copy executables -$hbbsPath = "$sourceDir\target\release\hbbs.exe" -$hbbrPath = "$sourceDir\target\release\hbbr.exe" - -if (Test-Path $hbbsPath) { - Copy-Item $hbbsPath "$OUTPUT_DIR\hbbs.exe" -Force - Copy-Item $hbbsPath "$BIN_API_DIR\hbbs-v8-api.exe" -Force - - $hbbsSize = (Get-Item $hbbsPath).Length - Write-Host " ? hbbs.exe ($([math]::Round($hbbsSize / 1MB, 2)) MB)" -} -else { - Write-Error "hbbs.exe not found" -} - -if (Test-Path $hbbrPath) { - Copy-Item $hbbrPath "$OUTPUT_DIR\hbbr.exe" -Force - Copy-Item $hbbrPath "$BIN_API_DIR\hbbr-v8-api.exe" -Force - - $hbbrSize = (Get-Item $hbbrPath).Length - Write-Host " ? hbbr.exe ($([math]::Round($hbbrSize / 1MB, 2)) MB)" -} -else { - Write-Error "hbbr.exe not found" -} - -Write-Success "Binaries packaged" - -# Summary -Write-Header "Build Complete!" - -Write-Host "Windows binaries created:" -ForegroundColor Green -Write-Host " ? $OUTPUT_DIR\hbbs.exe" -Write-Host " ? $OUTPUT_DIR\hbbr.exe" -Write-Host "" -Write-Host "Also copied to installer directory:" -ForegroundColor Green -Write-Host " ? $BIN_API_DIR\hbbs-v8-api.exe" -Write-Host " ? $BIN_API_DIR\hbbr-v8-api.exe" -Write-Host "" -Write-Host "Features included:" -ForegroundColor Cyan -Write-Host " ? HTTP API on port 21114" -Write-Host " ? Real-time device status" -Write-Host " ? Bidirectional ban enforcement" -Write-Host " ? 20-second timeout synchronization" -Write-Host "" -Write-Host "Ready to use with install-improved.ps1!" -ForegroundColor Yellow -Write-Host "" diff --git a/hbbs-patch/build-windows.sh b/hbbs-patch/build-windows.sh deleted file mode 100644 index 314e677f..00000000 --- a/hbbs-patch/build-windows.sh +++ /dev/null @@ -1,287 +0,0 @@ -#!/bin/bash - -############################################################################# -# RustDesk HBBS/HBBR Windows Cross-Compilation Build Script -# -# This script cross-compiles RustDesk HBBS and HBBR for Windows (x86_64) -# with HTTP API and ban enforcement features. -# -# Requirements: -# - Linux environment -# - Rust toolchain with Windows target -# - MinGW-w64 cross-compiler -# -# Usage: -# bash build-windows.sh -# -# Output: -# - hbbs-ban-check-package/hbbs.exe (Windows) -# - hbbs-ban-check-package/hbbr.exe (Windows) -############################################################################# - -set -e # Exit on error - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -# Configuration -RUSTDESK_VERSION="1.1.14" -GITHUB_REPO="https://github.com/rustdesk/rustdesk-server.git" -WINDOWS_TARGET="x86_64-pc-windows-gnu" -OUTPUT_DIR="hbbs-ban-check-package" - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}RustDesk Windows Build Script${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# Add cargo to PATH if not already there -if [ -d "$HOME/.cargo/bin" ] && [[ ":$PATH:" != *":$HOME/.cargo/bin:"* ]]; then - export PATH="$HOME/.cargo/bin:$PATH" - echo "Added ~/.cargo/bin to PATH" -fi - -# Check if we're on Linux -if [[ "$OSTYPE" != "linux-gnu"* ]]; then - echo -e "${RED}✗ This script must be run on Linux${NC}" - exit 1 -fi - -# Step 1: Check for Rust and add Windows target -echo -e "${BLUE}[1/8] Checking Rust installation and Windows target...${NC}" - -# Check if cargo is available -if ! command -v cargo &> /dev/null; then - echo -e "${RED}✗ Cargo not found. Please install Rust.${NC}" - exit 1 -fi - -echo "Rust version: $(cargo --version)" - -# Try to add Windows target (works with both rustup and standalone) -if command -v rustup &> /dev/null; then - echo "Using rustup to add Windows target..." - rustup target add $WINDOWS_TARGET 2>/dev/null || true - echo -e "${GREEN}✓ Windows target configured via rustup${NC}" -else - echo "Rustup not found - attempting standalone Rust cross-compilation..." - echo -e "${YELLOW}⚠ Note: Without rustup, cross-compilation may require additional setup${NC}" -fi - -# Step 2: Check for MinGW cross-compiler -echo -e "${BLUE}[2/8] Checking for MinGW-w64...${NC}" -if ! command -v x86_64-w64-mingw32-gcc &> /dev/null; then - echo -e "${YELLOW}⚠ MinGW-w64 not found, attempting to install...${NC}" - - if command -v apt-get &> /dev/null; then - sudo apt-get update - sudo apt-get install -y mingw-w64 - elif command -v yum &> /dev/null; then - sudo yum install -y mingw64-gcc mingw64-gcc-c++ - else - echo -e "${RED}✗ Please install MinGW-w64 manually${NC}" - exit 1 - fi -fi - -if command -v x86_64-w64-mingw32-gcc &> /dev/null; then - echo -e "${GREEN}✓ MinGW-w64 found: $(x86_64-w64-mingw32-gcc --version | head -n1)${NC}" -else - echo -e "${RED}✗ MinGW-w64 installation failed${NC}" - exit 1 -fi - -# Step 3: Clone or use existing rustdesk-server -echo -e "${BLUE}[3/8] Preparing RustDesk source...${NC}" -if [ ! -d "rustdesk-server-$RUSTDESK_VERSION" ]; then - if [ ! -f "rustdesk-server-$RUSTDESK_VERSION.tar.gz" ]; then - echo "Downloading RustDesk Server v$RUSTDESK_VERSION..." - wget "https://github.com/rustdesk/rustdesk-server/archive/refs/tags/$RUSTDESK_VERSION.tar.gz" \ - -O "rustdesk-server-$RUSTDESK_VERSION.tar.gz" - fi - - echo "Extracting archive..." - tar -xzf "rustdesk-server-$RUSTDESK_VERSION.tar.gz" -fi - -cd "rustdesk-server-$RUSTDESK_VERSION" - -# Initialize git submodules (required for hbb_common) -echo "Initializing git submodules..." -if [ -d .git ]; then - git submodule update --init --recursive 2>/dev/null || echo "Git submodules update failed (expected if not git clone)" -fi - -# Check if libs/hbb_common is empty and clone if needed -if [ ! -f "libs/hbb_common/Cargo.toml" ]; then - echo "hbb_common not found, cloning directly..." - rm -rf libs/hbb_common - mkdir -p libs/hbb_common - - # Try cloning the actual dependency (rustdesk-hbb_common) - if ! git clone --depth 1 https://github.com/rustdesk-org/rustdesk-hbb_common.git libs/hbb_common 2>/dev/null; then - echo -e "${YELLOW}⚠ Using tarball instead of git clone${NC}" - - # Download as tarball instead - wget -q https://github.com/rustdesk-org/rustdesk-hbb_common/archive/refs/heads/master.tar.gz -O /tmp/hbb_common.tar.gz - tar -xzf /tmp/hbb_common.tar.gz -C libs/ - mv libs/rustdesk-hbb_common-master libs/hbb_common - rm /tmp/hbb_common.tar.gz - fi - - if [ -f "libs/hbb_common/Cargo.toml" ]; then - echo -e "${GREEN}✓ hbb_common ready${NC}" - else - echo -e "${RED}✗ Failed to get hbb_common${NC}" - exit 1 - fi -else - echo -e "${GREEN}✓ hbb_common already present${NC}" -fi - -echo -e "${GREEN}✓ Source ready${NC}" - -# Step 4: Copy custom source files -echo -e "${BLUE}[4/8] Applying custom modifications...${NC}" - -# Copy HTTP API module -if [ -f "../src/http_api.rs" ]; then - echo "Copying http_api.rs..." - cp "../src/http_api.rs" "src/http_api.rs" -else - echo -e "${RED}✗ http_api.rs not found${NC}" - exit 1 -fi - -# Copy modified main.rs -if [ -f "../src/main.rs" ]; then - echo "Copying main.rs..." - cp "../src/main.rs" "src/main.rs" -else - echo -e "${RED}✗ main.rs not found${NC}" - exit 1 -fi - -# Copy modified peer.rs -if [ -f "../src/peer.rs" ]; then - echo "Copying peer.rs..." - cp "../src/peer.rs" "src/peer.rs" -fi - -# Copy modified rendezvous_server.rs -if [ -f "../src/rendezvous_server.rs" ]; then - echo "Copying rendezvous_server.rs..." - cp "../src/rendezvous_server.rs" "src/rendezvous_server.rs" -fi - -echo -e "${GREEN}✓ Custom files copied${NC}" - -# Step 5: Patch lib.rs to include HTTP API module -echo -e "${BLUE}[5/8] Patching lib.rs...${NC}" -if ! grep -q "pub mod http_api;" src/lib.rs; then - # Add after the last 'pub mod' declaration - sed -i '/^pub mod /a pub mod http_api;' src/lib.rs - echo -e "${GREEN}✓ lib.rs patched${NC}" -else - echo -e "${GREEN}✓ lib.rs already patched${NC}" -fi - -# Step 6: Update Cargo.toml dependencies -echo -e "${BLUE}[6/8] Updating Cargo.toml...${NC}" - -# Check if dependencies already exist -if ! grep -q "axum = " Cargo.toml; then - echo "Adding axum dependency..." - sed -i '/\[dependencies\]/a axum = "0.5"' Cargo.toml -fi - -if ! grep -q "sqlx = " Cargo.toml; then - echo "Adding sqlx dependency..." - sed -i '/\[dependencies\]/a sqlx = { version = "0.6", features = ["sqlite", "runtime-tokio-native-tls"] }' Cargo.toml -fi - -echo -e "${GREEN}✓ Dependencies updated${NC}" - -# Step 7: Configure Cargo for Windows cross-compilation -echo -e "${BLUE}[7/8] Configuring Cargo for Windows...${NC}" -mkdir -p .cargo - -cat > .cargo/config.toml << 'EOF' -[target.x86_64-pc-windows-gnu] -linker = "x86_64-w64-mingw32-gcc" -ar = "x86_64-w64-mingw32-ar" - -[build] -rustflags = ["-C", "target-feature=+crt-static"] -EOF - -echo -e "${GREEN}✓ Cargo configured${NC}" - -# Step 8: Build for Windows -echo -e "${BLUE}[8/8] Building for Windows (this may take several minutes)...${NC}" -echo "" - -# Set environment variables for cross-compilation -export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc -export CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++ -export AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar -export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc - -# Build with release profile -cargo build --release --target $WINDOWS_TARGET - -if [ $? -eq 0 ]; then - echo "" - echo -e "${GREEN}✓ Build successful!${NC}" -else - echo "" - echo -e "${RED}✗ Build failed${NC}" - exit 1 -fi - -# Step 9: Package binaries -echo -e "${BLUE}Packaging binaries...${NC}" -cd .. - -# Create output directory -mkdir -p "$OUTPUT_DIR" - -# Copy Windows executables -cp "rustdesk-server-$RUSTDESK_VERSION/target/$WINDOWS_TARGET/release/hbbs.exe" "$OUTPUT_DIR/" -cp "rustdesk-server-$RUSTDESK_VERSION/target/$WINDOWS_TARGET/release/hbbr.exe" "$OUTPUT_DIR/" - -# Copy to bin-with-api for installer -mkdir -p bin-with-api -cp "$OUTPUT_DIR/hbbs.exe" "bin-with-api/hbbs-v8-api.exe" -cp "$OUTPUT_DIR/hbbr.exe" "bin-with-api/hbbr-v8-api.exe" - -# Calculate sizes -hbbs_size=$(stat -f%z "$OUTPUT_DIR/hbbs.exe" 2>/dev/null || stat -c%s "$OUTPUT_DIR/hbbs.exe" 2>/dev/null) -hbbr_size=$(stat -f%z "$OUTPUT_DIR/hbbr.exe" 2>/dev/null || stat -c%s "$OUTPUT_DIR/hbbr.exe" 2>/dev/null) - -echo "" -echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN}Build Complete!${NC}" -echo -e "${GREEN}========================================${NC}" -echo "" -echo "Windows binaries created:" -echo " • $OUTPUT_DIR/hbbs.exe ($(numfmt --to=iec $hbbs_size 2>/dev/null || echo "$hbbs_size bytes"))" -echo " • $OUTPUT_DIR/hbbr.exe ($(numfmt --to=iec $hbbr_size 2>/dev/null || echo "$hbbr_size bytes"))" -echo "" -echo "Also copied to bin-with-api/ for installer:" -echo " • bin-with-api/hbbs-v8-api.exe" -echo " • bin-with-api/hbbr-v8-api.exe" -echo "" -echo -e "${BLUE}Features included:${NC}" -echo " ✓ HTTP API on port 21114" -echo " ✓ Real-time device status" -echo " ✓ Bidirectional ban enforcement" -echo " ✓ 20-second timeout synchronization" -echo "" -echo -e "${YELLOW}Note: Windows binaries compiled on Linux using MinGW${NC}" -echo -e "${YELLOW}Test them on a Windows system to verify functionality${NC}" -echo "" diff --git a/hbbs-patch/build.sh b/hbbs-patch/build.sh deleted file mode 100644 index 796886ab..00000000 --- a/hbbs-patch/build.sh +++ /dev/null @@ -1,501 +0,0 @@ -#!/bin/bash -# HBBS Ban Check - Automatic Patch and Build Script -# -# This script: -# 1. Clones RustDesk Server v1.1.14 -# 2. Applies ban check patches -# 3. Compiles modified hbbs -# 4. Creates installation package - -set -e - -# Source cargo environment if exists -[ -f "$HOME/.cargo/env" ] && source "$HOME/.cargo/env" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}HBBS Ban Check - Build Script${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# Check requirements -echo -e "${YELLOW}[1/8] Checking requirements...${NC}" - -if ! command -v git &> /dev/null; then - echo -e "${RED}✗ Git not found${NC}" - exit 1 -fi -echo -e "${GREEN}✓ Git${NC}" - -if ! command -v cargo &> /dev/null; then - echo -e "${RED}✗ Rust/Cargo not installed${NC}" - echo "Install from: https://rustup.rs/" - exit 1 -fi -echo -e "${GREEN}✓ Rust $(cargo --version)${NC}" - -# Clone repository -echo "" -echo -e "${YELLOW}[2/8] Cloning RustDesk Server...${NC}" - -if [ -d "rustdesk-server" ]; then - echo "Removing existing directory..." - rm -rf rustdesk-server -fi - -git clone --depth 1 --branch 1.1.14 --recurse-submodules https://github.com/rustdesk/rustdesk-server.git -cd rustdesk-server - -echo -e "${GREEN}✓ Cloned v1.1.14${NC}" - -# Copy HTTP API and main.rs files -echo "" -echo -e "${YELLOW}[2.5/8] Copying HTTP API and main.rs files...${NC}" - -# Copy the HTTP API module -cp ../src/http_api.rs src/http_api.rs -# Copy the updated main.rs with API support -cp ../src/main.rs src/main.rs - -# Add http_api module to lib.rs -if ! grep -q "pub mod http_api;" src/lib.rs; then - echo "pub mod http_api;" >> src/lib.rs -fi - -echo -e "${GREEN}✓ HTTP API and main.rs copied${NC}" - -# Add rusqlite dependency -echo "" -echo -e "${YELLOW}[3/8] Adding rusqlite dependency...${NC}" - -# Add rusqlite to Cargo.toml dependencies section -# Using version 0.27 to match libsqlite3-sys 0.24 (same as used by SQLx) -sed -i '/^\[dependencies\]/a rusqlite = { version = "0.27", features = ["bundled"] }' Cargo.toml - -echo -e "${GREEN}✓ rusqlite added to Cargo.toml${NC}" - -# Add HTTP API dependencies -echo "" -echo -e "${YELLOW}[3.5/8] Adding HTTP API dependencies...${NC}" - -# Note: serde and tokio are already in dependencies, we just need axum and sqlx -# Check if they already exist to avoid duplicates -if ! grep -q "^axum =" Cargo.toml; then - sed -i '/^\[dependencies\]/a axum = { version = "0.5", features = ["headers"] }' Cargo.toml -fi -if ! grep -q "^sqlx =" Cargo.toml; then - sed -i '/^\[dependencies\]/a sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite"] }' Cargo.toml -fi - -echo -e "${GREEN}✓ HTTP API dependencies added${NC}" - -# Apply database.rs patch -echo "" -echo -e "${YELLOW}[4/8] Applying database.rs patch...${NC}" - -# Insert is_device_banned method using synchronous rusqlite to avoid nested runtime panic -# Note: hbb_common re-exports both anyhow and tokio, so we use full paths -sed -i '/pub async fn update_pk/,/^ }$/{ -/^ }$/ a\ -\ - /// Check if a device is banned in the database\ - /// Returns true if device has is_banned=1, false otherwise\ - /// Uses synchronous rusqlite to avoid nested Tokio runtime panic\ - pub async fn is_device_banned(\&self, id: \&str) -> ResultType {\ - let db_path = "./db_v2.sqlite3";\ - let id = id.to_string();\ - \ - // Execute in blocking thread pool to avoid runtime conflict\ - let result = hbb_common::tokio::task::spawn_blocking(move || -> ResultType {\ - use rusqlite::{Connection, OptionalExtension};\ - \ - // Use READ_WRITE to support WAL mode (no actual writes performed)\ - let conn = Connection::open_with_flags(\ - db_path,\ - rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE\ - )?;\ - \ - let mut stmt = conn.prepare(\ - "SELECT is_banned FROM peer WHERE id = ? AND is_deleted = 0"\ - )?;\ - \ - let result: Option = stmt\ - .query_row([&id], |row| row.get(0))\ - .optional()?;\ - \ - Ok(result.map(|banned| banned == 1).unwrap_or(false))\ - })\ - .await\ - .map_err(|e| hbb_common::anyhow::anyhow!("Spawn blocking failed: {}", e))??;\ -\ - Ok(result)\ - } -}' src/database.rs - -echo -e "${GREEN}✓ database.rs patched${NC}" - -# Apply peer.rs patch -echo "" -echo -e "${YELLOW}[5/8] Applying peer.rs patch...${NC}" - -# Patch 1: Ban check in update_pk - reject and remove from memory -sed -i '/log::info!("update_pk/a\ - \ - // BAN CHECK: Verify device is not banned before registration\ - match self.db.is_device_banned(\&id).await {\ - Ok(true) => {\ - log::warn!("Registration REJECTED for device {}: DEVICE IS BANNED", id);\ - // Remove from memory to prevent cached access\ - self.map.write().await.remove(\&id);\ - return register_pk_response::Result::UUID_MISMATCH;\ - }\ - Ok(false) => {\ - log::debug!("Ban check passed for device {}", id);\ - }\ - Err(e) => {\ - log::error!("Failed to check ban status for device {}: {}. Allowing (fail-open)", id, e);\ - }\ - }' src/peer.rs - -# Patch 2: Ban check in get() - prevent loading banned devices from DB -sed -i '/let peer = Peer {/i\ - // BAN CHECK: Do not load banned devices into memory\ - if let Ok(true) = self.db.is_device_banned(id).await {\ - log::warn!("Blocked loading banned device {} from database", id);\ - return None;\ - }' src/peer.rs - -# Patch 3: Add method to find device ID by socket address -# Add AFTER the closing brace of is_in_memory() method -sed -i '/self\.map\.read()\.await\.contains_key(id)/,/^ }$/{ -/^ }$/a\ -\ - /// Find device ID by socket address (for ban enforcement)\ - /// Returns the ID of the peer with matching socket address, or None\ - pub(crate) async fn get_id_by_addr(\&self, addr: SocketAddr) -> Option {\ - let map = self.map.read().await;\ - for (id, peer) in map.iter() {\ - let peer_addr = peer.read().await.socket_addr;\ - if peer_addr == addr {\ - return Some(id.clone());\ - }\ - }\ - None\ - } -}' src/peer.rs - -echo -e "${GREEN}✓ peer.rs patched${NC}" - -# Apply rendezvous_server.rs patch - block banned devices from relay and P2P connections -echo "" -echo -e "${YELLOW}[6/8] Applying rendezvous_server.rs patches...${NC}" - -# Patch 1: Block RequestRelay for banned devices (target only - more reliable) -sed -i '/Some(rendezvous_message::Union::RequestRelay(mut rf)) => {/,/return true;/{ -/Some(rendezvous_message::Union::RequestRelay(mut rf)) => {/a\ - // BAN CHECK: Block relay if target is banned\ - match self.pm.db.is_device_banned(\&rf.id).await {\ - Ok(true) => {\ - log::warn!("Relay REJECTED - target {} is banned", rf.id);\ - return true;\ - }\ - Ok(false) => {},\ - Err(e) => {\ - log::error!("Ban check failed for relay target {}: {}", rf.id, e);\ - }\ - }\ - // BAN CHECK: Block relay if sender (uuid) is banned\ - if !rf.uuid.is_empty() {\ - match self.pm.db.is_device_banned(\&rf.uuid).await {\ - Ok(true) => {\ - log::warn!("Relay REJECTED - sender {} (uuid) is banned", rf.uuid);\ - return true;\ - }\ - Ok(false) => {},\ - Err(e) => {\ - log::error!("Ban check failed for sender {}: {}", rf.uuid, e);\ - }\ - }\ - } -}' src/rendezvous_server.rs - -# Patch 2: Block PunchHoleRequest for banned devices (target only - sender will be caught by message relay) -sed -i '/async fn handle_punch_hole_request(/,/let id = ph.id;/{ -/let id = ph.id;/a\ - \ - // BAN CHECK 1: Block if TARGET device is banned\ - match self.pm.db.is_device_banned(\&id).await {\ - Ok(true) => {\ - log::warn!("Punch hole REJECTED - target {} is BANNED", id);\ - let mut msg_out = RendezvousMessage::new();\ - msg_out.set_punch_hole_response(PunchHoleResponse {\ - failure: punch_hole_response::Failure::OFFLINE.into(),\ - ..Default::default()\ - });\ - return Ok((msg_out, None));\ - }\ - Ok(false) => {\ - log::debug!("Target ban check passed for {}", id);\ - }\ - Err(e) => {\ - log::error!("Failed to check target ban status for {}: {}", id, e);\ - }\ - }\ - \ - // BAN CHECK 2: Block if SOURCE device (initiating connection) is banned\ - if let Some(source_id) = self.pm.get_id_by_addr(addr).await {\ - match self.pm.db.is_device_banned(\&source_id).await {\ - Ok(true) => {\ - log::warn!("Punch hole REJECTED - source {} (from {}) is BANNED", source_id, addr);\ - let mut msg_out = RendezvousMessage::new();\ - msg_out.set_punch_hole_response(PunchHoleResponse {\ - failure: punch_hole_response::Failure::LICENSE_MISMATCH.into(),\ - ..Default::default()\ - });\ - return Ok((msg_out, None));\ - }\ - Ok(false) => {\ - log::debug!("Source ban check passed for {}", source_id);\ - }\ - Err(e) => {\ - log::error!("Failed to check source ban status for {}: {}", source_id, e);\ - }\ - }\ - } else {\ - log::debug!("Could not find source device ID for address {}", addr);\ - } -}' src/rendezvous_server.rs - -echo -e "${GREEN}✓ rendezvous_server.rs patched${NC}" - -# Patch to add API server support -echo "" -echo -e "${YELLOW}[6.4/8] Adding HTTP API server support...${NC}" - -# 1. Update start() function signature to accept api_port -sed -i 's/pub async fn start(port: i32, serial: i32, key: \&str, rmem: usize)/pub async fn start(port: i32, serial: i32, key: \&str, rmem: usize, api_port: u16)/' src/rendezvous_server.rs - -# 2. Start API server in background task (add after PeerMap::new().await?) -sed -i '/let pm = PeerMap::new().await?;/a\ - \ - // Start HTTP API server in background\ - let pm_clone = pm.clone();\ - let db_path = std::env::var("DB_URL").unwrap_or("./db_v2.sqlite3".to_string());\ - tokio::spawn(async move {\ - if let Err(e) = crate::http_api::start_api_server(db_path, api_port, Arc::new(pm_clone)).await {\ - log::error!("HTTP API server failed: {}", e);\ - }\ - });' src/rendezvous_server.rs - -echo -e "${GREEN}✓ HTTP API server support added${NC}" - -# NEW CRITICAL PATCH 3: Block RelayResponse - THE actual message relay -echo "" -echo -e "${YELLOW}[6.5/8] Adding RelayResponse ban check (CRITICAL)...${NC}" - -# This blocks the actual data being relayed between devices -sed -i '/Some(rendezvous_message::Union::RelayResponse(mut rr)) => {/a\ - // CRITICAL BAN CHECK: Block relay response if sender or target is banned\ - // This is where actual remote control data flows\ - let relay_id = rr.id();\ - if !relay_id.is_empty() {\ - // Check if relay ID (could be sender or target) is banned\ - match self.pm.db.is_device_banned(relay_id).await {\ - Ok(true) => {\ - log::warn!("RelayResponse BLOCKED - device {} is banned", relay_id);\ - return true;\ - }\ - Ok(false) => {},\ - Err(e) => {\ - log::error!("Ban check failed for relay {}: {}", relay_id, e);\ - }\ - }\ - }' src/rendezvous_server.rs - -echo -e "${GREEN}✓ RelayResponse ban check added${NC}" - -# NEW CRITICAL PATCH 4: Block HBBR relay_server.rs - actual data relay -echo "" -echo -e "${YELLOW}[6.6/8] Adding HBBR relay server ban check (MOST CRITICAL)...${NC}" - -# Add ban check to make_pair_ before pairing -# Strategy: Check if ANY device from the initiating IP is banned -sed -i '/if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union {/,/if !rf.uuid.is_empty() {/{ -/if !rf.uuid.is_empty() {/a\ - // CRITICAL BAN CHECK: Block relay if device from this IP is banned\ - // Strategy: Query database for all devices with recent activity from this IP\ - // and check if any of them is banned\ - let db_path = "./db_v2.sqlite3";\ - let client_ip = addr.ip().to_string();\ - let target_id = rf.id.clone();\ - \ - let is_banned = hbb_common::tokio::task::spawn_blocking(move || {\ - use rusqlite::{Connection, OptionalExtension};\ - \ - match Connection::open_with_flags(\ - db_path,\ - rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE\ - ) {\ - Ok(conn) => {\ - // Check if target device is banned\ - if !target_id.is_empty() {\ - if let Ok(Some(Some(banned))) = conn\ - .prepare("SELECT is_banned FROM peer WHERE id = ? AND is_deleted = 0")\ - .and_then(|mut stmt| stmt.query_row([&target_id], |row| row.get::<_, Option>(0)).optional()) {\ - if banned == 1 {\ - log::warn!("HBBR Relay BLOCKED - target device {} is BANNED", target_id);\ - return true;\ - }\ - }\ - }\ - \ - // Check if ANY device from this IP is banned\ - // This catches the initiating device even if we don'\''t have its exact ID\ - let info_pattern = format!("%{}%", client_ip);\ - match conn.prepare(\ - "SELECT id, is_banned FROM peer WHERE info LIKE ? AND is_deleted = 0 LIMIT 10"\ - ) {\ - Ok(mut stmt) => {\ - if let Ok(mut rows) = stmt.query([&info_pattern]) {\ - while let Ok(Some(row)) = rows.next() {\ - if let (Ok(id), Ok(Some(banned))) = (\ - row.get::<_, String>(0),\ - row.get::<_, Option>(1)\ - ) {\ - if banned == 1 {\ - log::warn!("HBBR Relay BLOCKED - device {} from IP {} is BANNED", id, client_ip);\ - return true;\ - }\ - }\ - }\ - }\ - }\ - Err(_) => {}\ - }\ - false\ - }\ - Err(_) => false\ - }\ - }).await;\ - \ - match is_banned {\ - Ok(true) => {\ - log::warn!("HBBR Relay REJECTED from {}", addr);\ - return;\ - }\ - Ok(false) => {\ - log::debug!("HBBR Relay allowed from {}", addr);\ - }\ - Err(e) => {\ - log::error!("HBBR ban check spawn failed: {}", e);\ - }\ - } -}' src/relay_server.rs - -echo -e "${GREEN}✓ HBBR relay ban check added${NC}" - -# Compile -echo "" -echo -e "${YELLOW}[7/8] Compiling hbbs...${NC}" -echo "This may take 5-10 minutes on first build..." - -cargo build --release --bin hbbs - -if [ ! -f "target/release/hbbs" ]; then - echo -e "${RED}✗ HBBS build failed${NC}" - exit 1 -fi - -echo -e "${GREEN}✓ HBBS compiled${NC}" - -# Compile HBBR (relay server) - CRITICAL for ban enforcement -echo "" -echo -e "${YELLOW}[8/8] Compiling hbbr (relay server)...${NC}" -echo "HBBR handles actual data relay - must have ban checks!" - -cargo build --release --bin hbbr - -if [ ! -f "target/release/hbbr" ]; then - echo -e "${RED}✗ HBBR build failed${NC}" - exit 1 -fi - -echo -e "${GREEN}✓ HBBR compiled${NC}" -echo -e "${GREEN}✓ All builds successful${NC}" - -# Create package -echo "" -echo -e "${YELLOW}[9/8] Creating installation package...${NC}" - -mkdir -p ../hbbs-ban-check-package -cp target/release/hbbs ../hbbs-ban-check-package/ -cp target/release/hbbr ../hbbs-ban-check-package/ -cp /opt/rustdesk/id_ed25519.pub ../hbbs-ban-check-package/ 2>/dev/null || echo "Note: No existing public key" - -# Create install script -cat > ../hbbs-ban-check-package/install.sh << 'INSTALL_EOF' -#!/bin/bash -# HBBS Ban Check - Installation Script - -set -e - -if [ "$EUID" -ne 0 ]; then - echo "Please run as root (sudo)" - exit 1 -fi - -echo "Installing HBBS with ban check..." - -# Backup -systemctl stop hbbs -cp /opt/rustdesk/hbbs /opt/rustdesk/hbbs.backup.$(date +%Y%m%d-%H%M%S) - -# Install -cp hbbs /opt/rustdesk/ -chmod +x /opt/rustdesk/hbbs - -# Restart -systemctl start hbbs -sleep 2 - -if systemctl is-active --quiet hbbs; then - echo "✓ HBBS with ban check installed successfully" - journalctl -u hbbs -n 10 --no-pager -else - echo "✗ HBBS failed to start. Restoring backup..." - systemctl stop hbbs - cp /opt/rustdesk/hbbs.backup.* /opt/rustdesk/hbbs - systemctl start hbbs - exit 1 -fi -INSTALL_EOF - -chmod +x ../hbbs-ban-check-package/install.sh - -echo -e "${GREEN}✓ Package created${NC}" - -# Summary -echo "" -echo -e "${BLUE}========================================${NC}" -echo -e "${GREEN}Build Complete!${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" -echo "Package location:" -echo " $(pwd)/../hbbs-ban-check-package/" -echo "" -echo "Installation:" -echo " 1. Copy package to server:" -echo " scp -r hbbs-ban-check-package/ user@server:/tmp/" -echo "" -echo " 2. On server, run:" -echo " cd /tmp/hbbs-ban-check-package" -echo " sudo ./install.sh" -echo "" -echo "Binary size: $(ls -lh target/release/hbbs | awk '{print $5}')" -echo "" diff --git a/hbbs-patch/cargo_deps.txt b/hbbs-patch/cargo_deps.txt new file mode 100644 index 00000000..e042cbc4 --- /dev/null +++ b/hbbs-patch/cargo_deps.txt @@ -0,0 +1,5 @@ + +# HTTP API dependencies +axum = { version = "0.6", features = ["tokio"] } +tower = "0.4" +serde_json = "1.0" diff --git a/hbbs-patch/database_patch.rs b/hbbs-patch/database_patch.rs deleted file mode 100644 index e8c00aff..00000000 --- a/hbbs-patch/database_patch.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Patch dla database.rs - dodaje metodę is_device_banned() -// -// Wstaw ten kod na końcu implementacji `impl Database` -// (przed zamykającym nawiasem klamrowym struktury) - - /// Check if a device is banned in the database - /// Returns true if device has is_banned=1, false otherwise - /// Uses separate synchronous SQLite connection to avoid nested runtime panic - pub async fn is_device_banned(&self, id: &str) -> ResultType { - use std::sync::Arc; - - // Database path - assuming same as used in HBBS (./db_v2.sqlite3) - let db_path = "./db_v2.sqlite3"; - let id = id.to_string(); - - // Execute synchronous query in blocking thread pool - // This avoids "Cannot start a runtime from within a runtime" error - let result = tokio::task::spawn_blocking(move || -> ResultType { - use rusqlite::Connection; - - // Open synchronous connection (read-only) - let conn = Connection::open_with_flags( - db_path, - rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY - )?; - - // Query for ban status - let mut stmt = conn.prepare( - "SELECT is_banned FROM peer WHERE id = ? AND is_deleted = 0" - )?; - - let result: Option = stmt - .query_row([&id], |row| row.get(0)) - .optional()?; - - // Return true if banned (is_banned = 1), false otherwise - Ok(result.map(|banned| banned == 1).unwrap_or(false)) - }) - .await - .map_err(|e| anyhow::anyhow!("Spawn blocking failed: {}", e))??; - - Ok(result) - } - - diff --git a/hbbs-patch/deploy-v6.ps1 b/hbbs-patch/deploy-v6.ps1 deleted file mode 100644 index af5ccfc7..00000000 --- a/hbbs-patch/deploy-v6.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -# Deploy HBBS v6 + HBBR with comprehensive ban enforcement -# This script deploys both signal server (hbbs) and relay server (hbbr) - -param( - [string]$Server = "YOUR_SERVER_IP", - [string]$User = "YOUR_SSH_USER" -) - -$ErrorActionPreference = "Stop" - -Write-Host "`n🚀 Deploying HBBS v6 + HBBR with comprehensive ban checks...`n" -ForegroundColor Cyan - -# Step 1: Wait for compilation -Write-Host "⏱️ Checking compilation status..." -ForegroundColor Yellow -$maxWait = 20 -for ($i = 1; $i -le $maxWait; $i++) { - $status = ssh "$User@$Server" "ps aux | grep -q '[c]argo build' && echo 'running' || echo 'done'" - - if ($status -match "done") { - Write-Host "✅ Compilation finished!" -ForegroundColor Green - break - } - - if ($i -eq $maxWait) { - Write-Host "❌ Compilation timeout" -ForegroundColor Red - exit 1 - } - - Write-Host " Waiting... ($i/$maxWait)" -ForegroundColor Gray - Start-Sleep -Seconds 30 -} - -# Step 2: Verify binaries exist -Write-Host "`n📦 Verifying binaries..." -ForegroundColor Yellow -$result = ssh "$User@$Server" @" -ls -lh /tmp/rustdesk-server/target/release/hbbs /tmp/rustdesk-server/target/release/hbbr 2>/dev/null | wc -l -"@ - -if ($result -ne "2") { - Write-Host "❌ Binaries not found! Compilation may have failed." -ForegroundColor Red - Write-Host "`nChecking build log for errors..." -ForegroundColor Yellow - ssh "$User@$Server" "tail -50 /tmp/build-v6-final.log | grep -A5 'error\[' || tail -30 /tmp/build-v6-final.log" - exit 1 -} - -Write-Host "✅ Both binaries found" -ForegroundColor Green - -# Step 3: Stop services -Write-Host "`n🛑 Stopping RustDesk services..." -ForegroundColor Yellow -ssh -t "$User@$Server" @" -sudo systemctl stop rustdesksignal -sudo systemctl stop rustdeskrelay 2>/dev/null || true -echo 'Services stopped' -"@ - -# Step 4: Backup existing binaries -Write-Host "`n💾 Creating backups..." -ForegroundColor Yellow -ssh -t "$User@$Server" @" -sudo cp /opt/rustdesk/hbbs /opt/rustdesk/hbbs.v6.backup 2>/dev/null && echo 'HBBS backup created' || echo 'No existing HBBS' -sudo cp /opt/rustdesk/hbbr /opt/rustdesk/hbbr.v6.backup 2>/dev/null && echo 'HBBR backup created' || echo 'No existing HBBR' -"@ - -# Step 5: Deploy new binaries -Write-Host "`n📥 Deploying new binaries..." -ForegroundColor Yellow -ssh -t "$User@$Server" @" -sudo cp /tmp/rustdesk-server/target/release/hbbs /opt/rustdesk/hbbs -sudo cp /tmp/rustdesk-server/target/release/hbbr /opt/rustdesk/hbbr -sudo chmod +x /opt/rustdesk/hbbs /opt/rustdesk/hbbr -sudo chown root:root /opt/rustdesk/hbbs /opt/rustdesk/hbbr -echo 'Binaries deployed' -"@ - -# Step 6: Start services -Write-Host "`n▶️ Starting services..." -ForegroundColor Yellow -ssh -t "$User@$Server" @" -sudo systemctl start rustdesksignal -sudo systemctl start rustdeskrelay 2>/dev/null || sudo systemctl start hbbr 2>/dev/null || echo 'Note: Relay service may have different name' -sleep 3 -echo '' -echo '=== HBBS Status ===' -sudo systemctl status rustdesksignal --no-pager | head -12 -echo '' -echo '=== HBBR Status ===' -sudo systemctl status rustdeskrelay --no-pager 2>/dev/null || ps aux | grep '[h]bbr' || echo 'HBBR may not be running as service' -"@ - -# Step 7: Verify deployment -Write-Host "`n✅ Verifying deployment..." -ForegroundColor Green -ssh "$User@$Server" @" -echo '' -echo 'Binary sizes:' -ls -lh /opt/rustdesk/hbbs /opt/rustdesk/hbbr | awk '{print \$9, \$5}' -echo '' -echo 'Checking ban check strings in binaries:' -strings /opt/rustdesk/hbbs | grep -E 'BLOCKED|Registration REJECTED' | head -3 -strings /opt/rustdesk/hbbr | grep -E 'HBBR Relay' | head -2 -"@ - -Write-Host "`n✅ Deployment complete!`n" -ForegroundColor Green -Write-Host "📝 Next steps:" -ForegroundColor Cyan -Write-Host " 1. Monitor logs: ssh $User@$Server 'sudo tail -f /var/log/rustdesk/signalserver.log'" -ForegroundColor Gray -Write-Host " 2. Test ban enforcement with device 1253021143" -ForegroundColor Gray -Write-Host " 3. Check HBBR logs for 'HBBR Relay BLOCKED' messages`n" -ForegroundColor Gray diff --git a/hbbs-patch/deploy-v8.sh b/hbbs-patch/deploy-v8.sh deleted file mode 100644 index 8193b2a6..00000000 --- a/hbbs-patch/deploy-v8.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -set -e - -echo "=== Deploying HBBS + HBBR v8 (bidirectional ban check) ===" - -# Backup -echo "Creating backups..." -cp /opt/rustdesk/hbbs /opt/rustdesk/hbbs.v7.backup 2>/dev/null || true -cp /opt/rustdesk/hbbr /opt/rustdesk/hbbr.v7.backup 2>/dev/null || true - -# Stop -echo "Stopping old processes..." -pkill -9 hbbs || true -pkill -9 hbbr || true -sleep 2 - -# Copy -echo "Copying v8 binaries..." -cp /tmp/hbbs-ban-check-package/hbbs /opt/rustdesk/hbbs -cp /tmp/hbbs-ban-check-package/hbbr /opt/rustdesk/hbbr -chmod +x /opt/rustdesk/hbbs /opt/rustdesk/hbbr - -# Clear logs -echo "Clearing logs..." -echo "" > /var/log/rustdesk/signalserver.log -echo "" > /var/log/rustdesk/hbbr.log - -# Start HBBS -echo "Starting HBBS v8..." -cd /opt/rustdesk -nohup ./hbbs -k _ -r YOUR_SERVER_IP:21117 >> /var/log/rustdesk/signalserver.log 2>&1 & -sleep 2 - -# Start HBBR -echo "Starting HBBR v8..." -nohup ./hbbr -k _ >> /var/log/rustdesk/hbbr.log 2>&1 & -sleep 2 - -# Verify -echo "" -echo "Processes:" -ps aux | grep -E "hbbs|hbbr" | grep -v grep - -echo "" -echo "Ports:" -netstat -tlnp 2>/dev/null | grep -E "21116|21117" || ss -tlnp | grep -E "21116|21117" - -echo "" -echo "=== Deployment v8 complete ===" -echo "New features:" -echo "- SOURCE device ban check (device initiating connection)" -echo "- TARGET device ban check (device being connected to)" -echo "- Bidirectional blocking" diff --git a/hbbs-patch/deploy.ps1 b/hbbs-patch/deploy.ps1 deleted file mode 100644 index 17ebca03..00000000 --- a/hbbs-patch/deploy.ps1 +++ /dev/null @@ -1,101 +0,0 @@ -# Quick HBBS Deployment Script -# Deploys pre-compiled binary to RustDesk server -# Usage: .\deploy.ps1 [binary-name] -# Example: .\deploy.ps1 hbbs-v3-patched - -param( - [Parameter(Mandatory=$false)] - [string]$Binary = "hbbs-v3-patched", - - [Parameter(Mandatory=$false)] - [string]$Server = "YOUR_SSH_USER@YOUR_SERVER_IP" -) - -$ErrorActionPreference = "Stop" - -Write-Host "========================================" -ForegroundColor Cyan -Write-Host "HBBS Quick Deployment" -ForegroundColor Cyan -Write-Host "========================================" -ForegroundColor Cyan -Write-Host "" - -# Check if binary exists -$BinaryPath = Join-Path $PSScriptRoot $Binary -if (-not (Test-Path $BinaryPath)) { - Write-Host "[ERROR] Binary not found: $BinaryPath" -ForegroundColor Red - exit 1 -} - -$FileSize = (Get-Item $BinaryPath).Length / 1MB -Write-Host "[1/4] Found binary: $Binary ($([math]::Round($FileSize, 2)) MB)" -ForegroundColor Yellow - -# Upload binary -Write-Host "[2/4] Uploading to server..." -ForegroundColor Yellow -scp $BinaryPath "${Server}:/tmp/hbbs-new" -if ($LASTEXITCODE -ne 0) { - Write-Host "[ERROR] Upload failed" -ForegroundColor Red - exit 1 -} -Write-Host " Upload complete" -ForegroundColor Green - -# Deploy on server -Write-Host "[3/4] Installing on server..." -ForegroundColor Yellow -$DeployScript = @" -# Stop service -sudo systemctl stop rustdesksignal - -# Backup current version -BACKUP_NAME="hbbs.backup.`$(date +%s)" -sudo cp /opt/rustdesk/hbbs /opt/rustdesk/`$BACKUP_NAME -echo "Backed up to: `$BACKUP_NAME" - -# Install new version -sudo cp /tmp/hbbs-new /opt/rustdesk/hbbs -sudo chmod +x /opt/rustdesk/hbbs -sudo chown root:root /opt/rustdesk/hbbs - -# Start service -sudo systemctl start rustdesksignal - -# Wait for startup -sleep 2 - -# Check status -sudo systemctl status rustdesksignal --no-pager -"@ - -ssh -t $Server $DeployScript - -if ($LASTEXITCODE -ne 0) { - Write-Host "[ERROR] Deployment failed" -ForegroundColor Red - exit 1 -} - -Write-Host "" -Write-Host "[4/4] Verifying deployment..." -ForegroundColor Yellow - -# Check logs for errors -$LogCheck = @" -if sudo grep -i "error\|panic" /var/log/rustdesk/signalserver.error | tail -5 | grep -q .; then - echo "WARNING: Errors found in logs" - exit 1 -else - echo "No errors in logs" -fi -"@ - -ssh $Server $LogCheck - -if ($LASTEXITCODE -eq 0) { - Write-Host "" - Write-Host "========================================" -ForegroundColor Green - Write-Host "Deployment Successful!" -ForegroundColor Green - Write-Host "========================================" -ForegroundColor Green -} else { - Write-Host "" - Write-Host "[WARNING] Service started but errors detected in logs" -ForegroundColor Yellow - Write-Host "Check logs: ssh $Server 'sudo tail -50 /var/log/rustdesk/signalserver.error'" -ForegroundColor Yellow -} - -Write-Host "" -Write-Host "Available backups:" -ForegroundColor Cyan -ssh $Server "ls -lh /opt/rustdesk/hbbs.backup.* 2>/dev/null | tail -5 || echo 'No backups found'" diff --git a/hbbs-patch/diagnose_ban.ps1 b/hbbs-patch/diagnose_ban.ps1 deleted file mode 100644 index de557e0b..00000000 --- a/hbbs-patch/diagnose_ban.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -# HBBS Ban Enforcement - Diagnostic Script -# Tests both directions and verifies database access - -param( - [string]$Server = "YOUR_SSH_USER@YOUR_SERVER_IP", - [string]$BannedDevice = "1253021143" -) - -Write-Host "========================================" -ForegroundColor Cyan -Write-Host "HBBS Ban Diagnostic v4" -ForegroundColor Cyan -Write-Host "========================================`n" -ForegroundColor Cyan - -# 1. Check database location and permissions -Write-Host "[1/5] Database Configuration" -ForegroundColor Yellow -$dbInfo = ssh $Server "ls -lh /opt/rustdesk/db_v2.sqlite* && echo '' && file /opt/rustdesk/db_v2.sqlite3" -Write-Host $dbInfo -ForegroundColor Gray - -# 2. Check HBBS process -Write-Host "`n[2/5] HBBS Process Status" -ForegroundColor Yellow -$processInfo = ssh $Server "ps aux | grep '/opt/rustdesk/hbbs' | grep -v grep && echo '' && sudo readlink /proc/`$(pgrep -f '/opt/rustdesk/hbbs')/cwd" -Write-Host $processInfo -ForegroundColor Gray - -# 3. Check device ban status in database (via API) -Write-Host "`n[3/5] Device Ban Status (API)" -ForegroundColor Yellow -$apiCheck = ssh $Server "curl -s http://localhost:5000/api/devices 2>/dev/null | grep -A10 '$BannedDevice' | head -15" -Write-Host $apiCheck -ForegroundColor Gray - -# 4. Check recent logs for ban enforcement -Write-Host "`n[4/5] Recent Ban Logs" -ForegroundColor Yellow -$banLogs = ssh $Server "grep -E 'REJECTED|ban.*$BannedDevice|$BannedDevice.*ban' /var/log/rustdesk/signalserver.log | tail -10" -if ($banLogs) { - Write-Host $banLogs -ForegroundColor Gray -} else { - Write-Host "No ban enforcement logs found" -ForegroundColor Yellow -} - -# 5. Check for registration attempts -Write-Host "`n[5/5] Registration Attempts" -ForegroundColor Yellow -$regLogs = ssh $Server "grep 'update_pk $BannedDevice' /var/log/rustdesk/signalserver.log | tail -5" -if ($regLogs) { - Write-Host "Device is still registering (should be blocked):" -ForegroundColor Red - Write-Host $regLogs -ForegroundColor Gray -} else { - Write-Host "No recent registration attempts" -ForegroundColor Green -} - -Write-Host "`n========================================" -ForegroundColor Cyan -Write-Host "Diagnostic Summary" -ForegroundColor Cyan -Write-Host "========================================" -ForegroundColor Cyan - -Write-Host "`nExpected behavior after v4 deployment:" -ForegroundColor Yellow -Write-Host "1. Device $BannedDevice should NOT appear in 'update_pk' logs" -ForegroundColor Gray -Write-Host "2. Should see 'Registration REJECTED' messages" -ForegroundColor Gray -Write-Host "3. Should see 'Relay REJECTED - initiator is banned' OR 'target is banned'" -ForegroundColor Gray -Write-Host "4. Device should NOT be 'online' in API response" -ForegroundColor Gray - -Write-Host "`nIf device still registers:" -ForegroundColor Yellow -Write-Host "- Check if v4 binary is deployed" -ForegroundColor Gray -Write-Host "- Restart HBBS service" -ForegroundColor Gray -Write-Host "- Check error logs for database access issues" -ForegroundColor Gray diff --git a/hbbs-patch/hbbs-ban-check-package/hbbr.exe b/hbbs-patch/hbbs-ban-check-package/hbbr.exe deleted file mode 100644 index 508130b6..00000000 Binary files a/hbbs-patch/hbbs-ban-check-package/hbbr.exe and /dev/null differ diff --git a/hbbs-patch/hbbs-ban-check-package/hbbs.exe b/hbbs-patch/hbbs-ban-check-package/hbbs.exe deleted file mode 100644 index 19326436..00000000 Binary files a/hbbs-patch/hbbs-ban-check-package/hbbs.exe and /dev/null differ diff --git a/hbbs-patch/peer_find_by_addr.patch b/hbbs-patch/peer_find_by_addr.patch deleted file mode 100644 index f8ef22c1..00000000 --- a/hbbs-patch/peer_find_by_addr.patch +++ /dev/null @@ -1,15 +0,0 @@ -// Patch for peer.rs - add method to find device ID by socket address - -// Add this method to PeerMap impl block (after is_in_memory method): - - // Find device ID by socket address - pub(crate) async fn get_id_by_addr(&self, addr: SocketAddr) -> Option { - let map = self.map.read().await; - for (id, peer) in map.iter() { - let peer_addr = peer.read().await.socket_addr; - if peer_addr == addr { - return Some(id.clone()); - } - } - None - } diff --git a/hbbs-patch/peer_patch.rs b/hbbs-patch/peer_patch.rs deleted file mode 100644 index 09a49f57..00000000 --- a/hbbs-patch/peer_patch.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Patch dla peer.rs - modyfikuje metodę update_pk -// -// Znajdź metodę `pub(crate) async fn update_pk(` w pliku peer.rs -// i zastąp jej początek (pierwszych ~20 linii) poniższym kodem: - - #[inline] - pub(crate) async fn update_pk( - &mut self, - id: String, - peer: LockPeer, - addr: SocketAddr, - uuid: Bytes, - pk: Bytes, - ip: String, - ) -> register_pk_response::Result { - log::info!("update_pk {} {:?} {:?} {:?}", id, addr, uuid, pk); - - // *** NOWE: Sprawdzenie czy urządzenie jest zbanowane *** - // Zapytanie do bazy danych przed akceptacją rejestracji - match self.db.is_device_banned(&id).await { - Ok(true) => { - // Urządzenie jest zbanowane - odrzuć rejestrację - log::warn!("Registration REJECTED for device {}: DEVICE IS BANNED", id); - return register_pk_response::Result::UUID_MISMATCH; - } - Ok(false) => { - // Urządzenie nie jest zbanowane - kontynuuj normalnie - log::debug!("Ban check passed for device {}", id); - } - Err(e) => { - // Błąd zapytania do bazy - loguj ale przepuść (fail-open policy) - log::error!("Failed to check ban status for device {}: {}. Allowing registration (fail-open)", id, e); - // Kontynuuj rejestrację mimo błędu bazy - } - } - // *** KONIEC NOWEGO KODU *** - - // Oryginalna logika rejestracji (bez zmian): - let (info_str, guid) = { - let mut w = peer.write().await; - w.socket_addr = addr; - w.uuid = uuid.clone(); - w.pk = pk.clone(); - w.last_reg_time = Instant::now(); - w.info.ip = ip; - ( - serde_json::to_string(&w.info).unwrap_or_default(), - w.guid.clone(), - ) - }; - - // ... reszta metody pozostaje bez zmian ... diff --git a/hbbs-patch/rustdesk-server-1.1.14.zip b/hbbs-patch/rustdesk-server-1.1.14.zip deleted file mode 100644 index 7db1fe42..00000000 Binary files a/hbbs-patch/rustdesk-server-1.1.14.zip and /dev/null differ diff --git a/hbbs-patch/rustdesk-server-1.1.14/Cargo.toml b/hbbs-patch/rustdesk-server-1.1.14/Cargo.toml new file mode 100644 index 00000000..aa4f11b3 --- /dev/null +++ b/hbbs-patch/rustdesk-server-1.1.14/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "hbbs" +version = "1.1.14" +authors = ["rustdesk "] +edition = "2021" +build = "build.rs" +default-run = "hbbs" + +[[bin]] +name = "hbbr" +path = "src/hbbr.rs" + +[[bin]] +name = "rustdesk-utils" +path = "src/utils.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hbb_common = { path = "libs/hbb_common" } +serde_derive = "1.0" +serde = "1.0" +serde_json = "1.0" +lazy_static = "1.4" +clap = "2" +rust-ini = "0.18" +minreq = { version = "2.4", features = ["punycode"] } +machine-uid = "0.2" +mac_address = "1.1.5" +whoami = "1.2" +base64 = "0.13" +axum = { version = "0.6", features = ["headers", "tokio"] } +tower = "0.4" +sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "sqlite", "macros", "chrono", "json" ] } +deadpool = "0.8" +async-trait = "0.1" +async-speed-limit = { git = "https://github.com/open-trade/async-speed-limit" } +uuid = { version = "1.0", features = ["v4"] } +bcrypt = "0.13" +chrono = "0.4" +jsonwebtoken = "8" +headers = "0.3" +once_cell = "1.8" +sodiumoxide = "0.2" +tokio-tungstenite = "0.17" +tungstenite = "0.17" +regex = "1.4" +tower-http = { version = "0.3", features = ["fs", "trace", "cors"] } +http = "0.2" +flexi_logger = { version = "0.22", features = ["async", "use_chrono_for_offset", "dont_minimize_extra_stacks"] } +ipnetwork = "0.20" +local-ip-address = "0.5.1" +dns-lookup = "1.0.8" +ping = "0.4.0" + +[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] +# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support +reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "native-tls", "gzip"], default-features=false } + +[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies] +reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false } + +[build-dependencies] +hbb_common = { path = "libs/hbb_common" } + +[workspace] +members = ["libs/hbb_common"] +exclude = ["ui"] + +#https://github.com/johnthagen/min-sized-rust +#https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles +[profile.release] +lto = true +codegen-units = 1 +panic = 'abort' +strip = true +#opt-level = 'z' # only have smaller size after strip # Default is 3, better performance +#rpath = true # Not needed diff --git a/hbbs-patch/src/http_api.rs b/hbbs-patch/src/http_api.rs index f51404b7..8ac70823 100644 --- a/hbbs-patch/src/http_api.rs +++ b/hbbs-patch/src/http_api.rs @@ -1,6 +1,6 @@ use axum::{ extract::Extension, - http::StatusCode, + http::{StatusCode, HeaderMap}, response::Json, routing::get, Router, @@ -9,14 +9,15 @@ use serde::Serialize; use sqlx::{sqlite::SqlitePool, Row}; use std::net::SocketAddr; use std::sync::Arc; -use crate::peer::PeerMap; +use std::fs; const REG_TIMEOUT: i32 = 20_000; +const API_KEY_FILE: &str = "/opt/rustdesk/.api_key"; #[derive(Clone)] pub struct ApiState { pub db_pool: SqlitePool, - pub peer_map: Arc, + pub api_key: String, } #[derive(Serialize)] @@ -33,10 +34,31 @@ struct ApiResponse { error: Option, } +// Middleware to verify API key +fn verify_api_key(headers: &HeaderMap, state: &ApiState) -> Result<(), StatusCode> { + match headers.get("X-API-Key") { + Some(key) => { + if key.to_str().unwrap_or("") == state.api_key { + Ok(()) + } else { + hbb_common::log::warn!("API: Invalid API key provided"); + Err(StatusCode::UNAUTHORIZED) + } + } + None => { + hbb_common::log::warn!("API: Missing X-API-Key header"); + Err(StatusCode::UNAUTHORIZED) + } + } +} + async fn get_online_peers( + headers: HeaderMap, Extension(state): Extension>, ) -> Result>>, StatusCode> { - match sqlx::query("SELECT id, note FROM peer") + // Verify API key + verify_api_key(&headers, &state)?; + match sqlx::query("SELECT id, note, info FROM peer WHERE (status IS NULL OR status = 0)") .fetch_all(&state.db_pool) .await { @@ -46,14 +68,11 @@ async fn get_online_peers( for row in rows.iter() { let id: String = row.get("id"); let note: Option = row.get("note"); + let _info: String = row.get("info"); - // Check real-time online status from PeerMap (same logic as RustDesk client) - let online = if let Some(peer) = state.peer_map.get_in_memory(&id).await { - let elapsed = peer.read().await.last_reg_time.elapsed().as_millis() as i32; - elapsed < REG_TIMEOUT - } else { - false - }; + // For now, mark all devices as offline since we can't determine online status from DB + // Real online status requires querying the in-memory PeerMap which isn't accessible here + let online = false; peers.push(PeerStatus { id, @@ -76,15 +95,66 @@ async fn get_online_peers( } } -async fn health_check() -> Json> { - Json(ApiResponse { +async fn health_check( + headers: HeaderMap, + Extension(state): Extension>, +) -> Result>, StatusCode> { + // Verify API key + verify_api_key(&headers, &state)?; + + Ok(Json(ApiResponse { success: true, data: Some("RustDesk API is running".to_string()), error: None, - }) + })) } -pub async fn start_api_server(db_path: String, port: u16, peer_map: Arc) -> Result<(), Box> { +fn load_or_generate_api_key() -> String { + // Try to read from file first + if let Ok(key) = fs::read_to_string(API_KEY_FILE) { + let key = key.trim().to_string(); + if !key.is_empty() { + hbb_common::log::info!("API: Loaded API key from {}", API_KEY_FILE); + return key; + } + } + + // Generate new key + use hbb_common::rand::Rng; + const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let mut rng = hbb_common::rand::thread_rng(); + let key: String = (0..64) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect(); + + // Try to save to file + if let Some(parent) = std::path::Path::new(API_KEY_FILE).parent() { + let _ = fs::create_dir_all(parent); + } + + if let Err(e) = fs::write(API_KEY_FILE, &key) { + hbb_common::log::warn!("API: Could not save API key to file: {}", e); + } else { + hbb_common::log::info!("API: Generated and saved new API key to {}", API_KEY_FILE); + // Set file permissions to 600 (owner read/write only) + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Ok(metadata) = fs::metadata(API_KEY_FILE) { + let mut perms = metadata.permissions(); + perms.set_mode(0o600); + let _ = fs::set_permissions(API_KEY_FILE, perms); + } + } + } + + key +} + +pub async fn start_api_server(db_path: String, port: u16) -> Result<(), Box> { use sqlx::sqlite::SqliteConnectOptions; use std::str::FromStr; @@ -94,9 +164,12 @@ pub async fn start_api_server(db_path: String, port: u16, peer_map: Arc let pool = SqlitePool::connect_with(connect_options).await?; + // Load or generate API key + let api_key = load_or_generate_api_key(); + let state = Arc::new(ApiState { db_pool: pool, - peer_map, + api_key: api_key.clone(), }); let app = Router::new() @@ -104,10 +177,12 @@ pub async fn start_api_server(db_path: String, port: u16, peer_map: Arc .route("/api/peers", get(get_online_peers)) .layer(axum::Extension(state)); - // SECURITY: Bind only to localhost (127.0.0.1) - not exposed to internet - // Web console connects locally, so no need for external access - let addr = SocketAddr::from(([127, 0, 0, 1], port)); - hbb_common::log::info!("HTTP API server listening on {} (localhost only)", addr); + // SECURITY UPDATE: Now binds to 0.0.0.0 (all interfaces) for LAN access + // Protected by API key authentication (X-API-Key header required) + let addr = SocketAddr::from(([0, 0, 0, 0], port)); + hbb_common::log::info!("HTTP API server listening on {} (LAN accessible, API key protected)", addr); + hbb_common::log::info!("API key saved to: {}", API_KEY_FILE); + hbb_common::log::info!("Use X-API-Key header with value from file for authentication"); axum::Server::bind(&addr) .serve(app.into_make_service()) diff --git a/hbbs-patch/src/main.rs b/hbbs-patch/src/main.rs index 80886bd1..5c694ed3 100644 --- a/hbbs-patch/src/main.rs +++ b/hbbs-patch/src/main.rs @@ -4,9 +4,12 @@ use flexi_logger::*; use hbb_common::{bail, config::RENDEZVOUS_PORT, ResultType}; use hbbs::{common::*, *}; +use std::sync::Arc; + +mod http_api; const RMEM: usize = 0; -const API_PORT: u16 = 21120; // Localhost-only API port (not exposed to internet) +const API_PORT: u16 = 21120; // HTTP API port (LAN accessible with X-API-Key auth) fn main() -> ResultType<()> { let _logger = Logger::try_with_env_or_str("info")? @@ -35,7 +38,18 @@ fn main() -> ResultType<()> { let serial: i32 = get_arg("serial").parse().unwrap_or(0); let api_port = get_arg("api-port").parse::().unwrap_or(API_PORT); + // Start HTTP API server in background + // API reads device status directly from SQLite database + std::thread::spawn(move || { + hbb_common::tokio::runtime::Runtime::new().unwrap().block_on(async { + let db_path = get_arg_or("db", "/opt/rustdesk/db_v2.sqlite3".to_owned()); + if let Err(e) = http_api::start_api_server(db_path, api_port).await { + hbb_common::log::error!("HTTP API failed to start: {}", e); + } + }); + }); + crate::common::check_software_update(); - RendezvousServer::start(port, serial, &get_arg_or("key", "-".to_owned()), rmem, api_port)?; + RendezvousServer::start(port, serial, &get_arg_or("key", "-".to_owned()), rmem)?; Ok(()) } diff --git a/hbbs-patch/src/rendezvous_server.rs b/hbbs-patch/src/rendezvous_server.rs index 187e1094..83e11d5a 100644 --- a/hbbs-patch/src/rendezvous_server.rs +++ b/hbbs-patch/src/rendezvous_server.rs @@ -713,7 +713,9 @@ impl RendezvousServer { log::debug!("Target ban check passed for device {}", id); } Err(e) => { - log::error!("Failed to check target ban status for {}: {}", id, e); + log::error!("SECURITY: Database unavailable, blocking connection for safety: {}", e); + // Return empty response to block connection (fail-closed) + return Ok((RendezvousMessage::new(), None)); } } @@ -734,7 +736,9 @@ impl RendezvousServer { log::debug!("Source ban check passed for device {}", source_id); } Err(e) => { - log::error!("Failed to check source ban status for {}: {}", source_id, e); + log::error!("SECURITY: Database unavailable, blocking source connection for safety: {}", e); + // Return empty response to block connection (fail-closed) + return Ok((RendezvousMessage::new(), None)); } } } else { diff --git a/hbbs-patch/test_ban_enforcement.ps1 b/hbbs-patch/test_ban_enforcement.ps1 deleted file mode 100644 index 428398c3..00000000 --- a/hbbs-patch/test_ban_enforcement.ps1 +++ /dev/null @@ -1,177 +0,0 @@ -# Test Ban Enforcement - Comprehensive Testing Script -# Tests bidirectional ban enforcement (initiator + target) - -param( - [Parameter(Mandatory=$false)] - [string]$BannedDeviceId = "58457133", - - [Parameter(Mandatory=$false)] - [string]$NormalDeviceId = "1253021143", - - [Parameter(Mandatory=$false)] - [string]$ApiUrl = "http://YOUR_SERVER_IP:5000", - - [Parameter(Mandatory=$false)] - [string]$Server = "YOUR_SSH_USER@YOUR_SERVER_IP" -) - -$ErrorActionPreference = "Continue" - -function Write-TestHeader { - param([string]$Title) - Write-Host "`n========================================" -ForegroundColor Cyan - Write-Host $Title -ForegroundColor Cyan - Write-Host "========================================`n" -ForegroundColor Cyan -} - -function Write-TestResult { - param([string]$Test, [bool]$Passed, [string]$Details = "") - if ($Passed) { - Write-Host "[✓] $Test" -ForegroundColor Green - } else { - Write-Host "[✗] $Test" -ForegroundColor Red - } - if ($Details) { - Write-Host " $Details" -ForegroundColor Gray - } -} - -Write-TestHeader "HBBS Ban Enforcement Tests" -Write-Host "Banned Device: $BannedDeviceId" -ForegroundColor Yellow -Write-Host "Normal Device: $NormalDeviceId" -ForegroundColor Yellow -Write-Host "API URL: $ApiUrl`n" -ForegroundColor Yellow - -# Test 1: Check service status -Write-TestHeader "[1/6] Service Status Check" -$serviceStatus = ssh $Server "sudo systemctl is-active rustdesksignal" 2>$null -$serviceRunning = $serviceStatus -eq "active" -Write-TestResult "Service rustdesksignal is running" $serviceRunning $serviceStatus - -if (-not $serviceRunning) { - Write-Host "`nERROR: Service is not running. Starting service..." -ForegroundColor Red - ssh -t $Server "sudo systemctl start rustdesksignal" - Start-Sleep -Seconds 3 -} - -# Test 2: Check HBBS version and binary -Write-TestHeader "[2/6] Binary Version Check" -$hbbsVersion = ssh $Server "ls -lh /opt/rustdesk/hbbs | awk '{print `$5, `$9}'" 2>$null -Write-Host "Binary: $hbbsVersion" -ForegroundColor Gray - -$backupCount = ssh $Server "ls -1 /opt/rustdesk/hbbs.backup.* 2>/dev/null | wc -l" 2>$null -Write-Host "Backups available: $backupCount" -ForegroundColor Gray - -# Test 3: Check API connectivity -Write-TestHeader "[3/6] API Connectivity Check" -try { - $devices = Invoke-RestMethod -Uri "$ApiUrl/api/devices" -Method Get -TimeoutSec 5 - $deviceCount = $devices.Count - Write-TestResult "API responding" $true "$deviceCount devices returned" -} catch { - Write-TestResult "API responding" $false $_.Exception.Message - Write-Host "`nERROR: Cannot connect to API. Exiting..." -ForegroundColor Red - exit 1 -} - -# Test 4: Check ban status of test device -Write-TestHeader "[4/6] Device Ban Status Check" -$bannedDevice = $devices | Where-Object { $_.id -eq $BannedDeviceId } -if ($bannedDevice) { - $isBanned = $bannedDevice.is_banned -eq $true - Write-TestResult "Device $BannedDeviceId found in API" $true - Write-TestResult "Device $BannedDeviceId is banned" $isBanned "is_banned: $($bannedDevice.is_banned)" - - if (-not $isBanned) { - Write-Host "`nWARNING: Test device is not banned. Ban it first via console:" -ForegroundColor Yellow - Write-Host " $ApiUrl" -ForegroundColor Yellow - } -} else { - Write-TestResult "Device $BannedDeviceId found in API" $false "Device not in database" -} - -# Test 5: Check logs for ban enforcement -Write-TestHeader "[5/6] Log Analysis - Ban Enforcement" -Write-Host "Checking last 50 ban-related log entries..." -ForegroundColor Gray - -$banLogs = ssh $Server "grep -i 'ban\|reject' /var/log/rustdesk/signalserver.log 2>/dev/null | tail -50" -if ($banLogs) { - $recentBans = ($banLogs -split "`n" | Select-Object -Last 10) -join "`n" - Write-Host "`nRecent ban activity:" -ForegroundColor Yellow - Write-Host $recentBans -ForegroundColor Gray - - # Count different types of rejections - $registrationRejections = ($banLogs | Select-String "Registration REJECTED").Count - $relayRejections = ($banLogs | Select-String "Relay.*REJECTED").Count - $punchHoleRejections = ($banLogs | Select-String "Punch hole.*REJECTED").Count - $initiatorBlocks = ($banLogs | Select-String "initiator.*banned").Count - $targetBlocks = ($banLogs | Select-String "target.*banned").Count - - Write-Host "`nSummary:" -ForegroundColor Cyan - Write-Host " Registration rejections: $registrationRejections" -ForegroundColor Gray - Write-Host " Relay rejections: $relayRejections" -ForegroundColor Gray - Write-Host " Punch hole rejections: $punchHoleRejections" -ForegroundColor Gray - Write-Host " Initiator blocks: $initiatorBlocks" -ForegroundColor Gray - Write-Host " Target blocks: $targetBlocks" -ForegroundColor Gray - - $bidirectionalWorks = ($initiatorBlocks -gt 0) -and ($targetBlocks -gt 0) - Write-TestResult "Bidirectional ban enforcement active" $bidirectionalWorks -} else { - Write-TestResult "Log file accessible" $false "No ban logs found" -} - -# Test 6: Check for errors -Write-TestHeader "[6/6] Error Log Check" -$errorLogs = ssh $Server "grep -i 'error\|panic' /var/log/rustdesk/signalserver.error 2>/dev/null | tail -20" -if ($errorLogs) { - $recentErrors = ($errorLogs -split "`n" | Select-Object -Last 5) -join "`n" - Write-Host "Recent errors found:" -ForegroundColor Red - Write-Host $recentErrors -ForegroundColor Gray - Write-TestResult "No critical errors" $false "Errors detected in logs" -} else { - Write-TestResult "No critical errors" $true "Error log is clean" -} - -# Test 7: Performance check -Write-TestHeader "Performance Metrics" -$cpuUsage = ssh $Server "ps -p `$(pgrep -f '/opt/rustdesk/hbbs') -o %cpu --no-headers | awk '{print `$1}'" 2>$null -$memUsage = ssh $Server "ps -p `$(pgrep -f '/opt/rustdesk/hbbs') -o %mem --no-headers | awk '{print `$1}'" 2>$null - -if ($cpuUsage) { - Write-Host " CPU Usage: $cpuUsage%" -ForegroundColor Gray - Write-Host " Memory Usage: $memUsage%" -ForegroundColor Gray -} else { - Write-Host " Process metrics unavailable" -ForegroundColor Yellow -} - -# Final Summary -Write-TestHeader "Test Summary" -Write-Host "1. Service Status: " -NoNewline -Write-Host $(if ($serviceRunning) { "PASS" } else { "FAIL" }) -ForegroundColor $(if ($serviceRunning) { "Green" } else { "Red" }) - -Write-Host "2. API Connectivity: " -NoNewline -Write-Host $(if ($devices) { "PASS" } else { "FAIL" }) -ForegroundColor $(if ($devices) { "Green" } else { "Red" }) - -Write-Host "3. Ban Status Correct: " -NoNewline -if ($bannedDevice) { - $banCorrect = $bannedDevice.is_banned -eq $true - Write-Host $(if ($banCorrect) { "PASS" } else { "WARN" }) -ForegroundColor $(if ($banCorrect) { "Green" } else { "Yellow" }) -} else { - Write-Host "N/A" -ForegroundColor Gray -} - -Write-Host "4. Log Analysis: " -NoNewline -Write-Host $(if ($banLogs) { "PASS" } else { "WARN" }) -ForegroundColor $(if ($banLogs) { "Green" } else { "Yellow" }) - -Write-Host "5. Error Check: " -NoNewline -Write-Host $(if (-not $errorLogs) { "PASS" } else { "WARN" }) -ForegroundColor $(if (-not $errorLogs) { "Green" } else { "Yellow" }) - -Write-Host "`n========================================" -ForegroundColor Cyan -Write-Host "Testing Complete!" -ForegroundColor Cyan -Write-Host "========================================`n" -ForegroundColor Cyan - -Write-Host "Next steps:" -ForegroundColor Yellow -Write-Host "1. If device $BannedDeviceId is not banned, ban it via console: $ApiUrl" -ForegroundColor Gray -Write-Host "2. Try connecting FROM banned device to normal device" -ForegroundColor Gray -Write-Host "3. Try connecting FROM normal device TO banned device" -ForegroundColor Gray -Write-Host "4. Check logs for 'initiator banned' and 'target banned' messages:" -ForegroundColor Gray -Write-Host " ssh $Server 'tail -f /var/log/rustdesk/signalserver.log | grep -i reject'" -ForegroundColor Gray diff --git a/install-improved.ps1 b/install-improved.ps1 index 45c1ca6f..85fdd1b9 100644 --- a/install-improved.ps1 +++ b/install-improved.ps1 @@ -1,9 +1,14 @@ -# BetterDesk Console - Windows Installation Script v9 +# BetterDesk Console - Windows Installation Script v1.5.0 # # This script installs the enhanced RustDesk HBBS/HBBR servers with # bidirectional ban enforcement, HTTP API, and web management console. # -# NEW in v9: +# NEW in v1.5.0: +# - Authentication system with bcrypt password hashing +# - Role-based access control (Admin, Operator, Viewer) +# - Sidebar navigation with multiple sections +# - Password-protected public key access +# - User management panel (admin only) # - Support for custom RustDesk installation directories # - Automatic verification of required RustDesk files # - Improved error handling and validation @@ -35,7 +40,7 @@ param( ) $ErrorActionPreference = "Stop" -$VERSION = "v9" +$VERSION = "v1.5.0" $BINARY_VERSION = "v8-api" $HBBS_API_PORT = 21114 diff --git a/install-improved.sh b/install-improved.sh index e3d07653..1a11e56d 100644 --- a/install-improved.sh +++ b/install-improved.sh @@ -1,16 +1,21 @@ #!/bin/bash ############################################################################# -# BetterDesk Console - Enhanced Installation Script v9 +# BetterDesk Console - Enhanced Installation Script v1.5.0 # # This script installs the enhanced RustDesk HBBS/HBBR servers with # bidirectional ban enforcement, HTTP API, and web management console. # -# NEW in v9: +# NEW in v1.5.0: +# - Authentication system with bcrypt password hashing +# - Role-based access control (Admin, Operator, Viewer) +# - Sidebar navigation with multiple sections +# - Password-protected public key access +# - User management panel (admin only) +# - CSRF protection and rate limiting # - Support for custom RustDesk installation directories # - Automatic verification of required RustDesk files # - --break-system-packages support for Docker/containerized environments -# - Improved error handling and validation # # Features: # - Automatic backup of existing RustDesk installation @@ -21,8 +26,9 @@ # - Configures systemd services # - Uses Google Material Icons (offline) # -# Author: GitHub Copilot -# License: MIT +# Author: UNITRONIX +# Repository: https://github.com/UNITRONIX/Rustdesk-FreeConsole +# License: AGPL-3.0 ############################################################################# set -e # Exit on error @@ -40,9 +46,10 @@ BACKUP_DIR="" CONSOLE_DIR="/opt/BetterDeskConsole" TEMP_DIR="/tmp/betterdesk-install" HBBS_API_PORT=21114 -VERSION="v9" # Current version with HTTP API +VERSION="v1.5.0" # Current version with Authentication & User Management BINARY_VERSION="v8-api" # Binary file suffix PIP_EXTRA_ARGS="" # Will be set to --break-system-packages if needed +FLASK_SECRET_KEY="" # Will be generated during installation # Helper functions print_header() { @@ -836,12 +843,31 @@ run_database_migrations() { fi fi + # Run v1.4.0 migration (authentication system) + if [ -f "$migrations_dir/v1.4.0_auth_system.py" ]; then + print_info "Running migration v1.4.0 (authentication system)..." + if python3 "$migrations_dir/v1.4.0_auth_system.py"; then + print_success "Migration v1.4.0 completed" + print_info "Default admin credentials created (check output above)" + else + print_warning "Migration v1.4.0 skipped (may be already applied)" + fi + fi + print_success "Database migrations completed" echo "" } +generate_secret_key() { + print_header "Generating Flask Secret Key" + + # Generate a secure random secret key + FLASK_SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") + print_success "Secret key generated" +} + install_web_console() { - print_header "Installing Web Management Console" + print_header "Installing Web Management Console v1.5.0" local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" local web_dir="$script_dir/web" @@ -853,18 +879,40 @@ install_web_console() { # Create console directory mkdir -p "$CONSOLE_DIR" + mkdir -p "$CONSOLE_DIR/templates" + mkdir -p "$CONSOLE_DIR/static" # Copy files print_info "Copying web console files..." - cp -r "$web_dir"/* "$CONSOLE_DIR/" + cp "$web_dir/app_v14.py" "$CONSOLE_DIR/app.py" + cp "$web_dir/auth.py" "$CONSOLE_DIR/auth.py" + cp "$web_dir/requirements.txt" "$CONSOLE_DIR/requirements.txt" + + # Copy templates (use v1.5.0 interface) + print_info "Installing v1.5.0 user interface..." + cp "$web_dir/templates/index_v15.html" "$CONSOLE_DIR/templates/index_v15.html" + cp "$web_dir/templates/login.html" "$CONSOLE_DIR/templates/login.html" + + # Copy static files + print_info "Installing JavaScript and CSS..." + if [ -d "$web_dir/static" ]; then + cp -r "$web_dir/static"/* "$CONSOLE_DIR/static/" 2>/dev/null || true + fi + cp "$web_dir/static/script_v15.js" "$CONSOLE_DIR/static/script_v15.js" # Update app.py with correct RustDesk path if [ "$RUSTDESK_DIR" != "/opt/rustdesk" ]; then print_info "Updating console configuration for custom RustDesk path..." sed -i "s|'/opt/rustdesk/db_v2.sqlite3'|'$RUSTDESK_DIR/db_v2.sqlite3'|g" "$CONSOLE_DIR/app.py" sed -i "s|'/opt/rustdesk/id_ed25519.pub'|'$RUSTDESK_DIR/id_ed25519.pub'|g" "$CONSOLE_DIR/app.py" + sed -i "s|'/opt/rustdesk/.api_key'|'$RUSTDESK_DIR/.api_key'|g" "$CONSOLE_DIR/app.py" + sed -i "s|DB_PATH = '/opt/rustdesk/db_v2.sqlite3'|DB_PATH = '$RUSTDESK_DIR/db_v2.sqlite3'|g" "$CONSOLE_DIR/auth.py" fi + # Generate and configure Flask secret key + print_info "Configuring Flask security..." + generate_secret_key + # Install Python dependencies print_info "Installing Python dependencies..." if [ -n "$PIP_EXTRA_ARGS" ]; then @@ -874,11 +922,11 @@ install_web_console() { pip3 install -r "$CONSOLE_DIR/requirements.txt" fi - # Create systemd service + # Create systemd service with environment variables print_info "Creating systemd service..." cat > /etc/systemd/system/betterdesk.service < /etc/systemd/system/betterdesk.service.d/override.conf < /dev/null; then - missing_deps+=($cmd) - fi - done - - if [ ${#missing_deps[@]} -ne 0 ]; then - print_error "Missing dependencies: ${missing_deps[*]}" - echo "" - echo "Please install missing dependencies:" - echo " Ubuntu/Debian: sudo apt install python3 python3-pip curl systemd" - echo " CentOS/RHEL: sudo yum install python3 python3-pip curl systemd" - exit 1 - fi - - print_success "All dependencies found" -} - -backup_rustdesk() { - print_header "Backing Up Existing RustDesk Installation" - - if [ ! -d "$RUSTDESK_DIR" ]; then - print_warning "No existing RustDesk installation found at $RUSTDESK_DIR" - print_info "Will proceed with fresh installation" - return 0 - fi - - echo -e "${YELLOW}Found existing RustDesk installation${NC}" - echo "" - echo "Options:" - echo " 1) Create automatic backup to $BACKUP_DIR" - echo " 2) I have already created a manual backup" - echo " 3) Skip backup (not recommended)" - echo "" - read -p "Choose option [1-3]: " backup_choice - - case $backup_choice in - 1) - print_info "Creating backup..." - cp -r "$RUSTDESK_DIR" "$BACKUP_DIR" - print_success "Backup created at: $BACKUP_DIR" - ;; - 2) - print_info "Using manual backup" - ;; - 3) - print_warning "Skipping backup - YOU ARE RESPONSIBLE FOR ANY DATA LOSS" - read -p "Are you SURE? Type 'yes' to continue: " confirm - if [ "$confirm" != "yes" ]; then - print_error "Installation cancelled" - exit 1 - fi - ;; - *) - print_error "Invalid option" - exit 1 - ;; - esac -} - -install_binaries() { - print_header "Installing Enhanced HBBS/HBBR $VERSION" - - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local bin_dir="$script_dir/hbbs-patch/bin" - - if [ ! -f "$bin_dir/hbbs-$VERSION" ] || [ ! -f "$bin_dir/hbbr-$VERSION" ]; then - print_error "Precompiled binaries not found in: $bin_dir" - print_info "Expected files: hbbs-$VERSION, hbbr-$VERSION" - exit 1 - fi - - # Create RustDesk directory if it doesn't exist - mkdir -p "$RUSTDESK_DIR" - - # Stop existing services - print_info "Stopping RustDesk services..." - systemctl stop rustdesksignal.service 2>/dev/null || true - systemctl stop rustdeskrelay.service 2>/dev/null || true - pkill -9 hbbs 2>/dev/null || true - pkill -9 hbbr 2>/dev/null || true - sleep 2 - - # Backup existing binaries - if [ -f "$RUSTDESK_DIR/hbbs" ]; then - print_info "Backing up old hbbs..." - cp "$RUSTDESK_DIR/hbbs" "$RUSTDESK_DIR/hbbs.backup.$(date +%Y%m%d-%H%M%S)" - fi - - if [ -f "$RUSTDESK_DIR/hbbr" ]; then - print_info "Backing up old hbbr..." - cp "$RUSTDESK_DIR/hbbr" "$RUSTDESK_DIR/hbbr.backup.$(date +%Y%m%d-%H%M%S)" - fi - - # Install new binaries - print_info "Installing HBBS $VERSION (with bidirectional ban enforcement)..." - cp "$bin_dir/hbbs-$VERSION" "$RUSTDESK_DIR/hbbs" - chmod +x "$RUSTDESK_DIR/hbbs" - - print_info "Installing HBBR $VERSION..." - cp "$bin_dir/hbbr-$VERSION" "$RUSTDESK_DIR/hbbr" - chmod +x "$RUSTDESK_DIR/hbbr" - - print_success "Binaries installed successfully" - - # Restart services - print_info "Restarting RustDesk services..." - systemctl daemon-reload 2>/dev/null || true - systemctl start rustdesksignal.service 2>/dev/null || true - systemctl start rustdeskrelay.service 2>/dev/null || true - - # Wait for services to start - sleep 3 - - # Verify services - local services_ok=true - if systemctl is-active --quiet rustdesksignal.service; then - print_success "HBBS service is running" - else - print_warning "HBBS service not running (may need manual start)" - services_ok=false - fi - - if systemctl is-active --quiet rustdeskrelay.service 2>/dev/null; then - print_success "HBBR service is running" - else - print_info "HBBR service not configured (optional)" - fi - - # Display version info - echo "" - print_info "HBBS/HBBR version: $VERSION" - print_info "Features:" - echo " ✓ Bidirectional ban enforcement" - echo " ✓ Source device ban check (prevents banned devices from initiating connections)" - echo " ✓ Target device ban check (prevents connections to banned devices)" - echo " ✓ Real-time ban database sync" - echo "" -} - -clone_rustdesk_server() { - # This function is no longer needed - using precompiled binaries - print_info "Using precompiled binaries - skipping source clone" -} - -apply_patches() { - # This function is no longer needed - binaries are pre-patched - print_info "Binaries are pre-patched - skipping patch application" -} - -compile_hbbs() { - # This function is no longer needed - using precompiled binaries - print_info "Using precompiled binaries - skipping compilation" -} - -install_hbbs() { - # This function has been replaced by install_binaries() - # Kept for compatibility but redirects to new function - print_info "Redirecting to install_binaries()..." -} - -run_database_migrations() { - print_header "Running Database Migrations" - - local db_path="$RUSTDESK_DIR/db_v2.sqlite3" - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local migrations_dir="$script_dir/migrations" - - # Check if database exists - if [ ! -f "$db_path" ]; then - print_warning "Database not found at $db_path" - print_info "Database will be created automatically when HBBS starts" - print_info "Skipping migrations - they will be applied on first run" - return 0 - fi - - # Create backup of database - local backup_file="$db_path.backup-$(date +%Y%m%d-%H%M%S)" - print_info "Creating database backup..." - cp "$db_path" "$backup_file" - print_success "Database backed up to: $backup_file" - - # Check if migrations directory exists - if [ ! -d "$migrations_dir" ]; then - print_error "Migrations directory not found: $migrations_dir" - exit 1 - fi - - # Run v1.0.1 migration (soft delete) - print_info "Running migration v1.0.1 (soft delete)..." - if python3 "$migrations_dir/v1.0.1_soft_delete.py"; then - print_success "Migration v1.0.1 completed successfully" - else - print_warning "Migration v1.0.1 failed or already applied" - fi - - # Run v1.1.0 migration (device bans) - print_info "Running migration v1.1.0 (device bans)..." - if python3 "$migrations_dir/v1.1.0_device_bans.py"; then - print_success "Migration v1.1.0 completed successfully" - else - print_warning "Migration v1.1.0 failed or already applied" - fi - - print_success "Database migrations completed" - echo "" -} - -install_web_console() { - print_header "Installing Web Management Console" - - local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local web_dir="$script_dir/web" - - if [ ! -d "$web_dir" ]; then - print_error "Web directory not found: $web_dir" - exit 1 - fi - - # Create console directory - mkdir -p "$CONSOLE_DIR" - - # Copy files - print_info "Copying web console files..." - cp -r "$web_dir"/* "$CONSOLE_DIR/" - - # Install Python dependencies - print_info "Installing Python dependencies..." - pip3 install -r "$CONSOLE_DIR/requirements.txt" - - # Create systemd service - print_info "Creating systemd service..." - cat > /etc/systemd/system/betterdesk.service < /dev/null; then - print_success "Web Console is accessible" - else - print_error "Web Console is not accessible" - fi -} - -cleanup() { - print_header "Cleaning Up" - - print_info "Removing temporary files..." - rm -rf "$TEMP_DIR" - - print_success "Cleanup completed" -} - -show_summary() { - print_header "Installation Complete!" - - echo -e "${GREEN}BetterDesk Console has been successfully installed!${NC}" - echo "" - echo "Access points:" - echo " • Web Console: http://$(hostname -I | awk '{print $1}'):5000" - echo " • HBBS API: http://localhost:$HBBS_API_PORT/api/health" - echo "" - echo "Services:" - echo " • HBBS: sudo systemctl status rustdesksignal.service" - echo " • Web Console: sudo systemctl status betterdesk.service" - echo "" - - if [ -d "$BACKUP_DIR" ]; then - echo "Backup location:" - echo " • $BACKUP_DIR" - echo "" - fi - - echo "Documentation:" - echo " • README.md in the installation directory" - echo " • GitHub: https://github.com/UNITRONIX/Rustdesk-FreeConsole" - echo "" - - print_info "Enjoy your enhanced RustDesk experience!" -} - -# Main installation flow -main() { - clear - print_header "BetterDesk Console Installer $VERSION" - echo "This script will install:" - echo " • Enhanced RustDesk HBBS/HBBR with bidirectional ban enforcement" - echo " • Web Management Console with Material Design" - echo " • Real-time device status monitoring" - echo "" - echo "Installation method: Precompiled binaries (no compilation required)" - echo "" - - check_root - check_dependencies - backup_rustdesk - install_binaries - run_database_migrations - install_web_console - test_installation - cleanup - show_summary -} - -# Run main function -main "$@" diff --git a/migrations/v1.4.0_auth_system.py b/migrations/v1.4.0_auth_system.py new file mode 100644 index 00000000..6f8ebc7c --- /dev/null +++ b/migrations/v1.4.0_auth_system.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +""" +Database Migration Script v1.4.0 - Authentication System +Adds user management, sessions, and audit logging to BetterDesk Console +""" + +import sqlite3 +import sys +import os +import secrets +import bcrypt +from datetime import datetime + +DB_PATH = '/opt/rustdesk/db_v2.sqlite3' +BACKUP_SUFFIX = '.backup-pre-v1.4.0' + +# Default admin credentials +DEFAULT_ADMIN_USERNAME = 'admin' +DEFAULT_ADMIN_PASSWORD = secrets.token_urlsafe(12) # Random password + + +def backup_database(): + """Create backup of database before migration""" + backup_path = DB_PATH + BACKUP_SUFFIX + + if os.path.exists(backup_path): + print(f"⚠️ Backup already exists: {backup_path}") + response = input("Overwrite? [y/N]: ").strip().lower() + if response != 'y': + print("❌ Migration cancelled") + sys.exit(1) + + print(f"📦 Creating backup: {backup_path}") + + # Copy database file + import shutil + shutil.copy2(DB_PATH, backup_path) + + print(f"✅ Backup created successfully") + return backup_path + + +def check_if_migration_needed(conn): + """Check if migration was already applied""" + cursor = conn.cursor() + + # Check if users table exists + cursor.execute(""" + SELECT name FROM sqlite_master + WHERE type='table' AND name='users' + """) + + if cursor.fetchone(): + print("ℹ️ Migration already applied (users table exists)") + return False + + return True + + +def apply_migration(conn): + """Apply migration SQL""" + cursor = conn.cursor() + + print("🔧 Creating users table...") + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username VARCHAR(50) UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + role VARCHAR(20) NOT NULL DEFAULT 'viewer', + created_at DATETIME NOT NULL, + last_login DATETIME, + is_active BOOLEAN NOT NULL DEFAULT 1, + CHECK (role IN ('admin', 'operator', 'viewer')) + ) + ''') + + print("🔧 Creating sessions table...") + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sessions ( + token VARCHAR(64) PRIMARY KEY, + user_id INTEGER NOT NULL, + created_at DATETIME NOT NULL, + expires_at DATETIME NOT NULL, + last_activity DATETIME NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ) + ''') + + print("🔧 Creating audit_log table...") + cursor.execute(''' + CREATE TABLE IF NOT EXISTS audit_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + action VARCHAR(50) NOT NULL, + device_id VARCHAR(100), + details TEXT, + ip_address VARCHAR(50), + timestamp DATETIME NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL + ) + ''') + + print("🔧 Creating indexes...") + cursor.execute('CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_audit_device ON audit_log(device_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp)') + + conn.commit() + print("✅ Database schema updated") + + +def create_default_admin(conn): + """Create default admin user""" + cursor = conn.cursor() + + # Check if admin already exists + cursor.execute("SELECT id FROM users WHERE username = ?", (DEFAULT_ADMIN_USERNAME,)) + if cursor.fetchone(): + print(f"ℹ️ Admin user '{DEFAULT_ADMIN_USERNAME}' already exists") + return None + + print(f"👤 Creating default admin user...") + + # Hash password + salt = bcrypt.gensalt() + password_hash = bcrypt.hashpw(DEFAULT_ADMIN_PASSWORD.encode('utf-8'), salt).decode('utf-8') + + # Insert admin user + cursor.execute(''' + INSERT INTO users (username, password_hash, role, created_at, is_active) + VALUES (?, ?, 'admin', ?, 1) + ''', (DEFAULT_ADMIN_USERNAME, password_hash, datetime.now())) + + conn.commit() + + print(f"✅ Default admin user created") + return DEFAULT_ADMIN_PASSWORD + + +def save_credentials(password): + """Save admin credentials to file""" + creds_file = '/opt/BetterDeskConsole/admin_credentials.txt' + + try: + os.makedirs(os.path.dirname(creds_file), exist_ok=True) + + with open(creds_file, 'w') as f: + f.write("=" * 60 + "\n") + f.write("BetterDesk Console - Default Admin Credentials\n") + f.write("=" * 60 + "\n\n") + f.write(f"Username: {DEFAULT_ADMIN_USERNAME}\n") + f.write(f"Password: {password}\n\n") + f.write("⚠️ IMPORTANT: Change this password immediately after first login!\n") + f.write("=" * 60 + "\n") + f.write(f"Created: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + + os.chmod(creds_file, 0o600) # Read/write for owner only + + print(f"📝 Credentials saved to: {creds_file}") + + except Exception as e: + print(f"⚠️ Could not save credentials file: {e}") + + +def main(): + print("=" * 60) + print("BetterDesk Console - Database Migration v1.4.0") + print("Adding Authentication System") + print("=" * 60) + print() + + # Check if database exists + if not os.path.exists(DB_PATH): + print(f"❌ Database not found: {DB_PATH}") + print(" Please run the installer first") + sys.exit(1) + + # Create backup + backup_path = backup_database() + + try: + # Connect to database + print(f"🔌 Connecting to database: {DB_PATH}") + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + + # Check if migration needed + if not check_if_migration_needed(conn): + conn.close() + print("\n✅ Database is already up to date") + sys.exit(0) + + # Apply migration + print("\n🚀 Starting migration...") + apply_migration(conn) + + # Create default admin + admin_password = create_default_admin(conn) + + conn.close() + + print("\n" + "=" * 60) + print("✅ Migration completed successfully!") + print("=" * 60) + + if admin_password: + print("\n🔐 DEFAULT ADMIN CREDENTIALS:") + print("=" * 60) + print(f" Username: {DEFAULT_ADMIN_USERNAME}") + print(f" Password: {admin_password}") + print("=" * 60) + print("\n⚠️ IMPORTANT:") + print(" 1. Save these credentials in a secure location") + print(" 2. Change the password immediately after first login") + print(" 3. Delete /opt/BetterDeskConsole/admin_credentials.txt after saving") + print() + + save_credentials(admin_password) + + print(f"\n📦 Backup location: {backup_path}") + print(" Keep this backup until you verify everything works correctly") + print() + + except Exception as e: + print(f"\n❌ Migration failed: {e}") + print(f"\n🔄 Restoring from backup...") + + try: + conn.close() + except: + pass + + # Restore backup + import shutil + shutil.copy2(backup_path, DB_PATH) + + print(f"✅ Database restored from backup") + print(f" Original backup preserved at: {backup_path}") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/repair-keys.sh b/repair-keys.sh deleted file mode 100644 index f2204040..00000000 --- a/repair-keys.sh +++ /dev/null @@ -1,456 +0,0 @@ -#!/bin/bash - -############################################################################# -# RustDesk Key Repair Tool -# -# This script helps diagnose and fix key-related issues in RustDesk -# installations. Use it when experiencing "Key mismatch" errors. -# -# Author: UNITRONIX -# License: MIT -############################################################################# - -set -e - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -RUSTDESK_DIR="/opt/rustdesk" - -print_header() { - echo -e "\n${BLUE}========================================${NC}" - echo -e "${BLUE}$1${NC}" - echo -e "${BLUE}========================================${NC}\n" -} - -print_success() { echo -e "${GREEN}✓ $1${NC}"; } -print_error() { echo -e "${RED}✗ $1${NC}"; } -print_warning() { echo -e "${YELLOW}⚠ $1${NC}"; } -print_info() { echo -e "${BLUE}ℹ $1${NC}"; } - -check_root() { - if [[ $EUID -ne 0 ]]; then - print_error "This script must be run as root (use sudo)" - exit 1 - fi -} - -detect_rustdesk_directory() { - if [ ! -d "$RUSTDESK_DIR" ]; then - print_warning "Default RustDesk directory not found: $RUSTDESK_DIR" - read -p "Enter your RustDesk installation directory: " custom_dir - if [ -d "$custom_dir" ]; then - RUSTDESK_DIR="$custom_dir" - print_success "Using directory: $RUSTDESK_DIR" - else - print_error "Directory not found: $custom_dir" - exit 1 - fi - else - print_success "Found RustDesk directory: $RUSTDESK_DIR" - fi -} - -show_key_info() { - print_header "Current Key Information" - - echo "📁 Directory: $RUSTDESK_DIR" - echo "" - - # List all key files with details - if ls "$RUSTDESK_DIR"/*.pub &>/dev/null; then - echo "🔑 Public key files (.pub):" - for pubfile in "$RUSTDESK_DIR"/*.pub; do - local size=$(stat -f%z "$pubfile" 2>/dev/null || stat -c%s "$pubfile" 2>/dev/null) - local modified=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$pubfile" 2>/dev/null || stat -c "%y" "$pubfile" 2>/dev/null | cut -d'.' -f1) - echo " ├─ $(basename $pubfile)" - echo " │ ├─ Size: $size bytes" - echo " │ ├─ Modified: $modified" - echo " │ └─ Content:" - cat "$pubfile" | sed 's/^/ │ /' - echo "" - done - else - print_warning "No .pub files found!" - echo "" - fi - - # Check for private keys - if ls "$RUSTDESK_DIR"/id_* 2>/dev/null | grep -v ".pub" &>/dev/null; then - echo "🔐 Private key files:" - for keyfile in "$RUSTDESK_DIR"/id_*; do - if [[ ! "$keyfile" =~ \.pub$ ]] && [ -f "$keyfile" ]; then - local size=$(stat -f%z "$keyfile" 2>/dev/null || stat -c%s "$keyfile" 2>/dev/null) - local perms=$(stat -f "%Sp" "$keyfile" 2>/dev/null || stat -c "%a" "$keyfile" 2>/dev/null) - echo " ├─ $(basename $keyfile)" - echo " │ ├─ Size: $size bytes" - echo " │ └─ Permissions: $perms" - fi - done - echo "" - fi - - # Check backups - if ls "$RUSTDESK_DIR"/*.backup* &>/dev/null || ls "$RUSTDESK_DIR"/*-backup-* &>/dev/null; then - echo "💾 Backup files found:" - ls -lh "$RUSTDESK_DIR"/*.backup* "$RUSTDESK_DIR"/*-backup-* 2>/dev/null | awk '{print " ├─ " $9 " (" $5 ")"}' - echo "" - fi - - # Check for backup directories - if ls -d /opt/rustdesk-backup-* &>/dev/null; then - echo "📦 Backup directories:" - for backup_dir in /opt/rustdesk-backup-*; do - echo " ├─ $backup_dir" - if ls "$backup_dir"/*.pub &>/dev/null; then - echo " │ └─ Contains .pub files ✓" - fi - done - echo "" - fi -} - -verify_key_permissions() { - print_header "Verifying Key Permissions" - - local fixed=0 - - # Fix private key permissions (should be 600) - for keyfile in "$RUSTDESK_DIR"/id_*; do - if [[ ! "$keyfile" =~ \.pub$ ]] && [ -f "$keyfile" ]; then - local current_perms=$(stat -f "%Sp" "$keyfile" 2>/dev/null || stat -c "%a" "$keyfile" 2>/dev/null) - if [ "$current_perms" != "600" ] && [ "$current_perms" != "-rw-------" ]; then - print_warning "Fixing permissions for $(basename $keyfile): $current_perms → 600" - chmod 600 "$keyfile" - ((fixed++)) - else - print_success "$(basename $keyfile): Permissions OK ($current_perms)" - fi - fi - done - - # Fix public key permissions (should be 644) - for pubfile in "$RUSTDESK_DIR"/*.pub; do - if [ -f "$pubfile" ]; then - local current_perms=$(stat -f "%Sp" "$pubfile" 2>/dev/null || stat -c "%a" "$pubfile" 2>/dev/null) - if [ "$current_perms" != "644" ] && [ "$current_perms" != "-rw-r--r--" ]; then - print_warning "Fixing permissions for $(basename $pubfile): $current_perms → 644" - chmod 644 "$pubfile" - ((fixed++)) - else - print_success "$(basename $pubfile): Permissions OK ($current_perms)" - fi - fi - done - - if [ $fixed -gt 0 ]; then - print_success "Fixed permissions for $fixed file(s)" - else - print_success "All permissions are correct" - fi -} - -export_public_key() { - print_header "Export Public Key" - - local pub_files=("$RUSTDESK_DIR"/*.pub) - - if [ ! -f "${pub_files[0]}" ]; then - print_error "No public key files found!" - return 1 - fi - - echo "Available public keys:" - local i=1 - for pubfile in "$RUSTDESK_DIR"/*.pub; do - if [ -f "$pubfile" ]; then - echo " $i) $(basename $pubfile)" - ((i++)) - fi - done - echo "" - - if [ $i -eq 2 ]; then - # Only one file, use it automatically - pubfile="${pub_files[0]}" - print_info "Using: $(basename $pubfile)" - else - read -p "Select key to export [1-$((i-1))]: " choice - pubfile="${pub_files[$((choice-1))]}" - fi - - if [ ! -f "$pubfile" ]; then - print_error "Invalid selection" - return 1 - fi - - echo "" - echo "════════════════════════════════════════════════════════════════" - echo "PUBLIC KEY (copy this to RustDesk clients):" - echo "════════════════════════════════════════════════════════════════" - cat "$pubfile" - echo "════════════════════════════════════════════════════════════════" - echo "" - - # Offer to save to file - read -p "Save to file? [y/N]: " save_choice - if [[ "$save_choice" =~ ^[Yy]$ ]]; then - local export_file="$HOME/rustdesk_public_key_$(date +%Y%m%d_%H%M%S).txt" - cat "$pubfile" > "$export_file" - print_success "Saved to: $export_file" - fi -} - -regenerate_keys() { - print_header "Regenerate Keys" - - echo -e "${RED}⚠️ WARNING: REGENERATING KEYS WILL BREAK ALL CLIENT CONNECTIONS ⚠️${NC}" - echo "" - echo "After regeneration, you MUST:" - echo " 1. Stop and restart RustDesk services" - echo " 2. Update ALL client configurations with new public key" - echo " 3. Reconfigure each RustDesk client individually" - echo "" - echo "Impact:" - echo " • All existing clients will be unable to connect" - echo " • 'Key mismatch' errors will appear on all devices" - echo " • Manual reconfiguration required for each client" - echo "" - read -p "Are you ABSOLUTELY SURE? Type 'REGENERATE' to confirm: " confirm - - if [ "$confirm" != "REGENERATE" ]; then - print_info "Operation cancelled" - return 0 - fi - - # Backup existing keys - print_info "Creating backup of existing keys..." - local backup_suffix="pre-regenerate-$(date +%Y%m%d-%H%M%S)" - - for keyfile in "$RUSTDESK_DIR"/id_ed25519*; do - if [ -f "$keyfile" ]; then - cp "$keyfile" "$keyfile.$backup_suffix" - print_success "Backed up: $(basename $keyfile)" - fi - done - - # Stop services - print_info "Stopping RustDesk services..." - systemctl stop rustdesksignal.service 2>/dev/null || true - systemctl stop rustdeskrelay.service 2>/dev/null || true - sleep 2 - - # Remove old keys - print_info "Removing old keys..." - rm -f "$RUSTDESK_DIR/id_ed25519" "$RUSTDESK_DIR/id_ed25519.pub" - - # Generate new keys - print_info "Generating new ED25519 key pair..." - if command -v ssh-keygen &>/dev/null; then - ssh-keygen -t ed25519 -f "$RUSTDESK_DIR/id_ed25519" -N "" -C "rustdesk-server-$(date +%Y%m%d)" - - if [ -f "$RUSTDESK_DIR/id_ed25519.pub" ]; then - print_success "✓ New keys generated successfully!" - echo "" - echo "════════════════════════════════════════════════════════════════" - echo "NEW PUBLIC KEY (configure this in ALL RustDesk clients):" - echo "════════════════════════════════════════════════════════════════" - cat "$RUSTDESK_DIR/id_ed25519.pub" - echo "════════════════════════════════════════════════════════════════" - echo "" - - # Save to file - local key_file="$HOME/NEW_RUSTDESK_KEY_$(date +%Y%m%d_%H%M%S).txt" - cat "$RUSTDESK_DIR/id_ed25519.pub" > "$key_file" - print_success "Key saved to: $key_file" - - # Fix permissions - chmod 600 "$RUSTDESK_DIR/id_ed25519" - chmod 644 "$RUSTDESK_DIR/id_ed25519.pub" - - # Restart services - print_info "Starting RustDesk services..." - systemctl start rustdesksignal.service - systemctl start rustdeskrelay.service - sleep 2 - - if systemctl is-active --quiet rustdesksignal.service; then - print_success "✓ Services restarted successfully" - else - print_error "Failed to restart services - check logs" - fi - else - print_error "Failed to generate keys" - return 1 - fi - else - print_error "ssh-keygen not found - cannot generate keys" - return 1 - fi - - echo "" - print_warning "⚠️ NEXT STEPS:" - echo " 1. Copy the new public key above" - echo " 2. Open RustDesk on each client device" - echo " 3. Go to Settings → ID/Relay Server" - echo " 4. Paste the new public key" - echo " 5. Save and test connection" -} - -restore_from_backup() { - print_header "Restore Keys from Backup" - - # Find backups - local backups=($(ls -d /opt/rustdesk-backup-* 2>/dev/null)) - local key_backups=($(ls "$RUSTDESK_DIR"/*.backup* "$RUSTDESK_DIR"/*-backup-* 2>/dev/null)) - - if [ ${#backups[@]} -eq 0 ] && [ ${#key_backups[@]} -eq 0 ]; then - print_error "No backups found!" - echo "" - echo "Checked locations:" - echo " • /opt/rustdesk-backup-*" - echo " • $RUSTDESK_DIR/*.backup*" - return 1 - fi - - echo "Available backups:" - echo "" - - local options=() - local i=1 - - # List directory backups - for backup in "${backups[@]}"; do - if [ -d "$backup" ]; then - echo " $i) Directory backup: $(basename $backup)" - if ls "$backup"/*.pub &>/dev/null; then - echo " Contains: $(ls "$backup"/*.pub | wc -l) public key file(s)" - fi - options+=("$backup") - ((i++)) - fi - done - - # List key file backups - for backup in "${key_backups[@]}"; do - if [ -f "$backup" ]; then - echo " $i) Key file: $(basename $backup)" - options+=("$backup") - ((i++)) - fi - done - - echo "" - read -p "Select backup to restore [1-$((i-1))]: " choice - - if [ $choice -lt 1 ] || [ $choice -ge $i ]; then - print_error "Invalid selection" - return 1 - fi - - local selected="${options[$((choice-1))]}" - - echo "" - print_warning "This will restore keys from:" - echo " $selected" - read -p "Continue? [y/N]: " confirm - - if [[ ! "$confirm" =~ ^[Yy]$ ]]; then - print_info "Operation cancelled" - return 0 - fi - - # Stop services - print_info "Stopping services..." - systemctl stop rustdesksignal.service 2>/dev/null || true - systemctl stop rustdeskrelay.service 2>/dev/null || true - sleep 2 - - # Restore - if [ -d "$selected" ]; then - # Restore from directory - print_info "Restoring from directory backup..." - cp "$selected"/id_ed25519* "$RUSTDESK_DIR/" 2>/dev/null || true - cp "$selected"/*.pub "$RUSTDESK_DIR/" 2>/dev/null || true - else - # Restore individual file - print_info "Restoring key file..." - local original_name=$(echo "$selected" | sed 's/\.[^.]*$//' | sed 's/-backup-[0-9]*$//') - cp "$selected" "$original_name" - fi - - # Fix permissions - chmod 600 "$RUSTDESK_DIR"/id_ed25519 2>/dev/null || true - chmod 644 "$RUSTDESK_DIR"/*.pub 2>/dev/null || true - - # Restart services - print_info "Starting services..." - systemctl start rustdesksignal.service - systemctl start rustdeskrelay.service - sleep 2 - - if systemctl is-active --quiet rustdesksignal.service; then - print_success "✓ Keys restored and services restarted!" - echo "" - echo "Current public key:" - cat "$RUSTDESK_DIR"/*.pub 2>/dev/null | head -1 - else - print_error "Failed to restart services - check logs" - fi -} - -# Main menu -main() { - clear - print_header "🔧 RustDesk Key Repair Tool" - - check_root - detect_rustdesk_directory - - echo "" - echo "What would you like to do?" - echo "" - echo " 1) 📋 Show current key information" - echo " 2) 🔐 Verify and fix key permissions" - echo " 3) 📤 Export public key" - echo " 4) 🔄 Regenerate keys (⚠️ BREAKS existing connections)" - echo " 5) 💾 Restore keys from backup" - echo " 6) 🚪 Exit" - echo "" - read -p "Choose option [1-6]: " choice - - case $choice in - 1) - show_key_info - ;; - 2) - verify_key_permissions - ;; - 3) - export_public_key - ;; - 4) - regenerate_keys - ;; - 5) - restore_from_backup - ;; - 6) - print_info "Goodbye!" - exit 0 - ;; - *) - print_error "Invalid option" - exit 1 - ;; - esac - - echo "" - read -p "Press ENTER to exit..." -} - -main "$@" diff --git a/screenshots/dashboard.png b/screenshots/dashboard.png index 0798c6c5..900a6ca6 100644 Binary files a/screenshots/dashboard.png and b/screenshots/dashboard.png differ diff --git a/screenshots/device-details.png b/screenshots/device-details.png index 115a1adf..b5656612 100644 Binary files a/screenshots/device-details.png and b/screenshots/device-details.png differ diff --git a/screenshots/devices-list.png b/screenshots/devices-list.png index f218dc67..4b659f23 100644 Binary files a/screenshots/devices-list.png and b/screenshots/devices-list.png differ diff --git a/screenshots/mobile-view.png b/screenshots/mobile-view.png deleted file mode 100644 index 5fc069ce..00000000 Binary files a/screenshots/mobile-view.png and /dev/null differ diff --git a/update-to-v1.4.0.sh b/update-to-v1.4.0.sh new file mode 100644 index 00000000..b18072af --- /dev/null +++ b/update-to-v1.4.0.sh @@ -0,0 +1,460 @@ +#!/bin/bash + +############################################################################# +# BetterDesk Console - Update Script to v1.4.0 +# +# This script updates existing BetterDesk Console installation to v1.4.0 +# with authentication system, sidebar menu, and security improvements. +# +# Features: +# - Automatic version detection +# - Safe database migration with backup +# - Updates web console files +# - Installs new dependencies (bcrypt, markupsafe) +# - Preserves existing configuration +# - Creates default admin user +# +# Author: GitHub Copilot + UNITRONIX +# License: MIT +############################################################################# + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Configuration +CONSOLE_DIR="/opt/BetterDeskConsole" +WEB_DIR="$CONSOLE_DIR/web" +BACKUP_DIR="/opt/betterdesk-backup-$(date +%Y%m%d-%H%M%S)" +CURRENT_VERSION_FILE="$CONSOLE_DIR/VERSION" +TARGET_VERSION="1.4.0" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +print_header() { + echo -e "\n${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}\n" +} + +print_success() { echo -e "${GREEN}✓ $1${NC}"; } +print_error() { echo -e "${RED}✗ $1${NC}"; } +print_warning() { echo -e "${YELLOW}⚠ $1${NC}"; } +print_info() { echo -e "${BLUE}ℹ $1${NC}"; } + +check_root() { + if [[ $EUID -ne 0 ]]; then + print_error "This script must be run as root (use sudo)" + exit 1 + fi +} + +detect_current_version() { + if [ -f "$CURRENT_VERSION_FILE" ]; then + CURRENT_VERSION=$(cat "$CURRENT_VERSION_FILE") + print_info "Current version: $CURRENT_VERSION" + return 0 + fi + + # Try to detect from files + if [ -f "$WEB_DIR/app.py" ]; then + if grep -q "require_auth" "$WEB_DIR/app.py"; then + CURRENT_VERSION="1.4.0+" + elif grep -q "is_banned" "$WEB_DIR/app.py"; then + CURRENT_VERSION="1.3.0" + else + CURRENT_VERSION="1.2.0 or older" + fi + print_warning "Version file not found. Detected: $CURRENT_VERSION (approximate)" + return 0 + fi + + print_error "Could not detect current version" + return 1 +} + +check_if_update_needed() { + if [ "$CURRENT_VERSION" == "$TARGET_VERSION" ]; then + print_info "Already running version $TARGET_VERSION" + echo "" + read -p "Force re-install? [y/N]: " force + if [[ ! "$force" =~ ^[Yy]$ ]]; then + exit 0 + fi + fi + + print_success "Update needed: $CURRENT_VERSION → $TARGET_VERSION" +} + +create_backup() { + print_header "Creating Backup" + + mkdir -p "$BACKUP_DIR" + + # Backup web console + if [ -d "$CONSOLE_DIR" ]; then + print_info "Backing up web console..." + cp -r "$CONSOLE_DIR" "$BACKUP_DIR/BetterDeskConsole" + print_success "Web console backed up" + fi + + # Backup database + if [ -f "/opt/rustdesk/db_v2.sqlite3" ]; then + print_info "Backing up database..." + cp "/opt/rustdesk/db_v2.sqlite3" "$BACKUP_DIR/db_v2.sqlite3" + print_success "Database backed up" + fi + + print_success "Backup created at: $BACKUP_DIR" +} + +install_dependencies() { + print_header "Installing Dependencies" + + # Detect Python environment + PIP_EXTRA_ARGS="" + if [ -f "/etc/debian_version" ] && python3 -c "import sys; exit(0 if sys.version_info >= (3,11) else 1)" 2>/dev/null; then + PIP_EXTRA_ARGS="--break-system-packages" + fi + + # Install bcrypt + if python3 -c "import bcrypt" 2>/dev/null; then + print_success "bcrypt already installed" + else + print_info "Installing bcrypt..." + pip3 install bcrypt $PIP_EXTRA_ARGS + print_success "bcrypt installed" + fi + + # Install markupsafe + if python3 -c "import markupsafe" 2>/dev/null; then + print_success "markupsafe already installed" + else + print_info "Installing markupsafe..." + pip3 install markupsafe $PIP_EXTRA_ARGS + print_success "markupsafe installed" + fi + + # Install Flask-WTF for CSRF protection + if python3 -c "import flask_wtf" 2>/dev/null; then + print_success "Flask-WTF already installed" + else + print_info "Installing Flask-WTF..." + pip3 install Flask-WTF $PIP_EXTRA_ARGS + print_success "Flask-WTF installed" + fi + + # Install Flask-Limiter for rate limiting + if python3 -c "import flask_limiter" 2>/dev/null; then + print_success "Flask-Limiter already installed" + else + print_info "Installing Flask-Limiter..." + pip3 install Flask-Limiter $PIP_EXTRA_ARGS + print_success "Flask-Limiter installed" + fi +} + +update_web_files() { + print_header "Updating Web Console Files" + + # Stop service if running + if systemctl is-active --quiet betterdesk 2>/dev/null; then + print_info "Stopping BetterDesk service..." + systemctl stop betterdesk + fi + + # Create directories + mkdir -p "$WEB_DIR/templates" + mkdir -p "$WEB_DIR/static" + + # Copy new files + print_info "Copying authentication module..." + cp "$SCRIPT_DIR/web/auth.py" "$WEB_DIR/" + + print_info "Copying updated app.py..." + cp "$SCRIPT_DIR/web/app_v14.py" "$WEB_DIR/app.py" + + print_info "Copying login template..." + cp "$SCRIPT_DIR/web/templates/login.html" "$WEB_DIR/templates/" + + print_info "Copying updated index template..." + cp "$SCRIPT_DIR/web/templates/index_v14.html" "$WEB_DIR/templates/index.html" + + print_info "Copying sidebar styles..." + cp "$SCRIPT_DIR/web/static/sidebar.css" "$WEB_DIR/static/" + + print_info "Copying sidebar JavaScript..." + cp "$SCRIPT_DIR/web/static/sidebar.js" "$WEB_DIR/static/" + + print_info "Copying updated script.js..." + cp "$SCRIPT_DIR/web/static/script_v14.js" "$WEB_DIR/static/script.js" + + # Set permissions + chown -R root:root "$WEB_DIR" + chmod 755 "$WEB_DIR" + chmod 644 "$WEB_DIR"/*.py + chmod 644 "$WEB_DIR"/templates/*.html + chmod 644 "$WEB_DIR"/static/* + + print_success "Web files updated" +} + +run_database_migration() { + print_header "Running Database Migration" + + if [ ! -f "$SCRIPT_DIR/migrations/v1.4.0_auth_system.py" ]; then + print_error "Migration script not found!" + exit 1 + fi + + print_info "Running migration v1.4.0..." + python3 "$SCRIPT_DIR/migrations/v1.4.0_auth_system.py" + + if [ $? -eq 0 ]; then + print_success "Database migration completed" + + # Show admin credentials if generated + if [ -f "$CONSOLE_DIR/admin_credentials.txt" ]; then + echo "" + print_warning "=" * 60 + print_warning "DEFAULT ADMIN CREDENTIALS GENERATED!" + print_warning "=" * 60 + cat "$CONSOLE_DIR/admin_credentials.txt" + echo "" + print_warning "⚠️ IMPORTANT: Change the password immediately after first login!" + print_warning "⚠️ Delete this file after saving credentials: $CONSOLE_DIR/admin_credentials.txt" + fi + else + print_error "Database migration failed!" + print_info "Check the error messages above" + print_info "Your backup is at: $BACKUP_DIR" + exit 1 + fi +} + +update_version_file() { + echo "$TARGET_VERSION" > "$CURRENT_VERSION_FILE" + print_success "Version file updated to $TARGET_VERSION" +} + +configure_api_security() { + print_header "Configuring API Security" + + # Detect RustDesk directory + RUSTDESK_DIR="/opt/rustdesk" + if [ ! -d "$RUSTDESK_DIR" ]; then + print_warning "RustDesk directory not found, trying alternate locations..." + if [ -d "/var/lib/rustdesk" ]; then + RUSTDESK_DIR="/var/lib/rustdesk" + else + print_error "Could not find RustDesk installation directory" + return 1 + fi + fi + + API_KEY_FILE="$RUSTDESK_DIR/.api_key" + + # Generate API key if it doesn't exist + if [ -f "$API_KEY_FILE" ]; then + print_info "API key already exists, keeping existing key" + API_KEY=$(cat "$API_KEY_FILE") + else + print_info "Generating new API key..." + API_KEY=$(openssl rand -base64 48 | tr -d '/+=' | cut -c1-64) + echo -n "$API_KEY" > "$API_KEY_FILE" + chmod 600 "$API_KEY_FILE" + print_success "API key generated and saved to $API_KEY_FILE" + fi + + # Update betterdesk.service with API key environment variable + SERVICE_FILE="/etc/systemd/system/betterdesk.service" + if [ -f "$SERVICE_FILE" ]; then + # Check if HBBS_API_KEY is already configured + if grep -q "HBBS_API_KEY" "$SERVICE_FILE"; then + print_info "Service file already contains HBBS_API_KEY" + else + print_info "Adding HBBS_API_KEY to betterdesk.service..." + + # Add environment variable after [Service] section + sed -i "/^\[Service\]/a Environment=\"HBBS_API_KEY=$API_KEY\"" "$SERVICE_FILE" + + # Also ensure Flask is configured for LAN access + if ! grep -q "FLASK_HOST=0.0.0.0" "$SERVICE_FILE"; then + sed -i "/^\[Service\]/a Environment=\"FLASK_HOST=0.0.0.0\"" "$SERVICE_FILE" + sed -i "/^\[Service\]/a Environment=\"FLASK_PORT=5000\"" "$SERVICE_FILE" + sed -i "/^\[Service\]/a Environment=\"FLASK_DEBUG=False\"" "$SERVICE_FILE" + fi + + systemctl daemon-reload + print_success "Service file updated with API security configuration" + fi + else + print_warning "betterdesk.service not found - skipping service configuration" + fi + + # Update rustdesksignal.service to bind API to LAN (0.0.0.0) + HBBS_SERVICE="/etc/systemd/system/rustdesksignal.service" + if [ -f "$HBBS_SERVICE" ]; then + # Check if API is already configured for LAN access + if grep -q "\-\-api-port" "$HBBS_SERVICE"; then + print_info "HBBS API port already configured" + + # Check if binding to 0.0.0.0 + if grep -q "0.0.0.0" "$HBBS_SERVICE"; then + print_info "HBBS API already configured for LAN access" + else + print_warning "HBBS API found but may be localhost-only" + print_info "Note: New HBBS binaries bind to 0.0.0.0 by default with API key authentication" + fi + fi + fi + + print_success "API security configuration complete" + echo "" + print_info "Security notes:" + echo " • API key stored in: $API_KEY_FILE" + echo " • Web console uses API key for HBBS requests" + echo " • HBBS API accessible on LAN with X-API-Key header authentication" + echo " • Web console accessible on: http://$(hostname -I | awk '{print $1}'):5000" +} + +restart_services() { + print_header "Restarting Services" + + # Restart BetterDesk web console + if [ -f "/etc/systemd/system/betterdesk.service" ]; then + print_info "Restarting BetterDesk service..." + systemctl daemon-reload + systemctl restart betterdesk + systemctl status betterdesk --no-pager + print_success "BetterDesk service restarted" + else + print_warning "BetterDesk service not found (manual start may be needed)" + fi +} + +show_completion_message() { + print_header "Update Complete!" + + echo -e "${GREEN}✅ BetterDesk Console has been updated to v$TARGET_VERSION${NC}" + echo "" + echo "New Features:" + echo " • User authentication with login system" + echo " • Role-based access control (admin/operator/viewer)" + echo " • Sidebar navigation menu with Users management" + echo " • Password-protected public key access" + echo " • Audit logging" + echo " • API key authentication for HBBS API" + echo " • LAN access for web console and API" + echo " • Enhanced security across all components" + echo "" + echo "Security Enhancements:" + echo " • Fail-closed policy for ban checks (HIGH)" + echo " • CSRF protection with Flask-WTF" + echo " • Rate limiting on login (5 per minute)" + echo " • Password requirements: 8+ chars, letters + numbers" + echo " • Content Security Policy headers" + echo " • Secure audit logging" + echo "" + echo "Access your console:" + echo " • Web Console: http://$(hostname -I | awk '{print $1}'):5000" + echo " • HBBS API: http://$(hostname -I | awk '{print $1}'):21120/api/health" + echo " • See admin credentials above (if new installation)" + echo "" + echo "Security:" + echo " • API key: /opt/rustdesk/.api_key" + echo " • All API requests now require X-API-Key header" + echo "" + echo "Backup location: $BACKUP_DIR" + echo "Keep this backup until you verify everything works correctly!" + echo "" + + if [ -f "$CONSOLE_DIR/admin_credentials.txt" ]; then + echo -e "${YELLOW}⚠️ Don't forget to:${NC}" + echo " 1. Login with default credentials" + echo " 2. Change the admin password" + echo " 3. Delete $CONSOLE_DIR/admin_credentials.txt" + echo "" + fi +} + +rollback() { + print_error "Update failed! Rolling back..." + + if [ -d "$BACKUP_DIR/BetterDeskConsole" ]; then + rm -rf "$CONSOLE_DIR" + cp -r "$BACKUP_DIR/BetterDeskConsole" "$CONSOLE_DIR" + print_success "Web console restored from backup" + fi + + if [ -f "$BACKUP_DIR/db_v2.sqlite3" ]; then + cp "$BACKUP_DIR/db_v2.sqlite3" "/opt/rustdesk/db_v2.sqlite3" + print_success "Database restored from backup" + fi + + # Restart services + if [ -f "/etc/systemd/system/betterdesk.service" ]; then + systemctl restart betterdesk + fi + + print_info "Rollback complete. Your backup is preserved at: $BACKUP_DIR" + exit 1 +} + +# ============================================================================ +# MAIN EXECUTION +# ============================================================================ + +main() { + print_header "BetterDesk Console Update to v$TARGET_VERSION" + + # Set trap for errors + trap rollback ERR + + # Checks + check_root + + # Detect current version + if ! detect_current_version; then + print_error "Installation directory not found: $CONSOLE_DIR" + echo "" + echo "This script updates existing BetterDesk Console installations." + echo "For new installations, use install-improved.sh" + exit 1 + fi + + # Check if update needed + check_if_update_needed + + # Confirm update + echo "" + echo "This will update BetterDesk Console from $CURRENT_VERSION to $TARGET_VERSION" + echo "A backup will be created before making any changes." + echo "" + read -p "Continue with update? [y/N]: " confirm + + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + print_info "Update cancelled by user" + exit 0 + fi + + # Execute update steps + create_backup + install_dependencies + update_web_files + run_database_migration + update_version_file + configure_api_security + restart_services + show_completion_message + + # Disable error trap + trap - ERR +} + +# Run main function +main "$@" diff --git a/web/app.py b/web/app.py deleted file mode 100644 index 02cd3e49..00000000 --- a/web/app.py +++ /dev/null @@ -1,441 +0,0 @@ -from flask import Flask, render_template, request, jsonify -import sqlite3 -from datetime import datetime -import os -import requests -import re - -app = Flask(__name__) - -# Configuration -DB_PATH = '/opt/rustdesk/db_v2.sqlite3' -PUB_KEY_PATH = '/opt/rustdesk/id_ed25519.pub' -# API is on localhost-only port 21120 (not exposed to internet) -HBBS_API_URL = 'http://localhost:21120/api' - -# Validation rules -MAX_NOTE_LENGTH = 500 -MAX_DEVICE_ID_LENGTH = 50 -DEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_-]+$') - -def validate_device_id(device_id): - """Validate device ID format and length.""" - if not device_id: - return False, "Device ID cannot be empty" - if len(device_id) > MAX_DEVICE_ID_LENGTH: - return False, f"Device ID too long (max {MAX_DEVICE_ID_LENGTH} characters)" - if not DEVICE_ID_PATTERN.match(device_id): - return False, "Device ID can only contain letters, numbers, underscores and hyphens" - return True, None - -def validate_note(note): - """Validate note length.""" - if note and len(note) > MAX_NOTE_LENGTH: - return False, f"Note too long (max {MAX_NOTE_LENGTH} characters)" - return True, None - -def sanitize_input(text): - """Basic sanitization of user input.""" - if not text: - return text - # Remove potential XSS patterns - text = text.replace('', '') - text = text.replace('', '') - return text.strip() - -def get_db_connection(): - """Create a read-write connection to the SQLite database.""" - conn = sqlite3.connect(DB_PATH) - conn.row_factory = sqlite3.Row - return conn - -def get_public_key(): - """Read the RustDesk public key from file - scans for any .pub file.""" - try: - # First try default path - if os.path.exists(PUB_KEY_PATH): - with open(PUB_KEY_PATH, 'r') as f: - key_content = f.read().strip() - return f"[id_ed25519.pub] {key_content}" - - # If default doesn't exist, scan for any .pub file in directory - rustdesk_dir = os.path.dirname(PUB_KEY_PATH) - if os.path.exists(rustdesk_dir): - pub_files = [f for f in os.listdir(rustdesk_dir) if f.endswith('.pub')] - if pub_files: - # Use the first .pub file found - pub_file_path = os.path.join(rustdesk_dir, pub_files[0]) - with open(pub_file_path, 'r') as f: - key_content = f.read().strip() - return f"[{pub_files[0]}] {key_content}" - - return "❌ No public key file (.pub) found in RustDesk directory" - except Exception as e: - return f"Error reading key: {str(e)}" - -@app.route('/') -def index(): - """Render the main dashboard page.""" - public_key = get_public_key() - return render_template('index.html', public_key=public_key) - -@app.route('/api/devices', methods=['GET']) -def get_devices(): - """Fetch all devices from the database with online status from HBBS API.""" - try: - # Try to get status from HBBS API - online_ids = set() - api_device_info = {} - try: - response = requests.get(f'{HBBS_API_URL}/peers', timeout=2) - if response.status_code == 200: - api_data = response.json() - if api_data.get('success') and api_data.get('data'): - # Collect online devices and their full info from API - for peer in api_data['data']: - device_id = peer.get('id') - if device_id: - api_device_info[device_id] = peer - if peer.get('online'): - online_ids.add(device_id) - except Exception as e: - print(f"Warning: Could not connect to HBBS API: {e}") - - # Get devices from database - conn = get_db_connection() - cursor = conn.cursor() - cursor.execute(''' - SELECT - guid, - id, - uuid, - pk, - created_at, - user, - status, - note, - info, - is_banned, - banned_at, - banned_by, - ban_reason - FROM peer - WHERE is_deleted = 0 - ORDER BY created_at DESC - ''') - - devices = [] - for row in cursor.fetchall(): - device_id = row['id'] - - # Determine online status - # Use ONLY the API status (same logic as RustDesk desktop client) - # This ensures consistency between web console and desktop client - if device_id in api_device_info: - # Device found in API - use the API's online status - online = api_device_info[device_id].get('online', False) - else: - # Device not found in API - consider it offline - # (If API is unreachable, fall back to database status) - online = row['status'] == 1 if not api_device_info else False - - device = { - 'guid': row['guid'].hex() if row['guid'] else '', - 'id': device_id, - 'uuid': row['uuid'].hex() if row['uuid'] else '', - 'pk': row['pk'].hex() if row['pk'] else '', - 'created_at': row['created_at'], - 'user': row['user'].hex() if row['user'] else '', - 'status': row['status'], - 'online': online, - 'note': row['note'] or '', - 'info': row['info'] or '', - 'is_banned': row['is_banned'] == 1, - 'banned_at': row['banned_at'], - 'banned_by': row['banned_by'] or '', - 'ban_reason': row['ban_reason'] or '' - } - devices.append(device) - - conn.close() - return jsonify({'success': True, 'devices': devices}) - except Exception as e: - return jsonify({'success': False, 'error': str(e)}), 500 - -@app.route('/api/device/', methods=['PUT']) -def update_device(device_id): - """Update a device's note and/or ID.""" - try: - # Validate input device ID - is_valid, error_msg = validate_device_id(device_id) - if not is_valid: - return jsonify({'success': False, 'error': error_msg}), 400 - - data = request.get_json() - if not data: - return jsonify({'success': False, 'error': 'No data provided'}), 400 - - conn = get_db_connection() - cursor = conn.cursor() - - # Build dynamic update query based on provided fields - updates = [] - params = [] - - if 'note' in data: - # Validate and sanitize note - is_valid, error_msg = validate_note(data['note']) - if not is_valid: - conn.close() - return jsonify({'success': False, 'error': error_msg}), 400 - - sanitized_note = sanitize_input(data['note']) - updates.append('note = ?') - params.append(sanitized_note) - - if 'new_id' in data and data['new_id']: - # Validate new device ID - is_valid, error_msg = validate_device_id(data['new_id']) - if not is_valid: - conn.close() - return jsonify({'success': False, 'error': error_msg}), 400 - - # Check if new ID already exists - cursor.execute('SELECT id FROM peer WHERE id = ? AND is_deleted = 0', (data['new_id'],)) - if cursor.fetchone(): - conn.close() - return jsonify({'success': False, 'error': 'Device ID already exists'}), 409 - - updates.append('id = ?') - params.append(data['new_id']) - - if not updates: - conn.close() - return jsonify({'success': False, 'error': 'No fields to update'}), 400 - - # Add updated_at timestamp - updates.append('updated_at = ?') - params.append(int(datetime.now().timestamp() * 1000)) - - params.append(device_id) - query = f"UPDATE peer SET {', '.join(updates)} WHERE id = ? AND is_deleted = 0" - - cursor.execute(query, params) - conn.commit() - affected = cursor.rowcount - conn.close() - - if affected == 0: - return jsonify({'success': False, 'error': 'Device not found or already deleted'}), 404 - - return jsonify({'success': True, 'message': 'Device updated successfully'}) - except sqlite3.IntegrityError as e: - return jsonify({'success': False, 'error': f'Database constraint violation: {str(e)}'}), 409 - except Exception as e: - return jsonify({'success': False, 'error': f'Unexpected error: {str(e)}'}), 500 - -@app.route('/api/device/', methods=['DELETE']) -def delete_device(device_id): - """Soft delete a device from the database.""" - try: - conn = get_db_connection() - cursor = conn.cursor() - - # Soft delete: set is_deleted=1 and timestamp - deleted_at = int(datetime.now().timestamp() * 1000) - cursor.execute( - 'UPDATE peer SET is_deleted = 1, deleted_at = ? WHERE id = ? AND is_deleted = 0', - (deleted_at, device_id) - ) - conn.commit() - affected = cursor.rowcount - conn.close() - - if affected == 0: - return jsonify({'success': False, 'error': 'Device not found or already deleted'}), 404 - - return jsonify({'success': True, 'message': 'Device deleted successfully'}) - except Exception as e: - return jsonify({'success': False, 'error': str(e)}), 500 - -@app.route('/api/stats', methods=['GET']) -def get_stats(): - """Get statistics about the devices.""" - try: - # Try to get online count from HBBS API - online_count = 0 - try: - response = requests.get(f'{HBBS_API_URL}/peers', timeout=2) - if response.status_code == 200: - api_data = response.json() - if api_data.get('success') and api_data.get('data'): - online_count = sum(1 for peer in api_data['data'] if peer.get('online')) - except Exception as e: - print(f"Warning: Could not connect to HBBS API for stats: {e}") - - conn = get_db_connection() - cursor = conn.cursor() - - # Total devices (excluding deleted) - cursor.execute('SELECT COUNT(*) as total FROM peer WHERE is_deleted = 0') - total = cursor.fetchone()['total'] - - # Banned devices count - cursor.execute('SELECT COUNT(*) as banned FROM peer WHERE is_banned = 1 AND is_deleted = 0') - banned = cursor.fetchone()['banned'] - - # If HBBS API didn't work, fallback to database status - if online_count == 0: - cursor.execute('SELECT COUNT(*) as active FROM peer WHERE status = 1 AND is_deleted = 0') - online_count = cursor.fetchone()['active'] - - # Devices with notes (excluding deleted) - cursor.execute('SELECT COUNT(*) as with_notes FROM peer WHERE note IS NOT NULL AND note != "" AND is_deleted = 0') - with_notes = cursor.fetchone()['with_notes'] - - conn.close() - - return jsonify({ - 'success': True, - 'stats': { - 'total': total, - 'active': online_count, - 'inactive': total - online_count, - 'with_notes': with_notes, - 'banned': banned - } - }) - except Exception as e: - return jsonify({'success': False, 'error': str(e)}), 500 - -@app.route('/api/hbbs/status', methods=['GET']) -def hbbs_status(): - """Check if HBBS API is available.""" - try: - response = requests.get(f'{HBBS_API_URL}/health', timeout=2) - if response.status_code == 200: - return jsonify({'success': True, 'message': 'HBBS API is running', 'api_data': response.json()}) - else: - return jsonify({'success': False, 'message': f'HBBS API returned status {response.status_code}'}) - except Exception as e: - return jsonify({'success': False, 'message': f'HBBS API not available: {str(e)}'}) - -@app.route('/api/device//ban', methods=['POST']) -def ban_device(device_id): - """Ban a device.""" - try: - # Validate device ID - is_valid, error_msg = validate_device_id(device_id) - if not is_valid: - return jsonify({'success': False, 'error': error_msg}), 400 - - data = request.get_json() or {} - - # Validate ban reason - ban_reason = sanitize_input(data.get('reason', '')) - if ban_reason and len(ban_reason) > 500: - return jsonify({'success': False, 'error': 'Ban reason too long (max 500 characters)'}), 400 - - banned_by = sanitize_input(data.get('banned_by', 'admin')) - - conn = get_db_connection() - cursor = conn.cursor() - - # Check if device exists and is not deleted - cursor.execute('SELECT id, is_banned FROM peer WHERE id = ? AND is_deleted = 0', (device_id,)) - device = cursor.fetchone() - - if not device: - conn.close() - return jsonify({'success': False, 'error': 'Device not found or already deleted'}), 404 - - if device['is_banned'] == 1: - conn.close() - return jsonify({'success': False, 'error': 'Device is already banned'}), 409 - - # Ban the device - banned_at = int(datetime.now().timestamp() * 1000) - cursor.execute(''' - UPDATE peer - SET is_banned = 1, - banned_at = ?, - banned_by = ?, - ban_reason = ?, - updated_at = ? - WHERE id = ? AND is_deleted = 0 - ''', (banned_at, banned_by, ban_reason, banned_at, device_id)) - - conn.commit() - affected = cursor.rowcount - conn.close() - - if affected == 0: - return jsonify({'success': False, 'error': 'Failed to ban device'}), 500 - - return jsonify({ - 'success': True, - 'message': f'Device {device_id} banned successfully', - 'banned_at': banned_at, - 'banned_by': banned_by - }) - - except sqlite3.Error as e: - return jsonify({'success': False, 'error': f'Database error: {str(e)}'}), 500 - except Exception as e: - return jsonify({'success': False, 'error': f'Unexpected error: {str(e)}'}), 500 - -@app.route('/api/device//unban', methods=['POST']) -def unban_device(device_id): - """Unban a device.""" - try: - # Validate device ID - is_valid, error_msg = validate_device_id(device_id) - if not is_valid: - return jsonify({'success': False, 'error': error_msg}), 400 - - conn = get_db_connection() - cursor = conn.cursor() - - # Check if device exists and is banned - cursor.execute('SELECT id, is_banned FROM peer WHERE id = ? AND is_deleted = 0', (device_id,)) - device = cursor.fetchone() - - if not device: - conn.close() - return jsonify({'success': False, 'error': 'Device not found or already deleted'}), 404 - - if device['is_banned'] == 0: - conn.close() - return jsonify({'success': False, 'error': 'Device is not banned'}), 409 - - # Unban the device - updated_at = int(datetime.now().timestamp() * 1000) - cursor.execute(''' - UPDATE peer - SET is_banned = 0, - banned_at = NULL, - banned_by = NULL, - ban_reason = NULL, - updated_at = ? - WHERE id = ? AND is_deleted = 0 - ''', (updated_at, device_id)) - - conn.commit() - affected = cursor.rowcount - conn.close() - - if affected == 0: - return jsonify({'success': False, 'error': 'Failed to unban device'}), 500 - - return jsonify({ - 'success': True, - 'message': f'Device {device_id} unbanned successfully' - }) - - except sqlite3.Error as e: - return jsonify({'success': False, 'error': f'Database error: {str(e)}'}), 500 - except Exception as e: - return jsonify({'success': False, 'error': f'Unexpected error: {str(e)}'}), 500 - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/web/app_demo.py b/web/app_demo.py deleted file mode 100644 index a3e1b79e..00000000 --- a/web/app_demo.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -""" -BetterDesk Console - Demo Version with Mock Data -For creating screenshots without exposing real device information -""" - -from flask import Flask, render_template, jsonify, request -import json -from datetime import datetime, timedelta -import random - -app = Flask(__name__) - -# Mock data for screenshots -MOCK_DEVICES = [ - {"id": "1234567890", "note": "Production Server - NYC", "online": True, "created_at": "2025-12-01 10:30:00", "user": "admin", "info": '{"os": "Ubuntu 22.04", "version": "1.2.3"}'}, - {"id": "0987654321", "note": "Development Workstation", "online": True, "created_at": "2025-12-05 14:20:00", "user": "dev_team", "info": '{"os": "Windows 11", "version": "1.2.3"}'}, - {"id": "5555555555", "note": "Marketing Office PC", "online": False, "created_at": "2025-11-20 09:15:00", "user": "marketing", "info": '{"os": "macOS 14", "version": "1.2.2"}'}, - {"id": "7777777777", "note": "Database Server - LA", "online": True, "created_at": "2025-12-10 16:45:00", "user": "dba", "info": '{"os": "Ubuntu 24.04", "version": "1.2.3"}'}, - {"id": "9999999999", "note": "Sales Laptop", "online": False, "created_at": "2025-11-15 11:30:00", "user": "sales", "info": '{"os": "Windows 10", "version": "1.2.1"}'}, - {"id": "1111111111", "note": "Backup Server", "online": True, "created_at": "2025-12-08 13:00:00", "user": "admin", "info": '{"os": "Debian 12", "version": "1.2.3"}'}, - {"id": "2222222222", "note": "Test Environment", "online": True, "created_at": "2025-12-12 08:45:00", "user": "qa_team", "info": '{"os": "Ubuntu 22.04", "version": "1.2.3"}'}, - {"id": "3333333333", "note": "HR Department PC", "online": False, "created_at": "2025-11-25 10:00:00", "user": "hr", "info": '{"os": "Windows 11", "version": "1.2.2"}'}, - {"id": "4444444444", "note": "Web Server - EU", "online": True, "created_at": "2025-12-15 15:30:00", "user": "webmaster", "info": '{"os": "CentOS 9", "version": "1.2.3"}'}, - {"id": "6666666666", "note": "Design Workstation", "online": True, "created_at": "2025-12-03 12:20:00", "user": "design", "info": '{"os": "macOS 14", "version": "1.2.3"}'}, - {"id": "8888888888", "note": None, "online": False, "created_at": "2025-11-10 14:00:00", "user": None, "info": None}, - {"id": "1212121212", "note": None, "online": False, "created_at": "2025-11-05 09:30:00", "user": None, "info": None}, -] - -MOCK_PUBLIC_KEY = "AGH8B3pM5QVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV=" - -@app.route('/') -def index(): - return render_template('index.html', public_key=MOCK_PUBLIC_KEY) - -@app.route('/api/devices', methods=['GET']) -def get_devices(): - return jsonify({"success": True, "devices": MOCK_DEVICES, "error": None}) - -@app.route('/api/stats', methods=['GET']) -def get_stats(): - total = len(MOCK_DEVICES) - active = sum(1 for d in MOCK_DEVICES if d['online']) - inactive = total - active - with_notes = sum(1 for d in MOCK_DEVICES if d['note']) - - return jsonify({ - "stats": { - "total": total, - "active": active, - "inactive": inactive, - "with_notes": with_notes - } - }) - -@app.route('/api/device/', methods=['GET']) -def get_device(device_id): - device = next((d for d in MOCK_DEVICES if d['id'] == device_id), None) - if device: - return jsonify({"success": True, "device": device, "error": None}) - return jsonify({"success": False, "device": None, "error": "Device not found"}), 404 - -if __name__ == '__main__': - print("=" * 60) - print("BetterDesk Console - DEMO MODE") - print("=" * 60) - print("This version uses mock data for screenshots.") - print("Running on: http://localhost:5001") - print("=" * 60) - app.run(host='0.0.0.0', port=5001, debug=True) diff --git a/web/app_v14.py b/web/app_v14.py new file mode 100644 index 00000000..d6683a1a --- /dev/null +++ b/web/app_v14.py @@ -0,0 +1,812 @@ +from flask import Flask, render_template, request, jsonify, redirect, url_for, g +import sqlite3 +from datetime import datetime +import os +import requests +import re +from flask_wtf.csrf import CSRFProtect, generate_csrf +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address + +# Import authentication module +from auth import ( + require_auth, require_role, optional_auth, + authenticate, create_session, verify_session, delete_session, + log_audit, cleanup_expired_sessions, + change_password, + ROLE_ADMIN, ROLE_OPERATOR, ROLE_VIEWER, + AuthError +) + +app = Flask(__name__) +app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', os.urandom(32)) +app.config['WTF_CSRF_CHECK_DEFAULT'] = False # Manual CSRF for API + +# Initialize CSRF protection +csrf = CSRFProtect() +csrf.init_app(app) + +# Initialize rate limiter +limiter = Limiter( + app=app, + key_func=get_remote_address, + default_limits=["1000 per hour", "100 per minute"], + storage_uri="memory://" +) + +# Configuration +DB_PATH = '/opt/rustdesk/db_v2.sqlite3' +PUB_KEY_PATH = '/opt/rustdesk/id_ed25519.pub' +API_KEY_PATH = '/opt/rustdesk/.api_key' +HBBS_API_URL = 'http://localhost:21120/api' + +# Load HBBS API key +def get_hbbs_api_key(): + """Load HBBS API key from file or environment variable.""" + # Try environment variable first + api_key = os.environ.get('HBBS_API_KEY') + if api_key: + return api_key + + # Try reading from file + try: + if os.path.exists(API_KEY_PATH): + with open(API_KEY_PATH, 'r') as f: + return f.read().strip() + except Exception as e: + print(f"Warning: Could not read API key from {API_KEY_PATH}: {e}") + + return None + +HBBS_API_KEY = get_hbbs_api_key() + +# Validation rules +MAX_NOTE_LENGTH = 500 +MAX_DEVICE_ID_LENGTH = 50 +DEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_-]+$') + + +def validate_device_id(device_id): + """Validate device ID format and length.""" + if not device_id: + return False, "Device ID cannot be empty" + if len(device_id) > MAX_DEVICE_ID_LENGTH: + return False, f"Device ID too long (max {MAX_DEVICE_ID_LENGTH} characters)" + if not DEVICE_ID_PATTERN.match(device_id): + return False, "Device ID can only contain letters, numbers, underscores and hyphens" + return True, None + + +def validate_note(note): + """Validate note length.""" + if note and len(note) > MAX_NOTE_LENGTH: + return False, f"Note too long (max {MAX_NOTE_LENGTH} characters)" + return True, None + + +def validate_password_strength(password): + """Validate password strength - minimum 8 characters, letters and numbers.""" + if len(password) < 8: + return False, "Password must be at least 8 characters long" + if not re.search(r'[A-Za-z]', password): + return False, "Password must contain at least one letter" + if not re.search(r'[0-9]', password): + return False, "Password must contain at least one number" + return True, None + + +def sanitize_input(text): + """Basic sanitization of user input.""" + if not text: + return text + from markupsafe import escape + return str(escape(text)).strip() + + +def get_db_connection(): + """Create a read-write connection to the SQLite database.""" + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def get_public_key(): + """Read the RustDesk public key from file.""" + try: + if os.path.exists(PUB_KEY_PATH): + with open(PUB_KEY_PATH, 'r') as f: + key_content = f.read().strip() + return f"[id_ed25519.pub] {key_content}" + + rustdesk_dir = os.path.dirname(PUB_KEY_PATH) + if os.path.exists(rustdesk_dir): + pub_files = [f for f in os.listdir(rustdesk_dir) if f.endswith('.pub')] + if pub_files: + pub_file_path = os.path.join(rustdesk_dir, pub_files[0]) + with open(pub_file_path, 'r') as f: + key_content = f.read().strip() + return f"[{pub_files[0]}] {key_content}" + + return "❌ No public key file (.pub) found in RustDesk directory" + except Exception as e: + return f"Error reading key: {str(e)}" + + +# ============================================================================ +# AUTHENTICATION ROUTES +# ============================================================================ + +@app.route('/login') +def login_page(): + """Render login page""" + return render_template('login.html') + + +@app.route('/api/auth/login', methods=['POST']) +@limiter.limit("5 per minute") +@csrf.exempt # CSRF exempt for login, validated by credentials +def login(): + """Login endpoint""" + try: + data = request.get_json() + username = data.get('username', '').strip() + password = data.get('password', '') + + if not username or not password: + return jsonify({'success': False, 'error': 'Username and password required'}), 400 + + # Note: Password strength is only validated on creation/change, not login + # (to allow legacy accounts with weaker passwords to still login) + + # Authenticate user + user = authenticate(username, password) + + # Create session + token = create_session(user['id']) + + # Log login + log_audit(user['id'], 'login', None, f"Login from {request.remote_addr}", request.remote_addr) + + return jsonify({ + 'success': True, + 'token': token, + 'username': user['username'], + 'role': user['role'] + }) + + except AuthError as e: + return jsonify({'success': False, 'error': str(e)}), 401 + except Exception as e: + return jsonify({'success': False, 'error': 'Login failed'}), 500 + + +@app.route('/api/auth/logout', methods=['POST']) +@require_auth +def logout(): + """Logout endpoint""" + try: + token = request.headers.get('Authorization', '').replace('Bearer ', '') + + # Log logout + log_audit(g.user['user_id'], 'logout', None, 'User logged out', request.remote_addr) + + # Delete session + delete_session(token) + + return jsonify({'success': True, 'message': 'Logged out successfully'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/auth/verify', methods=['GET']) +@require_auth +def verify_token(): + """Verify authentication token""" + return jsonify({ + 'success': True, + 'user': { + 'username': g.user['username'], + 'role': g.user['role'] + } + }) + + +# ============================================================================ +# MAIN ROUTES +# ============================================================================ + +@app.route('/') +def index(): + """Render the main dashboard page. Auth check done by JavaScript on client side.""" + return render_template('index_v15.html') + + +@app.route('/api/devices', methods=['GET']) +@require_auth +@limiter.exempt # Authenticated users bypass rate limit +def get_devices(): + """Fetch all devices from the database with online status from HBBS API.""" + try: + # Try to get status from HBBS API + online_ids = set() + api_device_info = {} + try: + headers = {} + if HBBS_API_KEY: + headers['X-API-Key'] = HBBS_API_KEY + + response = requests.get(f'{HBBS_API_URL}/peers', headers=headers, timeout=2) + if response.status_code == 200: + api_data = response.json() + if api_data.get('success') and api_data.get('data'): + for peer in api_data['data']: + device_id = peer.get('id') + if device_id: + api_device_info[device_id] = peer + if peer.get('online'): + online_ids.add(device_id) + elif response.status_code == 401: + print(f"Warning: HBBS API authentication failed. Check API key.") + except Exception as e: + print(f"Warning: Could not connect to HBBS API: {e}") + + # Get devices from database + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute(''' + SELECT + guid, id, uuid, pk, created_at, user, status, note, info, + is_banned, banned_at, banned_by, ban_reason + FROM peer + WHERE is_deleted = 0 + ORDER BY created_at DESC + ''') + + devices = [] + for row in cursor.fetchall(): + device_id = row['id'] + + if device_id in api_device_info: + online = api_device_info[device_id].get('online', False) + else: + online = row['status'] == 1 if not api_device_info else False + + device = { + 'guid': row['guid'].hex() if row['guid'] else '', + 'id': device_id, + 'uuid': row['uuid'].hex() if row['uuid'] else '', + 'pk': row['pk'].hex() if row['pk'] else '', + 'created_at': row['created_at'], + 'user': row['user'].hex() if row['user'] else '', + 'status': row['status'], + 'online': online, + 'note': row['note'] or '', + 'info': row['info'] or '', + 'is_banned': row['is_banned'] == 1, + 'banned_at': row['banned_at'], + 'banned_by': row['banned_by'] or '', + 'ban_reason': row['ban_reason'] or '' + } + devices.append(device) + + conn.close() + return jsonify({'success': True, 'devices': devices}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/device/', methods=['PUT']) +@require_auth +@require_role(ROLE_ADMIN, ROLE_OPERATOR) +def update_device(device_id): + """Update a device's note and/or ID.""" + try: + is_valid, error_msg = validate_device_id(device_id) + if not is_valid: + return jsonify({'success': False, 'error': error_msg}), 400 + + data = request.get_json() + if not data: + return jsonify({'success': False, 'error': 'No data provided'}), 400 + + conn = get_db_connection() + cursor = conn.cursor() + + updates = [] + params = [] + + if 'note' in data: + is_valid, error_msg = validate_note(data['note']) + if not is_valid: + conn.close() + return jsonify({'success': False, 'error': error_msg}), 400 + + sanitized_note = sanitize_input(data['note']) + updates.append('note = ?') + params.append(sanitized_note) + + if 'new_id' in data and data['new_id']: + is_valid, error_msg = validate_device_id(data['new_id']) + if not is_valid: + conn.close() + return jsonify({'success': False, 'error': error_msg}), 400 + + cursor.execute('SELECT id FROM peer WHERE id = ? AND is_deleted = 0', (data['new_id'],)) + if cursor.fetchone(): + conn.close() + return jsonify({'success': False, 'error': 'Device ID already exists'}), 409 + + updates.append('id = ?') + params.append(data['new_id']) + + if not updates: + conn.close() + return jsonify({'success': False, 'error': 'No fields to update'}), 400 + + updates.append('updated_at = ?') + params.append(int(datetime.now().timestamp() * 1000)) + params.append(device_id) + + query = f"UPDATE peer SET {', '.join(updates)} WHERE id = ? AND is_deleted = 0" + cursor.execute(query, params) + conn.commit() + affected = cursor.rowcount + conn.close() + + if affected == 0: + return jsonify({'success': False, 'error': 'Device not found'}), 404 + + # Log audit with selective data + audit_details = [] + if 'note' in data: + audit_details.append(f"note: {data.get('note', '')[:50]}...") # First 50 chars + if 'new_id' in data: + audit_details.append(f"new_id: {data['new_id']}") + log_audit(g.user['user_id'], 'update_device', device_id, + f"Updated: {', '.join(audit_details)}", request.remote_addr or 'unknown') + + return jsonify({'success': True, 'message': 'Device updated successfully'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/device/', methods=['DELETE']) +@require_auth +@require_role(ROLE_ADMIN, ROLE_OPERATOR) +def delete_device(device_id): + """Soft delete a device from the database.""" + try: + conn = get_db_connection() + cursor = conn.cursor() + + deleted_at = int(datetime.now().timestamp() * 1000) + cursor.execute( + 'UPDATE peer SET is_deleted = 1, deleted_at = ? WHERE id = ? AND is_deleted = 0', + (deleted_at, device_id) + ) + conn.commit() + affected = cursor.rowcount + conn.close() + + if affected == 0: + return jsonify({'success': False, 'error': 'Device not found'}), 404 + + # Log audit + log_audit(g.user['user_id'], 'delete_device', device_id, + 'Device deleted', request.remote_addr or 'unknown') + + return jsonify({'success': True, 'message': 'Device deleted successfully'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/stats', methods=['GET']) +@require_auth +@limiter.exempt # Authenticated users bypass rate limit +def get_stats(): + """Get statistics about the devices.""" + try: + online_count = 0 + try: + response = requests.get(f'{HBBS_API_URL}/peers', timeout=2) + if response.status_code == 200: + api_data = response.json() + if api_data.get('success') and api_data.get('data'): + online_count = sum(1 for peer in api_data['data'] if peer.get('online')) + except Exception as e: + print(f"Warning: Could not connect to HBBS API for stats: {e}") + + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute('SELECT COUNT(*) as total FROM peer WHERE is_deleted = 0') + total = cursor.fetchone()['total'] + + cursor.execute('SELECT COUNT(*) as banned FROM peer WHERE is_banned = 1 AND is_deleted = 0') + banned = cursor.fetchone()['banned'] + + if online_count == 0: + cursor.execute('SELECT COUNT(*) as active FROM peer WHERE status = 1 AND is_deleted = 0') + online_count = cursor.fetchone()['active'] + + cursor.execute('SELECT COUNT(*) as with_notes FROM peer WHERE note IS NOT NULL AND note != "" AND is_deleted = 0') + with_notes = cursor.fetchone()['with_notes'] + + conn.close() + + return jsonify({ + 'success': True, + 'stats': { + 'total': total, + 'active': online_count, + 'inactive': total - online_count, + 'with_notes': with_notes, + 'banned': banned + } + }) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/device//ban', methods=['POST']) +@require_auth +@require_role(ROLE_ADMIN, ROLE_OPERATOR) +def ban_device(device_id): + """Ban a device.""" + try: + is_valid, error_msg = validate_device_id(device_id) + if not is_valid: + return jsonify({'success': False, 'error': error_msg}), 400 + + data = request.get_json() or {} + + ban_reason = sanitize_input(data.get('reason', '')) + if ban_reason and len(ban_reason) > 500: + return jsonify({'success': False, 'error': 'Ban reason too long'}), 400 + + banned_by = g.user['username'] + + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute('SELECT id, is_banned FROM peer WHERE id = ? AND is_deleted = 0', (device_id,)) + device = cursor.fetchone() + + if not device: + conn.close() + return jsonify({'success': False, 'error': 'Device not found'}), 404 + + if device['is_banned'] == 1: + conn.close() + return jsonify({'success': False, 'error': 'Device is already banned'}), 409 + + banned_at = int(datetime.now().timestamp() * 1000) + cursor.execute(''' + UPDATE peer + SET is_banned = 1, banned_at = ?, banned_by = ?, ban_reason = ?, updated_at = ? + WHERE id = ? AND is_deleted = 0 + ''', (banned_at, banned_by, ban_reason, banned_at, device_id)) + + conn.commit() + conn.close() + + # Log audit + log_audit(g.user['user_id'], 'ban_device', device_id, + f"Banned device. Reason: {ban_reason}", request.remote_addr) + + return jsonify({ + 'success': True, + 'message': f'Device {device_id} banned successfully', + 'banned_at': banned_at, + 'banned_by': banned_by + }) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/device//unban', methods=['POST']) +@require_auth +@require_role(ROLE_ADMIN, ROLE_OPERATOR) +def unban_device(device_id): + """Unban a device.""" + try: + is_valid, error_msg = validate_device_id(device_id) + if not is_valid: + return jsonify({'success': False, 'error': error_msg}), 400 + + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute('SELECT id, is_banned FROM peer WHERE id = ? AND is_deleted = 0', (device_id,)) + device = cursor.fetchone() + + if not device: + conn.close() + return jsonify({'success': False, 'error': 'Device not found'}), 404 + + if device['is_banned'] == 0: + conn.close() + return jsonify({'success': False, 'error': 'Device is not banned'}), 409 + + updated_at = int(datetime.now().timestamp() * 1000) + cursor.execute(''' + UPDATE peer + SET is_banned = 0, banned_at = NULL, banned_by = NULL, ban_reason = NULL, updated_at = ? + WHERE id = ? AND is_deleted = 0 + ''', (updated_at, device_id)) + + conn.commit() + conn.close() + + # Log audit + log_audit(g.user['user_id'], 'unban_device', device_id, + 'Device unbanned', request.remote_addr) + + return jsonify({ + 'success': True, + 'message': f'Device {device_id} unbanned successfully' + }) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +# ============================================================================ +# USER MANAGEMENT ROUTES (Admin only) +# ============================================================================ + +@app.route('/api/users', methods=['GET']) +@require_auth +@require_role(ROLE_ADMIN) +def list_all_users(): + """List all users (admin only)""" + try: + from auth import list_users + users = list_users() + + # Don't send password hashes + for user in users: + user.pop('password_hash', None) + + return jsonify({'success': True, 'users': users}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/users', methods=['POST']) +@require_auth +@require_role(ROLE_ADMIN) +def create_new_user(): + """Create new user (admin only)""" + try: + data = request.get_json() + username = sanitize_input(data.get('username', '')) + password = data.get('password', '') + role = data.get('role', ROLE_VIEWER) + + if not username or not password: + return jsonify({'success': False, 'error': 'Username and password required'}), 400 + + # Validate password strength + is_valid, error_msg = validate_password_strength(password) + if not is_valid: + return jsonify({'success': False, 'error': error_msg}), 400 + + from auth import create_user + user = create_user(username, password, role) + + # Log audit + log_audit(g.user['user_id'], 'create_user', None, + f"Created user: {username} with role: {role}", request.remote_addr) + + return jsonify({'success': True, 'message': 'User created successfully', 'user': user}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 400 + + +@app.route('/api/users/', methods=['PUT']) +@require_auth +@require_role(ROLE_ADMIN) +def update_user(user_id): + """Update user (admin only)""" + try: + data = request.get_json() + action = data.get('action') + + from auth import update_user_role, activate_user, deactivate_user, reset_password + + if action == 'change_role': + new_role = data.get('role') + if not new_role: + return jsonify({'success': False, 'error': 'Role required'}), 400 + + update_user_role(user_id, new_role) + log_audit(g.user['user_id'], 'update_user_role', None, + f"Changed role of user {user_id} to {new_role}", request.remote_addr) + return jsonify({'success': True, 'message': 'User role updated'}) + + elif action == 'activate': + activate_user(user_id) + log_audit(g.user['user_id'], 'activate_user', None, + f"Activated user {user_id}", request.remote_addr) + return jsonify({'success': True, 'message': 'User activated'}) + + elif action == 'deactivate': + deactivate_user(user_id) + log_audit(g.user['user_id'], 'deactivate_user', None, + f"Deactivated user {user_id}", request.remote_addr) + return jsonify({'success': True, 'message': 'User deactivated'}) + + elif action == 'reset_password': + new_password = data.get('password') + if not new_password: + return jsonify({'success': False, 'error': 'Password required'}), 400 + + # Validate password strength + is_valid, error_msg = validate_password_strength(new_password) + if not is_valid: + return jsonify({'success': False, 'error': error_msg}), 400 + + reset_password(user_id, new_password) + log_audit(g.user['user_id'], 'reset_password', None, + f"Reset password for user {user_id}", request.remote_addr) + return jsonify({'success': True, 'message': 'Password reset successfully'}) + + else: + return jsonify({'success': False, 'error': 'Invalid action'}), 400 + + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 400 + + +@app.route('/api/users/', methods=['DELETE']) +@require_auth +@require_role(ROLE_ADMIN) +def delete_user_account(user_id): + """Delete user (admin only)""" + try: + # Prevent self-deletion + if user_id == g.user['user_id']: + return jsonify({'success': False, 'error': 'Cannot delete your own account'}), 400 + + from auth import delete_user + delete_user(user_id) + + log_audit(g.user['user_id'], 'delete_user', None, + f"Deleted user {user_id}", request.remote_addr) + + return jsonify({'success': True, 'message': 'User deleted successfully'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +# ============================================================================ +# PASSWORD CHANGE & KEY VERIFICATION +# ============================================================================ + +@app.route('/api/auth/change-password', methods=['POST']) +@require_auth +def change_user_password(): + """Change current user's password""" + try: + data = request.get_json() + old_password = data.get('old_password', '') + new_password = data.get('new_password', '') + + if not old_password or not new_password: + return jsonify({'success': False, 'error': 'Old and new password required'}), 400 + + # Validate password strength + is_valid, error_msg = validate_password_strength(new_password) + if not is_valid: + return jsonify({'success': False, 'error': error_msg}), 400 + + # Change password and get new token + new_token = change_password(g.user['user_id'], old_password, new_password) + + log_audit(g.user['user_id'], 'change_password', None, + 'User changed password', request.remote_addr) + + return jsonify({ + 'success': True, + 'message': 'Password changed successfully', + 'token': new_token + }) + except AuthError as e: + return jsonify({'success': False, 'error': str(e)}), 400 + except Exception as e: + return jsonify({'success': False, 'error': 'Password change failed'}), 500 + + +@app.route('/api/auth/verify-password', methods=['POST']) +@require_auth +def verify_password_endpoint(): + """Verify user's password (for accessing protected content like public key)""" + try: + data = request.get_json() + password = data.get('password', '') + + if not password: + return jsonify({'success': False, 'error': 'Password required'}), 400 + + # Verify user's password + from auth import verify_password as verify_pwd, get_auth_db + conn = get_auth_db() + cursor = conn.cursor() + cursor.execute('SELECT password_hash FROM users WHERE id = ?', (g.user['user_id'],)) + user = cursor.fetchone() + conn.close() + + if not user or not verify_pwd(password, user['password_hash']): + log_audit(g.user['user_id'], 'password_verify_failed', None, + 'Failed password verification', request.remote_addr) + return jsonify({'success': False, 'error': 'Invalid password'}), 401 + + log_audit(g.user['user_id'], 'password_verify_success', None, + 'Password verified successfully', request.remote_addr) + + return jsonify({'success': True, 'message': 'Password verified'}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +@app.route('/api/public-key', methods=['GET']) +@require_auth +def get_public_key_endpoint(): + """Get server public key (requires prior password verification in frontend)""" + try: + # Only admin and operator can view key + if g.user['role'] not in [ROLE_ADMIN, ROLE_OPERATOR]: + return jsonify({'success': False, 'error': 'Insufficient permissions'}), 403 + + public_key = get_public_key() + + log_audit(g.user['user_id'], 'view_public_key', None, + 'Accessed public key', request.remote_addr) + + return jsonify({'success': True, 'key': public_key}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 500 + + +# Cleanup expired sessions periodically +@app.before_request +def before_request(): + """Run before each request""" + # Cleanup expired sessions (every 100th request to avoid overhead) + import random + if random.randint(1, 100) == 1: + try: + cleanup_expired_sessions() + except: + pass + + +@app.after_request +def add_security_headers(response): + """Add security headers to all responses""" + # Content Security Policy + response.headers['Content-Security-Policy'] = ( + "default-src 'self'; " + "script-src 'self' 'unsafe-inline'; " + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com; " + "font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com; " + "img-src 'self' data:; " + "connect-src 'self'" + ) + # Additional security headers + response.headers['X-Content-Type-Options'] = 'nosniff' + response.headers['X-Frame-Options'] = 'SAMEORIGIN' + response.headers['X-XSS-Protection'] = '1; mode=block' + response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' + return response + + +# Note: Authentication is handled by @require_auth decorator on each endpoint +# No global before_request check needed - let JavaScript on pages handle redirects + + +if __name__ == '__main__': + DEBUG = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true' + HOST = os.environ.get('FLASK_HOST', '0.0.0.0') + PORT = int(os.environ.get('FLASK_PORT', 5000)) + app.run(host=HOST, port=PORT, debug=DEBUG) diff --git a/web/auth.py b/web/auth.py new file mode 100644 index 00000000..7289164d --- /dev/null +++ b/web/auth.py @@ -0,0 +1,421 @@ +""" +Authentication and Authorization Module for BetterDesk Console +""" + +import sqlite3 +import secrets +import bcrypt +import functools +from datetime import datetime, timedelta +from flask import request, jsonify, g +from typing import Optional + + +# Database path +DB_PATH = '/opt/rustdesk/db_v2.sqlite3' + +# Session expiry (24 hours) +SESSION_EXPIRY_HOURS = 24 + +# Roles +ROLE_ADMIN = 'admin' +ROLE_OPERATOR = 'operator' +ROLE_VIEWER = 'viewer' + +ROLES_HIERARCHY = { + ROLE_ADMIN: 3, # Full access + ROLE_OPERATOR: 2, # Can ban/unban, edit devices + ROLE_VIEWER: 1 # Read-only +} + + +class AuthError(Exception): + """Custom exception for authentication errors""" + pass + + +def get_auth_db(): + """Get database connection for auth operations""" + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def hash_password(password: str) -> str: + """Hash password using bcrypt""" + salt = bcrypt.gensalt() + return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8') + + +def verify_password(password: str, password_hash: str) -> bool: + """Verify password against hash""" + return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8')) + + +def generate_session_token() -> str: + """Generate secure session token""" + return secrets.token_urlsafe(32) + + +def create_user(username: str, password: str, role: str = ROLE_VIEWER) -> dict: + """Create a new user""" + if role not in ROLES_HIERARCHY: + raise AuthError(f"Invalid role: {role}") + + password_hash = hash_password(password) + conn = get_auth_db() + cursor = conn.cursor() + + try: + cursor.execute(''' + INSERT INTO users (username, password_hash, role, created_at, is_active) + VALUES (?, ?, ?, ?, 1) + ''', (username, password_hash, role, datetime.now())) + + conn.commit() + user_id = cursor.lastrowid + + return { + 'id': user_id, + 'username': username, + 'role': role + } + except sqlite3.IntegrityError: + raise AuthError("Username already exists") + finally: + conn.close() + + +def authenticate(username: str, password: str) -> dict: + """Authenticate user and return user data""" + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute(''' + SELECT id, username, password_hash, role, is_active + FROM users + WHERE username = ? + ''', (username,)) + + user = cursor.fetchone() + conn.close() + + if not user: + raise AuthError("Invalid username or password") + + if not user['is_active']: + raise AuthError("Account is disabled") + + if not verify_password(password, user['password_hash']): + raise AuthError("Invalid username or password") + + return { + 'id': user['id'], + 'username': user['username'], + 'role': user['role'] + } + + +def create_session(user_id: int) -> str: + """Create a new session and return token""" + token = generate_session_token() + expires_at = datetime.now() + timedelta(hours=SESSION_EXPIRY_HOURS) + + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO sessions (token, user_id, created_at, expires_at, last_activity) + VALUES (?, ?, ?, ?, ?) + ''', (token, user_id, datetime.now(), expires_at, datetime.now())) + + # Update last login + cursor.execute(''' + UPDATE users SET last_login = ? WHERE id = ? + ''', (datetime.now(), user_id)) + + conn.commit() + conn.close() + + return token + + +def verify_session(token: str) -> dict: + """Verify session token and return user data""" + if not token: + raise AuthError("No authentication token provided") + + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute(''' + SELECT s.user_id, s.expires_at, u.username, u.role, u.is_active + FROM sessions s + JOIN users u ON s.user_id = u.id + WHERE s.token = ? + ''', (token,)) + + session = cursor.fetchone() + + if not session: + conn.close() + raise AuthError("Invalid session token") + + # Check if session expired + expires_at = datetime.fromisoformat(session['expires_at']) + if datetime.now() > expires_at: + # Delete expired session + cursor.execute('DELETE FROM sessions WHERE token = ?', (token,)) + conn.commit() + conn.close() + raise AuthError("Session expired") + + # Check if user is active + if not session['is_active']: + conn.close() + raise AuthError("Account is disabled") + + # Update last activity + cursor.execute(''' + UPDATE sessions SET last_activity = ? WHERE token = ? + ''', (datetime.now(), token)) + + conn.commit() + conn.close() + + return { + 'user_id': session['user_id'], + 'username': session['username'], + 'role': session['role'] + } + + +def delete_session(token: str): + """Delete session (logout)""" + conn = get_auth_db() + cursor = conn.cursor() + cursor.execute('DELETE FROM sessions WHERE token = ?', (token,)) + conn.commit() + conn.close() + + +def cleanup_expired_sessions(): + """Remove expired sessions from database""" + conn = get_auth_db() + cursor = conn.cursor() + cursor.execute('DELETE FROM sessions WHERE expires_at < ?', (datetime.now(),)) + deleted = cursor.rowcount + conn.commit() + conn.close() + return deleted + + +def log_audit(user_id: int, action: str, device_id: Optional[str] = None, + details: Optional[str] = None, ip_address: Optional[str] = None): + """Log user action for audit trail""" + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO audit_log (user_id, action, device_id, details, ip_address, timestamp) + VALUES (?, ?, ?, ?, ?, ?) + ''', (user_id, action, device_id, details, ip_address or 'unknown', datetime.now())) + + conn.commit() + conn.close() + + +def get_user_by_id(user_id: int) -> Optional[dict]: + """Get user data by ID""" + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute(''' + SELECT id, username, role, created_at, last_login, is_active + FROM users WHERE id = ? + ''', (user_id,)) + + user = cursor.fetchone() + conn.close() + + if not user: + return None + + return dict(user) + + +def list_users() -> list: + """List all users (admin only)""" + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute(''' + SELECT id, username, role, created_at, last_login, is_active + FROM users + ORDER BY created_at DESC + ''') + + users = [dict(row) for row in cursor.fetchall()] + conn.close() + + return users + + +def update_user_role(user_id: int, new_role: str): + """Update user role (admin only)""" + if new_role not in ROLES_HIERARCHY: + raise AuthError(f"Invalid role: {new_role}") + + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute('UPDATE users SET role = ? WHERE id = ?', (new_role, user_id)) + conn.commit() + conn.close() + + +def deactivate_user(user_id: int): + """Deactivate user account""" + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute('UPDATE users SET is_active = 0 WHERE id = ?', (user_id,)) + # Also delete all sessions for this user + cursor.execute('DELETE FROM sessions WHERE user_id = ?', (user_id,)) + + conn.commit() + conn.close() + + +def activate_user(user_id: int): + """Activate user account""" + conn = get_auth_db() + cursor = conn.cursor() + cursor.execute('UPDATE users SET is_active = 1 WHERE id = ?', (user_id,)) + conn.commit() + conn.close() + + +def delete_user(user_id: int): + """Delete user account (admin only)""" + conn = get_auth_db() + cursor = conn.cursor() + + # Delete all sessions first + cursor.execute('DELETE FROM sessions WHERE user_id = ?', (user_id,)) + # Delete user + cursor.execute('DELETE FROM users WHERE id = ?', (user_id,)) + + conn.commit() + conn.close() + + +def change_password(user_id: int, old_password: str, new_password: str) -> str: + """Change user password and return new session token""" + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute('SELECT password_hash FROM users WHERE id = ?', (user_id,)) + user = cursor.fetchone() + + if not user: + conn.close() + raise AuthError("User not found") + + if not verify_password(old_password, user['password_hash']): + conn.close() + raise AuthError("Current password is incorrect") + + new_hash = hash_password(new_password) + cursor.execute('UPDATE users SET password_hash = ? WHERE id = ?', (new_hash, user_id)) + + # Invalidate all sessions + cursor.execute('DELETE FROM sessions WHERE user_id = ?', (user_id,)) + + conn.commit() + conn.close() + + # Create new session + return create_session(user_id) + + +def reset_password(user_id: int, new_password: str): + """Reset user password (admin only)""" + new_hash = hash_password(new_password) + + conn = get_auth_db() + cursor = conn.cursor() + + cursor.execute('UPDATE users SET password_hash = ? WHERE id = ?', (new_hash, user_id)) + # Invalidate all sessions for this user + cursor.execute('DELETE FROM sessions WHERE user_id = ?', (user_id,)) + + conn.commit() + conn.close() + + +# Flask decorators + +def require_auth(f): + """Decorator to require authentication""" + @functools.wraps(f) + def decorated_function(*args, **kwargs): + token = request.headers.get('Authorization') + + if token and token.startswith('Bearer '): + token = token[7:] # Remove 'Bearer ' prefix + + if not token: + return jsonify({'success': False, 'error': 'No authorization token provided'}), 401 + + try: + user_data = verify_session(token) + g.user = user_data # Store in Flask's g object + return f(*args, **kwargs) + except AuthError as e: + return jsonify({'success': False, 'error': str(e)}), 401 + + return decorated_function + + +def require_role(*allowed_roles): + """Decorator to require specific role(s)""" + def decorator(f): + @functools.wraps(f) + def decorated_function(*args, **kwargs): + if not hasattr(g, 'user'): + return jsonify({'success': False, 'error': 'Authentication required'}), 401 + + user_role = g.user['role'] + + if user_role not in allowed_roles: + return jsonify({ + 'success': False, + 'error': f'Insufficient permissions. Required: {", ".join(allowed_roles)}' + }), 403 + + return f(*args, **kwargs) + + return decorated_function + return decorator + + +def optional_auth(f): + """Decorator for optional authentication (doesn't fail if not authenticated)""" + @functools.wraps(f) + def decorated_function(*args, **kwargs): + token = request.headers.get('Authorization') + + if token and token.startswith('Bearer '): + token = token[7:] + try: + user_data = verify_session(token) + g.user = user_data + except AuthError: + g.user = None + else: + g.user = None + + return f(*args, **kwargs) + + return decorated_function diff --git a/web/betterdesk.service b/web/betterdesk.service index a711bb35..b158c3a1 100644 --- a/web/betterdesk.service +++ b/web/betterdesk.service @@ -6,6 +6,9 @@ After=network.target Type=simple User=root WorkingDirectory=/opt/BetterDeskConsole +Environment="FLASK_HOST=0.0.0.0" +Environment="FLASK_PORT=5000" +Environment="FLASK_DEBUG=False" ExecStart=/usr/bin/python3 /opt/BetterDeskConsole/app.py Restart=always RestartSec=10 diff --git a/web/requirements.txt b/web/requirements.txt index 86ffd368..f7fdf708 100644 --- a/web/requirements.txt +++ b/web/requirements.txt @@ -1,2 +1,6 @@ Flask==3.0.0 +Flask-WTF==1.2.1 +Flask-Limiter==3.5.0 requests==2.31.0 +bcrypt==4.1.2 +markupsafe==2.1.3 diff --git a/web/static/script.js b/web/static/script.js index 23ef76cb..28593d64 100644 --- a/web/static/script.js +++ b/web/static/script.js @@ -158,19 +158,19 @@ function showDetails(deviceId) {
GUID:
-
${device.guid || 'N/A'}
+
${escapeHtml(device.guid) || 'N/A'}
UUID:
-
${device.uuid || 'N/A'}
+
${escapeHtml(device.uuid) || 'N/A'}
Public Key:
-
${device.pk || 'N/A'}
+
${escapeHtml(device.pk) || 'N/A'}
User:
-
${device.user || 'N/A'}
+
${escapeHtml(device.user) || 'N/A'}
Status:
diff --git a/web/static/script_v14.js b/web/static/script_v14.js new file mode 100644 index 00000000..5b3defd6 --- /dev/null +++ b/web/static/script_v14.js @@ -0,0 +1,924 @@ +// BetterDesk Console - Main JavaScript with Authentication v1.4.0 +// Global variables +let allDevices = []; +let currentDeviceId = null; +let authToken = null; +let userRole = null; + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + // Check authentication + checkAuth(); + + // Load data + loadDevices(); + loadStats(); + + // Auto-refresh every 2 seconds + setInterval(() => { + loadDevices(); + loadStats(); + }, 2000); +}); + +// Authentication check +function checkAuth() { + authToken = localStorage.getItem('authToken'); + userRole = localStorage.getItem('role'); + + if (!authToken) { + window.location.href = '/login'; + return false; + } + + return true; +} + +// Get auth headers for API calls +function getAuthHeaders() { + return { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }; +} + +// Handle authentication errors +function handleAuthError(error, response) { + if (response && response.status === 401) { + // Token expired or invalid + localStorage.removeItem('authToken'); + localStorage.removeItem('username'); + localStorage.removeItem('role'); + window.location.href = '/login'; + return true; + } + return false; +} + +// Load devices from API +async function loadDevices() { + if (!checkAuth()) return; + + try { + const response = await fetch('/api/devices', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const data = await response.json(); + + if (data.success) { + allDevices = data.devices; + renderDevices(allDevices); + updateNavStats(allDevices); + } else { + showToast('Error loading devices: ' + data.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to load devices', 'error'); + } +} + +// Load statistics +async function loadStats() { + if (!checkAuth()) return; + + try { + const response = await fetch('/api/stats', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const data = await response.json(); + + if (data.success) { + document.getElementById('statTotal').textContent = data.stats.total; + document.getElementById('statActive').textContent = data.stats.active; + document.getElementById('statInactive').textContent = data.stats.inactive; + document.getElementById('statBanned').textContent = data.stats.banned || 0; + document.getElementById('statNotes').textContent = data.stats.with_notes; + + // Update top bar stats + const topTotal = document.getElementById('topTotalDevices'); + const topActive = document.getElementById('topActiveDevices'); + if (topTotal) topTotal.querySelector('span').textContent = data.stats.total; + if (topActive) topActive.querySelector('span').textContent = data.stats.active; + } + } catch (error) { + console.error('Error loading stats:', error); + } +} + +// Update navigation stats +function updateNavStats(devices) { + const total = devices.length; + const active = devices.filter(d => d.online).length; + + const totalDevicesEl = document.querySelector('#totalDevices span'); + const activeDevicesEl = document.querySelector('#activeDevices span'); + + if (totalDevicesEl) totalDevicesEl.textContent = total; + if (activeDevicesEl) activeDevicesEl.textContent = active; +} + +// Render devices table +function renderDevices(devices) { + const tbody = document.getElementById('devicesTableBody'); + + if (devices.length === 0) { + tbody.innerHTML = ` + + + + No devices found + + + `; + return; + } + + tbody.innerHTML = devices.map(device => { + const isBanned = device.is_banned === true || device.is_banned === 1; + const rowClass = isBanned ? 'style="opacity: 0.6; background: rgba(255, 0, 0, 0.05);"' : ''; + + // Check permissions for actions + const canEdit = userRole === 'admin' || userRole === 'operator'; + const canBan = userRole === 'admin' || userRole === 'operator'; + + return ` + + + ${escapeHtml(device.id)} + ${isBanned ? '
BANNED' : ''} + + ${escapeHtml(device.note) || 'No note'} + + + + ${device.online ? 'Online' : 'Offline'} + + + ${formatDate(device.created_at)} + + + + ${canEdit ? ` + + ` : ''} + ${canBan ? (isBanned ? + `` : + `` + ) : ''} + ${canEdit ? ` + + ` : ''} + + + `}).join(''); +} + +// Filter devices by search +function filterDevices() { + const searchTerm = document.getElementById('searchInput').value.toLowerCase(); + + if (!searchTerm) { + renderDevices(allDevices); + return; + } + + const filtered = allDevices.filter(device => + device.id.toLowerCase().includes(searchTerm) || + (device.note && device.note.toLowerCase().includes(searchTerm)) + ); + + renderDevices(filtered); +} + +// Connect to device via rustdesk:// protocol +function connectDevice(deviceId) { + window.location.href = `rustdesk://${deviceId}`; + showToast(`Connecting to ${deviceId}...`); +} + +// Show device details modal +function showDetails(deviceId) { + const device = allDevices.find(d => d.id === deviceId); + if (!device) return; + + const isBanned = device.is_banned === true || device.is_banned === 1; + + const detailsContent = document.getElementById('detailsContent'); + detailsContent.innerHTML = ` +
+
ID:
+
${escapeHtml(device.id)}
+
+
+
GUID:
+
${escapeHtml(device.guid) || 'N/A'}
+
+
+
UUID:
+
${escapeHtml(device.uuid) || 'N/A'}
+
+
+
Public Key:
+
${escapeHtml(device.pk) || 'N/A'}
+
+
+
User:
+
${escapeHtml(device.user) || 'N/A'}
+
+
+
Status:
+
+ + + ${device.online ? 'Online' : 'Offline'} + +
+
+ ${isBanned ? ` +
+
BAN STATUS:
+
BANNED
+
+
+
Banned At:
+
${device.banned_at ? formatDate(device.banned_at) : 'N/A'}
+
+
+
Banned By:
+
${escapeHtml(device.banned_by) || 'N/A'}
+
+
+
Ban Reason:
+
${escapeHtml(device.ban_reason) || 'No reason provided'}
+
+ ` : ''} +
+
Note:
+
${escapeHtml(device.note) || 'No note'}
+
+
+
Created:
+
${formatDate(device.created_at)}
+
+
+
Info:
+
${escapeHtml(device.info) || 'N/A'}
+
+ `; + + openModal('detailsModal'); +} + +// Edit device +function editDevice(deviceId) { + const device = allDevices.find(d => d.id === deviceId); + if (!device) return; + + currentDeviceId = deviceId; + document.getElementById('editDeviceId').value = deviceId; + document.getElementById('editNewId').value = ''; + document.getElementById('editNote').value = device.note || ''; + + openModal('editModal'); +} + +// Save device changes +async function saveDevice() { + if (!checkAuth()) return; + + const newId = document.getElementById('editNewId').value.trim(); + const note = document.getElementById('editNote').value.trim(); + + if (note.length > 500) { + showToast('Note is too long (max 500 characters)', 'error'); + return; + } + + if (newId && newId.length > 50) { + showToast('Device ID is too long (max 50 characters)', 'error'); + return; + } + + if (newId && !/^[a-zA-Z0-9_-]+$/.test(newId)) { + showToast('Device ID can only contain letters, numbers, underscores and hyphens', 'error'); + return; + } + + if (newId && newId !== currentDeviceId) { + if (!confirm(`⚠️ WARNING: Changing device ID!\n\nOld ID: ${currentDeviceId}\nNew ID: ${newId}\n\nAre you sure?`)) { + return; + } + } + + const data = { note }; + if (newId) data.new_id = newId; + + try { + const response = await fetch(`/api/device/${currentDeviceId}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify(data) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('Device updated successfully'); + closeEditModal(); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to update device', 'error'); + } +} + +// Delete device +function deleteDevice(deviceId) { + currentDeviceId = deviceId; + document.getElementById('deleteDeviceId').textContent = deviceId; + openModal('deleteModal'); +} + +// Confirm delete +async function confirmDelete() { + if (!checkAuth()) return; + + const device = allDevices.find(d => d.id === currentDeviceId); + + if (!confirm(`⚠️ DELETE DEVICE: ${currentDeviceId}\n\nAre you absolutely sure?`)) { + return; + } + + try { + const response = await fetch(`/api/device/${currentDeviceId}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('Device deleted successfully'); + closeDeleteModal(); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to delete device', 'error'); + } +} + +// Copy public key to clipboard +function copyPublicKey() { + const keyText = document.getElementById('publicKeyDisplay').textContent; + navigator.clipboard.writeText(keyText).then(() => { + showToast('Public key copied to clipboard'); + }).catch(err => { + console.error('Error copying:', err); + showToast('Failed to copy public key', 'error'); + }); +} + +// Refresh devices manually +async function refreshDevices() { + showToast('Refreshing devices...'); + await loadDevices(); + await loadStats(); +} + +// Modal functions +function openModal(modalId) { + document.getElementById(modalId).classList.add('active'); +} + +function closeModal(modalId) { + document.getElementById(modalId).classList.remove('active'); +} + +function closeEditModal() { + closeModal('editModal'); + currentDeviceId = null; +} + +function closeDeleteModal() { + closeModal('deleteModal'); + currentDeviceId = null; +} + +function closeDetailsModal() { + closeModal('detailsModal'); +} + +// Close modal when clicking outside +window.onclick = function(event) { + if (event.target.classList.contains('modal')) { + event.target.classList.remove('active'); + } +} + +// Toast notification +function showToast(message, type = 'success') { + const toast = document.getElementById('toast'); + const icon = toast.querySelector('i'); + + if (type === 'error') { + icon.className = 'fas fa-exclamation-circle'; + icon.style.color = 'var(--danger-color)'; + } else { + icon.className = 'fas fa-check-circle'; + icon.style.color = 'var(--success-color)'; + } + + document.getElementById('toastMessage').textContent = message; + toast.classList.add('show'); + + setTimeout(() => { + toast.classList.remove('show'); + }, 3000); +} + +// Utility functions +function escapeHtml(text) { + if (!text) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +function formatDate(dateString) { + if (!dateString) return 'N/A'; + const date = new Date(dateString); + const options = { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }; + return date.toLocaleDateString('en-US', options); +} + +// Ban device +async function banDevice(deviceId) { + if (!checkAuth()) return; + + const reason = prompt(`⚠️ BAN DEVICE: ${deviceId}\n\nEnter ban reason (optional):`); + + if (reason === null) return; + + if (reason && reason.length > 500) { + showToast('Ban reason is too long (max 500 characters)', 'error'); + return; + } + + if (!confirm(`Are you sure you want to BAN device ${deviceId}?`)) { + return; + } + + try { + const response = await fetch(`/api/device/${deviceId}/ban`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + reason: reason || '', + banned_by: 'admin' + }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`Device ${deviceId} banned successfully`); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to ban device', 'error'); + } +} + +// Unban device +async function unbanDevice(deviceId) { + if (!checkAuth()) return; + + if (!confirm(`✓ UNBAN DEVICE: ${deviceId}\n\nAre you sure?`)) { + return; + } + + try { + const response = await fetch(`/api/device/${deviceId}/unban`, { + method: 'POST', + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`Device ${deviceId} unbanned successfully`); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to unban device', 'error'); + } +} +// ============================================================================ +// PUBLIC KEY VERIFICATION +// ============================================================================ + +async function verifyPasswordForKey() { + const password = document.getElementById('keyPassword').value; + + if (!password) { + showToast('Please enter your password', 'error'); + return; + } + + try { + const response = await fetch('/api/key/verify', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ password: password }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + document.getElementById('publicKeyDisplay').textContent = result.key; + document.getElementById('keyPasswordForm').style.display = 'none'; + document.getElementById('keyDisplay').style.display = 'block'; + document.getElementById('keyPassword').value = ''; + showToast('Public key revealed'); + } else { + showToast('Error: ' + result.error, 'error'); + document.getElementById('keyPassword').value = ''; + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to verify password', 'error'); + } +} + +function copyPublicKey() { + const keyText = document.getElementById('publicKeyDisplay').textContent; + navigator.clipboard.writeText(keyText).then(() => { + showToast('Public key copied to clipboard'); + }).catch(err => { + showToast('Failed to copy', 'error'); + }); +} + +// ============================================================================ +// PASSWORD CHANGE +// ============================================================================ + +function showChangePasswordModal() { + document.getElementById('changePasswordModal').classList.add('show'); +} + +function closeChangePasswordModal() { + document.getElementById('changePasswordModal').classList.remove('show'); + document.getElementById('currentPassword').value = ''; + document.getElementById('newPassword').value = ''; + document.getElementById('confirmPassword').value = ''; +} + +async function confirmChangePassword() { + const currentPassword = document.getElementById('currentPassword').value; + const newPassword = document.getElementById('newPassword').value; + const confirmPassword = document.getElementById('confirmPassword').value; + + if (!currentPassword || !newPassword || !confirmPassword) { + showToast('All fields are required', 'error'); + return; + } + + if (newPassword.length < 6) { + showToast('New password must be at least 6 characters', 'error'); + return; + } + + if (newPassword !== confirmPassword) { + showToast('New passwords do not match', 'error'); + return; + } + + try { + const response = await fetch('/api/auth/change-password', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + old_password: currentPassword, + new_password: newPassword + }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('Password changed successfully'); + closeChangePasswordModal(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to change password', 'error'); + } +} + +// ============================================================================ +// USER MANAGEMENT +// ============================================================================ + +async function loadUsers() { + if (!checkAuth()) return; + + try { + const response = await fetch('/api/users', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const data = await response.json(); + + if (data.success) { + renderUsers(data.users); + } else { + showToast('Error loading users: ' + data.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to load users', 'error'); + } +} + +function renderUsers(users) { + const tbody = document.getElementById('usersTableBody'); + + if (users.length === 0) { + tbody.innerHTML = 'No users found'; + return; + } + + tbody.innerHTML = users.map(user => { + const statusBadge = user.is_active ? + 'Active' : + 'Inactive'; + + const roleColor = user.role === 'admin' ? 'danger' : + user.role === 'operator' ? 'warning' : 'info'; + const roleBadge = `${user.role}`; + + const createdDate = user.created_at ? new Date(user.created_at).toLocaleDateString() : 'N/A'; + const lastLogin = user.last_login ? new Date(user.last_login).toLocaleString() : 'Never'; + + return ` + + ${user.username} + ${roleBadge} + ${statusBadge} + ${createdDate} + ${lastLogin} + + + + ${user.is_active ? + `` : + `` + } + + + `; + }).join(''); +} + +// Add User Modal +function showAddUserModal() { + document.getElementById('addUserModal').classList.add('show'); +} + +function closeAddUserModal() { + document.getElementById('addUserModal').classList.remove('show'); + document.getElementById('newUsername').value = ''; + document.getElementById('newUserPassword').value = ''; + document.getElementById('newUserRole').value = 'viewer'; +} + +async function confirmAddUser() { + const username = document.getElementById('newUsername').value.trim(); + const password = document.getElementById('newUserPassword').value; + const role = document.getElementById('newUserRole').value; + + if (!username || !password) { + showToast('Username and password are required', 'error'); + return; + } + + if (password.length < 6) { + showToast('Password must be at least 6 characters', 'error'); + return; + } + + try { + const response = await fetch('/api/users', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ username, password, role }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`User ${username} created successfully`); + closeAddUserModal(); + loadUsers(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to create user', 'error'); + } +} + +// Edit User Modal +function showEditUserModal(userId, username, role) { + document.getElementById('editUserId').value = userId; + document.getElementById('editUserUsername').value = username; + document.getElementById('editUserRole').value = role; + document.getElementById('resetUserPassword').value = ''; + document.getElementById('editUserModal').classList.add('show'); +} + +function closeEditUserModal() { + document.getElementById('editUserModal').classList.remove('show'); +} + +async function confirmEditUser() { + const userId = document.getElementById('editUserId').value; + const role = document.getElementById('editUserRole').value; + const password = document.getElementById('resetUserPassword').value; + + try { + // Change role + const roleResponse = await fetch(`/api/users/${userId}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify({ action: 'change_role', role }) + }); + + if (handleAuthError(null, roleResponse)) return; + + const roleResult = await roleResponse.json(); + + if (!roleResult.success) { + showToast('Error: ' + roleResult.error, 'error'); + return; + } + + // Reset password if provided + if (password && password.length >= 6) { + const passResponse = await fetch(`/api/users/${userId}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify({ action: 'reset_password', password }) + }); + + const passResult = await passResponse.json(); + + if (!passResult.success) { + showToast('Role updated but password reset failed: ' + passResult.error, 'error'); + closeEditUserModal(); + loadUsers(); + return; + } + } + + showToast('User updated successfully'); + closeEditUserModal(); + loadUsers(); + } catch (error) { + console.error('Error:', error); + showToast('Failed to update user', 'error'); + } +} + +// Delete User Modal +function showDeleteUserModal(userId, username) { + document.getElementById('deleteUserId').value = userId; + document.getElementById('deleteUserUsername').textContent = username; + document.getElementById('deleteUserModal').classList.add('show'); +} + +function closeDeleteUserModal() { + document.getElementById('deleteUserModal').classList.remove('show'); +} + +async function confirmDeleteUser() { + const userId = document.getElementById('deleteUserId').value; + + try { + const response = await fetch(`/api/users/${userId}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('User deleted successfully'); + closeDeleteUserModal(); + loadUsers(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to delete user', 'error'); + } +} + +// Toggle User Status +async function toggleUserStatus(userId, activate) { + const action = activate ? 'activate' : 'deactivate'; + + try { + const response = await fetch(`/api/users/${userId}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify({ action }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`User ${activate ? 'activated' : 'deactivated'} successfully`); + loadUsers(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to change user status', 'error'); + } +} \ No newline at end of file diff --git a/web/static/script_v15.js b/web/static/script_v15.js new file mode 100644 index 00000000..dc450a0a --- /dev/null +++ b/web/static/script_v15.js @@ -0,0 +1,844 @@ +// BetterDesk Console v1.5.0 - Enhanced UI with Sidebar Navigation +// Global variables +let allDevices = []; +let currentDeviceId = null; +let authToken = null; +let userRole = null; +let username = null; +let publicKeyCache = null; + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + // Check authentication + if (!checkAuth()) return; + + // Setup user info in sidebar + setupUserInfo(); + + // Setup sidebar navigation + setupSidebar(); + + // Load initial data + loadDevices(); + loadStats(); + + // Auto-refresh dashboard every 5 seconds + setInterval(() => { + const dashboardSection = document.getElementById('dashboard'); + if (dashboardSection && dashboardSection.classList.contains('active')) { + loadDevices(); + loadStats(); + } + }, 5000); +}); + +// Authentication check +function checkAuth() { + authToken = localStorage.getItem('authToken'); + userRole = localStorage.getItem('role'); + username = localStorage.getItem('username'); + + if (!authToken) { + window.location.href = '/login'; + return false; + } + + return true; +} + +// Setup user info in sidebar +function setupUserInfo() { + document.getElementById('sidebarUsername').textContent = username || 'User'; + document.getElementById('sidebarRole').textContent = userRole || 'viewer'; + + // Show admin-only sections + if (userRole === 'admin') { + document.querySelectorAll('.admin-only').forEach(el => { + el.style.display = ''; + }); + } +} + +// Setup sidebar navigation +function setupSidebar() { + document.querySelectorAll('.sidebar-item').forEach(item => { + item.addEventListener('click', function(e) { + e.preventDefault(); + const sectionId = this.dataset.section; + + // Update active states + document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active')); + this.classList.add('active'); + + // Show selected section + document.querySelectorAll('.content-section').forEach(s => s.classList.remove('active')); + document.getElementById(sectionId).classList.add('active'); + + // Load section-specific data + if (sectionId === 'users' && userRole === 'admin') { + loadUsers(); + } + }); + }); +} + +// Get auth headers for API calls +function getAuthHeaders() { + return { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }; +} + +// Handle authentication errors +function handleAuthError(error, response) { + if (response && response.status === 401) { + localStorage.removeItem('authToken'); + localStorage.removeItem('username'); + localStorage.removeItem('role'); + window.location.href = '/login'; + return true; + } + return false; +} + +// Logout function +async function logout() { + try { + await fetch('/api/auth/logout', { + method: 'POST', + headers: getAuthHeaders() + }); + } catch (error) { + console.error('Logout error:', error); + } finally { + localStorage.removeItem('authToken'); + localStorage.removeItem('username'); + localStorage.removeItem('role'); + window.location.href = '/login'; + } +} + +// ============================================================================ +// DASHBOARD - DEVICE MANAGEMENT +// ============================================================================ + +// Load devices from API +async function loadDevices() { + if (!checkAuth()) return; + + try { + const response = await fetch('/api/devices', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const data = await response.json(); + + if (data.success) { + allDevices = data.devices; + renderDevices(allDevices); + updateNavStats(allDevices); + } else { + showToast('Error loading devices: ' + data.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to load devices', 'error'); + } +} + +// Load statistics +async function loadStats() { + if (!checkAuth()) return; + + try { + const response = await fetch('/api/stats', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const data = await response.json(); + + if (data.success) { + document.getElementById('statTotal').textContent = data.stats.total; + document.getElementById('statActive').textContent = data.stats.active; + document.getElementById('statInactive').textContent = data.stats.inactive; + document.getElementById('statBanned').textContent = data.stats.banned || 0; + document.getElementById('statNotes').textContent = data.stats.with_notes; + } + } catch (error) { + console.error('Error loading stats:', error); + } +} + +// Update navigation stats +function updateNavStats(devices) { + const total = devices.length; + const active = devices.filter(d => d.online).length; + + const totalDevicesEl = document.querySelector('#totalDevices span'); + const activeDevicesEl = document.querySelector('#activeDevices span'); + + if (totalDevicesEl) totalDevicesEl.textContent = total; + if (activeDevicesEl) activeDevicesEl.textContent = active; +} + +// Render devices table +function renderDevices(devices) { + const tbody = document.getElementById('devicesTableBody'); + + if (devices.length === 0) { + tbody.innerHTML = ` + + + + No devices found + + + `; + return; + } + + tbody.innerHTML = devices.map(device => { + const isBanned = device.is_banned === true || device.is_banned === 1; + const rowClass = isBanned ? 'style="opacity: 0.6; background: rgba(255, 0, 0, 0.05);"' : ''; + + const canEdit = userRole === 'admin' || userRole === 'operator'; + const canBan = userRole === 'admin' || userRole === 'operator'; + + return ` + + + ${escapeHtml(device.id)} + ${isBanned ? '
BANNED' : ''} + + ${escapeHtml(device.note) || 'No note'} + + + + ${device.online ? 'Online' : 'Offline'} + + + ${formatDate(device.created_at)} + + + + ${canEdit ? ` + + ` : ''} + ${canBan ? (isBanned ? + `` : + `` + ) : ''} + ${canEdit ? ` + + ` : ''} + + + `}).join(''); +} + +// Filter devices by search +function filterDevices() { + const searchTerm = document.getElementById('searchInput').value.toLowerCase(); + + if (!searchTerm) { + renderDevices(allDevices); + return; + } + + const filtered = allDevices.filter(device => + device.id.toLowerCase().includes(searchTerm) || + (device.note && device.note.toLowerCase().includes(searchTerm)) + ); + + renderDevices(filtered); +} + +// Connect to device +function connectDevice(deviceId) { + window.location.href = `rustdesk://${deviceId}`; + showToast(`Connecting to ${deviceId}...`); +} + +// Show device details +function showDetails(deviceId) { + const device = allDevices.find(d => d.id === deviceId); + if (!device) return; + + const isBanned = device.is_banned === true || device.is_banned === 1; + + const detailsContent = document.getElementById('detailsContent'); + detailsContent.innerHTML = ` +
+
ID:
+
${escapeHtml(device.id)}
+
+
+
GUID:
+
${escapeHtml(device.guid) || 'N/A'}
+
+
+
UUID:
+
${escapeHtml(device.uuid) || 'N/A'}
+
+
+
Status:
+
+ + + ${device.online ? 'Online' : 'Offline'} + +
+
+ ${isBanned ? ` +
+
BAN STATUS:
+
BANNED
+
+
+
Banned At:
+
${device.banned_at ? formatDate(device.banned_at) : 'N/A'}
+
+
+
Banned By:
+
${escapeHtml(device.banned_by) || 'N/A'}
+
+
+
Ban Reason:
+
${escapeHtml(device.ban_reason) || 'No reason provided'}
+
+ ` : ''} +
+
Note:
+
${escapeHtml(device.note) || 'No note'}
+
+
+
Created:
+
${formatDate(device.created_at)}
+
+ `; + + openModal('detailsModal'); +} + +// Edit device +function editDevice(deviceId) { + const device = allDevices.find(d => d.id === deviceId); + if (!device) return; + + currentDeviceId = deviceId; + document.getElementById('editDeviceId').value = deviceId; + document.getElementById('editNewId').value = ''; + document.getElementById('editNote').value = device.note || ''; + + openModal('editModal'); +} + +// Save device changes +async function saveDevice() { + if (!checkAuth()) return; + + const newId = document.getElementById('editNewId').value.trim(); + const note = document.getElementById('editNote').value.trim(); + + if (note.length > 500) { + showToast('Note is too long (max 500 characters)', 'error'); + return; + } + + const data = { note }; + if (newId) data.new_id = newId; + + try { + const response = await fetch(`/api/device/${currentDeviceId}`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify(data) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('Device updated successfully'); + closeEditModal(); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to update device', 'error'); + } +} + +// Delete device +function deleteDevice(deviceId) { + currentDeviceId = deviceId; + document.getElementById('deleteDeviceId').textContent = deviceId; + openModal('deleteModal'); +} + +// Confirm delete +async function confirmDelete() { + if (!checkAuth()) return; + + try { + const response = await fetch(`/api/device/${currentDeviceId}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('Device deleted successfully'); + closeDeleteModal(); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to delete device', 'error'); + } +} + +// Ban device +async function banDevice(deviceId) { + if (!checkAuth()) return; + + const reason = prompt(`⚠️ BAN DEVICE: ${deviceId}\n\nEnter ban reason (optional):`); + if (reason === null) return; + + try { + const response = await fetch(`/api/device/${deviceId}/ban`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + reason: reason || '', + banned_by: username + }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`Device ${deviceId} banned successfully`); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to ban device', 'error'); + } +} + +// Unban device +async function unbanDevice(deviceId) { + if (!checkAuth()) return; + + if (!confirm(`✓ UNBAN DEVICE: ${deviceId}\n\nAre you sure?`)) return; + + try { + const response = await fetch(`/api/device/${deviceId}/unban`, { + method: 'POST', + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`Device ${deviceId} unbanned successfully`); + await loadDevices(); + await loadStats(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to unban device', 'error'); + } +} + +// Refresh devices manually +async function refreshDevices() { + showToast('Refreshing devices...'); + await loadDevices(); + await loadStats(); +} + +// ============================================================================ +// PUBLIC KEY SECTION +// ============================================================================ + +async function verifyPasswordForKey() { + const password = document.getElementById('keyPassword').value; + + if (!password) { + showToast('Please enter your password', 'error'); + return; + } + + try { + const response = await fetch('/api/auth/verify-password', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ password: password }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + // Password correct, fetch public key + const keyResponse = await fetch('/api/public-key', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, keyResponse)) return; + + const keyData = await keyResponse.json(); + + if (keyData.success) { + publicKeyCache = keyData.key; + document.getElementById('publicKeyDisplay').textContent = keyData.key; + document.getElementById('keyPasswordPrompt').style.display = 'none'; + document.getElementById('keyContent').style.display = 'block'; + document.getElementById('keyPassword').value = ''; + showToast('Public key revealed'); + } else { + showToast('Error loading key: ' + keyData.error, 'error'); + } + } else { + showToast('Incorrect password', 'error'); + document.getElementById('keyPassword').value = ''; + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to verify password', 'error'); + } +} + +function copyPublicKey() { + const keyText = document.getElementById('publicKeyDisplay').textContent; + navigator.clipboard.writeText(keyText).then(() => { + showToast('Public key copied to clipboard'); + }).catch(err => { + showToast('Failed to copy', 'error'); + }); +} + +function lockKey() { + publicKeyCache = null; + document.getElementById('keyPasswordPrompt').style.display = 'block'; + document.getElementById('keyContent').style.display = 'none'; + document.getElementById('keyPassword').value = ''; +} + +// ============================================================================ +// SETTINGS - PASSWORD CHANGE +// ============================================================================ + +async function changePassword(event) { + event.preventDefault(); + + const currentPassword = document.getElementById('currentPassword').value; + const newPassword = document.getElementById('newPassword').value; + const confirmPassword = document.getElementById('confirmPassword').value; + + if (newPassword !== confirmPassword) { + showToast('New passwords do not match', 'error'); + return; + } + + if (newPassword.length < 8) { + showToast('Password must be at least 8 characters', 'error'); + return; + } + + try { + const response = await fetch('/api/auth/change-password', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + old_password: currentPassword, + new_password: newPassword + }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('Password changed successfully'); + document.getElementById('passwordForm').reset(); + // New token issued, update local storage + if (result.token) { + authToken = result.token; + localStorage.setItem('authToken', result.token); + } + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to change password', 'error'); + } +} + +// ============================================================================ +// USER MANAGEMENT (ADMIN ONLY) +// ============================================================================ + +async function loadUsers() { + if (!checkAuth()) return; + if (userRole !== 'admin') return; + + try { + const response = await fetch('/api/users', { + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const data = await response.json(); + + if (data.success) { + renderUsers(data.users); + } else { + showToast('Error loading users: ' + data.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to load users', 'error'); + } +} + +function renderUsers(users) { + const tbody = document.getElementById('usersTableBody'); + + if (users.length === 0) { + tbody.innerHTML = 'No users found'; + return; + } + + tbody.innerHTML = users.map(user => { + const statusClass = user.is_active ? 'status-active' : 'status-inactive'; + const statusText = user.is_active ? 'Active' : 'Inactive'; + + let roleClass = 'role-viewer'; + if (user.role === 'admin') roleClass = 'role-admin'; + else if (user.role === 'operator') roleClass = 'role-operator'; + + const lastLogin = user.last_login ? formatDate(user.last_login) : 'Never'; + + return ` + + ${escapeHtml(user.username)} + ${user.role} + ${lastLogin} + ${statusText} + + + + + + `; + }).join(''); +} + +// Add User Modal +function showAddUserModal() { + openModal('addUserModal'); +} + +function closeAddUserModal() { + closeModal('addUserModal'); + document.getElementById('newUsername').value = ''; + document.getElementById('newUserPassword').value = ''; + document.getElementById('newUserRole').value = 'viewer'; +} + +async function createUser() { + const username = document.getElementById('newUsername').value.trim(); + const password = document.getElementById('newUserPassword').value; + const role = document.getElementById('newUserRole').value; + + if (!username || !password) { + showToast('Username and password are required', 'error'); + return; + } + + if (password.length < 8) { + showToast('Password must be at least 8 characters', 'error'); + return; + } + + try { + const response = await fetch('/api/users', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ username, password, role }) + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast(`User ${username} created successfully`); + closeAddUserModal(); + loadUsers(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to create user', 'error'); + } +} + +// Edit/Delete User (placeholders - to be implemented with proper modals) +function showEditUserModal(userId, username, role, isActive) { + // TODO: Implement edit user modal + showToast('Edit user functionality - coming soon'); +} + +function showDeleteUserModal(userId, username) { + if (!confirm(`⚠️ DELETE USER: ${username}\n\nAre you sure?`)) return; + deleteUser(userId); +} + +async function deleteUser(userId) { + try { + const response = await fetch(`/api/users/${userId}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + + if (handleAuthError(null, response)) return; + + const result = await response.json(); + + if (result.success) { + showToast('User deleted successfully'); + loadUsers(); + } else { + showToast('Error: ' + result.error, 'error'); + } + } catch (error) { + console.error('Error:', error); + showToast('Failed to delete user', 'error'); + } +} + +// ============================================================================ +// MODAL FUNCTIONS +// ============================================================================ + +function openModal(modalId) { + document.getElementById(modalId).classList.add('active'); +} + +function closeModal(modalId) { + document.getElementById(modalId).classList.remove('active'); +} + +function closeEditModal() { + closeModal('editModal'); + currentDeviceId = null; +} + +function closeDeleteModal() { + closeModal('deleteModal'); + currentDeviceId = null; +} + +function closeDetailsModal() { + closeModal('detailsModal'); +} + +// Close modal when clicking outside +window.onclick = function(event) { + if (event.target.classList.contains('modal')) { + event.target.classList.remove('active'); + } +} + +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +function showToast(message, type = 'success') { + const toast = document.getElementById('toast'); + const icon = toast.querySelector('i'); + + if (type === 'error') { + icon.className = 'fas fa-exclamation-circle'; + icon.style.color = 'var(--danger-color)'; + } else { + icon.className = 'fas fa-check-circle'; + icon.style.color = 'var(--success-color)'; + } + + document.getElementById('toastMessage').textContent = message; + toast.classList.add('show'); + + setTimeout(() => { + toast.classList.remove('show'); + }, 3000); +} + +function escapeHtml(text) { + if (!text) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +function formatDate(dateString) { + if (!dateString) return 'N/A'; + const date = new Date(dateString); + const options = { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }; + return date.toLocaleDateString('en-US', options); +} diff --git a/web/static/sidebar.css b/web/static/sidebar.css new file mode 100644 index 00000000..723a1b5e --- /dev/null +++ b/web/static/sidebar.css @@ -0,0 +1,412 @@ +/* Sidebar Styles for BetterDesk Console v1.4.0 */ + +:root { + --sidebar-width: 280px; +} + +/* Layout adjustments for sidebar */ +body.has-sidebar { + display: flex; + min-height: 100vh; +} + +/* Sidebar Container */ +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(20px); + border-right: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + flex-direction: column; + z-index: 1000; + overflow-y: auto; +} + +/* Sidebar Header */ +.sidebar-header { + padding: 24px 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar-brand { + display: flex; + align-items: center; + gap: 12px; + color: white; + font-size: 20px; + font-weight: 700; + white-space: nowrap; +} + +.sidebar-brand i { + font-size: 28px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.brand-text { + /* Text always visible */ +} + +/* Toggle button removed - sidebar always expanded */ + +/* Sidebar User Section */ +.sidebar-user { + padding: 20px; + display: flex; + align-items: center; + gap: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.user-avatar { + width: 48px; + height: 48px; + border-radius: 12px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + flex-shrink: 0; +} + +.user-info { + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; +} + +.user-name { + color: white; + font-weight: 600; + font-size: 14px; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-role { + color: rgba(255, 255, 255, 0.7); + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Sidebar Menu */ +.sidebar-menu { + flex: 1; + padding: 12px; + overflow-y: auto; + overflow-x: hidden; +} + +.sidebar-menu::-webkit-scrollbar { + width: 4px; +} + +.sidebar-menu::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +.menu-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + border-radius: 12px; + transition: all 0.3s; + margin-bottom: 4px; + cursor: pointer; + position: relative; + white-space: nowrap; +} + +.menu-item i { + font-size: 18px; + width: 20px; + text-align: center; + flex-shrink: 0; +} + +.menu-item span { + font-size: 14px; + font-weight: 500; +} + +.menu-item:hover { + background: rgba(255, 255, 255, 0.1); + color: white; + transform: translateX(4px); +} + +.menu-item.active { + background: rgba(255, 255, 255, 0.15); + color: white; + font-weight: 600; +} + +.menu-item.active::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 4px; + height: 60%; + background: white; + border-radius: 0 4px 4px 0; +} + +/* Sidebar Footer */ +.sidebar-footer { + padding: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.logout-btn { + color: #ff6b6b !important; + justify-content: flex-start; +} + +.logout-btn:hover { + background: rgba(255, 107, 107, 0.1) !important; +} + +/* Main Content Area */ +.main-content { + flex: 1; + margin-left: var(--sidebar-width); + display: flex; + flex-direction: column; + min-height: 100vh; +} + +/* Top Navbar */ +.top-navbar { + position: sticky; + top: 0; + z-index: 999; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(20px); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding: 16px 24px; +} + +.nav-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; +} + +.mobile-menu-toggle { + display: none; + background: rgba(255, 255, 255, 0.1); + border: none; + color: white; + width: 40px; + height: 40px; + border-radius: 8px; + cursor: pointer; + font-size: 18px; +} + +.page-title { + color: white; + font-size: 24px; + font-weight: 700; + margin: 0; + flex: 1; +} + +.nav-stats { + display: flex; + gap: 12px; +} + +.stat-badge { + background: rgba(255, 255, 255, 0.1); + padding: 8px 16px; + border-radius: 12px; + display: flex; + align-items: center; + gap: 8px; + color: white; +} + +.stat-badge i { + font-size: 16px; +} + +.stat-badge.active { + background: rgba(76, 175, 80, 0.2); + color: #4caf50; +} + +/* Content Container */ +.content-container { + flex: 1; + padding: 24px; + overflow-y: auto; +} + +.page-content { + display: none; +} + +.page-content.active { + display: block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Action Bar */ +.action-bar { + margin-bottom: 24px; + display: flex; + gap: 12px; + justify-content: flex-end; +} + +/* Responsive Design */ +@media (max-width: 1024px) { + :root { + --sidebar-width: 260px; + } +} + +@media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + } + + .sidebar.mobile-open { + transform: translateX(0); + } + + .main-content { + margin-left: 0 !important; + } + + .mobile-menu-toggle { + display: flex; + align-items: center; + justify-content: center; + } + + .page-title { + font-size: 20px; + } + + .nav-stats { + display: none; + } + + .content-container { + padding: 16px; + } + + /* Overlay when sidebar is open on mobile */ + .sidebar-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; + display: none; + } + + .sidebar.mobile-open ~ .sidebar-overlay { + display: block; + } +} + +/* Settings Page Styles */ +.settings-container { + padding: 24px; + border-radius: 16px; +} + +.settings-section { + margin-bottom: 24px; +} + +.settings-section h3 { + color: white; + margin-bottom: 16px; +} + +/* Key Container */ +.key-container { + padding: 24px; + border-radius: 16px; +} + +.key-display { + background: rgba(0, 0, 0, 0.2); + padding: 16px; + border-radius: 12px; + margin: 16px 0; + word-break: break-all; +} + +.key-display code { + color: #4caf50; + font-family: 'Courier New', monospace; +} + +/* About Container */ +.about-container { + padding: 24px; + border-radius: 16px; + color: white; +} + +.about-container h2 { + margin-bottom: 20px; +} + +.about-container p { + margin: 12px 0; +} + +.about-container ul { + list-style: none; + padding-left: 0; +} + +.about-container li { + padding: 8px 0; + padding-left: 24px; + position: relative; +} + +.about-container li::before { + content: '✓'; + position: absolute; + left: 0; + color: #4caf50; +} diff --git a/web/static/sidebar.js b/web/static/sidebar.js new file mode 100644 index 00000000..428f126e --- /dev/null +++ b/web/static/sidebar.js @@ -0,0 +1,210 @@ +/* Sidebar JavaScript for BetterDesk Console v1.4.0 */ + +// Initialize sidebar +document.addEventListener('DOMContentLoaded', function() { + initializeSidebar(); + loadUserInfo(); + setupMenuNavigation(); + setupMobileMenu(); +}); + +function initializeSidebar() { + // Sidebar is always expanded - no toggle needed + console.log('Sidebar initialized (always expanded)'); +} + +function loadUserInfo() { + const username = localStorage.getItem('username') || 'User'; + const role = localStorage.getItem('role') || 'viewer'; + + // Update sidebar user info + const usernameEl = document.getElementById('sidebarUsername'); + const userRoleEl = document.getElementById('sidebarUserRole'); + + if (usernameEl) { + usernameEl.textContent = username; + } + + if (userRoleEl) { + const roleNames = { + 'admin': 'Administrator', + 'operator': 'Operator', + 'viewer': 'Viewer' + }; + userRoleEl.textContent = roleNames[role] || role; + } + + // Show/hide menu items based on role + updateMenuVisibility(role); +} + +function updateMenuVisibility(role) { + const menuUsers = document.getElementById('menuUsers'); + const menuAudit = document.getElementById('menuAudit'); + const menuSettings = document.getElementById('menuSettings'); + const menuKey = document.getElementById('menuKey'); + + // Admin sees everything + if (role === 'admin') { + if (menuUsers) menuUsers.style.display = 'flex'; + if (menuAudit) menuAudit.style.display = 'flex'; + if (menuSettings) menuSettings.style.display = 'flex'; + if (menuKey) menuKey.style.display = 'flex'; + } + // Operator sees audit and settings + else if (role === 'operator') { + if (menuUsers) menuUsers.style.display = 'none'; + if (menuAudit) menuAudit.style.display = 'flex'; + if (menuSettings) menuSettings.style.display = 'flex'; + if (menuKey) menuKey.style.display = 'none'; + } + // Viewer sees only settings + else { + if (menuUsers) menuUsers.style.display = 'none'; + if (menuAudit) menuAudit.style.display = 'none'; + if (menuSettings) menuSettings.style.display = 'flex'; + if (menuKey) menuKey.style.display = 'none'; + } +} + +function setupMenuNavigation() { + const menuItems = document.querySelectorAll('.menu-item[data-page]'); + + menuItems.forEach(item => { + item.addEventListener('click', function(e) { + e.preventDefault(); + + const page = this.dataset.page; + + // Update active menu item + menuItems.forEach(mi => mi.classList.remove('active')); + this.classList.add('active'); + + // Show corresponding page + showPage(page); + + // Close mobile menu if open + closeMobileMenu(); + }); + }); +} + +function showPage(pageName) { + // Hide all pages + const pages = document.querySelectorAll('.page-content'); + pages.forEach(page => page.classList.remove('active')); + + // Show selected page + const targetPage = document.getElementById(pageName + 'Page'); + if (targetPage) { + targetPage.classList.add('active'); + } + + // Update page title + const pageTitles = { + 'dashboard': 'Device Management', + 'users': 'User Management', + 'audit': 'Audit Log', + 'settings': 'Settings', + 'key': 'Public Key', + 'about': 'About BetterDesk' + }; + + const pageTitle = document.getElementById('pageTitle'); + if (pageTitle && pageTitles[pageName]) { + pageTitle.textContent = pageTitles[pageName]; + } + + // Load page-specific data + if (pageName === 'dashboard') { + if (typeof refreshDevices === 'function') { + refreshDevices(); + } + } else if (pageName === 'users') { + if (typeof loadUsers === 'function') { + loadUsers(); + } + } +} + +function setupMobileMenu() { + const mobileToggle = document.getElementById('mobileMenuToggle'); + const sidebar = document.getElementById('sidebar'); + + if (mobileToggle) { + mobileToggle.addEventListener('click', function() { + sidebar.classList.toggle('mobile-open'); + + // Create/remove overlay + if (sidebar.classList.contains('mobile-open')) { + createOverlay(); + } else { + removeOverlay(); + } + }); + } +} + +function createOverlay() { + const existing = document.querySelector('.sidebar-overlay'); + if (existing) return; + + const overlay = document.createElement('div'); + overlay.className = 'sidebar-overlay'; + overlay.addEventListener('click', closeMobileMenu); + document.body.appendChild(overlay); +} + +function removeOverlay() { + const overlay = document.querySelector('.sidebar-overlay'); + if (overlay) { + overlay.remove(); + } +} + +function closeMobileMenu() { + const sidebar = document.getElementById('sidebar'); + sidebar.classList.remove('mobile-open'); + removeOverlay(); +} + +async function logout() { + if (!confirm('Are you sure you want to logout?')) { + return; + } + + const token = localStorage.getItem('authToken'); + + // Call logout API + try { + await fetch('/api/auth/logout', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + } + }); + } catch (error) { + console.error('Logout error:', error); + } + + // Clear local storage + localStorage.removeItem('authToken'); + localStorage.removeItem('username'); + localStorage.removeItem('role'); + + // Redirect to login + window.location.href = '/login'; +} + +function showChangePasswordModal() { + // TODO: Implement change password modal + alert('Change password functionality coming soon!'); +} + +// Export functions for use in other scripts +window.sidebarFunctions = { + showPage, + logout, + loadUserInfo, + updateMenuVisibility +}; diff --git a/web/static/style.css b/web/static/style.css index 9fa8ecdc..b81c5b2b 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -699,3 +699,173 @@ textarea.form-control { flex-wrap: wrap; } } +/* About Page Styles */ +.about-container { + padding: 2rem; + max-width: 900px; + margin: 0 auto; +} + +.about-container h2 { + font-size: 2rem; + margin-bottom: 2rem; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 0.75rem; +} + +.about-section { + margin-bottom: 2.5rem; +} + +.about-section h3 { + font-size: 1.4rem; + margin-bottom: 1rem; + color: var(--primary-color); +} + +.about-section p { + margin-bottom: 0.75rem; + line-height: 1.6; + color: var(--text-secondary); +} + +.about-section ul { + list-style: none; + padding-left: 0; +} + +.about-section ul li { + margin-bottom: 0.75rem; + padding-left: 1.5rem; + position: relative; + line-height: 1.6; + color: var(--text-secondary); +} + +.about-section ul li i { + position: absolute; + left: 0; + top: 0.25rem; + color: var(--success-color); +} + +.about-section ul li strong { + color: var(--text-primary); +} + +.github-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); + color: var(--text-primary); + text-decoration: none; + border-radius: 8px; + font-weight: 600; + transition: all 0.3s ease; + border: 1px solid var(--glass-border); +} + +.github-link:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(107, 114, 128, 0.4); + background: linear-gradient(135deg, #4b5563 0%, #374151 100%); +} + +.github-link i { + font-size: 1.2rem; +} + +/* User Management Styles */ +.users-container { + padding: 2rem; +} + +.users-container h2 { + margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.75rem; +} +/* Badge Styles */ +.badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.85rem; + font-weight: 600; + text-transform: capitalize; +} + +.badge-success { + background: var(--success-color); + color: white; +} + +.badge-danger { + background: var(--danger-color); + color: white; +} + +.badge-warning { + background: var(--warning-color); + color: white; +} + +.badge-info { + background: var(--info-color); + color: white; +} + +/* Settings Page Styles */ +.settings-container { + padding: 2rem; + max-width: 800px; + margin: 0 auto; +} + +.settings-container h2 { + font-size: 2rem; + margin-bottom: 2rem; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.settings-section { + margin-bottom: 2rem; + padding: 1.5rem; + background: rgba(30, 30, 30, 0.5); + border-radius: 12px; + border: 1px solid var(--glass-border); +} + +.settings-section h3 { + font-size: 1.3rem; + margin-bottom: 1rem; + color: var(--primary-color); +} + +.settings-section p { + margin-bottom: 1rem; + color: var(--text-secondary); + line-height: 1.6; +} + +/* Key Page Styles */ +.key-container { + padding: 2rem; + max-width: 800px; + margin: 0 auto; +} + +.key-container h2 { + font-size: 2rem; + margin-bottom: 1.5rem; + display: flex; + align-items: center; + gap: 0.75rem; +} diff --git a/web/templates/index.html b/web/templates/index.html deleted file mode 100644 index 6f8cc13e..00000000 --- a/web/templates/index.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - RustDesk Console - Dashboard - - - - - -
- - - - - -
- -
-

Device Management

-
- - -
-
- - -
-
-
- -
-
-
Total Devices
-
0
-
-
- -
-
- -
-
-
Active
-
0
-
-
- -
-
- -
-
-
Inactive
-
0
-
-
- -
-
- -
-
-
Banned
-
0
-
-
- -
-
- -
-
-
With Notes
-
0
-
-
-
- - -
-
-

Devices

- -
- -
- - - - - - - - - - - - - - - -
IDNoteStatusCreatedActions
-
- Loading devices... -
-
-
-
- - - - - - - - - - - - - - -
- - -
- - - - diff --git a/web/templates/index_v14.html b/web/templates/index_v14.html new file mode 100644 index 00000000..627f9f0e --- /dev/null +++ b/web/templates/index_v14.html @@ -0,0 +1,536 @@ + + + + + + BetterDesk Console - Dashboard + + + + + + +
+ + + + + +
+ + + + +
+ +
+ +
+ +
+ + +
+
+
+ +
+
+
Total Devices
+
0
+
+
+ +
+
+ +
+
+
Active
+
0
+
+
+ +
+
+ +
+
+
Inactive
+
0
+
+
+ +
+
+ +
+
+
Banned
+
0
+
+
+ +
+
+ +
+
+
With Notes
+
0
+
+
+
+ + +
+
+

Devices

+ +
+ +
+ + + + + + + + + + + + + + + +
IDNoteStatusCreatedActions
+
+ Loading devices... +
+
+
+
+ + +
+

Devices management page (to be implemented)

+
+ +
+
+

User Management

+
+ +
+
+ + + + + + + + + + + + + + + + +
UsernameRoleStatusCreatedLast LoginActions
Loading users...
+
+
+
+ +
+
+

Audit Log

+

Audit log page (to be implemented)

+
+
+ +
+
+

Settings

+
+

Account Security

+

Change your account password to keep your account secure.

+ +
+
+
+ +
+
+

RustDesk Public Key

+

For security reasons, please verify your password to view the public key.

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

About BetterDesk Console

+ +
+

Version Information

+

Version: 1.4.0

+

Build: v9

+
+ +
+

Features

+
    +
  • Real-time device monitoring and management
  • +
  • Bidirectional ban enforcement (source + target)
  • +
  • User authentication & role-based access control
  • +
  • Comprehensive audit logging
  • +
  • HTTP API for device status
  • +
  • Modern glassmorphism UI design
  • +
+
+ +
+

Built With Open Source

+

This project is built using the following open source technologies:

+
    +
  • RustDesk - Open source remote desktop software (AGPL-3.0)
  • +
  • Flask - Python web framework (BSD-3-Clause)
  • +
  • SQLite - Embedded database (Public Domain)
  • +
  • bcrypt - Password hashing library (Apache-2.0)
  • +
  • Font Awesome - Icon library (Font Awesome Free License)
  • +
+
+ +
+

Repository & Documentation

+

GitHub Repository:

+

+ + github.com/UNITRONIX/Rustdesk-FreeConsole + +

+

+ Find documentation, installation guides, and source code in the repository. +

+
+ +
+

License

+

MIT License - Free to use and modify

+
+ +
+

Author

+

Developed by UNITRONIX

+

With contributions from the open source community

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + diff --git a/web/templates/index_v15.html b/web/templates/index_v15.html new file mode 100644 index 00000000..88829e37 --- /dev/null +++ b/web/templates/index_v15.html @@ -0,0 +1,829 @@ + + + + + + BetterDesk Console v1.5 + + + + + + +
+ + + + + +
+ +
+ + + + +
+ +
+

Device Dashboard

+
+ +
+
+ + +
+
+
+ +
+
+
Total Devices
+
0
+
+
+ +
+
+ +
+
+
Active
+
0
+
+
+ +
+
+ +
+
+
Inactive
+
0
+
+
+ +
+
+ +
+
+
Banned
+
0
+
+
+ +
+
+ +
+
+
With Notes
+
0
+
+
+
+ + +
+
+

Connected Devices

+ +
+ +
+ + + + + + + + + + + + + + + +
IDNoteStatusCreatedActions
+
+ Loading devices... +
+
+
+
+
+ + +
+
+

Server Public Key

+ +
+

Protected Content

+

+ Enter your password to view the server public key +

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

Account Settings

+ +
+

Change Password

+
+
+ + +
+
+ + + + Minimum 8 characters, must contain letters and numbers + +
+
+ + +
+ +
+
+
+
+ + + + + +
+
+

About BetterDesk Console

+ +
+

Project Information

+

+ BetterDesk Console v1.5.0 - Advanced management interface for RustDesk Server +

+

+ A powerful, secure web console for managing RustDesk remote desktop server. Features include + device management, user authentication with role-based access control, ban enforcement with + fail-closed security policy, and comprehensive audit logging. +

+
+ +
+

Source Code

+

+ This project is open source and available on GitHub: +

+

+ + github.com/UNITRONIX/Rustdesk-FreeConsole + +

+

+ Issues, feature requests, and contributions are welcome! +

+
+ +
+

Open Source Components

+

This project is built with the following open source software:

+ +
+ +
+

Security Features

+
    +
  • Authentication System - Secure login with bcrypt password hashing
  • +
  • Role-Based Access Control - Admin, Operator, and Viewer roles
  • +
  • CSRF Protection - Cross-Site Request Forgery prevention
  • +
  • Rate Limiting - Protection against brute force attacks
  • +
  • Fail-Closed Policy - Banned devices cannot connect even if service restarts
  • +
  • Audit Logging - Complete history of user actions
  • +
  • Content Security Policy - Protection against XSS attacks
  • +
+
+ +
+

Credits

+

+ Developed by UNITRONIX
+ Special thanks to the RustDesk team and open source community. +

+

+ © 2026 UNITRONIX. Released under AGPL-3.0 License. +

+
+
+
+
+ + + + + + + + + + + + + + +
+ + +
+ + + + diff --git a/web/templates/login.html b/web/templates/login.html new file mode 100644 index 00000000..4a677428 --- /dev/null +++ b/web/templates/login.html @@ -0,0 +1,378 @@ + + + + + + Login - BetterDesk Console + + + + + + + + +