diff --git a/backend/migrations/021_add_5_photo_support.sql b/backend/migrations/021_add_5_photo_support.sql index df8f39c..02ad0ff 100644 --- a/backend/migrations/021_add_5_photo_support.sql +++ b/backend/migrations/021_add_5_photo_support.sql @@ -175,3 +175,7 @@ BEGIN END $$; COMMIT; + + + + diff --git a/backup_script.sh b/backup_script.sh new file mode 100755 index 0000000..1a95770 --- /dev/null +++ b/backup_script.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# M 프로젝트 자동 백업 스크립트 +# 사용법: ./backup_script.sh + +set -e + +# 백업 디렉토리 설정 +BACKUP_DIR="/Users/hyungi/M-Project/backups" +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_FOLDER="$BACKUP_DIR/$DATE" + +echo "🚀 M 프로젝트 백업 시작: $DATE" + +# 백업 폴더 생성 +mkdir -p "$BACKUP_FOLDER" + +# 1. 데이터베이스 백업 (가장 중요!) +echo "📊 데이터베이스 백업 중..." +docker exec m-project-db pg_dump -U mproject mproject > "$BACKUP_FOLDER/database_backup.sql" +echo "✅ 데이터베이스 백업 완료" + +# 2. Docker 볼륨 백업 +echo "💾 Docker 볼륨 백업 중..." +docker run --rm -v m-project_postgres_data:/data -v "$BACKUP_FOLDER":/backup alpine tar czf /backup/postgres_volume.tar.gz -C /data . +docker run --rm -v m-project_uploads:/data -v "$BACKUP_FOLDER":/backup alpine tar czf /backup/uploads_volume.tar.gz -C /data . +echo "✅ Docker 볼륨 백업 완료" + +# 3. 설정 파일 백업 +echo "⚙️ 설정 파일 백업 중..." +cp docker-compose.yml "$BACKUP_FOLDER/" +cp -r nginx/ "$BACKUP_FOLDER/" +cp -r backend/migrations/ "$BACKUP_FOLDER/" +echo "✅ 설정 파일 백업 완료" + +# 4. 백업 정보 기록 +echo "📝 백업 정보 기록 중..." +cat > "$BACKUP_FOLDER/backup_info.txt" << EOF +M 프로젝트 백업 정보 +=================== +백업 일시: $(date) +백업 타입: 전체 백업 +백업 위치: $BACKUP_FOLDER + +포함된 내용: +- database_backup.sql: PostgreSQL 데이터베이스 덤프 +- postgres_volume.tar.gz: PostgreSQL 데이터 볼륨 +- uploads_volume.tar.gz: 업로드 파일 볼륨 +- docker-compose.yml: Docker 설정 +- nginx/: Nginx 설정 +- migrations/: 데이터베이스 마이그레이션 파일 + +복구 방법: +1. ./restore_script.sh $(pwd) + +또는 수동 복구: +1. docker-compose down +2. docker volume rm m-project_postgres_data m-project_uploads +3. docker-compose up -d db +4. docker exec -i m-project-db psql -U mproject mproject < database_backup.sql +5. docker-compose up -d + +백업 정책: +- 최신 10개 백업만 유지 (용량 절약) +- 매일 오후 9시 자동 백업 +- 매주 일요일 오후 9시 30분 추가 백업 +EOF + +# 5. 백업 크기 확인 +BACKUP_SIZE=$(du -sh "$BACKUP_FOLDER" | cut -f1) +echo "📏 백업 크기: $BACKUP_SIZE" + +# 6. 오래된 백업 정리 (최신 10개만 유지) +echo "🧹 오래된 백업 정리 중..." +BACKUP_COUNT=$(find "$BACKUP_DIR" -type d -name "20*" | wc -l) +if [ $BACKUP_COUNT -gt 10 ]; then + REMOVE_COUNT=$((BACKUP_COUNT - 10)) + echo "📊 현재 백업 개수: $BACKUP_COUNT개, 삭제할 개수: $REMOVE_COUNT개" + find "$BACKUP_DIR" -type d -name "20*" | sort | head -n $REMOVE_COUNT | xargs rm -rf + echo "✅ 오래된 백업 $REMOVE_COUNT개 삭제 완료" +else + echo "📊 현재 백업 개수: $BACKUP_COUNT개 (정리 불필요)" +fi + +echo "🎉 백업 완료!" +echo "📁 백업 위치: $BACKUP_FOLDER" +echo "📏 백업 크기: $BACKUP_SIZE" diff --git a/frontend/daily-work.html b/frontend/daily-work.html index 3cdc7b7..ba2a00c 100644 --- a/frontend/daily-work.html +++ b/frontend/daily-work.html @@ -341,6 +341,12 @@ // API에서 최신 프로젝트 데이터 가져오기 const apiUrl = window.API_BASE_URL || (() => { const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } if (hostname === 'm.hyungi.net') { return 'https://m-api.hyungi.net/api'; } diff --git a/frontend/issues-archive.html b/frontend/issues-archive.html index f8231ae..0f91897 100644 --- a/frontend/issues-archive.html +++ b/frontend/issues-archive.html @@ -287,7 +287,19 @@ // 프로젝트 로드 async function loadProjects() { try { - const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const apiUrl = window.API_BASE_URL || (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } + if (hostname === 'm.hyungi.net') { + return 'https://m-api.hyungi.net/api'; + } + return '/api'; + })(); const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, diff --git a/frontend/issues-dashboard.html b/frontend/issues-dashboard.html index a31bac5..211fc48 100644 --- a/frontend/issues-dashboard.html +++ b/frontend/issues-dashboard.html @@ -325,6 +325,12 @@ try { const apiUrl = window.API_BASE_URL || (() => { const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } if (hostname === 'm.hyungi.net') { return 'https://m-api.hyungi.net/api'; } @@ -342,6 +348,12 @@ try { const apiUrl = window.API_BASE_URL || (() => { const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } if (hostname === 'm.hyungi.net') { return 'https://m-api.hyungi.net/api'; } diff --git a/frontend/issues-inbox.html b/frontend/issues-inbox.html index 256d56b..28e8809 100644 --- a/frontend/issues-inbox.html +++ b/frontend/issues-inbox.html @@ -670,6 +670,12 @@ try { const apiUrl = window.API_BASE_URL || (() => { const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } if (hostname === 'm.hyungi.net') { return 'https://m-api.hyungi.net/api'; } diff --git a/frontend/reports-daily.html b/frontend/reports-daily.html index a90449d..d91b8a0 100644 --- a/frontend/reports-daily.html +++ b/frontend/reports-daily.html @@ -234,7 +234,19 @@ // 프로젝트 목록 로드 async function loadProjects() { try { - const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const apiUrl = window.API_BASE_URL || (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } + if (hostname === 'm.hyungi.net') { + return 'https://m-api.hyungi.net/api'; + } + return '/api'; + })(); const response = await fetch(`${apiUrl}/projects/`, { headers: { @@ -302,7 +314,19 @@ } try { - const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const apiUrl = window.API_BASE_URL || (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } + if (hostname === 'm.hyungi.net') { + return 'https://m-api.hyungi.net/api'; + } + return '/api'; + })(); const response = await fetch(`${apiUrl}/reports/daily-preview?project_id=${selectedProjectId}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` @@ -427,7 +451,19 @@ button.innerHTML = '생성 중...'; button.disabled = true; - const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const apiUrl = window.API_BASE_URL || (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } + if (hostname === 'm.hyungi.net') { + return 'https://m-api.hyungi.net/api'; + } + return '/api'; + })(); const response = await fetch(`${apiUrl}/reports/daily-export`, { method: 'POST', headers: { diff --git a/frontend/static/js/core/permissions.js b/frontend/static/js/core/permissions.js index 4f7ce86..de74fc0 100644 --- a/frontend/static/js/core/permissions.js +++ b/frontend/static/js/core/permissions.js @@ -205,7 +205,19 @@ class PagePermissionManager { } try { - const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const apiUrl = window.API_BASE_URL || (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } + if (hostname === 'm.hyungi.net') { + return 'https://m-api.hyungi.net/api'; + } + return '/api'; + })(); const response = await fetch(`${apiUrl}/page-permissions/grant`, { method: 'POST', headers: { @@ -238,7 +250,19 @@ class PagePermissionManager { */ async getUserPagePermissions(userId) { try { - const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const apiUrl = window.API_BASE_URL || (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port) { + return `${protocol}//${hostname}:${port}/api`; + } + if (hostname === 'm.hyungi.net') { + return 'https://m-api.hyungi.net/api'; + } + return '/api'; + })(); const response = await fetch(`${apiUrl}/users/${userId}/page-permissions`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` diff --git a/restore_script.sh b/restore_script.sh new file mode 100755 index 0000000..d3bdfb9 --- /dev/null +++ b/restore_script.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# M 프로젝트 복구 스크립트 +# 사용법: ./restore_script.sh /path/to/backup/folder + +set -e + +if [ -z "$1" ]; then + echo "❌ 사용법: $0 <백업폴더경로>" + echo "예시: $0 /Users/hyungi/M-Project/backups/20251108_152538" + exit 1 +fi + +BACKUP_FOLDER="$1" + +if [ ! -d "$BACKUP_FOLDER" ]; then + echo "❌ 백업 폴더가 존재하지 않습니다: $BACKUP_FOLDER" + exit 1 +fi + +echo "🔄 M 프로젝트 복구 시작" +echo "📁 백업 폴더: $BACKUP_FOLDER" + +# 백업 정보 확인 +if [ -f "$BACKUP_FOLDER/backup_info.txt" ]; then + echo "📋 백업 정보:" + cat "$BACKUP_FOLDER/backup_info.txt" + echo "" +fi + +read -p "⚠️ 기존 데이터가 모두 삭제됩니다. 계속하시겠습니까? (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "❌ 복구가 취소되었습니다." + exit 1 +fi + +# 1. 서비스 중지 +echo "🛑 서비스 중지 중..." +cd /Users/hyungi/M-Project +docker-compose down + +# 2. 기존 볼륨 삭제 +echo "🗑️ 기존 볼륨 삭제 중..." +docker volume rm m-project_postgres_data m-project_uploads 2>/dev/null || true + +# 3. 데이터베이스 컨테이너만 시작 +echo "🚀 데이터베이스 컨테이너 시작 중..." +docker-compose up -d db + +# 데이터베이스 준비 대기 +echo "⏳ 데이터베이스 준비 대기 중..." +sleep 10 + +# 4. 데이터베이스 복구 +if [ -f "$BACKUP_FOLDER/database_backup.sql" ]; then + echo "📊 데이터베이스 복구 중..." + docker exec -i m-project-db psql -U mproject mproject < "$BACKUP_FOLDER/database_backup.sql" + echo "✅ 데이터베이스 복구 완료" +else + echo "❌ 데이터베이스 백업 파일을 찾을 수 없습니다." +fi + +# 5. Docker 볼륨 복구 +if [ -f "$BACKUP_FOLDER/postgres_volume.tar.gz" ]; then + echo "💾 PostgreSQL 볼륨 복구 중..." + docker run --rm -v m-project_postgres_data:/data -v "$BACKUP_FOLDER":/backup alpine tar xzf /backup/postgres_volume.tar.gz -C /data + echo "✅ PostgreSQL 볼륨 복구 완료" +fi + +if [ -f "$BACKUP_FOLDER/uploads_volume.tar.gz" ]; then + echo "📁 업로드 볼륨 복구 중..." + docker run --rm -v m-project_uploads:/data -v "$BACKUP_FOLDER":/backup alpine tar xzf /backup/uploads_volume.tar.gz -C /data + echo "✅ 업로드 볼륨 복구 완료" +fi + +# 6. 설정 파일 복구 +if [ -f "$BACKUP_FOLDER/docker-compose.yml" ]; then + echo "⚙️ 설정 파일 복구 중..." + cp "$BACKUP_FOLDER/docker-compose.yml" ./ + if [ -d "$BACKUP_FOLDER/nginx" ]; then + cp -r "$BACKUP_FOLDER/nginx/" ./ + fi + if [ -d "$BACKUP_FOLDER/migrations" ]; then + cp -r "$BACKUP_FOLDER/migrations/" ./backend/ + fi + echo "✅ 설정 파일 복구 완료" +fi + +# 7. 전체 서비스 시작 +echo "🚀 전체 서비스 시작 중..." +docker-compose up -d + +# 8. 서비스 상태 확인 +echo "⏳ 서비스 시작 대기 중..." +sleep 15 + +echo "🔍 서비스 상태 확인 중..." +docker-compose ps + +echo "🎉 복구 완료!" +echo "🌐 프론트엔드: http://localhost:16080" +echo "🔗 백엔드 API: http://localhost:16000" +echo "📊 데이터베이스: localhost:16432" + diff --git a/setup_auto_backup.sh b/setup_auto_backup.sh new file mode 100755 index 0000000..1a72aa2 --- /dev/null +++ b/setup_auto_backup.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# M 프로젝트 자동 백업 설정 스크립트 + +echo "🔧 M 프로젝트 자동 백업 설정" + +# 현재 crontab 백업 +crontab -l > /tmp/current_crontab 2>/dev/null || touch /tmp/current_crontab + +# M 프로젝트 백업 작업이 이미 있는지 확인 +if grep -q "M-Project backup" /tmp/current_crontab; then + echo "⚠️ M 프로젝트 백업 작업이 이미 설정되어 있습니다." + echo "기존 설정:" + grep "M-Project backup" /tmp/current_crontab + read -p "기존 설정을 덮어쓰시겠습니까? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "❌ 설정이 취소되었습니다." + exit 1 + fi + # 기존 M 프로젝트 백업 작업 제거 + grep -v "M-Project backup" /tmp/current_crontab > /tmp/new_crontab + mv /tmp/new_crontab /tmp/current_crontab +fi + +# 새로운 백업 작업 추가 +cat >> /tmp/current_crontab << 'EOF' + +# M-Project backup - 매일 오후 9시에 실행 +0 21 * * * /Users/hyungi/M-Project/backup_script.sh >> /Users/hyungi/M-Project/backup.log 2>&1 + +# M-Project backup - 매주 일요일 오후 9시 30분에 전체 백업 (추가 보안) +30 21 * * 0 /Users/hyungi/M-Project/backup_script.sh >> /Users/hyungi/M-Project/backup.log 2>&1 +EOF + +# 새로운 crontab 적용 +crontab /tmp/current_crontab + +# 정리 +rm /tmp/current_crontab + +echo "✅ 자동 백업 설정 완료!" +echo "" +echo "📅 백업 스케줄:" +echo " - 매일 오후 9시: 자동 백업" +echo " - 매주 일요일 오후 9시 30분: 추가 백업" +echo "" +echo "📋 현재 crontab 설정:" +crontab -l | grep -A2 -B2 "M-Project" +echo "" +echo "📄 백업 로그 위치: /Users/hyungi/M-Project/backup.log" +echo "📁 백업 저장 위치: /Users/hyungi/M-Project/backups/"