From 9d0adf6f8b3462ab03a21a9b28212c918956e611 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 6 Sep 2025 00:33:02 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20RDP=20=EC=9E=90=EB=8F=99=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=9B=B9=20=EC=A0=9C=EC=96=B4=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Flask 기반 웹 제어 패널 구현 - 토글 스위치로 RDP 자동 로그인 제어 - Tailscale IP 기반 접속 정보 표시 - Python venv 환경 사용 - systemd 서비스로 자동 실행 - PBS 자동 등록 스크립트 기획서 추가 🤖 Generated with Claude Code Co-Authored-By: Claude --- install_web_control.sh | 106 +++++++ pbs_auto_registration.md | 262 ++++++++++++++++ rdp-control-web.service | 15 + rdp_control_web.py | 228 ++++++++++++++ requirements.txt | 3 + templates/index.html | 628 +++++++++++++++++++++++++++++++++++++++ uninstall_web_control.sh | 52 ++++ 7 files changed, 1294 insertions(+) create mode 100755 install_web_control.sh create mode 100644 pbs_auto_registration.md create mode 100644 rdp-control-web.service create mode 100644 rdp_control_web.py create mode 100644 requirements.txt create mode 100644 templates/index.html create mode 100755 uninstall_web_control.sh diff --git a/install_web_control.sh b/install_web_control.sh new file mode 100755 index 0000000..adb3c56 --- /dev/null +++ b/install_web_control.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +# RDP 자동 로그인 웹 제어 패널 설치 스크립트 + +set -e + +echo "=========================================" +echo "RDP 자동 로그인 웹 제어 패널 설치" +echo "=========================================" + +# 색상 정의 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# root 권한 확인 +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}이 스크립트는 root 권한으로 실행해야 합니다${NC}" + exit 1 +fi + +# 작업 디렉토리 설정 +WORK_DIR="/root/proxmox-rdp-autosetup" +VENV_DIR="${WORK_DIR}/venv" + +# Python3 및 pip 설치 확인 +echo -e "${YELLOW}Python3 및 필요한 패키지 설치 중...${NC}" +apt-get update +apt-get install -y python3 python3-pip python3-venv + +# 가상 환경 생성 +echo -e "${YELLOW}Python 가상 환경 생성 중...${NC}" +cd ${WORK_DIR} +python3 -m venv ${VENV_DIR} + +# 가상 환경 활성화 및 패키지 설치 +echo -e "${YELLOW}Flask 및 관련 패키지 설치 중...${NC}" +source ${VENV_DIR}/bin/activate +pip install --upgrade pip +pip install -r ${WORK_DIR}/requirements.txt + +# systemd 서비스 파일 복사 +echo -e "${YELLOW}systemd 서비스 설정 중...${NC}" +cp /root/proxmox-rdp-autosetup/rdp-control-web.service /etc/systemd/system/ + +# systemd 데몬 리로드 +systemctl daemon-reload + +# 서비스 활성화 및 시작 +echo -e "${YELLOW}웹 서비스 시작 중...${NC}" +systemctl enable rdp-control-web.service +systemctl start rdp-control-web.service + +# 서비스 상태 확인 +sleep 2 +if systemctl is-active --quiet rdp-control-web.service; then + echo -e "${GREEN}✅ 웹 제어 패널이 성공적으로 설치되었습니다!${NC}" + echo "" + echo "=========================================" + echo "접속 정보:" + echo "=========================================" + + # IP 주소 가져오기 + IP=$(ip -4 addr show scope global | grep inet | head -1 | awk '{print $2}' | cut -d'/' -f1) + + # Tailscale IP 확인 + TAILSCALE_IP="" + if command -v tailscale &> /dev/null; then + TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "") + fi + + echo "로컬 접속: http://${IP}:5000" + + if [ -n "$TAILSCALE_IP" ]; then + echo "Tailscale 접속: http://${TAILSCALE_IP}:5000" + fi + + echo "" + echo "=========================================" + echo "서비스 관리 명령어:" + echo "=========================================" + echo "상태 확인: systemctl status rdp-control-web" + echo "재시작: systemctl restart rdp-control-web" + echo "중지: systemctl stop rdp-control-web" + echo "로그 확인: journalctl -u rdp-control-web -f" + echo "" +else + echo -e "${RED}❌ 서비스 시작 실패${NC}" + echo "로그를 확인하려면 다음 명령어를 실행하세요:" + echo "journalctl -u rdp-control-web -n 50" + exit 1 +fi + +# 방화벽 설정 (ufw가 설치되어 있는 경우) +if command -v ufw &> /dev/null; then + echo -e "${YELLOW}방화벽 규칙 추가 중...${NC}" + ufw allow 5000/tcp + echo -e "${GREEN}✅ 포트 5000이 방화벽에 허용되었습니다${NC}" +fi + +echo "" +echo -e "${GREEN}=========================================${NC}" +echo -e "${GREEN}설치 완료!${NC}" +echo -e "${GREEN}웹 브라우저에서 위 주소로 접속하세요${NC}" +echo -e "${GREEN}=========================================${NC}" \ No newline at end of file diff --git a/pbs_auto_registration.md b/pbs_auto_registration.md new file mode 100644 index 0000000..e53a029 --- /dev/null +++ b/pbs_auto_registration.md @@ -0,0 +1,262 @@ +# Proxmox Backup Server (PBS) 자동 등록 스크립트 기획서 + +## 📋 프로젝트 개요 + +### 목적 +Proxmox VE 호스트에 PBS(Proxmox Backup Server)를 자동으로 등록하고 백업 작업을 구성하는 스크립트 + +### 주요 기능 +- PBS 서버 자동 감지 및 연결 +- 스토리지 구성 자동화 +- 백업 작업 스케줄 설정 +- 기존 백업 설정 마이그레이션 + +## 🎯 요구사항 + +### 필수 요구사항 +1. **PBS 서버 정보** + - PBS 서버 IP/호스트명 + - PBS 사용자 인증 정보 (username@realm) + - PBS API 토큰 또는 비밀번호 + - Datastore 이름 + +2. **Proxmox VE 요구사항** + - Proxmox VE 6.x 이상 + - root 권한 + - pvesm 명령어 사용 가능 + +3. **네트워크 요구사항** + - PBS 서버와 통신 가능 (포트 8007) + - 지문(fingerprint) 자동 획득 가능 + +## 🔧 기능 상세 + +### 1. PBS 서버 감지 및 검증 +```bash +# PBS 서버 연결 테스트 +# API 접근 가능 여부 확인 +# 지문(fingerprint) 자동 획득 +``` + +### 2. 스토리지 등록 +```bash +# PBS 스토리지를 Proxmox VE에 추가 +pvesm add pbs \ + --server \ + --datastore \ + --username \ + --password \ + --fingerprint +``` + +### 3. 백업 작업 구성 +- 일일 백업 스케줄 설정 +- VM/CT 선택적 백업 +- 보존 정책 설정 + +### 4. 암호화 설정 (선택사항) +- 백업 암호화 키 생성 +- 키 안전한 저장 + +## 📝 스크립트 구조 + +``` +pbs_auto_registration.sh +├── 1. 환경 체크 +│ ├── Proxmox 버전 확인 +│ ├── 필수 도구 확인 +│ └── root 권한 확인 +│ +├── 2. PBS 정보 수집 +│ ├── 대화형 모드 +│ ├── 설정 파일 모드 +│ └── 환경 변수 모드 +│ +├── 3. PBS 연결 검증 +│ ├── 네트워크 연결 테스트 +│ ├── API 인증 테스트 +│ └── Datastore 접근 확인 +│ +├── 4. 스토리지 구성 +│ ├── 기존 스토리지 확인 +│ ├── 새 스토리지 추가 +│ └── 권한 설정 +│ +├── 5. 백업 작업 설정 +│ ├── 백업 대상 선택 +│ ├── 스케줄 설정 +│ └── 알림 설정 +│ +└── 6. 검증 및 완료 + ├── 설정 테스트 + ├── 첫 백업 실행 (선택) + └── 로그 출력 +``` + +## 🔐 보안 고려사항 + +### 인증 방식 +1. **API 토큰 (권장)** + - 토큰 ID와 시크릿 사용 + - 제한된 권한 부여 가능 + +2. **패스워드 인증** + - 임시 사용 후 토큰으로 전환 권장 + - 설정 파일에 평문 저장 금지 + +### 암호화 +- 전송 중 암호화: HTTPS (포트 8007) +- 저장 시 암호화: 선택적 백업 암호화 + +## 📦 설정 파일 예시 + +### `/etc/pve/pbs_config.conf` +```ini +# PBS Server Configuration +PBS_SERVER=192.168.1.100 +PBS_PORT=8007 +PBS_DATASTORE=backup-store +PBS_USERNAME=backup@pbs + +# API Token (recommended) +PBS_TOKEN_ID=backup@pbs!automation +PBS_TOKEN_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + +# Storage Configuration +STORAGE_ID=pbs-backup +STORAGE_CONTENT=backup + +# Backup Schedule +BACKUP_SCHEDULE="0 2 * * *" # 매일 새벽 2시 +BACKUP_MODE=snapshot +BACKUP_COMPRESS=zstd + +# Retention Policy +KEEP_DAILY=7 +KEEP_WEEKLY=4 +KEEP_MONTHLY=6 +KEEP_YEARLY=1 + +# Notification +NOTIFY_EMAIL=admin@example.com +NOTIFY_MODE=failure # always|failure +``` + +## 🚀 사용 방법 + +### 기본 실행 (대화형) +```bash +bash pbs_auto_registration.sh +``` + +### 설정 파일 사용 +```bash +bash pbs_auto_registration.sh --config /etc/pve/pbs_config.conf +``` + +### 자동 모드 (프롬프트 없음) +```bash +bash pbs_auto_registration.sh --auto \ + --server 192.168.1.100 \ + --datastore backup-store \ + --username backup@pbs \ + --token-secret "xxxx" +``` + +## 🔄 기존 백업 마이그레이션 + +### 로컬 백업에서 PBS로 전환 +1. 기존 백업 목록 확인 +2. PBS로 백업 복사 (선택사항) +3. 백업 작업 재구성 +4. 기존 로컬 백업 정리 + +## 📊 모니터링 및 검증 + +### 백업 상태 확인 +```bash +# PBS 스토리지 상태 +pvesm status + +# 백업 작업 목록 +pvesh get /cluster/backup + +# 최근 백업 로그 +cat /var/log/pve/tasks/active +``` + +### 헬스 체크 +- PBS 연결 상태 +- 스토리지 용량 +- 백업 성공률 +- 보존 정책 준수 + +## 🛠️ 문제 해결 + +### 일반적인 문제 +1. **연결 실패** + - 방화벽 규칙 확인 + - PBS 서비스 상태 확인 + - 인증서/지문 문제 + +2. **권한 오류** + - PBS 사용자 권한 확인 + - Datastore 접근 권한 + - API 토큰 권한 + +3. **백업 실패** + - 스토리지 용량 확인 + - 네트워크 안정성 + - VM/CT 상태 확인 + +## 📈 향후 개선 사항 + +1. **다중 PBS 서버 지원** + - 복제/미러링 설정 + - 로드 밸런싱 + +2. **고급 백업 정책** + - VM별 다른 스케줄 + - 조건부 백업 + - 증분 백업 최적화 + +3. **자동 복구** + - 백업 검증 자동화 + - 복구 테스트 자동화 + - 재해 복구 계획 + +4. **통합 관리** + - 웹 UI 대시보드 + - 중앙 집중식 관리 + - 멀티 클러스터 지원 + +## 📚 참고 자료 + +- [Proxmox Backup Server Documentation](https://pbs.proxmox.com/docs/) +- [Proxmox VE Storage Documentation](https://pve.proxmox.com/wiki/Storage) +- [PBS API Reference](https://pbs.proxmox.com/docs/api-viewer/) + +## 🔍 테스트 시나리오 + +### 단위 테스트 +1. PBS 서버 연결 테스트 +2. 인증 테스트 +3. 스토리지 추가/제거 테스트 + +### 통합 테스트 +1. 전체 설정 프로세스 +2. 백업 실행 테스트 +3. 복구 테스트 + +### 스트레스 테스트 +1. 대용량 VM 백업 +2. 동시 다중 백업 +3. 네트워크 장애 시나리오 + +## 📄 라이선스 +MIT License + +## 👥 기여자 +- 프로젝트 관리자 +- 개발팀 +- 테스트팀 \ No newline at end of file diff --git a/rdp-control-web.service b/rdp-control-web.service new file mode 100644 index 0000000..66a06d5 --- /dev/null +++ b/rdp-control-web.service @@ -0,0 +1,15 @@ +[Unit] +Description=RDP Auto Login Control Web Service +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/root/proxmox-rdp-autosetup +Environment="PATH=/root/proxmox-rdp-autosetup/venv/bin:/usr/bin:/usr/local/bin" +ExecStart=/root/proxmox-rdp-autosetup/venv/bin/gunicorn --bind 0.0.0.0:5000 --workers 2 --timeout 120 rdp_control_web:app +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/rdp_control_web.py b/rdp_control_web.py new file mode 100644 index 0000000..25d4d2d --- /dev/null +++ b/rdp_control_web.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import json +from flask import Flask, render_template, jsonify, request +from flask_cors import CORS +import logging +from pathlib import Path + +app = Flask(__name__) +CORS(app) + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +CONFIG_FILE = "/etc/xrdp/auto_login.conf" +SYSTEMD_SERVICE = "xrdp-autologin.service" + +def get_auto_login_status(): + """자동 로그인 상태 확인""" + try: + # systemd 서비스 상태 확인 + result = subprocess.run( + ["systemctl", "is-active", SYSTEMD_SERVICE], + capture_output=True, + text=True + ) + service_active = result.stdout.strip() == "active" + + # 설정 파일 존재 여부 확인 + config_exists = os.path.exists(CONFIG_FILE) + + # systemd 서비스 enabled 상태 확인 + result = subprocess.run( + ["systemctl", "is-enabled", SYSTEMD_SERVICE], + capture_output=True, + text=True + ) + service_enabled = result.stdout.strip() == "enabled" + + return { + "enabled": service_enabled, + "active": service_active, + "config_exists": config_exists + } + except Exception as e: + logger.error(f"상태 확인 실패: {str(e)}") + return { + "enabled": False, + "active": False, + "config_exists": False, + "error": str(e) + } + +def set_auto_login(enabled): + """자동 로그인 활성화/비활성화""" + try: + if enabled: + # 서비스 활성화 및 시작 + subprocess.run(["systemctl", "enable", SYSTEMD_SERVICE], check=True) + subprocess.run(["systemctl", "start", SYSTEMD_SERVICE], check=True) + + # xrdp 서비스 재시작 + subprocess.run(["systemctl", "restart", "xrdp"], check=True) + subprocess.run(["systemctl", "restart", "xrdp-sesman"], check=True) + + logger.info("RDP 자동 로그인 활성화됨") + return True, "RDP 자동 로그인이 활성화되었습니다." + else: + # 서비스 중지 및 비활성화 + subprocess.run(["systemctl", "stop", SYSTEMD_SERVICE], check=True) + subprocess.run(["systemctl", "disable", SYSTEMD_SERVICE], check=True) + + # 자동 로그인 설정 제거 + if os.path.exists(CONFIG_FILE): + backup_file = f"{CONFIG_FILE}.backup" + subprocess.run(["mv", CONFIG_FILE, backup_file], check=True) + + # xrdp 서비스 재시작 + subprocess.run(["systemctl", "restart", "xrdp"], check=True) + subprocess.run(["systemctl", "restart", "xrdp-sesman"], check=True) + + logger.info("RDP 자동 로그인 비활성화됨") + return True, "RDP 자동 로그인이 비활성화되었습니다." + except subprocess.CalledProcessError as e: + logger.error(f"명령 실행 실패: {str(e)}") + return False, f"설정 변경 실패: {str(e)}" + except Exception as e: + logger.error(f"예외 발생: {str(e)}") + return False, f"오류 발생: {str(e)}" + +def get_system_info(): + """시스템 정보 가져오기""" + try: + # 호스트명 + hostname = subprocess.run( + ["hostname"], + capture_output=True, + text=True + ).stdout.strip() + + # IP 주소 + ip_result = subprocess.run( + ["ip", "-4", "addr", "show", "scope", "global"], + capture_output=True, + text=True + ) + ip_addresses = [] + for line in ip_result.stdout.split('\n'): + if 'inet' in line: + ip = line.strip().split()[1].split('/')[0] + ip_addresses.append(ip) + + # RDP 포트 확인 + rdp_port = "3389" + + # Tailscale 상태 + tailscale_ip = None + try: + ts_result = subprocess.run( + ["tailscale", "ip", "-4"], + capture_output=True, + text=True + ) + if ts_result.returncode == 0: + tailscale_ip = ts_result.stdout.strip() + except: + pass + + return { + "hostname": hostname, + "ip_addresses": ip_addresses, + "rdp_port": rdp_port, + "tailscale_ip": tailscale_ip + } + except Exception as e: + logger.error(f"시스템 정보 가져오기 실패: {str(e)}") + return {} + +@app.route('/') +def index(): + """메인 페이지""" + return render_template('index.html') + +@app.route('/api/status', methods=['GET']) +def get_status(): + """현재 상태 조회 API""" + status = get_auto_login_status() + system_info = get_system_info() + return jsonify({ + "status": status, + "system": system_info + }) + +@app.route('/api/toggle', methods=['POST']) +def toggle_auto_login(): + """자동 로그인 토글 API""" + data = request.get_json() + enabled = data.get('enabled', False) + + success, message = set_auto_login(enabled) + + if success: + return jsonify({ + "success": True, + "message": message, + "status": get_auto_login_status() + }) + else: + return jsonify({ + "success": False, + "message": message, + "status": get_auto_login_status() + }), 500 + +@app.route('/api/logs', methods=['GET']) +def get_logs(): + """최근 로그 조회 API""" + try: + # xrdp 로그 + xrdp_logs = subprocess.run( + ["journalctl", "-u", "xrdp", "-n", "20", "--no-pager"], + capture_output=True, + text=True + ).stdout + + # 자동 로그인 서비스 로그 + autologin_logs = subprocess.run( + ["journalctl", "-u", SYSTEMD_SERVICE, "-n", "20", "--no-pager"], + capture_output=True, + text=True + ).stdout + + return jsonify({ + "xrdp_logs": xrdp_logs, + "autologin_logs": autologin_logs + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/restart', methods=['POST']) +def restart_services(): + """서비스 재시작 API""" + try: + subprocess.run(["systemctl", "restart", "xrdp"], check=True) + subprocess.run(["systemctl", "restart", "xrdp-sesman"], check=True) + + status = get_auto_login_status() + if status['enabled']: + subprocess.run(["systemctl", "restart", SYSTEMD_SERVICE], check=True) + + return jsonify({ + "success": True, + "message": "서비스가 재시작되었습니다." + }) + except Exception as e: + return jsonify({ + "success": False, + "message": f"재시작 실패: {str(e)}" + }), 500 + +if __name__ == '__main__': + # templates 디렉토리 생성 + Path("templates").mkdir(exist_ok=True) + + # 개발 서버 실행 (프로덕션에서는 gunicorn 사용 권장) + app.run(host='0.0.0.0', port=5000, debug=False) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2631797 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask==2.3.3 +Flask-Cors==4.0.0 +gunicorn==21.2.0 \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d3085b7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,628 @@ + + + + + + RDP 자동 로그인 제어 패널 + + + +
+

