diff --git a/README.md b/README.md index 3cc2755..b14b18f 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,9 @@ letsencrypt-regru --help letsencrypt-regru --obtain -v letsencrypt-regru --check -v letsencrypt-regru --staging -v + +# Очистить lock-файлы Certbot (если процесс завис) +letsencrypt-regru --force-cleanup ``` #### ⚙️ Служебные команды (внутреннее использование) @@ -1520,6 +1523,40 @@ nslookup -type=TXT _acme-challenge.example.com dig TXT _acme-challenge.example.com ``` +### Проблема: "Another instance of Certbot is already running" + +**Причина:** Предыдущий процесс Certbot не завершился корректно или остались lock-файлы. + +**Решение:** + +```bash +# Вариант 1: Принудительная очистка lock-файлов (рекомендуется) +letsencrypt-regru --force-cleanup + +# Вариант 2: Ручная очистка +# Проверьте запущенные процессы certbot +ps aux | grep certbot + +# Остановите зависшие процессы +sudo pkill certbot +# Или принудительно +sudo pkill -9 certbot + +# Удалите lock-файлы +sudo rm -f /var/lib/letsencrypt/.certbot.lock +sudo rm -f /etc/letsencrypt/.certbot.lock + +# Попробуйте снова +letsencrypt-regru --obtain +``` + +**Вариант 3: Подождать автоматически** +Скрипт автоматически: +1. Обнаруживает запущенные процессы Certbot +2. Ждёт их завершения (60 секунд) +3. Пытается очистить lock-файлы +4. Выдаёт рекомендации по решению проблемы + ### Проблема: Certbot не установлен **Решение:** diff --git a/letsencrypt_regru_api.py b/letsencrypt_regru_api.py index 59d0aa2..626df78 100644 --- a/letsencrypt_regru_api.py +++ b/letsencrypt_regru_api.py @@ -930,6 +930,94 @@ class LetsEncryptManager: self.logger.error("Certbot не установлен!") return False + def check_certbot_running(self) -> bool: + """ + Проверка наличия запущенных процессов certbot + + Returns: + True если процесс certbot запущен + """ + try: + # Проверяем через ps + result = subprocess.run( + ["ps", "aux"], + capture_output=True, + text=True + ) + + # Ищем процессы certbot (исключая текущий grep) + certbot_processes = [ + line for line in result.stdout.split('\n') + if 'certbot' in line.lower() and 'grep' not in line.lower() + and str(os.getpid()) not in line # Исключаем текущий процесс + ] + + if certbot_processes: + self.logger.warning("Обнаружены запущенные процессы Certbot:") + for proc in certbot_processes: + self.logger.warning(f" {proc}") + return True + + return False + + except Exception as e: + self.logger.debug(f"Не удалось проверить запущенные процессы: {e}") + return False + + def cleanup_certbot_locks(self) -> bool: + """ + Очистка lock-файлов certbot + + Returns: + True если lock-файлы были удалены или их не было + """ + lock_files = [ + "/var/lib/letsencrypt/.certbot.lock", + "/etc/letsencrypt/.certbot.lock", + ] + + removed = False + for lock_file in lock_files: + if os.path.exists(lock_file): + try: + os.remove(lock_file) + self.logger.info(f"Удалён lock-файл: {lock_file}") + removed = True + except Exception as e: + self.logger.warning(f"Не удалось удалить lock-файл {lock_file}: {e}") + + if not removed: + self.logger.debug("Lock-файлы certbot не найдены") + + return True + + def wait_for_certbot(self, timeout: int = 300) -> bool: + """ + Ожидание завершения работы других процессов certbot + + Args: + timeout: Максимальное время ожидания в секундах + + Returns: + True если certbot больше не запущен + """ + self.logger.info("Ожидание завершения других процессов Certbot...") + + start_time = time.time() + check_interval = 5 # Проверяем каждые 5 секунд + + while time.time() - start_time < timeout: + if not self.check_certbot_running(): + self.logger.info("Другие процессы Certbot завершены") + return True + + elapsed = int(time.time() - start_time) + self.logger.info(f"Ожидание... ({elapsed}/{timeout} секунд)") + time.sleep(check_interval) + + self.logger.error(f"Превышено время ожидания ({timeout} секунд)") + return False + def check_certificate_expiry(self) -> Optional[int]: """ Проверка срока действия сертификата @@ -1115,6 +1203,26 @@ class LetsEncryptManager: else: self.logger.info("=== Запрос нового SSL сертификата ===") + # Проверяем, не запущен ли уже certbot + if self.check_certbot_running(): + self.logger.warning("Обнаружен запущенный процесс Certbot") + self.logger.info("Варианты решения:") + self.logger.info(" 1. Дождитесь завершения текущего процесса") + self.logger.info(" 2. Остановите процесс вручную: sudo pkill certbot") + self.logger.info(" 3. Используйте --force-cleanup для очистки lock-файлов") + + # Пытаемся подождать + if not self.wait_for_certbot(timeout=60): + self.logger.error("Не удалось дождаться завершения Certbot") + self.logger.info("Попытка очистки lock-файлов...") + self.cleanup_certbot_locks() + + # Проверяем снова + if self.check_certbot_running(): + self.logger.error("Certbot всё ещё запущен. Требуется ручное вмешательство.") + self.logger.error("Выполните: sudo pkill -9 certbot") + return False + # Формируем список доменов domains = [self.domain] if self.config.get("wildcard", False): @@ -1396,33 +1504,34 @@ def main(): ════════════════════════════════════════════════════════════════════════════════ Основные команды: - %(prog)s -c config.json --check Проверить срок действия - %(prog)s -c config.json --obtain Получить production сертификат - %(prog)s -c config.json --renew Обновить сертификат - %(prog)s -c config.json --auto Авто-режим (для cron/systemd) + letsencrypt-regru --check Проверить срок действия + letsencrypt-regru --obtain Получить production сертификат + letsencrypt-regru --renew Обновить сертификат + letsencrypt-regru --auto Авто-режим (для cron/systemd) Команды тестирования: - %(prog)s -c config.json --staging Тестовый Let's Encrypt (БЕЗ лимитов!) - %(prog)s -c config.json --test-cert Самоподписанный (локально) - %(prog)s -c config.json --test-api Проверить API reg.ru - %(prog)s -c config.json --test-dns Проверить DNS записи + letsencrypt-regru --staging Тестовый Let's Encrypt (БЕЗ лимитов!) + letsencrypt-regru --test-cert Самоподписанный (локально) + letsencrypt-regru --test-api Проверить API reg.ru + letsencrypt-regru --test-dns Проверить DNS записи Отладка: - %(prog)s -c config.json --obtain -v Подробный вывод + letsencrypt-regru --obtain -v Подробный вывод + letsencrypt-regru --force-cleanup Очистить lock-файлы Certbot ════════════════════════════════════════════════════════════════════════════════ РЕКОМЕНДУЕМЫЙ WORKFLOW ════════════════════════════════════════════════════════════════════════════════ 1. Проверка настройки: - %(prog)s -c config.json --test-api ✓ API доступен? - %(prog)s -c config.json --test-dns ✓ DNS работает? + letsencrypt-regru --test-api ✓ API доступен? + letsencrypt-regru --test-dns ✓ DNS работает? 2. Тестирование (неограниченно): - %(prog)s -c config.json --staging ✓ Полный процесс SSL + letsencrypt-regru --staging ✓ Полный процесс SSL 3. Production: - %(prog)s -c config.json --obtain ✓ Боевой сертификат + letsencrypt-regru --obtain ✓ Боевой сертификат ════════════════════════════════════════════════════════════════════════════════ СРАВНЕНИЕ РЕЖИМОВ ТЕСТИРОВАНИЯ @@ -1518,6 +1627,11 @@ def main(): help="Подробный вывод для диагностики", action="store_true" ) + parser.add_argument( + "--force-cleanup", + help="Принудительная очистка lock-файлов Certbot (если процесс завис)", + action="store_true" + ) args = parser.parse_args() @@ -1526,6 +1640,66 @@ def main(): create_sample_config(args.create_config) return 0 + # Принудительная очистка lock-файлов + if args.force_cleanup: + print("=" * 80) + print("ПРИНУДИТЕЛЬНАЯ ОЧИСТКА LOCK-ФАЙЛОВ CERTBOT") + print("=" * 80) + + lock_files = [ + "/var/lib/letsencrypt/.certbot.lock", + "/etc/letsencrypt/.certbot.lock", + ] + + # Проверяем запущенные процессы + try: + result = subprocess.run( + ["ps", "aux"], + capture_output=True, + text=True + ) + certbot_processes = [ + line for line in result.stdout.split('\n') + if 'certbot' in line.lower() and 'grep' not in line.lower() + ] + + if certbot_processes: + print("\n⚠️ ПРЕДУПРЕЖДЕНИЕ: Обнаружены запущенные процессы Certbot:") + for proc in certbot_processes: + print(f" {proc}") + print("\nРекомендуется сначала остановить процессы:") + print(" sudo pkill certbot") + print("\nПродолжить очистку lock-файлов? (y/N): ", end='') + + response = input().strip().lower() + if response != 'y': + print("Отменено.") + return 0 + except Exception as e: + print(f"Не удалось проверить процессы: {e}") + + # Удаляем lock-файлы + removed_count = 0 + for lock_file in lock_files: + if os.path.exists(lock_file): + try: + os.remove(lock_file) + print(f"✅ Удалён: {lock_file}") + removed_count += 1 + except Exception as e: + print(f"❌ Ошибка при удалении {lock_file}: {e}") + else: + print(f"ℹ️ Не найден: {lock_file}") + + print("\n" + "=" * 80) + if removed_count > 0: + print(f"✅ Удалено lock-файлов: {removed_count}") + print("Теперь можно попробовать запустить Certbot снова.") + else: + print("ℹ️ Lock-файлы не найдены.") + print("=" * 80) + return 0 + # Загрузка конфигурации config = load_config(args.config)