Увеличены значения параметров ожидания и проверки DNS в конфигурации и документации
Синхронизация Wiki / Синхронизация Wiki (push) Successful in 2s

This commit is contained in:
Dmitriy Fofanov
2026-02-25 08:49:21 +03:00
parent df2a6192f5
commit 3a3a5be790
8 changed files with 142 additions and 45 deletions
+3 -3
View File
@@ -154,9 +154,9 @@ jobs:
"email": os.environ["CFG_EMAIL"],
"cert_dir": os.environ.get("CFG_CERT_DIR") or "/etc/letsencrypt/live",
"log_file": os.environ.get("CFG_LOG_FILE") or "/var/log/letsencrypt_regru.log",
"dns_propagation_wait": to_int(os.environ.get("CFG_DNS_PROPAGATION_WAIT"), 60),
"dns_check_attempts": to_int(os.environ.get("CFG_DNS_CHECK_ATTEMPTS"), 10),
"dns_check_interval": to_int(os.environ.get("CFG_DNS_CHECK_INTERVAL"), 10),
"dns_propagation_wait": to_int(os.environ.get("CFG_DNS_PROPAGATION_WAIT"), 180),
"dns_check_attempts": to_int(os.environ.get("CFG_DNS_CHECK_ATTEMPTS"), 20),
"dns_check_interval": to_int(os.environ.get("CFG_DNS_CHECK_INTERVAL"), 15),
"renewal_days": to_int(os.environ.get("CFG_RENEWAL_DAYS"), 30),
"npm_enabled": to_bool(os.environ.get("CFG_NPM_ENABLED"), True),
"npm_host": os.environ.get("CFG_NPM_HOST") or "",
+3 -3
View File
@@ -711,9 +711,9 @@ sudo nano /etc/letsencrypt/regru_config.json
"email": "admin@example.com",
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10
"dns_propagation_wait": 180,
"dns_check_attempts": 20,
"dns_check_interval": 15
}
```
+3 -3
View File
@@ -10,9 +10,9 @@
"email": "admin@example.com",
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10,
"dns_propagation_wait": 180,
"dns_check_attempts": 20,
"dns_check_interval": 15,
"renewal_days": 30,
"npm_enabled": true,
"npm_host": "http://192.0.2.1:81",
+3 -3
View File
@@ -141,9 +141,9 @@ function Create-SampleConfig {
domain = "example.com"
wildcard = $true
email = "admin@example.com"
dns_propagation_wait = 60
dns_check_attempts = 10
dns_check_interval = 10
dns_propagation_wait = 180
dns_check_attempts = 20
dns_check_interval = 15
}
$sampleConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath
+3 -3
View File
@@ -340,9 +340,9 @@ create_config() {
"email": "${email}",
"cert_dir": "${CERT_DIR}",
"log_file": "${LOG_DIR}/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10,
"dns_propagation_wait": 180,
"dns_check_attempts": 20,
"dns_check_interval": 15,
"renewal_days": 30,
"npm_enabled": ${npm_enabled},
"npm_host": "${npm_host}",
+120 -23
View File
@@ -75,9 +75,9 @@ DEFAULT_CONFIG = {
"log_file": "/var/log/letsencrypt_regru.log",
# Параметры DNS
"dns_propagation_wait": 60, # Время ожидания распространения DNS (секунды)
"dns_check_attempts": 10, # Количество попыток проверки DNS
"dns_check_interval": 10, # Интервал между проверками DNS (секунды)
"dns_propagation_wait": 180, # Время ожидания распространения DNS (секунды)
"dns_check_attempts": 20, # Количество попыток проверки DNS
"dns_check_interval": 15, # Интервал между проверками DNS (секунды)
# Параметры обновления сертификата
"renewal_days": 30, # За сколько дней до истечения обновлять (по умолчанию 30)
@@ -259,34 +259,42 @@ class RegRuAPI:
self.logger.error(f"Ошибка HTTP запроса: {e}")
raise
def get_zone_records(self, domain: str) -> List[Dict]:
def get_zone_records(self, domain: str, retries: int = 3) -> List[Dict]:
"""
Получение DNS записей домена
Получение DNS записей домена (с повторами при пустом ответе)
Args:
domain: Доменное имя
retries: Количество повторных попыток при пустом ответе
Returns:
Список DNS записей
"""
self.logger.info(f"Получение DNS записей для домена: {domain}")
# Задержка перед запросом (защита от rate limit)
time.sleep(1)
for attempt in range(retries):
# Задержка перед запросом (защита от rate limit)
time.sleep(2 if attempt > 0 else 1)
params = {
"domain_name": domain,
}
try:
result = self._make_request("zone/get_resource_records", params)
if "answer" in result and "records" in result["answer"]:
records = result["answer"]["records"]
self.logger.info(f"Получено {len(records)} DNS записей")
return records
except Exception as e:
self.logger.warning(f"Ошибка при получении DNS записей (попытка {attempt + 1}/{retries}): {e}")
if attempt < retries - 1:
self.logger.info(f"DNS записи не получены, повтор ({attempt + 2}/{retries})...")
params = {
"domain_name": domain,
}
result = self._make_request("zone/get_resource_records", params)
if "answer" in result and "records" in result["answer"]:
records = result["answer"]["records"]
self.logger.info(f"Получено {len(records)} DNS записей")
return records
else:
self.logger.warning("DNS записи не найдены")
return []
self.logger.warning("DNS записи не найдены после всех попыток")
return []
def add_txt_record(self, domain: str, subdomain: str, txt_value: str) -> bool:
"""
@@ -1354,7 +1362,7 @@ class LetsEncryptManager:
self.logger.info("✅ TXT запись успешно добавлена в API reg.ru")
# Ждем распространения DNS
wait_time = self.config.get("dns_propagation_wait", 60)
wait_time = self.config.get("dns_propagation_wait", 120)
self.logger.info("")
self.logger.info("⏳ Ожидание распространения DNS...")
self.logger.info(f" Время ожидания: {wait_time} секунд")
@@ -1394,7 +1402,10 @@ class LetsEncryptManager:
def dns_cleanup_hook(self, validation_domain: str, validation_token: str) -> bool:
"""
Обработчик очистки DNS challenge - удаление TXT записи
Обработчик очистки DNS challenge - удаление ВСЕХ TXT записей для данного subdomain.
Удаляет ВСЕ _acme-challenge TXT записи (не только с совпадающим токеном),
чтобы не оставлять stale записей от параллельных wildcard/apex challenge'ей.
Args:
validation_domain: Домен валидации (например, example.com или *.example.com)
@@ -1414,7 +1425,36 @@ class LetsEncryptManager:
self.logger.info(f"Домен: {registrable_domain}, Поддомен: {subdomain}")
return self.api.remove_txt_record(registrable_domain, subdomain, validation_token)
# Удаляем ВСЕ TXT записи с этим subdomain (а не только совпадающие по значению)
try:
records = self.api.get_zone_records(registrable_domain)
matching = [
r for r in records
if r.get("rectype") == "TXT"
and r.get("subdomain", "").lower() == subdomain.lower()
]
if not matching:
self.logger.info("TXT записи для удаления не найдены")
return True
self.logger.info(f"Найдено {len(matching)} TXT записей для удаления")
for record in matching:
record_id = record.get("id")
record_text = record.get("text", "")[:30]
try:
self.api._make_request("zone/remove_record", {
"domain_name": registrable_domain,
"record_id": record_id,
})
self.logger.info(f"Удалена TXT запись ID={record_id} ({record_text}...)")
time.sleep(1)
except Exception as e:
self.logger.warning(f"Не удалось удалить запись {record_id}: {e}")
return True
except Exception as e:
self.logger.warning(f"Ошибка при очистке TXT записей: {e}")
# Fallback: пытаемся удалить конкретную запись по значению
return self.api.remove_txt_record(registrable_domain, subdomain, validation_token)
def _get_authoritative_nameservers(self, domain: str) -> List[str]:
"""Возвращает список authoritative NS для домена."""
@@ -1563,6 +1603,59 @@ class LetsEncryptManager:
"""
return self.verify_dns_record_external(self.domain, subdomain, expected_value)
def _clean_stale_acme_records(self, domains: List[str]) -> None:
"""
Удаляет ВСЕ stale _acme-challenge TXT записи для указанных доменов
ПЕРЕД запуском certbot. Это предотвращает ситуацию, когда старые
токены от предыдущих неудачных попыток мешают валидации.
"""
configured_domains = get_configured_domains(self.config)
# Собираем уникальные (registrable_domain, subdomain) пары
pairs_seen = set()
pairs = []
for domain in domains:
registrable_domain, subdomain_prefix = extract_registrable_domain(
domain, configured_domains
)
subdomain = f"_acme-challenge.{subdomain_prefix}" if subdomain_prefix else "_acme-challenge"
key = (registrable_domain.lower(), subdomain.lower())
if key not in pairs_seen:
pairs_seen.add(key)
pairs.append((registrable_domain, subdomain))
# Для каждого уникального registrable_domain — одна загрузка записей
zone_cache: Dict[str, List[Dict]] = {}
for registrable_domain, subdomain in pairs:
rd_lower = registrable_domain.lower()
if rd_lower not in zone_cache:
try:
zone_cache[rd_lower] = self.api.get_zone_records(registrable_domain)
except Exception as e:
self.logger.warning(f"Не удалось получить DNS записи {registrable_domain}: {e}")
zone_cache[rd_lower] = []
records = zone_cache[rd_lower]
stale_ids = [
r.get("id") for r in records
if r.get("rectype") == "TXT"
and r.get("subdomain", "").lower() == subdomain.lower()
and r.get("id")
]
if stale_ids:
self.logger.info(f"Удаление {len(stale_ids)} stale _acme-challenge TXT записей "
f"({subdomain}.{registrable_domain})")
for record_id in stale_ids:
try:
self.api._make_request("zone/remove_record", {
"domain_name": registrable_domain,
"record_id": record_id,
})
time.sleep(1)
except Exception as e:
self.logger.warning(f"Не удалось удалить запись {record_id}: {e}")
def obtain_certificate(self, staging: bool = False) -> bool:
"""
Получение нового сертификата
@@ -1617,6 +1710,10 @@ class LetsEncryptManager:
for d in domains:
domain_args.extend(["-d", d])
# Удаляем stale _acme-challenge TXT записи от предыдущих неудачных попыток
self.logger.info("Очистка stale ACME TXT записей перед запуском certbot...")
self._clean_stale_acme_records(domains)
# Создаём временные wrapper скрипты для hooks
import tempfile
+3 -3
View File
@@ -25,9 +25,9 @@
"email": "admin@example.com",
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10,
"dns_propagation_wait": 180,
"dns_check_attempts": 20,
"dns_check_interval": 15,
"renewal_days": 30,
"npm_enabled": true,
"npm_host": "http://192.0.2.1:81",
+4 -4
View File
@@ -286,9 +286,9 @@ sudo systemctl restart letsencrypt-regru.timer
"email": "admin@example.com",
"cert_dir": "/etc/letsencrypt/live",
"log_file": "/var/log/letsencrypt-regru/letsencrypt_regru.log",
"dns_propagation_wait": 60,
"dns_check_attempts": 10,
"dns_check_interval": 10,
"dns_propagation_wait": 180,
"dns_check_attempts": 20,
"dns_check_interval": 15,
"renewal_days": 30,
"npm_enabled": true,
"npm_host": "http://10.10.10.14:81",
@@ -399,7 +399,7 @@ letsencrypt-regru --obtain -v
**DNS не обновляется**
```bash
## Увеличьте время ожидания в config.json
"dns_propagation_wait": 120,
"dns_propagation_wait": 180,
"dns_check_attempts": 20
```