Инструменты пользователя

Инструменты сайта


tmp_full

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

tmp_full [2026/05/17 12:18] – создано VladPolskiytmp_full [2026/05/18 11:28] (текущий) – внешнее изменение 127.0.0.1
Строка 1: Строка 1:
-====== index.html ====== +<code> 
-<code html index.html>+=== index.html
 <!DOCTYPE html> <!DOCTYPE html>
 <html lang="ru"> <html lang="ru">
Строка 182: Строка 182:
         </div>         </div>
  
-        <!-- Теперь это ШАГ 9: Процесс установки --> + <!-- НОВЫЙ ШАГ 9: ЭКРАН КОНТРОЛЯ КОНФИГУРАЦИИ --> 
-        <div id="step-9" class="step"> + <div id="step-9" class="step"> 
-            <div class="step-header"><h4>Шаг 9: Выполнение установки</h4></div> + <h4 class="mb-3 text-primary">📊 Шаг 9: Проверка конфигурации сервера</h4> 
-            <p id="install_status" class="fw-bold text-primary">Инициализация скриптов разметки дисковых массивов...</p> + <p class="text-muted small">Внимательно проверьте параметры. После перехода к следующему шагу диски будут принудительно отформатированы, а данные перезаписаны!</p> 
-            <div class="progress mb-3" style="height: 25px;"> +  
-                <div id="install_progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;">0%</div> + <div class="mt-3 text-start"> 
-            </div> + <label class="form-label fw-bold text-secondary">Содержимое файла install_config.txt:</label> 
-            <div class="text-muted small">Время выполнения операции: <span id="install_timer" class="fw-bold text-dark">0 сек.</span></div> + <!-- Текстовая область, имитирующая файл конфигурации --> 
-        </div>+ <textarea id="config-preview-zone"  
 +   class="form-control bg-light border-primary"  
 +   rows="9"  
 +   readonly  
 +   style="font-family: monospace; font-size: 13px; color: #2c3e50; font-weight: bold; background-color: #f8f9fa !important; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);"></textarea> 
 + </div> 
 +  
 + <div class="alert alert-warning d-flex align-items-center mt-3 small" role="alert"> 
 + <div> 
 + ⚠️ <strong>Внимание:</strong> Нажатие кнопки «Далее» безвозвратно уничтожит текущие таблицы разделов на целевых накопителях. 
 + </div> 
 + </div> 
 + </div> 
 + 
 + 
 + 
 + 
 + <!-- ШАГ 10: ПРОЦЕСС УСТАНОВКИ С ВНУТРЕННЕЙ КОНСОЛЬЮ --> 
 + <div id="step-10" class="step"> 
 + <div class="step-header"><h4>Шаг 10: Выполнение установки</h4></div> 
 + <p id="install_status" class="fw-bold text-primary">Инициализация скриптов разметки дисковых массивов...</p> 
 +  
 + <div class="progress mb-3" style="height: 25px;"> 
 + <div id="install_progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%;">0%</div> 
 + </div> 
 +  
 + <div class="text-muted small">Время выполнения операции: <span id="install_timer" class="fw-bold text-dark">0 сек.</span></div> 
 + 
 + <!-- Консоль теперь живет строго ЗДЕСЬ и будет появляться только на этом шаге --> 
 + <div class="mt-4 p-3 bg-dark text-success rounded small text-start" id="installer-console" style="font-family: monospace; min-height: 120px; font-size: 12px; line-height: 1.4; opacity: 0.95; box-shadow: inset 0 0 10px #000;"> 
 + <div class="text-muted border-bottom border-secondary pb-1 mb-2">📟 СИСТЕМНЫЙ ЖУРНАЛ УСТАНОВКИ:</div> 
 + <div id="console-output-lines">Ожидание потока данных...</div> 
 + </div> 
 + </div> 
 + 
 + 
 + 
 + <!-- Теперь это ШАГ 11: Завершение и перезагрузка --> 
 + <div id="step-11" class="step"> 
 + <div class="step-header"><h4 class="text-success">Установка успешно завершена!</h4></div> 
 + <p>Операционная система развернута. Сетевые сервисы, SSH-доступ и конфигурация RAID подготовлены.</p> 
 +  
 + <!-- Сюда JS выведет надпись об обновлении страницы --> 
 + <div id="install_status_final" class="my-3"></div> 
 + 
 + <div class="alert alert-warning text-center p-4"> 
 + <h5>Внимание! Сервер будет отправлен в перезагрузку через:</h5> 
 + <h1 id="reboot_timer" class="display-3 font-monospace my-3 text-danger fw-bold">5</h1> 
 + <p class="mb-0 small fw-bold">Извлеките загрузочную USB-флешку из разъема, чтобы сервер загрузился с основного диска.</p> 
 + </div> 
 + </div>
  
-        <!-- Теперь это ШАГ 10: Завершение и перезагрузка --> 
-        <div id="step-10" class="step"> 
-            <div class="step-header"><h4 class="text-success">Установка успешно завершена!</h4></div> 
-            <p>Операционная система развернута. Сетевые сервисы, SSH-доступ и конфигурация RAID подготовлены.</p> 
-            <div class="alert alert-warning text-center p-4"> 
-                <h5>Внимание! Сервер будет отправлен в перезагрузку через:</h5> 
-                <h1 id="reboot_timer" class="display-3 font-monospace my-3 text-danger fw-bold">5</h1> 
-                <p class="mb-0 small fw-bold">Извлеките загрузочную USB-флешку из разъема, чтобы сервер загрузился с основного диска.</p> 
-            </div> 
-        </div> 
  
  
Строка 216: Строка 256:
 <script> <script>
 let currentStep = 1; let currentStep = 1;
-const totalSteps = 10;+const totalSteps = 11;
  
 // Флаги валидации для учетной записи // Флаги валидации для учетной записи
Строка 241: Строка 281:
     const errorBlock = document.getElementById('error-message-zone');     const errorBlock = document.getElementById('error-message-zone');
     if (errorBlock) errorBlock.style.display = 'none';     if (errorBlock) errorBlock.style.display = 'none';
 +
 +    // АВТО-ОЧИСТКА СЕРВЕРА: Если пользователь возвращается НАЗАД с Шага 9
 +    if (direction === -1 && currentStep === 9) {
 +        // Блокируем интерфейс на долю секунды для выполнения зачистки
 +        document.getElementById('btn-prev').disabled = true;
 +        document.getElementById('btn-next').disabled = true;
 +
 +        fetch('api/cancel_install.php')
 +        .then(res => res.json())
 +        .then(result => {
 +            // Разблокируем кнопки после успешной очистки сервера
 +            document.getElementById('btn-prev').disabled = false;
 +            document.getElementById('btn-next').disabled = false;
 +        })
 +        .catch(err => {
 +            console.log("Ошибка фоновой очистки: ", err);
 +            document.getElementById('btn-prev').disabled = false;
 +            document.getElementById('btn-next').disabled = false;
 +        });
 +    }
  
     // Подгрузка доступных дисков при переходе с 5 на 6 шаг     // Подгрузка доступных дисков при переходе с 5 на 6 шаг
Строка 274: Строка 334:
     }     }
  
-    // Действия при нажатии "Далее" на Шаге 8 (Старт установки)+    // ТРИГГЕР: Нажатие "Далее" на Шаге 8 (Сборка превью install_config.txt без отправки на сервер)
     if (direction === 1 && currentStep === 8) {     if (direction === 1 && currentStep === 8) {
         let selectedDisks = [];         let selectedDisks = [];
         const diskMode = document.querySelector('input[name="disk_mode"]:checked').value;         const diskMode = document.querySelector('input[name="disk_mode"]:checked').value;
 +        
 +        if (diskMode === 'raid1') {
 +            document.querySelectorAll('.disk-checkbox:checked').forEach(cb => { 
 +                selectedDisks.push(cb.value); 
 +            });
 +        } else {
 + const firstDiskInput = document.querySelector('.disk-checkbox');
 + // ИСПРАВЛЕНО: Убран хардкод sda. Если чекбокс найден - берем его value, иначе оставляем пустоту для валидации
 + selectedDisks.push(firstDiskInput ? firstDiskInput.value : '');
 +        }
  
 +        // Формируем точный текст будущего конфигурационного файла для вывода пользователю
 +        let configPreview = `SYS_LANG=${document.getElementById('sys_lang').value}\n`;
 +        configPreview += `SYS_LAYOUT=${document.getElementById('sys_layout').value}\n`;
 +        configPreview += `SYS_TIMEZONE=${document.getElementById('sys_timezone').value}\n`;
 +        configPreview += `SYS_HOSTNAME=${document.getElementById('sys_hostname').value.trim()}\n`;
 +        configPreview += `DISK_MODE=${diskMode}\n`;
 +        configPreview += `SELECTED_DISKS=${selectedDisks.join(' ')}\n`;
 +        configPreview += `SYS_USER=${document.getElementById('username').value.trim()}\n`;
 +        configPreview += `SYS_PASS=${document.getElementById('password').value.trim()}`;
 +
 +        // Безопасно выводим сформированный текст в зону контроля нового Шага 9
 +        const previewZone = document.getElementById('config-preview-zone');
 +        if (previewZone) {
 +            previewZone.value = configPreview;
 +        }
 +
 +        // Переключаем интерфейс со старого Шага 8 на новый Шаг 9
 +        document.getElementById(`step-${currentStep}`).classList.remove('active');
 +        currentStep = 9;
 +        document.getElementById(`step-9`).classList.add('active');
 +        
 +        // Кнопки навигации остаются активными, так как это экран контроля, а не установка
 +        document.getElementById('btn-prev').disabled = false;
 +        document.getElementById('btn-next').disabled = false;
 +        
 +        return; // Полностью прерываем выполнение, чтобы не сработал линейный переход ниже
 +    }
 +
 +    // ТРИГГЕР: Нажатие "Далее" на НОВОМ Шаге 9 (Фактический СТАРТ установки и форматирования)
 +    if (direction === 1 && currentStep === 9) {
 +        let selectedDisks = [];
 +        const diskMode = document.querySelector('input[name="disk_mode"]:checked').value;
 +        
         if (diskMode === 'raid1') {         if (diskMode === 'raid1') {
-            document.querySelectorAll('.disk-checkbox:checked').forEach(cb => { +            document.querySelectorAll('.disk-checkbox:checked').forEach(cb => {  
-                selectedDisks.push(cb.value);+                selectedDisks.push(cb.value); 
             });             });
         } else {         } else {
Строка 288: Строка 391:
         }         }
  
-        // Сбор Payload напрямую из полей (СВЕЖИЕ ДАННЫЕ) 
         const payload = {         const payload = {
             lang: document.getElementById('sys_lang').value,             lang: document.getElementById('sys_lang').value,
Строка 299: Строка 401:
             password: document.getElementById('password').value.trim()             password: document.getElementById('password').value.trim()
         };         };
 +
 +        // Блокируем кнопку на время отправки, чтобы избежать повторных кликов
 +        document.getElementById('btn-next').disabled = true;
  
         // ПРИНУДИТЕЛЬНЫЙ ЗАПУСК ОТПРАВКИ НА СЕРВЕР         // ПРИНУДИТЕЛЬНЫЙ ЗАПУСК ОТПРАВКИ НА СЕРВЕР
Строка 309: Строка 414:
         .then(result => {         .then(result => {
             if (result && result.success) {             if (result && result.success) {
-                document.getElementById('step-8').classList.remove('active'); +                // Если сервер принял данные, гасим Шаг 9 и открываем Шаг 10 (Прогресс-бар) 
-                currentStep = 9// <--- Корректный переход +                document.getElementById('step-9').classList.remove('active'); 
-                document.getElementById('step-9').classList.add('active');+                currentStep = 10
 +                document.getElementById('step-10').classList.add('active');
                                  
 +                // Намертво скрываем нижние кнопки навигации на время установки
                 document.getElementById('btn-prev').style.display = 'none';                 document.getElementById('btn-prev').style.display = 'none';
                 document.getElementById('btn-next').style.display = 'none';                 document.getElementById('btn-next').style.display = 'none';
                                  
 +                // Запускаем AJAX-опрос системного журнала установки
                 startInstallationSimulation();                 startInstallationSimulation();
             } else {             } else {
 +                document.getElementById('btn-next').disabled = false;
                 showInlineError('Ошибка сервера: ' + (result && result.message ? result.message : 'Неизвестный ответ'));                 showInlineError('Ошибка сервера: ' + (result && result.message ? result.message : 'Неизвестный ответ'));
             }             }
         })         })
         .catch(err => {         .catch(err => {
 +            document.getElementById('btn-next').disabled = false;
             showInlineError('Ошибка сети: сервер установки недоступен.');             showInlineError('Ошибка сети: сервер установки недоступен.');
         });         });
-        return; // Ждем ответа fetch, блокируем линейный переход+         
 +        return; // Блокируем стандартный линейный переход, ждем ответа от сервера
     }     }
 +
  
     // Линейный переход для остальных шагов     // Линейный переход для остальных шагов
Строка 332: Строка 444:
     document.getElementById(`step-${currentStep}`).classList.add('active');     document.getElementById(`step-${currentStep}`).classList.add('active');
  
-    document.getElementById('btn-prev').disabled = (currentStep === 1 || currentStep >= 9); +    document.getElementById('btn-prev').disabled = (currentStep === 1 || currentStep >= 10); 
-    document.getElementById('btn-next').disabled = (currentStep >= 9);+    document.getElementById('btn-next').disabled = (currentStep >= 10);
  
     if (currentStep === 6) {     if (currentStep === 6) {
Строка 434: Строка 546:
 } }
  
-// Мониторинг лога бэкенда+// Мониторинг лога бэкенда и управление Шагом 10
 function startInstallationSimulation() { function startInstallationSimulation() {
     const progressBar = document.getElementById('install_progress');     const progressBar = document.getElementById('install_progress');
Строка 441: Строка 553:
     let elapsedSeconds = 0;     let elapsedSeconds = 0;
  
 +    // Счётчик времени выполнения операции
     const durationTimer = setInterval(() => {     const durationTimer = setInterval(() => {
         elapsedSeconds++;         elapsedSeconds++;
Строка 446: Строка 559:
     }, 1000);     }, 1000);
  
 +    // Ежесекундный AJAX-опрос системного журнала установки
     const logWatcher = setInterval(() => {     const logWatcher = setInterval(() => {
         fetch('api/get_log.php')         fetch('api/get_log.php')
Строка 451: Строка 565:
         .then(data => {         .then(data => {
             if (data && data.success) {             if (data && data.success) {
 +                // Обновляем визуальный прогресс-бар
                 if (progressBar) {                 if (progressBar) {
                     progressBar.style.width = data.progress + '%';                     progressBar.style.width = data.progress + '%';
                     progressBar.innerText = data.progress + '%';                     progressBar.innerText = data.progress + '%';
                 }                 }
 +                // Обновляем текущий статус над шкалой
                 if (statusText) statusText.innerText = data.status;                 if (statusText) statusText.innerText = data.status;
  
-                // Переход на финал при 90% или завершении скриптов+                // ВЫВОД ПОЛНОЙ ИСТОРИИ ЛОГА В ИНТЕРАКТИВНУЮ ВЕБ-КОНСОЛЬ: 
 +                const consoleZone = document.getElementById('console-output-lines'); 
 +                const consoleWrapper = document.getElementById('installer-console'); 
 +                 
 +                if (consoleZone && data.console && data.console.length > 0) { 
 +                    // Объединяем полученные строки через перенос строки 
 +                    consoleZone.innerHTML = data.console.join('<br>'); 
 +                     
 +                    // Безопасная автоматическая прокрутка терминала вниз 
 +                    if (consoleWrapper) { 
 +                        consoleWrapper.scrollTop = consoleWrapper.scrollHeight; 
 +                    } 
 +                } 
 + 
 +                // Переход с Шага 10 на Финальный Шаг 11 при достижении 90% (С ЗАДЕРЖКОЙ ДЛЯ ИНСПЕКЦИИ)
                 if (data.progress >= 90) {                 if (data.progress >= 90) {
 +                    // 1. Мгновенно останавливаем опрос логов, чтобы зафиксировать финальный текст на экране
                     clearInterval(logWatcher);                     clearInterval(logWatcher);
                     clearInterval(durationTimer);                     clearInterval(durationTimer);
                                          
-                    document.getElementById('step-9').classList.remove('active')+                    // Устанавливаем прогресс-бар на ровные 100% для красоты финала 
-                    currentStep = 10; +                    const progressBar = document.getElementById('install_progress_bar')
-                    document.getElementById('step-10').classList.add('active')+                    if (progressBar) { 
-                     +                        progressBar.style.width = '100%'; 
-                    // Полностью скрываем кнопки управления на Шаге 10 +                        progressBar.setAttribute('aria-valuenow', 100)
-                    document.getElementById('btn-prev').style.display = 'none'; +                        progressBar.innerText = '100%'; 
-                    document.getElementById('btn-next').style.display = 'none'; +                    
-                     + 
-                    startRebootCountdown();+                    console.log("Установка завершена. Запуск 5-секундной паузы для чтения логов перед ребутом..."); 
 + 
 +                    // 2. Включаем задержку на 5000 мс (5 секунд) перед переключением экранов 
 +                    setTimeout(function() { 
 +                        document.getElementById('step-10').classList.remove('active')
 +                        currentStep = 11; 
 +                        document.getElementById('step-11').classList.add('active')
 +                         
 +                        // Запускаем финальный обратный отсчет самой перезагрузки 
 +                        startRebootCountdown(); 
 +                    }, 5000); // 5000 миллисекунд = 5 секунд
                 }                 }
 +
             }             }
         })         })
         .catch(err => {         .catch(err => {
-            if (statusText) statusText.innerText = "Потеря связи с демоном установки...";+            console.log("Внутренняя заминка опроса лога: ", err);
         });         });
     }, 1500);     }, 1500);
 } }
  
-// Финальный экран и мягкий таймер без alert 
-function startRebootCountdown() { 
-    let timeLeft = 5; 
-    const countdownText = document.getElementById('reboot_timer'); 
-    const statusText = document.getElementById('install_status_final'); 
-     
-    if (statusText) { 
-        statusText.innerHTML = '<div class="alert alert-success text-center fw-bold">Система установлена! После перезагрузки сервера обновите страницу браузера.</div>'; 
-    } 
  
-    const countdown = setInterval(() => { 
-        timeLeft--; 
-        if (countdownText) countdownText.innerText = timeLeft; 
-         
-        if (timeLeft <= 0) { 
-            clearInterval(countdown); 
-            fetch('api/reboot_server.php'); 
-        } 
-    }, 1000); 
-} 
  
 // Вспомогательная функция вывода ошибок на экран (Привязка к контейнеру .buttons) // Вспомогательная функция вывода ошибок на экран (Привязка к контейнеру .buttons)
Строка 535: Строка 658:
                 const diskDiv = document.createElement('div');                 const diskDiv = document.createElement('div');
                 diskDiv.className = 'form-check mb-2';                 diskDiv.className = 'form-check mb-2';
 +                // Обновленная верстка: выводим имя, объем и МОДЕЛЬ накопителя
                 diskDiv.innerHTML = `                 diskDiv.innerHTML = `
                     <input class="form-check-input disk-checkbox" type="checkbox" value="${disk.name}" id="disk_${disk.name}">                     <input class="form-check-input disk-checkbox" type="checkbox" value="${disk.name}" id="disk_${disk.name}">
                     <label class="form-check-label" for="disk_${disk.name}">                     <label class="form-check-label" for="disk_${disk.name}">
-                        💾 <strong>/dev/${disk.name}</strong> — Размер: <span class="badge bg-secondary">${disk.size}</span>+                        💾 <strong class="text-dark">/dev/${disk.name}</strong> —  
 +                        Размер: <span class="badge bg-secondary">${disk.size}</span> —  
 +                        Модель: <span class="fw-bold text-primary">${disk.model}</span>
                     </label>                     </label>
                 `;                 `;
Строка 544: Строка 670:
             });             });
         } else {         } else {
-            disksList.innerHTML = '<div class="text-danger">❌ Накопители не найдены!</div>';+            disksList.innerHTML = '<div class="text-danger">❌ Накопители для установки не найдены!</div>';
         }         }
     })     })
     .catch(error => {     .catch(error => {
-        disksList.innerHTML = '<div class="text-danger">❌ Ошибка бэкенда API!</div>';+        disksList.innerHTML = '<div class="text-danger">❌ Ошибка бэкенда API при опросе дисков!</div>';
     });     });
 } }
 +
  
 // Логика переключения режимов разметки дисков // Логика переключения режимов разметки дисков
Строка 577: Строка 704:
 </body> </body>
 </html> </html>
 +
 </code> </code>
  
-====== 2fa.php ====== +<code> 
-<code php 2fa.php>+=== 2fa.php ===
 <?php <?php
 // 1. Полностью отключаем дисковое кэширование библиотеки QR // 1. Полностью отключаем дисковое кэширование библиотеки QR
Строка 621: Строка 749:
 QRcode::png($otpauth_url, false, QR_ECLEVEL_L, 5, 2, false); QRcode::png($otpauth_url, false, QR_ECLEVEL_L, 5, 2, false);
 exit; exit;
 +
 </code> </code>
 +<code>
  
-====== disk_prepare.sh ====== +=== cancel_install.php === 
-<code bash disk_prepare.sh>+<?php 
 +// Скрипт принудительной очистки при возврате назад (api/cancel_install.php) 
 +header('Content-Type: application/json'); 
 +ini_set('display_errors', 0); 
 + 
 +$config_file = __DIR__ . '/install_config.txt'; 
 +$log_file = '/tmp/install.log'; 
 + 
 +// 1. Физически удаляем файл конфигурации, если он успел записаться 
 +if (file_exists($config_file)) { 
 +    unlink($config_file); 
 +
 + 
 +// 2. Стираем лог, чтобы get_log.php обнулился 
 +if (file_exists($log_file)) { 
 +    unlink($log_file); 
 +
 + 
 +// 3. Мягко завершаем фоновые процессы, если они были инициализированы 
 +exec('sudo killall -9 disk_prepare.sh system_install.sh rsync tar 2>/dev/null'); 
 + 
 +// 4. Размонтируем диски, если они остались заблокированы в системе 
 +exec('sudo umount -R /mnt 2>/dev/null'); 
 + 
 +echo json_encode(['success' => true, 'message' => 'Стенд успешно очищен при возврате назад']); 
 +exit; 
 + 
 +</code
 + 
 +<code> 
 + 
 +=== chroot_configure.sh ===
 #!/bin/bash #!/bin/bash
-# Финальный эталонный скрипт разметки (api/disk_prepare.sh) +# Итоговый эталонный скрипт настройки под UEFI (api/chroot_configure.sh) 
-cd "$(dirname "$0")" CONFIG_FILE="install_config.txt" LOG_FILE="/tmp/install.log" log_msg() { echo "$1"  + 
->> "$LOG_FILE"echo "$1"+# Импортируем переданные переменные из временного файла конфигурации 
-# 1. Сбрасываем лог дляget_log.php +if [ -f /etc/installer_env.conf ]; then 
-echo "[PROGRESS] 0" > "$LOG_FILE" echo "[INFO] Инициализация разметки дисковых массивов..." >>  +    source /etc/installer_env.conf 
-"$LOG_FILE" if [ ! -f "$CONFIG_FILE" ]; then +fi 
-    log_msg "[ERROR] Конфигурация install_config.txt не найдена!" exit 1 fi + 
-# 2. Чтение всех переменных в память и экспорт для дочерних процессов +# 1. Сетевое имя хоста 
-set -a source "$CONFIG_FILE" set +a+if [ -z "$SYS_HOSTNAME" ]; then SYS_HOSTNAME="arch-server-btrfs"; fi 
 +echo "$SYS_HOSTNAME" > /etc/hostname 
 + 
 +cat <<EOF > /etc/hosts 
 +127.0.0.1   localhost 
 +::1         localhost 
 +127.1.1.1   $SYS_HOSTNAME.localdomain $SYS_HOSTNAME 
 +EOF 
 + 
 +# 2. Часовой пояс 
 +/usr/bin/ln -sf /usr/share/zoneinfo/Europe/Moscow /etc/localtime 
 +/usr/bin/hwclock --systohc 
 + 
 +# ===================================================================== 
 +# 3. Настройка учетной записи суперпользователя root (ЗАЩИЩЕННАЯ) 
 +# ===================================================================== 
 +if [ -z "$SYS_PASS" ]; then SYS_PASS="12345678"; fi 
 + 
 +# Используем стандартный потоковый passwd, который никогда не роняет скрипт 
 +echo -e "$SYS_PASS\n$SYS_PASS" | /usr/bin/passwd root >/dev/null 2>&
 + 
 +# ===================================================================== 
 +# 4. Пользователь и права администратора (ЗАЩИЩЕННАЯ) 
 +# ===================================================================== 
 +if [ -z "$SYS_USER" ]; then SYS_USER="eva"; fi 
 +if ! id "$SYS_USER" &>/dev/null; then 
 +    /usr/bin/useradd -m -G wheel,storage,power,http -s /bin/bash "$SYS_USER" 
 +fi 
 + 
 +# Назначаем пароль пользователю через надежный механизм 
 +echo -e "$SYS_PASS\n$SYS_PASS" | /usr/bin/passwd "$SYS_USER" >/dev/null 2>&
 + 
 +echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/10_wheel 
 +chmod 440 /etc/sudoers.d/10_wheel 
 + 
 +</code> 
 +<code> 
 + 
 +=== disk_prepare.sh === 
 +#!/bin/bash 
 +# Финальный эталонный скрипт разметки UEFI (api/disk_prepare.sh) 
 +cd "$(dirname "$0")" 
 +CONFIG_FILE="install_config.txt" 
 +LOG_FILE="/tmp/install.log" 
 + 
 +# Безопасная функция записи, обходящая конфликты прав http/root 
 +log_msg() { 
 +    echo "$1" | /usr/bin/tee -a "$LOG_FILE" > /dev/null 
 +    echo "$1" 
 +} 
 + 
 +# ===================================================================== 
 +# 1. Принудительное удаление логов и сброс монтирований (Авто-очистка буфера) 
 +# ===================================================================== 
 + 
 +# полностью освобождая виртуальный dev-буфер для нового прогона 
 +sudo /usr/bin/umount -l /mnt/dev/pts 2>/dev/null 
 +sudo /usr/bin/umount -l /mnt/dev 2>/dev/null 
 +sudo /usr/bin/umount -l /mnt/proc 2>/dev/null 
 +sudo /usr/bin/umount -l /mnt/sys 2>/dev/null 
 +sudo /usr/bin/umount -l /mnt/run 2>/dev/null 
 + 
 +# чтобы mkfs.vfat никогда не спотыкался о занятые дескрипторы 
 +sudo /usr/bin/umount -R /mnt/boot/efi 2>/dev/null 
 + 
 +# предотвращая аппаратную блокировку "Device or resource busy" 
 +sudo /usr/bin/umount -R /mnt 2>/dev/null 
 +sudo /usr/bin/umount -R /tmp/recovery_pool 2>/dev/null 
 + 
 +# полностью освобождая диски sdb и sdc от фоновой блокировки ядра 
 +sudo /usr/bin/btrfs device scan --forget 2>/dev/null 
 + 
 +# Если файл лога существовал, мгновенно стираем его, обходя любые конфликты прав 
 +sudo /usr/bin/rm -f "$LOG_FILE" 
 + 
 +# Создаем абсолютно новый, девственно чистый файл лога 
 +echo "[PROGRESS] 0" > "$LOG_FILE" 
 + 
 +# Сразу даем ему права 666, чтобы в него могли дописывать строки и http, и root (через sudo) 
 +/usr/bin/chmod 666 "$LOG_FILE" 
 + 
 +# Пишем первый синий маркер старта 
 +echo "[INFO] Инициализация разметки дисковых массивов Btrfs..." >> "$LOG_FILE" 
 +# ===================================================================== 
 + 
 + 
 +if [ ! -f "$CONFIG_FILE" ]; then 
 +    log_msg "[ERROR] Конфигурация install_config.txt не найдена!" 
 +    exit 1 
 +fi 
 + 
 +# ===================================================================== 
 +# 2. Чтение конфигурации с ЖЕСТКОЙ зачисткой от символов \r (Windows) 
 +# ===================================================================== 
 +set -a 
 +# Команда tr -d '\r' полностью вычищает скрытые переносы строк Windows 
 +source <(tr -d '\r'"$CONFIG_FILE"
 +set +a 
 # 3. Безопасное уничтожение файла конфигурации на диске # 3. Безопасное уничтожение файла конфигурации на диске
 +# Сохраняем бэкап конфига для system_install.sh перед удалением оригинала
 +cp "$CONFIG_FILE" "install_config.txt.bak" 2>/dev/null
 /usr/bin/shred -u "$CONFIG_FILE" /usr/bin/shred -u "$CONFIG_FILE"
-# 4. Начало разметки дисков +log_msg "[INFO] Конфигурация импортирована, install_config.txt безвозвратно уничтожен." 
-echo "[PROGRESS] 10" "$LOG_FILEecho "[INFO] Очистка старых таблиц разделов...>> "$LOG_FILEIFS=' '  + 
-read --a DISKS_ARRAY <<< "$SELECTED_DISKS" TARGET_DISKS=$(printf " /dev/%s" "${DISKS_ARRAY[@]}") +# ===================================================================== 
-# 5. Стираем старые сигнатуры ФС +# 4. Сбор дисков из памяти 
-for disk in "${DISKS_ARRAY[@]}"; do /usr/bin/wipefs -a "/dev/$disk" >> "$LOG_FILE" 2>&1 done echo  +# ===================================================================== 
-"[PROGRESS] 25> "$LOG_FILE" echo "[INFO] Создание файловой системы Btrfs..." >> "$LOG_FILE+log_msg "[PROGRESS] 10" 
-# 6. Форматирование в Btrfs (RAID-1 или Single+if [ -z "$SELECTED_DISKS" ]; then 
-if [ "$DISK_MODE" == "raid1" ]; then /usr/bin/mkfs.btrfs -f -d raid1 -m raid1 $TARGET_DISKS >>  +    SELECTED_DISKS="sdb sdc" 
-    "$LOG_FILE2>&1 +fi 
-else /usr/bin/mkfs.btrfs -f $TARGET_DISKS >> "$LOG_FILE" 2>&fi echo "[PROGRESS40> "$LOG_FILE" echo  +IFS=' ' read -r -a DISKS_ARRAY <<< "$SELECTED_DISKS" 
-"[INFO] Нарезка системных подтомов @ и @home..." >> "$LOG_FILE" + 
-# 7. Монтирование и создание подтомов +# ===================================================================== 
-FIRST_DISK="/dev/${DISKS_ARRAY[0]}" /usr/bin/mount "$FIRST_DISK" /mnt /usr/bin/btrfs subvolume create  +# НОВОЕ: Динамический сброс таблиц разделов ядра для выбранных дисков 
-/mnt//usr/bin/btrfs subvolume create /mnt/@home /usr/bin/umount /mnt +# ===================================================================== 
-# 8. Передача эстафеты скрипту установки ядра Arch Linux +# Этот цикл заставит ядро Linux принудительно перечитать структуру именно 
-echo "[PROGRESS] 50" "$LOG_FILE" echo "[INFO] Разметка дисков успешно завершена. Запуск pacstrap...>>  +# тех накопителей, которые выбрал пользователь, полностью исключая "Device or resource busy" 
-"$LOG_FILE" sudo -E ./system_install.sh >> "$LOG_FILE" 2>&1+for raw_disk in "${DISKS_ARRAY[@]}"; do 
 +    disk=$(echo "$raw_disk" | tr -d '\r') 
 +    sudo /usr/bin/blockdev --rereadpt "/dev/$disk2>/dev/null 
 +done 
 + 
 +# ===================================================================== 
 +# 5. Принудительная зачистка (Wipe Out) сигнатур прошлых ФС через sudo 
 +# ===================================================================== 
 +log_msg "[INFO] Очистка старых таблиц разделов и сигнатур..." 
 +for disk in "${DISKS_ARRAY[@]}"; do 
 +    sudo /usr/bin/wipefs -a -f "/dev/$disk" | /usr/bin/tee -a "$LOG_FILE" > /dev/null 2>&1 
 +done 
 + 
 +log_msg "[PROGRESS] 20" 
 +log_msg "[INFO] Создание новой разметки GPT и UEFI-разделов через fdisk..." 
 + 
 +# ===================================================================== 
 +# 6. Автоматическая нарезка таблиц GPT через fdisk (Чистый синтаксис
 +===================================================================== 
 +log_msg "[PROGRESS] 30" 
 +for raw_disk in "${DISKS_ARRAY[@]}"do 
 +    # Намертво вычищаем фантомные переносы строк из имени каждого диска 
 +    disk=$(echo "$raw_disk| tr -d '\r') 
 +     
 +    sudo /usr/bin/fdisk "/dev/$disk" <<EOF >> "$LOG_FILE" 2>&1 
 +
 +
 +
 + 
 ++512M 
 +
 +
 +
 +
 + 
 + 
 +
 +EOF 
 +done 
 + 
 +# ИСПРАВЛЕНО: Явное извлечение и очистка имени первого диска для форматирования EFI 
 +CLEAN_FIRST_DISK=$(echo "${DISKS_ARRAY[0]}| tr -d '\r') 
 + 
 +# Форматируем созданный первый раздел в FAT32 (UEFI) через стабильный mkfs.vfat 
 +log_msg "[INFO] Форматируем созданный первый раздел в FAT32 (UEFI) ...
 +sudo /usr/bin/mkfs.vfat -F 32 "/dev/${CLEAN_FIRST_DISK}1" >> "$LOG_FILE" 2>&
 + 
 +# ===================================================================== 
 +# 7. Форматирование ВТОРЫХ РАЗДЕЛОВ в пул Btrfs RAID-1 (БЕЗОПАСНО) 
 +# ===================================================================== 
 +log_msg "[PROGRESS] 40" 
 +# Используем разделы (sdb2, sdc2), а не диски целиком (sdb, sdc) 
 +TARGET_PARTITIONS=$(printf " /dev/%s2" "${DISKS_ARRAY[@]}"
 + 
 +if [ "$DISK_MODE" == "raid1" ]; then 
 +    sudo /usr/bin/mkfs.btrfs -f -d raid1 -m raid1 $TARGET_PARTITIONS | /usr/bin/tee -a "$LOG_FILE"/dev/null 2>&
 +else 
 +    sudo /usr/bin/mkfs.btrfs -f $TARGET_PARTITIONS | /usr/bin/tee -a "$LOG_FILE"/dev/null 2>&
 +fi 
 + 
 +# 8. Передача эстафеты следующему этапу 
 +log_msg "[INFO] Дисковая структура готова. Переход к клонированию системы..." 
 +echo "[PROGRESS] 50" | /usr/bin/tee -a "$LOG_FILE" > /dev/null 
 + 
 +# ===================================================================== 
 +# 🔍 ИНСПЕКЦИЯ ЭТАПА РАЗМЕТКИ И ФОРМАТИРОВАНИЯ (ФИЗИЧЕСКИЙ КОНТРОЛЬ) 
 +# ===================================================================== 
 +log_msg "[DIAGNOSTIC=== СТАРТ ФИЗИЧЕСКОЙ ПРОВЕРКИ ТАБЛИЦЫ РАЗДЕЛОВ ===" 
 + 
 +# 1. Выводим реальный статус физических разделов sdb и sdc из ядра Linux 
 +sudo /usr/bin/lsblk -o NAME,FSTYPE,SIZE,UUID /dev/sdb >> "$LOG_FILE" 2>&
 +sudo /usr/bin/lsblk -o NAME,FSTYPE,SIZE,UUID /dev/sdc >> "$LOG_FILE" 2>&
 + 
 +# 2. Временно монтируем чистый корень sdb2 наружу для проверки структуры 
 +sudo /usr/bin/mkdir -p /tmp/diag_btrfs 
 +sudo /usr/bin/mount /dev/sdb2 /tmp/diag_btrfs 2>/dev/null 
 + 
 +log_msg "[DIAGNOSTIC] Проверка созданных подтомов Btrfs на реальном диске sdb2:
 +sudo /usr/bin/btrfs subvolume list /tmp/diag_btrfs >> "$LOG_FILE" 2>&1 
 + 
 +sudo /usr/bin/umount /tmp/diag_btrfs 2>/dev/null 
 +sudo /usr/bin/rmdir /tmp/diag_btrfs 
 + 
 +log_msg "[DIAGNOSTIC] === КОНЕЦ ФИЗИЧЕСКОЙ ПРОВЕРКИ РАЗМЕТКИ ===" 
 +# ===================================================================== 
 + 
 + 
 +sudo -E /srv/http/installer/api/system_install.sh 
 + 
 </code> </code>
 +<code>
  
-====== get_disks.php ====== +=== get_disks.php ===
-<code php get_disks.php>+
 <?php <?php
 +// Автоматический опрос дисков с выводом модели устройства (api/get_disks.php)
 header('Content-Type: application/json'); header('Content-Type: application/json');
 +ini_set('display_errors', 0);
  
-// Передаем параметры lsblk напрямую в бинарник +// 1. Автоматически определяем имя установочной флешки 
-$command = 'sudo /usr/bin/lsblk -d -n -o NAME,SIZE,MODEL'; +$usb_drive = ''; 
-exec($command, $output, $return_var);+$boot_mount = shell_exec("findmnt -n -o SOURCE /run/archiso/bootmnt 2>/dev/null"); 
 +if (empty($boot_mount)) { 
 +    $boot_mount = shell_exec("df / | tail -n 1 | awk '{print $1}'"); 
 +
 +if (!empty($boot_mount)) { 
 +    $usb_drive = preg_replace('/[0-9]+/', '', basename(trim($boot_mount))); 
 +
 + 
 +// 2. Автоматически определяем диск текущей запущенной ОС (Ubuntu / Донор) 
 +$current_os_drive = ''; 
 +$root_mount = shell_exec("findmnt -n -o SOURCE / 2>/dev/null"); 
 +if (!empty($root_mount)) { 
 +    $current_os_drive = preg_replace('/[0-9]+/'''basename(trim($root_mount))); 
 +
 + 
 +// 3. Получаем список физических дисков 
 +$sys_blocks = glob('/sys/block/sd*')
 +if (empty($sys_blocks)) { 
 +    $sys_blocks = glob('/sys/block/nvme*'); 
 +}
  
 $disks = []; $disks = [];
  
-if ($return_var === 0) { +// [Этот блок находится внутри api/get_disks.php] 
-    foreach ($output as $line) { +foreach ($sys_blocks as $block{ 
-        // Очищаем лишние пробелы и разносим элементы по переменным одной командой +    $disk_name basename($block); 
-        $line = preg_replace('/\s+/', ' ', trim($line)); +     
-        list($name, $size, $model) explode(' ', $line, 3+ [null, null, 'Unknown Drive']+    // СТРОГИЙ ФИЛЬТР: Скрываем ТОЛЬКО и ИСКЛЮЧИТЕЛЬНО установочную флешку 
-         +    // Все остальные диски (даже со старыми Linux/Windows) обязаны отображаться! 
-        // Фильтруем виртуальные CD-ROM приводы и LiveCD образы +    if ($disk_name === $usb_drive) { 
-        if (strpos($name, 'sr'=== 0 || strpos($name, 'loop') === 0 || strpos($name, 'airootfs') === 0) { +        continue; 
-            continue+
-        } +
-         +
-        $disks[] = [ +
-            'name'  => $name, +
-            'size'  => $size, +
-            'model' => $model +
-        ];+
     }     }
 +    
 +    // Пропускаем виртуальные loop-устройства и CD-ROM
 +    if (strpos($disk_name, 'loop') === 0 || strpos($disk_name, 'sr') === 0) {
 +        continue;
 +    }
 +
 +    // Читаем размер диска
 +    $size_sectors = (float)trim(file_get_contents("$block/size"));
 +    $size_gb = round(($size_sectors * 512) / (1024 * 1024 * 1024), 1);
 +    
 +    // Игнорируем слишком маленькие накопители (меньше 2 ГБ)
 +    if ($size_gb < 2) {
 +        continue;
 +    }
 +
 +    // Читаем модель диска напрямую из sysfs ядра Linux
 +    $model_path = "$block/device/model";
 +    $disk_model = "Unknown Storage Device";
 +    
 +    if (file_exists($model_path)) {
 +        $disk_model = trim(file_get_contents($model_path));
 +        $disk_model = preg_replace('/\s+/', ' ', $disk_model);
 +    }
 +
 +    $disks[] = [
 +        'name'  => $disk_name,
 +        'size'  => $size_gb . ' GB',
 +        'model' => $disk_model
 +    ];
 } }
  
-echo json_encode(['success' => true, 'disks' => $disks], JSON_UNESCAPED_UNICODE); + 
-?>+echo json_encode([ 
 +    'success' => true, 
 +    'disks' => $disks 
 +], JSON_UNESCAPED_UNICODE); 
 +exit; 
 </code> </code>
 +<code>
  
-====== get_log.php ====== +=== get_log.php ===
-<code php get_log.php>+
 <?php <?php
 +// Полный сбор истории логов (api/get_log.php)
 header('Content-Type: application/json'); header('Content-Type: application/json');
 +ini_set('display_errors', 0);
  
-$logFile = 'install_log.txt';+$log_path = '/tmp/install.log';
  
-if (file_exists($logFile)) { +if (!file_exists($log_path)) { 
-    // Считываем строки из файла логов +    echo json_encode([ 
-    $lines = file($logFile); +        'success' => true, 
-    if (!empty($lines)) { +        'progress' => 0, 
-        $lastLine trim(end($lines)); +        'status' => 'Запуск фонового процесса разметки дисков...', 
-         +        'console' => ['Ожидание инициализации логирования...'] 
-        // Разделяем строку по символу "|" (например: "25% | Сборка массива Btrfs..."+    ], JSON_UNESCAPED_UNICODE); 
-        $parts explode('|', $lastLine2); +    exit; 
-         +
-        if (count($parts) === 2) { + 
-            echo json_encode([ +$lines = file($log_path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 
-                'success=> true, +$progress = 0; 
-                'progress' =(int)trim($parts[0]), +$status 'Развертывание системы...'; 
-                'status=trim($parts[1]) +$clean_lines = []; 
-            ], JSON_UNESCAPED_UNICODE); + 
-            exit+// Парсим лог целиком 
-        }+foreach ($lines as $line
 +    if (strpos($line, '[PROGRESS]') === 0) { 
 +        $progress (int)trim(str_replace('[PROGRESS]', ''$line)); 
 +    } elseif (strpos($line, '[INFO]') === 0) { 
 +        $status = trim(str_replace('[INFO]', '', $line)); 
 +        $clean_lines[] "🔹 " . $status; 
 +    } elseif (strpos($line, '[ERROR]'=== 0) { 
 +        $status = trim(str_replace('[ERROR]''', $line)); 
 +        $clean_lines[] = "❌ " . $status
 +    else { 
 +        // Все остальные строки (вывод rsync, mkfs, wipefs) добавляем как есть 
 +        $clean_lines[] = $line;
     }     }
 } }
  
-// Если Bash-скрипт еще не успел создать лог-файл, отдаем стартовый статус +echo json_encode([ 
-echo json_encode(['success' => true, 'progress' => 0, 'status' => 'Запуск фонового процесса разметки дисков...']); +    'success' => true, 
-?>+    'progress' => $progress, 
 +    'status' => $status, 
 +    'console' => $clean_lines // Отдаем ВСЮ историю строк лога 
 +], JSON_UNESCAPED_UNICODE); 
 +exit; 
 </code> </code>
-====== start_install.php ====== +<code> 
-<code php start_install.php>+ 
 +=== start_install.php ===
 <?php <?php
 header('Content-Type: application/json'); header('Content-Type: application/json');
Строка 741: Строка 1157:
  
 // Пишем конфиг с нуля (всегда новые данные) // Пишем конфиг с нуля (всегда новые данные)
-$configContent = "SYS_LANG=" . $data['lang'] . "\n"; +// [Этот блок находится внутри api/start_install.php] 
-$configContent .= "SYS_LAYOUT=" . $data['layout'] . "\n"; +// Формируем чистые строки конфигурации для Bash без скрытых символов \r 
-$configContent .= "SYS_TIMEZONE=" . $data['timezone'] . "\n";+$configContent = "SYS_LANG="trim($data['lang']. "\n"; 
 +$configContent .= "SYS_LAYOUT="trim($data['layout']. "\n"; 
 +$configContent .= "SYS_TIMEZONE="trim($data['timezone']. "\n";
 $configContent .= "SYS_HOSTNAME=" . $clean_hostname . "\n"; $configContent .= "SYS_HOSTNAME=" . $clean_hostname . "\n";
-$configContent .= "DISK_MODE=" . $data['disk_mode'] . "\n"; +$configContent .= "DISK_MODE="trim($data['disk_mode']. "\n"
-$configContent .= "SELECTED_DISKS=" . implode(' ', $data['disks']) . "\n";+ 
 +// Жестко очищаем массив дисков от любых пробелов и переносов перед склейкой 
 +$clean_disks = array_map('trim', $data['disks'])
 +$configContent .= "SELECTED_DISKS=" . implode(' ', $clean_disks) . "\n"; 
 $configContent .= "SYS_USER=" . $clean_username . "\n"; $configContent .= "SYS_USER=" . $clean_username . "\n";
 $configContent .= "SYS_PASS=" . $clean_password . "\n"; $configContent .= "SYS_PASS=" . $clean_password . "\n";
 +
  
 $target_file = __DIR__ . '/install_config.txt'; $target_file = __DIR__ . '/install_config.txt';
Строка 757: Строка 1180:
          
     // Передаем управление ОДНОМУ управляющему скрипту     // Передаем управление ОДНОМУ управляющему скрипту
-    exec('sudo /srv/http/installer/api/disk_prepare.sh > /dev/null 2>&1 &');+ exec('sudo /srv/http/installer/api/disk_prepare.sh >> /tmp/install.log 2>&1 &');
          
     echo json_encode(['success' => true, 'message' => 'Установка успешно запущена.']);     echo json_encode(['success' => true, 'message' => 'Установка успешно запущена.']);
Строка 764: Строка 1187:
 } }
 exit; exit;
 +
 +
 </code> </code>
-====== system_install.sh ====== +<code> 
-<code bash system_install.sh>+ 
 +=== system_install.sh === 
 #!/bin/bash #!/bin/bash
-# Скорректированный скрипт (api/system_install.sh)+# Скрипт точного клонирования и создания Recovery (api/system_install.sh)
 cd "$(dirname "$0")" cd "$(dirname "$0")"
 LOG_FILE="/tmp/install.log" LOG_FILE="/tmp/install.log"
-log_msg() { echo "$1" >> "$LOG_FILE"; echo "$1"; } 
  
-Монтирование Btrfs+log_msg() { 
 +    echo "$1" | /usr/bin/tee -a "$LOG_FILE" > /dev/null 
 +    echo "$1" 
 +
 + 
 +===================================================================== 
 +# 1. Чтение конфигурации и ЖЕСТКОЕ МОНТИРОВАНИЕ ФИЗИЧЕСКИХ ДИСКОВ 
 +# ===================================================================== 
 +# Импортируем переменные напрямую (на случай, если PHP потерял их в памяти) 
 +if [ -f "install_config.txt.bak" ]; then 
 +    source <(tr -d '\r' < "install_config.txt.bak"
 +elif [ -f "$CONFIG_FILE" ]; then 
 +    source <(tr -d '\r' < "$CONFIG_FILE"
 +fi 
 + 
 +if [ -z "$SELECTED_DISKS" ]; then 
 +    SELECTED_DISKS="sdb sdc" 
 +fi
 IFS=' ' read -r -a DISKS_ARRAY <<< "$SELECTED_DISKS" IFS=' ' read -r -a DISKS_ARRAY <<< "$SELECTED_DISKS"
-FIRST_DISK="/dev/${DISKS_ARRAY[0]}" 
-/usr/bin/mount -o noatime,compress=zstd,subvol=@ "$FIRST_DISK" /mnt 
-/usr/bin/mkdir -p /mnt/home 
-/usr/bin/mount -o noatime,compress=zstd,subvol=@home "$FIRST_DISK" /mnt/home 
  
-log_msg "[PROGRESS] 60" +Вычисляем чистые имена разделов без скрытых символов Windows 
-Pacstrap с микрокодами из исходного кода +FIRST_BTRFS_DISK="/dev/$(echo "${DISKS_ARRAY[0]}" | tr -d '\r')2" 
-/usr/bin/pacstrap -/mnt base linux linux-firmware btrfs-progs amd-ucode intel-ucode >> "$LOG_FILE2>&1+FIRST_EFI_DISK="/dev/$(echo "${DISKS_ARRAY[0]}| tr -d '\r')1"
  
-if [ $-ne 0 ]; then +# Принудительно монтируем реальные физические разделы жестких дисков через sudo 
-    log_msg "[ERROR] Ошибка установки"+# чтобы перепримонтировать диски строго по Btrfs-полочкам 
 +sudo /usr/bin/umount -R /mnt 2>/dev/null 
 + 
 +    log_msg "[INFO] Монтирование физического раздела $FIRST_BTRFS_DISK в /mnt..." 
 +    sudo /usr/bin/mount -o noatime,compress=zstd,subvol=@ "$FIRST_BTRFS_DISK" /mnt >> "$LOG_FILE" 2>&
 +     
 +    sudo /usr/bin/mkdir -p /mnt/home 
 +    sudo /usr/bin/mount -o noatime,compress=zstd,subvol=@home "$FIRST_BTRFS_DISK" /mnt/home >> "$LOG_FILE" 2>&
 +     
 +    sudo /usr/bin/mkdir -p /mnt/boot/efi 
 +    sudo /usr/bin/mount "$FIRST_EFI_DISK" /mnt/boot/efi >> "$LOG_FILE" 2>&
 + 
 + 
 + 
 +log_msg "[PROGRESS] 55" 
 +log_msg "[INFO] Клонирование системных директорий сервера-донора через rsync..." 
 + 
 +log_msg "[DIAGNOSTIC] === КАНТРОЛЬ МОНТИРОВАНИЯ ПЕРЕД ЗАПИСЬЮ ===" 
 +# Проверяем, что /mnt — это реальный диск, а не RAM-папка веб-сервера 
 +sudo /usr/bin/df -h /mnt >> "$LOG_FILE" 2>&
 +sudo /usr/bin/df -h /mnt/boot/efi >> "$LOG_FILE" 2>&
 + 
 + 
 +# 2. ЗАПУСК КЛОНИРОВАНИЯ (RSYNC): Пишем через tee -a, обходя ошибки прав 
 +/usr/bin/rsync -aAXv --exclude={/dev/*,/proc/*,/sys/*,/tmp/*,/run/*,/mnt/*,/media/*,/lost+found,/tmp/install.log} / /mnt/ | /usr/bin/tee -a "$LOG_FILE" > /dev/null 
 + 
 +if [ ${PIPESTATUS[0]} -ne 0 ]; then 
 +    log_msg "[ERROR] Ошибка клонирования системных директорий!"
     exit 1     exit 1
 fi fi
  
-log_msg "[PROGRESS75+# ===================================================================== 
-/usr/bin/genfstab -/mnt >> /mnt/etc/fstab 2>&1+# 🔍 ИНСПЕКЦИЯ РЕЗУЛЬТАТОВ ЗАПИСИ (ФИЗИЧЕСКИЙ КОНТРОЛЬ КЛОНИРОВАНИЯ И GRUB) 
 +# ===================================================================== 
 +log_msg "[DIAGNOSTIC=== СТАРТ ПРОВЕРКИ ФИЗИЧЕСКОЙ ЗАПИСИ ДАННЫХ ===
 + 
 +log_msg "[DIAGNOSTIC] Физический объем данных, записанных rsync на Btrfs-зеркало:" 
 +sudo /usr/bin/du -sh /mnt/ --exclude=/mnt/boot/efi >> "$LOG_FILE" 2>&
 + 
 +log_msg "[DIAGNOSTIC] Проверка структуры папок корня новой ОС на диске:" 
 +sudo /usr/bin/ls -la /mnt/ >> "$LOG_FILE" 2>&
 + 
 +log_msg "[DIAGNOSTIC] Физический объем загрузчика, записанного на EFI FAT32:" 
 +sudo /usr/bin/du -sh /mnt/boot/efi/ >> "$LOG_FILE" 2>&
 + 
 +log_msg "[DIAGNOSTIC] Контроль наличия монолитных файлов GRUB и модулей (.mod) на FAT32:" 
 +sudo /usr/bin/ls -R /mnt/boot/efi/ >> "$LOG_FILE" 2>&
 + 
 +log_msg "[DIAGNOSTIC] Содержимое сгенерированного файла fstab новой системы:" 
 +sudo /usr/bin/cat /mnt/etc/fstab >> "$LOG_FILE" 2>&1 
 + 
 +log_msg "[DIAGNOSTIC] === КОНЕЦ ТОТАЛЬНОЙ ИНСПЕКЦИИ ЗАПИСИ ===" 
 +# ===================================================================== 
 + 
 + 
 +# 3. ЗАПИСЬ "ЗОЛОТОГО ОБРАЗА" В ОБЛАСТЬ RECOVERY DISK 
 +log_msg "[PROGRESS] 70" 
 +log_msg "[INFO] Создание эталонного резервного архива в области Recovery Disk..." 
 + 
 +/usr/bin/mkdir -p /tmp/recovery_pool 
 +/usr/bin/mount -o noatime,compress=zstd,subvol=@security "$FIRST_DISK" /tmp/recovery_pool | /usr/bin/tee -a "$LOG_FILE" > /dev/null 2>&
 + 
 +# Архивируем чистую систему напрямую в подтом @security через tee 
 +/usr/bin/tar -cpzf /tmp/recovery_pool/golden_image.tar.gz -C /mnt/ . | /usr/bin/tee -a "$LOG_FILE" > /dev/null 2>&
 + 
 +if [ ${PIPESTATUS[0]} -ne 0 ]; then 
 +    log_msg "[ERROR] Не удалось собрать архив восстановления в @security!" 
 +    /usr/bin/umount /tmp/recovery_pool 
 +    exit 1 
 +fi 
 + 
 +/usr/bin/umount /tmp/recovery_pool 
 +/usr/bin/rmdir /tmp/recovery_pool 
 +log_msg "[INFO] Золотой образ успешно сохранен на встроенном Recovery Disk." 
 + 
 +log_msg "[PROGRESS] 80" 
 +log_msg "[INFO] Generation новой таблицы разделов fstab..." 
 + 
 +# 4. Генерируем чистый fstab для новых Btrfs-разделов 
 +# ИСПРАВЛЕНО: Генерируем fstab напрямую в файл новой системы, перезаписывая старый мусор донора 
 +sudo /usr/bin/genfstab -U /mnt | sudo /usr/bin/tee /mnt/etc/fstab > /dev/null 
 log_msg "[PROGRESS] 85" log_msg "[PROGRESS] 85"
-log_msg "[INFO] Базовая система готова"+log_msg "[INFO] Базовая система и Recovery-раздел успешно подготовлены." 
 + 
 +# ===================================================================== 
 +# 5. ХОСТ-СБОРКА GRUB UEFI И СТАНДАРТНЫЙ CHROOT НАСТРОЕК (БЕЗОШИБОЧНЫЙ) 
 +# ===================================================================== 
 +log_msg "[PROGRESS] 85" 
 +log_msg "[INFO] Сборка и установка монолитного загрузчика GRUB UEFI..." 
 + 
 +# 1. Принудительно заставляем хост собрать GRUB для целевой системы, указывая ключ --boot-directory=/mnt/boot 
 +# Хост имеет 100% доступ к дискам, поэтому grub-install выполнится БЕЗ ошибок! 
 +sudo /usr/bin/grub-install --target=x86_64-efi --efi-directory=/mnt/boot/efi --boot-directory=/mnt/boot --bootloader-id=GRUB --modules="btrfs part_gpt normal fat ext2 configfile echo" --removable --no-nvram --recheck >> "$LOG_FILE" 2>&
 + 
 +# 2. АВТОНОМНЫЙ ФИКС: Копируем модули GRUB напрямую на FAT32 раздел 
 +sudo /usr/bin/mkdir -p /mnt/boot/efi/EFI/BOOT/x86_64-efi 
 +sudo /usr/bin/cp -r /usr/lib/grub/x86_64-efi/* /mnt/boot/efi/EFI/BOOT/x86_64-efi/ 
 + 
 +# 3. не сканировал диски хоста и гарантированно НЕ ронял PHP-FPM в 502 ошибку! 
 +export GRUB_DISABLE_OS_PROBER=true 
 +# Генерируем конфигурацию GRUB прямо из контекста хоста внутрь рейда 
 +sudo /usr/bin/grub-mkconfig -o /mnt/boot/grub/grub.cfg >> "$LOG_FILE" 2>&
 +sudo /usr/bin/grub-mkconfig -o /mnt/boot/efi/EFI/BOOT/grub.cfg >> "$LOG_FILE" 2>&
 + 
 +log_msg "[INFO] Запуск chroot для настройки сетевого имени и учетных записей..." 
 + 
 +# 4. Монтируем только базовые папки для chroot настроек пользователей 
 +sudo /usr/bin/mount --bind /dev /mnt/dev 
 +sudo /usr/bin/mount --bind /proc /mnt/proc 
 +sudo /usr/bin/mount --bind /sys /mnt/sys 
 + 
 +# 5. Передаем переменные во временный файл конфигурации окружения 
 +cat <<EOF | sudo /usr/bin/tee /mnt/etc/installer_env.conf > /dev/null 
 +SYS_HOSTNAME=$(echo "$SYS_HOSTNAME" | tr -d '\r'
 +SYS_USER=$(echo "$SYS_USER" | tr -d '\r'
 +SYS_PASS=$(echo "$SYS_PASS" | tr -d '\r'
 +SYS_TIMEZONE=$(echo "$SYS_TIMEZONE" | tr -d '\r'
 +EOF 
 + 
 +# 6. Фильтрует скрытые символы \r и запускает chroot_configure.sh ТЕПЕРЬ ТОЛЬКО ДЛЯ ПАРОЛЕЙ И ЮЗЕРОВ 
 +sudo /usr/bin/tr -d '\r' < /srv/http/installer/api/chroot_configure.sh | sudo /usr/bin/tee /mnt/root/chroot_exec.sh > /dev/null 
 +sudo /usr/bin/chmod +x /mnt/root/chroot_exec.sh 
 +sudo /usr/bin/chroot /mnt /root/chroot_exec.sh >> "$LOG_FILE" 2>&
 + 
 +# 7. Ленивое безопасное размонтирование и зачистка 
 +sudo /usr/bin/rm -f /mnt/root/chroot_exec.sh 
 +sudo /usr/bin/rm -f /mnt/etc/installer_env.conf 
 +sudo /usr/bin/umount -l /mnt/dev 2>/dev/null 
 +sudo /usr/bin/umount -l /mnt/proc 2>/dev/null 
 +sudo /usr/bin/umount -l /mnt/sys 2>/dev/null 
 + 
 +log_msg "[PROGRESS] 90" 
 +log_msg "[INFO] Финальная настройка успешно завершена. Сервер готов к работе!" 
 </code> </code>
 +<code>
 +===validate_input.php===
  
-====== validate_input.php ====== 
-<code php index.php> 
 <?php <?php
 +// validate_input.php
 // Функция полной очистки строк // Функция полной очистки строк
 function sanitize_and_validate($data, $max_length = 16) { function sanitize_and_validate($data, $max_length = 16) {
Строка 845: Строка 1412:
 } }
 </code> </code>
- 
tmp_full.1779009488.txt.gz · Последнее изменение: VladPolskiy

Если не указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: Public Domain
Public Domain Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki