mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
e855f5786d
Add a new betterdesk-server Go codebase (server, api, auth, db, relay, signal, metrics, audit, ratelimit, proto, tools, tests) and related deployment/migration scripts. Add a comprehensive SECURITY_AUDIT_2026-03-01 report and .gitattributes; update copilot-instructions (ALL-IN-ONE v2.4.0), README, VERSION, Dockerfiles, scripts, docker-compose and entrypoint. Large updates to web-nodejs (translations, routes, services, frontend assets and middleware) and numerous new utilities; remove legacy Flask web files and archive hbbs-patch-v2 artifacts. Prepares repository for PostgreSQL support, DB migration tooling and the new Go server as the production backend.
530 lines
16 KiB
Bash
530 lines
16 KiB
Bash
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# BetterDesk Go Server — Production Deployment Script
|
|
# Version: 1.0.0
|
|
# Replaces: hbbs + hbbr (Rust) with a single betterdesk-server binary (Go)
|
|
#
|
|
# Usage:
|
|
# sudo ./deploy.sh # Interactive deployment
|
|
# sudo ./deploy.sh --auto # Automatic deployment with defaults
|
|
# sudo ./deploy.sh --migrate-only # Only migrate database, don't install
|
|
# sudo ./deploy.sh --rollback # Restore from backup and revert to Rust
|
|
#
|
|
# Requirements:
|
|
# - Linux x86_64
|
|
# - Root or sudo access
|
|
# - Existing RustDesk/BetterDesk installation (optional, for migration)
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
VERSION="1.0.0"
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Default paths (matching betterdesk.sh conventions)
|
|
INSTALL_DIR="/opt/betterdesk"
|
|
OLD_INSTALL_DIR="/opt/rustdesk"
|
|
DATA_DIR="${INSTALL_DIR}/data"
|
|
BACKUP_DIR="${INSTALL_DIR}/backups"
|
|
LOG_DIR="/var/log/betterdesk"
|
|
SERVICE_NAME="betterdesk"
|
|
|
|
# Binary names
|
|
GO_BINARY="betterdesk-server-linux-amd64"
|
|
MIGRATE_BINARY="migrate-linux-amd64"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
log_step() { echo -e "${BLUE}[STEP]${NC} $*"; }
|
|
|
|
# ============================================================================
|
|
# Pre-flight checks
|
|
# ============================================================================
|
|
|
|
check_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
log_error "This script must be run as root (sudo)."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
check_binary() {
|
|
if [[ ! -f "${SCRIPT_DIR}/${GO_BINARY}" ]]; then
|
|
log_error "Binary not found: ${SCRIPT_DIR}/${GO_BINARY}"
|
|
log_error "Build it first: GOOS=linux GOARCH=amd64 go build -o ${GO_BINARY} ."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
detect_existing_installation() {
|
|
# Check for existing Rust hbbs/hbbr installation
|
|
EXISTING_DB=""
|
|
EXISTING_KEY=""
|
|
EXISTING_DIR=""
|
|
|
|
for dir in /opt/rustdesk /opt/betterdesk /opt/hbbs; do
|
|
if [[ -d "$dir" ]]; then
|
|
# Look for the database file
|
|
for db in "$dir/db_v2.sqlite3" "$dir/data/db_v2.sqlite3" "$dir/db_v2.sqlite3"; do
|
|
if [[ -f "$db" ]]; then
|
|
EXISTING_DB="$db"
|
|
EXISTING_DIR="$dir"
|
|
break 2
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
|
|
# Find existing key file
|
|
for dir in /opt/rustdesk /opt/betterdesk /opt/hbbs; do
|
|
for key in "$dir/id_ed25519" "$dir/data/id_ed25519" "$dir/id_ed25519"; do
|
|
if [[ -f "$key" ]]; then
|
|
EXISTING_KEY="$key"
|
|
break 2
|
|
fi
|
|
done
|
|
done
|
|
|
|
if [[ -n "$EXISTING_DB" ]]; then
|
|
log_info "Found existing database: $EXISTING_DB"
|
|
fi
|
|
if [[ -n "$EXISTING_KEY" ]]; then
|
|
log_info "Found existing key file: $EXISTING_KEY"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Backup
|
|
# ============================================================================
|
|
|
|
create_backup() {
|
|
local timestamp
|
|
timestamp=$(date +%Y%m%d_%H%M%S)
|
|
local backup_path="${BACKUP_DIR}/backup_${timestamp}"
|
|
|
|
mkdir -p "$backup_path"
|
|
|
|
log_step "Creating backup at ${backup_path}..."
|
|
|
|
# Backup database
|
|
if [[ -n "$EXISTING_DB" ]]; then
|
|
cp -v "$EXISTING_DB" "${backup_path}/"
|
|
# Also copy WAL/SHM files
|
|
[[ -f "${EXISTING_DB}-wal" ]] && cp -v "${EXISTING_DB}-wal" "${backup_path}/"
|
|
[[ -f "${EXISTING_DB}-shm" ]] && cp -v "${EXISTING_DB}-shm" "${backup_path}/"
|
|
log_info "Database backed up"
|
|
fi
|
|
|
|
# Backup key file
|
|
if [[ -n "$EXISTING_KEY" ]]; then
|
|
cp -v "$EXISTING_KEY" "${backup_path}/"
|
|
[[ -f "${EXISTING_KEY}.pub" ]] && cp -v "${EXISTING_KEY}.pub" "${backup_path}/"
|
|
log_info "Key files backed up"
|
|
fi
|
|
|
|
# Backup existing services
|
|
for svc in rustdesksignal rustdeskrelay betterdesk hbbs hbbr; do
|
|
if [[ -f "/etc/systemd/system/${svc}.service" ]]; then
|
|
cp -v "/etc/systemd/system/${svc}.service" "${backup_path}/"
|
|
fi
|
|
done
|
|
|
|
# Backup existing binaries (for rollback)
|
|
if [[ -n "$EXISTING_DIR" ]]; then
|
|
for bin in hbbs hbbr betterdesk-server; do
|
|
[[ -f "${EXISTING_DIR}/${bin}" ]] && cp -v "${EXISTING_DIR}/${bin}" "${backup_path}/"
|
|
done
|
|
fi
|
|
|
|
echo "$timestamp" > "${backup_path}/backup.info"
|
|
log_info "Backup complete: ${backup_path}"
|
|
echo "$backup_path"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Migration
|
|
# ============================================================================
|
|
|
|
migrate_database() {
|
|
if [[ -z "$EXISTING_DB" ]]; then
|
|
log_info "No existing database found — starting fresh."
|
|
return 0
|
|
fi
|
|
|
|
local new_db="${DATA_DIR}/db_v2.sqlite3"
|
|
|
|
log_step "Migrating database from Rust to Go schema..."
|
|
|
|
if [[ -f "${SCRIPT_DIR}/tools/migrate/${MIGRATE_BINARY}" ]]; then
|
|
"${SCRIPT_DIR}/tools/migrate/${MIGRATE_BINARY}" \
|
|
-src "$EXISTING_DB" \
|
|
-dst "$new_db"
|
|
else
|
|
log_warn "Migration tool not found. Copying database as-is."
|
|
log_warn "The Go server will create new tables but won't read old 'peer' table data."
|
|
cp "$EXISTING_DB" "$new_db"
|
|
fi
|
|
|
|
log_info "Database migration complete: $new_db"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Installation
|
|
# ============================================================================
|
|
|
|
install_binary() {
|
|
log_step "Installing BetterDesk Go server..."
|
|
|
|
mkdir -p "$INSTALL_DIR" "$DATA_DIR" "$LOG_DIR" "$BACKUP_DIR"
|
|
|
|
# Copy binary
|
|
cp "${SCRIPT_DIR}/${GO_BINARY}" "${INSTALL_DIR}/betterdesk-server"
|
|
chmod +x "${INSTALL_DIR}/betterdesk-server"
|
|
|
|
# Copy migration tool
|
|
if [[ -f "${SCRIPT_DIR}/tools/migrate/${MIGRATE_BINARY}" ]]; then
|
|
cp "${SCRIPT_DIR}/tools/migrate/${MIGRATE_BINARY}" "${INSTALL_DIR}/migrate"
|
|
chmod +x "${INSTALL_DIR}/migrate"
|
|
fi
|
|
|
|
# Copy existing key file if found
|
|
if [[ -n "$EXISTING_KEY" ]]; then
|
|
if [[ ! -f "${DATA_DIR}/id_ed25519" ]]; then
|
|
cp "$EXISTING_KEY" "${DATA_DIR}/id_ed25519"
|
|
[[ -f "${EXISTING_KEY}.pub" ]] && cp "${EXISTING_KEY}.pub" "${DATA_DIR}/id_ed25519.pub"
|
|
log_info "Key file copied to ${DATA_DIR}/"
|
|
fi
|
|
fi
|
|
|
|
log_info "Binary installed: ${INSTALL_DIR}/betterdesk-server"
|
|
}
|
|
|
|
stop_old_services() {
|
|
log_step "Stopping old services..."
|
|
|
|
for svc in rustdesksignal rustdeskrelay hbbs hbbr betterdesk; do
|
|
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
|
log_info "Stopping $svc..."
|
|
systemctl stop "$svc" || true
|
|
fi
|
|
if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
|
|
log_info "Disabling $svc..."
|
|
systemctl disable "$svc" || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
create_systemd_service() {
|
|
log_step "Creating systemd service..."
|
|
|
|
cat > /etc/systemd/system/${SERVICE_NAME}.service << 'EOF'
|
|
[Unit]
|
|
Description=BetterDesk Server (Signal + Relay + API)
|
|
Documentation=https://github.com/UNITRONIX/Rustdesk-FreeConsole
|
|
After=network.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=root
|
|
WorkingDirectory=/opt/betterdesk/data
|
|
ExecStart=/opt/betterdesk/betterdesk-server \
|
|
-mode all \
|
|
-db /opt/betterdesk/data/db_v2.sqlite3 \
|
|
-key-file /opt/betterdesk/data/id_ed25519 \
|
|
-port 21116 \
|
|
-relay-port 21117 \
|
|
-api-port 21114 \
|
|
-admin-port 21000 \
|
|
-log-format json \
|
|
-audit-log /var/log/betterdesk/audit.jsonl
|
|
Restart=always
|
|
RestartSec=5
|
|
LimitNOFILE=65535
|
|
|
|
# Security hardening
|
|
ProtectSystem=strict
|
|
ReadWritePaths=/opt/betterdesk/data /var/log/betterdesk
|
|
NoNewPrivileges=true
|
|
ProtectHome=true
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectControlGroups=true
|
|
|
|
# Environment variables (override defaults)
|
|
# Uncomment and set as needed:
|
|
#Environment=JWT_SECRET=your-secret-here
|
|
#Environment=ADMIN_PASSWORD=your-password-here
|
|
#Environment=FORCE_HTTPS=Y
|
|
#Environment=RELAY_MAX_CONNS_PER_IP=20
|
|
#Environment=TLS_CERT=/etc/letsencrypt/live/your-domain/fullchain.pem
|
|
#Environment=TLS_KEY=/etc/letsencrypt/live/your-domain/privkey.pem
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
systemctl daemon-reload
|
|
log_info "Systemd service created: ${SERVICE_NAME}"
|
|
}
|
|
|
|
enable_and_start() {
|
|
log_step "Enabling and starting BetterDesk server..."
|
|
|
|
systemctl enable "${SERVICE_NAME}"
|
|
systemctl start "${SERVICE_NAME}"
|
|
|
|
sleep 2
|
|
|
|
if systemctl is-active --quiet "${SERVICE_NAME}"; then
|
|
log_info "BetterDesk server is running!"
|
|
else
|
|
log_error "Service failed to start. Check: journalctl -u ${SERVICE_NAME} -n 50"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Rollback
|
|
# ============================================================================
|
|
|
|
rollback() {
|
|
log_step "Rolling back to previous installation..."
|
|
|
|
# Find latest backup
|
|
local latest_backup
|
|
latest_backup=$(ls -1td "${BACKUP_DIR}"/backup_* 2>/dev/null | head -1)
|
|
|
|
if [[ -z "$latest_backup" ]]; then
|
|
log_error "No backups found in ${BACKUP_DIR}"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Restoring from: $latest_backup"
|
|
|
|
# Stop new service
|
|
systemctl stop "${SERVICE_NAME}" 2>/dev/null || true
|
|
systemctl disable "${SERVICE_NAME}" 2>/dev/null || true
|
|
|
|
# Restore old services
|
|
for svc_file in "${latest_backup}"/*.service; do
|
|
[[ -f "$svc_file" ]] && cp "$svc_file" /etc/systemd/system/
|
|
done
|
|
|
|
# Restore database
|
|
for db_file in "${latest_backup}"/db_v2.sqlite3*; do
|
|
[[ -f "$db_file" ]] && cp "$db_file" "${OLD_INSTALL_DIR}/"
|
|
done
|
|
|
|
# Restore key files
|
|
for key_file in "${latest_backup}"/id_ed25519*; do
|
|
[[ -f "$key_file" ]] && cp "$key_file" "${OLD_INSTALL_DIR}/"
|
|
done
|
|
|
|
# Restore old binaries
|
|
for bin in hbbs hbbr; do
|
|
[[ -f "${latest_backup}/${bin}" ]] && cp "${latest_backup}/${bin}" "${OLD_INSTALL_DIR}/"
|
|
done
|
|
|
|
systemctl daemon-reload
|
|
|
|
# Re-enable old services
|
|
for svc in rustdesksignal rustdeskrelay; do
|
|
if [[ -f "/etc/systemd/system/${svc}.service" ]]; then
|
|
systemctl enable "$svc" 2>/dev/null || true
|
|
systemctl start "$svc" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
log_info "Rollback complete. Old services should be running."
|
|
}
|
|
|
|
# ============================================================================
|
|
# Status / Verification
|
|
# ============================================================================
|
|
|
|
verify_deployment() {
|
|
echo ""
|
|
log_step "Verifying deployment..."
|
|
echo ""
|
|
|
|
# Check service
|
|
if systemctl is-active --quiet "${SERVICE_NAME}"; then
|
|
log_info "Service: RUNNING"
|
|
else
|
|
log_error "Service: NOT RUNNING"
|
|
fi
|
|
|
|
# Check API health
|
|
sleep 1
|
|
if curl -sf http://localhost:21114/api/health > /dev/null 2>&1; then
|
|
local health
|
|
health=$(curl -sf http://localhost:21114/api/health)
|
|
log_info "API Health: ${health}"
|
|
else
|
|
log_warn "API not responding yet (may still be starting)"
|
|
fi
|
|
|
|
# Check ports
|
|
echo ""
|
|
log_info "Listening ports:"
|
|
ss -tlnp | grep -E '2111[4-9]|21000' || true
|
|
|
|
# Show initial credentials from journal
|
|
echo ""
|
|
log_info "Check initial admin credentials:"
|
|
echo " journalctl -u ${SERVICE_NAME} | grep 'INITIAL ADMIN'"
|
|
echo ""
|
|
|
|
# Show useful commands
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " USEFUL COMMANDS"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " Status: systemctl status ${SERVICE_NAME}"
|
|
echo " Logs: journalctl -u ${SERVICE_NAME} -f"
|
|
echo " Stop: systemctl stop ${SERVICE_NAME}"
|
|
echo " Start: systemctl start ${SERVICE_NAME}"
|
|
echo " Restart: systemctl restart ${SERVICE_NAME}"
|
|
echo " Rollback: $0 --rollback"
|
|
echo " Health: curl http://localhost:21114/api/health"
|
|
echo " Stats: curl http://localhost:21114/api/server/stats"
|
|
echo " Admin TCP: nc localhost 21000"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Firewall
|
|
# ============================================================================
|
|
|
|
configure_firewall() {
|
|
log_step "Configuring firewall rules..."
|
|
|
|
if command -v ufw &>/dev/null; then
|
|
ufw allow 21114/tcp comment "BetterDesk API"
|
|
ufw allow 21115/tcp comment "BetterDesk NAT Test"
|
|
ufw allow 21116/tcp comment "BetterDesk Signal TCP"
|
|
ufw allow 21116/udp comment "BetterDesk Signal UDP"
|
|
ufw allow 21117/tcp comment "BetterDesk Relay"
|
|
ufw allow 21118/tcp comment "BetterDesk WS Signal"
|
|
ufw allow 21119/tcp comment "BetterDesk WS Relay"
|
|
log_info "UFW rules added"
|
|
elif command -v firewall-cmd &>/dev/null; then
|
|
for port in 21114/tcp 21115/tcp 21116/tcp 21116/udp 21117/tcp 21118/tcp 21119/tcp; do
|
|
firewall-cmd --permanent --add-port="$port" 2>/dev/null || true
|
|
done
|
|
firewall-cmd --reload 2>/dev/null || true
|
|
log_info "firewalld rules added"
|
|
else
|
|
log_warn "No firewall manager found. Ensure ports 21114-21119 are open."
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
print_banner() {
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " BetterDesk Go Server — Deployment v${VERSION}"
|
|
echo " Single binary replacing hbbs + hbbr"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
}
|
|
|
|
main() {
|
|
print_banner
|
|
check_root
|
|
|
|
# Parse arguments
|
|
AUTO=false
|
|
MIGRATE_ONLY=false
|
|
DO_ROLLBACK=false
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--auto) AUTO=true ;;
|
|
--migrate-only) MIGRATE_ONLY=true ;;
|
|
--rollback) DO_ROLLBACK=true ;;
|
|
-h|--help)
|
|
echo "Usage: $0 [--auto] [--migrate-only] [--rollback]"
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if $DO_ROLLBACK; then
|
|
detect_existing_installation
|
|
rollback
|
|
exit 0
|
|
fi
|
|
|
|
check_binary
|
|
detect_existing_installation
|
|
|
|
if ! $AUTO; then
|
|
echo "This script will:"
|
|
echo " 1. Create a backup of the existing database and keys"
|
|
echo " 2. Stop old hbbs/hbbr services"
|
|
echo " 3. Migrate the database to the new Go schema"
|
|
echo " 4. Install the BetterDesk Go server"
|
|
echo " 5. Create and start a systemd service"
|
|
echo " 6. Configure firewall rules"
|
|
echo ""
|
|
if [[ -n "$EXISTING_DB" ]]; then
|
|
echo " Existing DB: $EXISTING_DB"
|
|
else
|
|
echo " No existing database found — fresh installation."
|
|
fi
|
|
echo ""
|
|
read -rp "Continue? [Y/n] " confirm
|
|
if [[ "${confirm,,}" == "n" ]]; then
|
|
echo "Aborted."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# Step 1: Backup
|
|
mkdir -p "$BACKUP_DIR"
|
|
if [[ -n "$EXISTING_DB" ]] || [[ -n "$EXISTING_KEY" ]]; then
|
|
create_backup
|
|
fi
|
|
|
|
if $MIGRATE_ONLY; then
|
|
mkdir -p "$DATA_DIR"
|
|
migrate_database
|
|
log_info "Migration-only mode — done."
|
|
exit 0
|
|
fi
|
|
|
|
# Step 2: Stop old services
|
|
stop_old_services
|
|
|
|
# Step 3: Install binary
|
|
install_binary
|
|
|
|
# Step 4: Migrate database
|
|
migrate_database
|
|
|
|
# Step 5: Create systemd service
|
|
create_systemd_service
|
|
|
|
# Step 6: Firewall
|
|
configure_firewall
|
|
|
|
# Step 7: Start
|
|
enable_and_start
|
|
|
|
# Step 8: Verify
|
|
verify_deployment
|
|
}
|
|
|
|
main "$@"
|