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

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


software:linux_server_iso:installer:index.html

index.html

index.html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Мастер установки Сервера</title>
    <!-- Подключаем чистый современный стиль -->
    <link rel="stylesheet" href="assets/bootstrap.min.css">
    <style>
        body { background: #f4f6f9; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
        .wizard-card { background: white; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.05); width: 650px; min-height: 450px; display: flex; flex-direction: column; justify-content: space-between; padding: 40px; }
        .step { display: none; }
        .step.active { display: block; }
        .step-header { border-bottom: 2px solid #efefef; padding-bottom: 15px; margin-bottom: 25px; }
    </style>
</head>
<body>
 
<div class="wizard-card">
    <div class="wizard-content">
 
        <!-- 1.2.1 Окно приветствия -->
        <div id="step-1" class="step active">
            <div class="step-header"><h4>Шаг 1: Приветствие</h4></div>
            <p class="lead">Добро пожаловать в веб-инсталлятор вашего нового сервера!</p>
            <p>Этот мастер поможет развернуть и настроить операционную систему без подключения монитора к серверу. Все действия выполняются удаленно через этот браузер.</p>
        </div>
 
        <!-- 1.2.2 Выбор языка -->
        <div id="step-2" class="step">
            <div class="step-header"><h4>Шаг 2: Язык системы</h4></div>
            <label class="form-label">Выберите основной язык для устанавливаемой ОС:</label>
            <select id="sys_lang" class="form-select form-select-lg">
                <option value="ru_RU.UTF-8">Русский (ru_RU)</option>
                <option value="en_US.UTF-8">English (en_US)</option>
            </select>
        </div>
 
        <!-- 1.2.3 Выбор раскладки клавиатуры -->
        <div id="step-3" class="step">
            <div class="step-header"><h4>Шаг 3: Раскладка клавиатуры</h4></div>
            <label class="form-label">Выберите раскладку клавиатуры по умолчанию:</label>
            <select id="sys_layout" class="form-select form-select-lg">
                <option value="ru">Русская (ru)</option>
                <option value="us">Английская (us)</option>
            </select>
        </div>
 
        <!-- 1.2.4 Выбор часового пояса -->
        <div id="step-4" class="step">
            <div class="step-header"><h4>Шаг 4: Часовой пояс</h4></div>
            <label class="form-label">Укажите ваш часовой пояс:</label>
            <select id="sys_timezone" class="form-select form-select-lg">
                <option value="Europe/Moscow">Москва (GMT+3)</option>
                <option value="Europe/Kaliningrad">Калининград (GMT+2)</option>
                <option value="Asia/Yekaterinburg">Екатеринбург (GMT+5)</option>
                <option value="UTC">UTC (Универсальное время)</option>
            </select>
        </div>
 
        <!-- 1.2.5 Имя компьютера -->
        <div id="step-5" class="step">
			<div class="step-header"><h4>Шаг 5: Сетевое имя (Hostname)</h4></div>
			<label class="form-label">Введите имя вашего будущего сервера:</label>
			<input type="text" id="sys_hostname" class="form-control form-control-lg" value="my-cool-server" oninput="validateHostname(this)">
			<div class="form-text text-danger">Разрешена только латиница (a-z), цифры и дефис.</div>
		</div>
 
 
                <!-- 1.2.6 Разбивка диска и RAID -->
        <div id="step-6" class="step">
            <div class="step-header"><h4>Шаг 6: Разметка накопителей</h4></div>
            <label class="form-label">Выберите конфигурацию дисковой подсистемы:</label>
            <div class="form-check mb-3">
                <input class="form-check-input" type="radio" name="disk_mode" id="disk_default" value="default" checked>
                <label class="form-check-label" for="disk_default">
                    <strong>Режим по умолчанию:</strong> Система + 25GB под установку, остальное в резерв под домашнюю папку пользователя
                </label>
            </div>
            <div class="form-check mb-4">
                <input class="form-check-input" type="radio" name="disk_mode" id="disk_raid" value="raid1">
                <label class="form-check-label" for="disk_raid">
                    <strong>Программный RAID-1 (Зеркало):</strong> Объединить два диска для отказоустойчивости
                </label>
            </div>
 
            <!-- ВОТ ЭТОТ БЛОК КРИТИЧЕСКИ НЕОБХОДИМ ДЛЯ ИСПРАВЛЕНИЯ ОШИБКИ 444 -->
            <div id="disks_selection_zone" class="p-3 bg-light border rounded mb-4" style="display: none;">
                <h6>Доступные физические диски для построения RAID-1 массивов:</h6>
                <div id="disks_list" class="mt-2">
                    <div class="text-muted small">Опрос накопителей контроллером...</div>
                </div>
                <div class="form-text text-muted mt-2">Для активации зеркалирования (RAID-1) выберите ровно два одинаковых накопителя.</div>
            </div>
 
            <div class="alert alert-danger mt-4 d-flex align-items-center">
                <div class="me-3" style="font-size: 24px;">⚠️</div>
                <div>
                    <strong>ВНИМАНИЕ:</strong> Все существующие данные (включая Windows или старые ОС) на выбранных накопителях будут <strong>полностью и безвозвратно удалены</strong>. Разделы будут переформатированы!
                </div>
            </div>
            <div class="form-check mb-3">
                <input class="form-check-input border-danger" type="checkbox" id="confirm_erase" onchange="toggleNextButton()">
                <label class="form-check-label text-danger fw-bold" for="confirm_erase">
                    Я понимаю риски. Подтверждаю полное уничтожение данных и форматирование дисков.
                </label>
            </div>
        </div>
 
 
		        <!-- 1.2.7 Создание пользователя -->
        <div id="step-7" class="step">
            <div class="step-header"><h4>Шаг 7: Учетная запись администратора</h4></div>
            <div class="mb-3">
                <label class="form-label">Имя пользователя (Логин):</label>
                <input type="text" id="username" class="form-control" value="eva" oninput="validateUsername(this)" placeholder="только маленькие латинские буквы">
                <div class="form-text text-muted">Используйте исключительно маленькие английские буквы (a-z).</div>
            </div>
            <div class="row mb-3">
                <div class="col">
                    <label class="form-label">Пароль:</label>
                    <input type="password" id="password" class="form-control" oninput="this.value = this.value.replace(/[а-яА-ЯёЁ]/g, ''); checkPasswordStrength(this.value)">
                    <div class="form-text text-danger fw-bold" style="font-size: 13px;">⚠️ Ввод строго на АНГЛИЙСКОЙ раскладке! Русский язык запрещен.</div>
                    <div class="progress mt-2" style="height: 6px;">
                        <div id="pass_progress" class="progress-bar bg-danger" style="width: 0%"></div>
                    </div>
                    <div id="pass_status" class="small text-muted mt-1">Сложность: слишком слабый</div>
                </div>
                <div class="col">
                    <label class="form-label">Подтверждение пароля:</label>
                    <input type="password" id="password_confirm" class="form-control" oninput="this.value = this.value.replace(/[а-яА-ЯёЁ]/g, ''); validatePasswordMatch()">
                    <div id="match_status" class="small text-danger mt-1"></div>
                </div>
            </div>
 
            <div class="p-3 bg-light border rounded mb-3">
                <h6 class="mb-2" style="font-size: 14px;">Критерии надежности пароля:</h6>
                <ul class="password-rules-list mb-0">
                    <li id="rule_len" class="text-danger">❌ Не менее 8 symbols</li>
                    <li id="rule_upper" class="text-danger">❌ Минимум одна заглавная буква (A-Z)</li>
                    <li id="rule_number" class="text-danger">❌ Минимум одна цифра (0-9)</li>
                    <li id="rule_symbol" class="text-danger">❌ Минимум один спецсимвол (@, #, $, !)</li>
                </ul>
            </div>
        </div>
 
        <!-- НОВЫЙ ШАГ 8: Окно Двухфакторной аутентификации (2FA) -->
        <div id="step-8" class="step">
            <div class="step-header"><h4>Шаг 8: Безопасность аккаунта (2FA)</h4></div>
            <div class="p-3 bg-light border rounded">
                <h5>Настройка Двухфакторной аутентификации</h5>
                <p class="small text-muted mb-2">Отсканируйте этот QR-код мобильным приложением перед началом развертывания системы:</p>
 
                <div class="mb-3 small text-secondary">
                    <strong>Поддерживаемые приложения на телефоне:</strong>
                    <ul class="mb-2 mt-1 ps-3">
                        <li>Google Authenticator (iOS / Android)</li>
                        <li>Yandex Key / Яндекс Ключ</li>
                        <li>Microsoft Authenticator</li>
                        <li>2FAS / Aegis / Икс-Ключ</li>
                    </ul>
                    <span class="text-warning fw-bold">⚠️ Внимание:</span> Сохраните этот аккаунт в приложении на телефоне. Код потребуется при каждом входе на сервер.
                </div>
 
                <div id="qrcode_container" class="text-center py-2">
                    <img id="qr-image" 
						 src="about:blank" 
						 onload="if(this.src=='about:blank') { this.src='api/2fa.php?username=' + encodeURIComponent(document.getElementById('username').value) + '&hostname=' + encodeURIComponent(document.getElementById('sys_hostname').value); }" 
						 alt="QR-код для 2FA" 
						 class="img-fluid">
				<!-- Временный вывод для тестирования шага 8 -->
				<div id="qr-debug-zone" class="mt-3 p-3 bg-light border rounded small text-muted text-start" style="font-family: monospace;">
					<strong>Отладка перед отправкой в API:</strong><br>
					Логин пользователя: <span id="debug-qr-user" class="fw-bold text-primary">ожидание...</span><br>
					Имя сервера (Host): <span id="debug-qr-host" class="fw-bold text-primary">ожидание...</span>
				</div>
				<!-- конец Временный вывод для тестирования шага 8 -->
 
				</div>
            </div>
        </div>
 
        <!-- Теперь это ШАГ 9: Процесс установки -->
        <div id="step-9" class="step">
            <div class="step-header"><h4>Шаг 9: Выполнение установки</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>
 
        <!-- Теперь это ШАГ 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>
 
 
 
    </div>
 
    <!-- Кнопки управления шагами -->
    <div class="buttons d-flex justify-content-between mt-4 border-top pt-3">
        <button id="btn-prev" class="btn btn-outline-secondary px-4" onclick="changeStep(-1)" disabled>Назад</button>
        <button id="btn-next" class="btn btn-primary px-4" onclick="changeStep(1)">Далее</button>
    </div>
</div>
 
<script>
let currentStep = 1;
const totalSteps = 10;
 
// Флаги валидации для учетной записи
let isPasswordValid = false;
let isPasswordMatching = false;
 
// Контроль блокировки кнопки "Далее" на Шаге 6 (Диски)
function toggleNextButton() {
    if (currentStep === 6) {
        const confirmed = document.getElementById('confirm_erase').checked;
        const diskMode = document.querySelector('input[name="disk_mode"]:checked').value;
        let isDiskSelectionOk = true;
 
        if (diskMode === 'raid1') {
            const selectedDisksCount = document.querySelectorAll('.disk-checkbox:checked').length;
            isDiskSelectionOk = (selectedDisksCount === 2); // Строго 2 диска для RAID-1
        }
        document.getElementById('btn-next').disabled = !(confirmed && isDiskSelectionOk);
    }
}
 
function changeStep(direction) {
    // Скрываем блок ошибок перед проверкой
    const errorBlock = document.getElementById('error-message-zone');
    if (errorBlock) errorBlock.style.display = 'none';
 
    // Подгрузка доступных дисков при переходе с 5 на 6 шаг
    if (currentStep === 5 && direction === 1) {
        loadSystemDisks();
    }
 
    // ТРИГГЕР: Переход с 7 на 8 шаг (Безопасное обновление QR)
    if (direction === 1 && currentStep === 7) {
        if (!isPasswordValid || !isPasswordMatching) {
            showInlineError('Пароль не соответствует требованиям или не совпадает!');
            return;
        }
 
        // Забираем данные и обновляем QR-код
        try {
            const currentHost = document.getElementById('sys_hostname').value.trim();
            const currentUser = document.getElementById('username').value.trim();
 
            const dbgUser = document.getElementById('debug-qr-user');
            const dbgHost = document.getElementById('debug-qr-host');
            if (dbgUser) dbgUser.innerText = currentUser;
            if (dbgHost) dbgHost.innerText = currentHost;
 
            // ИСПРАВЛЕНО: Точный поиск картинки по ID
            const qrImg = document.getElementById('qr-image');
            if (qrImg) {
                qrImg.src = `api/2fa.php?username=${encodeURIComponent(currentUser)}&hostname=${encodeURIComponent(currentHost)}`;
            }
        } catch (e) {
            console.log("Ошибка обновления элементов Шага 8: ", e);
        }
    }
 
    // Действия при нажатии "Далее" на Шаге 8 (Старт установки)
    if (direction === 1 && currentStep === 8) {
        let selectedDisks = [];
        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');
            selectedDisks.push(firstDiskInput ? firstDiskInput.value : 'sda');
        }
 
        // Сбор Payload напрямую из полей (СВЕЖИЕ ДАННЫЕ)
        const payload = {
            lang: document.getElementById('sys_lang').value,
            layout: document.getElementById('sys_layout').value,
            timezone: document.getElementById('sys_timezone').value,
            hostname: document.getElementById('sys_hostname').value.trim(),
            disk_mode: diskMode,
            disks: selectedDisks,
            username: document.getElementById('username').value.trim(),
            password: document.getElementById('password').value.trim()
        };
 
        // ПРИНУДИТЕЛЬНЫЙ ЗАПУСК ОТПРАВКИ НА СЕРВЕР
        fetch('api/start_install.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        })
        .then(res => res.json())
        .then(result => {
            if (result && result.success) {
                document.getElementById('step-8').classList.remove('active');
                currentStep = 9; // <--- Корректный переход
                document.getElementById('step-9').classList.add('active');
 
                document.getElementById('btn-prev').style.display = 'none';
                document.getElementById('btn-next').style.display = 'none';
 
                startInstallationSimulation();
            } else {
                showInlineError('Ошибка сервера: ' + (result && result.message ? result.message : 'Неизвестный ответ'));
            }
        })
        .catch(err => {
            showInlineError('Ошибка сети: сервер установки недоступен.');
        });
        return; // Ждем ответа fetch, блокируем линейный переход
    }
 
    // Линейный переход для остальных шагов
    document.getElementById(`step-${currentStep}`).classList.remove('active');
    currentStep += direction;
    document.getElementById(`step-${currentStep}`).classList.add('active');
 
    document.getElementById('btn-prev').disabled = (currentStep === 1 || currentStep >= 9);
    document.getElementById('btn-next').disabled = (currentStep >= 9);
 
    if (currentStep === 6) {
        toggleNextButton();
    }
}
 
 
 
 
 
// Валидаторы ввода с ограничением до 16 символов
function validateHostname(input) {
    input.value = input.value.toLowerCase().replace(/[^a-z0-9-]/g, '').substring(0, 16);
}
 
// Изменено: разрешаем латиницу и цифры для соответствия бэкенду
function validateUsername(input) {
    input.value = input.value.toLowerCase().replace(/[^a-z0-9]/g, '').substring(0, 16);
}
 
// Запрет пробелов и ограничение длины в самом поле пароля
const passwordInputField = document.getElementById('password');
if (passwordInputField) {
    passwordInputField.addEventListener('input', function() {
        this.value = this.value.replace(/\s+/g, '').substring(0, 16);
    });
    // Беззвучный запрет вставки
    ['paste', 'drop'].forEach(e => {
        passwordInputField.addEventListener(e, (ev) => ev.preventDefault());
    });
}
 
// Проверка силы пароля
function checkPasswordStrength(password) {
    const rules = {
        len: password.length >= 8,
        upper: /[A-Z]/.test(password),
        number: /[0-9]/.test(password),
        symbol: /[@$!%*?&#]/.test(password)
    };
 
    updateRuleVisual('rule_len', rules.len, "Не менее 8 символов");
    updateRuleVisual('rule_upper', rules.upper, "Минимум одна заглавная буква (A-Z)");
    updateRuleVisual('rule_number', rules.number, "Минимум одна цифра (0-9)");
    updateRuleVisual('rule_symbol', rules.symbol, "Минимум один спецсимвол (@, #, $, !)");
 
    let score = Object.values(rules).filter(Boolean).length;
    const progress = document.getElementById('pass_progress');
    const status = document.getElementById('pass_status');
 
    if (progress) {
        progress.className = "progress-bar ";
        if (score <= 1) {
            progress.style.width = "25%"; progress.classList.add("bg-danger");
            if (status) status.innerText = "Сложность: слишком слабый"; isPasswordValid = false;
        } else if (score === 2) {
            progress.style.width = "50%"; progress.classList.add("bg-warning");
            if (status) status.innerText = "Сложность: средний"; isPasswordValid = false;
        } else if (score === 3) {
            progress.style.width = "75%"; progress.classList.add("bg-info");
            if (status) status.innerText = "Сложность: хороший"; isPasswordValid = false;
        } else if (score === 4) {
            progress.style.width = "100%"; progress.classList.add("bg-success");
            if (status) status.innerText = "Сложность: отличный (безопасный)"; isPasswordValid = true;
        }
    }
    validatePasswordMatch();
}
 
function updateRuleVisual(elementId, isValid, text) {
    const el = document.getElementById(elementId);
    if (el) {
        el.innerText = (isValid ? "✅ " : "❌ ") + text;
        el.className = isValid ? "text-success fw-bold" : "text-danger";
    }
}
 
function validatePasswordMatch() {
    const passInput = document.getElementById('password');
    const confirmInput = document.getElementById('password_confirm');
    const matchStatus = document.getElementById('match_status');
 
    if (!passInput || !confirmInput || !matchStatus) return;
 
    const pass = passInput.value;
    const confirm = confirmInput.value;
 
    if (!confirm) { matchStatus.innerText = ""; isPasswordMatching = false; return; }
 
    if (pass === confirm) {
        matchStatus.innerText = "✅ Пароли совпадают";
        matchStatus.className = "small text-success fw-bold";
        isPasswordMatching = true;
    } else {
        matchStatus.innerText = "❌ Пароли не совпадают";
        matchStatus.className = "small text-danger";
        isPasswordMatching = false;
    }
}
 
// Мониторинг лога бэкенда
function startInstallationSimulation() {
    const progressBar = document.getElementById('install_progress');
    const statusText = document.getElementById('install_status');
    const timerText = document.getElementById('install_timer');
    let elapsedSeconds = 0;
 
    const durationTimer = setInterval(() => {
        elapsedSeconds++;
        if (timerText) timerText.innerText = elapsedSeconds + ' сек.';
    }, 1000);
 
    const logWatcher = setInterval(() => {
        fetch('api/get_log.php')
        .then(res => res.json())
        .then(data => {
            if (data && data.success) {
                if (progressBar) {
                    progressBar.style.width = data.progress + '%';
                    progressBar.innerText = data.progress + '%';
                }
                if (statusText) statusText.innerText = data.status;
 
                // Переход на финал при 90% или завершении скриптов
                if (data.progress >= 90) {
                    clearInterval(logWatcher);
                    clearInterval(durationTimer);
 
                    document.getElementById('step-9').classList.remove('active');
                    currentStep = 10;
                    document.getElementById('step-10').classList.add('active');
 
                    // Полностью скрываем кнопки управления на Шаге 10
                    document.getElementById('btn-prev').style.display = 'none';
                    document.getElementById('btn-next').style.display = 'none';
 
                    startRebootCountdown();
                }
            }
        })
        .catch(err => {
            if (statusText) statusText.innerText = "Потеря связи с демоном установки...";
        });
    }, 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)
function showInlineError(message) {
    let errorZone = document.getElementById('error-message-zone');
    if (!errorZone) {
        errorZone = document.createElement('div');
        errorZone.id = 'error-message-zone';
        errorZone.className = 'alert alert-danger my-2 text-center';
 
        const buttonsContainer = document.querySelector('.buttons');
        const prevBtn = document.getElementById('btn-prev');
        if (buttonsContainer && prevBtn) {
            buttonsContainer.insertBefore(errorZone, prevBtn);
        } else {
            const card = document.querySelector('.wizard-card') || document.body;
            card.appendChild(errorZone);
        }
    }
    errorZone.innerText = message;
    errorZone.style.display = 'block';
}
 
function loadSystemDisks() {
    const disksList = document.getElementById('disks_list');
    if (!disksList) return;
    disksList.innerHTML = '<div class="spinner-border spinner-border-sm text-primary"></div> Опрашиваю дисковую подсистему...';
 
    fetch('api/get_disks.php')
    .then(response => response.json())
    .then(data => {
        if (data && data.success && data.disks.length > 0) {
            disksList.innerHTML = '';
            data.disks.forEach(disk => {
                const diskDiv = document.createElement('div');
                diskDiv.className = 'form-check mb-2';
                diskDiv.innerHTML = `
                    <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}">
                        💾 <strong>/dev/${disk.name}</strong> — Размер: <span class="badge bg-secondary">${disk.size}</span>
                    </label>
                `;
                disksList.appendChild(diskDiv);
            });
        } else {
            disksList.innerHTML = '<div class="text-danger">❌ Накопители не найдены!</div>';
        }
    })
    .catch(error => {
        disksList.innerHTML = '<div class="text-danger">❌ Ошибка бэкенда API!</div>';
    });
}
 
// Логика переключения режимов разметки дисков
document.addEventListener('change', function(e) {
    if (e.target && e.target.name === 'disk_mode') {
        const zone = document.getElementById('disks_selection_zone');
        if (zone) zone.style.display = (e.target.value === 'raid1') ? 'block' : 'none';
    }
});
 
document.addEventListener('click', function(e) {
    if (e.target && e.target.classList.contains('disk-checkbox')) {
        toggleNextButton();
    }
});
 
// Защита от возврата кнопкой Back браузера
history.pushState(null, null, location.href);
window.addEventListener('popstate', function () {
    history.pushState(null, null, location.href);
    showInlineError('Для навигации используйте кнопки мастера установки.');
});
</script>
 
 
</body>
</html>
Только авторизованные участники могут оставлять комментарии.
software/linux_server_iso/installer/index.html.txt · Последнее изменение: 127.0.0.1

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