Last active
January 4, 2026 22:01
-
-
Save vkhazin/7e2689c371364ab245add809f275b01b to your computer and use it in GitHub Desktop.
Turn a Ubuntu server into VDI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Idempotent script to configure Ubuntu 25.04 as RDP server with GNOME Desktop | |
| # | |
| # Run directly from gist (latest version): | |
| # curl -sSL https://gist.githubusercontent.com/vkhazin/7e2689c371364ab245add809f275b01b/raw | sudo bash | |
| # | |
| # Or run locally: sudo bash ubuntu-vdi.sh | |
| # Tested on: Ubuntu 25.04 (Plucky) | |
| set -e | |
| echo "=== Ubuntu RDP Server Setup ===" | |
| echo "Starting configuration..." | |
| # Fix DNS resolution (common issue after Desktop install) | |
| if ! ping -c 1 google.com >/dev/null 2>&1; then | |
| echo "Fixing DNS resolution..." | |
| cat > /etc/systemd/resolved.conf << 'DNS_EOF' | |
| [Resolve] | |
| DNS=8.8.8.8 8.8.4.4 | |
| FallbackDNS=1.1.1.1 1.0.0.1 | |
| DNS_EOF | |
| systemctl restart systemd-resolved | |
| sleep 2 | |
| fi | |
| # Update package lists | |
| echo "Updating package lists..." | |
| apt-get update -qq | |
| # Install desktop environment (Ubuntu Desktop - GNOME) | |
| if ! dpkg -l | grep -q "ubuntu-desktop"; then | |
| echo "Installing Ubuntu Desktop environment..." | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y \ | |
| ubuntu-desktop \ | |
| gnome-session \ | |
| gnome-terminal \ | |
| dbus-x11 | |
| echo "Ubuntu Desktop installed successfully" | |
| else | |
| echo "Ubuntu Desktop already installed, skipping..." | |
| fi | |
| # Install Google Chrome with repository | |
| if ! dpkg -l | grep -q "^ii google-chrome-stable"; then | |
| echo "Installing Google Chrome..." | |
| # Add Google Chrome repository if not exists | |
| if [ ! -f "/etc/apt/sources.list.d/google-chrome.list" ]; then | |
| wget -q -O /tmp/google-chrome.key https://dl.google.com/linux/linux_signing_key.pub | |
| gpg --dearmor -o /usr/share/keyrings/google-chrome-keyring.gpg /tmp/google-chrome.key | |
| rm -f /tmp/google-chrome.key | |
| echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list | |
| apt-get update -qq | |
| fi | |
| # Install Chrome | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y google-chrome-stable | |
| echo "Google Chrome installed successfully" | |
| else | |
| echo "Google Chrome already installed, skipping..." | |
| fi | |
| # Install xrdp and dependencies | |
| if ! dpkg -l | grep -q "^ii xrdp"; then | |
| echo "Installing xrdp..." | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y xrdp curl | |
| echo "xrdp installed successfully" | |
| else | |
| echo "xrdp already installed, skipping..." | |
| # Ensure curl is installed for monitor | |
| if ! command -v curl >/dev/null 2>&1; then | |
| echo "Installing curl for monitor..." | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y curl | |
| fi | |
| fi | |
| # Configure xrdp to use GNOME | |
| XRDP_STARTWM="/etc/xrdp/startwm.sh" | |
| echo "Configuring xrdp to use GNOME..." | |
| # Backup original file (only once) | |
| if [ ! -f "${XRDP_STARTWM}.original" ]; then | |
| cp "$XRDP_STARTWM" "${XRDP_STARTWM}.original" 2>/dev/null || true | |
| fi | |
| # Rewrite startwm.sh for GNOME | |
| cat > "$XRDP_STARTWM" << 'STARTWM_EOF' | |
| #!/bin/sh | |
| # xrdp X session start script (c) 2015, 2017, 2021 mirabilos | |
| # published under The MirOS Licence | |
| if test -r /etc/profile; then | |
| . /etc/profile | |
| fi | |
| if test -r ~/.profile; then | |
| . ~/.profile | |
| fi | |
| # GNOME session configuration | |
| export GNOME_SHELL_SESSION_MODE=ubuntu | |
| export XDG_CURRENT_DESKTOP=ubuntu:GNOME | |
| export XDG_SESSION_TYPE=x11 | |
| # test -x /etc/X11/Xsession && exec /etc/X11/Xsession | |
| # exec /bin/sh /etc/X11/Xsession | |
| exec gnome-session | |
| STARTWM_EOF | |
| chmod +x "$XRDP_STARTWM" | |
| echo "xrdp configured for GNOME" | |
| # Create .xsession file for users | |
| create_xsession() { | |
| local user_home=$1 | |
| local xsession_file="$user_home/.xsession" | |
| if [ ! -f "$xsession_file" ]; then | |
| echo "export GNOME_SHELL_SESSION_MODE=ubuntu" > "$xsession_file" | |
| echo "export XDG_CURRENT_DESKTOP=ubuntu:GNOME" >> "$xsession_file" | |
| echo "exec /usr/bin/gnome-session" >> "$xsession_file" | |
| chown $(stat -c '%U:%G' "$user_home") "$xsession_file" 2>/dev/null || true | |
| chmod +x "$xsession_file" | |
| echo "Created .xsession for $user_home" | |
| fi | |
| } | |
| # Configure for existing non-system users | |
| for user_home in /home/*; do | |
| if [ -d "$user_home" ]; then | |
| create_xsession "$user_home" | |
| fi | |
| done | |
| # Add xrdp user to ssl-cert group for certificate access | |
| if groups xrdp | grep -qv ssl-cert; then | |
| echo "Adding xrdp user to ssl-cert group..." | |
| adduser xrdp ssl-cert | |
| else | |
| echo "xrdp already in ssl-cert group, skipping..." | |
| fi | |
| # Configure firewall (if ufw is installed and active) | |
| if command -v ufw >/dev/null 2>&1; then | |
| if ufw status | grep -q "Status: active"; then | |
| if ! ufw status | grep -q "3389"; then | |
| echo "Configuring firewall to allow RDP (port 3389)..." | |
| ufw allow 3389/tcp | |
| echo "Firewall configured" | |
| else | |
| echo "Firewall already configured, skipping..." | |
| fi | |
| else | |
| echo "UFW is installed but not active, skipping firewall configuration" | |
| fi | |
| else | |
| echo "UFW not installed, skipping firewall configuration" | |
| fi | |
| # Enable and start xrdp services | |
| echo "Enabling xrdp services..." | |
| systemctl enable xrdp >/dev/null 2>&1 || true | |
| systemctl enable xrdp-sesman >/dev/null 2>&1 || true | |
| if systemctl is-active --quiet xrdp; then | |
| echo "Restarting xrdp services..." | |
| systemctl restart xrdp | |
| systemctl restart xrdp-sesman | |
| else | |
| echo "Starting xrdp services..." | |
| systemctl start xrdp | |
| systemctl start xrdp-sesman | |
| fi | |
| # Get server IP addresses | |
| echo "" | |
| echo "=== Setup Complete ===" | |
| echo "RDP Server is ready!" | |
| echo "" | |
| echo "Connection Details:" | |
| echo " Port: 3389" | |
| echo " IP Addresses:" | |
| ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '127.0.0.1' | sed 's/^/ /' | |
| echo "" | |
| echo "To connect from Windows:" | |
| echo " 1. Open Remote Desktop Connection (mstsc.exe)" | |
| echo " 2. Enter the server IP address" | |
| echo " 3. Use your Ubuntu username and password" | |
| echo "" | |
| echo "IMPORTANT NOTES:" | |
| echo " - First login may take 30-60 seconds to load GNOME" | |
| echo " - Chrome requires --no-sandbox flag when running as root" | |
| echo " - Use a non-root user for regular RDP sessions" | |
| echo "" | |
| echo "Status:" | |
| systemctl status xrdp --no-pager -l | head -n 5 | |
| echo "" | |
| echo "Setup completed successfully!" | |
| # Create RDP port monitor script | |
| echo "" | |
| echo "=== Setting up RDP Port Monitor ===" | |
| cat > /usr/local/bin/rdp-monitor.sh << 'MONITOR_EOF' | |
| #!/bin/bash | |
| # RDP Port Monitor - Auto-reboot if port 3389 is not accessible | |
| # Created by ubuntu-vdi.sh | |
| LOG_FILE="/var/log/rdp-monitor.log" | |
| MAX_LOG_SIZE=10485760 # 10MB | |
| # Rotate log if too large | |
| if [ -f "$LOG_FILE" ] && [ $(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null) -gt $MAX_LOG_SIZE ]; then | |
| mv "$LOG_FILE" "$LOG_FILE.old" | |
| fi | |
| log_message() { | |
| echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" | |
| } | |
| # Get external IP address | |
| EXTERNAL_IP=$(curl -s -4 --max-time 5 ifconfig.me || curl -s -4 --max-time 5 icanhazip.com || echo "") | |
| if [ -z "$EXTERNAL_IP" ]; then | |
| log_message "WARNING: Could not determine external IP address" | |
| exit 0 | |
| fi | |
| log_message "Checking RDP port on external IP: $EXTERNAL_IP" | |
| # Check if port 3389 is accessible from external IP | |
| # Using timeout and nc (netcat) to test the port | |
| if timeout 10 bash -c "echo > /dev/tcp/$EXTERNAL_IP/3389" 2>/dev/null; then | |
| log_message "SUCCESS: RDP port 3389 is accessible on $EXTERNAL_IP" | |
| exit 0 | |
| else | |
| log_message "FAILURE: RDP port 3389 is NOT accessible on $EXTERNAL_IP" | |
| # Double-check: verify xrdp services are actually running locally | |
| XRDP_RUNNING=false | |
| SESMAN_RUNNING=false | |
| if systemctl is-active --quiet xrdp; then | |
| XRDP_RUNNING=true | |
| log_message "INFO: xrdp service is running" | |
| else | |
| log_message "ERROR: xrdp service is NOT running" | |
| fi | |
| if systemctl is-active --quiet xrdp-sesman; then | |
| SESMAN_RUNNING=true | |
| log_message "INFO: xrdp-sesman service is running" | |
| else | |
| log_message "ERROR: xrdp-sesman service is NOT running" | |
| fi | |
| if $XRDP_RUNNING && $SESMAN_RUNNING; then | |
| log_message "INFO: Both services running but port not accessible externally" | |
| # Check if port is listening locally | |
| if ss -tlnp | grep -q ":3389"; then | |
| log_message "INFO: Port 3389 is listening locally - possible firewall/network issue" | |
| # Try to restart both services before rebooting | |
| log_message "ACTION: Attempting to restart xrdp services" | |
| systemctl restart xrdp-sesman | |
| systemctl restart xrdp | |
| sleep 5 | |
| # Check again | |
| if timeout 10 bash -c "echo > /dev/tcp/$EXTERNAL_IP/3389" 2>/dev/null; then | |
| log_message "SUCCESS: RDP port accessible after service restart" | |
| exit 0 | |
| fi | |
| else | |
| log_message "WARNING: Port 3389 not listening locally despite services running" | |
| fi | |
| else | |
| log_message "ERROR: One or both xrdp services are not running" | |
| log_message "ACTION: Attempting to restart both services" | |
| systemctl restart xrdp-sesman | |
| systemctl restart xrdp | |
| sleep 5 | |
| # Check again | |
| if timeout 10 bash -c "echo > /dev/tcp/$EXTERNAL_IP/3389" 2>/dev/null; then | |
| log_message "SUCCESS: RDP port accessible after service restart" | |
| exit 0 | |
| fi | |
| fi | |
| log_message "CRITICAL: Initiating system reboot to restore RDP access" | |
| # Send alert before reboot (optional - could add email/webhook here) | |
| wall "RDP port check failed. System will reboot in 10 seconds to restore service." | |
| sleep 10 | |
| reboot | |
| fi | |
| MONITOR_EOF | |
| chmod +x /usr/local/bin/rdp-monitor.sh | |
| echo "RDP monitor script created at /usr/local/bin/rdp-monitor.sh" | |
| # Create systemd service for the monitor | |
| cat > /etc/systemd/system/rdp-monitor.service << 'SERVICE_EOF' | |
| [Unit] | |
| Description=RDP Port Monitor | |
| After=network-online.target xrdp.service | |
| Wants=network-online.target | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/usr/local/bin/rdp-monitor.sh | |
| StandardOutput=journal | |
| StandardError=journal | |
| [Install] | |
| WantedBy=multi-user.target | |
| SERVICE_EOF | |
| # Create systemd timer to run every 5 minutes | |
| cat > /etc/systemd/system/rdp-monitor.timer << 'TIMER_EOF' | |
| [Unit] | |
| Description=RDP Port Monitor Timer | |
| Requires=rdp-monitor.service | |
| [Timer] | |
| OnBootSec=2min | |
| OnUnitActiveSec=5min | |
| AccuracySec=1s | |
| [Install] | |
| WantedBy=timers.target | |
| TIMER_EOF | |
| # Reload systemd and enable the timer | |
| systemctl daemon-reload | |
| systemctl enable rdp-monitor.timer | |
| systemctl start rdp-monitor.timer | |
| echo "RDP monitor timer enabled (checks every 5 minutes)" | |
| echo "Monitor logs: /var/log/rdp-monitor.log" | |
| echo "" | |
| # Verify monitor setup | |
| echo "Verifying monitor setup..." | |
| if systemctl is-active --quiet rdp-monitor.timer; then | |
| echo "✓ Monitor timer is active" | |
| NEXT_RUN=$(systemctl status rdp-monitor.timer --no-pager | grep "Trigger:" | awk '{print $2, $3, $4}') | |
| echo " Next check: $NEXT_RUN" | |
| else | |
| echo "⚠ Warning: Monitor timer is not active" | |
| fi | |
| echo "" | |
| echo "RDP monitor timer enabled (checks every 5 minutes)" | |
| echo "Monitor logs: /var/log/rdp-monitor.log" | |
| echo "" | |
| echo "Monitor commands:" | |
| echo " Status: sudo systemctl status rdp-monitor.timer" | |
| echo " Logs: sudo tail -f /var/log/rdp-monitor.log" | |
| echo " Manual: sudo /usr/local/bin/rdp-monitor.sh" | |
| echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment