# PostgreSQL SSH 터널 및 데이터베이스 접속 가이드 ## 📋 목차 1. [SSH 터널 설정](#ssh-터널-설정) 2. [PostgreSQL 접속](#postgresql-접속) 3. [Python으로 DB 조회](#python으로-db-조회) 4. [주요 테이블 구조](#주요-테이블-구조) 5. [자주 사용하는 쿼리](#자주-사용하는-쿼리) 6. [문제 해결](#문제-해결) --- ## SSH 터널 설정 ### 1. 현재 SSH 터널 확인 ```bash # 실행 중인 SSH 터널 확인 ps aux | grep "ssh.*5433" | grep -v grep # 특정 포트 사용 확인 netstat -tlnp | grep 5433 lsof -i :5433 ``` ### 2. SSH 터널 생성 ```bash # 기본 SSH 터널 생성 (백그라운드 실행) ssh -f -N -L 5433:localhost:5432 admin@124.55.18.179 -p 51123 # StrictHostKeyChecking 비활성화 (테스트용) ssh -o StrictHostKeyChecking=no -f -N -L 5433:localhost:5432 admin@124.55.18.179 -p 51123 # 옵션 설명: # -f : 백그라운드 실행 # -N : 원격 명령 실행하지 않음 (포트 포워딩만) # -L : 로컬 포트 포워딩 (로컬포트:원격호스트:원격포트) # -p : SSH 포트 지정 ``` ### 3. SSH 터널 종료 ```bash # 프로세스 ID 찾기 ps aux | grep "ssh.*5433" # 프로세스 종료 sudo kill [PID] # 또는 한 번에 sudo pkill -f "ssh.*5433" ``` --- ## PostgreSQL 접속 ### 1. 연결 정보 ``` Host: localhost (SSH 터널 사용시) 또는 192.168.219.45 (직접 연결) Port: 5433 (터널 로컬 포트) 또는 5432 (직접 연결) Database: main_db (기존 auth_db는 더 이상 사용 안함) Username: robeings Password: robeings ``` ### 2. psql 명령어로 접속 ```bash # psql 설치 (Ubuntu/Debian) sudo apt-get install postgresql-client # DB 접속 (51123 서버에서 직접) psql postgresql://robeings:robeings@localhost:5432/main_db # 또는 (51124 서버에서 SSH 터널 사용) psql postgresql://robeings:robeings@localhost:5433/main_db # 또는 psql -h localhost -p 5432 -U robeings -d main_db ``` ### 3. psql 기본 명령어 ```sql -- 테이블 목록 보기 \dt -- 특정 테이블 구조 보기 \d gmail_tokens \d users -- 데이터베이스 목록 \l -- 현재 데이터베이스 정보 \conninfo -- 종료 \q ``` --- ## Python으로 DB 조회 ### 1. 필요한 패키지 설치 ```bash pip install psycopg2-binary ``` ### 2. 기본 연결 및 조회 ```python import psycopg2 import json # DB 연결 (51123 서버에서) conn = psycopg2.connect('postgresql://robeings:robeings@localhost:5432/main_db') # DB 연결 (51124 서버에서 SSH 터널 사용) conn = psycopg2.connect('postgresql://robeings:robeings@localhost:5433/main_db') cur = conn.cursor() # 쿼리 실행 cur.execute("SELECT * FROM users LIMIT 5") rows = cur.fetchall() for row in rows: print(row) # 연결 종료 cur.close() conn.close() ``` ### 3. 안전한 쿼리 실행 (파라미터 바인딩) ```python # SQL Injection 방지를 위해 항상 파라미터 바인딩 사용 user_id = 'b6ea2ee0-a15a-5cf4-93a9-a9ca20d4c4a0' cur.execute( "SELECT * FROM users WHERE id = %s", (user_id,) # 튜플로 전달 ) ``` --- ## 주요 테이블 구조 ### 1. users 테이블 ```sql CREATE TABLE users ( id UUID PRIMARY KEY, email VARCHAR UNIQUE, name VARCHAR, picture VARCHAR, oauth_provider VARCHAR, oauth_id VARCHAR, is_active BOOLEAN, created_at TIMESTAMP, updated_at TIMESTAMP, last_login_at TIMESTAMP, username VARCHAR ); ``` ### 2. gmail_tokens 테이블 ```sql CREATE TABLE gmail_tokens ( id SERIAL PRIMARY KEY, user_id UUID REFERENCES users(id), slack_id VARCHAR(100), -- Slack 사용자 ID (새로 추가) is_equipped BOOLEAN, has_token BOOLEAN, -- token_data 존재 여부 access_token TEXT, -- 개별 컬럼으로 분리 refresh_token TEXT, -- 개별 컬럼으로 분리 token_type VARCHAR, expires_at FLOAT, token_data JSONB, -- 레거시 호환용 oauth_config JSONB, -- OAuth 설정 정보 scopes JSONB, -- Gmail API 권한 목록 metadata JSONB, expiry TIMESTAMP, created_at TIMESTAMP, updated_at TIMESTAMP, robeing_id VARCHAR, -- 로빙 ID (rb8001 등) equipped_to VARCHAR ); ``` ### 3. conversation_logs 테이블 ```sql CREATE TABLE conversation_logs ( id SERIAL PRIMARY KEY, robeing_id VARCHAR, channel_id VARCHAR, message VARCHAR, response VARCHAR, intent VARCHAR, confidence DOUBLE PRECISION, timestamp TIMESTAMP, user_id UUID REFERENCES users(id), -- NULL 허용으로 변경됨 slack_user_id VARCHAR(100) -- Slack ID 직접 저장 (새로 추가) ); ``` ### 4. slack_user_mapping 테이블 ```sql CREATE TABLE slack_user_mapping ( id UUID PRIMARY KEY, slack_user_id VARCHAR(100) NOT NULL, slack_workspace_id UUID, user_id UUID REFERENCES users(id), workspace_member_id UUID, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` --- ## 자주 사용하는 쿼리 ### 1. Gmail 토큰 확인 ```python import psycopg2 import json conn = psycopg2.connect('postgresql://robeings:robeings@localhost:5432/main_db') cur = conn.cursor() # Gmail 토큰 상태 확인 query = """ SELECT u.username, u.email, g.user_id, g.is_equipped, g.robeing_id, g.token_data, g.scopes FROM gmail_tokens g JOIN users u ON g.user_id = u.id ORDER BY g.created_at DESC """ cur.execute(query) rows = cur.fetchall() for row in rows: print(f"\nUser: {row[0]} ({row[1]})") print(f" UUID: {row[2]}") print(f" Equipped: {row[3]}") print(f" Robeing: {row[4]}") token_data = row[5] if row[5] else {} if token_data and 'access_token' in token_data: access_token = token_data.get('access_token', '') if access_token.startswith('ya29'): print(f" Token: 실제 OAuth 토큰 있음") else: print(f" Token: 테스트 토큰") scopes = row[6] if row[6] else [] if scopes: print(f" Scopes: {len(scopes)}개") cur.close() conn.close() ``` ### 2. Slack User ID를 UUID로 변환 ```python import uuid def slack_id_to_uuid(slack_user_id): """Slack User ID를 일관된 UUID로 변환""" namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') # 51123 매핑 API 호출로 UUID 조회 return call_mapping_api(slack_user_id) # 예시 slack_id = 'U091UNVE41M' user_uuid = slack_id_to_uuid(slack_id) print(f"Slack ID: {slack_id}") print(f"UUID: {user_uuid}") # 출력: b6ea2ee0-a15a-5cf4-93a9-a9ca20d4c4a0 ``` ### 3. 사용자별 Gmail 토큰 추가/업데이트 ```python import psycopg2 import json import uuid def add_gmail_token(slack_user_id, email, access_token, refresh_token): """Gmail 토큰 추가 또는 업데이트""" # Slack ID를 UUID로 변환 namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') # 51123 매핑 API를 통해 UUID 조회 user_uuid = await get_uuid_from_mapping_api(slack_user_id) conn = psycopg2.connect('postgresql://robeings:robeings@localhost:5432/main_db') cur = conn.cursor() try: # 1. users 테이블에 사용자 추가 (없으면) cur.execute(""" INSERT INTO users (id, email, username, created_at) VALUES (%s, %s, %s, NOW()) ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email """, (user_uuid, email, slack_user_id)) # 2. gmail_tokens 추가 또는 업데이트 token_data = { "access_token": access_token, "refresh_token": refresh_token, "token_type": "Bearer" } cur.execute(""" INSERT INTO gmail_tokens (user_id, token_data, is_equipped, robeing_id, created_at) VALUES (%s, %s::jsonb, true, 'rb8001', NOW()) ON CONFLICT (user_id) DO UPDATE SET token_data = EXCLUDED.token_data, updated_at = NOW() """, (user_uuid, json.dumps(token_data))) conn.commit() print(f"✅ Gmail 토큰 저장 완료: {email}") except Exception as e: conn.rollback() print(f"❌ 오류 발생: {e}") finally: cur.close() conn.close() ``` --- ## 문제 해결 ### 1. SSH 터널이 연결되지 않을 때 ```bash # 기존 터널 프로세스 종료 sudo pkill -f "ssh.*5433" # 포트 사용 확인 lsof -i :5433 # 다시 연결 ssh -f -N -L 5433:localhost:5432 admin@124.55.18.179 -p 51123 ``` ### 2. psycopg2 설치 오류 ```bash # Ubuntu/Debian sudo apt-get install libpq-dev python3-dev pip install psycopg2 # 또는 바이너리 버전 사용 pip install psycopg2-binary ``` ### 3. UUID 타입 오류 ```python # UUID 문자열을 PostgreSQL UUID 타입으로 변환 import uuid # 방법 1: 직접 캐스팅 cur.execute( "SELECT * FROM users WHERE id = %s::uuid", ('b6ea2ee0-a15a-5cf4-93a9-a9ca20d4c4a0',) ) # 방법 2: uuid 객체 사용 user_uuid = uuid.UUID('b6ea2ee0-a15a-5cf4-93a9-a9ca20d4c4a0') cur.execute( "SELECT * FROM users WHERE id = %s", (str(user_uuid),) ) ``` ### 4. JSONB 필드 처리 ```python import json # JSONB 데이터 삽입 data = {"key": "value", "nested": {"field": "data"}} cur.execute( "INSERT INTO table_name (jsonb_column) VALUES (%s::jsonb)", (json.dumps(data),) ) # JSONB 데이터 조회 cur.execute("SELECT jsonb_column FROM table_name") row = cur.fetchone() json_data = row[0] # 자동으로 Python dict로 변환됨 ``` --- ## 환경 변수 설정 `.env` 파일에 추가: ```bash # PostgreSQL Connection (51123 서버 직접 연결) POSTGRES_CONNECTION_STRING=postgresql://robeings:robeings@localhost:5432/main_db # PostgreSQL Connection (51124 서버 SSH 터널) POSTGRES_CONNECTION_STRING=postgresql://robeings:robeings@localhost:5433/main_db # SSH Tunnel Config SSH_HOST=124.55.18.179 SSH_PORT=51123 SSH_USER=admin LOCAL_PORT=5433 REMOTE_PORT=5432 ``` Python에서 사용: ```python import os from dotenv import load_dotenv load_dotenv() conn_string = os.getenv('POSTGRES_CONNECTION_STRING') conn = psycopg2.connect(conn_string) ``` --- ## 자동화 스크립트 ### SSH 터널 자동 재연결 스크립트 ```bash #!/bin/bash # /home/heejae/scripts/ssh_tunnel_monitor.sh LOCAL_PORT=5433 SSH_CMD="ssh -f -N -L 5433:localhost:5432 admin@124.55.18.179 -p 51123" # 터널이 살아있는지 확인 if ! nc -z localhost $LOCAL_PORT 2>/dev/null; then echo "SSH tunnel is down. Restarting..." # 기존 프로세스 종료 pkill -f "ssh.*$LOCAL_PORT" # 새 터널 생성 $SSH_CMD echo "SSH tunnel restarted" else echo "SSH tunnel is running" fi ``` crontab에 추가 (5분마다 체크): ```bash */5 * * * * /home/heejae/scripts/ssh_tunnel_monitor.sh ``` --- ## 참고 링크 - [PostgreSQL 공식 문서](https://www.postgresql.org/docs/) - [psycopg2 문서](https://www.psycopg.org/docs/) - [SSH 터널링 가이드](https://www.ssh.com/academy/ssh/tunneling) --- --- ## 서버별 DB 연결 상태 (2025-08-26 현재) ### 51123 서버 (192.168.219.45) - **PostgreSQL**: 로컬 실행 중 (포트 5432) - **데이터베이스**: main_db - **직접 연결**: `postgresql://robeings:robeings@localhost:5432/main_db` ### 51124 서버 (192.168.219.52) - **PostgreSQL**: 없음 (51123 서버 DB 사용) - **연결 방법**: SSH 터널 - **터널 설정**: `ssh -N -L 5433:localhost:5432 admin@192.168.219.45` - **연결 문자열**: `postgresql://robeings:robeings@localhost:5433/main_db` ### 주요 서비스별 DB 연결 | 서비스 | 서버 | DB 연결 방법 | |--------|------|-------------| | auth-server | 51123 | 직접 연결 (localhost:5432) | | robeing-gateway | 51123 | 직접 연결 (localhost:5432) | | rb8001 | 51124 | SSH 터널 (localhost:5433) | | State Service | 51124 | SSH 터널 (localhost:5433) | | skill-email | 51124 | SSH 터널 (localhost:5433) | ### 주의사항 - **auth_db는 더 이상 사용하지 않음** (main_db로 통합) - State Service의 DATABASE_URL이 auth_db를 참조하면 main_db로 수정 필요 - SSH 터널은 시스템 재시작 시 재생성 필요 --- *마지막 업데이트: 2025-08-26*