From c6e635a5c403993b0881ba059114283f8a58dc9b Mon Sep 17 00:00:00 2001 From: host Date: Sun, 12 Apr 2026 00:32:20 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README (1).md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 README (1).md diff --git a/README (1).md b/README (1).md new file mode 100644 index 0000000..c578397 --- /dev/null +++ b/README (1).md @@ -0,0 +1,311 @@ +### ASSISTANT +# 🔄 SSL Sync: Nginx Proxy Manager → Home Assistant + +> 📖 **Описание** +> Автоматическая синхронизация Let's Encrypt сертификатов с сервера **Nginx Proxy Manager** в **Home Assistant** через SSH и cron. +> Оптимизировано для работы через туннель Keenetic, включает проверку целостности, дедупликацию (перезапуск только при реальном изменении), ротацию лога и безопасный перезапуск HA. + +--- + +## 🌐 Архитектура решения +| Компонент | IP / Путь | Роль | +|-----------|-----------|------| +| **Nginx Proxy Manager** | `192.168.1.2` | Хост-сервер, хранит сертификаты | +| **Home Assistant** | `192.168.31.10` | VM-клиент, принимает сертификаты в `/ssl/` | +| **Сеть** | Туннель Keenetic | Обеспечивает маршрутизацию между подсетями `192.168.1.0/24` и `192.168.31.0/24` | +| **Цель** | `/opt/npm/letsencrypt/live/npm-44/` → `root@192.168.31.10:/ssl/` | Ежедневная авто-синхронизация | + +--- + +## ✅ Требования +- Доступ к **хосту NPM** по SSH (скрипт выполняется в базовой ОС, а не в контейнере) +- SSH-доступ к HA от пользователя `root` (включен в HA OS или через Add-on `SSH & Web Terminal`) +- Установленные `openssh-client`, `cron`, `bash`, `openssl` на хосте NPM +- В `configuration.yaml` HA указана папка `/ssl` для сертификатов + +--- + +## 🛠 Пошаговая настройка + +### 1. 🔑 Настройка беспарольного SSH + +Выполните на **хосте NPM** (`192.168.1.2`): + +```bash +# 1. Генерация ключа (если отсутствует) +ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_npm_ha -N "" -C "npm-to-ha" + +# 2. Копирование публичного ключа на HA +ssh-copy-id -i ~/.ssh/id_ed25519_npm_ha.pub root@192.168.31.10 + +# 3. Фиксируем fingerprint хоста (вместо StrictHostKeyChecking=no) +ssh-keyscan -H 192.168.31.10 >> ~/.ssh/known_hosts + +# 4. Проверка подключения +ssh -i ~/.ssh/id_ed25519_npm_ha root@192.168.31.10 echo "✅ SSH OK" +``` + +> ⚠️ **Почему шаг 3 важен:** `StrictHostKeyChecking=no` отключает защиту от MITM-атак даже внутри LAN. Однократный `ssh-keyscan` безопаснее и не требует интерактивного подтверждения при каждом запуске. + +> ⚠️ **HA OS**: Если используется Home Assistant OS, убедитесь, что SSH включён через `Settings → Add-ons → SSH & Web Terminal`, а ключ добавлен в поле `Authorized Keys`. + +--- + +### 2. 📜 Создание скрипта синхронизации + +```bash +sudo mkdir -p /opt/npm/scripts +sudo nano /opt/npm/scripts/sync_ssl_to_ha.sh +``` + +Вставьте следующий код: + +```bash +#!/bin/bash + +# === КОНФИГУРАЦИЯ === +FULLCHAIN="/opt/npm/letsencrypt/live/npm-44/fullchain.pem" +PRIVKEY="/opt/npm/letsencrypt/live/npm-44/privkey.pem" +HA_USER="root" +HA_IP="192.168.31.10" +DEST_DIR="/ssl" +SSH_KEY="/root/.ssh/id_ed25519_npm_ha" +LOG="/var/log/ha_ssl_sync.log" +MAX_LOG_LINES=500 # Ротация: хранить не более N строк лога + +# Флаги для уведомлений (раскомментируйте и настройте при необходимости) +# HA_TOKEN="your_long_lived_token" +# HA_NOTIFY_URL="http://192.168.31.10:8123/api/services/notify/persistent_notification" + +SSH_OPTS="-i $SSH_KEY -o StrictHostKeyChecking=yes -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3" + +# === ФУНКЦИИ === +log() { echo "$(date '+%Y-%m-%d %H:%M:%S') | $1" >> "$LOG"; } + +rotate_log() { + local lines + lines=$(wc -l < "$LOG" 2>/dev/null || echo 0) + if [[ $lines -gt $MAX_LOG_LINES ]]; then + tail -n "$MAX_LOG_LINES" "$LOG" > "${LOG}.tmp" && mv "${LOG}.tmp" "$LOG" + fi +} + +validate_cert() { + # Проверяем, что fullchain содержит валидный PEM-сертификат и не просрочен + if ! openssl x509 -in "$FULLCHAIN" -noout -checkend 0 2>/dev/null; then + log "❌ Сертификат невалиден или уже просрочен: $FULLCHAIN" + return 1 + fi + # Проверяем соответствие ключа сертификату + local cert_mod privkey_mod + cert_mod=$(openssl x509 -noout -modulus -in "$FULLCHAIN" 2>/dev/null | md5sum) + privkey_mod=$(openssl rsa -noout -modulus -in "$PRIVKEY" 2>/dev/null | md5sum) + if [[ "$cert_mod" != "$privkey_mod" ]]; then + log "❌ Приватный ключ не соответствует сертификату!" + return 1 + fi + return 0 +} + +cert_changed() { + # Получаем серийный номер текущего сертификата на HA и сравниваем с локальным + local local_serial ha_serial + local_serial=$(openssl x509 -noout -serial -in "$FULLCHAIN" 2>/dev/null | cut -d= -f2) + ha_serial=$(ssh $SSH_OPTS "$HA_USER@$HA_IP" \ + "openssl x509 -noout -serial -in $DEST_DIR/fullchain.pem 2>/dev/null | cut -d= -f2" 2>/dev/null) + [[ "$local_serial" != "$ha_serial" ]] +} + +# notify_ha() { +# curl -s -X POST "$HA_NOTIFY_URL" \ +# -H "Authorization: Bearer $HA_TOKEN" \ +# -H "Content-Type: application/json" \ +# -d "{\"message\": \"$1\", \"title\": \"SSL Sync\"}" > /dev/null +# } + +# === ОСНОВНАЯ ЛОГИКА === +rotate_log +log "=== Старт синхронизации ===" + +# 1. Проверка исходных файлов +if [[ ! -f "$FULLCHAIN" || ! -f "$PRIVKEY" ]]; then + log "❌ Исходные сертификаты не найдены: $FULLCHAIN или $PRIVKEY" + exit 1 +fi + +# 2. Валидация сертификата перед копированием +if ! validate_cert; then + exit 1 +fi + +# 3. Проверка: изменился ли сертификат? +if ! cert_changed; then + log "✅ Сертификат не изменился — синхронизация не требуется." + exit 0 +fi + +log "🆕 Обнаружен новый сертификат, начинаем синхронизацию..." + +# 4. Копирование файлов +scp $SSH_OPTS "$FULLCHAIN" "$HA_USER@$HA_IP:$DEST_DIR/fullchain.pem" >> "$LOG" 2>&1 +SCP1=$? +scp $SSH_OPTS "$PRIVKEY" "$HA_USER@$HA_IP:$DEST_DIR/privkey.pem" >> "$LOG" 2>&1 +SCP2=$? + +# 5. Перезапуск только при успешном копировании обоих файлов +if [[ $SCP1 -eq 0 && $SCP2 -eq 0 ]]; then + log "✅ Файлы успешно скопированы" + ssh $SSH_OPTS "$HA_USER@$HA_IP" "chmod 600 $DEST_DIR/privkey.pem && ha core restart" >> "$LOG" 2>&1 + log "🔄 Команда рестарта HA отправлена" + # notify_ha "Сертификат обновлён и HA перезапущен" +else + log "❌ Ошибка копирования (fullchain=$SCP1, privkey=$SCP2). Рестарт отменён." + # notify_ha "⚠️ Ошибка синхронизации SSL — рестарт HA отменён" + exit 1 +fi +``` + +--- + +### 3. 🔐 Настройка прав и логирования + +```bash +sudo chmod +x /opt/npm/scripts/sync_ssl_to_ha.sh +sudo touch /var/log/ha_ssl_sync.log +sudo chown root:root /opt/npm/scripts/sync_ssl_to_ha.sh +sudo chmod 700 /opt/npm/scripts/sync_ssl_to_ha.sh # только root, без лишних прав +``` + +> ℹ️ `chmod 700` вместо `750` — скрипт содержит путь к SSH-ключу и должен быть доступен только владельцу. + +--- + +### 4. ⏰ Автоматизация через Cron + +```bash +sudo crontab -e +``` + +Добавьте строку: +```cron +# Ежедневно в 04:15 (через 15 мин после планового обновления Let's Encrypt в NPM) +15 4 * * * /bin/bash /opt/npm/scripts/sync_ssl_to_ha.sh +``` + +> 💡 **Почему 04:15?** NPM запускает проверку обновления сертификатов в `04:00`. Задержка в 15 минут гарантирует, что свежие файлы уже записаны на диск. + +> 💡 **Альтернатива:** если NPM поддерживает deploy-хуки (например через `post-renewal`), можно запускать скрипт напрямую по событию обновления, а не по расписанию — это надёжнее. + +--- + +### 5. ⚙️ Конфигурация Home Assistant + +В `configuration.yaml` убедитесь, что указан путь к `/ssl`: + +```yaml +http: + ssl_certificate: /ssl/fullchain.pem + ssl_key: /ssl/privkey.pem + # server_port: 443 # Раскомментируйте, если HA слушает 443 напрямую + use_x_forwarded_for: true + trusted_proxies: + - 192.168.1.2 + - 127.0.0.1 +``` + +После первого изменения перезапустите HA вручную: +```bash +ssh root@192.168.31.10 "ha core restart" +``` + +--- + +## 🧪 Тестирование + +1. **Ручной запуск:** + ```bash + sudo /opt/npm/scripts/sync_ssl_to_ha.sh + ``` + +2. **Проверка лога:** + ```bash + tail -n 20 /var/log/ha_ssl_sync.log + ``` + +3. **Проверка срока действия сертификата на HA:** + ```bash + ssh root@192.168.31.10 "openssl x509 -noout -dates -in /ssl/fullchain.pem" + ``` + +4. **Валидация в HA:** + - Откройте `https://<ваш-домен-или-ip>:443` + - Убедитесь, что браузер не ругается на сертификат + - В `Настройки → Система → Журналы` не должно быть ошибок SSL + +5. **Принудительный тест синхронизации** (временно удалить сертификат на HA): + ```bash + ssh root@192.168.31.10 "rm /ssl/fullchain.pem" + sudo /opt/npm/scripts/sync_ssl_to_ha.sh + ``` + +--- + +## 🐛 Устранение неполадок + +| Симптом | Причина | Решение | +|--------|---------|---------| +| `Permission denied` при `scp` | Ключ не добавлен или неправильные права | Проверьте `~/.ssh/authorized_keys` на HA и `chmod 600 ~/.ssh/id_ed25519_npm_ha` | +| `Host key verification failed` | Fingerprint не добавлен | Выполните `ssh-keyscan -H 192.168.31.10 >> ~/.ssh/known_hosts` на хосте NPM | +| `❌ Приватный ключ не соответствует` | Рассинхронизация файлов в NPM | Проверьте целостность файлов в `/opt/npm/letsencrypt/live/npm-44/` | +| `ha: command not found` | CLI не в PATH или не установлен | Используйте `docker restart homeassistant` или `systemctl restart home-assistant@homeassistant` | +| HA не стартует после синхронизации | Неправильные права на `privkey.pem` | Скрипт уже делает `chmod 600`. Если ошибка — проверьте, не перезаписывает ли файл Supervisor | +| Cron не запускает скрипт | Отсутствие полных путей или переменных окружения | В cron всегда указывайте `/bin/bash /полный/путь/скрипт.sh` | +| Таймаут по SSH | Нестабильный туннель Keenetic | Параметры `ConnectTimeout=10`, `ServerAliveInterval=5`, `ServerAliveCountMax=3` уже заданы в `SSH_OPTS` | +| Лог растёт бесконечно | Нет ротации | Ротация встроена в скрипт (`MAX_LOG_LINES`). Также можно добавить `logrotate` | + +--- + +## 🔒 Безопасность и рекомендации + +- ✅ Ключ SSH хранится с правами `600`, скрипт — с правами `700`. Не передавайте их третьим лицам. +- ✅ `StrictHostKeyChecking=yes` — fingerprint HA зафиксирован в `known_hosts`, MITM-атаки невозможны. +- ✅ Скрипт проверяет валидность и соответствие ключа сертификату **до** копирования. +- ✅ Перезапуск HA происходит **только** при успешном копировании обоих файлов **и** только при реальном изменении сертификата. +- ✅ Встроенная ротация лога — файл не вырастет бесконечно. +- 🔔 Для мониторинга раскомментируйте блок `notify_ha` и добавьте `HA_TOKEN` — будете получать уведомления прямо в интерфейсе HA (или настройте Telegram через `notify.telegram`). +- 🔄 Раз в квартал проверяйте актуальность ключа SSH и ротацию `authorized_keys` на HA. + +--- + +## 📦 Структура файлов + +``` +/opt/npm/scripts/ +└── sync_ssl_to_ha.sh # Основной скрипт +/root/.ssh/ +├── id_ed25519_npm_ha # Приватный SSH-ключ (chmod 600) +├── id_ed25519_npm_ha.pub # Публичный ключ +└── known_hosts # Fingerprint HA (StrictHostKeyChecking=yes) +/var/log/ +└── ha_ssl_sync.log # Лог операций (авторотация) +``` + +--- + +## 📋 Сводка изменений относительно v1.0.0 + +| Что изменилось | Почему | +|----------------|--------| +| `StrictHostKeyChecking=no` → `yes` + `ssh-keyscan` | Защита от MITM даже во внутренней сети | +| Добавлена валидация сертификата (`openssl x509 -checkend`) | Не синхронизировать просроченный или повреждённый файл | +| Добавлена проверка соответствия ключа сертификату | Исключить ситуацию рассинхронизации файлов в NPM | +| Добавлена дедупликация по серийному номеру | HA не перезапускается, если сертификат не менялся | +| Добавлена ротация лога (`MAX_LOG_LINES`) | Лог не вырастет до бесконечности | +| `chmod 750` → `chmod 700` | Скрипт с путями к ключам должен быть доступен только `root` | +| `ServerAliveCountMax=3` добавлен в `SSH_OPTS` | Надёжнее при нестабильном туннеле Keenetic | +| Добавлена заготовка `notify_ha()` | Уведомления в HA при успехе и ошибке — раскомментировать и настроить | + +--- + +📝 *Документация подготовлена для размещения в Gitea/Wiki. Версия: `1.1.0` | Обновлено: `2026-04-12`* +🔗 *При изменении архитектуры сети или пути к сертификатам обновите переменные в шапке скрипта.*