수정
Some checks failed
Deploy to Vercel / deploy (push) Has been cancelled
Deploy to Railway / deploy (push) Has been cancelled

This commit is contained in:
2025-08-21 20:43:34 +09:00
parent 2195cdf1b9
commit 49d16f0512
34 changed files with 2313 additions and 3368 deletions

View File

@@ -43,7 +43,8 @@
"Bash(ssh:*)",
"Bash(scp:*)",
"Bash(cat:*)",
"Bash(./deploy-manual.sh)"
"Bash(./deploy-manual.sh)",
"Bash(npm install)"
],
"deny": [],
"ask": [],

View File

@@ -1,298 +0,0 @@
# 시놀로지 NAS 대안 Git 서버 설치 방안
## 🚀 개요
시놀로지 Git Server 패키지가 작동하지 않을 때 사용할 수 있는 대안적인 Git 서버 설치 방법들을 제공합니다.
## 🐳 방법 1: Docker를 이용한 Gitea 설치 (권장)
### 1.1 장점
- 웹 기반 Git 관리 인터페이스
- GitHub와 유사한 사용자 경험
- 이슈 관리, 위키, 프로젝트 관리 기능
- 가벼움 (Go 언어 기반)
### 1.2 설치 과정
#### Docker 설치 확인
```bash
# DSM > 패키지 센터 > Docker 설치
# 또는 SSH에서 확인
docker --version
```
#### Gitea 컨테이너 실행
```bash
# SSH로 NAS 접속
ssh admin@your-nas-ip
# Gitea 데이터 디렉토리 생성
sudo mkdir -p /volume1/docker/gitea
# Gitea 컨테이너 실행
docker run -d \
--name gitea \
-p 3000:3000 \
-p 222:22 \
-v /volume1/docker/gitea:/data \
-e USER_UID=1000 \
-e USER_GID=1000 \
gitea/gitea:latest
```
#### 웹 설정
1. 브라우저에서 `http://your-nas-ip:3000` 접속
2. 초기 설정 완료:
- 데이터베이스: SQLite3 (기본)
- 관리자 계정 생성
- 저장소 루트 경로: `/data/git/repositories`
### 1.3 저장소 생성 및 연결
```bash
# 웹 인터페이스에서 새 저장소 'jaryo-file-manager' 생성
# 로컬에서 연결
git remote add gitea http://your-nas-ip:3000/username/jaryo-file-manager.git
git push gitea master
```
## 📦 방법 2: 순수 Git 서버 설치
### 2.1 수동 Git 설치
```bash
# SSH로 NAS 접속
ssh admin@your-nas-ip
# 패키지 관리자 업데이트
sudo apt update
# Git 설치
sudo apt install git git-daemon-run
# 버전 확인
git --version
```
### 2.2 Git 서비스 설정
```bash
# Git 사용자 생성
sudo adduser git
sudo su git
cd /home/git
# SSH 키 디렉토리 생성
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys
# Git 저장소 디렉토리 생성
mkdir /home/git/repositories
```
### 2.3 systemd 서비스 설정
```bash
# Git daemon 서비스 파일 생성
sudo tee /etc/systemd/system/git-daemon.service << EOF
[Unit]
Description=Git Daemon
After=network.target
[Service]
ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/home/git/repositories --export-all --verbose --enable=receive-pack
Restart=always
User=git
Group=git
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# 서비스 활성화
sudo systemctl enable git-daemon
sudo systemctl start git-daemon
sudo systemctl status git-daemon
```
### 2.4 저장소 생성
```bash
# git 사용자로 전환
sudo su git
cd /home/git/repositories
# bare 저장소 생성
git init --bare jaryo-file-manager.git
```
## 🌐 방법 3: GitLab CE Docker 설치
### 3.1 특징
- 기업급 Git 관리 플랫폼
- CI/CD 파이프라인 지원
- 이슈 추적, 위키, 프로젝트 관리
- 더 많은 리소스 필요
### 3.2 설치 과정
```bash
# GitLab 데이터 디렉토리 생성
sudo mkdir -p /volume1/docker/gitlab/{config,logs,data}
# GitLab 컨테이너 실행 (최소 4GB RAM 권장)
docker run -d \
--hostname your-nas-ip \
--name gitlab \
-p 8080:80 \
-p 8443:443 \
-p 8022:22 \
-v /volume1/docker/gitlab/config:/etc/gitlab \
-v /volume1/docker/gitlab/logs:/var/log/gitlab \
-v /volume1/docker/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
```
### 3.3 초기 설정
```bash
# 컨테이너 시작 대기 (2-3분)
docker logs -f gitlab
# 브라우저에서 http://your-nas-ip:8080 접속
# 초기 root 비밀번호 확인
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
```
## 🔧 방법 4: Forgejo (Gitea Fork) 설치
### 4.1 특징
- Gitea의 커뮤니티 중심 포크
- 더 빠른 개발 주기
- 오픈소스 중심
### 4.2 설치 과정
```bash
# Forgejo 데이터 디렉토리 생성
sudo mkdir -p /volume1/docker/forgejo
# Forgejo 컨테이너 실행
docker run -d \
--name forgejo \
-p 3000:3000 \
-p 222:22 \
-v /volume1/docker/forgejo:/data \
-e USER_UID=1000 \
-e USER_GID=1000 \
codeberg.org/forgejo/forgejo:latest
```
## 📱 방법 5: 간단한 HTTP Git 서버
### 5.1 Python 기반 간단 서버
```bash
# Python Git HTTP 서버 스크립트 생성
cat > /volume1/web/git-http-server.py << 'EOF'
#!/usr/bin/env python3
import os
import http.server
import socketserver
from subprocess import Popen, PIPE
class GitHTTPHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path.endswith('.git/info/refs'):
# Git 정보 요청 처리
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
repo_path = self.path.split('/')[1]
git_dir = f'/volume1/git/{repo_path}'
if os.path.exists(git_dir):
proc = Popen(['git', 'upload-pack', '--advertise-refs', git_dir],
stdout=PIPE, stderr=PIPE)
output, _ = proc.communicate()
self.wfile.write(output)
else:
self.wfile.write(b'Repository not found')
else:
super().do_GET()
PORT = 8000
with socketserver.TCPServer(("", PORT), GitHTTPHandler) as httpd:
print(f"Git HTTP Server running on port {PORT}")
httpd.serve_forever()
EOF
# 실행 권한 부여
chmod +x /volume1/web/git-http-server.py
# 서버 실행
python3 /volume1/web/git-http-server.py
```
## 🔀 방법별 비교표
| 방법 | 난이도 | 리소스 사용량 | 기능 | 웹 UI | 권장도 |
|------|--------|---------------|------|-------|--------|
| Gitea | 쉬움 | 낮음 | 풍부 | ✅ | ⭐⭐⭐⭐⭐ |
| 순수 Git | 보통 | 매우 낮음 | 기본 | ❌ | ⭐⭐⭐ |
| GitLab CE | 어려움 | 높음 | 매우 풍부 | ✅ | ⭐⭐⭐⭐ |
| Forgejo | 쉬움 | 낮음 | 풍부 | ✅ | ⭐⭐⭐⭐ |
| Python 서버 | 보통 | 낮음 | 제한적 | ❌ | ⭐⭐ |
## 🚀 빠른 시작 가이드 (Gitea 권장)
### 1단계: Docker 설치 확인
```bash
# DSM 패키지 센터에서 Docker 설치
```
### 2단계: Gitea 설치
```bash
ssh admin@your-nas-ip
sudo mkdir -p /volume1/docker/gitea
docker run -d --name gitea -p 3000:3000 -p 222:22 -v /volume1/docker/gitea:/data gitea/gitea:latest
```
### 3단계: 웹 설정
- `http://your-nas-ip:3000` 접속
- 초기 설정 완료
- 관리자 계정 생성
### 4단계: 저장소 생성 및 연결
```bash
# 웹에서 새 저장소 생성
# 로컬에서 연결
cd /c/Users/COMTREE/claude_code/jaryo
git remote add gitea http://your-nas-ip:3000/admin/jaryo-file-manager.git
git push gitea master
```
## 🛠️ 문제 해결
### Docker 관련 문제
```bash
# 컨테이너 상태 확인
docker ps -a
# 로그 확인
docker logs gitea
# 컨테이너 재시작
docker restart gitea
```
### 포트 충돌 문제
```bash
# 사용 중인 포트 확인
sudo netstat -tulpn | grep :3000
# 다른 포트 사용
docker run -d --name gitea -p 3001:3000 -p 223:22 -v /volume1/docker/gitea:/data gitea/gitea:latest
```
### 권한 문제
```bash
# 데이터 디렉토리 권한 수정
sudo chown -R 1000:1000 /volume1/docker/gitea
```
이 가이드를 통해 시놀로지 NAS Git Server 패키지 문제를 우회하여 안정적인 Git 서버를 구축할 수 있습니다.

View File

@@ -1,40 +0,0 @@
// 파일 API
module.exports = (req, res) => {
// CORS 헤더 설정
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
// 샘플 파일 데이터
const files = [
{
id: 1,
title: '샘플 문서',
description: '데모용 파일입니다',
category: '문서',
tags: ['샘플', '테스트'],
created_at: new Date().toISOString(),
file_url: '#'
},
{
id: 2,
title: '이미지 파일',
description: '예시 이미지',
category: '이미지',
tags: ['샘플'],
created_at: new Date().toISOString(),
file_url: '#'
}
];
res.status(200).json({
success: true,
data: files,
message: '파일 목록을 성공적으로 불러왔습니다.'
});
};

View File

@@ -1,4 +0,0 @@
const app = require('../server');
// Vercel 서버리스 함수로 export
module.exports = app;

View File

@@ -1,124 +0,0 @@
// Vercel Serverless 함수 핸들러
module.exports = function handler(req, res) {
// CORS 헤더 설정
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
const { url, method } = req;
// 헬스 체크
if (url === '/health' || url === '/api/health') {
res.json({
success: true,
message: 'Jaryo File Manager is running',
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'production',
path: url
});
return;
}
// 루트 경로
if (url === '/' || url === '/api') {
res.setHeader('Content-Type', 'text/html');
res.end(`
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jaryo File Manager</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
.container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; }
.status { background: #e8f5e8; border: 1px solid #4caf50; padding: 15px; border-radius: 5px; margin: 20px 0; }
.feature { background: #f0f8ff; border: 1px solid #2196f3; padding: 15px; border-radius: 5px; margin: 10px 0; }
.button { background: #4caf50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; text-decoration: none; display: inline-block; margin: 5px; }
.button:hover { background: #45a049; }
</style>
</head>
<body>
<div class="container">
<h1>🚀 Jaryo File Manager</h1>
<div class="status">
<h3>✅ 배포 성공!</h3>
<p>Vercel Serverless에서 성공적으로 실행 중입니다.</p>
<p><strong>시간:</strong> ${new Date().toLocaleString('ko-KR')}</p>
</div>
<div class="feature">
<h3>🔧 API 엔드포인트</h3>
<p><a href="/health" class="button">헬스 체크</a></p>
<p><a href="/api/files" class="button">파일 목록 API</a></p>
<p><a href="/api/files/public" class="button">공개 파일 API</a></p>
</div>
<div class="feature">
<h3>📱 페이지</h3>
<p><a href="/index.html" class="button">메인 페이지</a></p>
<p><a href="/admin/index.html" class="button">관리자 페이지</a></p>
</div>
</div>
</body>
</html>
`);
return;
}
// API 라우트들
if (url === '/api/files' || url === '/api/files/public') {
res.json({
success: true,
data: [
{
id: 1,
title: '샘플 문서',
description: '데모용 파일입니다',
category: '문서',
tags: ['샘플', '테스트'],
created_at: new Date().toISOString(),
file_url: '#'
},
{
id: 2,
title: '이미지 파일',
description: '예시 이미지',
category: '이미지',
tags: ['샘플'],
created_at: new Date().toISOString(),
file_url: '#'
}
],
message: url.includes('public') ? '공개 파일 목록' : '파일 목록 API (데모 모드)'
});
return;
}
if (url === '/api/categories') {
res.json({
success: true,
data: [
{ id: 1, name: '문서', description: '문서 파일' },
{ id: 2, name: '이미지', description: '이미지 파일' },
{ id: 3, name: '동영상', description: '동영상 파일' },
{ id: 4, name: '프레젠테이션', description: '프레젠테이션 파일' },
{ id: 5, name: '기타', description: '기타 파일' }
],
message: '카테고리 목록'
});
return;
}
// 404 핸들러
res.status(404).json({
success: false,
error: '요청한 리소스를 찾을 수 없습니다.',
path: url,
method: method
});
}