🖥️ RDP 자동 로그인 제어 패널

+ +
+

📡 접속 가능 주소

+
+
접속 정보를 불러오는 중...
+
+
+ +
+
+ 호스트명 + - +
+
+ IP 주소 + - +
+
+ Tailscale IP + - +
+
+ RDP 포트 + 3389 +
+
+ 서비스 상태 + + + 확인 중... + +
+
+ +
+ 자동 로그인 + + OFF +
+ +
+ + +
+ +
+
+ +
+
+ 📋 서비스 로그 + +
+
+ 로그를 불러오는 중... +
+
+
+ + + + \ No newline at end of file diff --git a/uninstall_web_control.sh b/uninstall_web_control.sh new file mode 100755 index 0000000..ca10942 --- /dev/null +++ b/uninstall_web_control.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# RDP 자동 로그인 웹 제어 패널 제거 스크립트 + +set -e + +echo "=========================================" +echo "RDP 자동 로그인 웹 제어 패널 제거" +echo "=========================================" + +# 색상 정의 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# root 권한 확인 +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}이 스크립트는 root 권한으로 실행해야 합니다${NC}" + exit 1 +fi + +# 서비스 중지 및 비활성화 +echo -e "${YELLOW}웹 서비스 중지 중...${NC}" +systemctl stop rdp-control-web.service 2>/dev/null || true +systemctl disable rdp-control-web.service 2>/dev/null || true + +# systemd 서비스 파일 제거 +echo -e "${YELLOW}systemd 서비스 제거 중...${NC}" +rm -f /etc/systemd/system/rdp-control-web.service +systemctl daemon-reload + +# Python 가상 환경 제거 +echo -e "${YELLOW}Python 가상 환경 제거 중...${NC}" +rm -rf /root/proxmox-rdp-autosetup/venv + +# 방화벽 규칙 제거 (ufw가 설치되어 있는 경우) +if command -v ufw &> /dev/null; then + echo -e "${YELLOW}방화벽 규칙 제거 중...${NC}" + ufw delete allow 5000/tcp 2>/dev/null || true + echo -e "${GREEN}✅ 포트 5000 방화벽 규칙이 제거되었습니다${NC}" +fi + +echo "" +echo -e "${GREEN}=========================================${NC}" +echo -e "${GREEN}웹 제어 패널이 제거되었습니다${NC}" +echo -e "${GREEN}=========================================${NC}" +echo "" +echo "참고: 다음 파일들은 수동으로 삭제해야 합니다:" +echo " - /root/proxmox-rdp-autosetup/rdp_control_web.py" +echo " - /root/proxmox-rdp-autosetup/templates/" +echo " - /root/proxmox-rdp-autosetup/requirements.txt" \ No newline at end of file