#!/bin/bash # Proxmox Auto RDP Setup Script # 자동으로 Proxmox 호스트를 RDP VM에 연결하는 설정을 수행합니다 # 사용법: bash -c "$(curl -fsSL [스크립트 URL])" set -euo pipefail # 색상 코드 정의 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color # 로그 함수들 msg_info() { echo -e "${BLUE}[INFO]${NC} $1" } msg_ok() { echo -e "${GREEN}[OK]${NC} $1" } msg_error() { echo -e "${RED}[ERROR]${NC} $1" exit 1 } msg_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } # 헤더 출력 print_header() { clear echo -e "${PURPLE}" echo "═══════════════════════════════════════════════════════════════════" echo " Proxmox Auto RDP Setup Script v1.0" echo "═══════════════════════════════════════════════════════════════════" echo -e "${NC}" echo "이 스크립트는 Proxmox VE 호스트가 부팅 시 자동으로 RDP 연결하도록 설정합니다." echo "" } # Proxmox 버전 확인 check_proxmox_version() { msg_info "Proxmox VE 버전 확인 중..." # Proxmox VE 설치 확인 if [ ! -f /etc/pve/.version ] && ! command -v pveversion > /dev/null 2>&1; then msg_error "Proxmox VE가 설치되어 있지 않습니다." fi # 버전 정보 추출 (수정된 방식) local version_info if command -v pveversion > /dev/null 2>&1; then version_info=$(pveversion 2>/dev/null | head -n1 | awk '{print $1}' | cut -d'/' -f2 2>/dev/null || echo "unknown") else version_info="unknown" fi # 버전이 숫자인지 확인 if [[ "$version_info" =~ ^[0-9]+\.[0-9]+ ]]; then local pve_version=$(echo "$version_info" | cut -d'.' -f1) if [ "$pve_version" -lt 8 ]; then msg_error "지원되지 않는 Proxmox VE 버전입니다. 8.x 이상이 필요합니다." fi msg_ok "Proxmox VE $pve_version.x 버전 확인됨" else msg_warn "Proxmox VE 버전을 확인할 수 없습니다. 계속 진행합니다..." read -p "계속 진행하시겠습니까? [y/N]: " continue_anyway case $continue_anyway in [yY]|[yY][eE][sS]) ;; *) msg_error "설정이 취소되었습니다." ;; esac fi } # 루트 권한 확인 check_root() { if [ "$EUID" -ne 0 ]; then msg_error "이 스크립트는 root 권한으로 실행해야 합니다. sudo를 사용하세요." fi } # 입력 검증 함수 validate_rdp_server() { local server="$1" # 기본적인 형식 검증 (호스트:포트 또는 호스트만) if [[ ! "$server" =~ ^[a-zA-Z0-9.-]+(:([0-9]{1,5}))?$ ]]; then return 1 fi # 포트 범위 검증 if [[ "$server" =~ :([0-9]+)$ ]]; then local port="${BASH_REMATCH[1]}" if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then return 1 fi fi return 0 } validate_username() { local username="$1" # 사용자명 길이 및 문자 검증 if [ ${#username} -lt 1 ] || [ ${#username} -gt 32 ]; then return 1 fi # 허용된 문자만 포함 확인 if [[ ! "$username" =~ ^[a-zA-Z0-9._-]+$ ]]; then return 1 fi return 0 } # 사용자 입력 받기 get_user_input() { echo -e "${CYAN}RDP 연결 정보를 입력해주세요:${NC}" echo "" # RDP 서버 정보 (검증 포함) while true; do read -p "RDP 서버 주소 (예: example.com:3389): " RDP_SERVER if [ -z "$RDP_SERVER" ]; then msg_error "RDP 서버 주소는 필수입니다." elif ! validate_rdp_server "$RDP_SERVER"; then msg_warn "잘못된 서버 주소 형식입니다. 다시 입력해주세요." echo " 예시: example.com 또는 example.com:3389" continue else break fi done # 사용자명 (검증 포함) while true; do read -p "RDP 사용자명: " RDP_USERNAME if [ -z "$RDP_USERNAME" ]; then msg_error "RDP 사용자명은 필수입니다." elif ! validate_username "$RDP_USERNAME"; then msg_warn "잘못된 사용자명 형식입니다. 영문, 숫자, ., _, - 만 사용 가능합니다." continue else break fi done # 패스워드 (재확인 포함) while true; do echo -n "RDP 패스워드: " read -s RDP_PASSWORD echo "" if [ -z "$RDP_PASSWORD" ]; then msg_error "RDP 패스워드는 필수입니다." fi echo -n "패스워드 확인: " read -s password_confirm echo "" if [ "$RDP_PASSWORD" != "$password_confirm" ]; then msg_warn "패스워드가 일치하지 않습니다. 다시 입력해주세요." continue else break fi done # 로컬 사용자명 (선택사항, 검증 포함) while true; do read -p "로컬 사용자명 [rdpuser]: " LOCAL_USER LOCAL_USER=${LOCAL_USER:-rdpuser} if ! validate_username "$LOCAL_USER"; then msg_warn "잘못된 로컬 사용자명 형식입니다. 영문, 숫자, ., _, - 만 사용 가능합니다." continue else break fi done echo "" echo -e "${YELLOW}입력된 정보:${NC}" echo " RDP 서버: $RDP_SERVER" echo " RDP 사용자: $RDP_USERNAME" echo " 로컬 사용자: $LOCAL_USER" echo "" # 최종 확인 local attempts=0 while [ $attempts -lt 3 ]; do read -p "설정을 계속하시겠습니까? [y/N]: " confirm case $confirm in [yY]|[yY][eE][sS]) return 0 ;; [nN]|[nN][oO]|"") msg_error "설정이 취소되었습니다." ;; *) msg_warn "y 또는 n으로 답해주세요." ((attempts++)) ;; esac done msg_error "너무 많은 잘못된 입력으로 설정이 취소되었습니다." } # 백업 생성 create_backup() { msg_info "기존 설정 백업 중..." local backup_dir="/root/proxmox-rdp-backup-$(date +%Y%m%d-%H%M%S)" mkdir -p "$backup_dir" # 기존 설정 백업 if [ -f /etc/systemd/system/getty@tty1.service.d/override.conf ]; then cp -r /etc/systemd/system/getty@tty1.service.d "$backup_dir/" 2>/dev/null || true fi if id "$LOCAL_USER" &>/dev/null; then cp -r "/home/$LOCAL_USER" "$backup_dir/home-$LOCAL_USER" 2>/dev/null || true fi msg_ok "백업 완료: $backup_dir" } # 네트워크 연결 확인 check_network() { msg_info "네트워크 연결 확인 중..." # 인터넷 연결 확인 if ! ping -c 1 -W 5 8.8.8.8 > /dev/null 2>&1; then msg_error "인터넷 연결을 확인할 수 없습니다. 네트워크 설정을 확인해주세요." fi # RDP 서버 연결 확인 local server_host="${RDP_SERVER%:*}" local server_port="${RDP_SERVER#*:}" # 포트가 지정되지 않았으면 기본값 3389 사용 if [ "$server_port" = "$server_host" ]; then server_port="3389" fi msg_info "RDP 서버 연결 확인 중... ($server_host:$server_port)" if command -v timeout > /dev/null; then if ! timeout 10 bash -c " /dev/null 2>&1; then msg_warn "RDP 서버에 연결할 수 없습니다. 서버 주소와 포트를 확인해주세요." read -p "계속 진행하시겠습니까? [y/N]: " continue_anyway case $continue_anyway in [yY]|[yY][eE][sS]) ;; *) msg_error "설정이 취소되었습니다." ;; esac else msg_ok "RDP 서버 연결 확인됨" fi else msg_warn "연결 테스트를 건너뜁니다 (timeout 명령어 없음)" fi } # 필수 패키지 설치 install_packages() { msg_info "필수 패키지 설치 중..." # 패키지 목록 업데이트 msg_info " - 패키지 목록 업데이트 중..." if ! apt update > /dev/null 2>&1; then msg_error "패키지 목록 업데이트에 실패했습니다." fi # 필수 패키지들 local packages="xorg openbox unclutter freerdp3-x11" for package in $packages; do msg_info " - $package 설치 중..." # 패키지가 이미 설치되어 있는지 확인 if dpkg -l | grep -q "^ii $package "; then msg_ok " $package 이미 설치됨" continue fi # 패키지 설치 시도 local retry_count=0 while [ $retry_count -lt 3 ]; do if apt install -y "$package" > /dev/null 2>&1; then msg_ok " $package 설치 완료" break else ((retry_count++)) if [ $retry_count -lt 3 ]; then msg_warn " $package 설치 실패, 재시도 중... ($retry_count/3)" sleep 2 else msg_error "$package 설치에 실패했습니다. 네트워크 연결이나 패키지 저장소를 확인해주세요." fi fi done done # 설치 확인 (더 유연한 방식) msg_info "설치된 패키지 확인 중..." for package in $packages; do local installed=false # 직접 패키지명 확인 if dpkg -l | grep -q "^ii $package "; then installed=true else # 패키지명이 다를 수 있으므로 부분 매치 확인 case $package in "openbox") if dpkg -l | grep -q "^ii openbox" || command -v openbox > /dev/null 2>&1; then installed=true fi ;; "xorg") if dpkg -l | grep -q "^ii.*xorg" || command -v startx > /dev/null 2>&1; then installed=true fi ;; "freerdp3-x11") if dpkg -l | grep -q "^ii.*freerdp" || command -v xfreerdp3 > /dev/null 2>&1; then installed=true fi ;; "unclutter") if dpkg -l | grep -q "^ii unclutter" || command -v unclutter > /dev/null 2>&1; then installed=true fi ;; *) if dpkg -l | grep -q "^ii $package " || command -v "$package" > /dev/null 2>&1; then installed=true fi ;; esac fi if [ "$installed" = false ]; then msg_error "$package가 정상적으로 설치되지 않았습니다." else msg_info " ✓ $package 설치 확인됨" fi done msg_ok "모든 패키지 설치 완료" } # 사용자 계정 생성 setup_user() { msg_info "사용자 계정 설정 중..." if ! id "$LOCAL_USER" &>/dev/null; then useradd -m -s /bin/bash "$LOCAL_USER" msg_ok "사용자 '$LOCAL_USER' 생성됨" else msg_warn "사용자 '$LOCAL_USER'가 이미 존재합니다." fi # 홈 디렉토리 권한 설정 chown -R "$LOCAL_USER:$LOCAL_USER" "/home/$LOCAL_USER" } # 자동 로그인 설정 setup_autologin() { msg_info "자동 로그인 설정 중..." # getty@tty1 서비스 override 디렉토리 생성 mkdir -p /etc/systemd/system/getty@tty1.service.d # override.conf 파일 생성 cat > /etc/systemd/system/getty@tty1.service.d/override.conf << EOF [Service] ExecStart= ExecStart=-/sbin/agetty --autologin $LOCAL_USER --noclear %I \$TERM Type=idle EOF # systemd 리로드 systemctl daemon-reload msg_ok "자동 로그인 설정 완료" } # X 자동 시작 설정 setup_x_autostart() { msg_info "X Window 자동 시작 설정 중..." # .bash_profile 생성 cat > "/home/$LOCAL_USER/.bash_profile" << 'EOF' # tty1에서만 X 자동 시작 if [[ -z $DISPLAY ]] && [[ $(tty) == /dev/tty1 ]]; then startx logout fi EOF chown "$LOCAL_USER:$LOCAL_USER" "/home/$LOCAL_USER/.bash_profile" msg_ok "X Window 자동 시작 설정 완료" } # RDP 연결 설정 setup_rdp_connection() { msg_info "RDP 연결 설정 중..." # .xinitrc 파일 생성 cat > "/home/$LOCAL_USER/.xinitrc" << EOF #!/bin/bash # 화면 절전 모드 비활성화 xset -dpms xset s off xset s noblank # 마우스 커서 숨기기 unclutter -idle 0.1 -root & # Openbox 윈도우 매니저 시작 openbox-session & # 잠시 대기 (X 완전 초기화) sleep 2 # FreeRDP3를 사용한 직접 RDP 연결 (풀스크린) xfreerdp3 \\ /v:$RDP_SERVER \\ /u:$RDP_USERNAME \\ /p:"$RDP_PASSWORD" \\ +f \\ /cert:ignore \\ +dynamic-resolution \\ /sound:sys:alsa \\ +clipboard # RDP 종료 시 X 세션도 종료 pkill -SIGTERM Xorg EOF # 실행 권한 및 소유권 설정 chmod +x "/home/$LOCAL_USER/.xinitrc" chown "$LOCAL_USER:$LOCAL_USER" "/home/$LOCAL_USER/.xinitrc" msg_ok "RDP 연결 설정 완료" } # Openbox 설정 setup_openbox() { msg_info "Openbox 윈도우 매니저 설정 중..." # Openbox 설정 디렉토리 생성 mkdir -p "/home/$LOCAL_USER/.config/openbox" # rc.xml 설정 파일 생성 cat > "/home/$LOCAL_USER/.config/openbox/rc.xml" << 'EOF' no yes EOF # 소유권 설정 chown -R "$LOCAL_USER:$LOCAL_USER" "/home/$LOCAL_USER/.config" msg_ok "Openbox 설정 완료" } # 설정 테스트 test_configuration() { msg_info "설정 테스트 중..." # systemd 서비스 상태 확인 if systemctl is-enabled getty@tty1.service > /dev/null 2>&1; then msg_ok "getty@tty1 서비스가 활성화되어 있습니다." else msg_warn "getty@tty1 서비스가 비활성화되어 있습니다." fi # 필수 파일들 존재 확인 local files=( "/etc/systemd/system/getty@tty1.service.d/override.conf" "/home/$LOCAL_USER/.bash_profile" "/home/$LOCAL_USER/.xinitrc" "/home/$LOCAL_USER/.config/openbox/rc.xml" ) for file in "${files[@]}"; do if [ -f "$file" ]; then msg_ok "설정 파일 존재: $file" else msg_error "설정 파일 누락: $file" fi done # FreeRDP3 설치 확인 if command -v xfreerdp3 > /dev/null 2>&1; then msg_ok "FreeRDP3 설치 확인됨" else msg_error "FreeRDP3가 설치되어 있지 않습니다." fi } # 완료 메시지 print_completion() { echo "" echo -e "${GREEN}═══════════════════════════════════════════════════════════════════${NC}" echo -e "${GREEN} 설정이 완료되었습니다!${NC}" echo -e "${GREEN}═══════════════════════════════════════════════════════════════════${NC}" echo "" echo -e "${CYAN}설정 요약:${NC}" echo " - RDP 서버: $RDP_SERVER" echo " - RDP 사용자: $RDP_USERNAME" echo " - 로컬 사용자: $LOCAL_USER" echo " - 자동 로그인: 활성화됨 (tty1)" echo " - 풀스크린 RDP: 활성화됨" echo "" echo -e "${YELLOW}다음 단계:${NC}" echo " 1. 시스템을 재부팅하세요" echo " 2. 자동으로 RDP 연결이 시작됩니다" echo " 3. 문제 발생 시 Ctrl+Alt+F2로 다른 터미널에 접근 가능합니다" echo "" echo -e "${CYAN}재부팅하시겠습니까? [y/N]:${NC} " read -r reboot_confirm case $reboot_confirm in [yY]|[yY][eE][sS]) msg_info "시스템을 재부팅합니다..." sleep 2 reboot ;; *) echo -e "${GREEN}설정이 완료되었습니다. 수동으로 재부팅해 주세요.${NC}" ;; esac } # 메인 함수 main() { print_header # 사전 검사 check_root check_proxmox_version # 사용자 입력 get_user_input # 네트워크 확인 check_network # 백업 생성 create_backup # 설정 수행 install_packages setup_user setup_autologin setup_x_autostart setup_rdp_connection setup_openbox # 테스트 및 완료 test_configuration print_completion } # 에러 핸들링 trap 'msg_error "스크립트 실행 중 오류가 발생했습니다."' ERR # 스크립트 실행 main "$@"