diff --git a/readme.md b/readme.md index 26b9498..c61813d 100644 --- a/readme.md +++ b/readme.md @@ -103,103 +103,7 @@ sudo nano /usr/local/bin/deploy_haproxy.sh Вставьте следующий код: -```bash -#!/usr/bin/env bash -set -Eeuo pipefail - -# ================= КОНФИГУРАЦИЯ ================= -BASE_DIR="/var/lib/depfile" -STATUS_DIR="${BASE_DIR}/status" -CHANGE_FILE="${STATUS_DIR}/change.me" -HASH_FILE="${STATUS_DIR}/sha256.hash" - -DEPLOY_DIR="${BASE_DIR}/deployment" -SUCCESS_FILE="${DEPLOY_DIR}/success.me" - -BACKUP_CFG="/etc/haproxy/backup/haproxy.cfg" -TARGET_CFG="/etc/haproxy/haproxy.cfg" -# =============================================== - -log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" -} - -err() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ОШИБКА] $*" >&2 -} - -# 1. Проверяем наличие триггер-файла change.me -if [ ! -f "${CHANGE_FILE}" ]; then - exit 0 -fi - -log "Обнаружен файл ${CHANGE_FILE}. Запуск процедуры развертывания..." - -# 2. Проверяем наличие файла с хешем -if [ ! -f "${HASH_FILE}" ]; then - err "Файл хеша ${HASH_FILE} не найден. Отмена операции." - exit 1 -fi - -# 3. Проверяем целостность файла change.me по sha256.hash -log "Проверка контрольной суммы SHA-256..." -if ! (cd "${STATUS_DIR}" && sha256sum -c --status "sha256.hash"); then - err "Несовпадение контрольной суммы файла ${CHANGE_FILE}!" - exit 2 -fi -log "Контрольная сумма успешно подтверждена." - -# 4. Проверяем наличие файла резервной копии конфигурации -if [ ! -f "${BACKUP_CFG}" ]; then - err "Файл резервной копии не найден: ${BACKUP_CFG}" - exit 3 -fi - -# 5. Валидация синтаксиса резервного конфига перед его применением -log "Валидация синтаксиса конфигурации из бэкапа..." -if ! haproxy -c -f "${BACKUP_CFG}" > /dev/null 2>&1; then - err "Резервная конфигурация содержит синтаксические ошибки. Отмена применения." - haproxy -c -f "${BACKUP_CFG}" || true - exit 4 -fi -log "Синтаксис резервного конфига корректен." - -# 6. Замена рабочей конфигурации -log "Копирование конфигурации в ${TARGET_CFG}..." -cp -f "${BACKUP_CFG}" "${TARGET_CFG}" - -# Повторный контрольный тест целевого файла -if ! haproxy -c -f "${TARGET_CFG}" > /dev/null 2>&1; then - err "Критическая ошибка: целевой файл после копирования не прошел валидацию!" - exit 5 -fi - -# 7. Перезапуск службы haproxy -log "Перезапуск службы haproxy..." -if ! systemctl restart haproxy; then - err "Не удалось перезапустить сервис haproxy!" - exit 6 -fi -log "Сервис haproxy успешно перезапущен." - -# 8. Очистка каталога status -log "Удаление директории ${STATUS_DIR}..." -rm -rf "${STATUS_DIR}" - -# 9. Формирование каталога deployment и файла success.me -mkdir -p "${DEPLOY_DIR}" -chown depfile:depfile "${DEPLOY_DIR}" -CURRENT_DATETIME=$(date '+%Y-%m-%d %H:%M:%S') - -# Перезаписываем файл актуальной датой и временем -echo "deploment success {${CURRENT_DATETIME}}" > "${SUCCESS_FILE}" -chown depfile:depfile "${SUCCESS_FILE}" -log "Зафиксирован статус в ${SUCCESS_FILE}." - -log "Процесс успешно завершен." -exit 0 - -``` +[deploy_haproxy.sh](deploy_haproxy.sh) Назначьте права на запуск: @@ -272,255 +176,9 @@ depfile ## 4. Клиентское приложение C# (.NET Framework 4.8) -### 4.1. Исходный код (`Program.cs`) +### 4.1. Исходный код +[Program.cs](Program.cs) -```csharp -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Text; - -namespace SshStatusUpdater -{ - internal class Program - { - // ================= НАСТРОЙКИ ПОДКЛЮЧЕНИЯ ================= - private const string RemoteUser = "depfile"; // Логин пользователя на сервере - private const string RemoteHost = "10.228.48.110"; // IP-адрес или доменное имя сервера - private const int RemotePort = 22; // Порт SSH-сервера - - // Путь к приватному ключу по умолчанию (если не задан аргументом) - private const string DefaultKeyPath = @"D:\sys\C-Sharp\deployfile\key\test_key.key"; - // ======================================================== - - static int Main(string[] args) - { - // 1. Определение пути к ключу (аргумент командной строки либо константа по умолчанию) - string rawKeyPath = (args.Length > 0 && !string.IsNullOrWhiteSpace(args[0])) - ? args[0].Trim('"') - : DefaultKeyPath; - - string keyPath = Environment.ExpandEnvironmentVariables(rawKeyPath); - - if (!File.Exists(keyPath)) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Ошибка: Файл ключа не найден по указанному пути: {keyPath}"); - Console.ResetColor(); - return 2; - } - - Console.WriteLine($"Используется ключ: {keyPath}"); - - // 2. Проверка доступности OpenSSH клиента (с возможностью автоустановки) - string sshExecutable = EnsureOpenSshAvailable(); - if (string.IsNullOrEmpty(sshExecutable)) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("\n[ОШИБКА] Клиент OpenSSH не найден в системе и не удалось выполнить его установку."); - Console.WriteLine("Для работы приложения необходимо установить компонент OpenSSH вручную."); - Console.WriteLine("Откройте PowerShell от имени Администратора и выполните:"); - Console.WriteLine(" Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0"); - Console.ResetColor(); - return 4; - } - - // 3. Формирование случайных данных и удаленной команды - string randomContent = GenerateRandomPayload(linesCount: 5, charsPerLine: 32); - - // Так как домашний каталог /var/lib/depfile, команда выполняется относительно него - string remoteCommand = "mkdir -p status && cat > status/change.me && (cd status && sha256sum change.me > sha256.hash)"; - - string sshArgs = $"-i \"{keyPath}\" -p {RemotePort} -o BatchMode=yes -o StrictHostKeyChecking=accept-new {RemoteUser}@{RemoteHost} \"{remoteCommand}\""; - - ProcessStartInfo psi = new ProcessStartInfo - { - FileName = sshExecutable, - Arguments = sshArgs, - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - - Console.WriteLine($"Подключение к {RemoteUser}@{RemoteHost}:{RemotePort}..."); - - try - { - using (Process proc = new Process { StartInfo = psi }) - { - proc.Start(); - - // Передаем сгенерированный текст в STDIN удаленного процесса ssh - using (StreamWriter sw = new StreamWriter(proc.StandardInput.BaseStream, new UTF8Encoding(false))) - { - sw.Write(randomContent); - } - - string stdOut = proc.StandardOutput.ReadToEnd(); - string stdErr = proc.StandardError.ReadToEnd(); - - proc.WaitForExit(); - - if (proc.ExitCode == 0) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("Файл status/change.me и хеш status/sha256.hash успешно созданы на сервере."); - Console.ResetColor(); - return 0; - } - else - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Ошибка выполнения SSH (Код: {proc.ExitCode}):"); - if (!string.IsNullOrWhiteSpace(stdErr)) - Console.WriteLine(stdErr.Trim()); - if (!string.IsNullOrWhiteSpace(stdOut)) - Console.WriteLine(stdOut.Trim()); - Console.ResetColor(); - return proc.ExitCode; - } - } - } - catch (Exception ex) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Критическая ошибка выполнения SSH: {ex.Message}"); - Console.ResetColor(); - return 3; - } - } - - private static string EnsureOpenSshAvailable() - { - string path = FindSshPath(); - if (!string.IsNullOrEmpty(path)) - return path; - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Клиент OpenSSH не обнаружен. Попытка автоматической установки компонента Windows..."); - Console.ResetColor(); - - bool installed = TryInstallOpenSsh(); - if (installed) - { - path = FindSshPath(); - if (!string.IsNullOrEmpty(path)) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("Клиент OpenSSH успешно установлен."); - Console.ResetColor(); - return path; - } - } - - return null; - } - - private static string FindSshPath() - { - string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); - - string[] candidates = new[] - { - Path.Combine(windir, "Sysnative", "OpenSSH", "ssh.exe"), - Path.Combine(windir, "System32", "OpenSSH", "ssh.exe"), - @"C:\Windows\System32\OpenSSH\ssh.exe" - }; - - foreach (var candidate in candidates) - { - if (File.Exists(candidate)) - return candidate; - } - - try - { - ProcessStartInfo testPsi = new ProcessStartInfo - { - FileName = "ssh", - Arguments = "-V", - UseShellExecute = false, - RedirectStandardError = true, - RedirectStandardOutput = true, - CreateNoWindow = true - }; - - using (var testProc = Process.Start(testPsi)) - { - testProc?.WaitForExit(1000); - return "ssh"; - } - } - catch - { - } - - return null; - } - - private static string TryInstallOpenSsh() - { - try - { - ProcessStartInfo installPsi = new ProcessStartInfo - { - FileName = "powershell.exe", - Arguments = "-NoProfile -ExecutionPolicy Bypass -Command \"Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0\"", - UseShellExecute = true, - Verb = "runas", - WindowStyle = ProcessWindowStyle.Normal - }; - - using (Process installProc = Process.Start(installPsi)) - { - if (installProc == null) - return false; - - installProc.WaitForExit(); - return installProc.ExitCode == 0; - } - } - catch (Win32Exception) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("Запрос прав Администратора был отклонён пользователем."); - Console.ResetColor(); - return false; - } - catch (Exception ex) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"Не удалось запустить автоматическую установку: {ex.Message}"); - Console.ResetColor(); - return false; - } - } - - private static string GenerateRandomPayload(int linesCount, int charsPerLine) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(); - Random rng = new Random(); - - for (int i = 0; i < linesCount; i++) - { - char[] buffer = new char[charsPerLine]; - for (int j = 0; j < charsPerLine; j++) - { - buffer[j] = chars[rng.Next(chars.Length)]; - } - sb.AppendLine(new string(buffer)); - } - - return sb.ToString(); - } - } -} - -``` ### 4.2. Сборка и создание ярлыка на рабочем столе