View File

@@ -1,21 +0,0 @@
// 테스트용 간단한 API
module.exports = (req, res) => {
// CORS 헤더 설정
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
// 간단한 응답
res.status(200).json({
success: true,
message: 'API가 정상 작동합니다!',
timestamp: new Date().toISOString(),
url: req.url,
method: req.method
});
};

View File

@@ -1,119 +0,0 @@
// Supabase configuration (오프라인 모드)
// ⚠️ 오프라인 모드로 강제 설정됨
const SUPABASE_CONFIG = {
url: 'https://kncudtzthmjegowbgnto.supabase.co',
anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtuY3VkdHp0aG1qZWdvd2JnbnRvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTU1Njc5OTksImV4cCI6MjA3MTE0Mzk5OX0.NlJN2vdgM96RvyVJE6ILQeDVUOU9X2F9vUn-jr_xlKc'
};
// Supabase 클라이언트 초기화 (강제 비활성화)
let supabase = null;
// 설정이 유효한지 확인
function isSupabaseConfigured() {
return false; // 강제로 false 반환
}
// Supabase 클라이언트 초기화 함수 (오프라인 모드 강제)
function initializeSupabase() {
console.log('⚠️ 오프라인 모드로 강제 설정되었습니다.');
return false;
}
// 인증 상태 변경 리스너 (오프라인 모드용 - 빈 함수)
function setupAuthListener(callback) {
// 오프라인 모드에서는 아무것도 하지 않음
return;
}
// 현재 사용자 가져오기 (오프라인 모드용 - null 반환)
async function getCurrentUser() {
return null;
}
// 로그인 (오프라인 모드용 - 빈 함수)
async function signIn(email, password) {
throw new Error('오프라인 모드에서는 로그인할 수 없습니다.');
}
// 회원가입 (오프라인 모드용 - 빈 함수)
async function signUp(email, password, metadata = {}) {
throw new Error('오프라인 모드에서는 회원가입할 수 없습니다.');
}
// 로그아웃 (오프라인 모드용 - 빈 함수)
async function signOut() {
throw new Error('오프라인 모드에서는 로그아웃할 수 없습니다.');
}
// 데이터베이스 헬퍼 함수들 (오프라인 모드용)
const SupabaseHelper = {
// 파일 목록 가져오기 (오프라인 모드용)
async getFiles(userId) {
console.log('🔍 SupabaseHelper.getFiles 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase 데이터베이스를 사용할 수 없습니다.');
},
// 파일 추가 (오프라인 모드용)
async addFile(fileData, userId) {
console.log('🔍 SupabaseHelper.addFile 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase 데이터베이스를 사용할 수 없습니다.');
},
// 파일 수정 (오프라인 모드용)
async updateFile(id, updates, userId) {
console.log('🔍 SupabaseHelper.updateFile 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase 데이터베이스를 사용할 수 없습니다.');
},
// 파일 삭제 (오프라인 모드용)
async deleteFile(id, userId) {
console.log('🔍 SupabaseHelper.deleteFile 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase 데이터베이스를 사용할 수 없습니다.');
},
// 실시간 구독 설정 (오프라인 모드용)
subscribeToFiles(userId, callback) {
console.log('🔍 SupabaseHelper.subscribeToFiles 호출됨 (오프라인 모드)');
return null;
},
// 파일 업로드 (오프라인 모드용)
async uploadFile(file, filePath) {
console.log('🔍 SupabaseHelper.uploadFile 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase Storage를 사용할 수 없습니다.');
},
// 파일 다운로드 URL 가져오기 (오프라인 모드용)
async getFileUrl(filePath) {
console.log('🔍 SupabaseHelper.getFileUrl 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase Storage를 사용할 수 없습니다.');
},
// 파일 삭제 (Storage) (오프라인 모드용)
async deleteStorageFile(filePath) {
console.log('🔍 SupabaseHelper.deleteStorageFile 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase Storage를 사용할 수 없습니다.');
},
// 첨부파일 정보 추가 (오프라인 모드용)
async addFileAttachment(fileId, attachmentData) {
console.log('🔍 SupabaseHelper.addFileAttachment 호출됨 (오프라인 모드)');
throw new Error('오프라인 모드에서는 Supabase 데이터베이스를 사용할 수 없습니다.');
},
// Storage 버킷 확인 및 생성 (오프라인 모드용)
async checkOrCreateBucket() {
console.log('🔍 SupabaseHelper.checkOrCreateBucket 호출됨 (오프라인 모드)');
return false;
}
};
// 전역으로 내보내기
window.SupabaseHelper = SupabaseHelper;
window.initializeSupabase = initializeSupabase;
window.isSupabaseConfigured = isSupabaseConfigured;
window.setupAuthListener = setupAuthListener;
window.getCurrentUser = getCurrentUser;
window.signIn = signIn;
window.signUp = signUp;
window.signOut = signOut;

View File

