GNU/_PAGE/backup/backup_compress.php
<?php
require_once '/home/www/GNU/_PAGE/head.php';
/**
 * Web Directory Zipper (그누보드 라이브러리 활용 버전)
 * -------------------------------------------------------------------------
 * 500 에러 방지 및 실시간 백업 목록 반영 강화 버전 (Dark UI Edition + Animation)
 */

// 1. 서버 환경 설정 (대용량 압축 시 에러 방지)
@set_time_limit(0);              // 실행 시간 제한 해제
@ini_set('memory_limit', '-1');     // 메모리 제한 해제
@ini_set('zlib.output_compression', 'Off');

// 2. 그누보드 핵심 엔진 로드
if (file_exists('./common.php')) {
    include_once('./common.php');
} else {
    if (!function_exists('sql_query')) {
        function sql_query($sql) { return null; }
    }
}

/**
 * 디렉토리 압축 함수 (시스템 리소스 최적화 및 검증 강화)
 */
function zipDirectory($source, $destination) {
    if (!class_exists('ZipArchive')) {
        return "서버에 ZipArchive PHP 모듈이 설치되어 있지 않습니다.";
    }

    $sourcePath = realpath($source);
    if (!$sourcePath || !file_exists($sourcePath)) return "대상 디렉토리가 존재하지 않습니다.";
    
    // 대상 디렉토리 자체에 대한 읽기 권한 체크
    if (!is_readable($sourcePath)) {
        return "대상 디렉토리($source)를 읽을 수 없습니다. 권한(777)을 확인하세요.";
    }

    $zip = new ZipArchive();
    $res = $zip->open($destination, ZipArchive::CREATE | ZipArchive::OVERWRITE);
    
    if ($res !== true) {
        return "압축 파일을 열 수 없습니다. (에러 코드: $res)";
    }

    $sourcePath = str_replace('\\', '/', $sourcePath);
    $fileCount = 0;
    $skippedCount = 0;

    try {
        $files = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($sourcePath, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($files as $file) {
            $file = str_replace('\\', '/', $file);
            $filePath = realpath($file);
            
            // 압축 파일 자신을 압축하려 하는 경우 방지
            if ($filePath === realpath($destination)) continue;

            $relativePath = substr($filePath, strlen($sourcePath) + 1);

            if (is_dir($file)) {
                $zip->addEmptyDir($relativePath);
            } else if (is_file($file)) {
                if (is_readable($file)) {
                    $zip->addFile($filePath, $relativePath);
                    $fileCount++;
                } else {
                    $skippedCount++;
                }
            }
        }
    } catch (Exception $e) {
        $zip->close();
        return "디렉토리 탐색 오류: " . $e->getMessage();
    }
    
    // Zip 파일 쓰기 완료 확인
    if (!$zip->close()) {
        return "압축 파일 저장(Close)에 실패했습니다. 디스크 공간이나 권한을 확인하세요.";
    }

    if ($fileCount === 0 && $skippedCount > 0) {
        @unlink($destination);
        return "읽을 수 있는 파일이 하나도 없습니다. ($skippedCount 개의 파일 접근 거부)";
    }

    // 파일이 실제로 생성되었는지 물리적 확인
    if (!file_exists($destination)) {
        return "파일 쓰기가 완료되었으나 물리적 경로에서 파일을 찾을 수 없습니다.";
    }

    // FTP에서 보일 수 있도록 권한 강제 조정
    @chmod($destination, 0644);

    return true;
}

$message = "";
$status = "";
$target_root = "/home/www";     
$backup_root = "/data/backup";   

// 3. 압축 실행 요청 처리
if (isset($_POST['action']) && $_POST['action'] == 'compress') {
    $target_dir = isset($_POST['target_dir']) ? trim($_POST['target_dir']) : '';
    $dest_dir = isset($_POST['dest_dir']) ? trim($_POST['dest_dir']) : $backup_root;
    
    // 저장 경로 생성 및 권한 체크
    if (!is_dir($dest_dir)) {
        if (!@mkdir($dest_dir, 0707, true)) {
            $status = "error";
            $message = "저장 폴더를 생성할 권한이 없습니다: " . $dest_dir;
        }
        @chmod($dest_dir, 0707);
    }

    if (!$message && $target_dir && is_dir($target_dir)) {
        $folder_name = basename($target_dir);
        $file_name = $folder_name . "_" . date("Ymd_His") . ".zip";
        $dest_path = rtrim($dest_dir, '/') . "/" . $file_name;

        $result = zipDirectory($target_dir, $dest_path);
        
        if ($result === true) {
            $status = "success";
            $message = "백업 성공! 파일이 생성되었습니다.<br><span class='text-[10px] font-mono opacity-70'>$dest_path</span>";
        } else {
            $status = "error";
            $message = "백업 실패: " . $result;
        }
    } else if (!$message) {
        $status = "error";
        $message = "대상 디렉토리를 찾을 수 없습니다.";
    }
}

// 4. 목록 가져오기 함수
function getDirectoryList($path) {
    $dirs = [];
    if (is_dir($path)) {
        $items = @scandir($path);
        if ($items) {
            foreach ($items as $item) {
                if ($item != "." && $item != ".." && is_dir($path . "/" . $item)) {
                    $dirs[] = $item;
                }
            }
        }
    }
    return $dirs;
}

$source_dirs = getDirectoryList($target_root);
$dest_sub_dirs = getDirectoryList($backup_root);

// 5. 백업 파일 목록 검색 (하위 폴더 포함 전체 검색)
$backup_list = [];
if (is_dir($backup_root)) {
    try {
        $dir_iterator = new RecursiveDirectoryIterator($backup_root, RecursiveDirectoryIterator::SKIP_DOTS);
        $iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
        foreach ($iterator as $file) {
            if ($file->isFile() && strtolower($file->getExtension()) == 'zip') {
                $backup_list[] = [
                    'name' => $file->getFilename(),
                    'path' => str_replace($backup_root, '', $file->getPathname()),
                    'size' => round($file->getSize() / 1024 / 1024, 2) . ' MB',
                    'date' => date("Y-m-d H:i:s", $file->getMTime()),
                    'mtime' => $file->getMTime()
                ];
            }
        }
        usort($backup_list, function($a, $b) { return $b['mtime'] - $a['mtime']; });
    } catch (Exception $e) {}
}
?>


    <title>Web Backup Tool - Dark Edition</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">


    
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Pretendard:wght@400;700;900&display=swap');
        
        body { font-family: 'Pretendard', sans-serif; background-color: #020617; margin: 0; padding: 0; color: #e2e8f0; overflow-x: hidden; }
        
        /* 입구 애니메이션 */
        @keyframes fadeInUp {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        .animate-fadeInUp { animation: fadeInUp 0.6s ease-out forwards; }
        .Main_box { margin:50px; }
        
        .card-blur { background: rgba(15, 23, 42, 0.85); backdrop-filter: blur(20px); border: 1px solid rgba(51, 65, 85, 0.5); }
        
        .custom-scrollbar::-webkit-scrollbar { width: 5px; }
        .custom-scrollbar::-webkit-scrollbar-track { background: #0f172a; }
        .custom-scrollbar::-webkit-scrollbar-thumb { background: #334155; border-radius: 10px; }
        .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #475569; }
        
        input[readonly] { cursor: default; }
        
        /* 버튼 상호작용 */
        .source-btn, .dest-btn { transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); }
        .source-btn:active, .dest-btn:active { transform: scale(0.97); }

        /*---------| BODY SCROLLBAR |---------*/
        body::-webkit-scrollbar                                            { width:10px; }
        body::-webkit-scrollbar-thumb                                      { background-color:#333; border:1px solid #222; }
        body::-webkit-scrollbar-thumb:hover                                { background-color:#555; cursor:default; }
        body::-webkit-scrollbar-track                                      { background-color:#0b0e11; }
    </style>

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">

    <script>
        function setSourceDir(dirName, element) {
            document.getElementById('target_dir_input').value = "<?php echo $target_root; ?>/" + dirName;
            highlightBtn('source-btn', element);
        }
        function setDestDir(dirName, element) {
            const path = dirName === 'root' ? "<?php echo $backup_root; ?>" : "<?php echo $backup_root; ?>/" + dirName;
            document.getElementById('dest_dir_input').value = path;
            highlightBtn('dest-btn', element);
        }
        function highlightBtn(className, element) {
            document.querySelectorAll('.' + className).forEach(btn => {
                btn.classList.remove('bg-indigo-950/50', 'border-indigo-500', 'ring-2', 'ring-indigo-500/20', 'text-indigo-300', 'shadow-lg', 'shadow-indigo-900/20');
            });
            element.classList.add('bg-indigo-950/50', 'border-indigo-500', 'ring-2', 'ring-indigo-500/20', 'text-indigo-300', 'shadow-lg', 'shadow-indigo-900/20');
        }

        // 실행 버튼 클릭 시 로딩 상태 표현
        function startLoading() {
            const btn = document.getElementById('exec-btn');
            const icon = btn.querySelector('i');
            const text = btn.querySelector('span');
            
            btn.classList.add('opacity-80', 'cursor-wait');
            icon.classList.remove('fa-bolt');
            icon.classList.add('fa-spinner', 'fa-spin');
            text.innerText = "프로세스 실행 중...";
        }
    </script>

<body class="items-center justify-center min-h-screen text-left">

    <div class="Main_box">
        
        <!-- 헤더 -->
        <div class="flex items-center justify-between mb-10">
            <div class="flex items-center space-x-5">
                <div class="bg-indigo-600 p-4 rounded-[5px] text-white shadow-xl shadow-indigo-900/50">
                    <i class="fas fa-archive text-2xl"></i>
                </div>
                <div class="text-left">
                    <h1 class="text-2xl font-black text-white tracking-tight">Data Zipper Pro</h1>
                    <p class="text-[11px] text-indigo-400 font-extrabold uppercase tracking-widest text-left">Maria Volume Manager · Dark</p>
                </div>
            </div>
            <button onclick="document.getElementById('history-panel').classList.toggle('hidden')" 
                    class="bg-slate-800 hover:bg-slate-700 active:scale-95 text-slate-300 px-5 py-2.5 rounded-[5px] text-xs font-bold transition-all border border-slate-700 shadow-sm flex items-center">
                <i class="fas fa-history mr-2 text-indigo-400"></i> 백업 내역 (<?php echo count($backup_list); ?>)
            </button>
        </div>

        <?php if ($message): ?>
        <div class="mb-8 p-6 rounded-[5px] flex items-center justify-between space-x-4 <?php echo $status == 'success' ? 'bg-emerald-950/30 text-emerald-400 border border-emerald-900/50' : 'bg-rose-950/30 text-rose-400 border border-rose-900/50'; ?> animate-fadeInUp">
            <div class="flex items-center space-x-4">
                <div class="<?php echo $status == 'success' ? 'bg-emerald-500' : 'bg-rose-500'; ?> p-2 rounded-[5px] text-white flex-shrink-0">
                    <i class="fas <?php echo $status == 'success' ? 'fa-check' : 'fa-exclamation'; ?> text-xs"></i>
                </div>
                <div class="text-sm font-bold text-left leading-relaxed"><?php echo $message; ?></div>
            </div>
            <a href="<?php echo $_SERVER['PHP_SELF']; ?>" class="bg-slate-800 hover:bg-slate-700 active:scale-95 text-slate-300 px-4 py-2 rounded-[5px] text-xs font-bold transition-all border border-slate-700 flex-shrink-0 shadow-lg">
                <i class="fas fa-redo mr-1 text-emerald-400"></i> 처음으로
            </a>
        </div>
        <?php endif; ?>

        <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-10">
            <!-- 1. 소스 선택 -->
            <div class="bg-slate-900/50 rounded-[7px] p-6 border border-slate-800 text-left">
                <h3 class="text-[10px] font-black text-slate-500 uppercase tracking-widest mb-4 flex items-center">
                    <i class="fas fa-file-export mr-2 text-indigo-500"></i> STEP 1. 소스 폴더 (/home/www)
                </h3>
                <div class="grid grid-cols-1 gap-2 max-h-52 overflow-y-auto custom-scrollbar pr-1">
                    <?php if (empty($source_dirs)): ?>
                        <p class="text-[11px] text-slate-600 italic py-4 text-center">폴더가 없거나 권한이 없습니다.</p>
                    <?php endif; ?>
                    <?php foreach ($source_dirs as $index => $dir): ?>
                    <button type="button" onclick="setSourceDir('<?php echo $dir; ?>', this)" 
                            class="source-btn w-full flex items-center p-4 bg-slate-800/40 border border-slate-700 rounded-[5px] text-[12px] font-bold text-slate-400 hover:border-indigo-500/50 hover:bg-slate-800 transition-all text-left">
                        <i class="fas fa-folder text-amber-500/80 mr-3"></i> <?php echo $dir; ?>
                    </button>
                    <?php endforeach; ?>
                </div>
            </div>

            <!-- 2. 저장 위치 선택 -->
            <div class="bg-slate-900/50 rounded-[7px] p-6 border border-slate-800 text-left">
                <h3 class="text-[10px] font-black text-slate-500 uppercase tracking-widest mb-4 flex items-center">
                    <i class="fas fa-file-import mr-2 text-emerald-500"></i> STEP 2. 저장 위치 (/data/backup)
                </h3>
                <div class="grid grid-cols-1 gap-2 max-h-52 overflow-y-auto custom-scrollbar pr-1">
                    <button type="button" onclick="setDestDir('root', this)" 
                            class="dest-btn w-full flex items-center p-4 bg-slate-800/40 border border-slate-700 rounded-[5px] text-[12px] font-bold text-slate-400 hover:border-emerald-500/50 hover:bg-slate-800 transition-all text-left">
                        <i class="fas fa-hdd text-slate-500 mr-3"></i> [기본] /data/backup
                    </button>
                    <?php foreach ($dest_sub_dirs as $dir): ?>
                    <button type="button" onclick="setDestDir('<?php echo $dir; ?>', this)" 
                            class="dest-btn w-full flex items-center p-4 bg-slate-800/40 border border-slate-700 rounded-[5px] text-[12px] font-bold text-slate-400 hover:border-emerald-500/50 hover:bg-slate-800 transition-all text-left">
                        <i class="fas fa-folder text-emerald-500/70 mr-3"></i> <?php echo $dir; ?>
                    </button>
                    <?php endforeach; ?>
                </div>
            </div>
        </div>

        <!-- 백업 내역 패널 -->
        <div id="history-panel" class="hidden mb-10 bg-black/40 rounded-[7px] border border-slate-800 overflow-hidden shadow-2xl text-left">
            <div class="px-6 py-4 bg-slate-900 border-b border-slate-800 flex justify-between items-center text-left">
                <span class="text-[10px] font-black text-slate-500 uppercase tracking-widest text-left">Recent Backups</span>
                <button onclick="location.reload()" class="text-indigo-400 hover:rotate-180 transition-transform duration-500"><i class="fas fa-sync text-xs"></i></button>
            </div>
            <div class="max-h-72 overflow-y-auto custom-scrollbar">
                <?php if (empty($backup_list)): ?>
                    <div class="p-12 text-center text-slate-600 text-xs italic">백업 내역이 없습니다.</div>
                <?php else: ?>
                    <div class="divide-y divide-slate-800/50">
                        <?php foreach ($backup_list as $file): ?>
                        <div class="px-6 py-4 flex justify-between items-center hover:bg-slate-800/30 transition-all text-left group">
                            <div class="flex flex-col text-left overflow-hidden">
                                <span class="text-[12px] font-bold text-slate-300 truncate text-left group-hover:text-indigo-400 transition-colors" title="<?php echo $file['path']; ?>"><?php echo $file['name']; ?></span>
                                <span class="text-[9px] text-slate-500 text-left mt-0.5"><?php echo $file['date']; ?> <span class="ml-2 text-slate-700 text-left"><?php echo $file['path']; ?></span></span>
                            </div>
                            <span class="text-[11px] font-black text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-[5px] ml-4 flex-shrink-0"><?php echo $file['size']; ?></span>
                        </div>
                        <?php endforeach; ?>
                    </div>
                <?php endif; ?>
            </div>
        </div>

        <!-- 실행 폼 -->
        <form method="POST" class="space-y-5" onsubmit="startLoading()">
            <input type="hidden" name="action" value="compress">
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-5">
                <div class="text-left">
                    <label class="block text-[11px] font-black text-slate-500 uppercase ml-3 mb-2 tracking-tighter text-left">Source Folder (압축 대상)</label>
                    <input type="text" name="target_dir" id="target_dir_input" readonly
                           class="w-full px-5 py-4 rounded-[5px] border border-slate-800 bg-slate-900 text-[13px] font-bold text-slate-400 outline-none focus:border-indigo-500 transition-all shadow-inner text-left" placeholder="좌측 STEP 1에서 선택">
                </div>
                <div class="text-left">
                    <label class="block text-[11px] font-black text-slate-500 uppercase ml-3 mb-2 tracking-tighter text-left">Destination (저장 위치)</label>
                    <input type="text" name="dest_dir" id="dest_dir_input" readonly
                           class="w-full px-5 py-4 rounded-[5px] border border-slate-800 bg-slate-900 text-[13px] font-bold text-slate-400 outline-none focus:border-emerald-500 transition-all shadow-inner text-left" placeholder="우측 STEP 2에서 위치 선택">
                </div>
            </div>

            <button type="submit" id="exec-btn"
                    class="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-black py-7 rounded-[5px] shadow-xl shadow-indigo-900/20 transition-all flex items-center justify-center space-x-4 group mt-6 border-b-4 border-indigo-800 active:border-b-0 active:translate-y-1">
                <i class="fas fa-bolt text-yellow-300 group-hover:scale-125 transition-transform duration-300"></i>
                <span class="text-xl tracking-tight uppercase">Start Backup Process</span>
            </button>
        </form>

        <!-- 하단 정보 -->
        <div class="mt-12 pt-8 border-t border-slate-800 grid grid-cols-3 gap-4">
            <div class="bg-slate-900/30 p-4 rounded-[5px] text-center border border-slate-800/50">
                <p class="text-[9px] font-black text-slate-600 uppercase mb-1.5 tracking-tighter text-center">Volume</p>
                <span class="text-[11px] font-bold text-slate-400 text-center">196GB (vdb)</span>
            </div>
            <div class="bg-slate-900/30 p-4 rounded-[5px] text-center border border-slate-800/50">
                <p class="text-[9px] font-black text-slate-600 uppercase mb-1.5 tracking-tighter text-center text-center">Source</p>
                <span class="text-[11px] font-bold text-slate-400 text-center text-center">/home/www</span>
            </div>
            <div class="bg-slate-900/30 p-4 rounded-[5px] text-center border border-slate-800/50 text-center">
                <p class="text-[9px] font-black text-slate-600 uppercase mb-1.5 tracking-tighter text-center text-center">Backup Area</p>
                <span class="text-[11px] font-bold text-slate-400 italic text-center text-center">/data/backup</span>
            </div>
        </div>
    </div>

</body>

<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>