#!/usr/bin/env python3 """GUARDiA Manager 서버 배포 스크립트""" import paramiko, time, sys, os, io, zipfile HOST = 'zioinfo.co.kr'; USER = 'root'; PASS = '1q2w3e!Q' LOCAL_DIST = 'C:/GUARDiA/manager/dist' LOCAL_BACKEND = 'C:/GUARDiA/manager/backend' SEP = chr(92) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(HOST, username=USER, password=PASS, timeout=15) sftp = client.open_sftp() def run(label, cmd, timeout=60): print(f'\n[{label}]') chan = client.get_transport().open_session() chan.set_combine_stderr(True) chan.exec_command(cmd) start = time.time() while not chan.exit_status_ready(): if chan.recv_ready(): sys.stdout.buffer.write(chan.recv(4096)); sys.stdout.flush() if time.time() - start > timeout: print('[TIMEOUT]'); break time.sleep(0.2) while chan.recv_ready(): sys.stdout.buffer.write(chan.recv(4096)) sys.stdout.flush() rc = chan.recv_exit_status() print(f'\n→ exit={rc}') return rc # 1. 서버 디렉터리 생성 run('디렉터리 생성', 'mkdir -p /var/www/manager /opt/manager/backend /opt/manager/logs && echo ok') # 2. React 빌드 결과 zip → 업로드 print('\n[dist 파일 패키징...]') zip_buf = io.BytesIO() count = 0 with zipfile.ZipFile(zip_buf, 'w', zipfile.ZIP_DEFLATED) as zf: for root, dirs, files in os.walk(LOCAL_DIST): rel = os.path.relpath(root, LOCAL_DIST).replace(SEP, '/') for f in files: arc = f if rel == '.' else f'{rel}/{f}' zf.write(os.path.join(root, f), arc); count += 1 zip_buf.seek(0) with sftp.open('/tmp/manager-dist.zip', 'wb') as f: f.write(zip_buf.read()) print(f'{count}개 파일 업로드') run('dist 배포', 'cd /var/www/manager && unzip -q -o /tmp/manager-dist.zip && echo "deployed" && ls | head -5') # 3. 백엔드 소스 업로드 print('\n[백엔드 소스 업로드...]') for item in os.listdir(LOCAL_BACKEND): lp = os.path.join(LOCAL_BACKEND, item) if os.path.isfile(lp) and item.endswith('.py'): sftp.put(lp, f'/opt/manager/backend/{item}') print(f' {item}') # routers / core for sub in ('routers', 'core'): sub_dir = os.path.join(LOCAL_BACKEND, sub) if not os.path.isdir(sub_dir): continue run(f'{sub} 디렉터리', f'mkdir -p /opt/manager/backend/{sub}') for fn in os.listdir(sub_dir): if fn.endswith('.py'): sftp.put(os.path.join(sub_dir, fn), f'/opt/manager/backend/{sub}/{fn}') print(f' {sub}/{fn}') # requirements + .env for fn in ('requirements.txt', '.env'): lp = os.path.join(LOCAL_BACKEND, fn) if os.path.exists(lp): sftp.put(lp, f'/opt/manager/backend/{fn}') print(f' {fn}') # 4. Python venv + 패키지 run('Python venv', 'python3 -m venv /opt/manager/venv && ' '/opt/manager/venv/bin/pip install -r /opt/manager/backend/requirements.txt -q && echo ok', 120) # 5. .env 서버 JWT secret 동기화 run('.env JWT secret 동기화', "GUARDIA_SECRET=$(grep GUARDIA_JWT_SECRET /opt/guardia/app/.env 2>/dev/null | cut -d= -f2-) && " "if [ -n \"$GUARDIA_SECRET\" ]; then " " sed -i \"s|GUARDIA_JWT_SECRET=.*|GUARDIA_JWT_SECRET=$GUARDIA_SECRET|\" /opt/manager/backend/.env && " " echo 'JWT secret synced'; fi") # 6. systemd 서비스 svc = """[Unit] Description=GUARDiA Manager API Backend After=network.target guardia.service [Service] User=root WorkingDirectory=/opt/manager/backend EnvironmentFile=-/opt/manager/backend/.env ExecStart=/opt/manager/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8002 --workers 1 Restart=on-failure RestartSec=5 StandardOutput=append:/opt/manager/logs/backend.log StandardError=append:/opt/manager/logs/backend.log [Install] WantedBy=multi-user.target """ with sftp.open('/etc/systemd/system/guardia-manager.service', 'w') as f: f.write(svc) # 7. Nginx 설정 nginx_conf = r"""server { listen 8090; server_name _; root /var/www/manager; index index.html; location / { try_files $uri $uri/ /index.html; add_header Cache-Control no-cache; } location /api/ { proxy_pass http://127.0.0.1:8002; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 60s; } location /guardia-api/ { proxy_pass http://127.0.0.1:8001/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location ~* \.(js|css|png|ico|svg|woff2)$ { expires 7d; add_header Cache-Control "public, immutable"; } gzip on; gzip_types text/plain text/css application/javascript application/json; } """ with sftp.open('/etc/nginx/sites-available/guardia-manager', 'w') as f: f.write(nginx_conf) # 8. 서비스 시작 run('UFW 8090 오픈', 'ufw allow 8090/tcp && ufw allow 8002/tcp && echo ok') run('systemd 등록 + 시작', 'systemctl daemon-reload && ' 'systemctl enable guardia-manager && ' 'systemctl restart guardia-manager && ' 'sleep 5 && systemctl is-active guardia-manager') run('Nginx 활성화', 'ln -sf /etc/nginx/sites-available/guardia-manager /etc/nginx/sites-enabled/ && ' 'nginx -t && systemctl reload nginx && echo NGINX_OK') # 9. 헬스체크 run('헬스체크', 'curl -s http://localhost:8002/health && echo "" && ' 'curl -s -o /dev/null -w "Manager UI: HTTP %{http_code}" http://localhost:8090/') sftp.close(); client.close() print('\n\n=== 배포 완료 ===') print('GUARDiA Manager: http://zioinfo.co.kr:8090')