@@ -1,62 +0,0 @@
-- 완전 초기화 후 Storage 정책 재설정
-- 1단계: 모든 기존 Storage 정책 삭제
DROP POLICY IF EXISTS "Users can upload to own folder" ON storage.objects;
DROP POLICY IF EXISTS "Users can view own files" ON storage.objects;
DROP POLICY IF EXISTS "Users can update own files" ON storage.objects;
DROP POLICY IF EXISTS "Users can delete own files" ON storage.objects;
DROP POLICY IF EXISTS "Public upload for testing" ON storage.objects;
DROP POLICY IF EXISTS "Public read for testing" ON storage.objects;
-- 혹시 다른 이름으로 생성된 정책들도 삭제
DROP POLICY IF EXISTS "Enable insert for authenticated users only" ON storage.objects;
DROP POLICY IF EXISTS "Enable select for authenticated users only" ON storage.objects;
DROP POLICY IF EXISTS "Enable update for authenticated users only" ON storage.objects;
DROP POLICY IF EXISTS "Enable delete for authenticated users only" ON storage.objects;
-- 2단계: RLS 활성화 확인 (보통 이미 활성화되어 있음)
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
-- 3단계: 새 정책 생성
-- 업로드 정책
CREATE POLICY "Users can upload to own folder"
ON storage.objects
FOR INSERT
WITH CHECK (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 조회 정책
CREATE POLICY "Users can view own files"
ON storage.objects
FOR SELECT
USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 업데이트 정책
CREATE POLICY "Users can update own files"
ON storage.objects
FOR UPDATE
USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 삭제 정책
CREATE POLICY "Users can delete own files"
ON storage.objects
FOR DELETE
USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 4단계: 정책 생성 확인
SELECT
'Storage policies created successfully!' as message,
COUNT(*) as policy_count
FROM pg_policies
WHERE schemaname = 'storage' AND tablename = 'objects';

View File

@@ -1,150 +0,0 @@
# Supabase 설정 가이드
이 문서는 자료실 시스템을 Supabase와 연동하기 위한 설정 가이드입니다.
## 1. Supabase 프로젝트 생성
1. [Supabase](https://supabase.com)에 접속하여 계정을 생성합니다.
2. 새 프로젝트를 생성합니다.
3. 프로젝트 이름과 비밀번호를 설정합니다.
4. 리전은 `ap-northeast-1` (Asia Pacific - Tokyo)를 선택하는 것을 권장합니다.
## 2. 데이터베이스 스키마 설정
1. Supabase 대시보드에서 **SQL Editor**로 이동합니다.
2. `supabase-schema.sql` 파일의 내용을 복사하여 실행합니다.
3. 스키마가 성공적으로 생성되었는지 **Table Editor**에서 확인합니다.
### 생성되는 테이블
- `files`: 파일 메타데이터 저장
- `file_attachments`: 첨부파일 정보 저장
## 3. Storage 버킷 설정
1. Supabase 대시보드에서 **Storage**로 이동합니다.
2. **New bucket** 버튼을 클릭합니다.
3. 버킷 이름을 `files`로 설정합니다.
4. **Public bucket** 체크박스는 해제합니다 (보안상 권장).
5. 버킷을 생성합니다.
### Storage 정책 설정
버킷 생성 후 **Policies** 탭에서 다음 정책들을 추가합니다:
#### SELECT 정책 (파일 조회)
```sql
CREATE POLICY "Users can view their own files" ON storage.objects
FOR SELECT USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
```
#### INSERT 정책 (파일 업로드)
```sql
CREATE POLICY "Users can upload their own files" ON storage.objects
FOR INSERT WITH CHECK (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
```
#### DELETE 정책 (파일 삭제)
```sql
CREATE POLICY "Users can delete their own files" ON storage.objects
FOR DELETE USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
```
## 4. API 키 및 URL 설정
1. Supabase 대시보드에서 **Settings** > **API**로 이동합니다.
2. 다음 정보를 확인합니다:
- **Project URL**: `https://your-project-id.supabase.co`
- **Project API keys** > **anon public**: `eyJ...`
3. `supabase-config.js` 파일을 수정합니다:
```javascript
const SUPABASE_CONFIG = {
url: 'https://your-project-id.supabase.co', // 실제 Project URL로 교체
anonKey: 'eyJ...' // 실제 anon public key로 교체
};
```
## 5. 인증 설정 (선택사항)
### 이메일 인증 비활성화 (개발용)
개발 환경에서 빠른 테스트를 위해 이메일 인증을 비활성화할 수 있습니다:
1. **Authentication** > **Settings**로 이동
2. **Enable email confirmations** 체크박스 해제
3. **Save** 클릭
⚠️ **주의**: 프로덕션 환경에서는 이메일 인증을 활성화하는 것을 강력히 권장합니다.
### 이메일 템플릿 설정 (프로덕션용)
1. **Authentication** > **Email Templates**에서 이메일 템플릿을 커스터마이징할 수 있습니다.
2. 회사 브랜드에 맞게 이메일 디자인을 수정하세요.
## 6. 보안 설정
### Row Level Security (RLS)
스키마 실행 시 자동으로 설정되지만, 다음 사항을 확인하세요:
1. **Authentication** > **Policies**에서 정책이 올바르게 설정되었는지 확인
2. 각 테이블에 사용자별 접근 제한이 적용되어 있는지 확인
### 환경변수 보안
프로덕션 환경에서는 API 키를 환경변수로 관리하세요:
```javascript
const SUPABASE_CONFIG = {
url: process.env.SUPABASE_URL || 'YOUR_SUPABASE_PROJECT_URL',
anonKey: process.env.SUPABASE_ANON_KEY || 'YOUR_SUPABASE_ANON_KEY'
};
```
## 7. 테스트
설정 완료 후 다음 기능들을 테스트하세요:
1. **회원가입/로그인** - 새 계정 생성 및 로그인
2. **파일 추가** - 새 자료 추가 (첨부파일 포함)
3. **파일 수정** - 기존 자료 수정
4. **파일 삭제** - 자료 삭제 (첨부파일도 함께 삭제되는지 확인)
5. **파일 다운로드** - 첨부파일 다운로드
6. **실시간 동기화** - 다른 브라우저에서 같은 계정으로 로그인하여 실시간 동기화 확인
## 8. 문제 해결
### 연결 오류
- Supabase URL과 API 키가 올바른지 확인
- 브라우저 콘솔에서 오류 메시지 확인
- CORS 설정 확인 (대부분 자동으로 설정됨)
### 권한 오류
- RLS 정책이 올바르게 설정되었는지 확인
- 사용자가 올바르게 인증되었는지 확인
### 파일 업로드 오류
- Storage 버킷이 올바르게 생성되었는지 확인
- Storage 정책이 올바르게 설정되었는지 확인
- 파일 크기 제한 확인 (Supabase 기본값: 50MB)
## 9. 추가 개선사항
### 성능 최적화
- 대용량 파일 처리를 위한 chunk 업로드 구현
- 이미지 최적화 및 썸네일 생성
- CDN 연동 고려
### 기능 확장
- 파일 공유 기능
- 버전 관리
- 협업 기능
- 백업 및 복원 기능
---
설정 중 문제가 발생하면 [Supabase 공식 문서](https://supabase.com/docs)를 참고하거나 이슈를 등록해주세요.

View File

@@ -1,39 +0,0 @@
-- Storage 전용 정책 (Supabase Dashboard → Storage → Policies에서 실행)
-- 1. 파일 업로드 정책
CREATE POLICY "Users can upload to own folder"
ON storage.objects
FOR INSERT
WITH CHECK (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 2. 파일 조회 정책
CREATE POLICY "Users can view own files"
ON storage.objects
FOR SELECT
USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 3. 파일 업데이트 정책
CREATE POLICY "Users can update own files"
ON storage.objects
FOR UPDATE
USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 4. 파일 삭제 정책
CREATE POLICY "Users can delete own files"
ON storage.objects
FOR DELETE
USING (
bucket_id = 'files' AND
auth.uid()::text = (storage.foldername(name))[1]
);
-- 참고: storage.foldername(name)[1]은 'user_id/filename.txt'에서 'user_id' 부분을 추출합니다.

View File

@@ -1,309 +0,0 @@
// Supabase configuration
// ⚠️ 실제 사용 시에는 이 값들을 환경변수나 설정 파일로 관리하세요
const SUPABASE_CONFIG = {
// 실제 Supabase 프로젝트 URL로 교체하세요
url: 'https://kncudtzthmjegowbgnto.supabase.co',
// 실제 Supabase anon key로 교체하세요
anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtuY3VkdHp0aG1qZWdvd2JnbnRvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTU1Njc5OTksImV4cCI6MjA3MTE0Mzk5OX0.NlJN2vdgM96RvyVJE6ILQeDVUOU9X2F9vUn-jr_xlKc'
};
// Supabase 클라이언트 초기화
let supabase;
// 설정이 유효한지 확인
function isSupabaseConfigured() {
return SUPABASE_CONFIG.url !== 'YOUR_SUPABASE_PROJECT_URL' &&
SUPABASE_CONFIG.anonKey !== 'YOUR_SUPABASE_ANON_KEY';
}
// Supabase 클라이언트 초기화 함수
function initializeSupabase() {
if (!isSupabaseConfigured()) {
console.warn('⚠️ Supabase가 설정되지 않았습니다. localStorage를 사용합니다.');
return false;
}
try {
supabase = window.supabase.createClient(SUPABASE_CONFIG.url, SUPABASE_CONFIG.anonKey);
console.log('✅ Supabase 클라이언트가 초기화되었습니다.');
return true;
} catch (error) {
console.error('❌ Supabase 초기화 오류:', error);
return false;
}
}
// 인증 상태 변경 리스너
function setupAuthListener(callback) {
if (!supabase) return;
supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth state changed:', event, session);
if (callback) callback(event, session);
});
}
// 현재 사용자 가져오기
async function getCurrentUser() {
if (!supabase) return null;
try {
const { data: { user }, error } = await supabase.auth.getUser();
if (error) throw error;
return user;
} catch (error) {
console.error('사용자 정보 가져오기 오류:', error);
return null;
}
}
// 로그인
async function signIn(email, password) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});
if (error) throw error;
return data;
}
// 회원가입
async function signUp(email, password, metadata = {}) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: metadata
}
});
if (error) throw error;
return data;
}
// 로그아웃
async function signOut() {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { error } = await supabase.auth.signOut();
if (error) throw error;
}
// 데이터베이스 헬퍼 함수들
const SupabaseHelper = {
// 파일 목록 가져오기
async getFiles(userId) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
let query = supabase
.from('files')
.select(`
*,
file_attachments (*)
`);
// 공개 파일 요청이 아닌 경우에만 사용자 ID로 필터링
if (userId !== 'public') {
query = query.eq('user_id', userId);
}
const { data, error } = await query.order('created_at', { ascending: false });
if (error) throw error;
return data;
},
// 파일 추가
async addFile(fileData, userId) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
// 데이터베이스 스키마에 맞는 필드만 추출
const dbFileData = {
title: fileData.title,
description: fileData.description || '',
category: fileData.category,
tags: fileData.tags || [],
user_id: userId
// created_at, updated_at은 데이터베이스에서 자동 생성
};
const { data, error } = await supabase
.from('files')
.insert([dbFileData])
.select()
.single();
if (error) throw error;
return data;
},
// 파일 수정
async updateFile(id, updates, userId) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
// 데이터베이스 스키마에 맞는 필드만 추출
const dbUpdates = {
title: updates.title,
description: updates.description,
category: updates.category,
tags: updates.tags || []
// updated_at은 트리거에 의해 자동 업데이트됨
};
const { data, error } = await supabase
.from('files')
.update(dbUpdates)
.eq('id', id)
.eq('user_id', userId)
.select()
.single();
if (error) throw error;
return data;
},
// 파일 삭제
async deleteFile(id, userId) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { error } = await supabase
.from('files')
.delete()
.eq('id', id)
.eq('user_id', userId);
if (error) throw error;
},
// 실시간 구독 설정
subscribeToFiles(userId, callback) {
if (!supabase) return null;
return supabase
.channel('files')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'files',
filter: `user_id=eq.${userId}`
}, callback)
.subscribe();
},
// 파일 업로드 (Storage)
async uploadFile(file, filePath) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { data, error } = await supabase.storage
.from('files')
.upload(filePath, file);
if (error) throw error;
return data;
},
// 파일 다운로드 URL 가져오기
async getFileUrl(filePath) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
try {
// 먼저 파일이 존재하는지 확인
const { data: fileExists, error: checkError } = await supabase.storage
.from('files')
.list(filePath.substring(0, filePath.lastIndexOf('/')), {
search: filePath.substring(filePath.lastIndexOf('/') + 1)
});
if (checkError) {
throw new Error(`Storage 버킷 오류: ${checkError.message}`);
}
if (!fileExists || fileExists.length === 0) {
throw new Error('파일을 찾을 수 없습니다.');
}
// 파일이 존재하면 URL 생성
const { data } = supabase.storage
.from('files')
.getPublicUrl(filePath);
return data.publicUrl;
} catch (error) {
console.error('파일 URL 생성 오류:', error);
throw error;
}
},
// 파일 삭제 (Storage)
async deleteStorageFile(filePath) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { error } = await supabase.storage
.from('files')
.remove([filePath]);
if (error) throw error;
},
// 첨부파일 정보 추가
async addFileAttachment(fileId, attachmentData) {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
const { data, error } = await supabase
.from('file_attachments')
.insert([{
file_id: fileId,
...attachmentData
}])
.select()
.single();
if (error) throw error;
return data;
},
// Storage 버킷 확인 및 생성
async checkOrCreateBucket() {
if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.');
try {
// 버킷 목록 확인
const { data: buckets, error: listError } = await supabase.storage.listBuckets();
if (listError) {
console.error('버킷 목록 조회 오류:', listError);
return false;
}
// 'files' 버킷이 있는지 확인
const filesBucket = buckets.find(bucket => bucket.name === 'files');
if (filesBucket) {
console.log('✅ files 버킷이 존재합니다.');
return true;
} else {
console.warn('⚠️ files 버킷이 존재하지 않습니다.');
console.log('Supabase Dashboard에서 files 버킷을 생성해주세요.');
return false;
}
} catch (error) {
console.error('버킷 확인 오류:', error);
return false;
}
}
};
// 전역으로 내보내기
window.SupabaseHelper = SupabaseHelper;
window.initializeSupabase = initializeSupabase;
window.isSupabaseConfigured = isSupabaseConfigured;
window.setupAuthListener = setupAuthListener;
window.getCurrentUser = getCurrentUser;
window.signIn = signIn;
window.signUp = signUp;
window.signOut = signOut;

View File

@@ -1,128 +0,0 @@
-- Supabase 데이터베이스 스키마
-- 이 파일을 Supabase SQL 에디터에서 실행하세요
-- 1. files 테이블 생성
CREATE TABLE IF NOT EXISTS public.files (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL,
tags TEXT[] DEFAULT '{}',
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- 2. file_attachments 테이블 생성 (파일 첨부 정보)
CREATE TABLE IF NOT EXISTS public.file_attachments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
file_id UUID REFERENCES public.files(id) ON DELETE CASCADE NOT NULL,
original_name TEXT NOT NULL,
storage_path TEXT NOT NULL,
file_size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- 3. Row Level Security (RLS) 정책 활성화
ALTER TABLE public.files ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.file_attachments ENABLE ROW LEVEL SECURITY;
-- 4. files 테이블 RLS 정책
-- 사용자는 자신의 파일만 조회할 수 있음
CREATE POLICY "Users can view their own files" ON public.files
FOR SELECT USING (auth.uid() = user_id);
-- 사용자는 자신의 파일만 생성할 수 있음
CREATE POLICY "Users can create their own files" ON public.files
FOR INSERT WITH CHECK (auth.uid() = user_id);
-- 사용자는 자신의 파일만 수정할 수 있음
CREATE POLICY "Users can update their own files" ON public.files
FOR UPDATE USING (auth.uid() = user_id);
-- 사용자는 자신의 파일만 삭제할 수 있음
CREATE POLICY "Users can delete their own files" ON public.files
FOR DELETE USING (auth.uid() = user_id);
-- 5. file_attachments 테이블 RLS 정책
-- 사용자는 자신의 파일 첨부만 조회할 수 있음
CREATE POLICY "Users can view their own file attachments" ON public.file_attachments
FOR SELECT USING (
auth.uid() = (
SELECT user_id FROM public.files WHERE id = file_attachments.file_id
)
);
-- 사용자는 자신의 파일에만 첨부를 생성할 수 있음
CREATE POLICY "Users can create attachments for their own files" ON public.file_attachments
FOR INSERT WITH CHECK (
auth.uid() = (
SELECT user_id FROM public.files WHERE id = file_attachments.file_id
)
);
-- 사용자는 자신의 파일 첨부만 삭제할 수 있음
CREATE POLICY "Users can delete their own file attachments" ON public.file_attachments
FOR DELETE USING (
auth.uid() = (
SELECT user_id FROM public.files WHERE id = file_attachments.file_id
)
);
-- 6. 인덱스 생성 (성능 최적화)
CREATE INDEX IF NOT EXISTS idx_files_user_id ON public.files(user_id);
CREATE INDEX IF NOT EXISTS idx_files_created_at ON public.files(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_files_category ON public.files(category);
CREATE INDEX IF NOT EXISTS idx_files_tags ON public.files USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_file_attachments_file_id ON public.file_attachments(file_id);
-- 7. 업데이트 트리거 함수 (updated_at 자동 갱신)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- 8. updated_at 자동 갱신 트리거
CREATE TRIGGER update_files_updated_at
BEFORE UPDATE ON public.files
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 9. Storage 버킷 생성 (실제로는 Supabase Dashboard에서 생성)
-- 버킷 이름: 'files'
-- 공개 액세스: false (인증된 사용자만 접근)
--
-- Storage 정책은 Supabase Dashboard에서 다음과 같이 설정:
-- SELECT: 사용자는 자신의 파일만 조회 가능
-- INSERT: 사용자는 자신의 폴더에만 업로드 가능
-- UPDATE: 사용자는 자신의 파일만 수정 가능
-- DELETE: 사용자는 자신의 파일만 삭제 가능
-- 10. 유용한 뷰 생성 (파일과 첨부 정보 조인)
-- 주의: 뷰는 자동으로 기본 테이블의 RLS 정책을 상속받으므로 별도 정책 설정 불필요
CREATE OR REPLACE VIEW public.files_with_attachments AS
SELECT
f.*,
COALESCE(
JSON_AGG(
JSON_BUILD_OBJECT(
'id', fa.id,
'original_name', fa.original_name,
'storage_path', fa.storage_path,
'file_size', fa.file_size,
'mime_type', fa.mime_type,
'created_at', fa.created_at
)
) FILTER (WHERE fa.id IS NOT NULL),
'[]'::json
) AS attachments
FROM public.files f
LEFT JOIN public.file_attachments fa ON f.id = fa.file_id
GROUP BY f.id, f.title, f.description, f.category, f.tags, f.user_id, f.created_at, f.updated_at;
-- 설정 완료 메시지
SELECT 'Supabase 스키마 설정이 완료되었습니다!' as message;

View File

@@ -1,16 +0,0 @@
-- 임시 공개 접근 정책 (테스트용만 사용!)
-- 보안상 권장하지 않음 - 운영환경에서는 사용하지 마세요
-- 모든 사용자가 files 버킷에 업로드 가능 (임시)
CREATE POLICY "Public upload for testing"
ON storage.objects
FOR INSERT
WITH CHECK (bucket_id = 'files');
-- 모든 사용자가 files 버킷 파일 조회 가능 (임시)
CREATE POLICY "Public read for testing"
ON storage.objects
FOR SELECT
USING (bucket_id = 'files');
-- 주의: 이 정책들은 테스트 후 반드시 삭제하고 위의 사용자별 정책으로 교체하세요!

View File

@@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1755826306 connect.sid s%3Alkct8oX-9zlTMoD6Mu3BP0RwCz0CFR-X.Mad5GaxzMugYjbnKEOxUq9mnbJkkJY3O79f3q%2BaE%2BJ4

View File

@@ -1,141 +0,0 @@
#!/bin/bash
# 시놀로지 NAS Git 저장소 생성 스크립트 (개선 버전)
# 사용법: ./create-git-repo.sh [repo-name] [git-dir]
# 기본 설정
DEFAULT_GIT_DIR="/volume1/git"
DEFAULT_REPO_NAME="jaryo-file-manager"
# 매개변수 처리
REPO_NAME="${1:-$DEFAULT_REPO_NAME}"
GIT_DIR="${2:-$DEFAULT_GIT_DIR}"
REPO_PATH="$GIT_DIR/$REPO_NAME.git"
echo "=== 시놀로지 NAS Git 저장소 생성 ==="
echo "저장소 이름: $REPO_NAME"
echo "Git 디렉토리: $GIT_DIR"
echo "저장소 경로: $REPO_PATH"
echo "=========================================="
# 권한 확인
if [ "$EUID" -ne 0 ] && [ "$(whoami)" != "admin" ]; then
echo "⚠️ 경고: 관리자 권한이 필요할 수 있습니다."
echo "sudo 또는 admin 계정으로 실행하세요."
fi
# Git 설치 확인
if ! command -v git &> /dev/null; then
echo "❌ Git이 설치되지 않았습니다."
echo "패키지 센터에서 Git Server를 설치하거나 다음 명령어를 실행하세요:"
echo "sudo apt update && sudo apt install git"
exit 1
fi
# Git 디렉토리 확인 및 생성
echo "📁 Git 디렉토리 확인 중..."
if [ ! -d "$GIT_DIR" ]; then
echo "Git 디렉토리가 없습니다. 생성 중..."
mkdir -p "$GIT_DIR" || {
echo "❌ Git 디렉토리 생성 실패. 권한을 확인하세요."
exit 1
}
# 권한 설정
if command -v chown &> /dev/null; then
chown admin:users "$GIT_DIR" 2>/dev/null || echo "⚠️ chown 권한 부족"
fi
chmod 755 "$GIT_DIR" 2>/dev/null || echo "⚠️ chmod 권한 부족"
echo "✅ Git 디렉토리 생성 완료: $GIT_DIR"
else
echo "✅ Git 디렉토리 존재 확인: $GIT_DIR"
fi
# 기존 저장소 확인
if [ -d "$REPO_PATH" ]; then
echo "⚠️ 저장소가 이미 존재합니다: $REPO_PATH"
read -p "삭제 후 재생성하시겠습니까? (y/N): " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
rm -rf "$REPO_PATH"
echo "🗑️ 기존 저장소 삭제 완료"
else
echo "❌ 작업을 취소합니다."
exit 1
fi
fi
# 저장소 디렉토리 생성
echo "📂 저장소 디렉토리 생성 중..."
mkdir -p "$REPO_PATH" || {
echo "❌ 저장소 디렉토리 생성 실패"
exit 1
}
# Git 저장소 초기화
echo "🔧 Git 저장소 초기화 중..."
cd "$REPO_PATH" || exit 1
git init --bare || {
echo "❌ Git 저장소 초기화 실패"
exit 1
}
# 권한 설정
echo "🔐 권한 설정 중..."
if command -v chown &> /dev/null; then
chown -R admin:users "$REPO_PATH" 2>/dev/null || echo "⚠️ chown 권한 부족"
fi
chmod -R 755 "$REPO_PATH" 2>/dev/null || echo "⚠️ chmod 권한 부족"
# Git hooks 설정 (선택사항)
echo "🪝 Git hooks 설정 중..."
cat > "$REPO_PATH/hooks/post-receive" << 'EOF'
#!/bin/bash
# 자동 배포 hook (선택사항)
echo "푸시 완료: $(date)"
echo "저장소: $PWD"
EOF
chmod +x "$REPO_PATH/hooks/post-receive" 2>/dev/null
# 저장소 설명 파일 생성
echo "📄 저장소 설명 파일 생성 중..."
cat > "$REPO_PATH/description" << EOF
Jaryo File Manager - 시놀로지 NAS 자료실 파일 관리 시스템
EOF
# Git 서비스 확인 및 시작
echo "🔄 Git 서비스 상태 확인 중..."
NAS_IP=$(hostname -I | awk '{print $1}' | tr -d ' ')
# 다양한 방법으로 IP 확인
if [ -z "$NAS_IP" ]; then
NAS_IP=$(ip route get 1 | awk '{print $7; exit}' 2>/dev/null)
fi
if [ -z "$NAS_IP" ]; then
NAS_IP="your-nas-ip"
fi
echo "✅ Git 저장소 생성 완료!"
echo "=========================================="
echo "📋 저장소 정보:"
echo " - 이름: $REPO_NAME"
echo " - 경로: $REPO_PATH"
echo " - 설명: 자료실 파일 관리 시스템"
echo ""
echo "🌐 연결 URL:"
echo " SSH: ssh://admin@$NAS_IP$REPO_PATH"
echo " HTTP: http://$NAS_IP:3000/git/$REPO_NAME.git"
echo ""
echo "🔗 로컬에서 연결하는 방법:"
echo " git remote add nas ssh://admin@$NAS_IP$REPO_PATH"
echo " git push nas master"
echo ""
echo "📝 다음 단계:"
echo " 1. 로컬 프로젝트에서 원격 저장소 추가"
echo " 2. 첫 번째 push 실행"
echo " 3. Git 서비스 동작 확인"
echo ""
echo "🔧 Git 서비스 수동 시작 (필요시):"
echo " sudo systemctl start git-daemon"
echo " sudo git daemon --base-path=$GIT_DIR --export-all --reuseaddr &"
echo ""
echo "📖 자세한 설정은 synology-git-diagnostic.md 파일을 참조하세요."

View File

@@ -1,68 +0,0 @@
const DatabaseHelper = require('./database/db-helper');
const fs = require('fs');
const path = require('path');
async function debugFiles() {
const db = new DatabaseHelper();
try {
await db.connect();
console.log('\n📋 데이터베이스의 모든 파일:');
const files = await db.getAllFiles();
files.forEach((file, index) => {
console.log(`\n${index + 1}. ${file.title} (ID: ${file.id})`);
console.log(` 카테고리: ${file.category}`);
console.log(` 첨부파일: ${file.files?.length || 0}`);
if (file.files && file.files.length > 0) {
file.files.forEach((attachment, idx) => {
console.log(` ${idx + 1}) ${attachment.original_name}`);
console.log(` - ID: ${attachment.id}`);
console.log(` - 경로: ${attachment.file_path}`);
console.log(` - 파일명: ${attachment.file_name}`);
console.log(` - 크기: ${attachment.file_size}`);
// 실제 파일 존재 확인
const fullPath = path.join(__dirname, attachment.file_path);
const exists = fs.existsSync(fullPath);
console.log(` - 실제 파일 존재: ${exists ? '✅' : '❌'} (${fullPath})`);
if (!exists) {
// 다른 경로들 시도
const paths = [
path.join(__dirname, 'uploads', attachment.file_name),
path.join(__dirname, 'uploads', attachment.original_name),
attachment.file_path,
];
console.log(` - 시도할 경로들:`);
paths.forEach(p => {
const pathExists = fs.existsSync(p);
console.log(` ${pathExists ? '✅' : '❌'} ${p}`);
});
}
});
}
});
console.log('\n📁 uploads 폴더의 실제 파일들:');
const uploadsDir = path.join(__dirname, 'uploads');
if (fs.existsSync(uploadsDir)) {
const actualFiles = fs.readdirSync(uploadsDir);
actualFiles.forEach(file => {
const filePath = path.join(uploadsDir, file);
const stats = fs.statSync(filePath);
console.log(` - ${file} (크기: ${stats.size})`);
});
}
} catch (error) {
console.error('❌ 오류:', error.message);
} finally {
await db.close();
}
}
debugFiles();

View File

@@ -1,46 +0,0 @@
#!/bin/bash
# 수동 배포 스크립트 - 각 단계를 개별 실행
NAS_IP="119.64.1.86"
NAS_USER="vibsin9322"
DEPLOY_DIR="/volume1/web/jaryo"
GITEA_URL="http://119.64.1.86:3000/vibsin9322/jaryo.git"
echo "=========================================="
echo "🔧 수동 배포 가이드"
echo "=========================================="
echo "다음 명령들을 하나씩 실행하세요:"
echo ""
echo "1⃣ SSH 연결 테스트:"
echo "ssh -p 2222 $NAS_USER@$NAS_IP"
echo ""
echo "2⃣ 기존 배포 백업 (있는 경우):"
echo "ssh -p 2222 $NAS_USER@$NAS_IP 'sudo cp -r $DEPLOY_DIR ${DEPLOY_DIR}_backup_\$(date +%Y%m%d_%H%M%S) 2>/dev/null || true'"
echo ""
echo "3⃣ 배포 디렉토리 준비:"
echo "ssh -p 2222 $NAS_USER@$NAS_IP 'sudo rm -rf $DEPLOY_DIR && sudo mkdir -p $DEPLOY_DIR && sudo chown $NAS_USER:users $DEPLOY_DIR'"
echo ""
echo "4⃣ Git 클론:"
echo "ssh -p 2222 $NAS_USER@$NAS_IP 'cd $DEPLOY_DIR && git clone $GITEA_URL .'"
echo ""
echo "5⃣ 의존성 설치:"
echo "ssh -p 2222 $NAS_USER@$NAS_IP 'cd $DEPLOY_DIR && npm install'"
echo ""
echo "6⃣ 데이터베이스 처리:"
echo "ssh -p 2222 $NAS_USER@$NAS_IP 'cd $DEPLOY_DIR && if [ -f data/database.db ]; then echo \"기존 DB 유지\"; else npm run init-db; fi'"
echo ""
echo "7⃣ 서비스 시작:"
echo "ssh -p 2222 $NAS_USER@$NAS_IP 'cd $DEPLOY_DIR && PORT=3005 nohup node server.js > logs/app.log 2>&1 & echo \$! > jaryo.pid'"
echo ""
echo "8⃣ 서비스 확인:"
echo "curl http://$NAS_IP:3005"
echo ""
echo "=========================================="

View File

@@ -1,234 +0,0 @@
const http = require("http");
const fs = require("fs");
const path = require("path");
const url = require("url");
const PORT = 3005;
const DATA_FILE = path.join(__dirname, 'data.json');
const UPLOAD_DIR = path.join(__dirname, 'uploads');
// 데이터 파일 초기화
function initializeData() {
const defaultData = {
users: [
{
id: '1',
email: 'admin@jaryo.com',
password: 'admin123',
name: '관리자',
role: 'admin'
}
],
files: [
{
id: '1',
title: '샘플 문서',
description: '자료실 테스트용 샘플 파일입니다.',
category: '문서',
tags: ['샘플', '테스트'],
user_id: '1',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
attachments: []
}
],
categories: ['문서', '이미지', '동영상', '프레젠테이션', '기타']
};
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, JSON.stringify(defaultData, null, 2));
}
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
}
// 데이터 읽기/쓰기
function readData() {
try {
const data = fs.readFileSync(DATA_FILE, 'utf8');
return JSON.parse(data);
} catch (error) {
console.error('데이터 읽기 오류:', error);
return { users: [], files: [], categories: [] };
}
}
function writeData(data) {
try {
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
return true;
} catch (error) {
console.error('데이터 쓰기 오류:', error);
return false;
}
}
// MIME 타입
const mimeTypes = {
".html": "text/html; charset=utf-8",
".css": "text/css",
".js": "application/javascript",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg"
};
// API 요청 처리
async function handleApiRequest(req, res, pathname, query) {
const data = readData();
if (pathname === "/api/files/public" && req.method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: true,
data: data.files,
total: data.files.length
}));
return;
}
if (pathname === "/api/files" && req.method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: true,
data: data.files,
total: data.files.length
}));
return;
}
if (pathname === "/api/auth/login" && req.method === "POST") {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
const { email, password } = JSON.parse(body);
const user = data.users.find(u => u.email === email && u.password === password);
if (user) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: true,
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
}
}));
} else {
res.writeHead(401, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: false,
error: '이메일 또는 비밀번호가 올바르지 않습니다.'
}));
}
} catch (error) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: false,
error: '잘못된 요청입니다.'
}));
}
});
return;
}
// 기본 응답
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: true,
message: "자료실 API 서버 실행 중",
timestamp: new Date().toISOString(),
path: pathname
}));
}
// 정적 파일 서빙
async function serveStaticFile(req, res, pathname) {
const filePath = path.join(__dirname, pathname);
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
res.end(`
<html>
<head><title>404 Not Found</title></head>
<body>
<h1>404 - 파일을 찾을 수 없습니다</h1>
<p>요청한 파일: ${pathname}</p>
<p><a href="/">홈으로 돌아가기</a></p>
</body>
</html>
`);
return;
}
const ext = path.extname(filePath);
const contentType = mimeTypes[ext] || "text/plain";
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
}
// HTTP 서버
const server = http.createServer(async (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
if (req.method === "OPTIONS") {
res.writeHead(200);
res.end();
return;
}
const parsedUrl = url.parse(req.url, true);
let pathname = parsedUrl.pathname;
const query = parsedUrl.query;
console.log(`📨 ${req.method} ${pathname}`);
if (pathname.startsWith("/api/")) {
try {
await handleApiRequest(req, res, pathname, query);
} catch (error) {
console.error('API 처리 오류:', error);
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({
success: false,
error: '서버 내부 오류가 발생했습니다.'
}));
}
return;
}
if (pathname === "/" || pathname === "/index.html") {
pathname = "/index.html";
} else if (pathname === "/admin" || pathname === "/admin/") {
pathname = "/admin/index.html";
}
await serveStaticFile(req, res, pathname);
});
server.listen(PORT, () => {
console.log(`🚀 향상된 자료실 서버가 포트 ${PORT}에서 실행 중입니다`);
console.log(`📍 접속 URL: http://119.64.1.86:${PORT}`);
console.log(`🔧 관리자 URL: http://119.64.1.86:${PORT}/admin`);
console.log(`⏰ 시작 시간: ${new Date().toLocaleString("ko-KR")}`);
initializeData();
console.log(`✅ 데이터 파일 초기화 완료`);
});
process.on("SIGINT", () => {
console.log("\n🛑 서버를 종료합니다...");
server.close(() => {
console.log("✅ 서버가 정상적으로 종료되었습니다");
process.exit(0);
});
});

View File

@@ -1,328 +0,0 @@
# 시놀로지 NAS 수동 배포 가이드
## 🚀 시놀로지 NAS에서 자료실 서비스 배포하기
### 사전 준비사항 ✅
1. **DSM 패키지 설치 확인**
- Node.js v16+ (패키지 센터에서 설치)
- Git Server (패키지 센터에서 설치)
- SSH 서비스 활성화 (제어판 > 터미널 및 SNMP)
2. **방화벽 설정**
- SSH 포트: 2222 허용
- 서비스 포트: 3005 허용
- Gitea 포트: 3000 허용
### 1단계: SSH 접속 🔗
```bash
# Windows에서 NAS 접속
ssh -p 2222 admin@119.64.1.86
# 접속 후 관리자 권한 확인
sudo whoami
```
### 2단계: 배포 디렉토리 준비 📁
```bash
# 웹 디렉토리로 이동
cd /volume1/web
# 기존 jaryo 폴더가 있다면 백업
if [ -d "jaryo" ]; then
sudo mv jaryo jaryo_backup_$(date +%Y%m%d_%H%M%S)
fi
# 새 디렉토리 생성
sudo mkdir -p jaryo
sudo chown admin:users jaryo
cd jaryo
```
### 3단계: 소스 코드 클론 📥
```bash
# Gitea에서 소스 코드 클론
git clone http://119.64.1.86:3000/vibsin9322/jaryo.git .
# 클론 성공 확인
ls -la
```
### 4단계: 의존성 설치 📦
```bash
# 기존 node_modules 제거 (있다면)
rm -rf node_modules package-lock.json
# npm 의존성 설치
npm install
# 설치 확인
ls -la node_modules
```
### 5단계: 데이터베이스 초기화 🗄️
```bash
# SQLite 데이터베이스 초기화
npm run init-db
# 데이터베이스 파일 확인
ls -la *.db
```
### 6단계: 서비스 시작 스크립트 생성 📝
```bash
# 시작 스크립트 생성
cat > start-jaryo.sh << 'EOF'
#!/bin/bash
# 자료실 서비스 시작 스크립트
PROJECT_DIR="/volume1/web/jaryo"
SERVICE_PORT="3005"
PID_FILE="$PROJECT_DIR/jaryo.pid"
LOG_FILE="$PROJECT_DIR/jaryo.log"
# 로그 디렉토리 생성
mkdir -p "$PROJECT_DIR/logs"
# 기존 프로세스 확인 및 종료
if [ -f "$PID_FILE" ]; then
OLD_PID=$(cat "$PID_FILE")
if ps -p "$OLD_PID" > /dev/null; then
echo "기존 서비스 종료 중... (PID: $OLD_PID)"
kill "$OLD_PID"
sleep 2
fi
rm -f "$PID_FILE"
fi
# 포트 사용 확인
if netstat -tulpn | grep ":$SERVICE_PORT " > /dev/null; then
echo "⚠️ 포트 $SERVICE_PORT가 이미 사용 중입니다."
echo "사용 중인 프로세스:"
netstat -tulpn | grep ":$SERVICE_PORT "
exit 1
fi
# 서비스 시작
echo "🚀 자료실 서비스 시작 중..."
cd "$PROJECT_DIR"
PORT="$SERVICE_PORT" nohup node server.js > "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
sleep 2
# 시작 확인
if ps -p $(cat "$PID_FILE") > /dev/null; then
echo "✅ 자료실 서비스 시작 완료!"
echo "📍 접속 URL: http://119.64.1.86:$SERVICE_PORT"
echo "📋 PID: $(cat "$PID_FILE")"
echo "📄 로그: $LOG_FILE"
else
echo "❌ 서비스 시작 실패"
echo "로그 확인:"
tail -20 "$LOG_FILE"
exit 1
fi
EOF
# 실행 권한 부여
chmod +x start-jaryo.sh
```
### 7단계: 중지 스크립트 생성 🛑
```bash
# 중지 스크립트 생성
cat > stop-jaryo.sh << 'EOF'
#!/bin/bash
# 자료실 서비스 중지 스크립트
PROJECT_DIR="/volume1/web/jaryo"
PID_FILE="$PROJECT_DIR/jaryo.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null; then
echo "🛑 자료실 서비스 중지 중... (PID: $PID)"
kill "$PID"
sleep 2
# 강제 종료 확인
if ps -p "$PID" > /dev/null; then
echo "⚡ 강제 종료 중..."
kill -9 "$PID"
fi
rm -f "$PID_FILE"
echo "✅ 자료실 서비스 중지 완료"
else
echo "⚠️ 프로세스가 이미 종료됨"
rm -f "$PID_FILE"
fi
else
echo "⚠️ PID 파일이 없습니다. 수동으로 프로세스를 확인하세요."
echo "실행 중인 Node.js 프로세스:"
ps aux | grep 'node.*server.js' | grep -v grep
fi
EOF
# 실행 권한 부여
chmod +x stop-jaryo.sh
```
### 8단계: 서비스 시작 🎬
```bash
# 서비스 시작
./start-jaryo.sh
# 로그 확인 (별도 터미널에서)
tail -f jaryo.log
```
### 9단계: 접속 테스트 🧪
**브라우저에서 접속:**
- **메인 페이지**: `http://119.64.1.86:3005`
- **관리자 페이지**: `http://119.64.1.86:3005/admin`
**명령줄에서 테스트:**
```bash
# 서비스 상태 확인
curl -s http://119.64.1.86:3005
# 프로세스 확인
ps aux | grep 'node.*server.js' | grep -v grep
# 포트 확인
netstat -tulpn | grep :3005
```
## 🔧 서비스 관리
### 일상적인 관리 명령어
```bash
# 서비스 시작
/volume1/web/jaryo/start-jaryo.sh
# 서비스 중지
/volume1/web/jaryo/stop-jaryo.sh
# 서비스 재시작
/volume1/web/jaryo/stop-jaryo.sh && /volume1/web/jaryo/start-jaryo.sh
# 로그 확인
tail -f /volume1/web/jaryo/jaryo.log
# 실시간 로그 보기
ssh -p 2222 admin@119.64.1.86 'tail -f /volume1/web/jaryo/jaryo.log'
```
### 상태 모니터링
```bash
# 서비스 상태 스크립트
cat > status-jaryo.sh << 'EOF'
#!/bin/bash
PROJECT_DIR="/volume1/web/jaryo"
PID_FILE="$PROJECT_DIR/jaryo.pid"
LOG_FILE="$PROJECT_DIR/jaryo.log"
echo "=== 자료실 서비스 상태 ==="
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null; then
echo "✅ 서비스 실행 중 (PID: $PID)"
echo "📊 메모리 사용량:"
ps -o pid,ppid,cmd,%mem,%cpu -p "$PID"
echo ""
echo "🌐 포트 상태:"
netstat -tulpn | grep :3005
else
echo "❌ 서비스 중지됨 (PID 파일 존재하지만 프로세스 없음)"
fi
else
echo "❌ 서비스 중지됨 (PID 파일 없음)"
fi
echo ""
echo "📄 최근 로그 (최근 5줄):"
if [ -f "$LOG_FILE" ]; then
tail -5 "$LOG_FILE"
else
echo "로그 파일이 없습니다."
fi
EOF
chmod +x status-jaryo.sh
```
## 🔄 자동 시작 설정 (선택사항)
### DSM 작업 스케줄러 사용
1. **DSM 로그인****제어판****작업 스케줄러**
2. **생성****예약된 작업****사용자 정의 스크립트**
3. 설정:
- **작업 이름**: Jaryo 자료실 자동 시작
- **사용자**: root
- **스케줄**: 시스템 부팅 시
- **스크립트**: `/volume1/web/jaryo/start-jaryo.sh`
## 🚨 문제 해결
### 일반적인 문제들
**1. 포트 충돌**
```bash
# 포트 사용 확인
netstat -tulpn | grep :3005
# 다른 포트 사용시 (예: 3006)
PORT=3006 node server.js
```
**2. 권한 문제**
```bash
# 디렉토리 권한 수정
sudo chown -R admin:users /volume1/web/jaryo
chmod -R 755 /volume1/web/jaryo
```
**3. Node.js 버전 문제**
```bash
# Node.js 버전 확인
node --version
# 최소 요구 버전: v16.0.0
```
**4. 데이터베이스 문제**
```bash
# 데이터베이스 재초기화
rm -f jaryo.db
npm run init-db
```
## 📱 최종 확인
배포 완료 후 다음 URL들이 정상 작동하는지 확인:
-**메인 페이지**: http://119.64.1.86:3005
-**관리자 페이지**: http://119.64.1.86:3005/admin
-**API 상태**: http://119.64.1.86:3005/api/files
-**Gitea 저장소**: http://119.64.1.86:3000/vibsin9322/jaryo
## 🎉 배포 완료!
시놀로지 NAS에서 자료실 서비스가 성공적으로 실행되고 있습니다.
서비스 관리는 위의 스크립트들을 사용하여 쉽게 할 수 있습니다.

View File

@@ -1,257 +0,0 @@
# 시놀로지 NAS Git 서버 연결 테스트 가이드
## 🧪 연결 테스트 단계별 가이드
### 1단계: 기본 연결 테스트
#### 1.1 SSH 연결 확인
```bash
# NAS SSH 연결 테스트
ssh admin@your-nas-ip
# 성공시 NAS 터미널에 접속됨
# 실패시 확인사항:
# - SSH 서비스 활성화 여부
# - 방화벽 설정
# - IP 주소 정확성
```
#### 1.2 Git 설치 확인
```bash
# NAS에서 Git 명령어 확인
which git
git --version
# Git 서비스 상태 확인
sudo systemctl status git-daemon
ps aux | grep git
```
### 2단계: 저장소 생성 및 설정
#### 2.1 자동 스크립트 실행
```bash
# 로컬에서 NAS로 스크립트 복사
scp create-git-repo.sh admin@your-nas-ip:/tmp/
# NAS에서 스크립트 실행
ssh admin@your-nas-ip
cd /tmp
chmod +x create-git-repo.sh
./create-git-repo.sh jaryo-file-manager
```
#### 2.2 수동 저장소 생성 (스크립트 실패시)
```bash
# NAS에서 직접 실행
ssh admin@your-nas-ip
# Git 디렉토리 생성
sudo mkdir -p /volume1/git
sudo chown admin:users /volume1/git
cd /volume1/git
# Bare 저장소 생성
mkdir jaryo-file-manager.git
cd jaryo-file-manager.git
git init --bare
sudo chown -R admin:users .
```
### 3단계: 로컬에서 연결 테스트
#### 3.1 기존 프로젝트에 원격 저장소 추가
```bash
# 현재 jaryo 프로젝트 디렉토리에서
cd /c/Users/COMTREE/claude_code/jaryo
# NAS Git 원격 저장소 추가
git remote add nas ssh://admin@your-nas-ip/volume1/git/jaryo-file-manager.git
# 원격 저장소 확인
git remote -v
```
#### 3.2 첫 번째 Push 테스트
```bash
# 모든 변경사항 커밋 (필요시)
git add .
git commit -m "Initial commit for NAS deployment"
# NAS로 푸시
git push nas master
# 성공시 출력 예시:
# Enumerating objects: X, done.
# Counting objects: 100% (X/X), done.
# Delta compression using up to Y threads
# Compressing objects: 100% (X/X), done.
# Writing objects: 100% (X/X), X.XX KiB | X.XX MiB/s, done.
# Total X (delta X), reused X (delta X), pack-reused 0
# To ssh://admin@your-nas-ip/volume1/git/jaryo-file-manager.git
# * [new branch] master -> master
```
### 4단계: 클론 테스트
#### 4.1 다른 디렉토리에서 클론 테스트
```bash
# 테스트용 디렉토리 생성
mkdir /tmp/git-test
cd /tmp/git-test
# NAS에서 클론
git clone ssh://admin@your-nas-ip/volume1/git/jaryo-file-manager.git
# 성공시 프로젝트 파일들이 다운로드됨
cd jaryo-file-manager
ls -la
```
#### 4.2 HTTP 클론 테스트 (Git HTTP 서버 실행시)
```bash
# Git HTTP 서버가 실행 중인 경우
git clone http://your-nas-ip:3000/jaryo-file-manager.git
```
### 5단계: 웹 인터페이스 테스트
#### 5.1 GitWeb 접속 테스트
- 브라우저에서 `http://your-nas-ip/git` 접속
- 또는 `http://your-nas-ip:3000` 접속
- 저장소 목록에서 `jaryo-file-manager` 확인
#### 5.2 Git HTTP 서버 상태 확인
```bash
# NAS에서 Git HTTP 서버 실행 확인
sudo netstat -tulpn | grep :3000
ps aux | grep git-daemon
```
## 🚨 문제 해결
### 연결 실패 시 체크리스트
#### ❌ "Connection refused" 오류
```bash
# 1. SSH 서비스 확인
ssh -v admin@your-nas-ip
# 2. 포트 확인 (기본: 22)
ssh -p 22 admin@your-nas-ip
# 3. 방화벽 확인
# DSM > 제어판 > 보안 > 방화벽
```
#### ❌ "Permission denied" 오류
```bash
# 1. 사용자 권한 확인
# DSM > 제어판 > 사용자 및 그룹 > admin > 애플리케이션
# 2. SSH 키 설정 (선택사항)
ssh-keygen -t rsa
ssh-copy-id admin@your-nas-ip
```
#### ❌ "Repository not found" 오류
```bash
# 1. 저장소 경로 확인
ssh admin@your-nas-ip
ls -la /volume1/git/
ls -la /volume1/git/jaryo-file-manager.git/
# 2. 권한 확인
sudo chown -R admin:users /volume1/git/jaryo-file-manager.git
chmod -R 755 /volume1/git/jaryo-file-manager.git
```
#### ❌ Git 명령어 없음
```bash
# Git 설치 확인
which git
# 패키지 센터에서 Git Server 설치
# 또는 수동 설치:
sudo apt update
sudo apt install git
```
### 네트워크 설정 문제
#### 내부 네트워크 접속 실패
```bash
# IP 주소 확인
ping your-nas-ip
nslookup your-nas-ip
# 포트 스캔
nmap -p 22,3000 your-nas-ip
```
#### 외부 네트워크 접속 설정
```bash
# 라우터 포트 포워딩 설정
# 22 (SSH) -> NAS_IP:22
# 3000 (Git HTTP) -> NAS_IP:3000
# 동적 DNS 설정 (선택사항)
# your-domain.dyndns.org -> your-public-ip
```
## 📊 연결 성공 확인
### ✅ 성공 지표들
1. **SSH 연결**: `ssh admin@your-nas-ip` 성공
2. **저장소 존재**: `/volume1/git/jaryo-file-manager.git` 확인
3. **Push 성공**: `git push nas master` 완료
4. **Clone 성공**: 다른 위치에서 클론 가능
5. **웹 접속**: 브라우저에서 Git 저장소 확인
### 📈 성능 테스트
```bash
# 대용량 파일 Push 테스트
dd if=/dev/zero of=test-large-file.bin bs=1M count=10
git add test-large-file.bin
git commit -m "Large file test"
time git push nas master
# 클론 속도 테스트
time git clone ssh://admin@your-nas-ip/volume1/git/jaryo-file-manager.git test-clone
```
## 🔧 고급 설정
### Git Hooks 활용
```bash
# NAS에서 post-receive hook 설정
ssh admin@your-nas-ip
cd /volume1/git/jaryo-file-manager.git/hooks
# 자동 배포 hook
cat > post-receive << 'EOF'
#!/bin/bash
echo "코드 푸시 완료: $(date)"
# 자동 배포 로직 추가 가능
# cd /volume1/web/jaryo && git pull
EOF
chmod +x post-receive
```
### 백업 설정
```bash
# 저장소 백업 스크립트
#!/bin/bash
BACKUP_DIR="/volume1/backup/git"
REPO_DIR="/volume1/git"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/git-repos-$DATE.tar.gz" -C "$REPO_DIR" .
echo "백업 완료: $BACKUP_DIR/git-repos-$DATE.tar.gz"
```
이 가이드를 따라하면 시놀로지 NAS Git 서버와의 연결을 성공적으로 테스트하고 설정할 수 있습니다.

