Обновлено форматирование даты в логах на DD.MM.YYYY для скриптов и хуков
This commit is contained in:
107
Makefile
107
Makefile
@@ -103,11 +103,33 @@ setup-dirs:
|
|||||||
# Установка зависимостей
|
# Установка зависимостей
|
||||||
install-dependencies:
|
install-dependencies:
|
||||||
@echo "$(YELLOW)→ Установка зависимостей Python...$(NC)"
|
@echo "$(YELLOW)→ Установка зависимостей Python...$(NC)"
|
||||||
@if ! command -v pip3 >/dev/null 2>&1; then \
|
@if command -v pip3 >/dev/null 2>&1; then \
|
||||||
echo "$(RED)✗ pip3 не найден. Установите python3-pip$(NC)"; \
|
pip3 install -q requests cryptography 2>/dev/null || pip3 install requests cryptography; \
|
||||||
|
elif command -v pip >/dev/null 2>&1; then \
|
||||||
|
pip install -q requests cryptography 2>/dev/null || pip install requests cryptography; \
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then \
|
||||||
|
if python3 -m pip --version >/dev/null 2>&1; then \
|
||||||
|
python3 -m pip install -q requests cryptography 2>/dev/null || python3 -m pip install requests cryptography; \
|
||||||
|
else \
|
||||||
|
echo "$(RED)✗ pip не установлен. Выполните:$(NC)"; \
|
||||||
|
echo " $(CYAN)sudo apt-get update && sudo apt-get install -y python3-pip$(NC)"; \
|
||||||
|
echo " или"; \
|
||||||
|
echo " $(CYAN)curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py$(NC)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
elif command -v python >/dev/null 2>&1; then \
|
||||||
|
if python -m pip --version >/dev/null 2>&1; then \
|
||||||
|
python -m pip install -q requests cryptography 2>/dev/null || python -m pip install requests cryptography; \
|
||||||
|
else \
|
||||||
|
echo "$(RED)✗ pip не установлен. Выполните:$(NC)"; \
|
||||||
|
echo " $(CYAN)sudo apt-get update && sudo apt-get install -y python3-pip$(NC)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo "$(RED)✗ Python не найден. Установите Python 3:$(NC)"; \
|
||||||
|
echo " $(CYAN)sudo apt-get update && sudo apt-get install -y python3 python3-pip$(NC)"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
@pip3 install -q requests cryptography 2>/dev/null || pip3 install requests cryptography
|
|
||||||
@echo "$(GREEN)✓ Зависимости установлены$(NC)"
|
@echo "$(GREEN)✓ Зависимости установлены$(NC)"
|
||||||
|
|
||||||
# Копирование скрипта
|
# Копирование скрипта
|
||||||
@@ -193,17 +215,20 @@ uninstall: check-root
|
|||||||
@echo "$(RED)║ Удаление Let's Encrypt SSL Manager ║$(NC)"
|
@echo "$(RED)║ Удаление Let's Encrypt SSL Manager ║$(NC)"
|
||||||
@echo "$(RED)╚════════════════════════════════════════════════════════════════╝$(NC)"
|
@echo "$(RED)╚════════════════════════════════════════════════════════════════╝$(NC)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@read -p "Вы уверены? Это удалит все файлы и настройки [y/N]: " -n 1 -r; \
|
@printf "Вы уверены? Это удалит все файлы и настройки [y/N]: "; \
|
||||||
echo ""; \
|
read REPLY; \
|
||||||
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
|
case "$$REPLY" in \
|
||||||
$(MAKE) remove-service; \
|
[Yy]* ) \
|
||||||
$(MAKE) remove-cron; \
|
$(MAKE) remove-service; \
|
||||||
$(MAKE) remove-files; \
|
$(MAKE) remove-cron; \
|
||||||
echo ""; \
|
$(MAKE) remove-files; \
|
||||||
echo "$(GREEN)✓ Удаление завершено$(NC)"; \
|
echo ""; \
|
||||||
else \
|
echo "$(GREEN)✓ Удаление завершено$(NC)"; \
|
||||||
echo "$(YELLOW)Удаление отменено$(NC)"; \
|
;; \
|
||||||
fi
|
* ) \
|
||||||
|
echo "$(YELLOW)Удаление отменено$(NC)"; \
|
||||||
|
;; \
|
||||||
|
esac
|
||||||
|
|
||||||
# Удаление systemd service
|
# Удаление systemd service
|
||||||
remove-service:
|
remove-service:
|
||||||
@@ -229,23 +254,29 @@ remove-files:
|
|||||||
@rm -rf $(INSTALL_DIR)
|
@rm -rf $(INSTALL_DIR)
|
||||||
@echo "$(GREEN)✓ Директория $(INSTALL_DIR) удалена$(NC)"
|
@echo "$(GREEN)✓ Директория $(INSTALL_DIR) удалена$(NC)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@read -p "Удалить конфигурацию $(CONFIG_FILE)? [y/N]: " -n 1 -r; \
|
@printf "Удалить конфигурацию $(CONFIG_FILE)? [y/N]: "; \
|
||||||
echo ""; \
|
read REPLY; \
|
||||||
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
|
case "$$REPLY" in \
|
||||||
rm -f $(CONFIG_FILE); \
|
[Yy]* ) \
|
||||||
echo "$(GREEN)✓ Конфигурация удалена$(NC)"; \
|
rm -f $(CONFIG_FILE); \
|
||||||
else \
|
echo "$(GREEN)✓ Конфигурация удалена$(NC)"; \
|
||||||
echo "$(YELLOW)Конфигурация сохранена$(NC)"; \
|
;; \
|
||||||
fi
|
* ) \
|
||||||
|
echo "$(YELLOW)Конфигурация сохранена$(NC)"; \
|
||||||
|
;; \
|
||||||
|
esac
|
||||||
@echo ""
|
@echo ""
|
||||||
@read -p "Удалить логи? [y/N]: " -n 1 -r; \
|
@printf "Удалить логи? [y/N]: "; \
|
||||||
echo ""; \
|
read REPLY; \
|
||||||
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
|
case "$$REPLY" in \
|
||||||
rm -f $(LOG_FILE) $(CRON_LOG); \
|
[Yy]* ) \
|
||||||
echo "$(GREEN)✓ Логи удалены$(NC)"; \
|
rm -f $(LOG_FILE) $(CRON_LOG); \
|
||||||
else \
|
echo "$(GREEN)✓ Логи удалены$(NC)"; \
|
||||||
echo "$(YELLOW)Логи сохранены$(NC)"; \
|
;; \
|
||||||
fi
|
* ) \
|
||||||
|
echo "$(YELLOW)Логи сохранены$(NC)"; \
|
||||||
|
;; \
|
||||||
|
esac
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# Утилиты
|
# Утилиты
|
||||||
@@ -301,12 +332,24 @@ check-config:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@if [ ! -f "$(CONFIG_FILE)" ]; then \
|
@if [ ! -f "$(CONFIG_FILE)" ]; then \
|
||||||
echo "$(RED)✗ Конфигурация не найдена: $(CONFIG_FILE)$(NC)"; \
|
echo "$(RED)✗ Конфигурация не найдена: $(CONFIG_FILE)$(NC)"; \
|
||||||
|
echo "$(YELLOW)Совет: Скопируйте config.json.example в $(CONFIG_FILE)$(NC)"; \
|
||||||
|
echo " $(CYAN)sudo cp config.json.example $(CONFIG_FILE)$(NC)"; \
|
||||||
|
echo " $(CYAN)sudo chmod 644 $(CONFIG_FILE)$(NC)"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
@echo "$(GREEN)✓ Конфигурация найдена$(NC)"
|
@echo "$(GREEN)✓ Конфигурация найдена$(NC)"
|
||||||
|
@if [ ! -r "$(CONFIG_FILE)" ]; then \
|
||||||
|
echo "$(RED)✗ Нет прав для чтения: $(CONFIG_FILE)$(NC)"; \
|
||||||
|
echo "$(YELLOW)Решение: Запустите команду с sudo:$(NC)"; \
|
||||||
|
echo " $(CYAN)sudo make check-config$(NC)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
@echo ""
|
@echo ""
|
||||||
@$(PYTHON) -c "import json; print(json.dumps(json.load(open('$(CONFIG_FILE)')), indent=2, ensure_ascii=False))" 2>/dev/null || \
|
@$(PYTHON) -c "import json; print(json.dumps(json.load(open('$(CONFIG_FILE)')), indent=2, ensure_ascii=False))" 2>&1 || \
|
||||||
(echo "$(RED)✗ Ошибка: Неверный формат JSON$(NC)"; exit 1)
|
(echo "$(RED)✗ Ошибка: Неверный формат JSON$(NC)"; \
|
||||||
|
echo "$(YELLOW)Подробности:$(NC)"; \
|
||||||
|
$(PYTHON) -c "import json; json.load(open('$(CONFIG_FILE)'))" 2>&1 | head -5; \
|
||||||
|
exit 1)
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "$(YELLOW)→ Проверка обязательных параметров:$(NC)"
|
@echo "$(YELLOW)→ Проверка обязательных параметров:$(NC)"
|
||||||
@$(PYTHON) -c "import json; c=json.load(open('$(CONFIG_FILE)')); assert c.get('regru_username'), 'regru_username не задан'" && echo " $(GREEN)✓ regru_username$(NC)" || echo " $(RED)✗ regru_username$(NC)"
|
@$(PYTHON) -c "import json; c=json.load(open('$(CONFIG_FILE)')); assert c.get('regru_username'), 'regru_username не задан'" && echo " $(GREEN)✓ regru_username$(NC)" || echo " $(RED)✗ regru_username$(NC)"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ NC='\033[0m' # No Color
|
|||||||
# Функция логирования
|
# Функция логирования
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
log() {
|
log() {
|
||||||
echo -e "${2:-$NC}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$LOG_FILE"
|
echo -e "${2:-$NC}[$(date +'%d.%m.%Y %H:%M:%S')] $1${NC}" | tee -a "$LOG_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def setup_logging(log_file: str, verbose: bool = False) -> logging.Logger:
|
|||||||
# Настройка форматирования
|
# Настройка форматирования
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
'%(asctime)s - %(levelname)s - %(message)s',
|
'%(asctime)s - %(levelname)s - %(message)s',
|
||||||
datefmt='%Y-%m-%d %H:%M:%S'
|
datefmt='%d.%m.%Y %H:%M:%S'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Создаем logger
|
# Создаем logger
|
||||||
@@ -406,6 +406,10 @@ class NginxProxyManagerAPI:
|
|||||||
"""
|
"""
|
||||||
Загрузка нового сертификата в NPM
|
Загрузка нового сертификата в NPM
|
||||||
|
|
||||||
|
ВАЖНО: NPM автоматически извлекает информацию из сертификата.
|
||||||
|
Мы загружаем сертификат через веб-интерфейс формы (multipart/form-data),
|
||||||
|
а не через JSON API, так как JSON endpoint имеет строгую валидацию схемы.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
domain: Основной домен
|
domain: Основной домен
|
||||||
cert_path: Путь к файлу сертификата
|
cert_path: Путь к файлу сертификата
|
||||||
@@ -425,29 +429,36 @@ class NginxProxyManagerAPI:
|
|||||||
with open(key_path, 'r') as f:
|
with open(key_path, 'r') as f:
|
||||||
certificate_key = f.read()
|
certificate_key = f.read()
|
||||||
|
|
||||||
# Если есть цепочка, объединяем с сертификатом
|
# Используем промежуточный сертификат если доступен
|
||||||
|
intermediate_certificate = ""
|
||||||
if chain_path and os.path.exists(chain_path):
|
if chain_path and os.path.exists(chain_path):
|
||||||
with open(chain_path, 'r') as f:
|
with open(chain_path, 'r') as f:
|
||||||
chain = f.read()
|
intermediate_certificate = f.read()
|
||||||
# NPM ожидает fullchain (cert + chain)
|
|
||||||
certificate = certificate + "\n" + chain
|
|
||||||
|
|
||||||
# Формируем payload для API
|
# NPM Web UI использует multipart/form-data для загрузки custom сертификатов
|
||||||
payload = {
|
# Эмулируем загрузку через веб-форму
|
||||||
"provider": "other",
|
files = {
|
||||||
"nice_name": f"{domain} - Let's Encrypt",
|
'certificate': ('cert.pem', certificate, 'application/x-pem-file'),
|
||||||
"domain_names": [domain],
|
'certificate_key': ('privkey.pem', certificate_key, 'application/x-pem-file'),
|
||||||
"certificate": certificate,
|
|
||||||
"certificate_key": certificate_key,
|
|
||||||
"meta": {
|
|
||||||
"letsencrypt_agree": True,
|
|
||||||
"dns_challenge": True,
|
|
||||||
"dns_provider": "reg_ru"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Добавляем промежуточный сертификат если есть
|
||||||
|
if intermediate_certificate:
|
||||||
|
files['intermediate_certificate'] = ('chain.pem', intermediate_certificate, 'application/x-pem-file')
|
||||||
|
|
||||||
|
# Дополнительные поля формы
|
||||||
|
data = {
|
||||||
|
'nice_name': domain,
|
||||||
|
'provider': 'other', # Обязательное поле: 'letsencrypt' или 'other'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.debug(f"Uploading certificate as multipart/form-data")
|
||||||
|
self.logger.debug(f"Files: {list(files.keys())}")
|
||||||
|
self.logger.debug(f"Data: {data}")
|
||||||
self.logger.info(f"Загрузка сертификата для {domain} в NPM...")
|
self.logger.info(f"Загрузка сертификата для {domain} в NPM...")
|
||||||
response = self.session.post(url, json=payload, timeout=30)
|
|
||||||
|
# Отправляем как multipart/form-data
|
||||||
|
response = self.session.post(url, files=files, data=data, timeout=30)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
result = response.json()
|
result = response.json()
|
||||||
@@ -493,20 +504,29 @@ class NginxProxyManagerAPI:
|
|||||||
with open(key_path, 'r') as f:
|
with open(key_path, 'r') as f:
|
||||||
certificate_key = f.read()
|
certificate_key = f.read()
|
||||||
|
|
||||||
# Если есть цепочка, объединяем с сертификатом
|
# Используем промежуточный сертификат если доступен
|
||||||
|
intermediate_certificate = ""
|
||||||
if chain_path and os.path.exists(chain_path):
|
if chain_path and os.path.exists(chain_path):
|
||||||
with open(chain_path, 'r') as f:
|
with open(chain_path, 'r') as f:
|
||||||
chain = f.read()
|
intermediate_certificate = f.read()
|
||||||
certificate = certificate + "\n" + chain
|
|
||||||
|
|
||||||
# Формируем payload для обновления
|
# NPM Web UI использует multipart/form-data для обновления
|
||||||
payload = {
|
files = {
|
||||||
"certificate": certificate,
|
'certificate': ('cert.pem', certificate, 'application/x-pem-file'),
|
||||||
"certificate_key": certificate_key
|
'certificate_key': ('privkey.pem', certificate_key, 'application/x-pem-file'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавляем промежуточный сертификат если есть
|
||||||
|
if intermediate_certificate:
|
||||||
|
files['intermediate_certificate'] = ('chain.pem', intermediate_certificate, 'application/x-pem-file')
|
||||||
|
|
||||||
|
# Дополнительные поля формы
|
||||||
|
data = {
|
||||||
|
'provider': 'other', # Обязательное поле
|
||||||
}
|
}
|
||||||
|
|
||||||
self.logger.info(f"Обновление сертификата ID {cert_id} в NPM...")
|
self.logger.info(f"Обновление сертификата ID {cert_id} в NPM...")
|
||||||
response = self.session.put(url, json=payload, timeout=30)
|
response = self.session.put(url, files=files, data=data, timeout=30)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
self.logger.info("Сертификат успешно обновлен в NPM")
|
self.logger.info("Сертификат успешно обновлен в NPM")
|
||||||
@@ -813,7 +833,7 @@ class LetsEncryptManager:
|
|||||||
expiry_date = cert.not_valid_after
|
expiry_date = cert.not_valid_after
|
||||||
days_left = (expiry_date - datetime.now()).days
|
days_left = (expiry_date - datetime.now()).days
|
||||||
|
|
||||||
self.logger.info(f"Сертификат истекает: {expiry_date.strftime('%Y-%m-%d')}")
|
self.logger.info(f"Сертификат истекает: {expiry_date.strftime('%d.%m.%Y %H:%M:%S')}")
|
||||||
self.logger.info(f"Осталось дней: {days_left}")
|
self.logger.info(f"Осталось дней: {days_left}")
|
||||||
|
|
||||||
return days_left
|
return days_left
|
||||||
|
|||||||
Reference in New Issue
Block a user