init commit
This commit is contained in:
447
.history/src/api/synology_20250830071505.py
Normal file
447
.history/src/api/synology_20250830071505.py
Normal 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 []
|
||||
Reference in New Issue
Block a user