2499
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,26 @@
{
"name": "jaryo-file-manager",
"version": "2.0.0",
"description": "자료실 파일 관리 시스템 - Vercel Serverless",
"description": "자료실 파일 관리 시스템",
"main": "server.js",
"scripts": {
"dev": "vercel dev",
"build": "echo 'Build complete'",
"start": "vercel dev"
"start": "node server.js",
"dev": "node server.js",
"build": "echo 'Build complete'"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"multer": "^1.4.5-lts.1",
"bcrypt": "^5.1.1",
"express-session": "^1.17.3",
"uuid": "^9.0.1",
"sqlite3": "^5.1.6"
},
"dependencies": {},
"devDependencies": {
"vercel": "^32.0.0"
},
"keywords": ["file-manager", "vercel", "serverless", "admin"],
"keywords": ["file-manager", "admin"],
"author": "Claude Code",
"license": "MIT",
"engines": {

View File

@@ -1,25 +0,0 @@
module.exports = {
apps: [{
name: 'jaryo-file-manager',
script: 'server.js',
cwd: '/volume1/web/jaryo',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production',
PORT: 3005
},
env_production: {
NODE_ENV: 'production',
PORT: 3005
},
log_file: '/volume1/web/jaryo/logs/combined.log',
out_file: '/volume1/web/jaryo/logs/out.log',
error_file: '/volume1/web/jaryo/logs/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
time: true
}]
};

View File

@@ -1,51 +0,0 @@
#!/bin/bash
# PM2를 사용한 시놀로지 NAS 서비스 시작 스크립트
# 사용법: ./pm2-start.sh
PROJECT_DIR="/volume1/web/jaryo"
LOG_DIR="/volume1/web/jaryo/logs"
echo "=== PM2로 Jaryo File Manager 서비스 시작 ==="
# 로그 디렉토리 생성
mkdir -p "$LOG_DIR"
# 프로젝트 디렉토리로 이동
cd "$PROJECT_DIR" || {
echo "오류: 프로젝트 디렉토리를 찾을 수 없습니다: $PROJECT_DIR"
exit 1
}
# PM2 설치 확인 및 설치
if ! command -v pm2 &> /dev/null; then
echo "PM2 설치 중..."
npm install -g pm2
fi
# 의존성 설치 확인
if [ ! -d "node_modules" ]; then
echo "의존성 설치 중..."
npm install
fi
# 데이터베이스 초기화
echo "데이터베이스 초기화 중..."
node scripts/init-database.js
# 기존 PM2 프로세스 중지
echo "기존 프로세스 정리 중..."
pm2 delete jaryo-file-manager 2>/dev/null || true
# PM2로 서비스 시작
echo "PM2로 서비스 시작 중..."
pm2 start pm2-ecosystem.config.js --env production
# PM2 시작 스크립트 생성
pm2 startup
pm2 save
echo "서비스가 PM2로 시작되었습니다."
echo "상태 확인: pm2 status"
echo "로그 확인: pm2 logs jaryo-file-manager"
echo "서비스 중지: pm2 stop jaryo-file-manager"

View File

@@ -1,12 +0,0 @@
services:
- type: web
name: jaryo-file-manager
env: node
plan: free
buildCommand: npm install
startCommand: npm start
envVars:
- key: NODE_ENV
value: production
- key: PORT
value: 10000

View File

@@ -1,73 +0,0 @@
const fs = require('fs');
const path = require('path');
const sqlite3 = require('sqlite3').verbose();
// 데이터베이스 파일 경로
const dbPath = path.join(__dirname, '../database/jaryo.db');
const schemaPath = path.join(__dirname, '../database/schema.sql');
// database 폴더가 없으면 생성
const dbDir = path.dirname(dbPath);
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true });
}
// uploads 폴더도 생성
const uploadsDir = path.join(__dirname, '../uploads');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}
console.log('🔧 SQLite 데이터베이스 초기화 시작...');
// 데이터베이스 연결
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('❌ 데이터베이스 연결 오류:', err.message);
return;
}
console.log('✅ SQLite 데이터베이스 연결 성공');
});
// 스키마 파일 읽기 및 실행
fs.readFile(schemaPath, 'utf8', (err, schema) => {
if (err) {
console.error('❌ 스키마 파일 읽기 오류:', err.message);
return;
}
// 여러 SQL 문을 분리하여 실행
const statements = schema.split(';').filter(stmt => stmt.trim().length > 0);
db.serialize(() => {
statements.forEach((statement, index) => {
if (statement.trim()) {
db.run(statement + ';', (err) => {
if (err) {
console.error(`❌ SQL 실행 오류 (${index + 1}):`, err.message);
console.error('실행하려던 SQL:', statement);
}
});
}
});
console.log('✅ 데이터베이스 스키마 생성 완료');
// 데이터 확인
db.all('SELECT COUNT(*) as count FROM files', (err, rows) => {
if (err) {
console.error('❌ 데이터 확인 오류:', err.message);
} else {
console.log(`📊 파일 테이블 레코드 수: ${rows[0].count}`);
}
db.close((err) => {
if (err) {
console.error('❌ 데이터베이스 종료 오류:', err.message);
} else {
console.log('🏁 데이터베이스 초기화 완료');
}
});
});
});
});

View File

