Add RDP Toggle API with venv support
RDP 관련 파일들을 RDP 폴더로 정리하고 API 시스템 추가 주요 변경사항: - FastAPI 기반 RDP/Shell 모드 전환 API 서버 추가 - venv 환경을 사용하는 자동 설치 스크립트 - requirements.txt로 패키지 의존성 관리 - systemd 서비스로 자동 시작 설정 - CORS 지원으로 외부 프론트엔드 연동 가능 - 실시간 상태 모니터링 API - 웹 기반 컨트롤 패널 포함 파일 구성: - rdp-toggle-api.py: FastAPI REST API 서버 - install-rdp-api.sh: venv 환경 자동 설치 - requirements.txt: Python 패키지 의존성 - rdp-toggle-web.html: 웹 컨트롤 패널 - README.md: 사용 가이드 API 기능: - GET /status: 현재 모드 확인 - POST /toggle: RDP/Shell 모드 전환 - GET /config: 설정 확인 - PUT /config: 설정 업데이트 리액트 프론트엔드에서 토글로 화면 모드 제어 가능 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
276
RDP/rdp-toggle-api.py
Normal file
276
RDP/rdp-toggle-api.py
Normal file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
RDP Toggle API Server
|
||||
Control RDP/Shell display mode via REST API
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import uvicorn
|
||||
|
||||
app = FastAPI(title="RDP Toggle API", version="1.0.0")
|
||||
|
||||
# CORS 설정 (외부에서 접근 가능)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# 상태 저장 파일
|
||||
STATE_FILE = "/var/lib/rdp-toggle/state.json"
|
||||
CONFIG_FILE = "/var/lib/rdp-toggle/config.json"
|
||||
|
||||
# 기본 설정
|
||||
DEFAULT_CONFIG = {
|
||||
"rdp_server": "192.168.0.229",
|
||||
"rdp_username": "0bin",
|
||||
"rdp_password": "trajet6640",
|
||||
"local_user": "rdpuser"
|
||||
}
|
||||
|
||||
class ToggleRequest(BaseModel):
|
||||
mode: str # "rdp" or "shell"
|
||||
|
||||
class ConfigUpdate(BaseModel):
|
||||
rdp_server: Optional[str] = None
|
||||
rdp_username: Optional[str] = None
|
||||
rdp_password: Optional[str] = None
|
||||
local_user: Optional[str] = None
|
||||
|
||||
class StatusResponse(BaseModel):
|
||||
current_mode: str
|
||||
rdp_active: bool
|
||||
last_changed: str
|
||||
config: dict
|
||||
|
||||
def ensure_directories():
|
||||
"""필요한 디렉토리 생성"""
|
||||
os.makedirs("/var/lib/rdp-toggle", exist_ok=True)
|
||||
|
||||
def load_state():
|
||||
"""현재 상태 로드"""
|
||||
if os.path.exists(STATE_FILE):
|
||||
with open(STATE_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
return {
|
||||
"current_mode": "shell",
|
||||
"rdp_active": False,
|
||||
"last_changed": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
def save_state(state):
|
||||
"""상태 저장"""
|
||||
ensure_directories()
|
||||
with open(STATE_FILE, 'w') as f:
|
||||
json.dump(state, f)
|
||||
|
||||
def load_config():
|
||||
"""설정 로드"""
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
return DEFAULT_CONFIG
|
||||
|
||||
def save_config(config):
|
||||
"""설정 저장"""
|
||||
ensure_directories()
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f)
|
||||
|
||||
def enable_rdp():
|
||||
"""RDP 모드 활성화"""
|
||||
config = load_config()
|
||||
|
||||
# 자동 로그인 설정
|
||||
subprocess.run([
|
||||
"mkdir", "-p", "/etc/systemd/system/getty@tty1.service.d"
|
||||
], check=False)
|
||||
|
||||
override_content = f"""[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin {config['local_user']} --noclear %I $TERM
|
||||
Type=idle"""
|
||||
|
||||
with open("/etc/systemd/system/getty@tty1.service.d/override.conf", "w") as f:
|
||||
f.write(override_content)
|
||||
|
||||
# X 자동 시작 스크립트
|
||||
bash_profile = f"""if [[ -z $DISPLAY ]] && [[ $(tty) == /dev/tty1 ]]; then
|
||||
startx
|
||||
logout
|
||||
fi"""
|
||||
|
||||
user_home = f"/home/{config['local_user']}"
|
||||
with open(f"{user_home}/.bash_profile", "w") as f:
|
||||
f.write(bash_profile)
|
||||
|
||||
# .xinitrc 업데이트
|
||||
xinitrc_content = f"""#!/bin/bash
|
||||
xset -dpms
|
||||
xset s off
|
||||
xset s noblank
|
||||
unclutter -idle 0.1 -root &
|
||||
openbox-session &
|
||||
sleep 2
|
||||
xfreerdp3 /v:{config['rdp_server']} /u:{config['rdp_username']} /p:"{config['rdp_password']}" +f /cert:ignore +dynamic-resolution /sound:sys:alsa +clipboard
|
||||
pkill -SIGTERM Xorg"""
|
||||
|
||||
with open(f"{user_home}/.xinitrc", "w") as f:
|
||||
f.write(xinitrc_content)
|
||||
|
||||
subprocess.run(["chmod", "+x", f"{user_home}/.xinitrc"])
|
||||
subprocess.run(["chown", f"{config['local_user']}:{config['local_user']}",
|
||||
f"{user_home}/.bash_profile", f"{user_home}/.xinitrc"])
|
||||
|
||||
# systemd 리로드 및 getty 재시작
|
||||
subprocess.run(["systemctl", "daemon-reload"])
|
||||
subprocess.run(["systemctl", "restart", "getty@tty1.service"])
|
||||
|
||||
return True
|
||||
|
||||
def disable_rdp():
|
||||
"""Shell 모드로 전환 (RDP 비활성화)"""
|
||||
config = load_config()
|
||||
|
||||
# RDP 프로세스 종료
|
||||
subprocess.run(["pkill", "-u", config['local_user'], "xfreerdp3"], check=False)
|
||||
subprocess.run(["pkill", "-u", config['local_user'], "-f", "xinit|Xorg|openbox"], check=False)
|
||||
|
||||
# 자동 로그인 설정 제거
|
||||
subprocess.run(["rm", "-f", "/etc/systemd/system/getty@tty1.service.d/override.conf"], check=False)
|
||||
|
||||
# 자동 시작 스크립트 제거
|
||||
user_home = f"/home/{config['local_user']}"
|
||||
subprocess.run(["rm", "-f", f"{user_home}/.bash_profile"], check=False)
|
||||
|
||||
# systemd 리로드 및 getty 재시작
|
||||
subprocess.run(["systemctl", "daemon-reload"])
|
||||
subprocess.run(["systemctl", "restart", "getty@tty1.service"])
|
||||
|
||||
# TTY1으로 전환
|
||||
subprocess.run(["chvt", "1"], check=False)
|
||||
|
||||
return True
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""API 정보"""
|
||||
return {
|
||||
"name": "RDP Toggle API",
|
||||
"version": "1.0.0",
|
||||
"endpoints": {
|
||||
"GET /status": "현재 상태 확인",
|
||||
"POST /toggle": "모드 전환 (rdp/shell)",
|
||||
"GET /config": "현재 설정 확인",
|
||||
"PUT /config": "설정 업데이트"
|
||||
}
|
||||
}
|
||||
|
||||
@app.get("/status", response_model=StatusResponse)
|
||||
async def get_status():
|
||||
"""현재 상태 반환"""
|
||||
state = load_state()
|
||||
config = load_config()
|
||||
|
||||
# 실제 프로세스 확인
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["pgrep", "-f", "xfreerdp3"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
rdp_running = result.returncode == 0
|
||||
state["rdp_active"] = rdp_running
|
||||
state["current_mode"] = "rdp" if rdp_running else "shell"
|
||||
except:
|
||||
pass
|
||||
|
||||
return StatusResponse(
|
||||
current_mode=state["current_mode"],
|
||||
rdp_active=state["rdp_active"],
|
||||
last_changed=state["last_changed"],
|
||||
config=config
|
||||
)
|
||||
|
||||
@app.post("/toggle")
|
||||
async def toggle_mode(request: ToggleRequest):
|
||||
"""모드 전환"""
|
||||
if request.mode not in ["rdp", "shell"]:
|
||||
raise HTTPException(status_code=400, detail="Mode must be 'rdp' or 'shell'")
|
||||
|
||||
state = load_state()
|
||||
|
||||
try:
|
||||
if request.mode == "rdp":
|
||||
success = enable_rdp()
|
||||
if success:
|
||||
state["current_mode"] = "rdp"
|
||||
state["rdp_active"] = True
|
||||
else: # shell
|
||||
success = disable_rdp()
|
||||
if success:
|
||||
state["current_mode"] = "shell"
|
||||
state["rdp_active"] = False
|
||||
|
||||
state["last_changed"] = datetime.now().isoformat()
|
||||
save_state(state)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"mode": request.mode,
|
||||
"message": f"Switched to {request.mode} mode"
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.get("/config")
|
||||
async def get_config():
|
||||
"""현재 설정 반환"""
|
||||
return load_config()
|
||||
|
||||
@app.put("/config")
|
||||
async def update_config(update: ConfigUpdate):
|
||||
"""설정 업데이트"""
|
||||
config = load_config()
|
||||
|
||||
if update.rdp_server:
|
||||
config["rdp_server"] = update.rdp_server
|
||||
if update.rdp_username:
|
||||
config["rdp_username"] = update.rdp_username
|
||||
if update.rdp_password is not None:
|
||||
config["rdp_password"] = update.rdp_password
|
||||
if update.local_user:
|
||||
config["local_user"] = update.local_user
|
||||
|
||||
save_config(config)
|
||||
|
||||
# 현재 RDP 모드인 경우 재시작
|
||||
state = load_state()
|
||||
if state["rdp_active"]:
|
||||
disable_rdp()
|
||||
enable_rdp()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"config": config
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Root 권한 확인
|
||||
if os.geteuid() != 0:
|
||||
print("This script must be run as root")
|
||||
exit(1)
|
||||
|
||||
ensure_directories()
|
||||
|
||||
# 서버 시작
|
||||
uvicorn.run(app, host="0.0.0.0", port=8090)
|
||||
Reference in New Issue
Block a user