init commit

This commit is contained in:
2025-08-30 10:33:46 +09:00
commit 49b3cea942
304 changed files with 116485 additions and 0 deletions

View File

@@ -0,0 +1,447 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Модуль для взаимодействия с API Synology NAS с использованием библиотеки python-synology
"""
import requests
import json
import logging
from typing import Dict, Any, Optional, List, Tuple
import socket
import struct
from time import sleep
import urllib3
from synology_dsm import SynologyDSM
from src.config.config import (
SYNOLOGY_HOST,
SYNOLOGY_PORT,
SYNOLOGY_USERNAME,
SYNOLOGY_PASSWORD,
SYNOLOGY_SECURE,
SYNOLOGY_TIMEOUT,
SYNOLOGY_MAC,
WOL_PORT
)
# Отключение предупреждений о небезопасных SSL-соединениях
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
logger = logging.getLogger(__name__)
class SynologyAPI:
"""Класс для взаимодействия с API Synology NAS с использованием python-synology"""
def __init__(self):
"""Инициализация класса SynologyAPI"""
self.protocol = "https" if SYNOLOGY_SECURE else "http"
self.dsm = None
def login(self) -> bool:
"""Авторизация в API Synology NAS используя python-synology"""
try:
# Создаем экземпляр SynologyDSM
self.dsm = SynologyDSM(
SYNOLOGY_HOST,
port=SYNOLOGY_PORT,
username=SYNOLOGY_USERNAME,
password=SYNOLOGY_PASSWORD,
secure=SYNOLOGY_SECURE,
timeout=SYNOLOGY_TIMEOUT,
verify_ssl=False
)
# Авторизация
self.dsm.login()
logger.info("Successfully logged in to Synology NAS")
return True
except Exception as e:
logger.error(f"Failed to log in to Synology NAS: {str(e)}")
self.dsm = None
return False
def logout(self) -> bool:
"""Выход из API Synology NAS"""
if not self.dsm:
return True
try:
self.dsm.logout()
self.dsm = None
logger.info("Successfully logged out from Synology NAS")
return True
except Exception as e:
logger.error(f"Error during logout: {str(e)}")
return False
def get_system_status(self) -> Optional[Dict[str, Any]]:
"""Получение расширенного статуса системы"""
if not self.dsm and not self.login():
return None
try:
result = {
"model": self.dsm.information.model,
"version": self.dsm.information.version_string,
"uptime": self.dsm.information.uptime,
"serial": self.dsm.information.serial,
"temperature": self.dsm.information.temperature,
"temperature_unit": "C",
"cpu_usage": self.dsm.utilisation.cpu_total_load,
"memory": {
"total_mb": self.dsm.utilisation.memory_size_mb,
"available_mb": self.dsm.utilisation.memory_available_real_mb,
"usage_percent": self.dsm.utilisation.memory_real_usage
},
"network": self._get_network_info(),
"volumes": self._get_volumes_info()
}
logger.info("Successfully fetched extended system status")
return result
except Exception as e:
logger.error(f"Error getting system status: {str(e)}")
return None
def _get_network_info(self) -> List[Dict[str, Any]]:
"""Получение информации о сетевых интерфейсах"""
try:
result = []
# Получение информации о сети
for device in self.dsm.network.interfaces:
net_info = {
"device": device,
"ip": self.dsm.network.get_ip(device),
"mask": self.dsm.network.get_mask(device),
"mac": self.dsm.network.get_mac(device),
"type": self.dsm.network.get_type(device),
"rx_bytes": self.dsm.network.get_rx(device),
"tx_bytes": self.dsm.network.get_tx(device)
}
result.append(net_info)
return result
except Exception as e:
logger.error(f"Error getting network info: {str(e)}")
return []
def _get_volumes_info(self) -> List[Dict[str, Any]]:
"""Получение информации о томах хранения"""
try:
result = []
# Получение информации о томах
for volume in self.dsm.storage.volumes:
vol_info = {
"name": volume,
"status": self.dsm.storage.volume_status(volume),
"device_type": self.dsm.storage.volume_device_type(volume),
"total_size": self.dsm.storage.volume_size_total(volume),
"used_size": self.dsm.storage.volume_size_used(volume),
"percent_used": self.dsm.storage.volume_percentage_used(volume)
}
result.append(vol_info)
return result
except Exception as e:
logger.error(f"Error getting volumes info: {str(e)}")
return []
def get_shared_folders(self) -> List[Dict[str, Any]]:
"""Получение списка общих папок"""
if not self.dsm and not self.login():
return []
try:
result = []
# Получение информации о общих папках
for folder in self.dsm.share.shares:
share_info = {
"name": folder,
"path": self.dsm.share.get_info(folder).get("path", ""),
"desc": self.dsm.share.get_info(folder).get("desc", "")
}
result.append(share_info)
logger.info(f"Successfully retrieved {len(result)} shared folders")
return result
except Exception as e:
logger.error(f"Error getting shared folders: {str(e)}")
return []
def get_system_info(self) -> Dict[str, Any]:
"""Получение основной информации о системе"""
if not self.dsm and not self.login():
return {}
try:
result = {
"model": self.dsm.information.model,
"serial": self.dsm.information.serial,
"version": self.dsm.information.version_string,
"uptime": self.dsm.information.uptime
}
logger.info("Successfully fetched system info")
return result
except Exception as e:
logger.error(f"Error getting system info: {str(e)}")
return {}
def shutdown_system(self) -> bool:
"""Выключение системы"""
if not self.dsm and not self.login():
return False
try:
# Используем низкоуровневый API для отправки команды выключения
endpoint = "SYNO.DSM.System"
api_path = "entry.cgi"
req_param = {"version": 1, "method": "shutdown"}
self.dsm.post(endpoint, api_path, req_param)
logger.info("Successfully initiated system shutdown")
return True
except Exception as e:
logger.error(f"Error shutting down system: {str(e)}")
return False
def reboot_system(self) -> bool:
"""Перезагрузка системы"""
if not self.dsm and not self.login():
return False
try:
# Используем низкоуровневый API для отправки команды перезагрузки
endpoint = "SYNO.DSM.System"
api_path = "entry.cgi"
req_param = {"version": 1, "method": "reboot"}
self.dsm.post(endpoint, api_path, req_param)
logger.info("Successfully initiated system reboot")
return True
except Exception as e:
logger.error(f"Error rebooting system: {str(e)}")
return False
def is_online(self) -> bool:
"""Проверка онлайн-статуса Synology NAS"""
try:
socket_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_obj.settimeout(SYNOLOGY_TIMEOUT)
result = socket_obj.connect_ex((SYNOLOGY_HOST, SYNOLOGY_PORT))
socket_obj.close()
return result == 0
except socket.error:
return False
def wake_on_lan(self) -> bool:
"""Отправка Wake-on-LAN пакета для включения Synology NAS"""
if not SYNOLOGY_MAC:
logger.error("MAC address not configured")
return False
try:
# Преобразование MAC-адреса в байты
mac_address = SYNOLOGY_MAC.replace(':', '').replace('-', '')
mac_bytes = bytes.fromhex(mac_address)
# Создание Magic Packet
magic_packet = b'\xff' * 6 + mac_bytes * 16
# Отправка пакета
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(magic_packet, (SYNOLOGY_HOST, WOL_PORT))
sock.close()
logger.info(f"Wake-on-LAN packet sent to {SYNOLOGY_MAC}")
return True
except Exception as e:
logger.error(f"Error sending Wake-on-LAN packet: {str(e)}")
return False
def wait_for_boot(self, max_attempts: int = 30, delay: int = 10) -> bool:
"""Ожидание загрузки Synology NAS"""
logger.info("Waiting for Synology NAS to boot...")
for attempt in range(max_attempts):
if self.is_online():
logger.info(f"Synology NAS is online after {attempt + 1} attempts")
# Дадим дополнительное время для полной загрузки всех сервисов
sleep(delay)
return True
sleep(delay)
logger.info(f"Waiting for boot... Attempt {attempt + 1}/{max_attempts}")
logger.error(f"Synology NAS didn't come online after {max_attempts} attempts")
return False
def power_on(self) -> bool:
"""Включение Synology NAS"""
if self.is_online():
logger.info("Synology NAS is already online")
return True
# Отправка WoL пакета
if not self.wake_on_lan():
return False
# Ожидание загрузки
return self.wait_for_boot()
def power_off(self) -> bool:
"""Выключение Synology NAS"""
if not self.is_online():
logger.info("Synology NAS is already offline")
return True
return self.shutdown_system()
def get_system_load(self) -> Dict[str, Any]:
"""Получение информации о загрузке системы"""
if not self.dsm and not self.login():
return {}
try:
result = {
"cpu_load": self.dsm.utilisation.cpu_total_load,
"memory": {
"total_mb": self.dsm.utilisation.memory_size_mb,
"available_mb": self.dsm.utilisation.memory_available_real_mb,
"cached_mb": self.dsm.utilisation.memory_cached_mb,
"usage_percent": self.dsm.utilisation.memory_real_usage
},
"network": {}
}
# Добавляем данные по сети
for device in self.dsm.network.interfaces:
result["network"][device] = {
"rx_bytes": self.dsm.network.get_rx(device),
"tx_bytes": self.dsm.network.get_tx(device)
}
logger.info("Successfully fetched system load")
return result
except Exception as e:
logger.error(f"Error getting system load: {str(e)}")
return {}
def get_storage_status(self) -> Dict[str, Any]:
"""Получение подробной информации о хранилище"""
if not self.dsm and not self.login():
return {}
try:
result = {
"volumes": self._get_volumes_info(),
"disks": self._get_disks_info(),
"total_size": 0,
"total_used": 0
}
# Суммируем общий размер и использование
for volume in result["volumes"]:
result["total_size"] += volume["total_size"]
result["total_used"] += volume["used_size"]
logger.info("Successfully fetched storage status")
return result
except Exception as e:
logger.error(f"Error getting storage status: {str(e)}")
return {}
def _get_disks_info(self) -> List[Dict[str, Any]]:
"""Получение информации о дисках"""
try:
result = []
# Получение информации о дисках
for disk in self.dsm.storage.disks:
disk_info = {
"name": disk,
"model": self.dsm.storage.disk_model(disk),
"type": self.dsm.storage.disk_type(disk),
"status": self.dsm.storage.disk_status(disk),
"temp": self.dsm.storage.disk_temp(disk)
}
result.append(disk_info)
return result
except Exception as e:
logger.error(f"Error getting disks info: {str(e)}")
return []
def get_security_status(self) -> Dict[str, bool]:
"""Получение информации о состоянии безопасности"""
if not self.dsm and not self.login():
return {"success": False}
try:
# Используем низкоуровневый API для получения информации о безопасности
endpoint = "SYNO.Core.Security.DSM"
api_path = "entry.cgi"
req_param = {"version": 1, "method": "status"}
response = self.dsm.get(endpoint, api_path, req_param)
if response and "data" in response:
return {
"success": True,
"status": response["data"].get("status", "unknown"),
"last_check": response["data"].get("last_check", None),
"is_secure": response["data"].get("is_secure", False)
}
else:
return {"success": False}
except Exception as e:
logger.error(f"Error getting security status: {str(e)}")
return {"success": False}
def get_users(self) -> List[str]:
"""Получение списка пользователей"""
if not self.dsm and not self.login():
return []
try:
users = []
# Используем низкоуровневый API для получения списка пользователей
endpoint = "SYNO.Core.User"
api_path = "entry.cgi"
req_param = {"version": 1, "method": "list", "additional": ["email"]}
response = self.dsm.get(endpoint, api_path, req_param)
if response and "data" in response and "users" in response["data"]:
for user in response["data"]["users"]:
if "name" in user:
users.append(user["name"])
logger.info(f"Successfully retrieved {len(users)} users")
return users
except Exception as e:
logger.error(f"Error getting users: {str(e)}")
return []