Skip to content

Instantly share code, notes, and snippets.

@vkhazin
Last active January 4, 2026 22:01
Show Gist options
  • Select an option

  • Save vkhazin/7e2689c371364ab245add809f275b01b to your computer and use it in GitHub Desktop.

Select an option

Save vkhazin/7e2689c371364ab245add809f275b01b to your computer and use it in GitHub Desktop.
Turn a Ubuntu server into VDI
#!/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