@@ -1,110 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jaryo File Manager - 테스트</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.status {
background: #e8f5e8;
border: 1px solid #4caf50;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
text-align: center;
}
.button {
background: #4caf50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
display: inline-block;
margin: 5px;
}
.button:hover {
background: #45a049;
}
.test-section {
margin: 20px 0;
padding: 15px;
background: #f0f8ff;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Jaryo File Manager</h1>
<div class="status">
<h3>✅ 정적 페이지 테스트</h3>
<p>이 페이지가 보인다면 Vercel 배포가 성공한 것입니다!</p>
<p><strong>시간:</strong> <span id="currentTime"></span></p>
</div>
<div class="test-section">
<h3>🔧 테스트 버튼들</h3>
<a href="/index.html" class="button">메인 페이지</a>
<a href="/api/test" class="button">API 테스트</a>
<a href="/api/files" class="button">파일 API</a>
</div>
<div class="test-section">
<h3>📡 AJAX 테스트</h3>
<button onclick="testAPI()" class="button">API 연결 테스트</button>
<div id="apiResult" style="margin-top: 10px;"></div>
</div>
</div>
<script>
// 현재 시간 표시
document.getElementById('currentTime').textContent = new Date().toLocaleString('ko-KR');
// API 테스트 함수
async function testAPI() {
const resultDiv = document.getElementById('apiResult');
resultDiv.innerHTML = '🔄 API 테스트 중...';
try {
const response = await fetch('/api/test');
const data = await response.json();
resultDiv.innerHTML = `
<div style="background: #e8f5e8; padding: 10px; border-radius: 5px;">
<strong>✅ API 연결 성공!</strong><br>
메시지: ${data.message}<br>
시간: ${data.timestamp}
</div>
`;
} catch (error) {
resultDiv.innerHTML = `
<div style="background: #ffe8e8; padding: 10px; border-radius: 5px;">
<strong>❌ API 연결 실패</strong><br>
오류: ${error.message}
</div>
`;
}
}
</script>
</body>
</html>

View File

@@ -1,12 +0,0 @@
#!/bin/bash
# SSH 연결 헬퍼 스크립트
NAS_IP="${1:-119.64.1.86}"
NAS_USER="${2:-vibsin9322}"
COMMAND="${3:-echo 'SSH 연결 성공'}"
# 비밀번호를 입력받아 SSH 연결
echo "🔑 SSH 연결 시도: $NAS_USER@$NAS_IP:2222"
echo "📝 비밀번호를 입력하세요:"
ssh -p 2222 -o StrictHostKeyChecking=no "$NAS_USER@$NAS_IP" "$COMMAND"

View File

@@ -619,7 +619,7 @@ header p {
/* 제목 스타일 */
.board-title {
color: #667eea;
font-weight: 500;
font-weight: 700;
text-decoration: none;
cursor: pointer;
display: block;

View File

@@ -1,203 +0,0 @@
# 시놀로지 NAS Git Server 진단 및 해결 가이드
## 🔍 1단계: Git Server 패키지 상태 확인
### 1.1 DSM 패키지 센터 점검
1. **DSM 로그인****패키지 센터**
2. **설치됨** 탭에서 "Git Server" 검색
3. 상태 확인:
-**실행 중**: 정상 동작
- ⚠️ **중지됨**: 서비스 시작 필요
-**미설치**: 패키지 설치 필요
### 1.2 Git Server 서비스 시작
```bash
# SSH로 NAS 접속 후
sudo systemctl status git-daemon
sudo systemctl start git-daemon
sudo systemctl enable git-daemon
```
## 🛠️ 2단계: 기본 설정 확인
### 2.1 SSH 서비스 활성화
1. **DSM 제어판****터미널 및 SNMP**
2. **SSH 서비스 활성화** 체크
3. 포트 번호 확인 (기본: 22)
### 2.2 사용자 권한 설정
1. **DSM 제어판****사용자 및 그룹**
2. 사용자 선택 → **편집****애플리케이션**
3. **Git Server** 권한 부여
### 2.3 방화벽 설정
1. **DSM 제어판****보안****방화벽**
2. 다음 포트 허용:
- SSH: 22
- Git HTTP: 3000
- Git HTTPS: 3001
## 📁 3단계: Git 디렉토리 구조 확인
### 3.1 기본 경로 확인
```bash
# SSH 접속 후 확인
ls -la /volume1/
ls -la /volume1/git/
# Git 설정 디렉토리 확인
ls -la /usr/local/git/
```
### 3.2 권한 문제 해결
```bash
# Git 디렉토리 생성
sudo mkdir -p /volume1/git
sudo chown -R admin:users /volume1/git
sudo chmod 755 /volume1/git
# Git Server 사용자 추가 (필요시)
sudo adduser git
sudo usermod -a -G users git
```
## 🔧 4단계: 레포지토리 수동 생성
### 4.1 Bare 레포지토리 생성
```bash
# SSH로 NAS 접속
ssh admin@your-nas-ip
# 프로젝트 디렉토리 생성
cd /volume1/git
sudo mkdir jaryo-file-manager.git
cd jaryo-file-manager.git
# Bare 레포지토리 초기화
sudo git init --bare
sudo chown -R admin:users .
```
### 4.2 웹 인터페이스 활성화
```bash
# Git HTTP 서버 시작
cd /volume1/git
sudo git daemon --reuseaddr --base-path=. --export-all --verbose --enable=receive-pack
```
## 🌐 5단계: 웹 인터페이스 설정
### 5.1 Git Web 설정
```bash
# CGit 또는 GitWeb 설치
sudo apt update
sudo apt install gitweb
# Apache 설정 (Web Station 사용시)
sudo ln -s /usr/share/gitweb /volume1/web/git
```
### 5.2 브라우저에서 접속
- URL: `http://your-nas-ip/git`
- 또는: `http://your-nas-ip:3000`
## 🚨 6단계: 문제 해결
### 6.1 "레포지토리 설정이 안보임" 해결
**원인 1: Git Server 패키지 미설치**
```bash
# 패키지 센터에서 Git Server 재설치
# 또는 수동 Git 설치
sudo apt update
sudo apt install git git-daemon-run
```
**원인 2: 서비스 시작 실패**
```bash
# 서비스 상태 확인
sudo systemctl status git-daemon
sudo journalctl -u git-daemon
# 수동 재시작
sudo systemctl restart git-daemon
```
**원인 3: 권한 문제**
```bash
# 권한 재설정
sudo chown -R www-data:www-data /volume1/git
sudo chmod -R 755 /volume1/git
```
### 6.2 포트 충돌 해결
```bash
# 포트 사용 확인
sudo netstat -tulpn | grep :3000
sudo netstat -tulpn | grep :22
# 다른 포트로 변경
sudo git daemon --port=3001 --reuseaddr --base-path=/volume1/git --export-all
```
## 📋 7단계: 연결 테스트
### 7.1 로컬에서 연결 테스트
```bash
# SSH 연결 테스트
ssh admin@your-nas-ip
# Git 클론 테스트
git clone ssh://admin@your-nas-ip/volume1/git/jaryo-file-manager.git
# 또는 HTTP 연결
git clone http://your-nas-ip:3000/jaryo-file-manager.git
```
### 7.2 기존 프로젝트 푸시
```bash
# 기존 프로젝트에서
git remote add nas ssh://admin@your-nas-ip/volume1/git/jaryo-file-manager.git
git push nas master
```
## 🔄 8단계: 자동화 설정
### 8.1 systemd 서비스 생성
```bash
# /etc/systemd/system/git-daemon.service
sudo tee /etc/systemd/system/git-daemon.service << EOF
[Unit]
Description=Git Daemon
After=network.target
[Service]
ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/volume1/git --export-all --verbose --enable=receive-pack
Restart=always
User=git
Group=git
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable git-daemon
sudo systemctl start git-daemon
```
## 📊 요약
레포지토리 설정이 보이지 않는 주요 원인:
1. ❌ Git Server 패키지 미설치/미실행
2. ❌ SSH 서비스 비활성화
3. ❌ 사용자 권한 부족
4. ❌ 방화벽 차단
5. ❌ Git 디렉토리 부재
해결 순서:
1. 패키지 설치/재시작
2. SSH 및 권한 설정
3. 수동 레포지토리 생성
4. 연결 테스트
5. 자동화 설정
이 가이드를 따라하면 시놀로지 NAS에서 Git 레포지토리를 성공적으로 설정할 수 있습니다.

View File

@@ -1,248 +0,0 @@
# 시놀로지 NAS에서 Jaryo File Manager 서비스 실행 가이드
## 1. 사전 준비사항
### 1.1 DSM 패키지 설치
1. **DSM 제어판****패키지 센터** 접속
2. 다음 패키지들을 설치:
- **Node.js** (최신 LTS 버전 권장)
- **Git Server** (선택사항, 소스코드 관리용)
- **Web Station** (선택사항, 웹 서버 프록시용)
### 1.2 SSH 활성화
1. **DSM 제어판****터미널 및 SNMP****SSH 서비스 활성화**
2. 포트 번호 확인 (기본: 22)
## 2. 프로젝트 배포
### 2.1 방법 1: 직접 파일 업로드 (간단한 방법)
1. **File Station**에서 `/volume1/web/` 폴더 생성
2. 프로젝트 파일들을 `jaryo` 폴더에 업로드
3. SSH로 접속하여 설정
### 2.2 방법 2: Git을 통한 배포 (권장)
```bash
# NAS에 SSH 접속
ssh admin@your-nas-ip
# 프로젝트 디렉토리 생성
mkdir -p /volume1/web/jaryo
cd /volume1/web/jaryo
# Git 저장소 클론 (로컬에서 push한 경우)
git clone [your-repository-url] .
# 또는 로컬에서 직접 파일 복사
# scp -r ./jaryo/* admin@your-nas-ip:/volume1/web/jaryo/
```
## 3. 서비스 설정 및 실행
### 3.1 스크립트 권한 설정
```bash
# SSH로 NAS 접속
ssh admin@your-nas-ip
# 프로젝트 디렉토리로 이동
cd /volume1/web/jaryo
# 스크립트 실행 권한 부여
chmod +x start-service.sh
chmod +x stop-service.sh
```
### 3.2 서비스 시작
```bash
# 서비스 시작
./start-service.sh
# 로그 확인
tail -f logs/app.log
# 프로세스 상태 확인
ps aux | grep "node server.js"
```
### 3.3 서비스 중지
```bash
# 서비스 중지
./stop-service.sh
```
## 4. 자동 시작 설정 (선택사항)
### 4.1 Task Scheduler 사용
1. **DSM 제어판****작업 스케줄러**
2. **작업 생성****사용자 정의 스크립트**
3. 설정:
- **작업 이름**: Jaryo Service Start
- **사용자**: root
- **스케줄**: 시스템 부팅 시
- **작업 설정**: `/volume1/web/jaryo/start-service.sh`
### 4.2 rc.local 사용 (고급 사용자)
```bash
# /etc/rc.local 파일 편집
sudo vi /etc/rc.local
# 다음 라인 추가
/volume1/web/jaryo/start-service.sh &
# 파일 저장 후 권한 설정
chmod +x /etc/rc.local
```
## 5. 방화벽 및 포트 설정
### 5.1 DSM 방화벽 설정
1. **DSM 제어판****보안****방화벽**
2. **방화벽 규칙 편집****규칙 생성**
3. 설정:
- **포트**: 3005 (애플리케이션 포트)
- **프로토콜**: TCP
- **소스**: 허용할 IP 범위
### 5.2 라우터 포트 포워딩 (외부 접속용)
라우터에서 포트 3005를 NAS의 IP로 포워딩 설정
## 6. 웹 서버 프록시 설정 (Web Station 사용)
### 6.1 Web Station 설정
1. **Web Station****가상 호스트****생성**
2. 설정:
- **도메인 이름**: your-domain.com (또는 IP)
- **포트**: 80 (또는 443 for HTTPS)
- **문서 루트**: `/volume1/web/jaryo`
- **HTTP 백엔드 서버**: `http://localhost:3005`
### 6.2 Apache 설정 (고급)
```apache
# /volume1/web/apache/conf/vhost/VirtualHost.conf
<VirtualHost *:80>
ServerName your-domain.com
DocumentRoot /volume1/web/jaryo
ProxyPreserveHost On
ProxyPass / http://localhost:3005/
ProxyPassReverse / http://localhost:3005/
<Directory /volume1/web/jaryo>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
```
## 7. 모니터링 및 유지보수
### 7.1 로그 모니터링
```bash
# 실시간 로그 확인
tail -f /volume1/web/jaryo/logs/app.log
# 로그 파일 크기 확인
du -h /volume1/web/jaryo/logs/app.log
# 로그 로테이션 설정 (logrotate 사용)
```
### 7.2 서비스 상태 확인
```bash
# 프로세스 확인
ps aux | grep "node server.js"
# 포트 사용 확인
netstat -tlnp | grep :3005
# 메모리 사용량 확인
top -p $(cat /volume1/web/jaryo/app.pid)
```
### 7.3 백업 설정
1. **Hyper Backup** 패키지 설치
2. `/volume1/web/jaryo` 폴더 백업 스케줄 설정
3. 데이터베이스 파일 (`jaryo.db`) 별도 백업 권장
## 8. 문제 해결
### 8.1 일반적인 문제들
**서비스가 시작되지 않는 경우:**
```bash
# Node.js 설치 확인
which node
node --version
# 의존성 재설치
cd /volume1/web/jaryo
rm -rf node_modules package-lock.json
npm install
# 권한 문제 확인
ls -la /volume1/web/jaryo/
chown -R admin:users /volume1/web/jaryo/
```
**포트 충돌 문제:**
```bash
# 포트 사용 확인
netstat -tlnp | grep :3005
# 다른 포트로 변경 (server.js 수정)
# const PORT = process.env.PORT || 3006;
```
**메모리 부족 문제:**
```bash
# 메모리 사용량 확인
free -h
# Node.js 메모리 제한 설정
# node --max-old-space-size=512 server.js
```
### 8.2 로그 분석
```bash
# 에러 로그만 확인
grep -i error /volume1/web/jaryo/logs/app.log
# 최근 100줄 확인
tail -100 /volume1/web/jaryo/logs/app.log
# 특정 시간대 로그 확인
grep "2024-01-15" /volume1/web/jaryo/logs/app.log
```
## 9. 보안 고려사항
1. **HTTPS 설정**: Let's Encrypt 인증서 사용
2. **방화벽 강화**: 필요한 포트만 개방
3. **정기 업데이트**: Node.js 및 패키지 업데이트
4. **백업**: 정기적인 데이터 백업
5. **모니터링**: 로그 모니터링 및 알림 설정
## 10. 성능 최적화
1. **PM2 사용**: 프로세스 관리자로 PM2 사용 고려
2. **캐싱**: 정적 파일 캐싱 설정
3. **압축**: gzip 압축 활성화
4. **CDN**: 정적 파일 CDN 사용 고려
---
**참고**: 이 가이드는 시놀로지 DSM 7.x 기준으로 작성되었습니다. 버전에 따라 일부 설정이 다를 수 있습니다.

View File

@@ -1,33 +0,0 @@
{
"version": 2,
"routes": [
{
"src": "/simple",
"dest": "/simple.html"
},
{
"src": "/api/test",
"dest": "/api/test.js"
},
{
"src": "/api/files/public",
"dest": "/api/files.js"
},
{
"src": "/api/files",
"dest": "/api/files.js"
},
{
"src": "/(.*\\.(css|js|json|svg|png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|html))",
"dest": "/$1"
},
{
"src": "/admin/(.*)",
"dest": "/admin/$1"
},
{
"src": "/",
"dest": "/simple.html"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB